Fire TVにおけるマルチメディアアプリの要件


Fire TVにおけるマルチメディアアプリの要件

Fire TVのアプリ同士がスムーズに連携するには、すべてのアプリが同じマルチメディア要件を遵守する必要があります。同要件には、アクティビティライフサイクルやオーディオフォーカスのイベント、MediaSession、デコーダーインスタンス、WakeLockなどの適切な処理方法が含まれています。ここに記載されている要件を遵守することにより、Fire TVのアプリ間を移動する際のユーザーエクスペリエンスを向上させることができます。

Fire TVでのアプリ間の連携

Fire TVでは、通常アプリはマルチメディアの再生を主な目的としています。そのため、Fire TV全体のアプリ間の連携が必要です。

多くの点で、TVデバイス対応アプリは、一般的なAndroidアプリよりも複雑になりがちです。[ホーム] ボタンや音声操作機能が備わっているため、ユーザーがそれを利用すれば、即座にアプリの再生が中断されることがあります。タブレットアプリやスマートフォンアプリとは異なり、Fire TVでは、ステータスバーと通知バーがアプリと連動しません。うまく動かないアプリの停止や終了の操作も、TVデバイスではより複雑です。音楽を再生しているアプリを終了したのに、映画を観始めても音楽が再生され続けたという経験があればおわかりでしょうが、複数のマルチメディアが同時に再生されるとひどい状態になります。

Fire TVでアプリがスムーズに連携するには、すべてのアプリが同じ基準に従ってマルチメディアを扱う必要があります。一般的には、Android開発者ページで推奨されているガイドラインに従います。ただし、TVデバイスではアプリ同士が複雑に作用し合うことから、AmazonではAndroid開発者向けドキュメントに記載されている多くの推奨事項を、提案としてではなく要件として扱っています。Fire TVの要件の方が厳しい場合は、その旨が要件の詳細に記されています。

Androidについての予備知識

この記事の内容は、Androidの開発について知識があることを前提としています。ここでの基本概念への理解を深めるために、Androidドキュメントの以下のトピックを参照してください。

ここに挙げるすべてのAndroid APIをFire OS 5(APIレベル22までサポート)で使用できるわけではないことに注意してください。一部のAPIは、Fire OS 6がサポートするAndroid APIレベル25(Nougat)専用です。要件の内容が、アプリがサポートしていないAPIレベルに及んでいる場合は、アプリのAPIレベルで提供されている実装のガイドラインに従ってください。

アプリの種類とアクティビティベース/サービスベースの区別に応じた要件

要件は、アプリの種類や、アプリがメディアをアクティビティとして再生するかサービスとして再生するかによって異なります。マルチメディアアプリは、以下のように分類されます。

  • オーディオのみを再生するアプリ(音楽アプリなど)
  • オーディオとビデオを再生するアプリ(テレビ局アプリなど)
  • ビデオのみを再生するアプリ(好みの音楽アプリをバックグラウンドで再生しながらプレイできるゲームなど)

多くの場合、こうしたアプリの種類に応じて要件が異なります。

また、アプリはアクティビティまたはサービスのどちらかとしてメディアを再生します。ビデオアプリは、レンダリング用のウィンドウが必要なため、通常アクティビティとして再生する方法を使用します。バックグラウンド(ほかのアクティビティによって見えない状態)で再生するオーディオのみのアプリは、中断を避けるためにフォアグラウンドのサービスを使用する必要があります。どちらの場合も、オーディオ出力の前にオーディオフォーカスの管理が必要です。上記のアプリの種類ごとの要件については、以下のセクションで説明します。

アクティビティベースのアプリの要件

マルチメディアアプリが(サービスではなく)アクティビティとして再生する場合、アクティビティライフサイクルイベントを慎重に処理する必要があります。Androidのアクティビティライフサイクルに記載されている可能な遷移すべてを認識しておいてください。各遷移の処理は、Fire TVアプリの要件です。

要件1.1: onPause()の処理

onPause()は、アクティビティが部分的にほかのアクティビティによって見えない状態(ユーザーが [ホーム] ボタンを長押してヘッドアップ通知が表示されているなど)のときに呼び出されます。onPause()が呼び出された後でもアクティビティは再生を継続できますが、オーディオフォーカスの変更にはコールバックを実行する必要があります(ライフサイクルイベントに関係なく、オーディオフォーカスを失うとオーディオを再生できません)。

要件1.2: onResume()の処理

onResume()は、onCreate()が呼び出されることなく元の再生状態を回復させる(つまり、onPause()での変更を元に戻す)必要があります。ここで言う「再生状態」とは、アプリに依存するさまざまなことを意味します。一般的には、自動的に(オーディオフォーカスもある場合)元の音量を使用し、元のUIに最新の進行状況バーを表示して、再生を継続します。再生に使用されるリソースを解放しなければならない場合は、パイプラインを再度初期化し、再生が中断される直前の位置にシークバックする必要があるかもしれません。

要件1.3: onStop()のリソースを解放する

onStop()が呼び出されたら、再生に使用されるすべてのリソース(AudioTrackMediaCodecなど)を解放する必要があります。ユーザーが [ホーム] ボタンを押した後にアプリに戻ったら、アプリはすべてのリソースを再取得する必要があります。

再生に別のアクティビティを使用する場合は、注意してください。アプリに即座に戻ると、ライフサイクルイベントはonStop()onRestart()onStart()onResume()という順序になります。onStop()でリソースを解放する場合、finish()を呼び出すことで再生アクティビティを終了するという方法があります。または、ライフサイクルイベント(onRestart()onStart()onResume())に従ってすべての再生リソースを再取得することもできます。

要件1.4: onDestroy()の使用を避ける

リソースを解放するにあたり、onDestroy()が呼び出されることを当てにしてはいけません。リソースはonStop()で既に解放されている必要があります。onDestroy()が呼び出されるとしても、そのタイミングは、デバイスのオペレーティングシステムの判断によります。ほかのアプリにすぐに切り替えると、呼び出されることはほとんどありません。

要件1.5: オーディオフォーカスがアクティビティライフサイクルから独立している

アクティビティライフサイクルの特定の状態で、特定のオーディオフォーカス変更イベントが受け取られることを当てにしてはいけません。多くの場合、ライフサイクルとオーディオフォーカスの変更イベントは一緒に受け取られます(アクティビティが音声操作によって中断されたときなど)。この場合、AUDIOFOCUS_LOSSonPause()アクティビティライフサイクルイベントの両方を受け取りますが、これらのイベントは互いに独立しているため、イベントの順序はいつでも変更できます。

サービスベースのアプリの要件

アプリがオーディオのみを再生する場合は、オーディオアプリの構築に記載されているフォアグラウンドサービスを使用して再生を実装することをお勧めします。この方法なら、ユーザーがほかのアクティビティを閲覧しているときにバックグラウンドでオーディオを再生できます。

要件2.1: サービスの開始

startForeground()メソッドを使用してフォアグラウンドサービスとしてサービスを開始し、メモリ不足の状態でも中断されないようにします。

要件2.2: サービスの停止

再生を終了したら、Service.stopForeground()を使用して、フォアグラウンドからサービスを削除します。このメソッドでは、追加料金が必要になるとサービスが停止することが示されます。

Media Sessionの要件

メディアアプリは、メディアセッションイベントを、その発生源に関係なく処理する必要があります。代表的なイベント発生源を以下に挙げます。

  • Fire TVデバイスのリモコン

  • [次へ]/[前へ] ボタンのあるBluetoothヘッドセット

  • マルチメディアボタンのある付属のキーボード、およびマウス

  • スマートウォッチ

  • ユーザーのスマートフォンで実行中のリモートアプリ

  • 音声操作(ユーザーが「アレクサ、一時停止して」と言うなど)に対応中のAmazonサービス

アクティビティライフサイクルの状態やオーディオフォーカスイベントに関係なく、こうしたイベントすべてを例外なく処理するには、アプリはMedia Sessionの利用のガイドラインに従う必要があります。特に、以下の点には注意してください。

「メディアセッションは、管理するプレーヤーと共生しています。メディアセッションと関連プレーヤーを所有するアクティビティまたはサービスのonCreate()メソッドでメディアセッションを作成および初期化する必要があります。」

メディアセッションイベントの処理の要件は以下のとおりです。

要件3.1: 配信コントロールコールバックの許可

MediaSessionを初期化するときに、転送コントローラーとメディアボタンからのコールバックを許可するようフラグを設定します(注意: APIレベル26については、すべてのセッションが配信コントロールを処理する見込みです)。

要件3.2: MediaSessionの発行

再生を開始する前にsetActiveを呼び出すことでメディアセッションを発行します。

要件3.3: 再生状態の更新

PlaybackStateを最新の状態に保ちます。

  • 有効なコントローラーのアクションのリストを維持します。たとえば、「一時停止」は再生中にのみ可能ですし、一部のアプリはコマーシャル中の早送りを制限します。

  • プレーヤーの位置を更新し、進行状況バーが正しく更新されるようにします。注意: 再生位置を更新するためだけに状態をリフレッシュすることを避けるには、4つのパラメーター(updateTimeを含む)を持つPlaybackStateCompat.Builder.setState()を使用するといいでしょう。

  • アーティスト名、トラックタイトル、トラック再生時間などのメタデータ情報が変わったら更新します。すべてのUI表示は更新される必要があります(スマートフォンのリモートアプリも)。

要件3.4: MediaSessionの解放

再生が終了したら、MediaSession.release() APIを使用してメディアセッションを解放します。

要件3.5: MediaSessionの期間

MediaSessionが有効な期間は、アプリが再生できる期間と完全に一致する必要があります。たとえば、バックグラウンドで音楽を再生する場合、バックグラウンドのままメディアボタンを処理する必要があります。オーディオフォーカスを一時的に失っているときに再生を一時停止する場合は、メディアセッションを無効化してメディアボタンの処理を中断する必要もあります。

オーディオフォーカスイベントの要件

オーディオフォーカスを処理すると、アプリがバランスよくオーディオを再生します。オーディオフォーカスを管理するには次のように説明されています。

「すべての音楽アプリが同時に再生することを避けるために、Androidではオーディオフォーカスという考え方を採用しています。オーディオフォーカスを保持できるアプリは一度に1つだけです。」

Fire TVでは、どの種類のオーディオを再生するアプリも、再生が開始する前にオーディオフォーカスを取得する必要があります。オーディオフォーカスの要求、オーディオフォーカスの変更の処理、オーディオフォーカスの中止に関しては、さまざまな要件があります。

要件4.1: 再生直前にオーディオフォーカスを要求する

アプリは、AudioManager.requestAudioFocus()をオーディオ再生の直前に呼び出す必要があります(多くの開発者がこの要件の直前という部分を見落としています)。 requestAudioFocus()abandonAudioFocus()の2つのオーディオフォーカス呼び出しの間隔は、フォーカス変更コールバックを処理する時間と完全に一致する必要があります。

よくある不適切な実装のシナリオ例を以下に示します。

  1. アプリ内の利用可能な各種コンテンツを一覧表示する画面で、ユーザーが [再生] をクリックする。
  2. 再生が許可されていることを確認するために、アプリがオーディオフォーカスを要求する。
  3. 「バッファー中」の画面を数秒間表示しているコンテンツのダウンロードをアプリが開始する。
  4. アプリがアクティビティに切り替え、オーディオフォーカスの変更を処理して実際に再生する。

実際の再生が開始する前に一時的に「バッファー中」画面が表示されている間(手順3)に、アプリがオーディオフォーカスを失うことも考えられます。手順2でオーディオフォーカスを要求した場合、それ以降継続的にAudioFocusイベントを処理する必要があります。ユーザーが手順4(AudioFocusイベントを処理する再生アクティビティ)に到達するまでに、アプリは再生の権限を失っているかもしれません(手順1で [再生] をクリックした後すぐにユーザーが音声操作を開始した場合など)。

要件4.2: フォーカスが付与されていることを検証する

アプリは、requestAudioFocus()の呼び出しがAUDIOFOCUS_REQUEST_GRANTEDを返すことを検証する必要があります。ほかの値が返されたら、アプリが再生の開始を許可されていないということになります。注意: APIレベル26では、AUDIOFOCUS_REQUEST_DELAYEDが採用されていますが、現在のところFire TVデバイス(APIレベル25)ではこの値は返されません。

要件4.3: オーディオフォーカスの中止

すべての再生セッションは、再生終了直後にオーディオフォーカスを中止する必要があります。アプリに複数の再生セッションがある場合(番組の次のエピソードの再生を新しいアクティビティで開始するなど)、2回目のオーディオフォーカスの要求は必要ありません。要求すると、最初のセッションがパラメーターAUDIOFOCUS_LOSSを伴うコールバックを受け取り、それに応じて、最初のセッションはそのリソース(2回目のセッションと共有されていないもの)を解放し、オーディオフォーカスを中止します。その結果、アプリがAudioFocusスタックに存在するのは常に1回だけになります(コマンドラインでadb shell dumpsys audioを手動で実行すれば、これを確認できます)。

オーディオフォーカスの変更の要件

オーディオフォーカスを要求すると、アプリはフォーカスの実装における変更 [AudioManager.OnAudioFocusChangeListener](https://developer.android.com/reference/android/media/AudioManager.OnAudioFocusChangeListener.html)も処理し、すべてのコールバックパラメーターを正しく処理する必要があります。コールバックパラメーターは、次のいずれかです。

  • AUDIOFOCUS_LOSS

  • AUDIOFOCUS_LOSS_TRANSIENT

  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

  • AUDIOFOCUS_GAIN

要件4.4: AUDIOFOCUS_LOSSの処理

アプリがAUDIOFOCUS_LOSSイベントを受け取ると、システムが再生の権利すべてをほかのアプリに付与済みであるため、アプリはオーディオの再生を完全に中止する必要があります。オーディオフォーカスがアプリに返されることは期待できません(これ以降AUDIOFOCUS_GAINを取得することはありません)。アプリを終了し、リソース(AudioTrackMediaCodecなど)を解放して、AudioManager.abandonAudioFocus()の呼び出しによって再生を終了したことをシステムに通知してください。

要件4.5: AUDIOFOCUS_LOSS_TRANSIENTの処理

アプリは、AUDIOFOCUS_LOSS_TRANSIENTイベントを受け取ると、オーディオの再生を「一時停止」する必要があります(ビデオについては厳しい要件はありません)。通常これは、短い通知やシステムサウンド、そのほかの一時的な状態によってアプリが中断されるときに発生し、オーディオフォーカスがすぐにアプリに返されます。

オーディオの一時停止は、これ以降オーディオを出力できないことを意味します。アプリは、ミュートする必要があります。再生全体を一時停止するか、オーディオの音量を0に下げてビデオの再生を続けることができます。オンデマンドコンテンツを再生中のビデオアプリは、ビデオとオーディオの両方の再生を完全に一時停止することを選択する必要があります。再生に使用されるリソースは保持することができます。完全な一時停止によってユーザーエクスペリエンスが低下する場合(中断のためのバッファリング時間が長いライブビデオフィードなど)、オーディオがミュートしてもビデオの再生をそのまま続けることができます。

要件4.6: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKの処理

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKを受け取ると、アプリは音量を下げる(ダッキング)必要があります。期待される音量レベルはさまざまな用途に応じて変わりますが、オーディオ出力と並行するほかのサウンドプレーヤーの妨げになってはいけません(たとえば、一時的に音量レベルを元の音量の30~40%に設定できます)。

通常このイベントは、短い通知(その間、オーディオアプリは音量を下げて音楽の再生を続けることができます)をシステムが再生しようとするときに受け取られます。注意: これは、一時的に音量レベルを0に設定することや再生を完全に一時停止することで、このイベントをAUDIOFOCUS_LOSS_TRANSIENTの処理と同じように効果的に処理できることを意味します。

要件4.7: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKの処理

エンコードされたオーディオコンテンツドルビーのパススルーなど)を出力するアプリは、AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKが受け取られると、再生を完全に一時停止します。エンコードされたコンテンツは、デコードされなければ、音量という概念を持ちません。パススルーの場合、デコードはレシーバー(AVR)でのみ発生するため、Fire TVでのストリーミング音量の設定は、実際の出力には影響しません。

Amazonのオーディオが強化されたプラットフォームは、コンテンツのデコード、低い音量レベルへの更新、コンテンツの再エンコードを行い、それをHDMIに送信することができる場合がありますが、この機能は保証されていません(詳細や可用性については、Amazonにお問い合わせください)。

要件4.8: AUDIOFOCUS_GAINの処理

AUDIOFOCUS_GAINを受け取ると、アプリは当初意図していたとおりに再生を継続できます。このイベントは、一時的な期間(一時停止、ミュート、低温量での再生が行われた期間)が終了したら配信されます。事前に音量を下げた場合、元の音量レベルに戻す必要があります。

このAUDIOFOCUS_GAIN イベントを受け取ると、ライブストリームを再生中のオーディオアプリとビデオアプリは、通常自動的に再生を継続します。オンデマンドビデオコンテンツを再生中のアプリは、再生を継続するか、ユーザーが押せば再生が継続する [再生] ボタンを表示しながら一時停止状態を保つようにします。

要件4.9: オーディオフォーカスの中止

オーディオの再生が終わったとき、アプリにオーディオフォーカスが既に付与されている場合、必ず、オーディオフォーカスが以前に要求したものと同じパラメーターを持つAudioManager.AbandonAudioFocus()を呼び出してください。再生に使用されるリソースは解放する必要があります(次回再生時には改めてオーディオフォーカスを要求できます)。

セキュアデコーダーの要件

要件5.1: セキュアデコーダーに対する制限

アプリが使用するセキュアデコーダーパイプラインは、どの時点でも1つだけでなければなりません。多くの場合、プラットフォームがサポートするセキュアデコーダー(MediaCodec)インスタンスは1つだけであるため、複数のセキュアコンテンツの並列デコードは失敗します。

要件5.2: リソースを解放する

セキュアデコーダーは、再生アクティビティのonStop()関数が呼び出されたら、即座に解放される必要があります。セキュアデコーダーはビデオ再生に使用されるため、それを必要とするアプリは、常にアクティビティを持ち、そのサーフェスでレンダリングすることになります。このアクティビティが終了するとすぐに、リソースを解放してほかのアプリがセキュアパイプラインの単一のインスタンスを取得できるようにします。

Wakelockの要件

一般的に、すべてのFire TVデバイスは(バッテリーからではなく)壁面コンセントから電力を得ると思われるため、アプリの消費電力とパフォーマンスの管理は省略できます。たとえば、CPUを常時作動させる、画面を暗くする、効率的なネットワーク利用やWi-Fiロックを管理するといったことは必要ありません。ただし、Keeping the Device Awake(デバイスをオンの状態に保つ)という開発者向けページに従ってWakelockを管理することは必要になります。

要件6.1: デバイスをオンの状態に保つ

再生が予測される間は、デバイスをオンの状態に保つ必要があります。つまり、通常アプリはアクティビティフラグFLAG_KEEP_SCREEN_ONを設定するか、FULL_WAKE_LOCKを取得することで動的なWakelock管理を実装することになります。

注意: FLAG_KEEP_SCREEN_ONを使用する場合、再生が一停止されたらフラグを解除する必要があります。

要件6.2: Wakelockの解放

何らかの理由で再生がアクティブでなくなった場合、Wakelockを解放する必要があります(再生は、映画が終了した場合、バッファリング画面が表示された場合、オーディオフォーカスが失われた場合にアクティブでなくなることがあります)。 アプリは、デバイスのスクリーンセーバーを(タイムアウト後に)表示させてからデバイスをスリープ状態にします。

出力状態の変更の要件

出力状態が変わったら、アプリは次の要件に従う必要があります。

要件7.1: HDMIの切断

HDMI切断イベントを受け取ったら、メディアの再生を一時停止する必要があります。ほとんどのアプリは、画面なしで再生を継続すべきではありません(不必要な4Kビデオ再生のデータ使用量は膨大になることがあります)。オーディオ専用アプリであっても、HDMIシンクが切断されたら再生を一時停止すべきです。

注意: ディスプレイの入力がFire TVから別の物に変わると、HDMIイベントも送信されます。HDMIケーブルがディスプレイに再接続されても、ほとんどのアプリは一時停止状態を維持します。

要件7.2: Bluetooth受信からHDMI出力への切り替え

ヘッドホンは大音量で使用されることが多いのに対し、TVスピーカーやサウンドシステムは通常それよりかなり低いレベルに設定されます。ユーザーが使用中のBluetoothヘッドセットを切断すると、オーディオストリームは自動的にスピーカーにルートを変更します。通常ユーザーは、音量が突然変わらないようにオーディオおよびビデオアプリが一時停止することを期待します。このシナリオをサポートするには、アプリはインテントACTION_AUDIO_BECOMING_NOISYを処理する必要があります。

要件7.3: Bluetooth出力への切り替え

エンコードされたオーディオストリームを直接出力するアプリ(ドルビーのパススルーなど)は、オーディオデバイスの変更をリッスンする必要があります。エンコードされたオーディオストリームは、Fire TVでデコードされなくてもHDMI出力に送信することができます(TVおよびAV受信機はほとんどのドルビー規格に対応しています)。ただし、Bluetoothヘッドホンがオンになるとオーディオストリームは自動的にヘッドホンにルートを変更しますが、Bluetooth規格はエンコードされたオーディオストリームがそうしたデバイスに送られることを許可していません。したがって、アプリはデバイスの機能や動作の変更に適切に備えておかなければなりません。オーディオデバイスの変更を認識する方法、およびその変更に対応する方法には複数の選択肢があります。

通知を受け取るために、アプリはAudioDeviceCallbacks(APIレベル23で導入)またはインテントAudioManager.ACTION_HDMI_AUDIO_PLUGに登録できます(後者の方が遅くて多くのリソースを使うため、AudioDeviceCallbackを使用することをお勧めします)。

変更に対応するために、アプリはPCM入力ストリームに切り替えるかパススルーをオフにする方法を選択でき(MediaCodecの使用により、エンコードされた入力ストリームはPCMにデコードされます)、Bluetoothヘッドホンはコンテンツを正しく再生できるようになります。

要件7.4: 異なるHDMI出力間の切り替え

PCM出力以外の出力(ドルビーやDTSのようなエンコーディングなど)に依存するアプリは、HDMIの切断イベントと再接続イベントの間にシンクの機能に変更があったかどうかを確認し、変更があった場合は、オーディオオブジェクトを再作成する必要があります。

ユーザーは、HDMIケーブルを各種オーディオエンコーディングに対応するディスプレイから外し、別のエンコーディングに対応するディスプレイにつなぐことができます。この変更に伴い、アプリがオーディオ出力を調整する必要が生じることがあります。

そのほかの要件

要件8.1: デバイスのグローバル状態を修正しない

デバイスのグローバル状態に影響を与えるAPI(AudioManger.setStreamVolume()など)の使用は避けてください。音量を更新しようとするアプリは、デバイスのすべてのアプリに作用するAudioManager APIではなく、AudioTrackまたはMediaPlayerインスタンスのsetVolume() APIを使用する必要があります。

要件8.2: 複雑なシナリオをテストする

これまでに挙げた要件の複雑なシナリオについて検討します。AudioFocusイベントおよびAndroidライフサイクルイベントを適切に処理する、MediaSessionを維持する、HDMIの接続/切断状態に従うといったことを行う場合、アプリは以下のような複雑なユースケースを処理する必要があります。

  1. アプリで再生を開始する。
  2. 音声検索を開始する。
  3. HDMIケーブルをディスプレイから外す。
  4. リモコンの [戻る] を押して音声検索からアプリに戻す。
  5. ペアリング済みのBluetoothヘッドホンをオンにする。
  6. HDMIケーブルをディスプレイに再度つなぐ。

このとき、ほとんどのアプリは、一時停止状態でユーザーの操作を待ちます。すべてのユーザーインターフェイスの表示は、一時停止状態を反映します。たとえば、リモコンアプリは、一時停止ボタンを無効化します。オーディオは、ドルビーのパススルー出力を回避しながら、Bluetoothヘッドホンにルートを変更します。