หัวใจสำคัญของไลบรารี ExoPlayer คืออินเทอร์เฟซ Player Player จะแสดงฟังก์ชันการทำงานของมีเดียเพลเยอร์ระดับสูงแบบดั้งเดิม เช่น ความสามารถในการบัฟเฟอร์สื่อ เล่น หยุดชั่วคราว และกรอ การติดตั้งใช้งานเริ่มต้น ExoPlayer ได้รับการออกแบบมาให้มีสมมติฐานเพียงเล็กน้อยเกี่ยวกับ (และด้วยเหตุนี้จึงมีข้อจำกัดเพียงเล็กน้อยเกี่ยวกับ) ประเภทของสื่อที่เล่น วิธีการและตำแหน่งที่จัดเก็บ และวิธีการแสดงผล แทนที่จะใช้การโหลดและการแสดงผลสื่อโดยตรง การติดตั้งใช้งาน ExoPlayer จะมอบสิทธิ์งานนี้ให้กับคอมโพเนนต์ที่แทรกเมื่อสร้างเพลเยอร์หรือเมื่อส่งแหล่งที่มาของสื่อใหม่ไปยังเพลเยอร์
คอมโพเนนต์ที่ใช้ร่วมกันในการติดตั้งใช้งาน ExoPlayer ทั้งหมดมีดังนี้
- อินสแตนซ์
MediaSourceที่กำหนดสื่อที่จะเล่น โหลดสื่อ และอ่านสื่อที่โหลดได้ อินสแตนซ์MediaSourceสร้างขึ้นจากMediaItemโดยMediaSource.Factoryภายในเพลเยอร์ นอกจากนี้ยังส่งไปยังเพลเยอร์ได้โดยตรงโดยใช้ API เพลย์ลิสต์ตามแหล่งที่มาของสื่อ - อินสแตนซ์
MediaSource.Factoryที่แปลงMediaItemเป็นMediaSourceระบบจะแทรกMediaSource.Factoryเมื่อสร้างเพลเยอร์ - อินสแตนซ์
Rendererที่แสดงผลคอมโพเนนต์แต่ละรายการของสื่อ ระบบจะแทรกข้อมูลเหล่านี้เมื่อสร้างเพลเยอร์ TrackSelectorที่เลือกแทร็กที่MediaSourceมีให้เพื่อใช้โดยRendererที่พร้อมใช้งานแต่ละรายการ ระบบจะแทรกTrackSelectorเมื่อสร้างเพลเยอร์LoadControlที่ควบคุมเวลาที่MediaSourceบัฟเฟอร์สื่อเพิ่มเติม และปริมาณสื่อที่บัฟเฟอร์ ระบบจะแทรกLoadControlเมื่อสร้างเพลเยอร์LivePlaybackSpeedControlที่ควบคุมความเร็วในการเล่นระหว่างการเล่นแบบสดเพื่อให้เพลเยอร์อยู่ใกล้กับออฟเซ็ตแบบสดที่กำหนดค่าไว้ ระบบจะแทรกLivePlaybackSpeedControlเมื่อสร้างเพลเยอร์
แนวคิดของการแทรกคอมโพเนนต์ที่ใช้ฟังก์ชันการทำงานของเพลเยอร์มีอยู่ทั่วทั้งไลบรารี การติดตั้งใช้งานเริ่มต้นของคอมโพเนนต์บางรายการจะมอบสิทธิ์การทำงานให้กับคอมโพเนนต์ที่แทรกเพิ่มเติม ซึ่งช่วยให้สามารถแทนที่คอมโพเนนต์ย่อยจำนวนมากด้วยการติดตั้งใช้งานที่กำหนดค่าในลักษณะที่กำหนดเองได้ทีละรายการ
การปรับแต่งเพลเยอร์
ตัวอย่างที่พบบ่อยของการปรับแต่งเพลเยอร์โดยการแทรกคอมโพเนนต์มีอธิบายไว้ในส่วนต่อไปนี้
กำลังกำหนดค่าสแต็กเครือข่าย
เรามีหน้าเกี่ยวกับการปรับแต่งสแต็กเครือข่ายที่ ExoPlayer ใช้
กำลังแคชข้อมูลที่โหลดจากเครือข่าย
ดูคำแนะนำสำหรับการแคชแบบ On-the-fly ชั่วคราวและการดาวน์โหลดสื่อ
การปรับแต่งการโต้ตอบกับเซิร์ฟเวอร์
แอปบางแอปอาจต้องการสกัดกั้นคำขอและการตอบกลับ HTTP คุณอาจต้องการแทรกส่วนหัวของคำขอที่กำหนดเอง อ่านส่วนหัวการตอบกลับของเซิร์ฟเวอร์ แก้ไข URI ของคำขอ ฯลฯ ตัวอย่างเช่น แอปอาจตรวจสอบสิทธิ์ตัวเองโดยแทรกโทเค็นเป็นส่วนหัวเมื่อขอเซกเมนต์สื่อ
ตัวอย่างต่อไปนี้แสดงวิธีใช้ลักษณะการทำงานเหล่านี้โดยการแทรก DataSource.Factory ที่กำหนดเองลงใน DefaultMediaSourceFactory
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
ในข้อมูลโค้ดก่อนหน้า HttpDataSource ที่แทรกไว้จะรวมส่วนหัว "Header: Value" ไว้ในคำขอ HTTP ทุกรายการ ลักษณะการทำงานนี้จะคงที่สำหรับการโต้ตอบกับแหล่งที่มา HTTP ทุกครั้ง
หากต้องการแนวทางที่ละเอียดยิ่งขึ้น คุณสามารถแทรกพฤติกรรมแบบทันท่วงทีโดยใช้ ResolvingDataSource ข้อมูลโค้ดต่อไปนี้แสดงวิธีแทรกส่วนหัวของคำขอก่อนที่จะโต้ตอบกับแหล่งที่มา HTTP
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
นอกจากนี้ คุณยังใช้ ResolvingDataSource เพื่อแก้ไข URI แบบทันท่วงทีได้ด้วย ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
การปรับแต่งการจัดการข้อผิดพลาด
การใช้ LoadErrorHandlingPolicy ที่กำหนดเองช่วยให้แอปปรับแต่งวิธีที่ ExoPlayer ตอบสนองต่อข้อผิดพลาดในการโหลดได้ ตัวอย่างเช่น แอปอาจต้องการให้ดำเนินการไม่สำเร็จอย่างรวดเร็วแทนที่จะลองใหม่หลายครั้ง หรืออาจต้องการปรับแต่งตรรกะ Back-off ที่ควบคุมระยะเวลาที่ผู้เล่นต้องรอก่อนที่จะลองใหม่แต่ละครั้ง ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ตรรกะ Back-off ที่กำหนดเอง
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor( loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo ): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
อาร์กิวเมนต์ LoadErrorInfo มีข้อมูลเพิ่มเติมเกี่ยวกับการโหลดที่ไม่สำเร็จเพื่อปรับแต่งตรรกะตามประเภทข้อผิดพลาดหรือคำขอที่ไม่สำเร็จ
การปรับแต่งแฟล็กของเครื่องมือแยกข้อมูล
คุณสามารถใช้แฟล็ก Extractor เพื่อปรับแต่งวิธีแยกรูปแบบแต่ละรายการจากสื่อแบบโปรเกรสซีฟ โดยสามารถตั้งค่าได้ใน DefaultExtractorsFactory ที่ระบุไว้ใน DefaultMediaSourceFactory ตัวอย่างต่อไปนี้จะส่ง Flag ที่เปิดใช้การกรอตามดัชนีสำหรับสตรีม MP3
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
การเปิดใช้การค้นหาอัตราบิตคงที่
สำหรับสตรีม MP3, ADTS และ AMR คุณสามารถเปิดใช้การกรอโดยประมาณได้โดยใช้สมมติฐานอัตราบิตคงที่ที่มีแฟล็ก FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
คุณตั้งค่าแฟล็กเหล่านี้สำหรับเครื่องมือแยกแต่ละรายการได้โดยใช้วิธีการ DefaultExtractorsFactory.setXyzExtractorFlags แต่ละรายการตามที่อธิบายไว้ก่อนหน้านี้ หากต้องการเปิดใช้การกรอแบบอัตราบิตคงที่สำหรับเครื่องมือแยกทั้งหมดที่รองรับ ให้ใช้ DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
จากนั้นคุณสามารถแทรก ExtractorsFactory โดยใช้ DefaultMediaSourceFactory ตามที่อธิบายไว้สำหรับการปรับแต่งแฟล็กตัวแยกข้อมูลก่อนหน้านี้
การเปิดใช้การจัดคิวบัฟเฟอร์แบบอะซิงโครนัส
การจัดคิวบัฟเฟอร์แบบอะซิงโครนัสเป็นการปรับปรุงไปป์ไลน์การแสดงผลของ ExoPlayer ซึ่งจะเรียกใช้อินสแตนซ์ MediaCodec ในโหมดอะซิงโครนัส และใช้เทรดเพิ่มเติมเพื่อกำหนดเวลาการถอดรหัสและการแสดงผลข้อมูล การเปิดใช้จะช่วยลดเฟรมที่หายไปและเสียงขาดหาย
การจัดคิวบัฟเฟอร์แบบอะซิงโครนัสจะเปิดใช้โดยค่าเริ่มต้นในอุปกรณ์ที่ใช้ Android 12 (ระดับ API 31) ขึ้นไป และสามารถเปิดใช้ด้วยตนเองได้ตั้งแต่ Android 6.0 (ระดับ API 23) เป็นต้นไป พิจารณาเปิดใช้ฟีเจอร์นี้สำหรับอุปกรณ์บางเครื่องที่คุณพบเฟรมที่หายไปหรือเสียงขาดหาย โดยเฉพาะเมื่อเล่นเนื้อหาที่ได้รับการคุ้มครองโดย DRM หรือเนื้อหาที่มีอัตราเฟรมสูง
ในกรณีที่ง่ายที่สุด คุณต้องแทรก DefaultRenderersFactory ลงในเพลเยอร์ดังนี้
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
หากคุณสร้างอินสแตนซ์ของโปรแกรมแสดงผลโดยตรง ให้ส่ง new
DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() ไปยังคอนสตรักเตอร์ MediaCodecVideoRenderer และ MediaCodecAudioRenderer
การปรับแต่งการดำเนินการด้วย ForwardingSimpleBasePlayer
คุณสามารถปรับแต่งลักษณะการทำงานบางอย่างของอินสแตนซ์ Player ได้โดยรวมไว้ในคลาสย่อยของ ForwardingSimpleBasePlayer คลาสนี้ช่วยให้คุณสกัดกั้น "การดำเนินการ" ที่เฉพาะเจาะจงได้ แทนที่จะต้องใช้เมธอด Player โดยตรง ซึ่งจะช่วยให้ลักษณะการทำงานของ play(), pause() และ setPlayWhenReady(boolean) เป็นไปในทิศทางเดียวกัน นอกจากนี้ยังช่วยให้มั่นใจว่าการเปลี่ยนแปลงสถานะทั้งหมดจะได้รับการเผยแพร่อย่างถูกต้องไปยังอินสแตนซ์ Player.Listener ที่ลงทะเบียนไว้ สำหรับ Use Case การปรับแต่งส่วนใหญ่ ควรใช้ ForwardingSimpleBasePlayer มากกว่า ForwardingPlayer ซึ่งมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่า เนื่องจากมีการรับประกันความสอดคล้องเหล่านี้
เช่น หากต้องการเพิ่มตรรกะที่กำหนดเองเมื่อเริ่มหรือหยุดเล่น ให้ทำดังนี้
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
หรือหากต้องการไม่อนุญาตคำสั่ง SEEK_TO_NEXT (และตรวจสอบว่า Player.seekToNext เป็นการดำเนินการที่ไม่มีผล):
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
การปรับแต่ง MediaSource
ตัวอย่างก่อนหน้านี้จะแทรกคอมโพเนนต์ที่กำหนดเองเพื่อใช้ระหว่างการเล่นออบเจ็กต์ MediaItem ทั้งหมดที่ส่งไปยังเพลเยอร์ ในกรณีที่จำเป็นต้องมีการปรับแต่งแบบละเอียด คุณยังสามารถแทรกคอมโพเนนต์ที่กำหนดเองลงในอินสแตนซ์ MediaSource แต่ละรายการ ซึ่งสามารถส่งไปยังเพลเยอร์ได้โดยตรง ตัวอย่างต่อไปนี้แสดงวิธีปรับแต่ง ProgressiveMediaSource เพื่อใช้ DataSource.Factory, ExtractorsFactory และ LoadErrorHandlingPolicy ที่กำหนดเอง
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
การสร้างคอมโพเนนต์ที่กำหนดเอง
ไลบรารีจะให้การใช้งานเริ่มต้นของคอมโพเนนต์ที่แสดงไว้ที่ด้านบนของหน้านี้สำหรับ Use Case ทั่วไป ExoPlayer สามารถใช้คอมโพเนนต์เหล่านี้ได้ แต่อาจสร้างขึ้นเพื่อใช้การติดตั้งใช้งานที่กำหนดเองหากจำเป็นต้องใช้ลักษณะการทำงานที่ไม่ได้มาตรฐาน กรณีการใช้งานบางส่วนสำหรับการติดตั้งใช้งานที่กำหนดเองมีดังนี้
Renderer– คุณอาจต้องการใช้Rendererที่กำหนดเองเพื่อจัดการประเภทสื่อที่การติดตั้งใช้งานเริ่มต้นที่ไลบรารีมีให้ไม่รองรับTrackSelector– การใช้TrackSelectorที่กำหนดเองช่วยให้นักพัฒนาแอปสามารถเปลี่ยนวิธีเลือกแทร็กที่แสดงโดยMediaSourceเพื่อให้Rendererแต่ละรายการที่พร้อมใช้งานนำไปใช้ได้LoadControl– การใช้LoadControlที่กำหนดเองช่วยให้นักพัฒนาแอปเปลี่ยนนโยบายการบัฟเฟอร์ของเพลเยอร์ได้Extractor– หากต้องการรองรับรูปแบบคอนเทนเนอร์ที่ไลบรารียังไม่รองรับในปัจจุบัน ให้พิจารณาใช้คลาสExtractorที่กำหนดเองMediaSource– การใช้คลาสMediaSourceที่กำหนดเองอาจเหมาะสมหากคุณต้องการรับตัวอย่างสื่อเพื่อป้อนให้กับโปรแกรมแสดงผลในลักษณะที่กำหนดเอง หรือหากคุณต้องการใช้ลักษณะการคอมโพสิตMediaSourceที่กำหนดเองMediaSource.Factory– การใช้MediaSource.Factoryที่กำหนดเองช่วยให้แอปพลิเคชันปรับแต่งวิธีสร้างMediaSourceจากMediaItemได้DataSource– แพ็กเกจอัปสตรีมของ ExoPlayer มีการติดตั้งใช้งานDataSourceจำนวนหนึ่งสำหรับ Use Case ต่างๆ อยู่แล้ว คุณอาจต้องการใช้คลาสDataSourceของคุณเองเพื่อโหลดข้อมูลด้วยวิธีอื่น เช่น ผ่านโปรโตคอลที่กำหนดเอง โดยใช้สแต็ก HTTP ที่กำหนดเอง หรือจากแคชถาวรที่กำหนดเอง
เมื่อสร้างคอมโพเนนต์ที่กำหนดเอง เราขอแนะนำให้ทำดังนี้
- หากคอมโพเนนต์ที่กำหนดเองจำเป็นต้องรายงานเหตุการณ์กลับไปยังแอป เราขอแนะนำให้คุณดำเนินการโดยใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่ เช่น ใช้คลาส
EventDispatcherหรือส่งHandlerร่วมกับ Listener ไปยังคอนสตรัคเตอร์ของคอมโพเนนต์ - เราขอแนะนำให้คอมโพเนนต์ที่กำหนดเองใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่เพื่อให้แอปกำหนดค่าใหม่ได้ในระหว่างการเล่น ในการดำเนินการนี้ คอมโพเนนต์ที่กำหนดเองควรใช้
PlayerMessage.Targetและรับการเปลี่ยนแปลงการกำหนดค่าในเมธอดhandleMessageโค้ดของแอปพลิเคชันควรส่งการเปลี่ยนแปลงการกำหนดค่าโดยเรียกใช้เมธอดcreateMessageของ ExoPlayer, กำหนดค่าข้อความ และส่งไปยังคอมโพเนนต์โดยใช้PlayerMessage.sendการส่งข้อความที่จะแสดงในชุดข้อความการเล่นช่วยให้มั่นใจได้ว่าข้อความจะทำงานตามลำดับกับการดำเนินการอื่นๆ ที่ทำในโปรแกรมเล่น