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

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

شناسه جلسه منحصر به فرد

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

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

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

اعطای کنترل به سایر مشتریان

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

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

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

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

تنظیمات برگزیده دکمه رسانه را مدیریت کنید

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

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

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

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

کاتلین

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

جاوا

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

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

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

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

تنظیمات برگزیده دکمه رسانه را تنظیم کنید

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

سفارشی کردن دستورات پخش پیش فرض

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

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

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

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

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

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

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

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

سفارشی سازی خطاهای مرگبار

برای ارائه اطلاعات محلی و معنی دار به کاربر، کد خطا، پیام خطا و موارد اضافی خطای خطای پخش مرگبار را می توان با استفاده از 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();

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

کاتلین

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

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

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

جاوا

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

،

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

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

شناسه جلسه منحصر به فرد

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

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

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

اعطای کنترل به سایر مشتریان

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

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

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

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

تنظیمات برگزیده دکمه رسانه را مدیریت کنید

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

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

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

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

کاتلین

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

جاوا

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

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

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

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

تنظیمات برگزیده دکمه رسانه را تنظیم کنید

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

سفارشی کردن دستورات پخش پیش فرض

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

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

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

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

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

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

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

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

سفارشی سازی خطاهای مرگبار

برای ارائه اطلاعات محلی و معنی دار به کاربر، کد خطا، پیام خطا و موارد اضافی خطای خطای پخش مرگبار را می توان با استفاده از 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();

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

کاتلین

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

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

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

جاوا

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

،

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

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

شناسه جلسه منحصر به فرد

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

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

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

اعطای کنترل به سایر مشتریان

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

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

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

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

تنظیمات برگزیده دکمه رسانه را مدیریت کنید

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

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

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

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

کاتلین

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

جاوا

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

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

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

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

تنظیمات برگزیده دکمه رسانه را تنظیم کنید

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

سفارشی کردن دستورات پخش پیش فرض

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

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

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

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

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

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

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

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

سفارشی سازی خطاهای مرگبار

برای ارائه اطلاعات محلی و معنی دار به کاربر، کد خطا، پیام خطا و موارد اضافی خطای خطای پخش مرگبار را می توان با استفاده از 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();

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

کاتلین

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

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

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

جاوا

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