عناصر التحكّم في الوسائط

يمكنك العثور على عناصر التحكّم في الوسائط في Android بالقرب من "الإعدادات السريعة". يتم ترتيب الجلسات من تطبيقات متعدّدة في لوحة عرض دوّارة يمكن التمرير فيها سريعًا. تعرض لوحة العرض الدوّارة الجلسات بالترتيب التالي:

  • أحداث البث المباشر التي يتم تشغيلها على الهاتف
  • عمليات البث عن بُعد، مثل تلك التي يتم رصدها على الأجهزة الخارجية أو جلسات البث
  • الجلسات السابقة التي يمكن استئنافها، بترتيب آخر مرة تم تشغيلها فيها

بدءًا من Android 13 (المستوى 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()، يجب الردّ على الأمر المخصّص الذي يختاره المستخدم.

Kotlin

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

Java

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.

راجِع مقالة إضافة طلبات مخصّصة للحصول على إرشادات حول كيفية الردّ على طلب مخصّص.

السلوك في الإصدارات الأقدم من Android 13

للحفاظ على التوافق مع الإصدارات القديمة، يواصل واجهة المستخدم في النظام توفير تنسيق بديل يستخدم إجراءات الإشعارات للتطبيقات التي لا يتم تحديثها لاستهداف Android 13، أو التي لا تتضمّن معلومات PlaybackState. يتم تحديد أزرار الإجراءات من قائمة Notification.Action المرفقة بالإشعار MediaStyle. يعرض النظام ما يصل إلى خمسة إجراءات بالترتيب الذي تمت فيه إضافتها. في الوضع المكثّف، يتم عرض ما يصل إلى ثلاثة أزرار، ويتم تحديدها من خلال القيم التي يتم تمريرها إلى setShowActionsInCompactView().

يتم وضع الإجراءات المخصّصة بالترتيب الذي تمت إضافتها به إلى PlaybackState.

يوضّح مثال الرمز البرمجي التالي كيفية إضافة إجراءات إلى MediaStyle notification :

Kotlin

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

Java

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 واجهات برمجة تطبيقات لتسهيل إتاحة استئناف تشغيل الوسائط. اطّلِع على مستندات استئناف التشغيل باستخدام Media3 للحصول على إرشادات حول تنفيذ هذه الميزة.

استخدام واجهات برمجة التطبيقات القديمة للوسائط

يوضّح هذا القسم كيفية الدمج مع عناصر التحكّم في الوسائط في النظام باستخدام واجهات برمجة التطبيقات MediaCompat القديمة.

يسترجع النظام المعلومات التالية من MediaMetadata MediaSession، ويعرضها عند توفّرها:

  • 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 لجلسة الوسائط.

Kotlin

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)

Java

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، بل أضِف عناصر إضافية إلى الجلسة:

Kotlin

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

Java

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 بدلاً من ذلك. تظهر هذه الإجراءات في الترتيب الذي تمت إضافتها به.

Kotlin

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)

Java

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. عليك تسجيل طلب معاودة اتصال يمكنه الاستجابة بشكل صحيح لهذه الأحداث.

Kotlin

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)

Java

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 طلب إعادة الاتصال onPlay().

تنفيذ MediaBrowserService

بعد تشغيل الجهاز، يبحث النظام عن التطبيقات الخمسة التي تم استخدامها مؤخرًا لتشغيل الوسائط، ويقدّم عناصر تحكّم يمكن استخدامها لإعادة تشغيل المحتوى من كل تطبيق.

يحاول النظام التواصل مع MediaBrowserService من خلال اتصال من SystemUI. يجب أن يسمح تطبيقك بعمليات الربط هذه، وإلا لن يتمكّن من إتاحة ميزة استئناف التشغيل.

يمكن تحديد الاتصالات الواردة من SystemUI والتحقّق منها باستخدام اسم الحزمة com.android.systemui والتوقيع. تم توقيع SystemUI باستخدام توقيع النظام الأساسي. يمكنك الاطّلاع على مثال على كيفية التحقّق من توقيع المنصة في تطبيق UAMP.

لكي تتيح إمكانية استئناف التشغيل، يجب أن ينفذ MediaBrowserService السلوكَين التاليَين:

  • يجب أن يعرض onGetRoot() جذرًا غير صفري بسرعة. يجب التعامل مع المنطق المعقد الآخر في onLoadChildren()

  • عند استدعاء onLoadChildren() على معرّف الوسائط الجذر، يجب أن تحتوي النتيجة على عنصر FLAG_PLAYABLE تابع.

  • من المفترض أن يعرض MediaBrowserService عنصر الوسائط الذي تم تشغيله مؤخرًا عندتلقّي طلب البحث EXTRA_RECENT. يجب أن تكون القيمة المعروضة عنصر وسائط فعليًا بدلاً من دالة عامة.

  • يجب أن يوفّر MediaBrowserService عنصر MediaDescription مناسبًا يتضمّن عنوانًا وعنوانًا فرعيًا غير فارغَين. يجب أيضًا ضبط معرّف موارد منتظم (URI) للرمز أو صورة نقطية للرمز.

توضّح أمثلة الرموز البرمجية التالية كيفية تنفيذ onGetRoot().

Kotlin

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)

Java

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