文系が趣味のプログラミング頑張ってみるブログ

楽しいです。基本はandroidやってます。api作るのにphp使ってたんですがrailsに乗り換えようとしてます。

ExoPlyer(ver2)デベロッパーガイド読んで訳してまとめてみた(まとめてない)

ExoPlayerという動画再生のライブラリなのですが、全く日本語の情報がなく仕事で必要になったのでまとめようと。メモ用。(ほぼガイドの翻訳まとめ)なんとなく再生するくらいは使ったけど全くちゃんと理解してないのでしっかり理解します。

ガイド訳してわからないとこはリファレンス読んだくらいなのでもう使ったことある人にはなんの意味もなさそうです。今から使うけど英語いちいち読むのかったるい人は参考にはなるかも...

動画再生の知識が足りないかもしれないので間違ってたらすいません。

 

まずMediaPlayerと大きく違うのはすべてJavaで書かれているのでNに手を出す必要がない。これは嬉しい。いまからすごい細かい動画の処理をしなきゃいけないっぽいので...というわけで頑張ってドキュメント翻訳してサンプル見て学ぼう。

 

概要

まずこの4つのクラスが重要だそうで

  • MediaSource・・・何を再生するのか決めて、メディアをロードする場所を定義してどこからロードするのか決める。Exoplayer.prepareするときにいれるそう

  • Renderer・・・メディアの内容をレンダリングする。プレイヤー作るときに入れるそう。

  • TrackSelector・・・各MedeiasourceのどれをどのRendererでつかうのかを選択する。プレイヤー作るときに入れる。

  • LoadControl・・・動画をどのくらいバッファリングするかを決める。プレイヤー作るときに入れる。

 

これらはデフォルトのも使ってもいいしRendererをカスタムしてネイティブでは使えないビデオコーデックを使うこととかもできる。

 

標準的な再生手順

  1. dependencyにexoplayerを加える
  2. SimpleExoPlayerインスタンスを作る
  3. viewにSimpleExoPlayerをアタッチする。
  4. MediaSourceをつかって再生の準備をする
  5. 終わったらリリースする。

 

2からメモ。

ExoPlayerインスタンスはExoPlayerFactoryクラスを使って作れる。このファクトリークラスで様々なカスタムレベルでのExoPlayerが作れるらしい。

デフォルトのRendererを使うExoPlayerFactory#newSimpleInstanceを使えばSimpleExoPlayerが戻ってくるそう。以下コードサンプル。

//1. Create a default TrackSelector
Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
    new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
    new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);

// 2. Create a default LoadControl
LoadControl loadControl = new DefaultLoadControl();

// 3. Create the player
SimpleExoPlayer player =
    ExoPlayerFactory.newSimpleInstance(context, trackSelector, loadControl);

BandwidthMeterってなんやねんめんどくせえと思いましたがとりあえずスルー

SimpleExoPlayerViewというクラスがあってPlaybackControllViewとレンダーされるsurfaceを一緒くたにしてくれるよみたいなこと書いてあるけど要するにvideoViewのExoPlayer版

// Bind the player to the view.
simpleExoPlayerView.setPlayer(player);

これで使えてXmlでactivityやフラグメントにおけるらしい。便利。 もちろんsetVideoSurfaceView,setVideoTextureView, setVideoSurfaceHolder, setVideoSurfaceとかつかうことでちゃんとTextureviewとかにも使えるんだそう。 setTextOutput を使えば setId3Output字幕とかID3タグ(アーティスト名とか保存してあるデータ)を取得できると。


MediaSourceの作り方(MediaPlayerのprepareの仕方)

サンプルとともにコメントで説明してある。

  • まずBandwidthmeterを設定するらしい。なんだよそれって感じ。

いらないならnullでもいいよって書いてあったからスルーしようとしたんだけど、一応調べてみたらこれこそ欲しいものだった!!

実はいまやろうとしてるのがiOSにある、ローディング速度が遅い時に再生するのを待つようにして、切れ目なく再生できるとこまでロードしたらコールバックを呼ぶ機能なんだけどこれにビットレート計算がいるからどうしようとか悩んでた。

そしたらBandwidthインターフェースはgetBitrateというmethodを持ってるらしく、このメソッドでビットレート予測が手に入るらしい!!マジナイス!!

// Measures bandwidth during playback. Can be null if not required.
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();

これでデフォルトのBandwidthmeterが使えてビットレートが取得できる。やったぜ

  • 次にDataSourceInstanceをつくる(正確にはDataSourceの中にあるファクトリーを作る
// Produces DataSource instances through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
    Util.getUserAgent(this, "yourApplicationName"), bandwidthMeter);

いやUtil.getUserAgentってなんやねんと思ったんだけども、なんか実はネットワークアクセスに自分の情報をヘッダーかなんかに入れなきゃいけないらしく、UserAgentっていう文字列を作らなきゃいけないらしい(調べたけどあやふや。バージョンとか端末情報とかっぽい) めんどくさいと思ったらUtil.getUserAgentで第一引数にContext,第二引数にアプリケーションネーム入れれば勝手に作ってくれるぽい。よかった。

  • 次にExtractorsFactoryなるものを生成
// Produces Extractor instances for parsing the media data.
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
  • そして作ったいろいろを全部ExtracterMediaSourceというクラスのコンストラクタの引数にぶち込む
// This is the MediaSource representing the media to be played.
MediaSource videoSource = new ExtractorMediaSource(mp4VideoUri,
    dataSourceFactory, extractorsFactory, null, null);

第4第5引数なんだろうとおもったんだけど ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory, int minLoadableRetryCount, Handler eventHandler, ExtractorMediaSource.EventListener eventListener) となってるらしく minLoadableRetryCountは失敗した時のリトライする数でこのクラス内にExtractorMediaSource.MIN_RETRY_COUNT_DEFAULT_FOR_MEDIAみたいに使える定数がある。 EventHandlerはおそらくTextureSurfaceとかに埋め込むときようのhandler EventListenerはその名の通りリスナー。とりあえずonLoadErrorが宣言されてた。

  • そしてそれを作ったプレイヤーに埋め込んでprepare!
// Prepare the player with the source.
player.prepare(videoSource);

これで再生できるらしい。prepareしたら再生とポーズはsetPlayWhenReady(boolean)をtrueにすると再生、falseにするとポーズ。ここちょっと違和感合ったけどまあいいでしょう。

終わったらリリースするの忘れないでだそう。ExoPlayer.releaseでできる。

MediaSourceの使い方

MediaSourceはDASH,SmoothStreaming,HLSといったストリーミングの形式、そしてまあ普通のmediafileにそれぞれあったクラスがあるよということで。

何だストリーミング形式って感じだったので調べて簡単に言うと、

  • ライブストリーミングとかは普通専用のプロトコル(通信データをどうやって送るとか決めるルール)ってのを作って通信するそうですがそれだとプラグインとかが必要になる。
  • バイルで見たいときとかもっと手軽にやりたい時はHTTPでストリーミングができるプログレッシブダウンロードを使う。
  • そしてモバイルとかで見ると通信環境とかが安定しないため通信環境にあわせて調整してくれるadaptive streamingというものを使うといい感じ。

それがdashとsmoothstreamingとhlsらしいです。この3つは細かく性能が違っているのですが作ってる会社が違うからだそうです(hlsはapple,smoothstreamingはmicrosoft。dashはしらん。ここでも仁義無き戦いが...) で、これらは + Dash=>DashMediaSource + SmoothStreaming=>SsMediaSource + HLS =>HlsMediaSource + 普通のストリーミング=> ExtractorMediaSource を使えばいいそうです。細かい引数とか実装方法はデモのPlayActivityをみてね!だそうです。 あのデモ見づらい気がするんだけど俺だけかな...

まあとりあえずMediaPlayerは結構複雑な再生機能使えるよという説明が次にあり、MergingMediaSource, LoopingMediaSource , ConcatenatingMediaSourceというクラスの説明が書いてある。

  • MergingMediaSource

字幕ファイルと動画を統合できる。

MediaSource videoSource = new ExtractorMediaSource(videoUri, ...);
MediaSource subtitleSource = new SingleSampleMediaSource(subtitleUri, ...);
// Plays the video with the sideloaded subtitle.
MergingMediaSource mergedSource =
    new MergingMediaSource(videoSource, subtitleSource);
  • LoopingMediaSource ループできる。何回ループするかの指定もできる。
MediaSource source = new ExtractorMediaSource(videoUri, ...);
// Loops the video indefinitely.
LoopingMediaSource loopingSource = new LoopingMediaSource(source);

LoopingMediaSource(MediaSource childSource,int LoopCount)だそうなので第二引数でループ何回するか決められる。

  • ConcatenatingMediaSource 複数のMediaSourceを連結して一つのビデオみたいに切れ目なく再生できるらしい。フォーマット合わせる必要もないし、なんならビデオと音声のみのオーディオをくっつけても問題ないらしい。すごいね。
MediaSource firstSource = new ExtractorMediaSource(firstVideoUri, ...);
MediaSource secondSource = new ExtractorMediaSource(secondVideoUri, ...);
// Plays the first video, then the second video.
ConcatenatingMediaSource concatenatedSource =
    new ConcatenatingMediaSource(firstSource, secondSource);
  • 上の複合も可能 例えばAとBのビデオをA,A,B,A,A,B,,,みたいにAと2回、Bを1回で無限ループ再生したい時に上のやつ組み合わせればできると
MediaSource firstSource = new ExtractorMediaSource(firstVideoUri, ...);
MediaSource secondSource = new ExtractorMediaSource(secondVideoUri, ...);
// Plays the first video twice.
LoopingMediaSource firstSourceTwice = new LoopingMediaSource(firstSource, 2);
// Plays the first video twice, then the second video.
ConcatenatingMediaSource concatenatedSource =
    new ConcatenatingMediaSource(firstSourceTwice, secondSource);
// Loops the sequence indefinitely.
LoopingMediaSource compositeSource = new LoopingMediaSource(concatenatedSource);

こっちでもできると

MediaSource firstSource = new ExtractorMediaSource(firstVideoUri, ...);
MediaSource secondSource = new ExtractorMediaSource(secondVideoUri, ...);
// Plays the first video twice, then the second video.
ConcatenatingMediaSource concatenatedSource =
    new ConcatenatingMediaSource(firstSource, firstSource, secondSource);
// Loops the sequence indefinitely.
LoopingMediaSource compositeSource = new LoopingMediaSource(concatenatedSource);

その下にめっちゃ重要なこと書いてある

It is important to avoid using the same MediaSource instance multiple times in a composition, unless explicitly allowed according to the documentation. The use of firstSource twice in the example above is one such case, since the Javadoc for ConcatenatingMediaSource explicitly states that duplicate entries are allowed. In general, however, the graph of objects formed by a composition should be a tree. Using multiple equivalent MediaSource instances in a composition is allowed.

ドキュメントに明示的に書かれていないかぎり同じメディアソースを複数回使うのは避けることだそうです。コンポジションはツリー状になってる必要があるので、基本的には複数の入れ物に同一のmediasourceは入っちゃいけないらしい。

イベントリスナー

ExoPlayerはExoPlayer.EventListenerインスタンスaddListenerremoveListenerによってセットできると

例えばSimpleExoPlayer#VideoListenerならonVideoSizeChangedというメソッドでレンダリングされてるビデオのアスペクト比とか手に入る。もっと低いレベルのリスナーもかくコンポーネントにあるでと言ってる。Bandwidthmeasureは低いレベルのリスナーに入るのかな

SendMessages

Sendmessagesを使うことで再生中にビデオの形状を変えたりもできると。Handlerをうまく使うんだろう(めんどくさそうだからやりたくないなぁ)

カスタマイズ

既存のMediaPlayerとくらべて一番いいのはそのカスタマイズの多様さ。Javaで書かれているためそれぞれのタスクをカスタマイズすることが可能。これはほんといいよね。

カスタムの例
  • Rendererをカスタムすればサポートされていないメディアタイプも再生できるよ(どんなタイプだろう)
  • Extractorをカスタマイズすればサポートされてないコンテナ(mp4とかそういうやつだって)もサポートできるよ。
  • `MediaSource改造すればrendererにカスタムされた方法でメディア渡すことできるよとできるよ。あと上みたいに再生の内容改造できるよ。
  • `TrackSelectorをカスタマイズすればrendererがメディアソースからどのメディアを再生するかを決める方法をかえられるよ(mediasourceで決めたメディアをスキップしたりできるってことかな)
  • DataSource改造して独自プロトコルとか別のストリーミング形式使えるよ(独自プロトコルってとてもめんどくさそうだからやりたくない)

カスタムコンポーネントにリスナーとか付けたいならexoplayerと同じ奴にするといいよってこととカスタムコンポーネントはExoPlayerComponentってクラスインプリメントしてhandleMessagesメソッドを実装するといいってさ

最後に書かれてるのデジタルライトマネージメントなんだけど・・・使わないしあんま興味もないから使うときに読もっと笑

よっしゃとりあえずガイド翻訳終了ー!!!なげー!! 全く同じのをキータにも投稿してみた。キータ初投稿だー怖い。