ExoPlayer を使用したメディア ストリーミング

1. 始める前に

526b239733391e74.png

スクリーンショット: YouTube Android アプリ

ExoPlayer は、Android の低レベルメディア API の上に構築されたアプリレベルのメディア プレーヤーです。ExoPlayer には、Android に組み込まれている MediaPlayer にまさるメリットがいくつかあります。たとえば、MediaPlayer と同じメディア形式を数多くサポートするだけでなく、DASH や SmoothStreaming などのアダプティブ形式もサポートします。高度なカスタマイズと拡張が可能で、多くの高度なユースケースに対応できます。ExoPlayer は、YouTube や Google Play ムービー&TV などの Google アプリで使用されるオープンソース プロジェクトです。

前提条件

  • Android 開発と Android Studio に関する適度な知識

演習内容

  • さまざまなソースのメディアを準備して再生する SimpleExoPlayer インスタンスを作成します。
  • ExoPlayer をアプリのアクティビティ ライフサイクルと統合して、単一ウィンドウまたはマルチウィンドウ環境でのバックグラウンド処理、フォアグラウンド処理、再生の再開をサポートします。
  • MediaItem を使用して、プレイリストを作成します。
  • メディア品質を利用可能な帯域幅に合わせて調整するアダプティブ動画ストリームを再生します。
  • イベント リスナーを登録して再生状態をモニタリングし、リスナーを使用して再生の品質を測定する方法を示します。
  • 標準の ExoPlayer UI コンポーネントを使用し、さらにアプリのスタイルに合わせてそれらをカスタマイズします。

必要なもの

  • Android Studio の最新の安定版
  • JellyBean(4.1)以上(理想的には複数のウィンドウをサポートする Nougat(7.1)以上)を搭載した Android デバイス。

2. 設定する

コードを取得する

最初に、Android Studio プロジェクトをダウンロードします。

または、GitHub リポジトリのクローンを作成することもできます。

git clone https://github.com/googlecodelabs/exoplayer-intro.git

ディレクトリ構造

クローンを作成するか ZIP を解凍すると、ルートフォルダ(exoplayer-intro)が作成されます。このフォルダには、複数のモジュール(アプリ モジュールが 1 つと、この Codelab の各ステップに対応するモジュールが 1 つずつ)を含む単一の Gradle プロジェクトに加えて、必要なすべてのリソースが格納されています。

プロジェクトをインポートする

  1. Android Studio を起動します。
  2. [File] > [New] > [Import Project] をクリックします。
  3. ルートの build.gradle ファイルを選択します。

111b190903697765.png

スクリーンショット: インポート時のプロジェクト構造

ビルドが完了すると、6 つのモジュールがあることを確認できます。すなわち、app モジュール(タイプはアプリ)と、exoplayer-codelab-N という名前の 5 つのモジュール(N00 から 04, で、それぞれのタイプはライブラリ)です。app モジュールは実際には空で、マニフェストのみが含まれています。app/build.gradle の Gradle 依存関係を使用してアプリをビルドすると、現在指定されている exoplayer-codelab-N モジュール内のすべてが結合されます。

app/build.gradle

dependencies {
   implementation project(":exoplayer-codelab-00")
}

メディア プレーヤーのアクティビティは exoplayer-codelab-N モジュールで保持されます。これを別個のライブラリ モジュールで保持するのは、モバイルや Android TV などのさまざまなプラットフォームをターゲットとする APK 間で共有できるようにするためです。それによって、ユーザーが必要とする場合にのみメディア再生機能をインストールできる Dynamic Delivery のような機能を利用することも可能になります。

  1. アプリをデプロイして実行し、すべてが正常であることを確認します。アプリでは、画面が黒い背景で塗りつぶされます。

2dae13fed92e6c8c.png

スクリーンショット: 黒いアプリが実行される

3.ストリーミングする

ExoPlayer の依存関係を追加する

ExoPlayer は、GitHub でホストされるオープンソース プロジェクトです。各リリースは、Android Studio と Gradle によって使用されるデフォルトのパッケージ リポジトリの一つである Google Maven を介して配布されます。各リリースは、次の形式の文字列で一意に識別されます。

com.google.android.exoplayer:exoplayer:X.X.X

クラスと UI コンポーネントをインポートするだけで、ExoPlayer をプロジェクトに追加できます。ExoPlayer はかなりサイズが小さく、含まれる機能とサポートされる形式に応じて約 70~300 KB の縮小フットプリントがあります。ExoPlayer ライブラリはモジュールに分割されており、デベロッパーは必要な機能のみをインポートできます。ExoPlayer のモジュール構造について詳しくは、ExoPlayer モジュールの追加をご覧ください。

  1. player-lib モジュールの build.gradle ファイルを開きます。
  2. dependencies セクションに以下の行を追加し、プロジェクトを同期します。

exoplayer-codelab-00/build.gradle

dependencies {
   [...]

implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'

}

PlayerView element を追加する

  1. exoplayer-codelab-00 モジュールから、レイアウト リソース ファイル activity_player.xml を開きます。
  2. FrameLayout 要素の内部にカーソルを置きます。
  3. <PlayerView と入力し始めると、Android Studio によって PlayerView 要素がオートコンプリートされます。
  4. widthheight には match_parent を使用します。
  5. ID を video_view として宣言します。

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

これ以降は、この UI 要素を動画ビューと呼びます。

  1. PlayerActivity で、編集したばかりの XML ファイルから作成されたビューツリーへの参照を取得できます。

PlayerActivity.kt

    private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
        ActivityPlayerBinding.inflate(layoutInflater)
    }
  1. ビューツリーのルートを、アクティビティのコンテンツ ビューとして設定します。また、videoView プロパティが viewBinding 参照で視認可能であることと、そのタイプが PlayerView であることを確認します。
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)
    }

ExoPlayer を作成する

ストリーミング メディアを再生するには、ExoPlayer オブジェクトが必要です。これを作成する最も簡単な方法は、SimpleExoPlayer.Builder クラスを使用することです。その名前が示すとおり、このクラスはビルダー パターンを使用して SimpleExoPlayer インスタンスを作成します。

SimpleExoPlayer は、ExoPlayer インターフェースの便利な多目的の実装です。

SimpleExoPlayer を作成するために、プライベート メソッド initializePlayer を追加します。

PlayerActivity.kt

private var player: SimpleExoPlayer? = null
[...]
   private fun initializePlayer() {
        player = SimpleExoPlayer.Builder(this)
            .build()
            .also { exoPlayer ->
                viewBinding.videoView.player = exoPlayer
            }
    }

コンテキストを使用して SimpleExoPlayer.Builder を作成した後、build を呼び出して SimpleExoPlayer オブジェクトを作成します。次に、それを player に割り当てます。これはメンバー フィールドとして宣言する必要があります。さらに、viewBinding.videoView.player 可変プロパティを使用して、player を対応するビューにバインドします。

メディア アイテムを作成する

player には、再生するコンテンツが必要です。そのために、MediaItem を作成します。MediaItem にはさまざまなタイプがありますが、最初はインターネット上の MP3 ファイル用のものを作成します。

MediaItem を作成する最も簡単な方法は、メディア ファイルの URI を受け入れる MediaItem.fromUri を使用することです。player.setMediaItem を使用して、MediaItemplayer に追加します。

  1. also ブロック内の initializePlayer に次のコードを追加します。

PlayerActivity.kt

private fun initializePlayer() {
    [...]
        .also { exoPlayer ->
            [...]
            val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
            exoPlayer.setMediaItem(mediaItem)
        }
}

R.string.media_url_mp3 は、strings.xmlhttps://storage.googleapis.com/exoplayer-test-media-0/play.mp3 として定義されていることに注意してください。

アクティビティのライフサイクルで適切に再生する

player は、メモリ、CPU、ネットワーク接続、ハードウェア コーデックなどの多くのリソースを占有する可能性があります。特に 1 つしかないハードウェア コーデックでは、これらのリソースの多くが不足します。アプリがリソースを使用していないとき(バックグラウンドで実行されているときなど)に、これらのリソースを解放して他のアプリが使用できるようにすることが重要です。

言い換えると、プレーヤーのライフサイクルをアプリのライフサイクルに関連付ける必要があります。この関連付けを実装するには、PlayerActivity の 4 つのメソッド(onStartonResumeonPauseonStop)をオーバーライドします。

  1. PlayerActivity を開いた状態で、[Code menu] > [Override methods...] をクリックします。
  2. onStartonResumeonPauseonStop を選択します。
  3. API レベルに応じて、onStart または onResume コールバックでプレーヤーを初期化します。

PlayerActivity.kt

public override fun onStart() {
 super.onStart()
 if (Util.SDK_INT >= 24) {
   initializePlayer()
 }
}

public override fun onResume() {
 super.onResume()
 hideSystemUi()
 if ((Util.SDK_INT < 24 || player == null)) {
   initializePlayer()
 }
}

API レベル 24 以上の Android は、複数のウィンドウをサポートします。アプリは視認可能ですが、分割ウィンドウ モードではアクティブにならないため、onStart でプレーヤーを初期化する必要があります。API レベル 24 以下の Android では、アプリがリソースを取得するまでできるだけ長く待機する必要があるため、プレーヤーを初期化する前に onResume まで待ちます。

  1. hideSystemUi メソッドを追加します。

PlayerActivity.kt

@SuppressLint("InlinedApi")
private fun hideSystemUi() {
 viewBinding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
     or View.SYSTEM_UI_FLAG_FULLSCREEN
     or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
     or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
     or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
     or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}

hideSystemUionResume で呼び出されるヘルパー メソッドであり、これにより全画面エクスペリエンスを実現できます。

  1. onPauseonStop で、releasePlayer(まもなく作成します)を使用してリソースを解放します。

PlayerActivity.kt

public override fun onPause() {
 super.onPause()
 if (Util.SDK_INT < 24) {
   releasePlayer()
 }
}

public override fun onStop() {
 super.onStop()
 if (Util.SDK_INT >= 24) {
   releasePlayer()
 }
}

API レベル 24 以下では、onStop が呼び出される保証がないため、onPause でできるだけ早くプレーヤーを解放する必要があります。API レベル 24 以上(マルチウィンドウ モードと分割ウィンドウ モードが導入されています)では、onStop が呼び出されることが保証されます。一時停止状態では、アクティビティが引き続き表示されるため、onStop まで待ってからプレーヤーを解放します。

次に、releasePlayer メソッドを作成する必要があります。このメソッドはプレーヤーのリソースを解放して破棄します。

  1. アクティビティに次のコードを追加します。

PlayerActivity.kt

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
[...]

private fun releasePlayer() {
    player?.run {
        playbackPosition = this.currentPosition
        currentWindow = this.currentWindowIndex
        playWhenReady = this.playWhenReady
        release()
    }
    player = null
}

プレーヤーを解放して破棄する前に、次の情報を保存します。

  • 再生 / 一時停止状態(playWhenReady を使用)。
  • 現在の再生位置(currentPosition を使用)。
  • 現在のウィンドウ インデックス(currentWindowIndex を使用)。ウィンドウについて詳しくは、タイムラインをご覧ください。

以上により、ユーザーによって中断された位置から再生を再開できるようになります。そのために必要なのは、プレーヤーの初期化時にこの状態情報を提供することだけです。

最終準備

ここで必要なのは、releasePlayer に保存した状態情報を初期化時にプレーヤーに提供することだけです。

  1. initializePlayer に次の行を追加します。

PlayerActivity.kt

private fun initializePlayer() {
    [...]
    exoPlayer.playWhenReady = playWhenReady
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.prepare()
}

次のことが起こります。

  • playWhenReady は、再生用のリソースがすべて取得されたらすぐに再生を開始するかどうかをプレーヤーに指示します。playWhenReady の初期値は true なので、アプリが初めて実行されたときは自動的に再生が開始されます。
  • seekTo は、特定のウィンドウ内の特定の位置までシークするようプレーヤーに指示します。currentWindowplaybackPosition はどちらもゼロに初期化されるので、アプリが初めて実行されたときは最初から再生が開始されます。
  • prepare は、再生に必要なリソースをすべて取得するようプレーヤーに指示します。

音声を再生する

これで完了です。アプリを起動して MP3 ファイルを再生し、埋め込みアートワークを確認してください。

d92917867ee23ef8.png

スクリーンショット: 単一のトラックを再生しているアプリ。

アクティビティのライフサイクルをテストする

アクティビティ ライフサイクルのさまざまな状態すべてでアプリが動作するかどうかをテストします。

  1. 別のアプリを起動し、アプリをフォアグラウンドに戻します。正しい位置から再開されるでしょうか?
  2. アプリを一時停止し、バックグラウンドに移動した後、再度フォアグラウンドに戻します。一時停止状態でバックグラウンドに移動した場合、一時停止状態のままになるでしょうか?
  3. アプリを回転させます。向きを縦向きから横向きに変更してから元に戻すと、どのように動作するでしょうか?

動画を再生する

動画を再生したい場合は、メディア アイテムの URI を MP4 ファイルに変更するだけで、簡単に対応できます。

  1. initializePlayer の URI を R.string.media_url_mp4 に変更します。
  2. アプリを再起動し、音声の場合と同様に、動画再生をバックグラウンドに移動した後の動作をテストします。

PlayerActivity.kt

private fun initializePlayer() {
  [...]
     val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
  [...]
}

PlayerView がすべてを処理します。アートワークではなく、動画が全画面表示されます。

425c6c65f78e8d46.png

スクリーンショット: 動画を再生しているアプリ。

これで完成です。ライフサイクル管理、状態保存、UI コントロールを備え、Android で全画面メディア ストリーミングを行うアプリを作成できました。

4. プレイリストを作成する

現在のアプリは単一のメディア ファイルを再生しますが、複数のメディア ファイルを連続で再生したい場合はどうすればよいでしょうか?そのためには、プレイリストが必要です。

プレイリストを作成するには、addMediaItem を使用して複数の MediaItemplayer に追加します。そうすればシームレスな再生が可能になり、バッファリングがバックグラウンドで処理されるため、メディア アイテムの変更時にバッファリング スピナーがユーザーに表示されなくなります。

  1. initializePlayer に次のコードを追加します。

PlayerActivity.kt

private void initializePlayer() {
  [...]
  exoPlayer.addMediaItem(mediaItem) // Existing code

  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
  exoPlayer.addMediaItem(secondMediaItem);
  [...]
}

プレーヤー コントロールの動作をチェックします。1f79fee4d082870f.png39627002c03ce320.png を使用して、連続したメディア アイテム間を移動できます。

7b5c034dafabe1bd.png

スクリーンショット:「次へ」ボタンと「前へ」ボタンが表示された再生コントロール

これはかなり便利です。詳しくは、メディア アイテムおよびプレイリストに関するデベロッパー ドキュメントと、Playlist API に関するこちらの記事をご覧ください。

5. アダプティブ ストリーミング

アダプティブ ストリーミングは、利用可能なネットワーク帯域幅に基づいてストリームの品質を変化させながら、メディアをストリーミングする手法です。これにより、ユーザーは帯域幅が許容する最高品質のメディアを体験できます。

通常、同じメディア コンテンツは、品質(ビットレートと解像度)が異なる複数のトラックに分割されます。プレーヤーは、利用可能なネットワーク帯域幅に基づいてトラックを選択します。

各トラックは、特定の期間(通常は 2~10 秒)のチャンクに分割されます。これにより、プレーヤーは利用可能な帯域幅の変化に応じて、トラックをすばやく切り替えることができます。プレーヤーは、シームレスな再生を行うためにこれらのチャンクを結合する処理を行います。

アダプティブなトラック選択

アダプティブ ストリーミングの核心は、現在の環境に最も適したトラックを選択することにあります。アダプティブなトラック選択を使用してアダプティブ ストリーミング メディアを再生するように、アプリを更新します。

  1. 次のコードで initializePlayer を更新します。

PlayerActivity.kt

private fun initializePlayer() {
   val trackSelector = DefaultTrackSelector(this).apply {
        setParameters(buildUponParameters().setMaxVideoSizeSd())
    }
   player = SimpleExoPlayer.Builder(this)
        .setTrackSelector(trackSelector)
        .build()
  [...]
}

最初に DefaultTrackSelector を作成します。これは、メディア アイテム内のトラックを選択する役割を果たします。次に、標準画質以下のトラックのみを選択するよう trackSelector に指示します。これは、品質を犠牲にしてユーザーのデータを保存するための良い方法です。最後に、trackSelector をビルダーに渡して、SimpleExoPlayer インスタンスの作成時に使用されるようにします。

アダプティブな MediaItem を作成する

DASH は、広く使用されているアダプティブ ストリーミング形式です。DASH コンテンツをストリーミングするには、これまでと同様に MediaItem を作成します。ただし、今回は fromUri の代わりに MediaItem.Builder を使用する必要があります。

なぜなら、fromUri は基盤となるメディア形式を決定するためにファイル拡張子を使用しますが、DASH URI にはファイル拡張子がないため、MediaItem を作成する際に APPLICATION_MPDMIME タイプを指定する必要があるからです。

  1. initializePlayer を次のように更新します。

PlayerActivity.kt

private void initializePlayer() {
  [...]

  // Replace this line
  val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));

  // With this
   val mediaItem = MediaItem.Builder()
        .setUri(getString(R.string.media_url_dash))
        .setMimeType(MimeTypes.APPLICATION_MPD)
        .build()

  // Also remove the following lines
  val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
    exoPlayer.addMediaItem(secondMediaItem)
}
  1. アプリを再起動し、DASH でアダプティブな動画ストリーミングが機能していることを確認します。ExoPlayer を使うと実に簡単です。

その他のアダプティブ ストリーミング形式

よく使用されるその他のアダプティブ ストリーミング形式としては HLSMimeTypes.APPLICATION_M3U8)と SmoothStreamingMimeTypes.APPLICATION_SS)があり、どちらも ExoPlayer でサポートされています。その他のアダプティブ メディアソースの作成方法については、ExoPlayer デモアプリを参照してください。

6. イベントをリッスンする

これまでのステップでは、プログレッシブなメディア ストリームとアダプティブなメディア ストリームをストリーミングする方法を学びました。ExoPlayer は、舞台裏で次のような多くの作業を行っています。

  • メモリの割り当て
  • コンテナ ファイルのダウンロード
  • コンテナからのメタデータの抽出
  • データのデコード
  • 画面とスピーカー システムへの動画、音声、テキストのレンダリング

ユーザーの再生エクスペリエンスを理解して改善するにあたっては、ExoPlayer が実行時に何をしているかを知ることが役立つ場合があります。

たとえば、次のような方法で再生状態の変化をユーザー インターフェースに反映させたい場合があります。

  • プレーヤーがバッファリング状態になったときに読み込みスピナーを表示する
  • トラックが終了したときに「次を再生」オプションを含むオーバーレイを表示する

ExoPlayer は、有用なイベントのコールバックを提供するいくつかのリスナー インターフェースを備えています。リスナーを使用すると、プレーヤーがどのような状態にあるかをログに記録できます。

リッスンする

  1. PlayerActivity クラスの外部で TAG 定数を作成します。これは後でロギングに使用します。

PlayerActivity.kt

private const val TAG = "PlayerActivity"
  1. PlayerActivity クラスの外部で、ファクトリ関数に Player.EventListener インターフェースを実装します。これは、エラーや再生状態の変化といった重要なプレーヤー イベントを通知するために使用します。
  2. 次のコードを追加して onPlaybackStateChanged をオーバーライドします。

PlayerActivity.kt

private fun playbackStateListener() = object : Player.EventListener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        val stateString: String = when (playbackState) {
            ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
            ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d(TAG, "changed state to $stateString")
    }
}
  1. PlayerActivity で、タイプが Player.EventListener のプライベート メンバーを宣言します。

PlayerActivity.kt

class PlayerActivity : AppCompatActivity() {
    [...]

    private val playbackStateListener: Player.EventListener = playbackStateListener()
}

onPlaybackStateChanged は、再生状態が変化したときに呼び出されます。新しい状態は playbackState パラメータで提供されます。

プレーヤーは次の 4 つの状態のいずれかになります。

状態

説明

ExoPlayer.STATE_IDLE

プレーヤーはインスタンス化されていますが、まだ準備されていません。

ExoPlayer.STATE_BUFFERING

十分なデータがバッファリングされていないため、プレーヤーは現在の位置から再生することができません。

ExoPlayer.STATE_READY

プレイヤーは現在の位置からすぐに再生できます。これは、プレーヤーの playWhenReady プロパティが true の場合、プレーヤーがメディアの再生を自動的に開始することを意味します。false の場合、プレーヤーは一時停止します。

ExoPlayer.STATE_ENDED

プレーヤーはメディアの再生を終了しました。

リスナーを登録する

コールバックが呼び出されるためには、playbackStateListener をプレーヤーに登録する必要があります。これは initializePlayer で行います。

  1. 再生の準備をする前にリスナーを登録します。

PlayerActivity.kt

private void initializePlayer() {
    [...]
    exoPlayer.seekTo(currentWindow, playbackPosition)
    exoPlayer.addListener(playbackStateListener)
    [...]
}

繰り返しになりますが、プレーヤーからの宙ぶらりんの参照(これはメモリリークを引き起こす可能性があります)を回避するために、コードを整理する必要があります。

  1. releasePlayer 内のリスナーを削除します。

PlayerActivity.kt

private void releasePlayer() {
 player?.run {
   [...]
   removeListener(playbackStateListener)
   release()
 }
  player = null
}
  1. logcat を開き、アプリを実行します。
  2. UI コントロールを使用して、再生をシークし、一時停止し、再開します。ログで再生状態の変化を確認できます。

もっと知識を深める

ExoPlayer は、ユーザーの再生エクスペリエンスを理解するために役立つリスナーを他にもいくつか備えています。たとえば、音声動画のリスナーや、すべてのリスナーからのコールバックを含む AnalyticsListener があります。最も重要なメソッドを次にいくつか示します。

  • onRenderedFirstFrame は、動画の最初のフレームがレンダリングされたときに呼び出されます。これを使用して、意味のあるコンテンツが画面に表示されるまでにユーザーが待つ必要がある時間を計算できます。
  • onDroppedVideoFrames は、動画フレームがドロップしたときに呼び出されます。ドロップしたフレームは、再生中にジャンクが発生し、ユーザー エクスペリエンスが低下している可能性を示します。
  • onAudioUnderrun は、音声のアンダーランが発生したときに呼び出されます。アンダーランはサウンドに可聴グリッチを生じさせ、ドロップした動画フレームよりもユーザーが気付きやすくなります。

AnalyticsListener は、addAnalyticsListenerplayer に追加できます。音声リスナーと動画リスナーにも、対応するメソッドがあります。

アプリとユーザーにとってどのようなイベントが重要かを検討してください。詳しくは、プレーヤー イベントのリッスンをご覧ください。イベント リスナーについては以上です。

7. ユーザー インターフェースをカスタマイズする

これまでは、ExoPlayer の PlayerControlView を使用して再生コントローラをユーザーに表示していました。

bcfe17eebcad9e13.png

スクリーンショット: デフォルトの再生コントローラ

これらのコントロールの機能または外観を変更したい場合はどうすればよいでしょうか?幸いにも、これらのコントロールは高度なカスタマイズが可能です。

最初に行う単純なカスタマイズは、コントローラをまったく使用しないようにすることです。これは、activity_player.xml 内の PlayerView 要素の use_controller 属性を使用すれば、簡単に行えます。

  1. use_controllerfalse に設定すると、コントロールは表示されなくなります。

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   [...]
   app:use_controller="false"/>
  1. FrameLayout に次の名前空間を追加します。

activity_player.xml

<FrameLayout
  [...]
  xmlns:app="http://schemas.android.com/apk/res-auto">

では、やってみましょう。

動作をカスタマイズする

PlayerControlView には、その動作に影響するいくつかの属性があります。たとえば、show_timeout を使用すると、ユーザーが最後にコントロールを操作してからそのコントロールが非表示になるまでの遅延をミリ秒単位でカスタマイズできます。手順は次のとおりです。

  1. app:use_controller="false" を削除します。
  2. プレーヤー ビューを変更して show_timeout を使用するようにします。

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:show_timeout="10000"/>

PlayerControlView の属性は、プログラムで設定することもできます。

外観をカスタマイズする

まずは動作をカスタマイズしました。しかし、PlayerControlView の外観や表示されるボタンを変更する場合はどうでしょうか?PlayerControlView の実装はボタンの存在を想定していないので、既存のボタンを削除して新しいボタンを追加するのは簡単です。

PlayerControlView をカスタマイズする方法は次のとおりです。

  1. player-lib/res/layout/ フォルダに、新しいレイアウト ファイル custom_player_control_view.xml を作成します。
  2. レイアウト フォルダのコンテキスト メニューから、[New] > [Layout resource file] を選択し、custom_player_control_view.xml という名前を付けます。

ae1e3795726d4e4e.png

スクリーンショット: プレーヤー コントロール ビューのレイアウト ファイルが作成されました

  1. こちらにある元のレイアウト ファイルcustom_player_control_view.xml にコピーします。
  2. ID が @id/exo_prev@id/exo_nextImageButton 要素を削除します。

カスタム レイアウトを使用するには、activity_player.xml ファイルで PlayerView 要素の属性 app:controller_layout_id を設定する必要があります。

  1. 次のコード スニペットに示すように、カスタム ファイルのレイアウト ID を使用します。

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:controller_layout_id="@layout/custom_player_control_view"/>
  1. アプリを再起動します。プレーヤー コントロール ビューから「前へ」ボタンと「次へ」ボタンがなくなりました。

89e6535a22c8e321.png

スクリーンショット: 「前へ」ボタンと「次へ」ボタンがなくなったカスタム プレーヤー コントロール ビュー

任意の希望する変更をレイアウト ファイルで適用できます。デフォルトでは、Android テーマの色が選択されています。アプリのデザインに応じて、この色をオーバーライドできます。

  1. ImageButton 要素に android:tint 属性を追加します。

custom_player_control_view.xml

<ImageButton android:id="@id/exo_rew"
   android:tint="#FF00A6FF"
   style="@style/ExoMediaButton.Rewind"/>
  1. カスタム ファイル内にある android:textColor 属性をすべて同じ色(#FF00A6FF)に変更します。

custom_player_control_view.xml

<TextView android:id="@id/exo_position"
   [...]
   android:textColor="#FF00A6FF"/>
<TextView android:id="@id/exo_duration"
   [...]
   android:textColor="#FF00A6FF"/>
  1. アプリを実行します。きれいに色付けされた UI コンポーネントが表示されます。

e9835d65d6dd0634.png

スクリーンショット: 色付けされたボタンとテキストビュー

デフォルトのスタイルをオーバーライドする

これまでは、カスタム レイアウト ファイルを作成し、activity_player.xmlcontroller_layout_id を使用してそれを参照しました。

もう一つのアプローチとして、PlayerControlView が使用するデフォルトのレイアウト ファイルをオーバーライドする方法があります。PlayerControlView のソースコードを見ると、レイアウトに R.layout.exo_player_control_view を使用していることがわかります。独自のレイアウト ファイルを同じファイル名で作成すると、PlayerControlView は代わりにそのファイルを使用します。

  1. 先ほど追加した controller_layout_id 属性を削除します。
  2. ファイル custom_player_control_view.xml を削除します。

activity_player.xmlPlayerView は次のようになります。

activity_player.xml

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/video_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>
  1. ライブラリ モジュール player-libres/layout フォルダに、exo_player_control_view.xml という名前のファイルを作成します。
  2. exo_player_control_view.xml に次のコードを挿入して、再生ボタン、一時停止ボタン、ロゴを含む ImageView を追加します。

exo_player**_control_view.xml**

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_gravity="bottom"
   android:layoutDirection="ltr"
   android:background="#CC000000"
   android:orientation="vertical">

 <LinearLayout
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:paddingTop="4dp"
   android:orientation="horizontal">

   <ImageButton android:id="@id/exo_play"
      style="@style/ExoMediaButton.Play"/>

   <ImageButton android:id="@id/exo_pause"
      style="@style/ExoMediaButton.Pause"/>

 </LinearLayout>

 <ImageView
     android:contentDescription="@string/logo"
     android:src="@drawable/google_logo"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"/>

</LinearLayout>

上記のコードは、ここに独自の要素を追加し、それらを標準のコントロール要素と混合する方法を示しています。以上で、ExoPlayerView はカスタム コントロールに加えて、コントロールの操作が保持されたときに非表示と表示を行うすべてのロジックを使用するようになりました。

8. 完了

お疲れさまでした。ExoPlayer をアプリに統合する方法について多くのことを習得しました。

詳細

ExoPlayer について詳しく知るには、デベロッパー ガイドソースコードを確認し、ExoPlayer ブログを購読してください。