อินเทอร์เฟซ Player
คือหัวใจหลักของไลบรารี ExoPlayer Player
แสดงฟังก์ชันการทำงานระดับสูงแบบดั้งเดิมของเครื่องเล่นสื่อ เช่น ความสามารถในการบัฟเฟอร์สื่อ เล่น หยุดชั่วคราว และกรอ การใช้งานเริ่มต้น ExoPlayer
ได้รับการออกแบบมาเพื่อให้มีการคาดเดาเพียงเล็กน้อยเกี่ยวกับ (และด้วยเหตุนี้จึงมีการจํากัดเพียงเล็กน้อยเกี่ยวกับ) ประเภทของสื่อที่เล่น วิธีการและตําแหน่งที่จัดเก็บ และวิธีแสดงผล การติดตั้งใช้งาน ExoPlayer
ไม่ได้ใช้การโหลดและการแสดงผลสื่อโดยตรง แต่มอบหมายงานนี้ให้คอมโพเนนต์ที่แทรกเมื่อสร้างโปรแกรมเล่นหรือเมื่อส่งแหล่งที่มาของสื่อใหม่ไปยังโปรแกรมเล่น
คอมโพเนนต์ที่ใช้ร่วมกันในการติดตั้งใช้งาน ExoPlayer
ทั้งหมดมีดังนี้
- อินสแตนซ์
MediaSource
ที่กําหนดสื่อที่จะเล่น โหลดสื่อ และอ่านสื่อที่โหลดได้ ระบบจะสร้างอินสแตนซ์MediaSource
จากMediaItem
โดยMediaSource.Factory
ภายในโปรแกรมเล่น นอกจากนี้ คุณยังส่งเพลย์ลิสต์ไปยังโปรแกรมเล่นโดยตรงได้โดยใช้ Playlist API ตามแหล่งที่มาของสื่อ - อินสแตนซ์
MediaSource.Factory
ที่แปลงMediaItem
เป็นMediaSource
ระบบจะแทรกMediaSource.Factory
เมื่อสร้างเพลเยอร์ - อินสแตนซ์
Renderer
ที่แสดงผลคอมโพเนนต์แต่ละรายการของสื่อ ระบบจะแทรกข้อมูลเหล่านี้เมื่อสร้างเพลเยอร์ TrackSelector
ที่เลือกแทร็กจากMediaSource
เพื่อใช้กับRenderer
แต่ละรายการที่มี ระบบจะแทรกTrackSelector
เมื่อสร้างเพลเยอร์LoadControl
ที่ควบคุมเวลาให้MediaSource
บัฟเฟอร์สื่อเพิ่มเติม และปริมาณสื่อที่บัฟเฟอร์ ระบบจะแทรกLoadControl
เมื่อสร้างผู้เล่นLivePlaybackSpeedControl
ที่ควบคุมความเร็วในการเล่นระหว่างการเล่นแบบสดเพื่อให้ผู้เล่นเล่นตามเวลาจริงที่กําหนดค่าไว้ ระบบจะแทรกLivePlaybackSpeedControl
เมื่อสร้างเพลเยอร์
แนวคิดในการแทรกคอมโพเนนต์ที่ใช้ฟังก์ชันการทำงานของเพลเยอร์มีอยู่ในไลบรารี การใช้งานเริ่มต้นของคอมโพเนนต์บางอย่างจะมอบหมายงานให้กับคอมโพเนนต์ที่แทรกเข้ามาเพิ่มเติม วิธีนี้ช่วยให้คุณแทนที่คอมโพเนนต์ย่อยหลายรายการด้วยการติดตั้งใช้งานที่กำหนดค่าในลักษณะที่กำหนดเองได้ทีละรายการ
การปรับแต่งผู้เล่น
ตัวอย่างทั่วไปบางส่วนของการปรับแต่งโปรแกรมเล่นโดยการแทรกคอมโพเนนต์มีอธิบายไว้ด้านล่าง
การกำหนดค่าสแต็กเครือข่าย
เรามีหน้าเว็บเกี่ยวกับการปรับแต่งสแต็กเครือข่ายที่ 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 ของเครื่องมือแยก
คุณสามารถใช้ Flag ของเครื่องมือแยกเพื่อปรับแต่งวิธีแยกแต่ละรูปแบบจากสื่อแบบสื่อสมบูรณ์ โดยสามารถตั้งค่าใน 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 FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
คุณตั้งค่า Flag เหล่านี้สําหรับเครื่องมือแยกแต่ละรายการได้โดยใช้เมธอดของ 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();
หากคุณสร้างอินสแตนซ์ของโปรแกรมแสดงผลโดยตรง ให้ส่ง AsynchronousMediaCodecAdapter.Factory
ไปยังตัวสร้าง 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
– แพ็กเกจ Upstream ของ ExoPlayer มีการใช้งานDataSource
หลายรายการสําหรับกรณีการใช้งานที่แตกต่างกันอยู่แล้ว คุณอาจต้องติดตั้งใช้งานคลาสDataSource
ของคุณเองเพื่อโหลดข้อมูลด้วยวิธีอื่น เช่น ผ่านโปรโตคอลที่กำหนดเอง โดยใช้สแต็ก HTTP ที่กําหนดเอง หรือจากแคชถาวรที่กําหนดเอง
เมื่อสร้างคอมโพเนนต์ที่กําหนดเอง เราขอแนะนําให้ทําดังนี้
- หากคอมโพเนนต์ที่กําหนดเองต้องรายงานเหตุการณ์กลับไปที่แอป เราขอแนะนําให้ทําโดยใช้รูปแบบเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่ เช่น ใช้คลาส
EventDispatcher
หรือส่งHandler
พร้อมกับตัวฟังไปยังคอนสตรคเตอร์ของคอมโพเนนต์ - เราขอแนะนำให้คอมโพเนนต์ที่กําหนดเองใช้รูปแบบเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่เพื่อให้แอปกําหนดค่าใหม่ได้ในระหว่างการเล่น โดยคอมโพเนนต์ที่กําหนดเองควรใช้
PlayerMessage.Target
และรับการเปลี่ยนแปลงการกําหนดค่าในเมธอดhandleMessage
โค้ดแอปพลิเคชันควรส่งการเปลี่ยนแปลงการกำหนดค่าโดยเรียกใช้เมธอดcreateMessage
ของ ExoPlayer กำหนดค่าข้อความ แล้วส่งไปยังคอมโพเนนต์โดยใช้PlayerMessage.send
การส่งข้อความที่จะส่งในเธรดการเล่นจะช่วยให้มั่นใจได้ว่าข้อความจะดำเนินการตามลำดับพร้อมกับการดำเนินการอื่นๆ ที่กำลังดำเนินการในโปรแกรมเล่น