1. 始める前に
スクリーンショット: 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 プロジェクトに加えて、必要なすべてのリソースが格納されています。
プロジェクトをインポートする
- Android Studio を起動します。
- [File] > [New] > [Import Project] をクリックします。
- ルートの
build.gradle
ファイルを選択します。
スクリーンショット: インポート時のプロジェクト構造
ビルドが完了すると、6 つのモジュールがあることを確認できます。すなわち、app
モジュール(タイプはアプリ)と、exoplayer-codelab-N
という名前の 5 つのモジュール(N
は 00
から 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 のような機能を利用することも可能になります。
- アプリをデプロイして実行し、すべてが正常であることを確認します。アプリでは、画面が黒い背景で塗りつぶされます。
スクリーンショット: 黒いアプリが実行される
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 モジュールの追加をご覧ください。
player-lib
モジュールのbuild.gradle
ファイルを開きます。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
を追加する
exoplayer-codelab-00
モジュールから、レイアウト リソース ファイルactivity_player.xml
を開きます。FrameLayout
要素の内部にカーソルを置きます。<PlayerView
と入力し始めると、Android Studio によってPlayerView
要素がオートコンプリートされます。width
とheight
にはmatch_parent
を使用します。- 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 要素を動画ビューと呼びます。
PlayerActivity
で、編集したばかりの XML ファイルから作成されたビューツリーへの参照を取得できます。
PlayerActivity.kt
private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
ActivityPlayerBinding.inflate(layoutInflater)
}
- ビューツリーのルートを、アクティビティのコンテンツ ビューとして設定します。また、
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
を使用して、MediaItem
を player
に追加します。
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.xml
で https://storage.googleapis.com/exoplayer-test-media-0/play.mp3 として定義されていることに注意してください。
アクティビティのライフサイクルで適切に再生する
player
は、メモリ、CPU、ネットワーク接続、ハードウェア コーデックなどの多くのリソースを占有する可能性があります。特に 1 つしかないハードウェア コーデックでは、これらのリソースの多くが不足します。アプリがリソースを使用していないとき(バックグラウンドで実行されているときなど)に、これらのリソースを解放して他のアプリが使用できるようにすることが重要です。
言い換えると、プレーヤーのライフサイクルをアプリのライフサイクルに関連付ける必要があります。この関連付けを実装するには、PlayerActivity
の 4 つのメソッド(onStart
、onResume
、onPause
、onStop
)をオーバーライドします。
PlayerActivity
を開いた状態で、[Code menu] > [Override methods...] をクリックします。onStart
、onResume
、onPause
、onStop
を選択します。- 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
まで待ちます。
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)
}
hideSystemUi
は onResume
で呼び出されるヘルパー メソッドであり、これにより全画面エクスペリエンスを実現できます。
onPause
とonStop
で、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
メソッドを作成する必要があります。このメソッドはプレーヤーのリソースを解放して破棄します。
- アクティビティに次のコードを追加します。
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
に保存した状態情報を初期化時にプレーヤーに提供することだけです。
initializePlayer
に次の行を追加します。
PlayerActivity.kt
private fun initializePlayer() {
[...]
exoPlayer.playWhenReady = playWhenReady
exoPlayer.seekTo(currentWindow, playbackPosition)
exoPlayer.prepare()
}
次のことが起こります。
playWhenReady
は、再生用のリソースがすべて取得されたらすぐに再生を開始するかどうかをプレーヤーに指示します。playWhenReady
の初期値はtrue
なので、アプリが初めて実行されたときは自動的に再生が開始されます。seekTo
は、特定のウィンドウ内の特定の位置までシークするようプレーヤーに指示します。currentWindow
とplaybackPosition
はどちらもゼロに初期化されるので、アプリが初めて実行されたときは最初から再生が開始されます。prepare
は、再生に必要なリソースをすべて取得するようプレーヤーに指示します。
音声を再生する
これで完了です。アプリを起動して MP3 ファイルを再生し、埋め込みアートワークを確認してください。
スクリーンショット: 単一のトラックを再生しているアプリ。
アクティビティのライフサイクルをテストする
アクティビティ ライフサイクルのさまざまな状態すべてでアプリが動作するかどうかをテストします。
- 別のアプリを起動し、アプリをフォアグラウンドに戻します。正しい位置から再開されるでしょうか?
- アプリを一時停止し、バックグラウンドに移動した後、再度フォアグラウンドに戻します。一時停止状態でバックグラウンドに移動した場合、一時停止状態のままになるでしょうか?
- アプリを回転させます。向きを縦向きから横向きに変更してから元に戻すと、どのように動作するでしょうか?
動画を再生する
動画を再生したい場合は、メディア アイテムの URI を MP4 ファイルに変更するだけで、簡単に対応できます。
initializePlayer
の URI をR.string.media_url_mp4
に変更します。- アプリを再起動し、音声の場合と同様に、動画再生をバックグラウンドに移動した後の動作をテストします。
PlayerActivity.kt
private fun initializePlayer() {
[...]
val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
[...]
}
PlayerView
がすべてを処理します。アートワークではなく、動画が全画面表示されます。
スクリーンショット: 動画を再生しているアプリ。
これで完成です。ライフサイクル管理、状態保存、UI コントロールを備え、Android で全画面メディア ストリーミングを行うアプリを作成できました。
4. プレイリストを作成する
現在のアプリは単一のメディア ファイルを再生しますが、複数のメディア ファイルを連続で再生したい場合はどうすればよいでしょうか?そのためには、プレイリストが必要です。
プレイリストを作成するには、addMediaItem
を使用して複数の MediaItem
を player
に追加します。そうすればシームレスな再生が可能になり、バッファリングがバックグラウンドで処理されるため、メディア アイテムの変更時にバッファリング スピナーがユーザーに表示されなくなります。
initializePlayer
に次のコードを追加します。
PlayerActivity.kt
private void initializePlayer() {
[...]
exoPlayer.addMediaItem(mediaItem) // Existing code
val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
exoPlayer.addMediaItem(secondMediaItem);
[...]
}
プレーヤー コントロールの動作をチェックします。 と
を使用して、連続したメディア アイテム間を移動できます。
スクリーンショット:「次へ」ボタンと「前へ」ボタンが表示された再生コントロール
これはかなり便利です。詳しくは、メディア アイテムおよびプレイリストに関するデベロッパー ドキュメントと、Playlist API に関するこちらの記事をご覧ください。
5. アダプティブ ストリーミング
アダプティブ ストリーミングは、利用可能なネットワーク帯域幅に基づいてストリームの品質を変化させながら、メディアをストリーミングする手法です。これにより、ユーザーは帯域幅が許容する最高品質のメディアを体験できます。
通常、同じメディア コンテンツは、品質(ビットレートと解像度)が異なる複数のトラックに分割されます。プレーヤーは、利用可能なネットワーク帯域幅に基づいてトラックを選択します。
各トラックは、特定の期間(通常は 2~10 秒)のチャンクに分割されます。これにより、プレーヤーは利用可能な帯域幅の変化に応じて、トラックをすばやく切り替えることができます。プレーヤーは、シームレスな再生を行うためにこれらのチャンクを結合する処理を行います。
アダプティブなトラック選択
アダプティブ ストリーミングの核心は、現在の環境に最も適したトラックを選択することにあります。アダプティブなトラック選択を使用してアダプティブ ストリーミング メディアを再生するように、アプリを更新します。
- 次のコードで
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_MPD
の MIME タイプを指定する必要があるからです。
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)
}
- アプリを再起動し、DASH でアダプティブな動画ストリーミングが機能していることを確認します。ExoPlayer を使うと実に簡単です。
その他のアダプティブ ストリーミング形式
よく使用されるその他のアダプティブ ストリーミング形式としては HLS(MimeTypes.APPLICATION_M3U8
)と SmoothStreaming(MimeTypes.APPLICATION_SS
)があり、どちらも ExoPlayer でサポートされています。その他のアダプティブ メディアソースの作成方法については、ExoPlayer デモアプリを参照してください。
6. イベントをリッスンする
これまでのステップでは、プログレッシブなメディア ストリームとアダプティブなメディア ストリームをストリーミングする方法を学びました。ExoPlayer は、舞台裏で次のような多くの作業を行っています。
- メモリの割り当て
- コンテナ ファイルのダウンロード
- コンテナからのメタデータの抽出
- データのデコード
- 画面とスピーカー システムへの動画、音声、テキストのレンダリング
ユーザーの再生エクスペリエンスを理解して改善するにあたっては、ExoPlayer が実行時に何をしているかを知ることが役立つ場合があります。
たとえば、次のような方法で再生状態の変化をユーザー インターフェースに反映させたい場合があります。
- プレーヤーがバッファリング状態になったときに読み込みスピナーを表示する
- トラックが終了したときに「次を再生」オプションを含むオーバーレイを表示する
ExoPlayer は、有用なイベントのコールバックを提供するいくつかのリスナー インターフェースを備えています。リスナーを使用すると、プレーヤーがどのような状態にあるかをログに記録できます。
リッスンする
PlayerActivity
クラスの外部でTAG
定数を作成します。これは後でロギングに使用します。
PlayerActivity.kt
private const val TAG = "PlayerActivity"
PlayerActivity
クラスの外部で、ファクトリ関数にPlayer.EventListener
インターフェースを実装します。これは、エラーや再生状態の変化といった重要なプレーヤー イベントを通知するために使用します。- 次のコードを追加して
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")
}
}
PlayerActivity
で、タイプがPlayer.EventListener
のプライベート メンバーを宣言します。
PlayerActivity.kt
class PlayerActivity : AppCompatActivity() {
[...]
private val playbackStateListener: Player.EventListener = playbackStateListener()
}
onPlaybackStateChanged
は、再生状態が変化したときに呼び出されます。新しい状態は playbackState
パラメータで提供されます。
プレーヤーは次の 4 つの状態のいずれかになります。
状態 | 説明 |
| プレーヤーはインスタンス化されていますが、まだ準備されていません。 |
| 十分なデータがバッファリングされていないため、プレーヤーは現在の位置から再生することができません。 |
| プレイヤーは現在の位置からすぐに再生できます。これは、プレーヤーの playWhenReady プロパティが |
| プレーヤーはメディアの再生を終了しました。 |
リスナーを登録する
コールバックが呼び出されるためには、playbackStateListener
をプレーヤーに登録する必要があります。これは initializePlayer
で行います。
- 再生の準備をする前にリスナーを登録します。
PlayerActivity.kt
private void initializePlayer() {
[...]
exoPlayer.seekTo(currentWindow, playbackPosition)
exoPlayer.addListener(playbackStateListener)
[...]
}
繰り返しになりますが、プレーヤーからの宙ぶらりんの参照(これはメモリリークを引き起こす可能性があります)を回避するために、コードを整理する必要があります。
releasePlayer
内のリスナーを削除します。
PlayerActivity.kt
private void releasePlayer() {
player?.run {
[...]
removeListener(playbackStateListener)
release()
}
player = null
}
- logcat を開き、アプリを実行します。
- UI コントロールを使用して、再生をシークし、一時停止し、再開します。ログで再生状態の変化を確認できます。
もっと知識を深める
ExoPlayer は、ユーザーの再生エクスペリエンスを理解するために役立つリスナーを他にもいくつか備えています。たとえば、音声と動画のリスナーや、すべてのリスナーからのコールバックを含む AnalyticsListener
があります。最も重要なメソッドを次にいくつか示します。
onRenderedFirstFrame
は、動画の最初のフレームがレンダリングされたときに呼び出されます。これを使用して、意味のあるコンテンツが画面に表示されるまでにユーザーが待つ必要がある時間を計算できます。onDroppedVideoFrames
は、動画フレームがドロップしたときに呼び出されます。ドロップしたフレームは、再生中にジャンクが発生し、ユーザー エクスペリエンスが低下している可能性を示します。onAudioUnderrun
は、音声のアンダーランが発生したときに呼び出されます。アンダーランはサウンドに可聴グリッチを生じさせ、ドロップした動画フレームよりもユーザーが気付きやすくなります。
AnalyticsListener
は、addAnalyticsListener
で player
に追加できます。音声リスナーと動画リスナーにも、対応するメソッドがあります。
アプリとユーザーにとってどのようなイベントが重要かを検討してください。詳しくは、プレーヤー イベントのリッスンをご覧ください。イベント リスナーについては以上です。
7. ユーザー インターフェースをカスタマイズする
これまでは、ExoPlayer の PlayerControlView
を使用して再生コントローラをユーザーに表示していました。
スクリーンショット: デフォルトの再生コントローラ
これらのコントロールの機能または外観を変更したい場合はどうすればよいでしょうか?幸いにも、これらのコントロールは高度なカスタマイズが可能です。
最初に行う単純なカスタマイズは、コントローラをまったく使用しないようにすることです。これは、activity_player.xml
内の PlayerView
要素の use_controller
属性を使用すれば、簡単に行えます。
use_controller
をfalse
に設定すると、コントロールは表示されなくなります。
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
[...]
app:use_controller="false"/>
FrameLayout
に次の名前空間を追加します。
activity_player.xml
<FrameLayout
[...]
xmlns:app="http://schemas.android.com/apk/res-auto">
では、やってみましょう。
動作をカスタマイズする
PlayerControlView
には、その動作に影響するいくつかの属性があります。たとえば、show_timeout
を使用すると、ユーザーが最後にコントロールを操作してからそのコントロールが非表示になるまでの遅延をミリ秒単位でカスタマイズできます。手順は次のとおりです。
app:use_controller="false"
を削除します。- プレーヤー ビューを変更して
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
をカスタマイズする方法は次のとおりです。
player-lib/res/layout/
フォルダに、新しいレイアウト ファイルcustom_player_control_view.xml
を作成します。- レイアウト フォルダのコンテキスト メニューから、[New] > [Layout resource file] を選択し、
custom_player_control_view.xml
という名前を付けます。
スクリーンショット: プレーヤー コントロール ビューのレイアウト ファイルが作成されました
- こちらにある元のレイアウト ファイルを
custom_player_control_view.xml
にコピーします。 - ID が
@id/exo_prev
と@id/exo_next
のImageButton
要素を削除します。
カスタム レイアウトを使用するには、activity_player.xml
ファイルで PlayerView
要素の属性 app:controller_layout_id
を設定する必要があります。
- 次のコード スニペットに示すように、カスタム ファイルのレイアウト 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"/>
- アプリを再起動します。プレーヤー コントロール ビューから「前へ」ボタンと「次へ」ボタンがなくなりました。
スクリーンショット: 「前へ」ボタンと「次へ」ボタンがなくなったカスタム プレーヤー コントロール ビュー
任意の希望する変更をレイアウト ファイルで適用できます。デフォルトでは、Android テーマの色が選択されています。アプリのデザインに応じて、この色をオーバーライドできます。
- 各
ImageButton
要素にandroid:tint
属性を追加します。
custom_player_control_view.xml
<ImageButton android:id="@id/exo_rew"
android:tint="#FF00A6FF"
style="@style/ExoMediaButton.Rewind"/>
- カスタム ファイル内にある
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"/>
- アプリを実行します。きれいに色付けされた UI コンポーネントが表示されます。
スクリーンショット: 色付けされたボタンとテキストビュー
デフォルトのスタイルをオーバーライドする
これまでは、カスタム レイアウト ファイルを作成し、activity_player.xml
の controller_layout_id
を使用してそれを参照しました。
もう一つのアプローチとして、PlayerControlView
が使用するデフォルトのレイアウト ファイルをオーバーライドする方法があります。PlayerControlView
のソースコードを見ると、レイアウトに R.layout.exo_player_control_view
を使用していることがわかります。独自のレイアウト ファイルを同じファイル名で作成すると、PlayerControlView
は代わりにそのファイルを使用します。
- 先ほど追加した
controller_layout_id
属性を削除します。 - ファイル
custom_player_control_view.xml
を削除します。
activity_player.xml
の PlayerView
は次のようになります。
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- ライブラリ モジュール
player-lib
のres/layout
フォルダに、exo_player_control_view.xml
という名前のファイルを作成します。 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 ブログを購読してください。