ExoPlayer รองรับ HLS ที่มีรูปแบบคอนเทนเนอร์หลายรูปแบบ นอกจากนี้ รูปแบบตัวอย่างเสียงและวิดีโอที่อยู่ในคอนเทนเนอร์ต้องได้รับการรองรับด้วย (ดูรายละเอียดในส่วนรูปแบบตัวอย่าง) เราขอแนะนำอย่างยิ่งให้ผู้ผลิตเนื้อหา HLS สร้างสตรีม HLS คุณภาพสูงตามที่อธิบายไว้ในบล็อกโพสต์นี้
| ฟีเจอร์ | รองรับ | ความคิดเห็น |
|---|---|---|
| คอนเทนเนอร์ | ||
| MPEG-TS | ใช่ | |
| FMP4/CMAF | ใช่ | |
| ADTS (AAC) | ใช่ | |
| MP3 | ใช่ | |
| คำบรรยายแทนเสียง / คำบรรยาย | ||
| CEA-608 | ใช่ | |
| CEA-708 | ใช่ | |
| WebVTT | ใช่ | |
| ข้อมูลเมตา | ||
| ID3 | ใช่ | |
| SCTE-35 | ไม่ | |
| การปกป้องเนื้อหา | ||
| AES-128 | ใช่ | |
| Sample AES-128 | ไม่ | |
| Widevine | ใช่ | API 19 ขึ้นไป (รูปแบบ "cenc") และ 25 ขึ้นไป (รูปแบบ "cbcs") |
| PlayReady SL2000 | ใช่ | Android TV เท่านั้น |
| การควบคุมเซิร์ฟเวอร์ | ||
| การอัปเดตเดลต้า | ใช่ | |
| การบล็อกการโหลดเพลย์ลิสต์ซ้ำ | ใช่ | |
| การบล็อกการโหลดคำแนะนำการโหลดล่วงหน้า | ใช่ | ยกเว้นช่วงไบต์ที่มีความยาวไม่แน่นอน |
| การแทรกโฆษณา | ||
| การแทรกโฆษณาตามคำแนะนำของเซิร์ฟเวอร์ (โฆษณาคั่นระหว่างหน้า) | เพียงบางส่วน | เฉพาะ VOD ที่มี X-ASSET-URI
ระบบจะเพิ่มไลฟ์สดและ X-ASSET-LIST ในภายหลัง |
| โฆษณาฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์ของ IMA | ใช่ | คู่มือการแทรกโฆษณา |
| การเล่นแบบสด | ||
| การเล่นไลฟ์สดปกติ | ใช่ | |
| HLS ที่มีเวลาในการตอบสนองต่ำ (Apple) | ใช่ | |
| HLS ที่มีเวลาในการตอบสนองต่ำ (ชุมชน) | ไม่ | |
| ข้อมูลไคลเอ็นต์สื่อทั่วไป CMCD | ใช่ | คู่มือการผสานรวม CMCD |
การใช้ MediaItem
หากต้องการเล่นสตรีม HLS คุณต้องใช้โมดูล HLS
Kotlin
implementation("androidx.media3:media3-exoplayer-hls:1.11.1")
ดึงดูด
implementation "androidx.media3:media3-exoplayer-hls:1.11.1"
จากนั้นคุณจะสร้าง MediaItem สำหรับ URI ของเพลย์ลิสต์ HLS และส่งไปยังเพลเยอร์ได้
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 คุณสามารถส่ง MimeTypes.APPLICATION_M3U8 ไปยัง setMimeType ของ MediaItem.Builder เพื่อระบุประเภทของเนื้อหาอย่างชัดเจน
URI ของรายการสื่ออาจชี้ไปยังเพลย์ลิสต์สื่อหรือเพลย์ลิสต์แบบหลายตัวแปรก็ได้ หาก URI ชี้ไปยังเพลย์ลิสต์แบบหลายตัวแปรที่ประกาศแท็ก #EXT-X-STREAM-INF หลายแท็ก ExoPlayer จะปรับเปลี่ยนระหว่างตัวแปรต่างๆ โดยอัตโนมัติ โดยพิจารณาทั้งแบนด์วิดท์ที่พร้อมใช้งานและความสามารถของอุปกรณ์
การใช้ HlsMediaSource
หากต้องการตัวเลือกการปรับแต่งเพิ่มเติม คุณสามารถสร้าง HlsMediaSource และส่งไปยังโปรแกรมเล่นโดยตรงแทน MediaItem
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();
การเข้าถึงไฟล์ Manifest
คุณสามารถเรียกข้อมูลไฟล์ Manifest ปัจจุบันได้โดยเรียกใช้ Player.getCurrentManifest
สำหรับ HLS คุณควรแคสต์ออบเจ็กต์ที่แสดงผลไปยัง HlsManifest ระบบจะเรียกใช้ Callback onTimelineChanged ของ Player.Listener ด้วยเมื่อใดก็ตามที่มีการโหลดไฟล์ Manifest ซึ่งจะเกิดขึ้น 1 ครั้งสำหรับเนื้อหาออนดีมานด์ และอาจเกิดขึ้นหลายครั้งสำหรับเนื้อหาสด ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่แอปสามารถดำเนินการบางอย่างเมื่อใดก็ตามที่มีการโหลดไฟล์ Manifest
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 ของสตรีมมาให้เรา เพื่อให้เราเพิ่มการสนับสนุนสำหรับสตรีมของคุณได้
ใช้ MediaItem กับ Playlist API
วิธีที่สะดวกที่สุดในการเล่นสตรีม HLS ที่มีโฆษณาคั่นระหว่างหน้าคือการสร้างอินสแตนซ์ ExoPlayer ด้วย HlsInterstitialsAdsLoader.AdsMediaSourceFactory
ซึ่งจะช่วยให้ใช้ playlist API ที่อิงตาม MediaItem ของอินเทอร์เฟซ Player เพื่อเล่นโฆษณาคั่นระหว่างหน้า HLS ได้
คุณสามารถแทรก MediaSource.Factory ของ ExoPlayer ลงในเครื่องมือสร้างเมื่อสร้างอินสแตนซ์เพลเยอร์ได้โดยทำดังนี้
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 และส่งให้ ExoPlayer โดยใช้ API เพลย์ลิสต์ตามแหล่งที่มาของสื่อเพื่อรองรับโฆษณาคั่นระหว่างหน้าได้
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 a 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. } }
ดู JavaDoc ของ HlsInterstitialsAdsLoader.Listener เพื่อดูเอกสารประกอบโดยละเอียดของ Callback ที่ใช้ได้ทั้งหมด
จากนั้นคุณจะเพิ่ม Listener ลงในตัวโหลดโฆษณาได้โดยทำดังนี้
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 ซ้ำได้สำหรับอินสแตนซ์เพลเยอร์หลายรายการที่สร้างแหล่งที่มาของสื่อหลายแหล่งซึ่งต้องโหลดโฆษณา
คุณสามารถสร้างอินสแตนซ์ได้ เช่น ในเมธอด onCreate ของ Activity แล้วนำมาใช้ซ้ำสำหรับอินสแตนซ์เพลเยอร์หลายรายการ ซึ่งจะทำงานได้ตราบใดที่อินสแตนซ์ผู้เล่นคนเดียวใช้งานอยู่พร้อมกัน ซึ่งมีประโยชน์สำหรับกรณีการใช้งานทั่วไปเมื่อแอปทำงานอยู่เบื้องหลัง อินสแตนซ์เพลเยอร์จะถูกทำลาย และระบบจะสร้างอินสแตนซ์ใหม่เมื่อแอปทำงานอยู่เบื้องหน้าอีกครั้ง
การกลับมาเล่นต่อโดยมีสถานะการเล่นโฆษณา
เพื่อหลีกเลี่ยงไม่ให้ผู้ใช้ต้องดูโฆษณาซ้ำ คุณสามารถบันทึกและกู้คืนสถานะการเล่นโฆษณาได้เมื่อสร้างเพลเยอร์ใหม่ ซึ่งทำได้โดยการเรียกใช้ getAdsResumptionStates() เมื่อกำลังจะปล่อยเพลเยอร์และจัดเก็บออบเจ็กต์ AdsResumptionState ที่แสดงผล เมื่อสร้างเพลเยอร์อีกครั้ง คุณจะคืนค่าสถานะได้โดยเรียกใช้ addAdResumptionState() ในอินสแตนซ์ตัวโหลดโฆษณา AdsResumptionState สามารถรวมกลุ่มได้ จึงจัดเก็บไว้ในแพ็กเกจ onSaveInstanceState ของ Activity ได้ โปรดทราบว่าระบบรองรับการกลับมาเล่นโฆษณาต่อสำหรับสตรีม 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 @Suppress("DEPRECATION") // getParcelableArrayList without class type is deprecated in API 33+ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create the ads loader instance. hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(this) // Restore ad resumption states. val bundles = if (Build.VERSION.SDK_INT >= 33) { savedInstanceState?.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY, Bundle::class.java) } else { savedInstanceState?.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY) } adsResumptionStates = bundles?.map(HlsInterstitialsAdsLoader.AdsResumptionState::fromBundle) } 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; @SuppressWarnings( "deprecation") // getParcelableArrayList without class type is deprecated in API 33+ @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; if (Build.VERSION.SDK_INT >= 33) { bundles = savedInstanceState.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY, Bundle.class); } else { 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 ได้ อ่านโพสต์ Medium เกี่ยวกับการเล่น HLS ใน ExoPlayer เพื่อดูคำอธิบายทั้งหมด ประเด็นหลักมีดังนี้
- ใช้ระยะเวลาของกลุ่มที่แน่นอน
- ใช้สตรีมสื่อแบบต่อเนื่อง หลีกเลี่ยงการเปลี่ยนแปลงโครงสร้างสื่อในกลุ่มต่างๆ
- ใช้แท็ก
#EXT-X-INDEPENDENT-SEGMENTS - แนะนำให้ใช้สตรีมแบบ Demuxed แทนไฟล์ที่มีทั้งวิดีโอและเสียง
- ใส่ข้อมูลทั้งหมดที่คุณสามารถใส่ได้ในเพลย์ลิสต์เวอร์ชันหลายตัวแปร
หลักเกณฑ์ต่อไปนี้มีผลกับไลฟ์สดโดยเฉพาะ
- ใช้แท็ก
#EXT-X-PROGRAM-DATE-TIME - ใช้แท็ก
#EXT-X-DISCONTINUITY-SEQUENCE - เปิดให้ใช้ข้อเสนอเป็นระยะเวลานาน 1 นาทีขึ้นไปก็ยอดเยี่ยมแล้ว