با استفاده از MediaSession پخش را کنترل و تبلیغ کنید

جلسات رسانه راهی جهانی برای تعامل با پخش کننده صوتی یا تصویری را ارائه می دهند. در Media3، پخش کننده پیش فرض کلاس ExoPlayer است که رابط Player را پیاده سازی می کند. اتصال جلسه رسانه به پخش کننده به یک برنامه امکان می دهد پخش رسانه را به صورت خارجی تبلیغ کند و دستورات پخش را از منابع خارجی دریافت کند.

دستورات ممکن است از دکمه های فیزیکی مانند دکمه پخش روی هدست یا کنترل از راه دور تلویزیون نشات گرفته باشند. آنها همچنین ممکن است از برنامه های مشتری که دارای کنترلر رسانه هستند، مانند دستور "مکث" به دستیار Google باشند. جلسه رسانه این دستورات را به پخش کننده برنامه رسانه واگذار می کند.

زمان انتخاب یک جلسه رسانه ای

هنگامی که MediaSession پیاده سازی می کنید، به کاربران اجازه می دهید پخش را کنترل کنند:

  • از طریق هدفون آنها. اغلب دکمه ها یا فعل و انفعالات لمسی وجود دارد که کاربر می تواند روی هدفون خود برای پخش یا توقف رسانه یا رفتن به آهنگ بعدی یا قبلی انجام دهد.
  • با صحبت کردن با دستیار Google . یک الگوی رایج این است که بگویید «OK Google, pause» برای توقف موقت هر رسانه ای که در حال حاضر در دستگاه پخش می شود.
  • از طریق ساعت Wear OS آنها. این امکان دسترسی آسان‌تر به رایج‌ترین کنترل‌های پخش را در حین بازی در تلفن آنها فراهم می‌کند.
  • از طریق کنترل های رسانه ای این چرخ فلک کنترل‌ها را برای هر جلسه رسانه در حال اجرا نشان می‌دهد.
  • در تلویزیون . به عملکردهایی با دکمه‌های پخش فیزیکی، کنترل پخش پلت فرم و مدیریت انرژی اجازه می‌دهد (مثلاً اگر تلویزیون، نوار صوتی یا گیرنده A/V خاموش شود یا ورودی تغییر کند، پخش باید در برنامه متوقف شود).
  • و هر فرآیند خارجی دیگری که باید بر پخش تأثیر بگذارد.

این برای بسیاری از موارد استفاده عالی است. به ویژه، شما باید به شدت از MediaSession استفاده کنید زمانی که:

  • شما در حال پخش جریانی محتوای ویدیویی طولانی مدت هستید، مانند فیلم یا تلویزیون زنده.
  • شما در حال پخش محتوای صوتی طولانی مدت هستید، مانند پادکست ها یا لیست های پخش موسیقی.
  • شما در حال ساختن یک برنامه تلویزیونی هستید.

با این حال، همه موارد استفاده به خوبی با MediaSession مطابقت ندارند. ممکن است بخواهید فقط از Player در موارد زیر استفاده کنید:

  • شما محتوای کوتاهی را نشان می‌دهید که در آن تعامل و تعامل کاربر بسیار مهم است.
  • هیچ ویدیوی فعالی وجود ندارد، مثلاً کاربر در حال پیمایش در فهرست است و چندین ویدیو به طور همزمان روی صفحه نمایش داده می‌شوند.
  • شما در حال پخش یک ویدیوی معرفی یا توضیح هستید که از کاربر انتظار دارید فعالانه آن را تماشا کند.
  • محتوای شما به حریم خصوصی حساس است و نمی‌خواهید فرآیندهای خارجی به فراداده رسانه دسترسی داشته باشند (به عنوان مثال حالت ناشناس در مرورگر)

اگر مورد استفاده شما با هیچ یک از موارد ذکر شده در بالا مطابقت ندارد، در نظر بگیرید که آیا از ادامه پخش برنامه خود در زمانی که کاربر فعالانه با محتوا درگیر نیست، مشکلی ندارید یا خیر. اگر پاسخ مثبت است، احتمالاً می خواهید MediaSession انتخاب کنید. اگر پاسخ منفی است، احتمالاً می خواهید به جای آن از Player استفاده کنید.

یک جلسه رسانه ای ایجاد کنید

یک جلسه رسانه در کنار پخش کننده ای که مدیریت می کند زندگی می کند. می توانید یک جلسه رسانه با یک Context و یک شی Player بسازید. شما باید یک جلسه رسانه را در صورت نیاز ایجاد و مقداردهی کنید، مانند متد چرخه حیات onStart() یا onResume() Activity یا Fragment ، یا متد onCreate() از Service که صاحب جلسه رسانه و پخش کننده مربوط به آن است.

برای ایجاد یک جلسه رسانه، یک Player مقداردهی اولیه کنید و آن را به MediaSession.Builder به شکل زیر عرضه کنید:

کاتلین

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

جاوا

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 باشند. به همین ترتیب، ممکن است بخواهید به سیستم 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 معمولاً دارای یک یا چند فیلد زیر برای توصیف رسانه درخواستی است:

  • MediaItem.id : یک شناسه عمومی که رسانه را شناسایی می کند.
  • MediaItem.RequestMetadata.mediaUri : یک URI درخواستی که ممکن است از یک طرحواره سفارشی استفاده کند و لزوماً مستقیماً توسط پخش کننده قابل پخش نیست.
  • MediaItem.RequestMetadata.searchQuery : یک عبارت جستجوی متنی، به عنوان مثال از Google Assistant.
  • MediaItem.MediaMetadata : فراداده ساختاریافته مانند "عنوان" یا "هنرمند".

برای گزینه‌های سفارشی‌سازی بیشتر برای لیست‌های پخش کاملاً جدید، می‌توانید علاوه بر این، onSetMediaItems() را لغو کنید که به شما امکان می‌دهد آیتم شروع و موقعیت را در لیست پخش تعریف کنید. به عنوان مثال، می توانید یک آیتم درخواستی را به کل لیست پخش گسترش دهید و به پخش کننده دستور دهید که از فهرست آیتم درخواست شده اولیه شروع کند. نمونه ای از پیاده سازی onSetMediaItems() با این ویژگی را می توان در برنامه نمایشی جلسه یافت.

طرح بندی سفارشی و دستورات سفارشی را مدیریت کنید

بخش‌های زیر نحوه تبلیغ یک چیدمان سفارشی دکمه‌های فرمان سفارشی را برای برنامه‌های مشتری و اجازه دادن به کنترل‌کننده‌ها برای ارسال دستورات سفارشی را توضیح می‌دهند.

طرح سفارشی جلسه را تعریف کنید

برای نشان دادن کنترل‌های بازپخشی که می‌خواهید به برنامه‌های مشتری نشان داده شود، طرح‌بندی سفارشی جلسه را هنگام ساخت MediaSession در متد onCreate() سرویس خود تنظیم کنید.

کاتلین

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()
}

جاوا

@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 در روش پاسخ به تماس onConnect ، مجموعه دستورات موجود را پیکربندی و برگردانید:

کاتلین

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()
  }
}

جاوا

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 لغو کنید.

کاتلین

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

جاوا

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 ارسال می‌شود، ردیابی کنید که کدام کنترل‌کننده رسانه درخواست ارسال می‌کند. این به شما این امکان را می دهد که رفتار برنامه خود را در پاسخ به دستور داده شده در صورتی که از سیستم، برنامه شخصی شما یا سایر برنامه های مشتری نشات می گیرد، تنظیم کنید.

پس از تعامل با کاربر، طرح بندی سفارشی را به روز کنید

پس از مدیریت یک فرمان سفارشی یا هر تعامل دیگری با پخش کننده خود، ممکن است بخواهید طرح نمایش داده شده در رابط کاربری کنترلر را به روز کنید. یک مثال معمولی یک دکمه جابجایی است که نماد خود را پس از فعال کردن عملکرد مرتبط با این دکمه تغییر می‌دهد. برای به‌روزرسانی طرح‌بندی، می‌توانید از MediaSession.setCustomLayout استفاده کنید:

کاتلین

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))

جاوا

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() ، قبل از ارسال آن به MediaSession ، Player خود را در ForwardingSimpleBasePlayer بپیچید.

کاتلین

val player = (logic to build a Player instance)

val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) {
  // Customizations
}

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

جاوا

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 برای درخواست فعلی بدست آورید:

کاتلین

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

جاوا

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 را به تنهایی مدیریت کند.

رسیدگی و گزارش خطا

دو نوع خطا وجود دارد که یک جلسه منتشر می کند و به کنترل کننده ها گزارش می دهد. خطاهای مهلک نقص فنی پخش کننده جلسه را گزارش می دهند که پخش را قطع می کند. خطاهای مهلک در صورت وقوع به طور خودکار به کنترل کننده گزارش می شود. خطاهای غیرمرگبار خطاهای غیر فنی یا خط مشی هستند که پخش را قطع نمی کنند و توسط برنامه به صورت دستی برای کنترلرها ارسال می شوند.

خطاهای پخش مرگبار

یک خطای مرگبار پخش به جلسه توسط پخش کننده گزارش می شود و سپس به کنترل کننده ها گزارش می شود تا از طریق Player.Listener.onPlayerError(PlaybackException) و Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException) تماس بگیرند.

در چنین حالتی، وضعیت پخش به STATE_IDLE منتقل می‌شود و MediaController.getPlaybackError() PlaybackException را که باعث انتقال شده است برمی‌گرداند. یک کنترلر می تواند PlayerException.errorCode بررسی کند تا اطلاعاتی در مورد دلیل خطا به دست آورد.

برای قابلیت همکاری، یک خطای مهلک با انتقال وضعیت آن به STATE_ERROR و تنظیم کد خطا و پیام مطابق PlaybackException ، به PlaybackStateCompat جلسه پلت فرم تکرار می شود.

سفارشی سازی یک خطای کشنده

برای ارائه اطلاعات محلی و معنی دار به کاربر، کد خطا، پیام خطا و موارد اضافی خطای خطای پخش مرگبار را می توان با استفاده از ForwardingPlayer هنگام ساخت جلسه سفارشی کرد:

کاتلین

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

جاوا

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

پخش کننده فوروارد کننده یک Player.Listener را در پخش کننده واقعی ثبت می کند و تماس هایی که خطا را گزارش می کنند را قطع می کند. سپس یک PlaybackException سفارشی شده به شنوندگانی که در پخش کننده فوروارد ثبت شده اند واگذار می شود. برای اینکه این کار کار کند، پخش کننده فوروارد Player.addListener و Player.removeListener را لغو می کند تا به شنوندگانی دسترسی داشته باشد که با آن کد خطا، پیام یا موارد اضافی ارسال شود:

کاتلین

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

جاوا

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

خطاهای غیر کشنده

خطاهای غیر کشنده ای که از یک استثنا فنی منشأ نمی گیرند می توانند توسط یک برنامه برای همه یا به یک کنترل کننده خاص ارسال شوند:

کاتلین

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

جاوا

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 یک خطای غیرمرگبار دریافت می کند:

کاتلین

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

جاوا

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });

،

جلسات رسانه راهی جهانی برای تعامل با پخش کننده صوتی یا تصویری را ارائه می دهند. در Media3، پخش کننده پیش فرض کلاس ExoPlayer است که رابط Player را پیاده سازی می کند. اتصال جلسه رسانه به پخش کننده به یک برنامه امکان می دهد پخش رسانه را به صورت خارجی تبلیغ کند و دستورات پخش را از منابع خارجی دریافت کند.

دستورات ممکن است از دکمه های فیزیکی مانند دکمه پخش روی هدست یا کنترل از راه دور تلویزیون نشات گرفته باشند. آنها همچنین ممکن است از برنامه های مشتری که دارای کنترلر رسانه هستند، مانند دستور "مکث" به دستیار Google باشند. جلسه رسانه این دستورات را به پخش کننده برنامه رسانه واگذار می کند.

زمان انتخاب یک جلسه رسانه ای

هنگامی که MediaSession پیاده سازی می کنید، به کاربران اجازه می دهید پخش را کنترل کنند:

  • از طریق هدفون آنها. اغلب دکمه ها یا فعل و انفعالات لمسی وجود دارد که کاربر می تواند روی هدفون خود برای پخش یا توقف رسانه یا رفتن به آهنگ بعدی یا قبلی انجام دهد.
  • با صحبت کردن با دستیار Google . یک الگوی رایج این است که بگویید «OK Google, pause» برای توقف موقت هر رسانه ای که در حال حاضر در دستگاه پخش می شود.
  • از طریق ساعت Wear OS آنها. این امکان دسترسی آسان‌تر به رایج‌ترین کنترل‌های پخش را در حین بازی در تلفن آنها فراهم می‌کند.
  • از طریق کنترل های رسانه ای این چرخ فلک کنترل‌ها را برای هر جلسه رسانه در حال اجرا نشان می‌دهد.
  • در تلویزیون . به عملکردهایی با دکمه‌های پخش فیزیکی، کنترل پخش پلت فرم و مدیریت انرژی اجازه می‌دهد (مثلاً اگر تلویزیون، نوار صوتی یا گیرنده A/V خاموش شود یا ورودی تغییر کند، پخش باید در برنامه متوقف شود).
  • و هر فرآیند خارجی دیگری که باید بر پخش تأثیر بگذارد.

این برای بسیاری از موارد استفاده عالی است. به ویژه، شما باید به شدت از MediaSession استفاده کنید زمانی که:

  • شما در حال پخش جریانی محتوای ویدیویی طولانی مدت هستید، مانند فیلم یا تلویزیون زنده.
  • شما در حال پخش محتوای صوتی طولانی مدت هستید، مانند پادکست ها یا لیست های پخش موسیقی.
  • شما در حال ساختن یک برنامه تلویزیونی هستید.

با این حال، همه موارد استفاده به خوبی با MediaSession مطابقت ندارند. ممکن است بخواهید فقط از Player در موارد زیر استفاده کنید:

  • شما محتوای کوتاهی را نشان می‌دهید که در آن تعامل و تعامل کاربر بسیار مهم است.
  • هیچ ویدیوی فعالی وجود ندارد، مثلاً کاربر در حال پیمایش در فهرست است و چندین ویدیو به طور همزمان روی صفحه نمایش داده می‌شوند.
  • شما در حال پخش یک ویدیوی معرفی یا توضیح هستید که از کاربر انتظار دارید فعالانه آن را تماشا کند.
  • محتوای شما به حریم خصوصی حساس است و نمی‌خواهید فرآیندهای خارجی به فراداده رسانه دسترسی داشته باشند (به عنوان مثال حالت ناشناس در مرورگر)

اگر مورد استفاده شما با هیچ یک از موارد ذکر شده در بالا مطابقت ندارد، در نظر بگیرید که آیا از ادامه پخش برنامه خود در زمانی که کاربر فعالانه با محتوا درگیر نیست، مشکلی ندارید یا خیر. اگر پاسخ مثبت است، احتمالاً می خواهید MediaSession انتخاب کنید. اگر پاسخ منفی است، احتمالاً می خواهید به جای آن از Player استفاده کنید.

یک جلسه رسانه ای ایجاد کنید

یک جلسه رسانه در کنار پخش کننده ای که مدیریت می کند زندگی می کند. می توانید یک جلسه رسانه با یک Context و یک شی Player بسازید. شما باید یک جلسه رسانه را در صورت نیاز ایجاد و مقداردهی کنید، مانند متد چرخه حیات onStart() یا onResume() Activity یا Fragment ، یا متد onCreate() از Service که صاحب جلسه رسانه و پخش کننده مربوط به آن است.

برای ایجاد یک جلسه رسانه، یک Player مقداردهی اولیه کنید و آن را به MediaSession.Builder به شکل زیر عرضه کنید:

کاتلین

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

جاوا

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 باشند. به همین ترتیب، ممکن است بخواهید به سیستم 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 معمولاً دارای یک یا چند فیلد زیر برای توصیف رسانه درخواستی است:

  • MediaItem.id : یک شناسه عمومی که رسانه را شناسایی می کند.
  • MediaItem.RequestMetadata.mediaUri : یک URI درخواستی که ممکن است از یک طرحواره سفارشی استفاده کند و لزوماً مستقیماً توسط پخش کننده قابل پخش نیست.
  • MediaItem.RequestMetadata.searchQuery : یک عبارت جستجوی متنی، به عنوان مثال از Google Assistant.
  • MediaItem.MediaMetadata : فراداده ساختاریافته مانند "عنوان" یا "هنرمند".

برای گزینه‌های سفارشی‌سازی بیشتر برای لیست‌های پخش کاملاً جدید، می‌توانید علاوه بر این، onSetMediaItems() را لغو کنید که به شما امکان می‌دهد آیتم شروع و موقعیت را در لیست پخش تعریف کنید. به عنوان مثال، می توانید یک آیتم درخواستی را به کل لیست پخش گسترش دهید و به پخش کننده دستور دهید که از فهرست آیتم درخواست شده اولیه شروع کند. نمونه ای از پیاده سازی onSetMediaItems() با این ویژگی را می توان در برنامه نمایشی جلسه یافت.

طرح بندی سفارشی و دستورات سفارشی را مدیریت کنید

بخش‌های زیر نحوه تبلیغ یک چیدمان سفارشی دکمه‌های فرمان سفارشی را برای برنامه‌های مشتری و اجازه دادن به کنترل‌کننده‌ها برای ارسال دستورات سفارشی را توضیح می‌دهند.

طرح سفارشی جلسه را تعریف کنید

برای نشان دادن کنترل‌های بازپخشی که می‌خواهید به برنامه‌های مشتری نشان داده شود، طرح‌بندی سفارشی جلسه را هنگام ساخت MediaSession در متد onCreate() سرویس خود تنظیم کنید.

کاتلین

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()
}

جاوا

@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 در روش پاسخ به تماس onConnect ، مجموعه دستورات موجود را پیکربندی و برگردانید:

کاتلین

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()
  }
}

جاوا

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 لغو کنید.

کاتلین

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

جاوا

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 ارسال می‌شود، ردیابی کنید که کدام کنترل‌کننده رسانه درخواست ارسال می‌کند. این به شما این امکان را می دهد که رفتار برنامه خود را در پاسخ به دستور داده شده در صورتی که از سیستم، برنامه شخصی شما یا سایر برنامه های مشتری نشات می گیرد، تنظیم کنید.

پس از تعامل با کاربر، طرح بندی سفارشی را به روز کنید

پس از مدیریت یک فرمان سفارشی یا هر تعامل دیگری با پخش کننده خود، ممکن است بخواهید طرح نمایش داده شده در رابط کاربری کنترلر را به روز کنید. یک مثال معمولی یک دکمه جابجایی است که نماد خود را پس از فعال کردن عملکرد مرتبط با این دکمه تغییر می‌دهد. برای به‌روزرسانی طرح‌بندی، می‌توانید از MediaSession.setCustomLayout استفاده کنید:

کاتلین

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))

جاوا

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() ، قبل از ارسال آن به MediaSession ، Player خود را در ForwardingSimpleBasePlayer بپیچید.

کاتلین

val player = (logic to build a Player instance)

val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) {
  // Customizations
}

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

جاوا

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 برای درخواست فعلی بدست آورید:

کاتلین

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

جاوا

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 را به تنهایی مدیریت کند.

رسیدگی و گزارش خطا

دو نوع خطا وجود دارد که یک جلسه منتشر می کند و به کنترل کننده ها گزارش می دهد. خطاهای مهلک نقص فنی پخش کننده جلسه را گزارش می دهند که پخش را قطع می کند. خطاهای مهلک در صورت وقوع به طور خودکار به کنترل کننده گزارش می شود. خطاهای غیرمرگبار خطاهای غیر فنی یا خط مشی هستند که پخش را قطع نمی کنند و توسط برنامه به صورت دستی برای کنترلرها ارسال می شوند.

خطاهای پخش مرگبار

یک خطای مرگبار پخش به جلسه توسط پخش کننده گزارش می شود و سپس به کنترل کننده ها گزارش می شود تا از طریق Player.Listener.onPlayerError(PlaybackException) و Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException) تماس بگیرند.

در چنین حالتی، وضعیت پخش به STATE_IDLE منتقل می‌شود و MediaController.getPlaybackError() PlaybackException را که باعث انتقال شده است برمی‌گرداند. یک کنترلر می تواند PlayerException.errorCode بررسی کند تا اطلاعاتی در مورد دلیل خطا به دست آورد.

برای قابلیت همکاری، یک خطای مهلک با انتقال وضعیت آن به STATE_ERROR و تنظیم کد خطا و پیام مطابق PlaybackException ، به PlaybackStateCompat جلسه پلت فرم تکرار می شود.

سفارشی سازی یک خطای کشنده

برای ارائه اطلاعات محلی و معنی دار به کاربر، کد خطا، پیام خطا و موارد اضافی خطای خطای پخش مرگبار را می توان با استفاده از ForwardingPlayer هنگام ساخت جلسه سفارشی کرد:

کاتلین

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

جاوا

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

پخش کننده فوروارد کننده یک Player.Listener را در پخش کننده واقعی ثبت می کند و تماس هایی که خطا را گزارش می کنند را قطع می کند. سپس یک PlaybackException سفارشی شده به شنوندگانی که در پخش کننده فوروارد ثبت شده اند واگذار می شود. برای اینکه این کار کار کند، پخش کننده فوروارد Player.addListener و Player.removeListener را لغو می کند تا به شنوندگانی دسترسی داشته باشد که با آن کد خطا، پیام یا موارد اضافی ارسال شود:

کاتلین

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

جاوا

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

خطاهای غیر کشنده

خطاهای غیر کشنده ای که از یک استثنا فنی منشأ نمی گیرند می توانند توسط یک برنامه برای همه یا به یک کنترل کننده خاص ارسال شوند:

کاتلین

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

جاوا

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 یک خطای غیرمرگبار دریافت می کند:

کاتلین

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

جاوا

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });