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

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

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

กรณีที่ควรเลือกเซสชันสื่อ

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

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

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

นี่คือช่วงพักจากวิธีการเดิมที่คุณต้องสร้างและดูแลรักษา PlaybackStateCompat แยกจากตัวโปรแกรมเล่น เช่น ระบุข้อผิดพลาด

รหัสเซสชันที่ไม่ซ้ำกัน

โดยค่าเริ่มต้น 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 ได้รับการจัดการโดยอัตโนมัติโดย เซสชัน

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

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

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

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

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

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

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

จัดการเลย์เอาต์ที่กำหนดเองและคำสั่งที่กำหนดเอง

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

กำหนดเลย์เอาต์ที่กำหนดเองของเซสชัน

เพื่อระบุให้แอปไคลเอ็นต์ทราบถึงตัวควบคุมการเล่นที่คุณต้องการให้แสดง ผู้ใช้ ให้ตั้งการออกแบบที่กำหนดเองของเซสชัน เมื่อสร้าง MediaSession ในเมธอด onCreate() ของ service.

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 เมื่อเชื่อมต่อกับเซสชันสื่อของคุณ คุณบรรลุเป้าหมายนี้ได้ภายในวันที่ การลบล้าง 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 ไว้ใน ForwardingPlayer

Kotlin

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

ดูข้อมูลเพิ่มเติมเกี่ยวกับ ForwardingPlayer ได้ที่คู่มือ ExoPlayer เกี่ยวกับ การปรับแต่ง

ระบุตัวควบคุมที่ขอของคำสั่งโปรแกรมเล่น

เมื่อการเรียกไปยังเมธอด Player มาจาก MediaController คุณจะดำเนินการต่อไปนี้ได้ ระบุแหล่งที่มาของต้นทางด้วย MediaSession.controllerForCurrentRequest และรับ ControllerInfo สำหรับคำขอปัจจุบัน:

Kotlin

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

Java

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

ตอบสนองต่อปุ่มสื่อ

ปุ่มสื่อคือปุ่มฮาร์ดแวร์ที่พบในอุปกรณ์ 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 กับโปรแกรมเล่นจริง และดักจับ Callback ที่รายงานข้อผิดพลาด ที่กำหนดเอง จากนั้นจะมอบสิทธิ์ PlaybackException ให้กับผู้ฟังที่ ที่ลงทะเบียนในโปรแกรมเล่นที่ส่งต่อแล้ว เพื่อให้วิธีนี้ทำงานได้ โปรแกรมเล่นที่ส่งต่อ ลบล้าง Player.addListener และ Player.removeListener เพื่อให้มีสิทธิ์เข้าถึง Listener ที่จะส่งรหัสข้อผิดพลาด ข้อความ หรือรายการเพิ่มเติมที่กำหนดเอง:

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