کنترل های رسانه ای

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

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

با شروع در Android 13 (سطح API 33)، برای اطمینان از اینکه کاربران می‌توانند به مجموعه‌ای غنی از کنترل‌های رسانه برای برنامه‌های پخش رسانه دسترسی داشته باشند، دکمه‌های عملکرد روی کنترل‌های رسانه از حالت Player مشتق شده‌اند.

به این ترتیب، می‌توانید مجموعه‌ای ثابت از کنترل‌های رسانه و تجربه کنترل رسانه صیقلی‌تری را در سراسر دستگاه‌ها ارائه دهید.

شکل 1 نمونه ای از نحوه ظاهر این دستگاه را به ترتیب در تلفن و تبلت نشان می دهد.

کنترل‌های رسانه از نظر نحوه نمایش آنها در دستگاه‌های تلفن و رایانه لوحی، با استفاده از نمونه‌ای از تراک نمونه که نحوه نمایش دکمه‌ها را نشان می‌دهد.
شکل 1: کنترل های رسانه ای روی دستگاه های تلفن و تبلت

سیستم حداکثر پنج دکمه عمل را بر اساس وضعیت Player همانطور که در جدول زیر توضیح داده شده است نمایش می دهد. در حالت فشرده، تنها سه اسلات عمل اول نمایش داده می شوند. این با نحوه نمایش کنترل‌های رسانه در دیگر پلتفرم‌های Android مانند Auto، Assistant و Wear OS همخوانی دارد.

اسلات معیارها اقدام
1 playWhenReady نادرست است یا وضعیت پخش فعلی STATE_ENDED است. بازی کنید
playWhenReady درست است و وضعیت پخش فعلی STATE_BUFFERING است. در حال بارگیری اسپینر
playWhenReady درست است و وضعیت پخش فعلی STATE_READY است. مکث کنید
2 فرمان پخش کننده COMMAND_SEEK_TO_PREVIOUS یا COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM در دسترس است. قبلی
نه فرمان پخش کننده COMMAND_SEEK_TO_PREVIOUS و نه COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM موجود نیست و یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
موارد اضافی جلسه شامل یک مقدار بولی true برای کلید EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV است. خالی
3 فرمان پخش کننده COMMAND_SEEK_TO_NEXT یا COMMAND_SEEK_TO_NEXT_MEDIA_ITEM در دسترس است. بعدی
نه فرمان پخش کننده COMMAND_SEEK_TO_NEXT و نه COMMAND_SEEK_TO_NEXT_MEDIA_ITEM موجود نیست و یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
موارد اضافی جلسه شامل یک مقدار بولی true برای کلید EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT است. خالی
4 یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
5 یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی

دستورات سفارشی به ترتیبی که به چیدمان سفارشی اضافه شده اند قرار می گیرند.

سفارشی کردن دکمه های فرمان

برای سفارشی کردن کنترل‌های رسانه سیستم با Jetpack Media3 ، می‌توانید هنگام اجرای MediaSessionService ، طرح‌بندی سفارشی جلسه و دستورات موجود کنترلرها را بر اساس آن تنظیم کنید:

  1. در onCreate() یک MediaSession بسازید و چیدمان سفارشی دکمه های فرمان را تعریف کنید .

  2. در MediaSession.Callback.onConnect() ، کنترل کننده ها را با تعریف دستورات موجود، از جمله دستورات سفارشی ، در ConnectionResult ، مجوز دهید.

  3. در MediaSession.Callback.onCustomCommand() به دستور سفارشی که توسط کاربر انتخاب می شود پاسخ دهید.

کاتلین

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

جاوا

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

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

با Jetpack Media3، هنگامی که MediaSession پیاده سازی می کنید، PlaybackState شما به طور خودکار با پخش کننده رسانه به روز نگه داشته می شود. به طور مشابه، هنگامی که یک MediaSessionService پیاده سازی می کنید، کتابخانه به طور خودکار یک اعلان MediaStyle را برای شما منتشر می کند و آن را به روز نگه می دارد.

به دکمه های عمل پاسخ دهید

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

برای راهنمایی در مورد نحوه پاسخگویی به یک فرمان سفارشی به افزودن دستورات سفارشی مراجعه کنید.

رفتار پیش از اندروید 13

برای سازگاری به عقب، سیستم UI به ارائه یک طرح بندی جایگزین ادامه می دهد که از اقدامات اعلان برای برنامه هایی استفاده می کند که برای هدف قرار دادن Android 13 به روز نمی شوند یا اطلاعات PlaybackState شامل نمی شوند. دکمه های اقدام از لیست Notification.Action پیوست شده به اعلان MediaStyle مشتق شده اند. این سیستم حداکثر پنج عمل را به ترتیب اضافه شدن آنها نمایش می دهد. در حالت فشرده، حداکثر سه دکمه نشان داده می شود که توسط مقادیر ارسال شده به setShowActionsInCompactView() تعیین می شود.

اقدامات سفارشی به ترتیبی که به PlaybackState اضافه شده اند قرار می گیرند.

مثال کد زیر نحوه افزودن اقدامات به اعلان MediaStyle را نشان می دهد:

کاتلین

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

جاوا

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

حمایت از از سرگیری رسانه ها

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

ویژگی ازسرگیری پخش را می توان با استفاده از برنامه تنظیمات، در زیر گزینه های صدا > رسانه، روشن و خاموش کرد. کاربر همچنین می تواند با ضربه زدن روی نماد چرخ دنده که پس از کشیدن انگشت روی چرخ فلک باز شده ظاهر می شود، به تنظیمات دسترسی پیدا کند.

Media3 برای تسهیل پشتیبانی از از سرگیری رسانه، APIهایی را ارائه می دهد. برای راهنمایی در مورد اجرای این ویژگی، از سرگیری پخش با مستندات Media3 را ببینید.

استفاده از API های رسانه قدیمی

این بخش نحوه ادغام با کنترل های رسانه سیستم را با استفاده از API های قدیمی MediaCompat توضیح می دهد.

این سیستم اطلاعات زیر را از MediaSession MediaMetadata بازیابی می کند و در صورت موجود بودن آن را نمایش می دهد:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (اگر مدت زمان تنظیم نشده باشد، نوار جستجو پیشرفت را نشان نمی دهد)

برای اطمینان از داشتن یک اعلان کنترل رسانه معتبر و دقیق، مقدار فراداده METADATA_KEY_TITLE یا METADATA_KEY_DISPLAY_TITLE به عنوان رسانه در حال پخش تنظیم کنید.

پخش کننده رسانه زمان سپری شده برای رسانه در حال پخش را به همراه یک نوار جستجو که به MediaSession PlaybackState نگاشت شده را نشان می دهد.

پخش کننده رسانه پیشرفت رسانه در حال پخش را به همراه یک نوار جستجو نشان می دهد که به MediaSession PlaybackState نگاشت شده است. نوار جستجو به کاربران امکان می دهد موقعیت را تغییر دهند و زمان سپری شده برای آیتم رسانه را نمایش می دهد. برای اینکه نوار جستجو فعال شود، باید PlaybackState.Builder#setActions پیاده سازی کنید و ACTION_SEEK_TO در آن قرار دهید.

اسلات اقدام معیارها
1 بازی کنید وضعیت فعلی PlaybackState یکی از موارد زیر است:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
در حال بارگیری اسپینر وضعیت فعلی PlaybackState یکی از موارد زیر است:
  • STATE_CONNECTING
  • STATE_BUFFERING
مکث کنید وضعیت فعلی PlaybackState هیچ یک از موارد فوق نیست.
2 قبلی اقدامات PlaybackState شامل ACTION_SKIP_TO_PREVIOUS است.
سفارشی کنش‌های PlaybackState شامل ACTION_SKIP_TO_PREVIOUS نیستند و کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
خالی موارد اضافی PlaybackState شامل یک مقدار بولی true برای کلید SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV است.
3 بعدی اقدامات PlaybackState شامل ACTION_SKIP_TO_NEXT است.
سفارشی کنش‌های PlaybackState شامل ACTION_SKIP_TO_NEXT نمی‌شوند و کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
خالی موارد اضافی PlaybackState شامل یک مقدار بولی true برای کلید SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT است.
4 سفارشی کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
5 سفارشی کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.

اقدامات استاندارد را اضافه کنید

مثال‌های کد زیر نحوه افزودن اقدامات استاندارد و سفارشی PlaybackState را نشان می‌دهد.

برای پخش، مکث، قبلی و بعدی، این اقدامات را در PlaybackState برای جلسه رسانه تنظیم کنید.

کاتلین

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

جاوا

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

اگر هیچ دکمه‌ای را در اسلات‌های قبلی یا بعدی نمی‌خواهید، ACTION_SKIP_TO_PREVIOUS یا ACTION_SKIP_TO_NEXT را اضافه نکنید و در عوض موارد اضافی را به جلسه اضافه کنید:

کاتلین

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

جاوا

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

افزودن اقدامات سفارشی

برای سایر اقداماتی که می‌خواهید روی کنترل‌های رسانه نشان دهید، می‌توانید یک PlaybackStateCompat.CustomAction ایجاد کنید و در عوض آن را به PlaybackState اضافه کنید. این اقدامات به ترتیبی که اضافه شده اند نشان داده می شوند.

کاتلین

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

جاوا

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

پاسخ به اقدامات PlaybackState

وقتی کاربر روی دکمه ای ضربه می زند، SystemUI از MediaController.TransportControls برای ارسال یک فرمان به MediaSession استفاده می کند. شما باید یک تماس برگشتی ثبت کنید که بتواند به درستی به این رویدادها پاسخ دهد.

کاتلین

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

جاوا

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

از سرگیری رسانه ها

برای اینکه برنامه پخش کننده شما در قسمت تنظیمات تنظیمات سریع ظاهر شود، باید یک اعلان MediaStyle با یک توکن MediaSession معتبر ایجاد کنید.

برای نمایش عنوان اعلان MediaStyle، از NotificationBuilder.setContentTitle() استفاده کنید.

برای نمایش نماد برند برای پخش کننده رسانه، از NotificationBuilder.setSmallIcon() استفاده کنید.

برای پشتیبانی از ازسرگیری پخش، برنامه‌ها باید MediaBrowserService و MediaSession را پیاده‌سازی کنند. MediaSession شما باید callback onPlay() را اجرا کند.

اجرای MediaBrowserService

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

سیستم سعی می کند با یک اتصال از SystemUI با MediaBrowserService شما تماس بگیرد. برنامه شما باید چنین اتصالاتی را مجاز کند، در غیر این صورت نمی تواند از سرگیری پخش پشتیبانی کند.

اتصالات از SystemUI را می توان با استفاده از نام بسته com.android.systemui و امضا شناسایی و تأیید کرد. SystemUI با امضای پلتفرم امضا شده است. نمونه ای از نحوه بررسی امضای پلت فرم را می توان در برنامه UAMP یافت.

به منظور پشتیبانی از ازسرگیری پخش، MediaBrowserService شما باید این رفتارها را اجرا کند:

  • onGetRoot() باید یک ریشه غیر تهی را به سرعت برگرداند. منطق پیچیده دیگر باید در onLoadChildren() مدیریت شود.

  • هنگامی که onLoadChildren() در شناسه رسانه ریشه فراخوانی می شود، نتیجه باید حاوی یک فرزند FLAG_PLAYABLE باشد.

  • MediaBrowserService باید آخرین آیتم رسانه ای پخش شده را هنگامی که درخواست EXTRA_RECENT دریافت می کند، برگرداند. مقدار بازگشتی باید یک آیتم رسانه واقعی باشد تا تابع عمومی.

  • MediaBrowserService باید MediaDescription مناسب با عنوان و زیرنویس غیر خالی ارائه دهد. همچنین باید یک نماد URI یا یک بیت مپ آیکون تنظیم کند.

مثال‌های کد زیر نحوه پیاده‌سازی onGetRoot() را نشان می‌دهند.

کاتلین

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

جاوا

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}
،

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

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

با شروع در Android 13 (سطح API 33)، برای اطمینان از اینکه کاربران می‌توانند به مجموعه‌ای غنی از کنترل‌های رسانه برای برنامه‌های پخش رسانه دسترسی داشته باشند، دکمه‌های عملکرد روی کنترل‌های رسانه از حالت Player مشتق شده‌اند.

به این ترتیب، می‌توانید مجموعه‌ای ثابت از کنترل‌های رسانه و تجربه کنترل رسانه صیقلی‌تری را در سراسر دستگاه‌ها ارائه دهید.

شکل 1 نمونه ای از نحوه ظاهر این دستگاه را به ترتیب در تلفن و تبلت نشان می دهد.

کنترل‌های رسانه از نظر نحوه نمایش آنها در دستگاه‌های تلفن و رایانه لوحی، با استفاده از نمونه‌ای از تراک نمونه که نحوه نمایش دکمه‌ها را نشان می‌دهد.
شکل 1: کنترل های رسانه ای روی دستگاه های تلفن و تبلت

سیستم حداکثر پنج دکمه عمل را بر اساس وضعیت Player همانطور که در جدول زیر توضیح داده شده است نمایش می دهد. در حالت فشرده، تنها سه اسلات عمل اول نمایش داده می شوند. این با نحوه نمایش کنترل‌های رسانه در دیگر پلتفرم‌های Android مانند Auto، Assistant و Wear OS همخوانی دارد.

اسلات معیارها اقدام
1 playWhenReady نادرست است یا وضعیت پخش فعلی STATE_ENDED است. بازی کنید
playWhenReady درست است و وضعیت پخش فعلی STATE_BUFFERING است. در حال بارگیری اسپینر
playWhenReady درست است و وضعیت پخش فعلی STATE_READY است. مکث کنید
2 فرمان پخش کننده COMMAND_SEEK_TO_PREVIOUS یا COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM در دسترس است. قبلی
نه فرمان پخش کننده COMMAND_SEEK_TO_PREVIOUS و نه COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM موجود نیست و یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
موارد اضافی جلسه شامل یک مقدار بولی true برای کلید EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV است. خالی
3 فرمان پخش کننده COMMAND_SEEK_TO_NEXT یا COMMAND_SEEK_TO_NEXT_MEDIA_ITEM در دسترس است. بعدی
نه فرمان پخش کننده COMMAND_SEEK_TO_NEXT و نه COMMAND_SEEK_TO_NEXT_MEDIA_ITEM موجود نیست و یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
موارد اضافی جلسه شامل یک مقدار بولی true برای کلید EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT است. خالی
4 یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی
5 یک فرمان سفارشی از طرح سفارشی که هنوز قرار داده نشده است برای پر کردن شکاف موجود است. سفارشی

دستورات سفارشی به ترتیبی که به چیدمان سفارشی اضافه شده اند قرار می گیرند.

سفارشی کردن دکمه های فرمان

برای سفارشی کردن کنترل‌های رسانه سیستم با Jetpack Media3 ، می‌توانید هنگام اجرای MediaSessionService ، طرح‌بندی سفارشی جلسه و دستورات موجود کنترلرها را بر اساس آن تنظیم کنید:

  1. در onCreate() یک MediaSession بسازید و چیدمان سفارشی دکمه های فرمان را تعریف کنید .

  2. در MediaSession.Callback.onConnect() ، کنترل کننده ها را با تعریف دستورات موجود، از جمله دستورات سفارشی ، در ConnectionResult ، مجوز دهید.

  3. در MediaSession.Callback.onCustomCommand() به دستور سفارشی که توسط کاربر انتخاب می شود پاسخ دهید.

کاتلین

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

جاوا

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

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

با Jetpack Media3، هنگامی که MediaSession پیاده سازی می کنید، PlaybackState شما به طور خودکار با پخش کننده رسانه به روز نگه داشته می شود. به طور مشابه، هنگامی که یک MediaSessionService پیاده سازی می کنید، کتابخانه به طور خودکار یک اعلان MediaStyle را برای شما منتشر می کند و آن را به روز نگه می دارد.

به دکمه های عمل پاسخ دهید

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

برای راهنمایی در مورد نحوه پاسخگویی به یک فرمان سفارشی به افزودن دستورات سفارشی مراجعه کنید.

رفتار پیش از اندروید 13

برای سازگاری به عقب، سیستم UI به ارائه یک طرح بندی جایگزین ادامه می دهد که از اقدامات اعلان برای برنامه هایی استفاده می کند که برای هدف قرار دادن Android 13 به روز نمی شوند یا اطلاعات PlaybackState شامل نمی شوند. دکمه های اقدام از لیست Notification.Action پیوست شده به اعلان MediaStyle مشتق شده اند. این سیستم حداکثر پنج عمل را به ترتیب اضافه شدن آنها نمایش می دهد. در حالت فشرده، حداکثر سه دکمه نشان داده می شود که توسط مقادیر ارسال شده به setShowActionsInCompactView() تعیین می شود.

اقدامات سفارشی به ترتیبی که به PlaybackState اضافه شده اند قرار می گیرند.

مثال کد زیر نحوه افزودن اقدامات به اعلان MediaStyle را نشان می دهد:

کاتلین

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

جاوا

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

حمایت از از سرگیری رسانه ها

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

ویژگی ازسرگیری پخش را می توان با استفاده از برنامه تنظیمات، در زیر گزینه های صدا > رسانه، روشن و خاموش کرد. کاربر همچنین می تواند با ضربه زدن روی نماد چرخ دنده که پس از کشیدن انگشت روی چرخ فلک باز شده ظاهر می شود، به تنظیمات دسترسی پیدا کند.

Media3 برای تسهیل پشتیبانی از از سرگیری رسانه، APIهایی را ارائه می دهد. برای راهنمایی در مورد اجرای این ویژگی، از سرگیری پخش با مستندات Media3 را ببینید.

استفاده از API های رسانه قدیمی

این بخش نحوه ادغام با کنترل های رسانه سیستم را با استفاده از API های قدیمی MediaCompat توضیح می دهد.

این سیستم اطلاعات زیر را از MediaSession MediaMetadata بازیابی می کند و در صورت موجود بودن آن را نمایش می دهد:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (اگر مدت زمان تنظیم نشده باشد، نوار جستجو پیشرفت را نشان نمی دهد)

برای اطمینان از داشتن یک اعلان کنترل رسانه معتبر و دقیق، مقدار فراداده METADATA_KEY_TITLE یا METADATA_KEY_DISPLAY_TITLE به عنوان رسانه در حال پخش تنظیم کنید.

پخش کننده رسانه زمان سپری شده برای رسانه در حال پخش را به همراه یک نوار جستجو که به MediaSession PlaybackState نگاشت شده را نشان می دهد.

پخش کننده رسانه پیشرفت رسانه در حال پخش را به همراه یک نوار جستجو نشان می دهد که به MediaSession PlaybackState نگاشت شده است. نوار جستجو به کاربران امکان می دهد موقعیت را تغییر دهند و زمان سپری شده برای آیتم رسانه را نمایش می دهد. برای اینکه نوار جستجو فعال شود، باید PlaybackState.Builder#setActions پیاده سازی کنید و ACTION_SEEK_TO در آن قرار دهید.

اسلات اقدام معیارها
1 بازی کنید وضعیت فعلی PlaybackState یکی از موارد زیر است:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
در حال بارگیری اسپینر وضعیت فعلی PlaybackState یکی از موارد زیر است:
  • STATE_CONNECTING
  • STATE_BUFFERING
مکث کنید وضعیت فعلی PlaybackState هیچ یک از موارد فوق نیست.
2 قبلی اقدامات PlaybackState شامل ACTION_SKIP_TO_PREVIOUS است.
سفارشی کنش‌های PlaybackState شامل ACTION_SKIP_TO_PREVIOUS نیستند و کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
خالی موارد اضافی PlaybackState شامل یک مقدار بولی true برای کلید SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV است.
3 بعدی اقدامات PlaybackState شامل ACTION_SKIP_TO_NEXT است.
سفارشی کنش‌های PlaybackState شامل ACTION_SKIP_TO_NEXT نمی‌شوند و کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
خالی موارد اضافی PlaybackState شامل یک مقدار بولی true برای کلید SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT است.
4 سفارشی کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.
5 سفارشی کنش‌های سفارشی PlaybackState شامل یک کنش سفارشی است که هنوز قرار داده نشده است.

اقدامات استاندارد را اضافه کنید

مثال‌های کد زیر نحوه افزودن اقدامات استاندارد و سفارشی PlaybackState را نشان می‌دهد.

برای پخش، مکث، قبلی و بعدی، این اقدامات را در PlaybackState برای جلسه رسانه تنظیم کنید.

کاتلین

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

جاوا

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

اگر هیچ دکمه‌ای را در اسلات‌های قبلی یا بعدی نمی‌خواهید، ACTION_SKIP_TO_PREVIOUS یا ACTION_SKIP_TO_NEXT را اضافه نکنید و در عوض موارد اضافی را به جلسه اضافه کنید:

کاتلین

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

جاوا

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

افزودن اقدامات سفارشی

برای سایر اقداماتی که می‌خواهید روی کنترل‌های رسانه نشان دهید، می‌توانید یک PlaybackStateCompat.CustomAction ایجاد کنید و در عوض آن را به PlaybackState اضافه کنید. این اقدامات به ترتیبی که اضافه شده اند نشان داده می شوند.

کاتلین

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

جاوا

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

پاسخ به اقدامات PlaybackState

وقتی کاربر روی دکمه ای ضربه می زند، SystemUI از MediaController.TransportControls برای ارسال یک فرمان به MediaSession استفاده می کند. شما باید یک تماس برگشتی ثبت کنید که بتواند به درستی به این رویدادها پاسخ دهد.

کاتلین

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

جاوا

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

از سرگیری رسانه ها

برای اینکه برنامه پخش کننده شما در قسمت تنظیمات تنظیمات سریع ظاهر شود، باید یک اعلان MediaStyle با یک توکن MediaSession معتبر ایجاد کنید.

برای نمایش عنوان اعلان MediaStyle، از NotificationBuilder.setContentTitle() استفاده کنید.

برای نمایش نماد برند برای پخش کننده رسانه، از NotificationBuilder.setSmallIcon() استفاده کنید.

برای پشتیبانی از ازسرگیری پخش، برنامه‌ها باید MediaBrowserService و MediaSession را پیاده‌سازی کنند. MediaSession شما باید callback onPlay() را اجرا کند.

اجرای MediaBrowserService

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

سیستم سعی می کند با یک اتصال از SystemUI با MediaBrowserService شما تماس بگیرد. برنامه شما باید چنین اتصالاتی را مجاز کند، در غیر این صورت نمی تواند از سرگیری پخش پشتیبانی کند.

اتصالات از SystemUI را می توان با استفاده از نام بسته com.android.systemui و امضا شناسایی و تأیید کرد. SystemUI با امضای پلتفرم امضا شده است. نمونه ای از نحوه بررسی امضای پلت فرم را می توان در برنامه UAMP یافت.

به منظور پشتیبانی از ازسرگیری پخش، MediaBrowserService شما باید این رفتارها را اجرا کند:

  • onGetRoot() باید یک ریشه غیر تهی را به سرعت برگرداند. منطق پیچیده دیگر باید در onLoadChildren() مدیریت شود.

  • هنگامی که onLoadChildren() در شناسه رسانه ریشه فراخوانی می شود، نتیجه باید حاوی یک فرزند FLAG_PLAYABLE باشد.

  • MediaBrowserService باید آخرین آیتم رسانه ای پخش شده را هنگامی که درخواست EXTRA_RECENT دریافت می کند، برگرداند. مقدار بازگشتی باید یک آیتم رسانه واقعی باشد تا تابع عمومی.

  • MediaBrowserService باید MediaDescription مناسب با عنوان و زیرنویس غیر خالی ارائه دهد. همچنین باید یک نماد URI یا یک بیت مپ آیکون تنظیم کند.

مثال‌های کد زیر نحوه پیاده‌سازی onGetRoot() را نشان می‌دهند.

کاتلین

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

جاوا

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}