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

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

فرمان‌ها ممکن است از دکمه‌های فیزیکی مانند دکمه پخش روی هدست یا کنترل از راه دور تلویزیون نشأت بگیرند. این دستورات همچنین ممکن است از برنامه‌های مشتری که کنترل‌کننده رسانه دارند، مثل دستور «مکث» به «دستیار Google»، صادر شوند. جلسه رسانه این فرمان‌ها را به پخش‌کننده برنامه رسانه واگذار می‌کند.

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

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

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

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

این با جلسه رسانه پلاتفرم متفاوت است که در آن باید PlaybackState را به‌طور مستقل از خود پخش‌کننده ایجاد و نگهداری می‌کردید، برای مثال برای نشان دادن هرگونه خطا.

شناسه جلسه یکتا

به‌طور پیش‌فرض، MediaSession.Builder جلسه‌ای با رشته‌ای خالی به‌عنوان شناسه جلسه ایجاد می‌کند. اگر برنامه‌ای قصد داشته باشد فقط یک نمونه جلسه ایجاد کند (که رایج‌ترین مورد است)، این کافی است.

اگر برنامه‌ای بخواهد چندین نمونه جلسه را به‌طور هم‌زمان مدیریت کند، برنامه باید مطمئن شود که شناسه جلسه هر جلسه یکتا است. شناسه جلسه را می‌توان هنگام ساختن جلسه با MediaSession.Builder.setId(String id) تنظیم کرد.

اگر می‌بینید IllegalStateException باعث ازکارافتادن برنامه‌تان با پیام خطای IllegalStateException: Session ID must be unique. ID= می‌شود، احتمالاً جلسه به‌طور غیرمنتظره‌ای قبل‌از اینکه نمونه ازپیش ایجادشده با همان شناسه منتشر شود ایجاد شده است. برای جلوگیری از لو رفتن جلسه‌ها به‌دلیل خطای برنامه‌نویسی، چنین مواردی با ایجاد استثنا شناسایی و اطلاع‌رسانی می‌شوند.

اعطای کنترل به کارخواه‌های دیگر

جلسه رسانه کلید کنترل بازپخش است. این ویژگی به شما امکان می‌دهد دستورات را از منابع خارجی به پخش‌کننده‌ای که کار پخش رسانه‌ها را انجام می‌دهد هدایت کنید. این منابع می‌تواند دکمه‌های فیزیکی مثل دکمه پخش روی هدست یا کنترل از دور تلویزیون، یا فرمان‌های غیرمستقیم مثل دستور «مکث» به «دستیار Google» باشد. به‌همین ترتیب، ممکن است بخواهید به سیستم Android دسترسی اعطا کنید تا کنترل‌های اعلان و صفحه قفل تسهیل شود، یا به ساعت Wear OS دسترسی اعطا کنید تا بتوانید بازپخش را از صفحه ساعت کنترل کنید. کاربران خارجی می‌توانند از کنترل‌کننده رسانه برای صدور فرمان‌های بازپخش به برنامه رسانه استفاده کنند. این فرمان‌ها توسط جلسه رسانه دریافت می‌شوند و درنهایت به پخش‌کننده رسانه واگذار می‌شوند.

نموداری که تعامل بین MediaSession و MediaController را نشان می‌دهد.
شکل ۱: کنترل‌کننده رسانه انتقال فرمان‌ها از منابع خارجی به جلسه رسانه را تسهیل می‌کند.
را ببینید.

وقتی کنترل‌کننده‌ای درحال اتصال به جلسه رسانه‌ای شما است، onConnect() روش فراخوانی می‌شود. می‌توانید از ControllerInfo ارائه‌شده برای تصمیم‌گیری درباره پذیرفتن یا رد کردن درخواست استفاده کنید. در بخش اعلام فرمان‌های سفارشی، نمونه‌ای از پذیرفتن درخواست اتصال را ببینید.

پس‌از اتصال، کنترل‌کننده می‌تواند فرمان‌های بازپخش را به جلسه ارسال کند. سپس جلسه آن دستورات را به پخش‌کننده واگذار می‌کند. فرمان‌های بازپخش و فهرست پخش تعریف‌شده در رابط Player به‌طور خودکار توسط جلسه مدیریت می‌شوند.

روش‌های دیگر تماس برگشتی به شما امکان می‌دهند، برای مثال، درخواست‌های فرمان‌های سفارشی و اصلاح فهرست پخش را مدیریت کنید. این بازخوان‌ها نیز به‌طور مشابه شامل یک ControllerInfo شیء هستند تا بتوانید نحوه پاسخ دادن به هر درخواست را براساس هر کنترل‌کننده تغییر دهید.

فهرست پخش را تغییر دهید

همان‌طور که در راهنمای ExoPlayer برای فهرست‌های پخش توضیح داده شده است، جلسه رسانه می‌تواند فهرست پخش پخش‌کننده خود را مستقیماً تغییر دهد. اگر COMMAND_SET_MEDIA_ITEM یا COMMAND_CHANGE_MEDIA_ITEMS برای کنترل‌کننده دردسترس باشد، کنترل‌کننده‌ها نیز می‌توانند فهرست پخش را تغییر دهند.

هنگام افزودن موارد جدید به فهرست پخش، پخش‌کننده معمولاً به MediaItemنمونه با شناسه منبع یکنواخت تعریف‌شده نیاز دارد تا آن‌ها را قابل‌پخش کند. به‌طور پیش‌فرض، موارد تازه اضافه شده به‌طور خودکار به روش‌های پخش‌کننده مانند player.addMediaItem اگر شناسه منبع یکنواخت تعریف شده باشد، بازارسال می‌شوند.

اگر می‌خواهید نمونه‌های MediaItem اضافه شده به پخش‌کننده را سفارشی‌سازی کنید، می‌توانید ملغی کنید onAddMediaItems(). این مرحله زمانی لازم است که بخواهید از کنترل‌کننده‌هایی که رسانه را بدون نشانی وب تعریف‌شده درخواست می‌کنند پشتیبانی کنید. درعوض، MediaItem معمولاً یک یا چند فیلد زیر را برای توصیف رسانه درخواستی تنظیم می‌کند:

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

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

مدیریت اولویت‌های دکمه رسانه

هر کنترل‌کننده‌ای، برای مثال «میانای کاربر سیستم»،‏ Android Auto، یا Wear OS، می‌تواند درباره اینکه کدام دکمه‌ها به کاربر نشان داده شود تصمیم بگیرد. برای نشان دادن اینکه کدام کنترل‌های پخش را می‌خواهید به کاربر نشان دهید، می‌توانید ترجیحات دکمه رسانه را در MediaSession مشخص کنید. این اولویت‌ها شامل فهرستی مرتب از CommandButton نمونه است که هریک اولویت دکمه‌ای را در رابط کاربری تعریف می‌کند.

تعریف دکمه‌های فرمان

از CommandButton نمونه برای تعریف اولویت‌های دکمه رسانه استفاده می‌شود. هر دکمه سه جنبه از عنصر واسط کاربر موردنظر را تعریف می‌کند:

  1. نماد، ظاهر دیداری را تعریف می‌کند. هنگام ایجاد CommandButton.Builder، نماد باید روی یکی از ثابت‌های ازپیش تعریف‌شده تنظیم شود. توجه داشته باشید که این یک منبع تصویر یا «بیت‌مپ» واقعی نیست. ثابت عمومی به کنترل‌کننده‌ها کمک می‌کند منبع مناسبی را برای ظاهر و احساس یکپارچه در رابط کاربری خودشان انتخاب کنند. اگر هیچ‌یک از ثابت‌های نماد ازپیش‌تعریف‌شده با مورد استفاده شما مطابقت ندارد، می‌توانید از setCustomIconResId استفاده کنید.
  2. فرمان، کنشی را که هنگام تعامل کاربر با دکمه راه‌اندازی می‌شود تعریف می‌کند. می‌توانید از setPlayerCommand برای Player.Command، یا از setSessionCommand برای SessionCommand پیش‌تعریف‌شده یا سفارشی استفاده کنید.
  3. جایگاه، که مشخص می‌کند دکمه در کجای واسط کاربر کنترل‌کننده قرار گیرد. این فیلد اختیاری است و به‌طور خودکار براساس نماد و فرمان تنظیم می‌شود. برای مثال، این ویژگی امکان می‌دهد مشخص کنید که دکمه‌ای باید در ناحیه پیمایش «به‌جلو» رابط کاربری نمایش داده شود، نه در ناحیه پیش‌فرض «سرریز».

کاتلین

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

جاوا

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

وقتی اولویت‌های دکمه رسانه حل‌وفصل می‌شود، الگوریتم زیر اعمال می‌شود:

  1. برای هر CommandButton در اولویت‌های دکمه رسانه، دکمه را در اولین جایگاه دردسترس و مجاز قرار دهید.
  2. اگر هریک از جایگاه‌های مرکزی، جلو، و عقب با دکمه‌ای پر نشده است، دکمه‌های پیش‌فرض را برای این جایگاه اضافه کنید.

می‌توانید از CommandButton.DisplayConstraints برای تولید پیش‌نمایشی از نحوه حل‌وفصل اولویت‌های دکمه رسانه براساس محدودیت‌های نمایش میانای کاربر استفاده کنید.

تنظیم اولویت‌های دکمه رسانه

آسان‌ترین راه برای تنظیم اولویت‌های دکمه رسانه این است که فهرست را هنگام ساختن MediaSession تعریف کنید. یا می‌توانید MediaSession.Callback.onConnect را ملغی کنید تا اولویت‌های دکمه رسانه را برای هر کنترل‌کننده متصل سفارشی‌سازی کنید.

کاتلین

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

جاوا

MediaSession mediaSession =
    new MediaSession.Builder(context, player)
        .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
        .build();

به‌روزرسانی اولویت‌های دکمه رسانه پس‌از تعامل کاربر

پس‌از مدیریت تعامل با پخش‌کننده، ممکن است بخواهید دکمه‌های نمایش‌داده‌شده در واسط کاربر کنترل‌کننده را به‌روز کنید. نمونه معمول آن دکمه مبدلی است که پس‌از راه‌اندازی کنش منسوب به این دکمه، نماد و کنش خود را تغییر می‌دهد. برای به‌روزرسانی اولویت‌های دکمه رسانه، می‌توانید از MediaSession.setMediaButtonPreferences برای به‌روزرسانی اولویت‌های همه کنترل‌کننده‌ها یا یک کنترل‌کننده خاص استفاده کنید:

کاتلین

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton))

جاوا

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton));

افزودن فرمان‌های سفارشی و سفارشی‌سازی رفتار پیش‌فرض

فرمان‌های پخش‌کننده دردسترس را می‌توان با فرمان‌های سفارشی گسترش داد و همچنین می‌توان فرمان‌های پخش‌کننده ورودی و دکمه‌های رسانه را رهگیری کرد تا عملکرد پیش‌فرض را تغییر داد.

اعلام و مدیریت فرمان‌های سفارشی

برنامه‌های رسانه‌ای می‌توانند فرمان‌های سفارشی تعریف کنند که برای نمونه می‌توانند در اولویت‌های دکمه رسانه استفاده شوند. برای مثال، ممکن است بخواهید دکمه‌هایی پیاده‌سازی کنید که به کاربر اجازه می‌دهد عنصر رسانه‌ای را در فهرست عناصر موردعلاقه ذخیره کند. MediaController فرمان‌های سفارشی را ارسال می‌کند و MediaSession.Callback آن‌ها را دریافت می‌کند.

برای تعریف کردن فرمان‌های سفارشی، باید MediaSession.Callback.onConnect() را ملغی کنید تا فرمان‌های سفارشی دردسترس را برای هر کنترل‌کننده متصل تنظیم کنید.

کاتلین

private class CustomMediaSessionCallback : MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  override fun onConnectAsync(
    session: MediaSession,
    controller: ControllerInfo,
  ): ListenableFuture<ConnectionResult> {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return Futures.immediateFuture(
      AcceptedResultBuilder(session, controller)
        .setAvailableSessionCommands(sessionCommands)
        .build()
    )
  }
}

جاوا

private static class CustomMediaSessionCallback implements MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  @Override
  public ListenableFuture<ConnectionResult> onConnectAsync(
      MediaSession session, ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return Futures.immediateFuture(
        new AcceptedResultBuilder(session, controller)
            .setAvailableSessionCommands(sessionCommands)
            .build());
  }
}

برای دریافت درخواست‌های فرمان سفارشی از MediaController، روش onCustomCommand() را در Callback ملغی کنید.

کاتلین

private class CustomCallback : MediaSession.Callback {
  // ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle,
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
    }
    // ...
    return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
  }
}

جاوا

private static class CustomCallback implements MediaSession.Callback {
  // ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
      MediaSession session,
      ControllerInfo controller,
      SessionCommand customCommand,
      Bundle args) {
    if (customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
    }
    // ...
    return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
  }
}

می‌توانید بااستفاده از packageName دارایی MediaSession.ControllerInfo شیئی که به روش‌های Callback منتقل می‌شود، کنترل کنید کدام کنترل‌کننده رسانه درخواست می‌دهد. این امکان را به شما می‌دهد تا عملکرد برنامه‌تان را در پاسخ به فرمان معینی که از سیستم، برنامه خودتان، یا برنامه‌های مشتری دیگر صادر می‌شود، سفارشی‌سازی کنید.

سفارشی‌سازی کردن فرمان‌های پخش‌کننده پیش‌فرض

همه فرمان‌های پیش‌فرض و مدیریت وضعیت به Player که در MediaSession است واگذار می‌شود. برای سفارشی‌سازی کردن رفتار فرمان تعریف‌شده در واسط Player، مثل play() یا seekToNext()، Player را در ForwardingSimpleBasePlayer بپیچید و سپس آن را به MediaSession منتقل کنید:

کاتلین

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

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

جاوا

ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

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

برای اطلاعات بیشتر درباره ForwardingSimpleBasePlayer، راهنمای ExoPlayer را در سفارشی‌سازی ببینید.

شناسایی کنترل‌کننده درخواست‌کننده فرمان پخش‌کننده

وقتی تماسی به روش Player از MediaController منشأ می‌گیرد، می‌توانید منبع منشأ را با MediaSession.controllerForCurrentRequest شناسایی کنید و ControllerInfo را برای درخواست کنونی به‌دست آورید:

کاتلین

private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) {
  private lateinit var session: MediaSession

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

جاوا

private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  private MediaSession session;

  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

سفارشی‌سازی مدیریت دکمه رسانه

دکمه‌های رسانه دکمه‌های سخت‌افزاری هستند که در دستگاه‌های Android و دیگر دستگاه‌های جانبی مثل دکمه پخش/مکث در هدفون بلوتوثی وجود دارند. وقتی رویدادهای دکمه رسانه به جلسه می‌رسند، Media3 آن‌ها را برای شما مدیریت می‌کند و روش Player مناسب را در بازیکن جلسه فرا می‌خواند.

توصیه می‌شود همه رویدادهای دکمه رسانه ورودی را در روش Player مربوطه مدیریت کنید. برای موارد استفاده پیشرفته‌تر، رویدادهای دکمه رسانه را می‌توان در MediaSession.Callback.onMediaButtonEvent(Intent) رهگیری کرد.

مدیریت و گزارش خطا

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

خطاهای مهلک بازپخش

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

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

تنظیم خطای پخش‌کننده سفارشی

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

وقتی برنامه‌ای بااستفاده از این «میانای برنامه‌سازی کاربردی» PlaybackException تنظیم می‌کند:

  • به MediaController نمونه متصل اطلاع داده خواهد شد. ‫Listener.onPlayerError(PlaybackException) و Listener.onPlayerErrorChanged(@Nullable PlaybackException) بازخوان‌های کنترل‌کننده با استثنای ارائه‌شده فراخوانی خواهند شد.

  • روش MediaController.getPlayerError() مجموعه PlaybackException تنظیم‌شده توسط برنامه را برمی‌گرداند.

  • وضعیت بازپخش برای کنترل‌کننده‌های تحت‌تأثیر به Player.STATE_IDLE تغییر خواهد کرد.

  • فرمان‌های دردسترس برداشته می‌شود و فقط فرمان‌های خواندن مثل COMMAND_GET_TIMELINE باقی می‌ماند، درصورتی‌که قبلاً اعطا شده باشند. وضعیت Timeline، برای مثال، در وضعیتی که استثنا برای کنترل‌کننده اعمال شده است ثابت می‌شود. فرمان‌هایی که تلاش می‌کنند وضعیت پخش‌کننده را تغییر دهند، مانند COMMAND_PLAY، تا زمانی که استثنای بازپخش برای کنترل‌کننده داده‌شده توسط برنامه برداشته نشود، حذف می‌شوند.

برای پاک کردن PlaybackException سفارشی که قبلاً تنظیم شده است و بازیابی گزارش وضعیت پخش‌کننده عادی، برنامه می‌تواند MediaSession.setPlaybackException(/* playbackException= */ null) یا MediaSession.setPlaybackException(ControllerInfo, /* playbackException= */ null) را فراخوانی کند.

سفارشی‌سازی خطاهای مهلک

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

کاتلین

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

جاوا

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

بازیکن بازارسال می‌تواند از ForwardingSimpleBasePlayer برای رهگیری خطا و سفارشی‌سازی کد خطا، پیام، یا موارد اضافی استفاده کند. به همین ترتیب، می‌توانید خطاهای جدیدی که در پخش‌کننده اصلی وجود ندارد نیز تولید کنید:

کاتلین

private class ErrorForwardingPlayer(private val context: Context, player: Player) :
  ForwardingSimpleBasePlayer(player) {

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon().setPlayerError(customizePlaybackException(state.playerError!!)).build()
    }
    return state
  }

  private fun customizePlaybackException(error: PlaybackException): PlaybackException {
    val buttonLabel: String
    val errorMessage: String
    when (error.errorCode) {
      PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
        buttonLabel = context.getString(R.string.err_button_label_restart_stream)
        errorMessage = context.getString(R.string.err_msg_behind_live_window)
      }
      else -> {
        buttonLabel = context.getString(R.string.err_button_label_ok)
        errorMessage = context.getString(R.string.err_message_default)
      }
    }
    val extras = Bundle()
    extras.putString("button_label", buttonLabel)
    return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
  }
}

جاوا

private static class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon().setPlayerError(customizePlaybackException(state.playerError)).build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    String buttonLabel;
    String errorMessage;
    switch (error.errorCode) {
      case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
        buttonLabel = context.getString(R.string.err_button_label_restart_stream);
        errorMessage = context.getString(R.string.err_msg_behind_live_window);
        break;
      default:
        buttonLabel = context.getString(R.string.err_button_label_ok);
        errorMessage = context.getString(R.string.err_message_default);
        break;
    }
    Bundle extras = new Bundle();
    extras.putString("button_label", buttonLabel);
    return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
  }
}

خطاهای غیرمهلک

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

کاتلین

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

جاوا

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

کاتلین

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