ExoPlayerとの戦い
最近ずっとAndroidのExoPlayerと戦っていた。ほんとに戦いだった。解決の糸口が見えてきたので書こうと思う。
まずわかったのはiOSと比べるとandroidのメディアをうまく使うのは遥かに難しいってこと。iOSでできることを再現するのがほんとに難しい。iphoneはipodから来てるからなのかもしれないがとにかくめんどくさい。native使えたらもっとなんとかなるのかもしれないけどまだ勉強してないんです...でもnative使えたらexoplayerの必要ないよな。
さて、まず何をしたかったかというと、主画面は普通にずっと再生されていて、副画面のような小さい画面がありそこには決められたタイミングで主画面の数秒先を流す。2秒ほど再生したらviewを消して次の決められたタイミングにシークして(飛ばして)また決められたタイミングで再生というものであった。
iOSでは普通に動いていた。まあシステムは簡単だしぱぱっと作ってやるかと思い作ったわけだ。そして動かすと...
まったく動かない。理由は明白だった。シークが遅すぎるのだ。シークして待機させておくつもりがシークが遅すぎて次の画面に間に合わない。なんだこれ。
これがシーク問題との戦いの始まりだった。
対策その1:とりあえずプレイヤーを増やしてみる
まずデフォルトのシークはもちろん間に合わないので、あんまり良くないんだけど副画面に使われてるexoplayerを予め2つとか3つとかにしておく。すると他のが流れている間にシークできる。
iOSもこの方法使ってたからこれで行こうとした。
しかし、それでも遅い。シークが間に合わない。問題はネットワークのスピードが遅いと8秒くらいシークに時間が掛かるとかが起こりうることだった。いや、かかりすぎだろ。まあ仕方ないけどさ。
実はiOSはこれを解消する方法として、ネットワークのスピードを計算して遅い場合プレイヤーをスタートしないで待つ、そして十分バッファできたらスタートするってシステムを採用していた。
これで解決するやんと思って同じシステムを作ってみようとしたんだけど、その前によく考えるとExoplayerはネットワークスピードが遅い時prepareにかかる時間が結構ある。で、ちゃんと見てみたらそもそも決められたバッファまで待つシステムになってるらしい。作る必要ないじゃん。
あれ、まてよじゃあなんでシークこんなにかかるんだ?シークするとはいっても数秒なのでバッファされている範囲内でのシークくらいしかしてない。なのになんだこれは。
そこでバッファがどうなってるか調べてみた。そして気づいた。シークのたびにExoplayerはバッファを消してその時点かそれよりちょっと前のところからバッファリングしなおしてることに。
いやいや...なんでよ...決められた分くらいのバッファは保存しとけばよくね...?
何でバッファをキープできないんだ。いやきっとそういう設定があるんだ。探そう。
ということで必死に探した。そして見つけた。githubで製作者が「シークした時のバッファーのキープはできないんだよね〜仕様上」って言ってるのを。
(ちなみに今はその問題をキャッシュを使って解決する方法で議論が進んでるぽいです。なぜもう少し早くしてくれなかった...)
まあつー訳でできないらしいんですよ。おかげでシークがものすごい遅くなるんすよ。糞がぁ。
でも嘆いてても仕方ないので解決策を考えた。いろいろやった。毎回サムネイルとってきてviewに貼り付けて止まったように見せるとかいう荒業もやってみた。サムネイルをとる秒数は10秒毎と決まっているらしく全然できなかった。
そうこうしているうちに一つの解決策を思いついた。
対策その2:ファイルをローカルにダウンロードする
仕組みは簡単。
1・動画ファイルをローカルにバックグラウンドでダウンロードする。
2・ダウンロードしながらダウンロード完了した部分は再生する。ダウンロードが追いつかなかったらしばらく止めてダウンロードを待つ。
・・・多分あんま良くない。動画ファイルが時間にそってダウンロードされる保証ないし。動画ストリーミングに詳しくないからなんとも言えない。
でもね、結果的にこれで解決したんすよ。今回の場合は解決したのでセーフ。
やり方は、まず動画を始める前にIntentServiceを起動、download managerを使ってダウンロード開始。
30%までダウンロードしたらそのローカルファイルを参照してexoplayerのMediaSourceを作る。そしてprepare。
もしダウンロードが途中で止まっちゃったりしたら再生を止める(ここが実は厄介)
この方法を使ったらね、すごいの。exoplayerのシークスピードが尋常じゃなかったし、何よりprepareがはえー笑
期待した通りのスピードがでました。しかもネットワークスピードに依存しないので安定してる。
まあローカルから再生してるのと同じだからそりゃそうなんだけどね。
まあもちろんダウンロードの時間も入れたらprepareは結構かかってるし、何よりダウンロードが完了してないとこにシークはできない。かなり使い方は限定されているきもする。普通には使えないな。
でも普通では無い状況だったら必要になる人がいるかもということで書いてみた。
この方法が使えるのは
- ユーザーが自由に動画をシークしたりすることがない(この時点でほぼだめだろ)
- こちらの指定された時間に結構早く、頻繁にシークしなきゃいけない。(けどダウンロードが完了してるとこまでしか時間指定はできない。)
という2つを満たした状況だけ。えぇ...いらな...悲し...
簡単にいうとニコ動の一般会員の機能しか使えね。やっぱ全く使い物にならんな。具体的な使い方あったら誰かおしえてレベル。スピードは早いよ!
さてでもとりあえずこの使い方の注意する点とポイント書いとく
ポイント1・Exoplayerはダウンロード途中の動画でも再生できる。
これ地味に面白い気もする。まあ必要な分抽出して再生するシステムなんだからそりゃそうなのかもしれないけど。おかげでダウンロードしながら再生って言う方法が取れた。
ポイント2・Exoplayerはバッファリングをコントロールすることができない。
これかなり厄介。なにが問題かというと突然圏外になった時とかの対処がやっかい。
再生はダウンロード途中の部分に辿り着く前に止めておけばいいから問題ないんだけど、バッファリングを止められない。結果、バッファリングがかってにダウンロードしてない部分までバッファリングしようとすることでエラーを起こして再生してくれなくなる。これほんとどうしようもない。
さてじゃあどうすんのって話なんだが、強行突破しました。マジダメな気がするけど解決したんだからいいじゃん。許して。
簡単に言うと、バッファリングがエラーはいた瞬間にもっかいprepareすればいい。prepareには第二引数にリセットポジションってのがあったからそれfalseにしとくと最初からにならない?っぽい。
exoplayerにはonPlayerErrorっていうコールバックメソッドがあるので、そこにもっかいprepareするコードを入れとくとですね。完璧に動いた。
これによってたとえバッファリングがかってにエラー起こしてもexoplayerを更新することで問題がなくなる。
ローカルにおいてはprepareのスピードがめっちゃ早いからできる芸当。いやあまじちゃんとした人がみたら怒られそう...
以上、exoplayerと戦った成果報告でした。
でもこれ実際いまissueでやり取りしてるキャッシュで解決する手法とやってることは一緒だよな。俺のはダウンロードしてる場所がかならず最初からなせいでシークできないから実際には全然違うけど。