เซสชันสื่อเป็นวิธีสากลในการโต้ตอบกับโปรแกรมเล่นเสียงหรือวิดีโอ ใน Media3 โปรแกรมเล่นเริ่มต้นคือคลาส ExoPlayer
ซึ่งใช้อินเทอร์เฟซ Player
การเชื่อมต่อเซสชันสื่อกับโปรแกรมเล่นช่วยให้แอปสามารถโฆษณาการเล่นสื่อภายนอกและรับคำสั่งการเล่นจากแหล่งที่มาภายนอกได้
คำสั่งอาจมาจากปุ่มจริง เช่น ปุ่มเล่นบนหูฟังหรือรีโมตทีวี นอกจากนี้ คำสั่งอาจมาจากแอปไคลเอ็นต์ที่มีตัวควบคุมสื่อ เช่น คำสั่ง "หยุดชั่วคราว" ไปยัง Google Assistant เซสชันสื่อจะมอบสิทธิ์คำสั่งเหล่านี้ให้กับโปรแกรมเล่นของแอปสื่อ
กรณีที่ควรเลือกเซสชันสื่อ
เมื่อใช้ MediaSession
คุณจะอนุญาตให้ผู้ใช้ควบคุมการเล่นได้ดังนี้
- ผ่านหูฟัง โดยมักจะมีปุ่มหรือการโต้ตอบด้วยการสัมผัสที่ผู้ใช้สามารถดำเนินการกับหูฟังเพื่อเล่นหรือหยุดสื่อชั่วคราว หรือไปยังแทร็กถัดไปหรือก่อนหน้า
- โดยพูดกับ Google Assistant รูปแบบคำสั่งทั่วไปคือพูดว่า "Ok Google หยุดชั่วคราว" เพื่อหยุดสื่อที่เล่นอยู่ในอุปกรณ์ชั่วคราว
- ผ่านนาฬิกา Wear OS วิธีนี้ช่วยให้ผู้ใช้เข้าถึงการควบคุมการเล่นที่ใช้บ่อยที่สุดได้ง่ายขึ้นขณะเล่นบนโทรศัพท์
- ผ่านตัวควบคุมสื่อ ภาพสไลด์นี้จะแสดงตัวควบคุมสำหรับเซสชันสื่อที่เล่นอยู่แต่ละรายการ
- บนทีวี อนุญาตให้ดำเนินการกับปุ่มเล่นจริง การควบคุมการเล่นบนแพลตฟอร์ม และการจัดการพลังงาน (เช่น หากทีวี ซาวด์บาร์ หรือรีซีฟเวอร์ A/V ปิดอยู่หรือมีการสลับอินพุต การเล่นในแอปควรหยุดลง)
- และกระบวนการภายนอกอื่นๆ ที่ต้องส่งผลต่อการเล่น
ซึ่งเหมาะสําหรับ Use Case หลายรายการ โดยเฉพาะอย่างยิ่ง คุณควรพิจารณาใช้ MediaSession
ในกรณีต่อไปนี้
- คุณกำลังสตรีมเนื้อหาวิดีโอแบบยาว เช่น ภาพยนตร์หรือรายการทีวีสด
- คุณกำลังสตรีมเนื้อหาเสียงแบบยาว เช่น พอดแคสต์หรือเพลย์ลิสต์เพลง
- คุณกำลังสร้างแอปทีวี
อย่างไรก็ตาม โปรดทราบว่าบาง Use Case อาจไม่เหมาะกับ MediaSession
คุณอาจต้อง
ใช้เฉพาะ Player
ในกรณีต่อไปนี้
- คุณแสดงเนื้อหาแบบสั้น ซึ่งการมีส่วนร่วมและการโต้ตอบของผู้ใช้มีความสําคัญ
- ไม่มีวิดีโอที่ใช้งานอยู่เพียงรายการเดียว เช่น ผู้ใช้กำลังเลื่อนดูรายการ และวิดีโอหลายรายการแสดงบนหน้าจอพร้อมกัน
- คุณกำลังเล่นวิดีโอแนะนำหรืออธิบายแบบครั้งเดียว ซึ่งคุณคาดหวังว่าผู้ใช้จะดูอย่างตั้งใจ
- เนื้อหาของคุณมีความละเอียดอ่อนด้านความเป็นส่วนตัวและคุณไม่ต้องการให้กระบวนการภายนอกเข้าถึงข้อมูลเมตาของสื่อ (เช่น โหมดไม่ระบุตัวตนในเบราว์เซอร์)
หาก Use Case ของคุณไม่ตรงกับ Use Case ที่ระบุไว้ข้างต้น ให้พิจารณาว่าคุณอนุญาตให้แอปเล่นเนื้อหาต่อไปได้ไหมเมื่อผู้ใช้ไม่ได้มีส่วนร่วมกับเนื้อหา หากคำตอบคือใช่ คุณอาจต้องเลือกตัวเลือก MediaSession
หากคำตอบคือไม่ คุณอาจต้องใช้ Player
แทน
สร้างเซสชันสื่อ
เซสชันสื่อจะแสดงอยู่พร้อมกับโปรแกรมเล่นที่จัดการ คุณสามารถสร้างเซสชันสื่อด้วยออบเจ็กต์ Context
และ Player
คุณควรสร้างและเริ่มต้นเซสชันสื่อเมื่อจำเป็น เช่น วิธีการเกี่ยวกับวงจรชีวิตของ onStart()
หรือ onResume()
ของ Activity
หรือ Fragment
หรือวิธีการ onCreate()
ของ Service
ที่เป็นเจ้าของเซสชันสื่อและโปรแกรมเล่นที่เชื่อมโยง
หากต้องการสร้างเซสชันสื่อ ให้เริ่มต้น Player
แล้วส่งให้กับ
MediaSession.Builder
ดังนี้
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
การจัดการสถานะอัตโนมัติ
ไลบรารี Media3 จะอัปเดตเซสชันสื่อโดยอัตโนมัติโดยใช้สถานะของเพลเยอร์ คุณจึงไม่ต้องจัดการการแมปจากเพลเยอร์ไปยังเซสชันด้วยตนเอง
วิธีนี้แตกต่างจากแนวทางเดิมที่คุณต้องสร้างและดูแลรักษา PlaybackStateCompat
แยกต่างหากจากตัวโปรแกรมเล่นเอง เช่น เพื่อบ่งบอกข้อผิดพลาด
รหัสเซสชันที่ไม่ซ้ำกัน
โดยค่าเริ่มต้น MediaSession.Builder
จะสร้างเซสชันโดยใช้สตริงว่างเป็นรหัสเซสชัน ซึ่งเพียงพอแล้วหากแอปตั้งใจจะสร้างอินสแตนซ์เซสชันเดียวเท่านั้น ซึ่งเป็นกรณีที่พบบ่อยที่สุด
หากแอปต้องการจัดการอินสแตนซ์เซสชันหลายรายการพร้อมกัน แอปต้องตรวจสอบว่ารหัสเซสชันของแต่ละเซสชันไม่ซ้ำกัน คุณสามารถตั้งค่ารหัสเซสชันเมื่อสร้างเซสชันด้วย MediaSession.Builder.setId(String id)
หากเห็นIllegalStateException
แอปขัดข้องพร้อมข้อความแสดงข้อผิดพลาด IllegalStateException: Session ID must be unique. ID=
แสดงว่าอาจมีการสร้างเซสชันโดยไม่คาดคิดก่อนที่จะมีการปล่อยอินสแตนซ์ที่มีรหัสเดียวกันซึ่งสร้างขึ้นก่อนหน้านี้ ระบบจะตรวจหาและแจ้งเตือนกรณีดังกล่าวด้วยการยกเว้นเพื่อหลีกเลี่ยงไม่ให้เซสชันรั่วไหลเนื่องจากข้อผิดพลาดในการเขียนโปรแกรม
มอบสิทธิ์ควบคุมให้กับไคลเอ็นต์รายอื่น
เซสชันสื่อเป็นกุญแจสำคัญในการควบคุมการเล่น ซึ่งช่วยให้คุณกำหนดเส้นทางคำสั่งจากแหล่งที่มาภายนอกไปยังโปรแกรมเล่นที่ทำหน้าที่เล่นสื่อได้ แหล่งที่มาเหล่านี้อาจเป็นปุ่มจริง เช่น ปุ่มเล่นบนชุดหูฟังหรือรีโมตคอนโทรลทีวี หรือคำสั่งโดยอ้อม เช่น บอกให้ Google Assistant "หยุดชั่วคราว" ในทำนองเดียวกัน คุณอาจต้องให้สิทธิ์เข้าถึงระบบ Android เพื่ออำนวยความสะดวกในการควบคุมการแจ้งเตือนและหน้าจอล็อก หรือให้สิทธิ์เข้าถึงนาฬิกา Wear OS เพื่อให้คุณควบคุมการเล่นจากหน้าปัดได้ ลูกค้าภายนอกสามารถใช้ตัวควบคุมสื่อเพื่อออกคำสั่งการเล่นไปยังแอปสื่อได้ โดยเซสชันสื่อจะรับคำสั่งเหล่านี้และส่งต่อไปยังโปรแกรมเล่นสื่อ
เมื่อตัวควบคุมกำลังจะเชื่อมต่อกับเซสชันสื่อ ระบบจะเรียกใช้เมธอด onConnect()
คุณสามารถใช้ ControllerInfo
ที่ระบุไว้เพื่อตัดสินใจว่าจะยอมรับหรือปฏิเสธคําขอ ดูตัวอย่างการยอมรับคําขอเชื่อมต่อได้ในส่วนประกาศคำสั่งที่ใช้ได้
หลังจากเชื่อมต่อแล้ว รีโมตคอนโทรลเลอร์จะส่งคำสั่งการเล่นไปยังเซสชันได้ จากนั้นเซสชันจะมอบสิทธิ์คำสั่งเหล่านั้นไปยังโปรแกรมเล่น เซสชันจะจัดการคำสั่งการเล่นและเพลย์ลิสต์ที่กําหนดไว้ในอินเทอร์เฟซ Player
โดยอัตโนมัติ
วิธีการเรียกกลับอื่นๆ ช่วยให้คุณจัดการกับคำขอต่างๆ ได้ เช่น คําสั่งการเล่นที่กําหนดเอง และการแก้ไขเพลย์ลิสต์)
แคล็กแบ็กเหล่านี้ยังมีออบเจ็กต์ ControllerInfo
ด้วยเพื่อให้คุณแก้ไขวิธีตอบสนองต่อคําขอแต่ละรายการตามตัวควบคุมแต่ละรายการได้
แก้ไขเพลย์ลิสต์
เซสชันสื่อสามารถแก้ไขเพลย์ลิสต์ของเพลเยอร์ได้โดยตรงตามที่อธิบายไว้ในคู่มือ ExoPlayer สำหรับเพลย์ลิสต์
ผู้ควบคุมยังแก้ไขเพลย์ลิสต์ได้หาก COMMAND_SET_MEDIA_ITEM
หรือ COMMAND_CHANGE_MEDIA_ITEMS
พร้อมใช้งานสำหรับผู้ควบคุม
เมื่อเพิ่มรายการใหม่ลงในเพลย์ลิสต์ โดยทั่วไปแล้วโปรแกรมเล่นจะต้องใช้MediaItem
อินสแตนซ์ที่มี URI ที่กําหนดเพื่อให้เล่นได้ โดยค่าเริ่มต้น ระบบจะส่งต่อรายการที่เพิ่มใหม่โดยอัตโนมัติไปยังเมธอดของโปรแกรมเล่น เช่น player.addMediaItem
หากมีการกําหนด URI
หากต้องการปรับแต่งอินสแตนซ์ MediaItem
ที่เพิ่มลงในเพลเยอร์ คุณสามารถ override
onAddMediaItems()
ขั้นตอนนี้จำเป็นเมื่อคุณต้องการรองรับตัวควบคุมที่ขอสื่อโดยไม่มี URI ที่กําหนดไว้ แต่โดยทั่วไป MediaItem
จะมีการตั้งค่าช่องต่อไปนี้อย่างน้อย 1 ช่องเพื่ออธิบายสื่อที่ขอ
MediaItem.id
: รหัสทั่วไปที่ระบุสื่อMediaItem.RequestMetadata.mediaUri
: URI คำขอที่อาจใช้สคีมาที่กำหนดเองและไม่จำเป็นต้องเล่นได้โดยตรงโดยโปรแกรมเล่นMediaItem.RequestMetadata.searchQuery
: คําค้นหาที่เป็นข้อความ เช่น จาก Google AssistantMediaItem.MediaMetadata
: ข้อมูลเมตาที่มีโครงสร้าง เช่น "ชื่อ" หรือ "ศิลปิน"
หากต้องการตัวเลือกการปรับแต่งเพิ่มเติมสำหรับเพลย์ลิสต์ใหม่ทั้งหมด คุณสามารถลบล้างonSetMediaItems()
เพิ่มเติมได้ ซึ่งจะช่วยให้คุณกำหนดรายการเริ่มต้นและตำแหน่งในเพลย์ลิสต์ได้ เช่น คุณสามารถขยายรายการที่ขอรายการเดียวเป็นทั้งเพลย์ลิสต์และสั่งให้โปรแกรมเล่นเริ่มต้นที่ดัชนีของรายการที่ขอในตอนแรก คุณสามารถดูตัวอย่างการใช้งาน onSetMediaItems()
กับฟีเจอร์นี้ได้ในแอปสาธิตเซสชัน
จัดการเลย์เอาต์ที่กำหนดเองและคำสั่งที่กำหนดเอง
ส่วนต่อไปนี้จะอธิบายวิธีแสดงโฆษณาเลย์เอาต์ที่กำหนดเองของปุ่มคำสั่งที่กำหนดเองไปยังแอปไคลเอ็นต์ และมอบสิทธิ์ให้ตัวควบคุมส่งคำสั่งที่กำหนดเอง
กําหนดเลย์เอาต์ที่กําหนดเองของเซสชัน
หากต้องการระบุแอปไคลเอ็นต์ว่าคุณต้องการแสดงการควบคุมการเล่นใดต่อผู้ใช้ ให้ตั้งค่าเลย์เอาต์ที่กำหนดเองของเซสชันเมื่อสร้าง MediaSession
ในเมธอด onCreate()
ของบริการ
Kotlin
override fun onCreate() { super.onCreate() val likeButton = CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build() val favoriteButton = CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle())) .build() session = MediaSession.Builder(this, player) .setCallback(CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build() }
Java
@Override public void onCreate() { super.onCreate(); CommandButton likeButton = new CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build(); CommandButton favoriteButton = new CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); Player player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player) .setCallback(new CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build(); }
ประกาศเพลเยอร์และคำสั่งที่กำหนดเองที่ใช้ได้
แอปพลิเคชันสื่อสามารถกำหนดคำสั่งที่กำหนดเองได้ เช่น สามารถใช้ในเลย์เอาต์ที่กำหนดเอง เช่น คุณอาจต้องการใช้ปุ่มที่อนุญาตให้ผู้ใช้บันทึกรายการสื่อลงในรายการรายการโปรด MediaController
จะส่งคําสั่งที่กําหนดเองและ MediaSession.Callback
จะรับคําสั่งเหล่านั้น
คุณสามารถกำหนดคำสั่งเซสชันที่กำหนดเองซึ่งพร้อมใช้งานสำหรับ MediaController
เมื่อเชื่อมต่อกับเซสชันสื่อ ซึ่งทำได้โดย overriding MediaSession.Callback.onConnect()
กำหนดค่าและแสดงชุดคำสั่งที่ใช้ได้เมื่อยอมรับคําขอการเชื่อมต่อจาก MediaController
ในเมธอด Callback ของ onConnect
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ConnectionResult onConnect( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } }
หากต้องการรับคําขอคําสั่งที่กําหนดเองจาก MediaController
ให้ลบล้างวิธี
onCustomCommand()
ใน Callback
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { ... override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture<SessionResult> { if (customCommand.customAction == SAVE_TO_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture( SessionResult(SessionResult.RESULT_SUCCESS) ) } ... } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { ... @Override public ListenableFuture<SessionResult> onCustomCommand( MediaSession session, ControllerInfo controller, SessionCommand customCommand, Bundle args ) { if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture( new SessionResult(SessionResult.RESULT_SUCCESS) ); } ... } }
คุณสามารถติดตามว่าตัวควบคุมสื่อใดกำลังส่งคำขอโดยใช้พร็อพเพอร์ตี้ packageName
ของออบเจ็กต์ MediaSession.ControllerInfo
ที่ส่งไปยังเมธอด Callback
ซึ่งจะช่วยให้คุณปรับแต่งลักษณะการทํางานของแอปเพื่อตอบสนองต่อคําสั่งหนึ่งๆ ได้ หากคําสั่งนั้นมาจากระบบ แอปของคุณเอง หรือแอปไคลเอ็นต์อื่นๆ
อัปเดตเลย์เอาต์ที่กําหนดเองหลังจากการโต้ตอบของผู้ใช้
หลังจากจัดการคําสั่งที่กําหนดเองหรือการโต้ตอบอื่นๆ กับโปรแกรมเล่นแล้ว คุณอาจต้องอัปเดตเลย์เอาต์ที่แสดงใน UI ของคอนโทรลเลอร์ ตัวอย่างทั่วไปคือปุ่มเปิด/ปิดที่เปลี่ยนไอคอนหลังจากเรียกการดำเนินการที่เชื่อมโยงกับปุ่มนี้ หากต้องการอัปเดตเลย์เอาต์ คุณสามารถใช้ตัวเลือกต่อไปนี้
MediaSession.setCustomLayout
Kotlin
val removeFromFavoritesButton = CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle())) .build() mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
CommandButton removeFromFavoritesButton = new CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle())) .build(); mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));
ปรับแต่งลักษณะการทำงานของคำสั่งการเล่น
หากต้องการปรับแต่งลักษณะการทํางานของคําสั่งที่กําหนดไว้ในอินเทอร์เฟซ Player
เช่น play()
หรือ seekToNext()
ให้ใส่ Player
ใน ForwardingSimpleBasePlayer
ก่อนส่งไปยัง MediaSession
Kotlin
val player = (logic to build a Player instance) val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = (logic to build a Player instance) ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
ดูข้อมูลเพิ่มเติมเกี่ยวกับ ForwardingSimpleBasePlayer
ได้ที่คำแนะนำของ ExoPlayer เกี่ยวกับการปรับแต่ง
ระบุตัวควบคุมที่ส่งคําขอของคําสั่งผู้เล่น
เมื่อการเรียกใช้เมธอด Player
มาจาก MediaController
คุณสามารถระบุแหล่งที่มาของต้นทางด้วย MediaSession.controllerForCurrentRequest
และรับ ControllerInfo
สําหรับคําขอปัจจุบันได้ ดังนี้
Kotlin
class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSeek( mediaItemIndex: Int, positionMs: Long, seekCommand: Int, ): ListenableFuture<*> { Log.d( "caller", "seek operation from package ${session.controllerForCurrentRequest?.packageName}", ) return super.handleSeek(mediaItemIndex, positionMs, seekCommand) } }
Java
public class CallerAwarePlayer extends ForwardingSimpleBasePlayer { public CallerAwarePlayer(Player player) { super(player); } @Override protected ListenableFuture<?> handleSeek( int mediaItemIndex, long positionMs, int seekCommand) { Log.d( "caller", "seek operation from package: " + session.getControllerForCurrentRequest().getPackageName()); return super.handleSeek(mediaItemIndex, positionMs, seekCommand); } }
ตอบสนองต่อปุ่มสื่อ
ปุ่มสื่อคือปุ่มฮาร์ดแวร์ที่มีอยู่ในอุปกรณ์ Android และอุปกรณ์ต่อพ่วงอื่นๆ เช่น ปุ่มเล่น/หยุดชั่วคราวบนชุดหูฟังบลูทูธ Media3 จะจัดการเหตุการณ์ปุ่มสื่อให้คุณเมื่อเหตุการณ์มาถึงเซสชันและเรียกใช้Player
เมธอดที่เหมาะสมในโปรแกรมเล่นเซสชัน
แอปสามารถลบล้างลักษณะการทำงานเริ่มต้นได้โดยลบล้าง
MediaSession.Callback.onMediaButtonEvent(Intent)
ในกรณีเช่นนี้ แอปสามารถ/ต้องจัดการข้อมูลเฉพาะทั้งหมดของ API ด้วยตนเอง
การจัดการและการรายงานข้อผิดพลาด
ข้อผิดพลาดที่เซสชันแสดงและรายงานไปยังตัวควบคุมมี 2 ประเภท ข้อผิดพลาดร้ายแรงจะรายงานการเล่นทางเทคนิคที่ไม่สำเร็จของโปรแกรมเล่นเซสชันที่ขัดจังหวะการเล่น ระบบจะรายงานข้อผิดพลาดร้ายแรงไปยังตัวควบคุมโดยอัตโนมัติเมื่อเกิดข้อผิดพลาด ข้อผิดพลาดที่ไม่ร้ายแรงคือข้อผิดพลาดที่ไม่เกี่ยวข้องกับเทคนิคหรือนโยบาย ซึ่งจะไม่ขัดจังหวะการเล่นและแอปพลิเคชันจะส่งไปยังตัวควบคุมด้วยตนเอง
ข้อผิดพลาดร้ายแรงในการเล่น
โปรแกรมเล่นจะรายงานข้อผิดพลาดร้ายแรงในการเล่นไปยังเซสชัน จากนั้นจะรายงานไปยังตัวควบคุมเพื่อเรียกผ่าน Player.Listener.onPlayerError(PlaybackException)
และ Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
ในกรณีเช่นนี้ สถานะการเล่นจะเปลี่ยนเป็น STATE_IDLE
และ MediaController.getPlaybackError()
จะแสดงผล PlaybackException
ที่ทําให้เกิดการเปลี่ยนสถานะ ผู้ควบคุมสามารถตรวจสอบ PlayerException.errorCode
เพื่อดูข้อมูลเกี่ยวกับสาเหตุของข้อผิดพลาด
สําหรับความสามารถในการทํางานร่วมกัน ระบบจะทําซ้ำข้อผิดพลาดร้ายแรงไปยัง PlaybackStateCompat
ของเซสชันแพลตฟอร์มโดยเปลี่ยนสถานะเป็น STATE_ERROR
และตั้งค่ารหัสและข้อความแสดงข้อผิดพลาดตาม PlaybackException
การปรับแต่งข้อผิดพลาดร้ายแรง
หากต้องการแสดงข้อมูลที่แปลแล้วและมีความหมายต่อผู้ใช้ คุณสามารถปรับแต่งรหัสข้อผิดพลาด ข้อความแสดงข้อผิดพลาด และข้อมูลเพิ่มเติมเกี่ยวกับข้อผิดพลาดในการเล่นร้ายแรงได้โดยใช้ ForwardingPlayer
เมื่อสร้างเซสชัน ดังนี้
Kotlin
val forwardingPlayer = ErrorForwardingPlayer(player) val session = MediaSession.Builder(context, forwardingPlayer).build()
Java
Player forwardingPlayer = new ErrorForwardingPlayer(player); MediaSession session = new MediaSession.Builder(context, forwardingPlayer).build();
โปรแกรมเล่นที่ส่งต่อจะลงทะเบียน Player.Listener
กับโปรแกรมเล่นจริง และขัดขวางการเรียกกลับที่รายงานข้อผิดพลาด จากนั้นระบบจะมอบสิทธิ์PlaybackException
ที่กําหนดเองให้แก่ผู้ฟังที่ลงทะเบียนไว้ในโปรแกรมเล่นการส่งต่อ ผู้เล่นการส่งต่อต้องลบล้าง Player.addListener
และ Player.removeListener
เพื่อให้เข้าถึงตัวฟังได้เพื่อส่งรหัสข้อผิดพลาด ข้อความ หรือข้อมูลเพิ่มเติมที่กําหนดเอง
Kotlin
class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingPlayer(player) { private val listeners: MutableList<Player.Listener> = mutableListOf() private var customizedPlaybackException: PlaybackException? = null init { player.addListener(ErrorCustomizationListener()) } override fun addListener(listener: Player.Listener) { listeners.add(listener) } override fun removeListener(listener: Player.Listener) { listeners.remove(listener) } override fun getPlayerError(): PlaybackException? { return customizedPlaybackException } private inner class ErrorCustomizationListener : Player.Listener { override fun onPlayerErrorChanged(error: PlaybackException?) { customizedPlaybackException = error?.let { customizePlaybackException(it) } listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) } } override fun onPlayerError(error: PlaybackException) { listeners.forEach { it.onPlayerError(customizedPlaybackException!!) } } private fun customizePlaybackException( error: PlaybackException, ): PlaybackException { val buttonLabel: String val errorMessage: String when (error.errorCode) { PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> { buttonLabel = context.getString(R.string.err_button_label_restart_stream) errorMessage = context.getString(R.string.err_msg_behind_live_window) } // Apps can customize further error messages by adding more branches. else -> { buttonLabel = context.getString(R.string.err_button_label_ok) errorMessage = context.getString(R.string.err_message_default) } } val extras = Bundle() extras.putString("button_label", buttonLabel) return PlaybackException(errorMessage, error.cause, error.errorCode, extras) } override fun onEvents(player: Player, events: Player.Events) { listeners.forEach { it.onEvents(player, events) } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
Java
private static class ErrorForwardingPlayer extends ForwardingPlayer { private final Context context; private List<Player.Listener> listeners; @Nullable private PlaybackException customizedPlaybackException; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; listeners = new ArrayList<>(); player.addListener(new ErrorCustomizationListener()); } @Override public void addListener(Player.Listener listener) { listeners.add(listener); } @Override public void removeListener(Player.Listener listener) { listeners.remove(listener); } @Nullable @Override public PlaybackException getPlayerError() { return customizedPlaybackException; } private class ErrorCustomizationListener implements Listener { @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { customizedPlaybackException = error != null ? customizePlaybackException(error, context) : null; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerErrorChanged(customizedPlaybackException); } } @Override public void onPlayerError(PlaybackException error) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException)); } } private PlaybackException customizePlaybackException( PlaybackException error, Context context) { String buttonLabel; String errorMessage; switch (error.errorCode) { case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW: buttonLabel = context.getString(R.string.err_button_label_restart_stream); errorMessage = context.getString(R.string.err_msg_behind_live_window); break; // Apps can customize further error messages by adding more case statements. default: buttonLabel = context.getString(R.string.err_button_label_ok); errorMessage = context.getString(R.string.err_message_default); break; } Bundle extras = new Bundle(); extras.putString("button_label", buttonLabel); return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras); } @Override public void onEvents(Player player, Events events) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onEvents(player, events); } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
ข้อผิดพลาดที่ไม่ร้ายแรง
แอปสามารถส่งข้อผิดพลาดที่ไม่ร้ายแรงซึ่งไม่ได้มาจากข้อยกเว้นทางเทคนิคไปยังตัวควบคุมทั้งหมดหรือตัวควบคุมที่เฉพาะเจาะจงได้ ดังนี้
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. mediaSession.mediaNotificationControllerInfo?.let { mediaSession.sendError(it, sessionError) }
Java
SessionError sessionError = new SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired)); // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. ControllerInfo mediaNotificationControllerInfo = mediaSession.getMediaNotificationControllerInfo(); if (mediaNotificationControllerInfo != null) { mediaSession.sendError(mediaNotificationControllerInfo, sessionError); }
ระบบจะทําซ้ำข้อผิดพลาดที่ไม่ร้ายแรงซึ่งส่งไปยังตัวควบคุมการแจ้งเตือนสื่อไปยัง PlaybackStateCompat
ของเซสชันแพลตฟอร์ม ดังนั้น ระบบจะตั้งค่าเฉพาะรหัสข้อผิดพลาดและข้อความแสดงข้อผิดพลาดเป็น PlaybackStateCompat
ตามลำดับ ส่วน PlaybackStateCompat.state
จะไม่มีการเปลี่ยนแปลงเป็น STATE_ERROR
รับข้อผิดพลาดที่ไม่ร้ายแรง
MediaController
ได้รับข้อผิดพลาดที่ไม่ร้ายแรงจากการใช้
MediaController.Listener.onError
Kotlin
val future = MediaController.Builder(context, sessionToken) .setListener(object : MediaController.Listener { override fun onError(controller: MediaController, sessionError: SessionError) { // Handle nonfatal error. } }) .buildAsync()
Java
MediaController.Builder future = new MediaController.Builder(context, sessionToken) .setListener( new MediaController.Listener() { @Override public void onError(MediaController controller, SessionError sessionError) { // Handle nonfatal error. } });