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 เท่านั้น |
การควบคุมเซิร์ฟเวอร์ | ||
การอัปเดต Delta | ใช่ | |
การบล็อกการโหลดเพลย์ลิสต์ซ้ำ | ใช่ | |
การบล็อกการโหลดคำแนะนำในการโหลดล่วงหน้า | ใช่ | ยกเว้นช่วงไบต์ที่มีความยาวที่ไม่ระบุ |
การแทรกโฆษณา | ||
การแสดงโฆษณาที่เซิร์ฟเวอร์แนะนํา (โฆษณาคั่นระหว่างหน้า) | เพียงบางส่วน | VOD ที่มี X-ASSET-URI เท่านั้น
เราจะเพิ่มสตรีมแบบสดและX-ASSET-LIST ในภายหลัง |
โฆษณาฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์ IMA | ใช่ | คู่มือการแทรกโฆษณา |
การเล่นแบบสด | ||
การเล่นแบบสดปกติ | ใช่ | |
HLS แบบเวลาในการตอบสนองต่ำ (Apple) | ใช่ | |
HLS แบบเวลาในการตอบสนองต่ำ (ชุมชน) | ไม่ | |
Common Media Client Data CMCD | ใช่ | คู่มือการผสานรวม CMCD |
การใช้ MediaItem
หากต้องการเล่นสตรีม HLS คุณต้องใช้โมดูล HLS
Kotlin
implementation("androidx.media3:media3-exoplayer-hls:1.7.1")
Groovy
implementation "androidx.media3:media3-exoplayer-hls:1.7.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, @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
กับเพลย์ลิสต์ API
วิธีเล่นสตรีม HLS ที่มีโฆษณาคั่นระหว่างหน้าได้สะดวกที่สุดคือการสร้างอินสแตนซ์ ExoPlayer ด้วย HlsInterstitialsAdsLoader.AdsMediaSourceFactory
ซึ่งจะช่วยให้ใช้ playlist API ของMediaItem
ที่อิงตามอินเทอร์เฟซ Player
เพื่อเล่นโฆษณาคั่นระหว่างหน้า HLS ได้
คุณสามารถแทรก MediaSource.Factory
ของ ExoPlayer
ลงในเครื่องมือสร้างเมื่อสร้างอินสแตนซ์ของโปรแกรมเล่น
Kotlin
hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var hlsMediaSourceFactory =
HlsInterstitialsAdsLoader.AdsMediaSourceFactory(
hlsInterstitialsAdsLoader,
playerView,
DefaultMediaSourceFactory(context),
)
// Build player with interstitials media source factory
player =
ExoPlayer.Builder(context)
.setMediaSourceFactory(hlsMediaSourceFactory)
.build()
// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player)
playerView.setPlayer(player)
Java
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
player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(hlsMediaSourceFactory)
.build();
// Set the player on the ads loader.
hlsInterstitialsAdsLoader.setPlayer(player);
playerView.setPlayer(player);
เมื่อตั้งค่าโปรแกรมเล่นเช่นนี้ การเล่นโฆษณาคั่นระหว่างหน้า HLS จะเป็นการตั้งค่ารายการสื่อที่มี AdsConfiguration
ในโปรแกรมเล่น
Kotlin
player.setMediaItem(
MediaItem.Builder()
.setUri("https://www.example.com/media.m3u8")
.setAdsConfiguration(
AdsConfiguration.Builder(Uri.parse("hls://interstitials"))
.setAdsId("ad-tag-0") // must be unique within playlist
.build())
.build())
player.prepare();
player.play();
Java
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
hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context)
// Create a MediaSource.Factory for HLS streams with interstitials.
var hlsMediaSourceFactory =
HlsInterstitialsAdsLoader.AdsMediaSourceFactory(hlsInterstitialsAdsLoader, playerView, context)
// Build player with default media source factory.
player = new 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(Uri.parse("hls://interstitials"))
.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.
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.
exoPlayer.setMediaSource(mediaSource);
exoPlayer.prepare();
exoPlayer.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
private 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
จากนั้นเพิ่ม Listener ลงในโปรแกรมโหลดโฆษณา
Kotlin
var 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
แล้วนําไปใช้ซ้ำกับอินสแตนซ์ผู้เล่นหลายรายการ ซึ่งจะใช้งานได้ตราบใดที่มีอินสแตนซ์โปรแกรมเล่นเดียวใช้งานอยู่พร้อมกัน ซึ่งมีประโยชน์สำหรับ Use Case ทั่วไปเมื่อระบบนำแอปไปไว้ในเบื้องหลัง ระบบจะทำลายอินสแตนซ์ของโปรแกรมเล่น จากนั้นจะสร้างอินสแตนซ์ใหม่เมื่อแอปกลับมาอยู่ในเบื้องหน้าอีกครั้ง
Kotlin
// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context);
// Build a player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);
// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);
// Build another player and set it on the ads loader (for example onStart).
player = ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);
// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);
// Release the ads loader when not used anymore (for example onDestroy).
hlsInterstitialsAdsLoader.release();
Java
// Create the ads loader instance (for example onCreate).
hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context);
// Build a player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);
// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);
// Build another player and set it on the ads loader (for example onStart).
player = new ExoPlayer.Builder(context).build();
hlsInterstitialsAdsLoader.setPlayer(player);
// Release the player and unset it on the ads loader (for example onStop).
player.release();
hlsInterstitialsAdsLoader.setPlayer(null);
// Release the ads loader when not used anymore (for example onDestroy).
hlsInterstitialsAdsLoader.release();
โดยทั่วไป อย่าลืมปล่อยอินสแตนซ์ของเพลเยอร์เก่าก่อนที่จะตั้งค่าอินสแตนซ์ของเพลเยอร์ถัดไปในโปรแกรมโหลดโฆษณา เมื่อมีการเผยแพร่ตัวโหลดโฆษณาแล้ว คุณจะใช้งานตัวโหลดโฆษณาไม่ได้อีก
การปรับแต่งการเล่น
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 ที่มีคุณภาพสูง
คุณสามารถทำตามหลักเกณฑ์บางอย่างเพื่อปรับปรุงเนื้อหา HLS เพื่อให้ ExoPlayer ทำงานได้เต็มประสิทธิภาพ อ่านโพสต์ Medium เกี่ยวกับการเล่น HLS ใน ExopLayer เพื่อดูคำอธิบายทั้งหมด ประเด็นหลักๆ มีดังนี้
- ใช้ระยะเวลาของกลุ่มที่แน่นอน
- ใช้สตรีมสื่อแบบต่อเนื่อง หลีกเลี่ยงการเปลี่ยนแปลงโครงสร้างสื่อในแต่ละกลุ่ม
- ใช้แท็ก
#EXT-X-INDEPENDENT-SEGMENTS
- แนะนำให้ใช้สตรีมที่แยกข้อมูลแล้ว แทนไฟล์ที่มีทั้งวิดีโอและเสียง
- ระบุข้อมูลทั้งหมดที่คุณทำได้ในเพลย์ลิสต์เวอร์ชันหลายตัวแปร
หลักเกณฑ์ต่อไปนี้ใช้กับสตรีมแบบสดโดยเฉพาะ
- ใช้แท็ก
#EXT-X-PROGRAM-DATE-TIME
- ใช้แท็ก
#EXT-X-DISCONTINUITY-SEQUENCE
- ระบุกรอบเวลาที่มีการใช้งานนาน อย่างน้อย 1 นาทีก็เยี่ยมแล้ว