ExoPlayer は、複数のコンテナ形式の HLS をサポートしています。含まれる音声と動画のサンプル形式もサポートされている必要があります(詳しくは、サンプル形式のセクションをご覧ください)。HLS コンテンツの制作者には、こちらのブログ投稿で説明されているように、高品質の HLS ストリームを生成することを強くおすすめします。
| 機能 | サポート対象 | コメント |
|---|---|---|
| コンテナ | ||
| MPEG-TS | ○ | |
| FMP4/CMAF | ○ | |
| ADTS(AAC) | ○ | |
| MP3 | ○ | |
| 字幕 / クローズド キャプション | ||
| CEA-608 | ○ | |
| CEA-708 | ○ | |
| WebVTT | ○ | |
| メタデータ | ||
| ID3 | ○ | |
| SCTE-35 | いいえ | |
| コンテンツの保護 | ||
| AES-128 | ○ | |
| AES-128 のサンプル | いいえ | |
| Widevine | ○ | API 19+(「cenc」スキーム)および 25+(「cbcs」スキーム) |
| PlayReady SL2000 | ○ | Android TV のみ |
| サーバー制御 | ||
| 差分更新 | ○ | |
| 再生リストの再読み込みをブロック | ○ | |
| プリロード ヒントの読み込みをブロック | ○ | 長さが未定義のバイト範囲を除く |
| 広告挿入 | ||
| サーバーガイド付き広告挿入 (インタースティシャル) | 部分的 | X-ASSET-URI の VOD のみ。ライブ配信と X-ASSET-LIST は後で追加されます。 |
| IMA サーバーサイド広告とクライアントサイド広告 | ○ | 広告挿入ガイド |
| ライブ再生 | ||
| 通常のライブ再生 | ○ | |
| 低遅延 HLS(Apple) | ○ | |
| 低レイテンシ HLS(コミュニティ) | いいえ | |
| 共通メディア クライアント データ CMCD | ○ | CMCD 統合ガイド |
MediaItem の使用
HLS ストリームを再生するには、HLS モジュールに依存する必要があります。
Kotlin
implementation("androidx.media3:media3-exoplayer-hls:1.9.2")
Groovy
implementation "androidx.media3:media3-exoplayer-hls:1.9.2"
その後、HLS 再生リスト URI の MediaItem を作成して、プレーヤーに渡すことができます。
Kotlin
// Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)) // Prepare the player. player.prepare()
Java
// Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)); // Prepare the player. player.prepare();
URI が .m3u8 で終わらない場合は、MediaItem.Builder の setMimeType に MimeTypes.APPLICATION_M3U8 を渡して、コンテンツのタイプを明示的に示すことができます。
メディア アイテムの URI は、メディア プレイリストまたはマルチバリアント プレイリストのいずれかを指す場合があります。URI が複数の #EXT-X-STREAM-INF タグを宣言するマルチバリアント プレイリストを指している場合、ExoPlayer は利用可能な帯域幅とデバイスの機能の両方を考慮して、バリアント間で自動的に適応します。
HlsMediaSource を使用する
カスタマイズ オプションをさらに追加するには、MediaItem の代わりに HlsMediaSource を作成してプレーヤーに直接渡します。
Kotlin
// Create a data source factory. val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory() // Create a HLS media source pointing to a playlist uri. val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)) // Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource) // Prepare the player. player.prepare()
Java
// Create a data source factory. DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory(); // Create a HLS media source pointing to a playlist uri. HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)); // Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource); // Prepare the player. player.prepare();
マニフェストへのアクセス
現在のマニフェストを取得するには、Player.getCurrentManifest を呼び出します。HLS の場合は、返されたオブジェクトを HlsManifest にキャストする必要があります。また、マニフェストが読み込まれるたびに、Player.Listener の onTimelineChanged コールバックも呼び出されます。オンデマンド コンテンツの場合は 1 回、ライブ コンテンツの場合は複数回発生する可能性があります。次のコード スニペットは、マニフェストが読み込まれるたびにアプリが何らかの処理を行う方法を示しています。
Kotlin
player.addListener( object : Player.Listener { override fun onTimelineChanged( timeline: Timeline, @Player.TimelineChangeReason reason: Int, ) { val manifest = player.currentManifest if (manifest is HlsManifest) { // Do something with the manifest. } } } )
Java
player.addListener( new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { Object manifest = player.getCurrentManifest(); if (manifest != null) { HlsManifest hlsManifest = (HlsManifest) manifest; // Do something with the manifest. } } });
インタースティシャルを含む HLS ストリームを再生する
HLS 仕様では、メディア再生リストにインタースティシャル情報を含めるために使用できる HLS インタースティシャルが定義されています。ExoPlayer はデフォルトでこれらのインタースティシャルを無視します。サポートは HlsInterstitialsAdsLoader を使用して追加できます。仕様のすべての機能が最初からサポートされるわけではありません。ストリームのサポートが不足している場合は、GitHub で問題を報告し、ストリーム URI をお送りください。ストリームのサポートを追加いたします。
プレイリスト API で MediaItem を使用する
インタースティシャルを含む HLS ストリームを再生する最も便利な方法は、HlsInterstitialsAdsLoader.AdsMediaSourceFactory を使用して ExoPlayer インスタンスをビルドすることです。これにより、Player インターフェースの MediaItem ベースのプレイリスト API を使用して HLS インタースティシャルを再生できます。
プレーヤー インスタンスのビルド時に、ExoPlayer の MediaSource.Factory をビルダーに挿入できます。
Kotlin
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, DefaultMediaSourceFactory(context), ) // Build player with interstitials media source factory val player = ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build() // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player) playerView.setPlayer(player)
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, new DefaultMediaSourceFactory(context)); // Build player with interstitials media source factory ExoPlayer player = new ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player);
このようなプレーヤーの設定では、HLS インタースティシャルを再生するには、プレーヤーで AdsConfiguration を使用してメディア アイテムを設定するだけです。
Kotlin
// Build an HLS media item with ads configuration to be played. player.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within playlist .build() ) .build() ) player.prepare() player.play()
Java
// Build an HLS media item with ads configuration to be played. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within playlist .build()) .build()); player.prepare(); player.play();
メディアソース ベースの API を使用する
または、デフォルトのメディアソース ファクトリをオーバーライドせずに ExoPlayer インスタンスをビルドすることもできます。インタースティシャルをサポートするために、アプリは HlsInterstitialsAdsLoader.AdsMediaSourceFactory を直接使用して MediaSource を作成し、メディアソース ベースのプレイリスト API を使用して ExoPlayer に提供できます。
Kotlin
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context, ) // Build player with default media source factory. val player = ExoPlayer.Builder(context).build() // Create an media source from an HLS media item with ads configuration. val mediaSource = hlsMediaSourceFactory.createMediaSource( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") .build() ) .build() ) // Set the media source on the player. player.setMediaSource(mediaSource) player.prepare() player.play()
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context); // Build player with default media source factory. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Create an media source from an HLS media item with ads configuration. MediaSource mediaSource = hlsMediaSourceFactory.createMediaSource( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") .build()) .build()); // Set the media source on the player. player.setMediaSource(mediaSource); player.prepare(); player.play();
広告イベントをリッスンする
Listener を HlsInterstitialsAdsLoader に追加して、HLS インタースティシャル再生に関するステータス変更のイベントをモニタリングできます。これにより、アプリや SDK は、再生された広告、読み込まれているアセット リスト、準備中の広告メディア ソースをトラッキングしたり、アセット リストの読み込みエラーや広告の準備エラーを検出したりできます。また、広告メディア ソースから出力されたメタデータを受信して、広告再生のきめ細かい検証や広告再生の進行状況のトラッキングを行うことができます。
Kotlin
class AdsLoaderListener : HlsInterstitialsAdsLoader.Listener { override fun onStart(mediaItem: MediaItem, adsId: Any, adViewProvider: AdViewProvider) { // Do something when HLS media item with interstitials is started. } override fun onMetadata( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, metadata: Metadata, ) { // Do something with metadata that is emitted by the ad media source of the given ad. } override fun onAdCompleted( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, ) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks of HlsInterstitialsAdsLoader.Listener. override fun onStop(mediaItem: MediaItem, adsId: Any, adPlaybackState: AdPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
Java
@OptIn(markerClass = UnstableApi.class) private static class AdsLoaderListener implements HlsInterstitialsAdsLoader.Listener { // implement HlsInterstitialsAdsLoader.Listener @Override public void onStart(MediaItem mediaItem, Object adsId, AdViewProvider adViewProvider) { // Do something when HLS media item with interstitials is started. } @Override public void onMetadata( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup, Metadata metadata) { // Do something with metadata that is emitted by the ad media source of the given ad. } @Override public void onAdCompleted( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks @Override public void onStop(MediaItem mediaItem, Object adsId, AdPlaybackState adPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
使用可能なすべてのコールバックの詳細なドキュメントについては、HlsInterstitialsAdsLoader.Listener の JavaDoc をご覧ください。
リスナーを広告ローダーに追加できます。
Kotlin
val listener = AdsLoaderListener() // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener)
Java
AdsLoaderListener listener = new AdsLoaderListener(); // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener);
HlsInterstitialsAdsLoader ライフサイクル
HlsInterstitialsAdsLoader または HlsInterstitialsAdsLoader.AdsMediaSourceFactory のインスタンスは、広告を読み込む必要がある複数のメディアソースを作成する複数のプレーヤー インスタンスで再利用できます。
たとえば、Activity の onCreate メソッドでインスタンスを作成し、複数のプレーヤー インスタンスで再利用できます。これは、同時に 1 つのプレーヤー インスタンスで使用されている場合に機能します。これは、アプリがバックグラウンドに移行し、プレーヤー インスタンスが破棄されてから、アプリが再びフォアグラウンドに移行したときに新しいインスタンスが作成されるという一般的なユースケースで役立ちます。
広告の再生状態を使用した再生の再開
ユーザーが広告を再度視聴する必要がないように、プレーヤーが再作成されたときに広告の再生状態を保存して復元できます。これは、プレーヤーが解放される直前に getAdsResumptionStates() を呼び出し、返された AdsResumptionState オブジェクトを保存することで行われます。プレーヤーが再作成されると、広告ローダー インスタンスで addAdResumptionState() を呼び出すことで状態を復元できます。AdsResumptionState はバンドル可能であるため、Activity の onSaveInstanceState バンドルに保存できます。広告の再開は VOD ストリームでのみサポートされています。
Kotlin
class HlsInterstitialsActivity : Activity() { companion object { const val ADS_RESUMPTION_STATE_KEY = "ads_resumption_state" } private var hlsInterstitialsAdsLoader: HlsInterstitialsAdsLoader? = null private var playerView: PlayerView? = null private var player: ExoPlayer? = null private var adsResumptionStates: List<HlsInterstitialsAdsLoader.AdsResumptionState>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create the ads loader instance. hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(this) // Restore ad resumption states. savedInstanceState?.getParcelableArrayList<Bundle>(ADS_RESUMPTION_STATE_KEY)?.let { bundles -> adsResumptionStates = bundles.map { HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(it) } } } override fun onStart() { super.onStart() // Build a player and set it on the ads loader. initializePlayer() hlsInterstitialsAdsLoader?.setPlayer(player) // Add any stored ad resumption states to the ads loader. adsResumptionStates?.forEach { hlsInterstitialsAdsLoader?.addAdResumptionState(it) } adsResumptionStates = null // Consume the states } override fun onStop() { super.onStop() // Get ad resumption states. adsResumptionStates = hlsInterstitialsAdsLoader?.adsResumptionStates releasePlayer() } override fun onDestroy() { // Release the ads loader when not used anymore. hlsInterstitialsAdsLoader?.release() hlsInterstitialsAdsLoader = null super.onDestroy() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // Store the ad resumption states. adsResumptionStates?.let { outState.putParcelableArrayList( ADS_RESUMPTION_STATE_KEY, ArrayList(it.map(HlsInterstitialsAdsLoader.AdsResumptionState::toBundle)), ) } } fun initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView!!, /* context= */ this, ) // Build player with interstitials media source player = ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build() // Set the player on the ads loader. hlsInterstitialsAdsLoader?.setPlayer(player) playerView?.player = player } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player?.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build() ) .build() ) player?.prepare() player?.play() } fun releasePlayer() { player?.release() player = null hlsInterstitialsAdsLoader?.setPlayer(null) playerView?.player = null } }
Java
@OptIn(markerClass = UnstableApi.class) public static class HlsInterstitialsActivity extends Activity { public static final String ADS_RESUMPTION_STATE_KEY = "ads_resumption_state"; @Nullable private HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader; @Nullable private PlayerView playerView; @Nullable private ExoPlayer player; private List<HlsInterstitialsAdsLoader.AdsResumptionState> adsResumptionStates; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create the ads loader instance. hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(this); // Restore ad resumption states. if (savedInstanceState != null) { ArrayList<Bundle> bundles = savedInstanceState.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY); if (bundles != null) { adsResumptionStates = new ArrayList<>(); for (Bundle bundle : bundles) { adsResumptionStates.add( HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(bundle)); } } } } @Override protected void onStart() { super.onStart(); // Build a player and set it on the ads loader. initializePlayer(); // Add any stored ad resumption states to the ads loader. if (adsResumptionStates != null) { for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { hlsInterstitialsAdsLoader.addAdResumptionState(state); } adsResumptionStates = null; // Consume the states } } @Override protected void onStop() { super.onStop(); // Get ad resumption states before releasing the player. if (hlsInterstitialsAdsLoader != null) { adsResumptionStates = hlsInterstitialsAdsLoader.getAdsResumptionStates(); } releasePlayer(); } @Override protected void onDestroy() { // Release the ads loader when not used anymore. if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.release(); hlsInterstitialsAdsLoader = null; } super.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Store the ad resumption states. if (adsResumptionStates != null) { ArrayList<Bundle> bundles = new ArrayList<>(); for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { bundles.add(state.toBundle()); } outState.putParcelableArrayList(ADS_RESUMPTION_STATE_KEY, bundles); } } private void initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView, /* context= */ this); // Build player with interstitials media source player = new ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player); } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build()) .build()); player.prepare(); player.play(); } private void releasePlayer() { if (player != null) { player.release(); player = null; } if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.setPlayer(null); } if (playerView != null) { playerView.setPlayer(null); } } }
再生をカスタマイズする
ExoPlayer には、アプリのニーズに合わせて再生エクスペリエンスを調整するための複数の方法が用意されています。例については、カスタマイズ ページをご覧ください。
チャンクレス準備を無効にする
デフォルトでは、ExoPlayer はチャンクレス準備を使用します。つまり、ExoPlayer はマルチバリアント プレイリストの情報のみを使用してストリームを準備します。これは、#EXT-X-STREAM-INF タグに CODECS 属性が含まれている場合に機能します。
メディア セグメントに、#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS タグを使用してマルチバリアント再生リストで宣言されていない多重化されたクローズド キャプション トラックが含まれている場合は、この機能を無効にする必要があります。そうしないと、これらの字幕トラックは検出されず、再生されません。次のスニペットに示すように、HlsMediaSource.Factory でチャンクなしの準備を無効にできます。ExoPlayer はこれらの追加トラックを検出するためにメディア セグメントをダウンロードする必要があるため、起動時間が長くなります。そのため、代わりにマルチバリアント プレイリストでクローズド キャプション トラックを宣言することをおすすめします。
Kotlin
val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri))
Java
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri));
高品質な HLS コンテンツを作成する
ExoPlayer を最大限に活用するには、HLS コンテンツを改善するためのガイドラインに沿って対応する必要があります。詳細については、ExoPlayer での HLS 再生に関する Medium の投稿をご覧ください。主なポイントは次のとおりです。
- 正確なセグメントの長さを使用します。
- 連続したメディア ストリームを使用します。セグメント間でメディア構造を変更しないでください。
#EXT-X-INDEPENDENT-SEGMENTSタグを使用します。- 動画と音声の両方を含むファイルではなく、デマルチプレックスされたストリームを優先します。
- マルチバリエーション再生リストに、可能な限りすべての情報を含めます。
ライブ ストリームには、次のガイドラインが適用されます。
#EXT-X-PROGRAM-DATE-TIMEタグを使用します。#EXT-X-DISCONTINUITY-SEQUENCEタグを使用します。- ライブ ウィンドウを長くします。1 分以上が理想的です。