เซสชันสื่อเป็นวิธีสากลในการโต้ตอบกับโปรแกรมเล่นเสียงหรือวิดีโอ ใน Media3 เพลเยอร์เริ่มต้นคือคลาส ExoPlayer ซึ่งใช้อินเทอร์เฟซ Player การเชื่อมต่อเซสชันสื่อกับเพลเยอร์ช่วยให้แอปโฆษณาการเล่นสื่อภายนอกและรับคำสั่งการเล่นจากแหล่งที่มาภายนอกได้
คำสั่งอาจมาจากปุ่มจริง เช่น ปุ่มเล่นบนชุดหูฟังหรือรีโมตทีวี นอกจากนี้ยังอาจมาจากแอปไคลเอ็นต์ที่มีตัวควบคุมสื่อ เช่น การสั่งให้ผู้ช่วย AI "หยุดชั่วคราว" ซึ่งรวมถึง Gemini หรือ Google Assistant เซสชันสื่อจะมอบสิทธิ์คำสั่งเหล่านี้ให้กับเพลเยอร์ของแอปสื่อ
กรณีที่ควรเลือกเซสชันสื่อ
เมื่อใช้ MediaSession คุณจะอนุญาตให้ผู้ใช้ควบคุมการเล่นได้ดังนี้
- ผ่านหูฟัง โดยปกติแล้วจะมีปุ่มหรือการโต้ตอบด้วยการสัมผัสที่ผู้ใช้สามารถทำได้บนหูฟังเพื่อเล่นหรือหยุดสื่อชั่วคราว หรือไปยังแทร็กถัดไปหรือก่อนหน้า
- โดยการพูดคุยกับผู้ช่วย AI ซึ่งรวมถึง Gemini หรือ Google Assistant รูปแบบที่พบบ่อยคือการพูดว่า "Ok Google หยุดชั่วคราว" เพื่อหยุดสื่อที่กำลังเล่นอยู่ในอุปกรณ์ชั่วคราว
- ผ่านนาฬิกา Wear OS ซึ่งจะช่วยให้เข้าถึงตัวควบคุมการเล่นที่ใช้บ่อยที่สุดได้ง่ายขึ้นขณะเล่นบนโทรศัพท์
- ผ่านตัวควบคุมสื่อ ภาพสไลด์นี้แสดงตัวควบคุมสำหรับเซสชันสื่อที่กำลังทำงานอยู่แต่ละเซสชัน
- ในทีวี อนุญาตให้ดำเนินการด้วยปุ่มเล่นจริง การควบคุมการเล่นของแพลตฟอร์ม และการจัดการพลังงาน (เช่น หากทีวี ซาวด์บาร์ หรือตัวรับสัญญาณ A/V ปิดอยู่หรือมีการเปลี่ยนอินพุต การเล่นควรหยุดในแอป)
- ผ่านตัวควบคุมสื่อของ Android Auto ซึ่งช่วยให้ควบคุมการเล่นได้อย่างปลอดภัยขณะขับรถ
- และกระบวนการภายนอกอื่นๆ ที่ต้องส่งผลต่อการเล่น
ซึ่งเหมาะสำหรับกรณีการใช้งานหลายอย่าง โดยเฉพาะอย่างยิ่ง คุณควรพิจารณาใช้ MediaSession ในกรณีต่อไปนี้
- คุณกำลังสตรีมเนื้อหาวิดีโอแบบยาว เช่น ภาพยนตร์หรือรายการทีวีสด
- คุณกำลังสตรีมเนื้อหาเสียงแบบยาว เช่น พอดแคสต์หรือเพลย์ลิสต์เพลง
- คุณกำลังสร้างแอป TV
อย่างไรก็ตาม กรณีการใช้งานบางกรณีอาจไม่เหมาะกับ MediaSession คุณอาจต้องการใช้เฉพาะ Player ในกรณีต่อไปนี้
- คุณกำลังแสดงเนื้อหาแบบสั้นซึ่งไม่จำเป็นต้องมีการควบคุมจากภายนอกหรือการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
- ไม่มีวิดีโอที่เล่นอยู่เพียงรายการเดียว เช่น ผู้ใช้กำลังเลื่อนดูรายการและมีวิดีโอหลายรายการแสดงบนหน้าจอพร้อมกัน
- คุณกำลังเล่นวิดีโอแนะนำหรือวิดีโออธิบายแบบครั้งเดียว ซึ่งคุณคาดหวังให้ผู้ใช้ตั้งใจดูโดยไม่ต้องใช้ตัวควบคุมการเล่นภายนอก
- เนื้อหาของคุณมีความละเอียดอ่อนด้านความเป็นส่วนตัว และคุณไม่ต้องการให้กระบวนการภายนอกเข้าถึงข้อมูลเมตาของสื่อ (เช่น โหมดไม่ระบุตัวตนในเบราว์เซอร์)
หากกรณีการใช้งานของคุณไม่ตรงกับรายการที่ระบุไว้ข้างต้น ให้พิจารณาว่าคุณยอมรับได้หรือไม่หากแอปเล่นต่อเมื่อผู้ใช้ไม่ได้มีส่วนร่วมกับเนื้อหาอย่างต่อเนื่อง หากคำตอบคือใช่ คุณอาจต้องการเลือก 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 จะอัปเดตเซสชันสื่อโดยอัตโนมัติโดยใช้สถานะของเพลเยอร์ ดังนั้น คุณจึงไม่จำเป็นต้องจัดการการแมปจากผู้เล่นไปยังเซสชันด้วยตนเอง
ซึ่งแตกต่างจากเซสชันสื่อของแพลตฟอร์มที่คุณต้องสร้างและดูแลรักษา PlaybackState โดยไม่ขึ้นอยู่กับตัวเพลเยอร์เอง เช่น เพื่อระบุข้อผิดพลาด
รหัสเซสชันที่ไม่ซ้ำกัน
โดยค่าเริ่มต้น MediaSession.Builder จะสร้างเซสชันที่มีสตริงว่างเป็นรหัสเซสชัน ซึ่งเพียงพอแล้วหากแอปตั้งใจที่จะสร้างอินสแตนซ์เซสชันเดียวเท่านั้น ซึ่งเป็นกรณีที่พบบ่อยที่สุด
หากแอปต้องการจัดการอินสแตนซ์เซสชันหลายรายการพร้อมกัน แอปต้องตรวจสอบว่ารหัสเซสชันของแต่ละเซสชันไม่ซ้ำกัน คุณสามารถตั้งค่ารหัสเซสชันได้เมื่อสร้างเซสชันด้วย MediaSession.Builder.setId(String id)
หากคุณเห็น IllegalStateException ทำให้แอปขัดข้องพร้อมข้อความแสดงข้อผิดพลาด IllegalStateException: Session ID must be unique. ID= แสดงว่าอาจมีการสร้างเซสชันโดยไม่คาดคิดก่อนที่อินสแตนซ์ที่สร้างไว้ก่อนหน้านี้ซึ่งมีรหัสเดียวกันจะได้รับการเผยแพร่ ระบบจะตรวจหาและแจ้งเตือนกรณีดังกล่าวโดยแสดงข้อยกเว้นเพื่อหลีกเลี่ยงไม่ให้เซสชันรั่วไหลเนื่องจากข้อผิดพลาดในการเขียนโปรแกรม
ให้สิทธิ์ควบคุมแก่ไคลเอ็นต์รายอื่น
เซสชันสื่อเป็นกุญแจสำคัญในการควบคุมการเล่น ซึ่งช่วยให้คุณกำหนดเส้นทางคำสั่งจากแหล่งที่มาภายนอกไปยังเพลเยอร์ที่ทำหน้าที่เล่นสื่อได้ แหล่งที่มาเหล่านี้อาจเป็นปุ่มจริง เช่น ปุ่มเล่นบนชุดหูฟังหรือรีโมตทีวี หรือคำสั่งทางอ้อม เช่น การสั่งให้ Gemini หรือ Google Assistant "หยุดชั่วคราว" ในทำนองเดียวกัน คุณอาจต้องการให้สิทธิ์เข้าถึงระบบ Android สำหรับการควบคุมการแจ้งเตือนและหน้าจอล็อก หรือให้สิทธิ์เข้าถึงนาฬิกา Wear OS เพื่อให้คุณควบคุมการเล่นจากหน้าปัดได้ ไคลเอ็นต์ภายนอกสามารถใช้ตัวควบคุมสื่อเพื่อส่งคำสั่งการเล่นไปยังแอปสื่อของคุณได้ เซสชันสื่อจะรับคำสั่งเหล่านี้และมอบสิทธิ์ให้แก่โปรแกรมเล่นสื่อ
เมื่อตัวควบคุมกำลังจะเชื่อมต่อกับเซสชันสื่อ เซสชันจะเรียกใช้ onConnectAsync() คุณสามารถใช้ ControllerInfo ที่ให้ไว้เพื่อตัดสินใจว่าจะยอมรับหรือปฏิเสธคำขอ ดูตัวอย่างการยอมรับคำขอเชื่อมต่อได้ที่หัวข้อประกาศและจัดการคำสั่งที่กำหนดเอง
หลังจากเชื่อมต่อแล้ว ตัวควบคุมจะส่งคำสั่งการเล่นไปยังเซสชันได้ จากนั้นเซสชันจะมอบสิทธิ์คำสั่งเหล่านั้นให้กับเพลเยอร์ เซสชันจะจัดการคำสั่งการเล่นและเพลย์ลิสต์ที่กำหนดไว้ในอินเทอร์เฟซ Player โดยอัตโนมัติ
เมธอด Callback อื่นๆ ช่วยให้คุณจัดการคำขอต่างๆ ได้ เช่น คำสั่งที่กำหนดเอง และการแก้ไขเพลย์ลิสต์ โดยการเรียกกลับเหล่านี้จะมีออบเจ็กต์ ControllerInfo เช่นเดียวกัน เพื่อให้คุณแก้ไขวิธีตอบสนองต่อคำขอแต่ละรายการตามตัวควบคุมแต่ละรายการได้
แก้ไขเพลย์ลิสต์
เซสชันสื่อสามารถแก้ไขเพลย์ลิสต์ของเพลเยอร์ได้โดยตรงตามที่อธิบายไว้ในคู่มือ ExoPlayer สำหรับเพลย์ลิสต์
นอกจากนี้ ตัวควบคุมยังแก้ไขเพลย์ลิสต์ได้หากตัวควบคุมมี COMMAND_SET_MEDIA_ITEM หรือ COMMAND_CHANGE_MEDIA_ITEMS
เมื่อเพิ่มรายการใหม่ลงในเพลย์ลิสต์ โดยปกติแล้วเพลเยอร์จะต้องมีอินสแตนซ์ MediaItem ที่มี URI ที่กำหนดไว้เพื่อให้เล่นได้ โดยค่าเริ่มต้น ระบบจะส่งต่อรายการที่เพิ่มใหม่ไปยังเมธอดของเพลเยอร์ เช่น player.addMediaItem โดยอัตโนมัติหากมีการกำหนด URI ไว้
หากต้องการปรับแต่งอินสแตนซ์ MediaItem ที่เพิ่มลงในเพลเยอร์ คุณสามารถลบล้าง onAddMediaItems() ได้ ขั้นตอนนี้จำเป็นเมื่อคุณต้องการรองรับตัวควบคุมที่ขอสื่อโดยไม่มี URI ที่กำหนดไว้ แต่โดยทั่วไปแล้ว MediaItem จะมีช่องต่อไปนี้อย่างน้อย 1 ช่องที่ตั้งค่าให้อธิบายสื่อที่ขอ
MediaItem.mediaId: รหัสทั่วไปที่ระบุสื่อMediaItem.RequestMetadata.mediaUri: URI คำขอที่อาจใช้สคีมาที่กำหนดเองและไม่จำเป็นต้องเล่นได้โดยตรงจากโปรแกรมเล่นMediaItem.RequestMetadata.searchQuery: คำค้นหาที่เป็นข้อความ เช่น จาก Gemini หรือ Google AssistantMediaItem.mediaMetadata: ข้อมูลเมตาที่มีโครงสร้าง เช่น "ชื่อ" หรือ "ศิลปิน"
หากต้องการตัวเลือกการปรับแต่งเพิ่มเติมสำหรับเพลย์ลิสต์ใหม่ทั้งหมด คุณสามารถลบล้าง onSetMediaItems() เพิ่มเติมได้ ซึ่งจะช่วยให้คุณกำหนดรายการเริ่มต้นและตำแหน่งในเพลย์ลิสต์ได้ เช่น คุณสามารถขยายรายการที่ขอรายการเดียวเป็นเพลย์ลิสต์ทั้งหมด และสั่งให้เพลเยอร์เริ่มเล่นที่ดัชนีของรายการที่ขอในตอนแรก ดูตัวอย่างการใช้งาน onSetMediaItems() ด้วยฟีเจอร์นี้ได้ในแอปเดโมของเซสชัน
เผยแพร่ข้อมูลเมตาของเพลย์ลิสต์
Media3 มี API สำหรับเผยแพร่ข้อมูลเมตาของเพลย์ลิสต์ที่มีโครงสร้างควบคู่ไปกับการเล่นแทร็กที่ใช้งานอยู่ การกำหนดมาตรฐานวิธีที่แอปสร้างการเป็นสมาชิกเพลย์ลิสต์และข้อมูลเมตาเชิงอธิบายในเซสชันสื่อต่างๆ ช่วยให้ Android แสดงจุดแรกเข้าในระดับระบบสำหรับการเล่นในแอปได้
ในบริบทนี้ คำว่าเพลย์ลิสต์หมายถึงการจัดกลุ่มแทร็กที่เรียงลำดับไว้เพื่อเล่นตามลำดับ ซึ่งไม่เพียงแต่ใช้กับรายการที่ผู้ใช้หรือแอปกำหนดไว้สำหรับรายการที่เล่นได้แต่ละรายการเท่านั้น แต่ยังใช้กับการจัดกลุ่ม เช่น อัลบั้มเพลง ตอนของพอดแคสต์ และบทของหนังสือเสียงด้วย
ประกาศรหัสเพลย์ลิสต์และข้อมูลเมตาของเพลย์ลิสต์
ใน Media3 การระบุเพลย์ลิสต์และข้อมูลเมตาที่อธิบายเพลย์ลิสต์จะแยกออกเป็น 2 ขอบเขตของ MediaMetadata ดังนี้
- รหัสเพลย์ลิสต์ระดับรายการ (
MediaItem.mediaMetadata.playlistId):- ทำเครื่องหมาย
MediaItemที่กำลังเล่นเป็นส่วนหนึ่งของเพลย์ลิสต์โดยเรียกใช้MediaMetadata.Builder.setPlaylistIdในMediaMetadataของรายการ ขณะที่MediaItem.mediaIdจะระบุแทร็กแต่ละแทร็กโดยไม่ซ้ำกัน playlistIdคือสตริงทึบแสงที่แอปกำหนดซึ่งระบุเพลย์ลิสต์อย่างไม่ซ้ำกันในเซสชันต่างๆ ตราบใดที่เพลย์ลิสต์ยังเล่นต่อได้ (เช่น37i9dQZF1DX76Wlfdnj7AP) และไม่ควรนำมาใช้ซ้ำกับเพลย์ลิสต์อื่น ตัวควบคุมที่เชื่อมต่อจะไม่ตีความรหัสนี้ แต่จะจัดเก็บและส่งกลับไปยังแอปเพื่อเล่นเพลย์ลิสต์ต่อ- ทุกแทร็กที่อยู่ในเพลย์ลิสต์จะมี
playlistIdเดียวกัน ปล่อยplaylistIdไม่ตั้งค่า (null) สำหรับแทร็กที่ไม่ได้เล่นจากบริบทเพลย์ลิสต์ เช่น แทร็กสแตนด์อโลนรายการเดียว
- ทำเครื่องหมาย
- ข้อมูลเมตาของเพลย์ลิสต์ระดับเพลเยอร์ (
Player.getPlaylistMetadata):- สร้างอินสแตนซ์
MediaMetadataโดยเฉพาะด้วยแอตทริบิวต์ที่อธิบายเพลย์ลิสต์ (setTitle,setArtworkUriและsetMediaType) แล้วตั้งค่าในเพลเยอร์โดยใช้Player.setPlaylistMetadata - เมื่อตัวควบคุมที่เชื่อมต่อตรวจพบ
currentMediaItem.mediaMetadata.playlistIdที่ไม่ใช่ค่า Null ระบบจะตรวจสอบMediaController.getPlaylistMetadataเพื่อดึงข้อมูลชื่อและ URI ของอาร์ตเวิร์กของเพลย์ลิสต์ - อัปเดตข้อมูลเมตาของเพลย์ลิสต์เมื่อใดก็ตามที่เพลย์ลิสต์ที่ใช้งานอยู่มีการเปลี่ยนแปลง (เช่น เมื่อผู้ใช้เริ่มเพลย์ลิสต์อื่น) เพื่อให้
getPlaylistMetadataอธิบายเพลย์ลิสต์ที่อ้างอิงโดยplaylistIdของรายการปัจจุบันเสมอ
- สร้างอินสแตนซ์
ตัวอย่างต่อไปนี้แสดงวิธีเผยแพร่ข้อมูลเมตาของเพลย์ลิสต์ใน Player และเชื่อมโยงแทร็ก MediaItem กับเพลย์ลิสต์นั้น
Kotlin
// 1. Declare the playlist descriptive metadata on the Player val playlistMetadata = MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build() player.setPlaylistMetadata(playlistMetadata) // 2. Mark the playing track(s) with the playlistId it belongs to val trackItem = MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build() ) .build() player.setMediaItem(trackItem)
Java
// 1. Declare the playlist descriptive metadata on the Player MediaMetadata playlistMetadata = new MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build(); player.setPlaylistMetadata(playlistMetadata); // 2. Mark the playing track(s) with the playlistId it belongs to MediaItem trackItem = new MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( new MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build()) .build(); player.setMediaItem(trackItem);
กลับมาเล่นเพลย์ลิสต์อีกครั้ง
เมื่อผู้ใช้เลือกเพลย์ลิสต์เพื่อเล่นต่อ ตัวควบคุมจะเชื่อมต่อกับเซสชันสื่อของแอปและส่ง MediaItem ของเพลย์ลิสต์ซึ่งมีการตั้งค่า mediaId เป็น playlistId ที่จัดเก็บไว้
Kotlin
val playlistItem = MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build() ) .build() controller.setMediaItem(playlistItem) controller.prepare() controller.play()
Java
MediaItem playlistItem = new MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( new MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build()) .build(); controller.setMediaItem(playlistItem); controller.prepare(); controller.play();
แอปของคุณจะขยายเพลย์ลิสต์ MediaItem เป็นแทร็กที่เล่นได้ภายใน MediaSession.Callback.onSetMediaItems (หรือ onAddMediaItems) และเผยแพร่ playlistId และ playlistMetadata อีกครั้งตามที่แสดงในส่วนก่อนหน้า
จัดการค่ากำหนดของปุ่มสื่อ
ตัวควบคุมทุกตัว เช่น UI ของระบบ, Android Auto หรือ Wear OS สามารถตัดสินใจเองได้ว่าจะแสดงปุ่มใดให้ผู้ใช้เห็น หากต้องการระบุตัวควบคุมการเล่นที่ต้องการแสดงต่อผู้ใช้ คุณสามารถระบุค่ากำหนดปุ่มสื่อใน MediaSession ค่ากำหนดเหล่านี้ประกอบด้วยรายการตามลำดับของอินสแตนซ์ CommandButton ซึ่งแต่ละอินสแตนซ์จะกำหนดค่ากำหนดสำหรับปุ่มในอินเทอร์เฟซผู้ใช้
กำหนดปุ่มคำสั่ง
ระบบจะใช้อินสแตนซ์ CommandButton เพื่อกำหนดค่ากำหนดของปุ่มสื่อ ปุ่มทุกปุ่มจะกำหนดองค์ประกอบ UI ที่ต้องการ 3 ด้าน ได้แก่
- ไอคอน ซึ่งกำหนดลักษณะที่ปรากฏ ต้องตั้งค่าไอคอนเป็นค่าคงที่ที่กำหนดไว้ล่วงหน้าค่าใดค่าหนึ่งเมื่อสร้าง
CommandButton.Builderโปรดทราบว่านี่ไม่ใช่บิตแมปหรือทรัพยากรรูปภาพจริง ค่าคงที่ทั่วไปช่วยให้ตัวควบคุมเลือกทรัพยากรที่เหมาะสมเพื่อให้มีรูปลักษณ์ที่สอดคล้องกันภายใน UI ของตนเอง หากค่าคงที่ของไอคอนที่กำหนดไว้ล่วงหน้าไม่ตรงกับ Use Case ของคุณ คุณสามารถใช้setCustomIconResIdแทนได้ - คำสั่ง ซึ่งกำหนดการดำเนินการที่เรียกใช้เมื่อผู้ใช้โต้ตอบกับปุ่ม คุณสามารถใช้
setPlayerCommandสำหรับPlayer.CommandหรือsetSessionCommandสำหรับSessionCommandที่กำหนดไว้ล่วงหน้าหรือกำหนดเอง - ช่อง ซึ่งกำหนดตำแหน่งที่ควรวางปุ่มใน UI ของตัวควบคุม ช่องนี้ไม่บังคับและระบบจะตั้งค่าโดยอัตโนมัติตามไอคอนและคำสั่ง เช่น ช่วยให้ระบุได้ว่าควรแสดงปุ่มในพื้นที่การนำทาง "ไปข้างหน้า" ของ UI แทนพื้นที่ "รายการเพิ่มเติม" เริ่มต้น
Kotlin
val button = CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build()
Java
CommandButton button = new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build();
เมื่อแก้ไขค่ากำหนดของปุ่มสื่อแล้ว ระบบจะใช้อัลกอริทึมต่อไปนี้
- สำหรับ
CommandButtonแต่ละรายการในค่ากำหนดปุ่มสื่อ ให้วางปุ่มในช่องแรกที่พร้อมใช้งานและได้รับอนุญาต - หากช่องตรงกลาง ด้านหน้า และด้านหลังไม่มีปุ่ม ให้เพิ่มปุ่มเริ่มต้นสำหรับช่องนี้
คุณสามารถใช้ CommandButton.DisplayConstraints เพื่อสร้างตัวอย่างวิธีแก้ไขค่ากำหนดของปุ่มสื่อโดยขึ้นอยู่กับข้อจำกัดการแสดงผล UI
ตั้งค่ากำหนดปุ่มสื่อ
วิธีที่ง่ายที่สุดในการตั้งค่ากำหนดปุ่มสื่อคือการกำหนดรายการเมื่อสร้าง MediaSession หรือจะลบล้าง MediaSession.Callback.onConnectAsync() เพื่อปรับแต่งค่ากำหนดปุ่มสื่อสำหรับตัวควบคุมที่เชื่อมต่อแต่ละตัวก็ได้
Kotlin
val mediaSession = MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build()
Java
MediaSession mediaSession = new MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build();
อัปเดตค่ากำหนดปุ่มสื่อหลังจากการโต้ตอบของผู้ใช้
หลังจากจัดการการโต้ตอบกับเพลเยอร์แล้ว คุณอาจต้องอัปเดตปุ่มที่แสดงใน UI ของตัวควบคุม ตัวอย่างที่พบบ่อยคือปุ่มเปิด/ปิดที่เปลี่ยนไอคอนและการทำงานหลังจากทริกเกอร์การทำงานที่เชื่อมโยงกับปุ่มนี้ หากต้องการอัปเดตค่ากำหนดของปุ่มสื่อ คุณสามารถใช้ MediaSession.setMediaButtonPreferences เพื่ออัปเดตค่ากำหนดสำหรับตัวควบคุมทั้งหมดหรือตัวควบคุมที่เฉพาะเจาะจงได้ ดังนี้
Kotlin
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton));
เพิ่มคำสั่งที่กำหนดเองและปรับแต่งลักษณะการทำงานเริ่มต้น
คำสั่งเพลเยอร์ที่ใช้ได้สามารถขยายได้ด้วยคำสั่งที่กำหนดเอง และยังสามารถสกัดกั้นคำสั่งเพลเยอร์และปุ่มสื่อที่เข้ามาเพื่อเปลี่ยนลักษณะการทำงานเริ่มต้นได้ด้วย
ประกาศและจัดการคำสั่งที่กำหนดเอง
แอปพลิเคชันสื่อสามารถกำหนดคำสั่งที่กำหนดเองซึ่งสามารถใช้ในค่ากำหนดปุ่มสื่อได้ เป็นต้น เช่น คุณอาจต้องการใช้ปุ่มที่อนุญาตให้ผู้ใช้บันทึกรายการสื่อลงในรายการโปรด MediaController จะส่งคำสั่งที่กำหนดเองและMediaSession.Callbackจะรับคำสั่งเหล่านั้น
หากต้องการกำหนดคำสั่งที่กำหนดเอง คุณต้องลบล้าง MediaSession.Callback.onConnectAsync() เพื่อตั้งค่าคำสั่งที่กำหนดเองที่ใช้ได้สำหรับตัวควบคุมที่เชื่อมต่อแต่ละตัว
Kotlin
private class CustomMediaSessionCallback : MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnectAsync( session: MediaSession, controller: ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return Futures.immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } }
Java
private static class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return Futures.immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } }
หากต้องการรับคำขอคำสั่งที่กำหนดเองจาก MediaController ให้ลบล้างเมธอด onCustomCommand() ใน Callback
Kotlin
private class CustomCallback : MediaSession.Callback { // ... override fun onCustomCommand( session: MediaSession, controller: 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)) } // ... return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } }
Java
private static class CustomCallback 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)); } // ... return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } }
คุณสามารถติดตามว่าตัวควบคุมสื่อใดกำลังส่งคำขอโดยใช้พร็อพเพอร์ตี้ packageName ของออบเจ็กต์ MediaSession.ControllerInfo ที่ส่งไปยังเมธอด Callback ซึ่งจะช่วยให้คุณปรับแต่งลักษณะการทำงานของแอปเพื่อตอบสนองต่อคำสั่งที่กำหนดได้ หากคำสั่งนั้นมาจากระบบ แอปของคุณเอง หรือแอปไคลเอ็นต์อื่นๆ
ปรับแต่งคำสั่งเพลเยอร์เริ่มต้น
ระบบจะมอบสิทธิ์คำสั่งเริ่มต้นและการจัดการสถานะทั้งหมดให้กับ Player ที่อยู่ใน MediaSession หากต้องการปรับแต่งลักษณะการทำงานของคำสั่งที่กำหนดไว้ในอินเทอร์เฟซ Player เช่น play() หรือ seekToNext() ให้รวม Player ไว้ใน ForwardingSimpleBasePlayer ก่อนส่งไปยัง MediaSession ดังนี้
Kotlin
val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
ดูข้อมูลเพิ่มเติมเกี่ยวกับ ForwardingSimpleBasePlayer ได้ที่คู่มือ ExoPlayer ในหัวข้อการปรับแต่ง
ระบุตัวควบคุมที่ขอคำสั่งเพลเยอร์
เมื่อการเรียกใช้เมธอด Player มีต้นทางมาจาก MediaController คุณสามารถระบุแหล่งที่มาของต้นทางด้วย MediaSession.controllerForCurrentRequest และรับ ControllerInfo สำหรับคำขอปัจจุบันได้ดังนี้
Kotlin
private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { private lateinit var session: MediaSession 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
private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer { private MediaSession session; 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 ที่เหมาะสมในเพลเยอร์เซสชัน
ขอแนะนำให้จัดการเหตุการณ์ปุ่มสื่อขาเข้าทั้งหมดในเมธอด Player ที่เกี่ยวข้อง สำหรับกรณีการใช้งานขั้นสูง เหตุการณ์ปุ่มสื่อจะถูกสกัดกั้นใน MediaSession.Callback.onMediaButtonEvent()
การจัดการและการรายงานข้อผิดพลาด
ข้อผิดพลาดที่เซสชันแสดงและรายงานไปยังตัวควบคุมมี 2 ประเภท ข้อผิดพลาดร้ายแรงจะรายงานความล้มเหลวในการเล่นทางเทคนิคของเพลเยอร์เซสชันที่ขัดจังหวะการเล่น ระบบจะรายงานข้อผิดพลาดร้ายแรงไปยังตัวควบคุมโดยอัตโนมัติเมื่อเกิดขึ้น ข้อผิดพลาดที่ไม่ร้ายแรงคือข้อผิดพลาดที่ไม่ใช่ทางเทคนิคหรือข้อผิดพลาดด้านนโยบายซึ่งไม่ขัดจังหวะการเล่น และแอปพลิเคชันจะส่งไปยังตัวควบคุมด้วยตนเอง
ข้อผิดพลาดร้ายแรงในการเล่น
เพลเยอร์จะรายงานข้อผิดพลาดร้ายแรงในการเล่นไปยังเซสชัน จากนั้นจะรายงานไปยังตัวควบคุมเพื่อเรียกผ่าน Player.Listener.onPlayerError(PlaybackException) และ Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
ในกรณีนี้ สถานะการเล่นจะเปลี่ยนเป็น STATE_IDLE และ MediaController.getPlayerError() จะแสดงผล PlaybackException ที่ทำให้เกิดการเปลี่ยนสถานะ ตัวควบคุมสามารถตรวจสอบ PlaybackException.errorCode เพื่อดูข้อมูลเกี่ยวกับสาเหตุของข้อผิดพลาดได้
การตั้งค่าข้อผิดพลาดของเพลเยอร์ที่กำหนดเอง
นอกเหนือจากข้อผิดพลาดร้ายแรงที่รายงานโดยเพลเยอร์แล้ว แอปพลิเคชันยังสามารถตั้งค่า PlaybackException ที่กำหนดเองในระดับ MediaSession โดยใช้ MediaSession.setPlaybackException(PlaybackException) ได้ด้วย ซึ่งช่วยให้แอปพลิเคชันส่งสัญญาณสถานะข้อผิดพลาดไปยังตัวควบคุมที่เชื่อมต่อได้ คุณสามารถตั้งค่าข้อยกเว้นสำหรับตัวควบคุมที่เชื่อมต่อทั้งหมดหรือสำหรับ ControllerInfo ที่เฉพาะเจาะจงได้
เมื่อแอปตั้งค่า PlaybackException โดยใช้ API นี้
ระบบจะแจ้งเตือนอินสแตนซ์
MediaControllerที่เชื่อมต่ออยู่ ระบบจะเรียกใช้ CallbackListener.onPlayerError(PlaybackException)และListener.onPlayerErrorChanged(@Nullable PlaybackException)ในตัวควบคุมพร้อมข้อยกเว้นที่ระบุเมธอด
MediaController.getPlayerError()จะแสดงผลPlaybackExceptionที่แอปพลิเคชันตั้งค่าไว้สถานะการเล่นของตัวควบคุมที่ได้รับผลกระทบจะเปลี่ยนเป็น
Player.STATE_IDLEระบบจะนำคำสั่งที่ใช้ได้ออก และจะเหลือเฉพาะคำสั่งอ่าน เช่น
COMMAND_GET_TIMELINEในกรณีที่ได้รับสิทธิ์แล้ว ตัวอย่างเช่น สถานะของTimelineจะหยุดนิ่งอยู่ที่สถานะเมื่อมีการใช้ข้อยกเว้นกับตัวควบคุม ระบบจะนำคำสั่งที่พยายามเปลี่ยนสถานะของเพลเยอร์ เช่นCOMMAND_PLAYออกจนกว่าแอปจะนำข้อยกเว้นการเล่นสำหรับตัวควบคุมที่ระบุออก
หากต้องการล้าง PlaybackException ที่กำหนดเองซึ่งตั้งค่าไว้ก่อนหน้านี้และคืนค่าการรายงานสถานะเพลเยอร์ตามปกติ แอปจะเรียกใช้ MediaSession.setPlaybackException(/* playbackException= */ null) หรือ MediaSession.setPlaybackException(ControllerInfo,
/* playbackException= */ null) ได้
การปรับแต่งข้อผิดพลาดร้ายแรง
หากต้องการให้ข้อมูลที่แปลแล้วและมีความหมายแก่ผู้ใช้ คุณสามารถปรับแต่งรหัสข้อผิดพลาด ข้อความแสดงข้อผิดพลาด และข้อมูลเพิ่มเติมเกี่ยวกับข้อผิดพลาดของการเล่นที่ร้ายแรงซึ่งมาจากเพลเยอร์จริงได้ โดยใช้ ForwardingSimpleBasePlayer เมื่อสร้างเซสชัน
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
คลาสย่อย ForwardingSimpleBasePlayer สามารถลบล้าง getState() เพื่อสกัดกั้นข้อผิดพลาดและปรับแต่งรหัสข้อผิดพลาด ข้อความ หรือข้อมูลเพิ่มเติมได้ ในทำนองเดียวกัน คุณยังสร้างข้อผิดพลาดใหม่ที่ไม่มีในโปรแกรมเล่นเดิมได้ด้วย ดังนี้
Kotlin
private class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { var state = super.getState() if (state.playerError != null) { state = state.buildUpon().setPlayerError(customizePlaybackException(state.playerError!!)).build() } return state } 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) } 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) } }
Java
private static class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer { private final Context context; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; } @Override protected State getState() { State state = super.getState(); if (state.playerError != null) { state = state.buildUpon().setPlayerError(customizePlaybackException(state.playerError)).build(); } return state; } private PlaybackException customizePlaybackException(PlaybackException error) { 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; 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); } }
ข้อผิดพลาดที่ไม่ร้ายแรง
แอปสามารถส่งข้อผิดพลาดที่ไม่ร้ายแรงซึ่งไม่ได้เกิดจากข้อยกเว้นทางเทคนิคไปยังตัวควบคุมทั้งหมดหรือตัวควบคุมที่เฉพาะเจาะจงได้ ดังนี้
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Option 2: Sending a nonfatal error to the media notification controller only // 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)); // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Option 2: Sending a nonfatal error to the media notification controller only // 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); }
เมื่อระบบส่งข้อผิดพลาดที่ไม่ร้ายแรงไปยังตัวควบคุมการแจ้งเตือนสื่อ รหัสข้อผิดพลาดและข้อความแสดงข้อผิดพลาดจะถูกจำลองไปยังเซสชันสื่อของแพลตฟอร์ม โดยที่ PlaybackState.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. } });