プロダクト導入時に足りない機能やできないこと

川上健太郎氏:今まで紹介したとおり、けっこう便利なんですが、当然足りない機能やできないこともあります。実際にプロダクトに導入するにあたって、足りなさそうな機能やできないことをリストアップしてみました。

まず画質設定です。これは単純に実装されていないので、ビットレートを設定できないです。iOSで言うと、AVPlayerItemのpreferredPeakBitRateにあたります。また字幕、副音声ですね。先ほど説明した字幕はローカルから読み込むんですが、ストリームから受け取って表示する機能はないので、これも実装する必要があります。

試しに初めてAndroidをやってみました。iOSはわりとサクッとできるんですが、AndroidはiOSに比べるとちょっとコード量を書かないといけません。自分でプラグインを開発するのはけっこう大変そうだという印象を受けました。副音声も同様で、自分で実装が必要です。

これは当然と言えば当然なんですが、UIも自分で実装する必要があります。プレーヤーを実装するうえでは、それほど凝ったUIはないと思うんですが、AVPlayer、ExoPlayerのデフォルトのUIは使えないので、自分でウィジェットをレイアウトする必要があります。

次は、DRM(Digital Rights Management)です。イシューを見てもらうとわかるんですが、video_playerはPlatform ViewじゃなくてTexture Widgetを使っているので、DRMは使えないと思います。

次はCookieで、iOSの話です。AVURLAsset時にCookieを渡したい場合が要件としてあったりするんですが、その場合、通常ではpotions: [AVURLAssetHTTPCookiesKey]をオプションに設定するとCookieが設定できます。

Flutterもこれができるのかなと思ったら、そうではありませんでした。けっこう議論を生んでいるようで、イシューがよく出てクローズしています。PR(Pull Request)もバラバラと出ているんですが、出して満足する人がいたり、レビューが放置されたりで、いまだにマージされていません。

React Nativeのvideo_player版もあって、そこでも同じような議論がありました。React Nativeはマージすることを決めたみたいで、同じような機能が2020年にマージされています。

最後にエラーハンドリングです。括弧を付けているんですが、できなくもないけど、ちょっとどうなのかなといった意味合いです。まずVideoPlayerControllerをメチャクチャ大雑把に説明すると、ValueNotifierになっていて、VideoPlayerValueになっています。

VideoPlayerValueはプレーヤーの状態を管理しているオブジェクトで、errorDescriptionというプロパティが生えています。ErrorTypeやErrorCodeなど、よくある列挙型は特に定義されていません。

どんな感じになっているのか、実際に投げられている各プラットフォーム側のコードをいくつか抜粋しました。上はiOSで、FlutterErrorというオブジェクトを生成して、errorWithCodeとmessage details。Androidも似たような感じなんですが、errorDescriptionというプロパティに入るのがmessageの中のもので、iOSだったらVideo cannot be fast-forwarded beyond 2.0x、AndroidだったらVideo player had errorとなっています。Descriptionなのでどうなんだろうという感じです。

さらに各プラットフォームで、プレーヤーからエラーを受け取っている部分を見ていきたいと思います。まずiOSの部分です。これはObjective-Cなんですが、AVPlayerItem.statusをオブザーブしているさらにその中を抜粋しました。

StatusがFailedになったときに単純にエラーを投げています。エラーを見るとしたら、NotificationCenterのAVPlayerItemNewlogEntryなどを見ると思うんですが、そのあたりは特になにもしていません。

続いて、Androidです。AndoroidもExoPlayのonPlayerErrorが来たときに投げているだけで、メッセージもAndroidではVideo player had errorで、iOSではFailedToLoadVideoなのでちょっと違います。

まとめです。できないわけではなく、無理矢理やろうと思えばできると思うんですが、エラーハンドリングに適したかたちにはなっていないという印象です。

ただエラー以外に、stateの状態にisBufferingやBufferの範囲を示すBufferedプロパティなどがあるので、これを見れば十分な場合もあるのかなと思っています。けっこう細かくハンドリングしたければ、自分でやるしかないと思います。

「video_player」の基本的な仕組み

次に実装を軽く追ってみたいと思います。基本的な仕組みは、BasicMessageChannelとEventChannelの組み合わせです。BasicMessageChannelは、プラットフォームとDart間で双方向にメッセージを送受信するためのAPIです。EventChannelはプラットフォームからDart側にイベントを通知するためのAPIです。

video_playerはFlutterプラグインで、確か唯一BasicMessageChannelを使っているプラグインです。よく似たプラグインとして、カメラプラグインみたいなものが取り上げられるんですが、カメラプラグインはMethodChannelしか使っていなかったはずです。

EventChannelとBasicMessageChannel

EventChannelがどんなふうに使われているかというと、プラットフォームのそれぞれのAVPlayerとExoPlayerで発生したイベントを、video_player_interfaceに通知して、video_playerはStreamでオブザーブします。

ちょっと説明を忘れたんですが、Flutterパッケージのほとんどは本体とホニャララプラットフォームインターフェイスに分かれていて、ホニャララプラットフォームインターフェイスが橋渡し的な役割を担っています。

実際にMessageChannelでやりとりしているのは、ホニャララプラットフォームインターフェイスで、video_playerにそれをインジェクトして、DI(Dependency Injection)して叩くっていう感じです。

実際のEventChannelの内容はこれしかなくて、初期化したよとか、再生完了したよとか、bufferingとか命名どおりです。

次は、BasicMessageChannelの例です。video_player側が、現在の再生位置を欲しいとインターフェイス側に投げます。そうするとインターフェイスがプラットフォーム側に「現在の再生位置をくれ」とメッセージを送ります。するとプラットフォーム側が、プラットフォーム内で処理してreplayを返すので、それをFutureで返します。

実際の関数内がどんな感じなのか、実際のコードを見ていきます。あとで説明するんですが、これは自動生成されたものなのでけっこうゴチャっとしています。

1行目に出てきているTextureMessageの中身を軽く説明します。これが実際にやりとりする内容が入っているプロパティで、textureIdが受け渡ししたいものです。encodeとdecodeはそのまま、encodeとdecodeするためのもので、どのmessageにも絶対入っています。

まずChannelを生成します。このChannelはAPIごとに作成されて、だいたい末尾のドットポジションが、.create.initialize.disposeなどに変わります。それを送って、thinkawaitで待ちます。

エラーハンドリングをして、ここでエラーが入ってきたらエラーを投げて、返したいものにdecodeをして返します。

(次回へつづく)