ควบคุมและโฆษณาการเล่นโดยใช้ MediaSession

เซสชันสื่อเป็นวิธีสากลในการโต้ตอบกับเครื่องเล่นเสียงหรือวิดีโอ ใน Media3 เพลเยอร์เริ่มต้นคือคลาส ExoPlayer ซึ่งใช้ อินเทอร์เฟซ Player การเชื่อมต่อเซสชันสื่อกับเพลเยอร์จะช่วยให้แอป โฆษณาการเล่นสื่อภายนอกและรับคำสั่งการเล่นจาก แหล่งที่มาภายนอกได้

คำสั่งอาจมาจากปุ่มจริง เช่น ปุ่มเล่นบน ชุดหูฟังหรือรีโมตทีวี หรืออาจมาจากแอปไคลเอ็นต์ที่มี ตัวควบคุมสื่อ เช่น การสั่งให้ Google Assistant "หยุดชั่วคราว" เซสชันสื่อ จะมอบหมายคำสั่งเหล่านี้ไปยังเพลเยอร์ของแอปสื่อ

ควรเลือกเซสชันสื่อเมื่อใด

เมื่อใช้ MediaSession คุณจะอนุญาตให้ผู้ใช้ควบคุมการเล่นได้ดังนี้

  • ผ่านหูฟัง โดยมักจะมีปุ่มหรือการโต้ตอบแบบสัมผัสที่ผู้ใช้สามารถทำบนหูฟังเพื่อเล่นหรือหยุดสื่อชั่วคราว หรือไปที่แทร็กถัดไป หรือก่อนหน้า
  • โดยพูดกับ Google Assistant รูปแบบที่ใช้กันทั่วไปคือพูดว่า "Ok Google หยุดชั่วคราว" เพื่อหยุดสื่อที่กำลังเล่นอยู่ในอุปกรณ์ชั่วคราว
  • ผ่านนาฬิกา Wear OS ซึ่งจะช่วยให้เข้าถึงการควบคุมการเล่นที่ใช้กันมากที่สุดได้ง่ายขึ้นขณะเล่นบนโทรศัพท์
  • ผ่านตัวควบคุมสื่อ ภาพสไลด์นี้จะแสดงตัวควบคุมสำหรับเซสชันสื่อที่กำลังทำงานแต่ละเซสชัน
  • บนทีวี อนุญาตการดำเนินการด้วยปุ่มเล่นจริง การควบคุมการเล่นบนแพลตฟอร์ม และการจัดการพลังงาน (เช่น หากทีวี ซาวด์บาร์ หรือตัวรับสัญญาณ A/V ปิดอยู่หรือมีการสลับอินพุต การเล่นควรหยุดในแอป)
  • ผ่านตัวควบคุมสื่อของ Android Auto ซึ่งช่วยให้ควบคุมการเล่นได้อย่างปลอดภัยขณะขับรถ
  • และกระบวนการภายนอกอื่นๆ ที่ต้องมีอิทธิพลต่อการเล่น

ซึ่งเหมาะกับกรณีการใช้งานหลายอย่าง โดยเฉพาะอย่างยิ่ง คุณควรพิจารณาใช้ MediaSession เมื่อ

  • คุณกำลังสตรีมเนื้อหาวิดีโอแบบยาว เช่น ภาพยนตร์หรือทีวีสด
  • คุณกำลังสตรีมเนื้อหาเสียงแบบยาว เช่น พอดแคสต์หรือเพลย์ลิสต์เพลง
  • คุณกำลังสร้างแอปทีวี

อย่างไรก็ตาม กรณีการใช้งานบางอย่างอาจไม่เหมาะกับ 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= แสดงว่ามีแนวโน้มที่ระบบสร้างเซสชันโดยไม่คาดคิดก่อนที่จะปล่อยอินสแตนซ์ที่สร้างไว้ก่อนหน้านี้ซึ่งมีรหัสเดียวกัน ระบบจะตรวจหาและแจ้งเตือนกรณีดังกล่าวโดยการส่งข้อยกเว้นเพื่อป้องกันไม่ให้เซสชันรั่วไหลเนื่องจากข้อผิดพลาดในการเขียนโปรแกรม

มอบสิทธิ์ควบคุมให้กับไคลเอ็นต์อื่นๆ

เซสชันสื่อเป็นกุญแจสำคัญในการควบคุมการเล่น ซึ่งช่วยให้คุณกำหนดเส้นทาง คำสั่งจากแหล่งที่มาภายนอกไปยังเพลเยอร์ที่ทำหน้าที่เล่นสื่อ ได้ แหล่งที่มาเหล่านี้อาจเป็นปุ่มจริง เช่น ปุ่มเล่นบน ชุดหูฟังหรือรีโมตคอนโทรลของทีวี หรือคำสั่งโดยอ้อม เช่น การสั่งให้ "หยุดชั่วคราว" แก่ Google Assistant ในทำนองเดียวกัน คุณอาจต้องการให้สิทธิ์เข้าถึงระบบ Android เพื่ออำนวยความสะดวกในการควบคุมการแจ้งเตือนและหน้าจอล็อก หรือให้สิทธิ์เข้าถึงนาฬิกา Wear OS เพื่อให้คุณควบคุมการเล่นจากหน้าปัดนาฬิกาได้ ไคลเอ็นต์ภายนอกสามารถ ใช้ตัวควบคุมสื่อเพื่อออกคำสั่งการเล่นไปยังแอปสื่อของคุณได้ โดยคำสั่งเหล่านี้จะ ได้รับจากเซสชันสื่อของคุณ ซึ่งจะส่งต่อคำสั่งไปยัง เครื่องเล่นสื่อในที่สุด

แผนภาพที่แสดงการโต้ตอบระหว่าง MediaSession กับ MediaController
รูปที่ 1: ตัวควบคุมสื่อช่วยในการส่ง คำสั่งจากแหล่งที่มาภายนอกไปยังเซสชันสื่อ

เมื่อกำลังจะเชื่อมต่อคอนโทรลเลอร์กับเซสชันสื่อ ระบบจะเรียกใช้เมธอด onConnect() คุณสามารถใช้ControllerInfo ที่ระบุไว้ เพื่อตัดสินใจว่าจะยอมรับ หรือปฏิเสธ คำขอ ดูตัวอย่างการยอมรับคำขอเชื่อมต่อได้ในส่วนประกาศ คำสั่งที่กำหนดเอง

หลังจากเชื่อมต่อแล้ว คอนโทรลเลอร์จะส่งคำสั่งการเล่นไปยังเซสชันได้ จากนั้น เซสชันจะมอบสิทธิ์คำสั่งเหล่านั้นให้แก่เพลเยอร์ เซสชันจะจัดการคำสั่งการเล่นและเพลย์ลิสต์ที่กำหนดไว้ในอินเทอร์เฟซ Player โดยอัตโนมัติ

วิธีการเรียกกลับอื่นๆ ช่วยให้คุณจัดการคำขอสำหรับคำสั่งที่กำหนดเองและการแก้ไขเพลย์ลิสต์ได้ เป็นต้น โดย การเรียกกลับเหล่านี้จะมีออบเจ็กต์ ControllerInfo เช่นเดียวกันเพื่อให้คุณแก้ไขวิธี ตอบสนองต่อคำขอแต่ละรายการตามตัวควบคุมแต่ละตัวได้

แก้ไขเพลย์ลิสต์

เซสชันสื่อสามารถแก้ไขเพลย์ลิสต์ของเพลเยอร์ได้โดยตรงตามที่อธิบายไว้ใน คำแนะนำ ExoPlayer สำหรับเพลย์ลิสต์ นอกจากนี้ ผู้ควบคุมยังแก้ไขเพลย์ลิสต์ได้หากมีสิทธิ์ COMMAND_SET_MEDIA_ITEM หรือ COMMAND_CHANGE_MEDIA_ITEMS สำหรับผู้ควบคุม

เมื่อเพิ่มรายการใหม่ลงในเพลย์ลิสต์ โดยปกติแล้วเพลเยอร์จะต้องมีMediaItem อินสแตนซ์ที่มีURI ที่กำหนด เพื่อให้เล่นได้ โดยค่าเริ่มต้น ระบบจะส่งต่อรายการที่เพิ่มใหม่ไปยังเมธอดของเพลเยอร์โดยอัตโนมัติ เช่น player.addMediaItem หากมีการกำหนด URI

หากต้องการปรับแต่งMediaItemอินสแตนซ์ที่เพิ่มลงในเพลเยอร์ คุณสามารถonAddMediaItems()ได้ ขั้นตอนนี้จำเป็นเมื่อคุณต้องการรองรับตัวควบคุมที่ขอสื่อ โดยไม่มี URI ที่กำหนด แต่โดยปกติแล้ว MediaItem จะมี ฟิลด์ต่อไปนี้อย่างน้อย 1 รายการเพื่ออธิบายสื่อที่ขอ

  • MediaItem.id: รหัสทั่วไปที่ระบุสื่อ
  • MediaItem.RequestMetadata.mediaUri: URI คำขอที่อาจใช้สคีมาที่กำหนดเองและไม่จำเป็นต้องเล่นโดยเพลเยอร์โดยตรง
  • MediaItem.RequestMetadata.searchQuery: คำค้นหาที่เป็นข้อความ เช่น จาก Google Assistant
  • MediaItem.MediaMetadata: ข้อมูลเมตาที่มีโครงสร้าง เช่น "ชื่อ" หรือ "ศิลปิน"

หากต้องการดูตัวเลือกการปรับแต่งเพิ่มเติมสำหรับเพลย์ลิสต์ใหม่ทั้งหมด คุณสามารถ ลบล้างเพิ่มเติม onSetMediaItems() ซึ่งจะช่วยให้คุณกำหนดรายการเริ่มต้นและตำแหน่งในเพลย์ลิสต์ได้ ตัวอย่างเช่น คุณขยายรายการที่ขอรายการเดียวให้เป็นทั้งเพลย์ลิสต์และสั่งให้ เพลเยอร์เริ่มที่ดัชนีของรายการที่ขอในตอนแรกได้ ดูตัวอย่างการใช้งาน onSetMediaItems() ด้วยฟีเจอร์นี้ได้ในแอปเดโมของเซสชัน

จัดการค่ากำหนดปุ่มสื่อ

ตัวควบคุมทุกตัว เช่น UI ของระบบ, Android Auto หรือ Wear OS สามารถตัดสินใจเองได้ว่าจะแสดงปุ่มใดแก่ผู้ใช้ หากต้องการระบุตัวควบคุมการเล่นที่ต้องการแสดงต่อผู้ใช้ คุณสามารถระบุค่ากำหนดของปุ่มสื่อใน MediaSession ได้ ค่ากำหนดเหล่านี้ประกอบด้วยรายการที่เรียงลำดับของCommandButtonอินสแตนซ์ ซึ่งแต่ละอินสแตนซ์จะกำหนดค่ากำหนดสำหรับปุ่มใน อินเทอร์เฟซผู้ใช้

กำหนดปุ่มคำสั่ง

CommandButton ใช้เพื่อกำหนดค่ากำหนดปุ่มสื่อ ปุ่มทุกปุ่มจะกำหนด 3 ด้านขององค์ประกอบ UI ที่ต้องการ ดังนี้

  1. ไอคอนที่กำหนดลักษณะที่ปรากฏ ต้องตั้งค่าไอคอนเป็นค่าคงที่ที่กำหนดไว้ล่วงหน้าค่าใดค่าหนึ่งเมื่อสร้าง CommandButton.Builder โปรดทราบว่า นี่ไม่ใช่บิตแมปหรือทรัพยากรรูปภาพจริง ค่าคงที่ทั่วไปช่วยให้ คอนโทรลเลอร์เลือกทรัพยากรที่เหมาะสมเพื่อให้รูปลักษณ์ที่สอดคล้องกัน ภายใน UI ของตนเอง หากค่าคงที่ของไอคอนที่กำหนดไว้ล่วงหน้าไม่เหมาะกับกรณีการใช้งานของคุณ คุณสามารถใช้ setCustomIconResId แทนได้
  2. Command ซึ่งกำหนดการดำเนินการที่ทริกเกอร์เมื่อผู้ใช้โต้ตอบกับปุ่ม คุณใช้ setPlayerCommand สำหรับ Player.Command หรือ setSessionCommand สำหรับ SessionCommand ที่กำหนดไว้ล่วงหน้าหรือกำหนดเองได้
  3. ช่องที่กำหนดตำแหน่งที่ควรวางปุ่มใน UI ของคอนโทรลเลอร์ ช่องนี้ไม่บังคับและระบบจะตั้งค่าโดยอัตโนมัติตามไอคอนและคำสั่ง เช่น ช่วยให้ระบุได้ว่าควรแสดงปุ่มในพื้นที่การนำทาง "ไปข้างหน้า" ของ UI แทนที่จะเป็นพื้นที่ "ล้น" เริ่มต้น

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setSessionCommand(SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setSessionCommand(new SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

เมื่อแก้ไขค่ากำหนดของปุ่มสื่อแล้ว ระบบจะใช้อัลกอริทึมต่อไปนี้

  1. สำหรับแต่ละ CommandButton ในค่ากำหนดปุ่มสื่อ ให้วางปุ่ม ในช่องแรกที่พร้อมใช้งานและอนุญาต
  2. หากช่องกลาง ช่องไปข้างหน้า และช่องย้อนกลับไม่ได้รับปุ่ม ให้เพิ่มปุ่มเริ่มต้นสำหรับช่องนี้

คุณใช้ CommandButton.DisplayConstraints เพื่อสร้างตัวอย่างของวิธี การกำหนดค่าปุ่มสื่อตามข้อจำกัดในการแสดง UI ได้

ตั้งค่ากำหนดปุ่มสื่อ

วิธีที่ง่ายที่สุดในการตั้งค่ากำหนดปุ่มสื่อคือการกำหนดรายการเมื่อสร้าง MediaSession หรือจะลบล้าง MediaSession.Callback.onConnectเพื่อปรับแต่งค่ากำหนดปุ่มสื่อสำหรับ ตัวควบคุมที่เชื่อมต่อแต่ละตัวก็ได้

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.onConnect() เพื่อตั้งค่าคำสั่งที่กำหนดเองที่ใช้ได้สำหรับ ตัวควบคุมที่เชื่อมต่อแต่ละตัว

Kotlin

private 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 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 ซึ่งจะช่วยให้คุณปรับแต่งลักษณะการทำงานของแอป เพื่อตอบสนองต่อคำสั่งที่กำหนดได้ ไม่ว่าคำสั่งนั้นจะมาจากระบบ แอปของคุณเอง หรือแอปไคลเอ็นต์อื่นๆ

ปรับแต่งคำสั่งเริ่มต้นของเพลเยอร์

ระบบจะมอบหมายคำสั่งเริ่มต้นและการจัดการสถานะทั้งหมดให้กับ Player ที่อยู่ใน MediaSession หากต้องการปรับแต่งลักษณะการทำงานของคำสั่งที่กำหนดไว้ในอินเทอร์เฟซ 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 method ถูกเรียกใช้โดย 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เมธอดที่เหมาะสมในเครื่องเล่นเซสชัน

ขอแนะนำให้จัดการเหตุการณ์ปุ่มสื่อขาเข้าทั้งหมดในเมธอด Player ที่เกี่ยวข้อง สำหรับกรณีการใช้งานขั้นสูงขึ้น คุณจะสกัดกั้นเหตุการณ์ปุ่มสื่อ ได้ใน MediaSession.Callback.onMediaButtonEvent(Intent)

การจัดการและการรายงานข้อผิดพลาด

เซสชันจะส่งและรายงานข้อผิดพลาด 2 ประเภทไปยังตัวควบคุม ข้อผิดพลาดร้ายแรงจะรายงานการเล่นเซสชัน ในโปรแกรมเล่นที่ล้มเหลวทางเทคนิคซึ่งขัดขวางการเล่น ระบบจะรายงานข้อผิดพลาดร้ายแรงไปยังคอนโทรลเลอร์ โดยอัตโนมัติเมื่อเกิดข้อผิดพลาด ข้อผิดพลาดที่ไม่ร้ายแรงคือข้อผิดพลาดที่ไม่ใช่ทางเทคนิคหรือข้อผิดพลาดด้านนโยบาย ซึ่งไม่ขัดขวางการเล่น และแอปพลิเคชันจะส่งข้อผิดพลาดเหล่านี้ไปยังตัวควบคุมด้วยตนเอง

ข้อผิดพลาดร้ายแรงในการเล่น

โปรแกรมเล่นจะรายงานข้อผิดพลาดในการเล่นที่ร้ายแรงไปยังเซสชัน จากนั้นจะรายงานไปยังตัวควบคุมเพื่อเรียกผ่าน Player.Listener.onPlayerError(PlaybackException) และ Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)

ในกรณีดังกล่าว สถานะการเล่นจะเปลี่ยนเป็น STATE_IDLE และ MediaController.getPlaybackError() จะแสดง PlaybackException ที่ทำให้เกิด การเปลี่ยนสถานะ โดยคอนโทรลเลอร์สามารถตรวจสอบ PlayerException.errorCode เพื่อดู ข้อมูลเกี่ยวกับสาเหตุของข้อผิดพลาดได้

เพื่อการทำงานร่วมกัน ระบบจะจำลองข้อผิดพลาดร้ายแรงไปยังเซสชันแพลตฟอร์มโดยเปลี่ยนสถานะเป็น 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();

ผู้เล่นที่ส่งต่อสามารถใช้ ForwardingSimpleBasePlayer เพื่อสกัดกั้น ข้อผิดพลาดและปรับแต่งรหัสข้อผิดพลาด ข้อความ หรือข้อมูลเพิ่มเติมได้ ในทำนองเดียวกัน คุณยัง สร้างข้อผิดพลาดใหม่ที่ไม่มีในเพลเยอร์ต้นฉบับได้ด้วย โดยทำดังนี้

Kotlin

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
  }

  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

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.
              }
            });