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

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

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

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

انتشار فراداده فهرست پخش

Media3 میاناهای برنامه‌سازی کاربردی‌ای ارائه می‌دهد که فراداده‌های ساختاریافته فهرست پخش را در کنار بازپخش قطعه فعال منتشر می‌کنند. استانداردسازی نحوه پر کردن عضویت فهرست پخش و فراداده توصیفی در جلسه‌های رسانه توسط برنامه شما به Android اجازه می‌دهد نقاط ورودی سطح سیستم را برای بازپخش در برنامه شما نمایش دهد.

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

اعلام شناسه فهرست پخش و فراداده فهرست پخش

در Media3، شناسایی فهرست پخش و فراداده توصیفی فهرست پخش در دو محدوده MediaMetadata تقسیم می‌شوند:

  • شناسه فهرست پخش سطح مورد (MediaItem.mediaMetadata.playlistId):
    • با فراخوانی MediaMetadata.Builder.setPlaylistId در MediaMetadata مورد، MediaItem درحال پخش را به‌عنوان بخشی از فهرست پخش علامت‌گذاری کنید، درحالی‌که MediaItem.mediaId به‌طور منحصربه‌فرد قطعه تکی را شناسایی می‌کند.
    • ‫playlistId رشته‌ای مبهم و تعریف‌شده توسط برنامه است که فهرست پخش را به‌طور یکتا در جلسه‌ها تا زمانی که فهرست پخش قابل‌ازسرگیری باشد (برای مثال، 37i9dQZF1DX76Wlfdnj7AP) شناسایی می‌کند و نباید برای فهرست پخش دیگری دوباره استفاده شود. کنترل‌کننده‌های متصل این شناسه را تفسیر نمی‌کنند؛ آن‌ها آن را ذخیره می‌کنند و برای ازسر گرفتن فهرست پخش به برنامه‌تان برمی‌گردانند.
    • هر قطعه‌ای که به فهرست پخش تعلق دارد، همان playlistId را دارد. برای قطعه‌هایی که از بافت فهرست پخش پخش نمی‌شوند، مانند یک قطعه مستقل، playlistId را تنظیم‌نشده (null) بگذارید.
  • فراداده فهرست پخش در سطح پخش‌کننده (Player.getPlaylistMetadata):
    • یک نمونه اختصاصی MediaMetadata را با ویژگی‌های توصیفی فهرست پخش (setTitle، setArtworkUri، و setMediaType) پر کنید و آن را بااستفاده از Player.setPlaylistMetadata در پخش‌کننده تنظیم کنید.
    • وقتی کنترل‌کننده متصل‌شده currentMediaItem.mediaMetadata.playlistId غیرتهی را شناسایی می‌کند، MediaController.getPlaylistMetadata را بررسی می‌کند تا عنوان فهرست پخش و شناسه منبع یکسان کار هنری را بازیابی کند.
    • هرگاه فهرست پخش فعال تغییر کرد (برای مثال، وقتی کاربر فهرست پخش دیگری را شروع می‌کند)، فراداده فهرست پخش را به‌روز کنید تا getPlaylistMetadata همیشه فهرست پخش ارجاع‌شده توسط playlistId مورد فعلی را توصیف کند.

مثال زیر نشان می‌دهد که چگونه فراداده فهرست پخش را در Player منتشر کنید و قطعه MediaItem را با آن فهرست پخش مرتبط کنید:

کاتلین

// 1. Declare the playlist descriptive metadata on the Player
val playlistMetadata =
  MediaMetadata.Builder()
    .setTitle("Morning Vibes")
    .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes"))
    .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
    .build()
player.setPlaylistMetadata(playlistMetadata)

// 2. Mark the playing track(s) with the playlistId it belongs to
val trackItem =
  MediaItem.Builder()
    .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6")
    .setMediaMetadata(
      MediaMetadata.Builder()
        .setTitle("Example Track")
        .setArtist("Example Artist")
        .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
        .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP")
        .build()
    )
    .build()
player.setMediaItem(trackItem)

جاوا

// 1. Declare the playlist descriptive metadata on the Player
MediaMetadata playlistMetadata =
    new MediaMetadata.Builder()
        .setTitle("Morning Vibes")
        .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes"))
        .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
        .build();
player.setPlaylistMetadata(playlistMetadata);

// 2. Mark the playing track(s) with the playlistId it belongs to
MediaItem trackItem =
    new MediaItem.Builder()
        .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6")
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setTitle("Example Track")
                .setArtist("Example Artist")
                .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
                .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP")
                .build())
        .build();
player.setMediaItem(trackItem);

ازسر گرفتن فهرست پخش

وقتی کاربر فهرست پخشی را برای ازسرگیری انتخاب می‌کند، کنترل‌کننده به جلسه رسانه برنامه شما متصل می‌شود و فهرست پخش MediaItem را که mediaId آن روی playlistId ذخیره‌شده تنظیم شده است ارسال می‌کند:

کاتلین

val playlistItem =
  MediaItem.Builder()
    .setMediaId("37i9dQZF1DX76Wlfdnj7AP")
    .setMediaMetadata(
      MediaMetadata.Builder()
        .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
        .setIsPlayable(true)
        .build()
    )
    .build()
controller.setMediaItem(playlistItem)
controller.prepare()
controller.play()

جاوا

MediaItem playlistItem =
    new MediaItem.Builder()
        .setMediaId("37i9dQZF1DX76Wlfdnj7AP")
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
                .setIsPlayable(true)
                .build())
        .build();
controller.setMediaItem(playlistItem);
controller.prepare();
controller.play();

برنامه شما فهرست پخش MediaItem را به قطعه‌های قابل‌پخش آن در داخل MediaSession.Callback.onSetMediaItems (یا onAddMediaItems) گسترش می‌دهد و playlistId و playlistMetadata را دوباره همان‌طور که در بخش قبلی نشان داده شده است منتشر می‌کند.

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

هر کنترل‌کننده‌ای، برای مثال «میانای کاربر سیستم»، 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.
              }
            });