TVのアプリ同士がスムーズに連携するには、すべてのアプリが同じマルチメディア要件を遵守する必要があります。同要件には、アクティビティライフサイクルイベントやオーディオフォーカスイベント、メディアセッション、デコーダーインスタンス、WakeLockなどの適切な処理方法が含まれています。TVのアプリ間を移動する際のユーザーエクスペリエンスを向上させることができます。TVでは、通常アプリはマルチメディアの再生を主な目的としています。TV全体のアプリ間の連携が必要です。多くの点で、TVデバイス対応アプリは、一般的なAndroidアプリよりも複雑になりがちです。ホームボタンや音声操作機能が備わっているため、ユーザーがそれを利用すれば、即座にアプリの再生が中断されることがあります。TVでは、ステータスバーと通知バーがアプリと連動しません。うまく動かないアプリの停止や終了の操作も、TVデバイスではより複雑です。音楽を再生しているアプリを終了したのに、映画を観始めても音楽が再生され続けたという経験があればおわかりでしょうが、複数のマルチメディアが同時に再生されるとひどい状態になります。TVでアプリがスムーズに連携するには、すべてのアプリが同じ基準に従ってマルチメディアを扱う必要があります。一般的には、Android開発者ページで推奨されているガイドラインに従います。TVデバイスではアプリ同士が複雑に作用し合うことから、AmazonではAndroid開発者向けドキュメントに記載されている多くの推奨事項を、提案としてではなく要件として扱っています。TVの要件の方が厳しい場合は、その旨が要件の詳細に記されています。Androidについての予備知識この記事の内容は、Androidの開発について知識があることを前提としています。ここでの基本概念への理解を深めるために、Androidドキュメントの以下のトピックを参照してください。5(APIレベル22までサポート)で使用できるわけではないことに注意してください。APIレベル25(Nougat)専用です。アプリでサポートされていないAPIレベルが要件で言及されている場合は、アプリのAPIレベルで提供されている実装のガイドラインに従ってください。要件は、アプリの種類や、アプリがメディアをアクティビティとして再生するかサービスとして再生するかによって異なります。マルチメディアアプリは、以下のように分類されます。オーディオのみを再生するアプリ(音楽アプリなど)オーディオとビデオを再生するアプリ(テレビ局アプリなど)ビデオのみを再生するアプリ(好みの音楽アプリをバックグラウンドで再生しながらプレイできるゲームなど)多くの場合、こうしたアプリの種類に応じて要件が異なります。アプリはアクティビティまたはサービスのどちらかとしてメディアを再生します。ビデオアプリは、レンダリング用のウィンドウが必要なため、通常アクティビティとして再生する方法を使用します。バックグラウンド(ほかのアクティビティによって見えない状態)で再生するオーディオのみのアプリは、中断を避けるためにフォアグラウンドのサービスを使用する必要があります。どちらの場合も、オーディオ出力の前にオーディオフォーカスの管理が必要です。上記のアプリの種類ごとの要件については、以下のセクションで説明します。アクティビティベースのアプリの要件マルチメディアアプリが(サービスではなく)アクティビティとして再生する場合、アクティビティライフサイクルイベントを慎重に処理する必要があります。Androidのアクティビティのライフサイクルについてに記載されている可能な遷移をすべて認識しておいてください。TV対応アプリの要件です。onPause()の処理onPause()は、アクティビティが部分的にほかのアクティビティによって見えない状態(ユーザーがホームボタンを長押してヘッドアップ通知が表示されているなど)のときに呼び出されます。onPause()が呼び出された後でもアクティビティは再生を継続できますが、オーディオフォーカスの変更にはコールバックを実行する必要があります(ライフサイクルイベントに関係なく、オーディオフォーカスを失うとオーディオを再生できません)。onResume()の処理onResume()では、onCreate()を呼び出さずに元の再生状態を復元する(つまり、onPause()での変更を元に戻す)必要があります。ここで言う「再生状態」とは、アプリに依存するさまざまな事項を意味します。一般的には、自動的に(オーディオフォーカスもある場合)元の音量を使用し、元のUIに最新の進行状況バーを表示して、再生を継続します。再生に使用されるリソースを解放しなければならない場合は、パイプラインを再度初期化し、再生が中断される直前の位置にシークバックする必要があるかもしれません。onStop()でのリソースの解放onStop()が呼び出されたら、再生に使用されるすべてのリソース(AudioTrack、MediaCodecなど)を解放する必要があります。ユーザーがホームボタンを押した後にアプリに戻ったら、アプリはすべてのリソースを再取得する必要があります。再生に別のアクティビティを使用する場合は、注意してください。onResume()という順序になります。onStop()でリソースを解放する場合、finish()を呼び出すことで再生アクティビティを終了するという方法があります。ライフサイクルイベント(onRestart()、onStart()、onResume())に従ってすべての再生リソースを再取得することもできます。onDestroy()の使用の回避リソースを解放するにあたり、onDestroy()の呼び出しには頼らないでください。リソースはonStop()で既に解放されている必要があります。onDestroy()が呼び出されるとしても、そのタイミングは、デバイスのオペレーティングシステムで決まります。ほかのアプリにすぐに切り替えると、呼び出されることはまずありません。特定のオーディオフォーカス変更イベントがアクティビティライフサイクルの特定の状態で受信されることを前提にしないでください。多くの場合、ライフサイクルイベントとオーディオフォーカス変更イベントは一緒に受信されます(アクティビティが音声操作によって中断されたときなど)。この場合、AUDIOFOCUS_LOSSとonPause()アクティビティライフサイクルイベントの両方を受け取りますが、これらのイベントは互いに独立しているため、イベントの順序はいつでも変更できます。onKeyDownのオーバーライドonKeyDown()をオーバーライドして入力イベントを処理する場合は、戻り値の設定を慎重に行います。イベントがそれ以上伝播しないようにするためにtrueを返す必要があります。falseを返すと、イベントは処理されなかったことになり、Androidフレームワークによって伝播されていきます。イベントを適切に処理できない場合でも、onKeyDown()イベントハンドラーはtrueを返す必要があります。一時停止できないライブコンテンツの再生でKEYCODE_MEDIA_PLAY_PAUSEを受信したときにも、trueを返すようにします。同じイベントがほかのアプリに伝播されて再生が開始されるのを避けるためです。サービスベースのアプリの要件アプリがオーディオのみを再生する場合は、オーディオアプリの概要に記載されているフォアグラウンドサービスを使用して再生を実装することをお勧めします。この方法なら、ユーザーがほかのアクティビティを閲覧しているときにバックグラウンドでオーディオを再生できます。サービスの開始startForeground()メソッドを使用してフォアグラウンドサービスとしてサービスを開始し、メモリ不足の状態でも中断されないようにします。再生を終了したら、stopForeground()を使用して、フォアグラウンドからサービスを削除します。このメソッドでは、追加料金が必要になるとサービスが停止することが示されます。メディアセッションの要件メディアアプリは、受信するメディアセッションイベントを、その発生源に関係なく処理する必要があります。代表的なイベント発生源を以下に挙げます。TVデバイスのリモコン次へ/前へボタンのあるBluetoothヘッドセットマルチメディアボタンのある付属のキーボード、およびマウススマートウォッチユーザーのスマートフォンで実行中のリモートアプリ音声操作(ユーザーが「アレクサ、一時停止して」と言うなど)に対応中のAmazonサービスアクティビティライフサイクルの状態やオーディオフォーカスイベントに関係なく、こうしたイベントをすべて例外なく処理するには、アプリはメディアセッションの使用のガイドラインに従う必要があります。以下の点には注意してください。「メディアセッションは、管理するプレーヤーと共生しています。メディアセッションとその関連プレーヤーを所有するアクティビティまたはサービスのonCreate()メソッドで、メディアセッションを作成および初期化する必要があります。」メディアセッションイベントの処理の要件は以下のとおりです。APIレベル26については、すべてのセッションが配信コントロールを処理する見込みです)。再生を開始する前にsetActiveを呼び出して、メディアセッションを公開します。再生状態の更新PlaybackStateを最新の状態に保ちます。有効なコントローラーアクションのリストを維持します。「一時停止」は再生中にのみ可能ですし、一部のアプリはコマーシャル中の早送りを制限します。プレーヤーの位置を更新し、進行状況バーが正しく更新されるようにします。再生位置を更新するためだけに状態をリフレッシュすることを避けるには、4つのパラメーター(updateTimeなど)を持つPlaybackStateCompat.Builder.setState()を使用するとよいでしょう。アーティスト名、トラックタイトル、トラック再生時間などのメタデータ情報が変わったら更新します。すべてのUI表示は更新される必要があります(スマートフォンのリモートアプリも)。APIを使用してメディアセッションを解放します。メディアセッションの期間メディアセッションの有効期間は、アプリが再生できる期間と完全に一致する必要があります。バックグラウンドで音楽を再生する場合、バックグラウンドのままメディアボタンを処理する必要があります。オーディオフォーカスを一時的に失っているときに再生を一時停止する場合は、メディアセッションを無効化してメディアボタンの処理を中断する必要もあります。オーディオフォーカスイベントの要件オーディオフォーカスを処理すると、アプリがうまく協調してオーディオを再生します。音声フォーカスの管理には次のように説明されています。「音声フォーカス」という概念が導入されています。つのアプリのみが音声フォーカスを保持できる、という概念です。TVでは、どの種類のオーディオを再生するアプリも、再生が開始する前にオーディオフォーカスを取得する必要があります。オーディオフォーカスの要求、オーディオフォーカスの変更の処理、オーディオフォーカスの解放に関しては、さまざまな要件があります。再生直前でのオーディオフォーカスの要求アプリは、AudioManager.requestAudioFocus()をオーディオ再生の直前に呼び出す必要があります(多くの開発者がこの要件の「直前」という部分を見落としています)。requestAudioFocus()とabandonAudioFocus()の2つのオーディオフォーカス呼び出しの間隔は、フォーカス変更コールバックを処理する時間と完全に一致する必要があります。ある不適切な実装のシナリオ例を以下に示します。再生が許可されていることを確認するために、アプリがオーディオフォーカスを要求する。「バッファー中」の画面を数秒間表示しているコンテンツのダウンロードをアプリが開始する。アプリがアクティビティに切り替え、オーディオフォーカスの変更を処理して実際に再生する。実際の再生が開始する前に一時的に「バッファー中」画面が表示されている間(手順3)に、アプリがオーディオフォーカスを失うことも考えられます。手順2でオーディオフォーカスを要求した場合、それ以降継続的にAudioFocusイベントを処理する必要があります。クリックした後すぐにユーザーが音声操作を開始した場合など)。フォーカスが付与されていることの検証アプリは、requestAudioFocus()の呼び出しがAUDIOFOCUS_REQUEST_GRANTEDを返すことを検証する必要があります。ほかの値が返されたら、アプリが再生の開始を許可されていないということになります。TVデバイス(APIレベル25)ではこの値は返されません。オーディオフォーカスの解放すべての再生セッションは、再生終了直後にオーディオフォーカスを解放する必要があります。アプリに複数の再生セッションがある場合(番組の次のエピソードの再生を新しいアクティビティで開始するなど)、2回目のオーディオフォーカスの要求は必要ありません。要求すると、最初のセッションがパラメーターAUDIOFOCUS_LOSSを伴うコールバックを受け取り、それに応じて、最初のセッションはそのリソース(2回目のセッションと共有されていないもの)を解放し、オーディオフォーカスも解放します。audioを手動で実行すれば、これを確認できます)。オーディオフォーカスの変更の要件オーディオフォーカスを要求する場合、アプリはAudioManager.OnAudioFocusChangeListenerを実装するフォーカスの変更も処理し、すべてのコールバックパラメーターを正しく処理する必要があります。コールバックパラメーターは、次のいずれかです。AUDIOFOCUS_LOSSの処理アプリがAUDIOFOCUS_LOSSイベントを受け取ると、システムが再生の権利すべてをほかのアプリに付与済みであるため、アプリはオーディオの再生を完全に中止する必要があります。オーディオフォーカスがアプリに返されることは期待できません(これ以降AUDIOFOCUS_GAINを取得することはありません)。アプリを終了し、リソース(AudioTrack、MediaCodecなど)を解放して、AudioManager.abandonAudioFocus()の呼び出しによって再生を終了したことをシステムに通知してください。AUDIOFOCUS_LOSS_TRANSIENTの処理アプリは、AUDIOFOCUS_LOSS_TRANSIENTイベントを受け取ると、オーディオの再生を「一時停止」する必要があります(ビデオについては厳しい要件はありません)。通常これは、短い通知やシステムサウンド、そのほかの一時的な状態によってアプリが中断されるときに発生し、オーディオフォーカスがすぐにアプリに返されます。オーディオの一時停止は、これ以降オーディオを出力できないことを意味します。アプリは、ミュートする必要があります。再生全体を一時停止するか、オーディオの音量を0に下げてビデオの再生を続けることができます。オンデマンドコンテンツを再生中のビデオアプリは、ビデオとオーディオの両方の再生を完全に一時停止することを選択する必要があります。再生に使用されるリソースは保持することができます。完全な一時停止によってユーザーエクスペリエンスが低下する場合(中断のためのバッファリング時間が長いライブビデオフィードなど)、オーディオがミュートしてもビデオの再生をそのまま続けることができます。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKの処理AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKを受け取ると、アプリは音量を下げる(ダッキング)必要があります。期待される音量レベルはさまざまな用途に応じて変わりますが、オーディオ出力と並行するほかのサウンドプレーヤーの妨げになってはいけません(たとえば、一時的に音量レベルを元の音量の30~40%に設定できます)。通常このイベントは、短い通知(その間、オーディオアプリは音量を下げて音楽の再生を続けることができます)をシステムが再生しようとするときに受け取られます。これは、一時的に音量レベルを0に設定することや再生を完全に一時停止することで、このイベントをAUDIOFOCUS_LOSS_TRANSIENTの処理と同じように効果的に処理できることを意味します。7以降)のオペレーティングシステムでは、アプリのOnAudioFocusChangeListenerコールバックを呼び出さずに、アプリの音量を小さくすることができます。ducking(英語のみ)を参照してください。5または6を搭載しているデバイスでアプリが動作する場合でも、このコールバックを実装して受信パラメーターAUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKを処理する必要があります。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKの処理エンコードされたオーディオコンテンツ(ドルビーのパススルーなど)を出力するアプリは、AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKを受け取ると、再生を完全に一時停止します。エンコードされたコンテンツは、デコードされなければ、音量という概念を持ちません。TVでのストリーミング音量の設定は、実際の出力には影響しません。Amazonのオーディオが強化されたプラットフォームは、コンテンツのデコード、低い音量レベルへの更新、コンテンツの再エンコードを行い、それをHDMIに送信することができる場合がありますが、この機能は保証されていません(詳細や使用可否については、Amazonにお問い合わせください)。AUDIOFOCUS_GAINの処理AUDIOFOCUS_GAINを受け取ると、アプリは当初意図していたとおりに再生を継続できます。このイベントは、一時的な期間(一時停止、ミュート、低音量での再生が行われた期間)が終了したら配信されます。事前に音量を下げた場合、元の音量レベルに戻す必要があります。イベントを受け取ると、ライブストリームを再生中のオーディオアプリとビデオアプリは、通常自動的に再生を継続します。オンデマンドビデオコンテンツを再生中のアプリは、再生を継続するか、ユーザーが押せば再生が継続する再生ボタンを表示しながら一時停止状態を保つようにします。オーディオフォーカスの解放オーディオの再生が終わったとき、アプリにオーディオフォーカスが既に付与されている場合、必ず、オーディオフォーカスが以前に要求したものと同じパラメーターを持つAudioManager.AbandonAudioFocus()を呼び出してください。再生に使用されるリソースは解放する必要があります(次回再生時には改めてオーディオフォーカスを要求できます)。AUDIOFOCUS_LOSSを受け取り、そのレスポンスとして再生を終了する場合は、AbandonAudioFocus()を呼び出すことも必要です。ここで取り上げていない詳細については、音声フォーカスの管理に関するAndroid開発者向けドキュメントを参照してください。Android開発者向けドキュメントでは、手引きの中で「should」(した方がよい)や「can」(できる)という表現が用いられていることがあります。TVのマルチメディアアプリに関しては、Android開発者向けドキュメントのほとんどのガイドラインは、提案ではなく、厳しい要件であると捉えています。セキュアデコーダーに対する制限アプリが使用するセキュアデコーダーパイプラインは、どの時点でも1つだけでなければなりません。多くの場合、プラットフォームがサポートするセキュアデコーダー(MediaCodec)インスタンスは1つだけであるため、複数のセキュアコンテンツの並列デコードは失敗します。リソースの解放セキュアデコーダーは、再生アクティビティのonStop()関数が呼び出されたら、即座に解放される必要があります。セキュアデコーダーはビデオ再生に使用されるため、それを必要とするアプリは、常にアクティビティを持ち、そのサーフェスでレンダリングすることになります。このアクティビティが終了するとすぐに、リソースを解放してほかのアプリがセキュアパイプラインの単一のインスタンスを取得できるようにします。TVデバイスは(バッテリーからではなく)壁面コンセントから電力を得ると思われるため、アプリの消費電力とパフォーマンスの管理は省略できます。CPUを常時作動させる、画面を暗くする、効率的なネットワーク利用やWi-Fiロックを管理するといったことは必要ありません。デバイスの起動状態を維持するという開発者向けページに従ってWakeLockを管理する必要はあります。再生が想定される間は、デバイスの起動状態を維持する必要があります。通常アプリはアクティビティフラグFLAG_KEEP_SCREEN_ONを設定するか、FULL_WAKE_LOCKを取得することで動的なWakeLock管理を実装することになります。FLAG_KEEP_SCREEN_ONを使用する場合、再生が一停止されたらフラグを解除する必要があります。何らかの理由で再生がアクティブでなくなった場合、WakeLockを解放する必要があります(再生は、映画が終了した場合、バッファリング画面が表示された場合、オーディオフォーカスが失われた場合にアクティブでなくなることがあります)。アプリは、デバイスのスクリーンセーバーを(タイムアウト後に)表示させてからデバイスをスリープ状態にします。出力状態が変わったら、アプリは次の要件に従う必要があります。HDMIの接続解除HDMI接続解除イベントを受け取ったら、メディアの再生を一時停止する必要があります。ほとんどのアプリは、画面なしで再生を継続すべきではありません(不必要な4Kビデオ再生のデータ使用量は膨大になることがあります)。オーディオ専用アプリであっても、HDMIシンクが接続解除されたら再生を一時停止すべきです。TVから別のものに変わると、HDMIイベントも送信されます。HDMIケーブルがディスプレイに再接続されても、ほとんどのアプリは一時停止状態を維持します。Bluetooth受信からHDMI出力への切り替えヘッドホンは大音量で使用されることが多いのに対し、TVスピーカーやサウンドシステムは通常それよりかなり低いレベルに設定されます。ユーザーが使用中のBluetoothヘッドセットを接続解除すると、オーディオストリームは自動的にスピーカーにルートを変更します。通常ユーザーは、音量が突然変わらないようにオーディオおよびビデオアプリが一時停止することを期待します。このシナリオをサポートするには、アプリはインテントACTION_AUDIO_BECOMING_NOISYを処理する必要があります。Bluetooth出力への切り替えエンコードされたオーディオストリームを直接出力するアプリ(ドルビーのパススルーなど)は、オーディオデバイスの変更をリッスンする必要があります。TVでデコードされなくてもHDMI出力に送信することができます(TVおよびAV受信機はほとんどのドルビー規格に対応しています)。Bluetoothヘッドホンがオンになるとオーディオストリームは自動的にヘッドホンにルートを変更しますが、Bluetooth規格はエンコードされたオーディオストリームがそうしたデバイスに送られることを許可していません。したがって、アプリはデバイスの機能や動作の変更に適切に備えておかなければなりません。オーディオデバイスの変更を認識する方法、およびその変更に対応する方法には複数の選択肢があります。通知を受け取るために、アプリはAudioDeviceCallbacks(APIレベル23で導入)またはインテントAudioManager.ACTION_HDMI_AUDIO_PLUGに登録できます(後者の方が遅くて多くのリソースを使用するため、AudioDeviceCallbackを使用することをお勧めします)。変更に対応するために、アプリはPCM入力ストリームに切り替えるかパススルーをオフにする方法を選択でき(MediaCodecの使用により、エンコードされた入力ストリームはPCMにデコードされます)、Bluetoothヘッドホンはコンテンツを正しく再生できるようになります。異なるHDMI出力間の切り替えPCM出力以外の出力(ドルビーやDTSのようなエンコーディングなど)に依存するアプリは、HDMIの接続解除イベントと再接続イベントの間にシンクの機能に変更があったかどうかを確認し、変更があった場合は、オーディオオブジェクトを再作成する必要があります。ユーザーは、HDMIケーブルを各種オーディオエンコーディングに対応するディスプレイから外し、別のエンコーディングに対応するディスプレイにつなぐことができます。この変更に伴い、アプリがオーディオ出力を調整する必要が生じることがあります。デバイスのグローバル状態の変更禁止デバイスのグローバル状態に影響を与えるAPI(AudioManger.setStreamVolume()など)の使用は避けてください。APIを使用する必要があります。最新のAPIレベルへのターゲット指定イベント処理とAndroidフレームワークの動作は、アプリがターゲットとするAPIレベルによってさまざまです。原則として、ターゲットとするAPIレベルは、アプリの実行を想定している最も高いレベルとしてください。Pie)をターゲットとする必要があります。CPU使用率の抑制デバイスの温度が許容範囲を超えるほど高くなると、温度を下げるためにCPUパフォーマンスに制限がかけられます。TVデバイスの再起動が行われます。マルチメディアを長時間再生すると、すべてのシステムリソースに負荷が継続的にかかります。Amazonでは、オーディオとビデオの両方についてあらゆる組み合わせの暗号化とエンコーディングを試すなど、マルチメディア再生の一般的なユースケースを徹底的にテストしています。デバイスの機能はすべて、適切なアプリコードであれば安全に動作できることが確認されています。アプリコードの効率性を保ち、デバイスの過熱を防ぐようにしてください。瞬間的にCPU負荷が高くなるのは問題ありませんが、CPU使用率の高い状態が続くとデバイスの温度が上昇し始めます。アプリのテストでは、最も要求の厳しいユースケース(最高レベルの解像度やフレームレート、再生の暗号化、すべてのオーバーレイをオンにするなど)を長時間実行するようにしてください。映画を再生するアプリの場合は、上映時間が非常に長い映画でテストします。CPUが制限されるレベルまでデバイスの温度が達するようなアプリは、望ましくありません。効率的なエンコーディング(AVCではなくHEVCなど)を用い、ソフトウェアでの実装ではなくハードウェアソリューションを使用して、マルチメディアパイプライン全体の負荷を軽減することを検討してください。複雑なシナリオのテストこれまでに挙げた要件の複雑なシナリオについて検討します。オーディオフォーカスイベントおよびAndroidライフサイクルイベントを適切に処理する、メディアセッションを維持する、HDMIの接続/接続解除状態に従うといったことを行う場合、アプリは以下のような複雑なユースケースを処理する必要があります。アプリで再生を開始する。HDMIケーブルをディスプレイから外す。リモコンの戻るを押して音声検索からアプリに戻す。ペアリング済みのBluetoothヘッドホンをオンにする。HDMIケーブルをディスプレイに再度つなぐ。このとき、ほとんどのアプリは、一時停止状態でユーザーの操作を待ちます。すべてのユーザーインターフェイスの表示は、一時停止状態を反映します。リモコンアプリは、一時停止ボタンを無効化します。オーディオは、ドルビーのパススルー出力を回避しながら、Bluetoothヘッドホンにルートを変更します。無断複写·転載を禁じます。