หัวใจสำคัญของไลบรารี 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
ที่ควบคุมความเร็วในการเล่นระหว่างการเล่นสด เพื่อให้เพลเยอร์เล่นใกล้กับออฟเซ็ตสดที่กำหนดค่าไว้ ระบบจะแทรก ALivePlaybackSpeedControl
เมื่อสร้างเพลเยอร์
แนวคิดของการแทรกคอมโพเนนต์ที่ใช้ฟังก์ชันการทำงานบางส่วนของเพลเยอร์ มีอยู่ทั่วทั้งไลบรารี การติดตั้งใช้งานเริ่มต้นของ คอมโพเนนต์บางอย่างจะมอบหมายงานให้กับคอมโพเนนต์ที่แทรกเพิ่มเติม ซึ่งช่วยให้เปลี่ยน คอมโพเนนต์ย่อยหลายรายการทีละรายการได้ด้วยการติดตั้งใช้งานที่ กำหนดค่าในลักษณะที่กำหนดเอง
การปรับแต่งผู้เล่น
ตัวอย่างทั่วไปบางส่วนของการปรับแต่งเพลเยอร์โดยการแทรกคอมโพเนนต์มีดังนี้
การกำหนดค่าสแต็กเครือข่าย
เรามีหน้าเว็บเกี่ยวกับการปรับแต่งสแต็กเครือข่ายที่ ExoPlayer ใช้
แคชข้อมูลที่โหลดจากเครือข่าย
ดูคำแนะนำสำหรับ การแคชชั่วคราวแบบทันที และการดาวน์โหลดสื่อ
การปรับแต่งการโต้ตอบกับเซิร์ฟเวอร์
แอปบางแอปอาจต้องการสกัดกั้นคำขอและการตอบกลับ 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 ตอบสนองต่อข้อผิดพลาดในการโหลดได้ ตัวอย่างเช่น แอปอาจต้องการให้ล้มเหลวอย่างรวดเร็ว
แทนที่จะลองใหม่หลายครั้ง หรืออาจต้องการปรับแต่งตรรกะการหยุดพักชั่วคราวที่
ควบคุมระยะเวลาที่ผู้เล่นรอระหว่างการลองใหม่แต่ละครั้ง ข้อมูลโค้ดต่อไปนี้
แสดงวิธีใช้ตรรกะการหยุดชั่วคราวที่กำหนดเอง
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: 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(LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
อาร์กิวเมนต์ LoadErrorInfo
มีข้อมูลเพิ่มเติมเกี่ยวกับการโหลดที่ไม่สำเร็จเพื่อ
ปรับแต่งตรรกะตามประเภทข้อผิดพลาดหรือคำขอที่ไม่สำเร็จ
การปรับแต่ง Flag ของโปรแกรมแยก
คุณใช้แฟล็กตัวแยกเพื่อปรับแต่งวิธีแยกรูปแบบแต่ละรูปแบบ
จากสื่อแบบ Progressive ได้ โดยตั้งค่าได้ใน DefaultExtractorsFactory
ที่ส่งให้ DefaultMediaSourceFactory
ตัวอย่างต่อไปนี้จะส่งแฟล็ก
ที่เปิดใช้การกรอตามดัชนีสำหรับสตรีม 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
คุณตั้งค่าสถานะเหล่านี้สำหรับตัวแยกแต่ละรายการได้โดยใช้เมธอด individual
DefaultExtractorsFactory.setXyzExtractorFlags
ตามที่อธิบายไว้ข้างต้น หากต้องการ
เปิดใช้การค้นหาบิตเรตคงที่สำหรับโปรแกรมแยกข้อมูลทั้งหมดที่รองรับ ให้ใช้
DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
จากนั้นจะแทรก ExtractorsFactory
ผ่าน DefaultMediaSourceFactory
ได้ตามที่อธิบายไว้สำหรับการปรับแต่ง Flag ของโปรแกรมแยกข้อมูลด้านบน
การเปิดใช้การจัดคิวบัฟเฟอร์แบบไม่พร้อมกัน
การจัดคิวบัฟเฟอร์แบบอะซิงโครนัสเป็นการเพิ่มประสิทธิภาพไปป์ไลน์การแสดงผลของ 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
ที่ลงทะเบียน สําหรับกรณีการใช้งานการปรับแต่งส่วนใหญ่ ForwardingSimpleBasePlayer
ควรเป็นตัวเลือกที่ต้องการมากกว่า ForwardingPlayer
ที่มีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่าเนื่องจากการรับประกันความสอดคล้องเหล่านี้
เช่น หากต้องการเพิ่มตรรกะที่กำหนดเองเมื่อเริ่มหรือหยุดการเล่น ให้ทำดังนี้
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
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
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));
การสร้างคอมโพเนนต์ที่กำหนดเอง
ไลบรารีมีการติดตั้งใช้งานเริ่มต้นของคอมโพเนนต์ที่แสดงที่ด้านบน
ของหน้านี้สําหรับกรณีการใช้งานทั่วไป ExoPlayer
สามารถใช้คอมโพเนนต์เหล่านี้ได้ แต่ก็อาจสร้างขึ้นเพื่อใช้การติดตั้งใช้งานที่กำหนดเองได้เช่นกัน หากจำเป็นต้องมีลักษณะการทำงานที่ไม่เป็นไปตามมาตรฐาน กรณีการใช้งานบางส่วนสำหรับการติดตั้งใช้งานที่กำหนดเองมีดังนี้
Renderer
- คุณอาจต้องการใช้Renderer
ที่กำหนดเองเพื่อจัดการ ประเภทสื่อที่การติดตั้งใช้งานเริ่มต้นที่ไลบรารีมีให้ไม่รองรับTrackSelector
- การใช้TrackSelector
ที่กำหนดเองช่วยให้นักพัฒนาแอป เปลี่ยนวิธีเลือกแทร็กที่MediaSource
แสดง เพื่อการใช้งานโดยRenderer
ที่พร้อมใช้งานแต่ละรายการได้LoadControl
– การใช้LoadControl
ที่กำหนดเองช่วยให้นักพัฒนาแอป เปลี่ยนนโยบายการบัฟเฟอร์ของเพลเยอร์ได้Extractor
– หากต้องการรองรับรูปแบบคอนเทนเนอร์ที่ไลบรารีไม่รองรับในปัจจุบัน ให้ลองใช้คลาสExtractor
ที่กำหนดเองMediaSource
- การใช้คลาสMediaSource
ที่กำหนดเองอาจ เหมาะสมหากคุณต้องการรับตัวอย่างสื่อเพื่อป้อนไปยังโปรแกรมแสดงผลในลักษณะ ที่กำหนดเอง หรือหากคุณต้องการใช้ลักษณะการทำงานของการคอมโพสิตMediaSource
ที่กำหนดเองMediaSource.Factory
- การใช้MediaSource.Factory
ที่กำหนดเอง ช่วยให้แอปพลิเคชันปรับแต่งวิธีสร้างMediaSource
จากMediaItem
ได้DataSource
- แพ็กเกจต้นทางของ ExoPlayer มีการใช้งานDataSource
จำนวนหนึ่งสำหรับกรณีการใช้งานต่างๆ อยู่แล้ว คุณอาจต้องการใช้คลาสDataSource
ของคุณเองเพื่อโหลดข้อมูลในลักษณะอื่น เช่น ผ่านโปรโตคอลที่กำหนดเอง ใช้สแต็ก HTTP ที่กำหนดเอง หรือจากแคชแบบถาวรที่กำหนดเอง
เมื่อสร้างคอมโพเนนต์ที่กำหนดเอง เราขอแนะนำให้ทำดังนี้
- หากคอมโพเนนต์ที่กำหนดเองต้องรายงานเหตุการณ์กลับไปยังแอป เราขอแนะนำ
ให้คุณทำเช่นนั้นโดยใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่ เช่น
ใช้คลาส
EventDispatcher
หรือส่งHandler
พร้อมกับ Listener ไปยังตัวสร้างของคอมโพเนนต์ - เราขอแนะนำให้คอมโพเนนต์ที่กำหนดเองใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่เพื่อให้แอปกำหนดค่าใหม่ได้ในระหว่างการเล่น โดยคอมโพเนนต์ที่กำหนดเองควรใช้
PlayerMessage.Target
และรับการเปลี่ยนแปลงการกำหนดค่าในเมธอดhandleMessage
โค้ดแอปพลิเคชันควรส่งการเปลี่ยนแปลงการกำหนดค่าโดยการเรียกใช้เมธอดcreateMessage
ของ ExoPlayer กำหนดค่าข้อความ และส่งไปยังคอมโพเนนต์โดยใช้PlayerMessage.send
การส่งข้อความเพื่อให้แสดงในเธรดการเล่น จะช่วยให้มั่นใจได้ว่าระบบจะดำเนินการตามลำดับพร้อมกับการดำเนินการอื่นๆ ที่ดำเนินการในเพลเยอร์