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

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

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

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

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

  • ازطریق هدفون. اغلب دکمه‌ها یا تعامل‌های لمسی وجود دارد که کاربر می‌تواند روی هدفون خود انجام دهد تا رسانه را پخش یا موقتاً متوقف کند یا به قطعه بعدی یا قبلی برود.
  • با صحبت کردن با دستیارهای هوش مصنوعی، ازجمله Gemini یا «دستیار Google». یک الگوی رایج این است که بگویید «Ok Google، موقتاً متوقف کن» تا هر رسانه‌ای که درحال‌حاضر در دستگاه درحال پخش است موقتاً متوقف شود.
  • ازطریق ساعت Wear OS. این کار دسترسی به رایج‌ترین کنترل‌های بازپخش را هنگام پخش در تلفن آسان‌تر می‌کند.
  • ازطریق کنترل‌های رسانه. این گردونه کنترل‌های هر جلسه رسانه درحال اجرا را نشان می‌دهد.
  • در تلویزیون. امکان کنش با دکمه‌های فیزیکی بازپخش، کنترل بازپخش پلاتفرم، و مدیریت مصرف برق (برای مثال، اگر تلویزیون، بلندگوی ستونی، یا گیرنده صوتی/تصویری خاموش شود یا ورودی تغییر کند، بازپخش باید در برنامه متوقف شود).
  • ازطریق کنترل‌های رسانه 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= ازکار می‌اندازد، احتمالاً جلسه‌ای به‌طور غیرمنتظره قبل‌از انتشار نمونه‌ای که قبلاً با همان شناسه ایجاد شده است ایجاد شده است. برای جلوگیری از لو رفتن جلسه‌ها به‌دلیل خطای برنامه‌نویسی، چنین مواردی شناسایی می‌شوند و با ایجاد استثنا اطلاع داده می‌شوند.

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

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

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

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

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

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

اصلاح فهرست پخش

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

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

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

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

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

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

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

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

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

  1. نماد، که ظاهر دیداری را تعریف می‌کند. هنگام ایجاد CommandButton.Builder، نماد باید روی یکی از ثابت‌های ازپیش‌تعریف‌شده تنظیم شود. توجه داشته باشید که این یک منبع تصویر یا Bitmap واقعی نیست. ثابت عمومی به کنترل‌کننده‌ها کمک می‌کند تا منبع مناسبی را برای ظاهر و حس هماهنگ در میانای کاربر خودشان انتخاب کنند. اگر هیچ‌کدام از ثابت‌های نماد ازپیش تعریف‌شده برای مورد استفاده شما مناسب نیست، می‌توانید به‌جای آن از 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.onConnectAsync() را لغو کنید تا اولویت‌های دکمه رسانه را برای هر کنترل‌کننده متصل سفارشی‌سازی کنید.

کاتلین

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.onConnectAsync() را لغو کنید تا فرمان‌های سفارشی دردسترس را برای هر کنترل‌کننده متصل تنظیم کنید.

کاتلین

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() رهگیری کرد.

رسیدگی به خطا و گزارش کردن

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

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

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

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

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

علاوه‌بر خطاهای مهلک گزارش‌شده توسط پخش‌کننده، برنامه می‌تواند PlaybackException سفارشی را در سطح MediaSession بااستفاده از MediaSession.setPlaybackException(PlaybackException) تنظیم کند. این اجازه به برنامه امکان می‌دهد تا حالت خطا را به کنترل‌کننده‌های متصل‌شده علامت دهد. این استثنا را می‌توان برای همه کنترل‌کننده‌های متصل یا برای 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) را فراخوانی کند.

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

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

کاتلین

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

جاوا

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

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

کاتلین

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