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

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

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

بدءًا من نظام التشغيل Android 13 (المستوى 33 لواجهة برمجة التطبيقات)، لضمان وصول المستخدمين إلى مجموعة وافية من عناصر التحكّم في الوسائط في التطبيقات التي تشغِّل الوسائط، يتم إنشاء أزرار الإجراءات في عناصر التحكّم في الوسائط من حالة Player.

بهذه الطريقة، يمكنك تقديم مجموعة متسقة من عناصر التحكّم في الوسائط وتجربة أكثر محسّنة للتحكّم في الوسائط على جميع الأجهزة.

ويوضح الشكل 1 مثالاً على الشكل الذي يبدو عليه ذلك على الهاتف والجهاز اللوحي، على التوالي.

عناصر التحكّم في الوسائط من حيث طريقة ظهورها على الهواتف والأجهزة اللوحية،
            باستخدام مثال على نموذج مقطع صوتي يوضّح كيفية ظهور الأزرار
الشكل 1: عناصر التحكم في الوسائط على الهواتف والأجهزة اللوحية

ويعرض النظام ما يصل إلى خمسة أزرار إجراءات استنادًا إلى حالة Player كما هو موضّح في الجدول التالي. في الوضع المكثف، يتم عرض خانات الإجراءات الثلاثة الأولى فقط. يتوافق هذا مع طريقة عرض عناصر التحكّم في الوسائط على أنظمة Android الأساسية الأخرى، مثل Auto و"مساعد Google" و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، ويُستخدَم الأمر المخصّص لملء الخانة من خلال التنسيق المخصّص الذي لم يتم وضعه بعد. قرض مخصص
(غير متوافقة بعد مع Media3) PlaybackState العناصر الإضافية تتضمن 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، ويُستخدَم الأمر المخصّص لملء الخانة من خلال التنسيق المخصّص الذي لم يتم وضعه بعد. قرض مخصص
(غير متوافقة بعد مع Media3) PlaybackState العناصر الإضافية تتضمن 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 تلقائيًا.

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

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

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

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

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

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

الاستجابة لإجراءات PLAYState

عندما ينقر المستخدم على زر، تستخدم واجهة 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_SECONDS. يجب أن تكون القيمة المعروضة عنصر وسائط فعلي بدلاً من دالة عامة.

  • يجب أن توفر 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);
}