پخش پس‌زمینه با MediaSessionService

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

از MediaSessionService استفاده کنید

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

MediaSessionService اجازه می دهد تا جلسه رسانه به طور جداگانه اجرا شود   از فعالیت برنامه
شکل 1 : MediaSessionService به جلسه رسانه اجازه می دهد جدا از فعالیت برنامه اجرا شود.

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

استفاده از MediaSessionService این امکان را برای کلاینت‌های خارجی مانند Google Assistant، کنترل‌های رسانه سیستم، دکمه‌های رسانه در دستگاه‌های جانبی یا دستگاه‌های همراه مانند Wear OS فراهم می‌کند تا سرویس شما را کشف کنند، به آن متصل شوند و پخش را کنترل کنند، همگی بدون دسترسی به فعالیت رابط کاربری برنامه شما. در واقع، می‌توان چندین برنامه کلاینت را به طور همزمان به یک MediaSessionService متصل کرد که هر برنامه MediaController مخصوص به خود را دارد.

چرخه عمر سرویس را پیاده سازی کنید

شما باید دو روش چرخه عمر سرویس خود را پیاده سازی کنید:

  • onCreate() زمانی فراخوانی می شود که اولین کنترلر در شرف اتصال است و سرویس نمونه سازی و راه اندازی می شود. این بهترین مکان برای ساخت Player و MediaSession است.
  • onDestroy() زمانی فراخوانی می شود که سرویس در حال توقف است. همه منابع از جمله پخش کننده و جلسه باید آزاد شوند.

می‌توانید به‌صورت اختیاری، onTaskRemoved(Intent) لغو کنید تا زمانی که کاربر برنامه را از وظایف اخیر رد می‌کند، چه اتفاقی می‌افتد. به‌طور پیش‌فرض، اگر پخش ادامه داشته باشد، سرویس اجرا می‌شود و در غیر این صورت متوقف می‌شود.

کاتلین

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null

  // Create your player and media session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
  }

  // Remember to release the player and media session in onDestroy
  override fun onDestroy() {
    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    super.onDestroy()
  }
}

جاوا

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;

  // Create your Player and MediaSession in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaSession = new MediaSession.Builder(this, player).build();
  }

  // Remember to release the player and media session in onDestroy
  @Override
  public void onDestroy() {
    mediaSession.getPlayer().release();
    mediaSession.release();
    mediaSession = null;
    super.onDestroy();
  }
}

به عنوان جایگزینی برای ادامه پخش در پس‌زمینه، می‌توانید در هر صورت زمانی که کاربر برنامه را رد کرد، سرویس را متوقف کنید:

کاتلین

override fun onTaskRemoved(rootIntent: Intent?) {
  pauseAllPlayersAndStopSelf()
}

جاوا

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  pauseAllPlayersAndStopSelf();
}

برای هر پیاده‌سازی دستی دیگری از onTaskRemoved ، می‌توانید از isPlaybackOngoing() استفاده کنید تا بررسی کنید که آیا پخش در حال انجام است و سرویس پیش‌زمینه شروع شده است یا خیر.

امکان دسترسی به جلسه رسانه را فراهم کنید

روش onGetSession() را نادیده بگیرید تا به سایر کلاینت‌ها به جلسه رسانه شما که در زمان ایجاد سرویس ساخته شده است دسترسی داشته باشند.

کاتلین

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null
  // [...] lifecycle methods omitted

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
    mediaSession
}

جاوا

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;
  // [...] lifecycle methods omitted

  @Override
  public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    return mediaSession;
  }
}

سرویس را در مانیفست اعلام کنید

یک برنامه برای اجرای یک سرویس پیش‌زمینه پخش به مجوزهای FOREGROUND_SERVICE و FOREGROUND_SERVICE_MEDIA_PLAYBACK نیاز دارد:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

همچنین باید کلاس Service خود را در مانیفست با فیلتر قصد MediaSessionService و foregroundServiceType که شامل mediaPlayback است، اعلام کنید.

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

پخش را با استفاده از MediaController کنترل کنید

در Activity یا Fragment حاوی رابط کاربری پخش کننده شما، می توانید با استفاده از MediaController یک پیوند بین رابط کاربری و جلسه رسانه خود ایجاد کنید. رابط کاربری شما از کنترلر رسانه برای ارسال دستورات از رابط کاربری شما به پخش کننده در جلسه استفاده می کند. برای جزئیات بیشتر در مورد ایجاد و استفاده از MediaController به راهنمای MediaController یک MediaController مراجعه کنید.

کنترل دستورات MediaController

MediaSession دستورات را از طریق MediaSession.Callback خود از کنترلر دریافت می کند. راه‌اندازی یک MediaSession یک پیاده‌سازی پیش‌فرض از MediaSession.Callback ایجاد می‌کند که به‌طور خودکار تمام دستوراتی را که MediaController به پخش‌کننده شما ارسال می‌کند کنترل می‌کند.

اطلاع رسانی

یک MediaSessionService به طور خودکار یک MediaNotification برای شما ایجاد می کند که در بیشتر موارد باید کار کند. به طور پیش فرض، اعلان منتشر شده یک اعلان MediaStyle است که با آخرین اطلاعات جلسه رسانه شما به روز می شود و کنترل های پخش را نمایش می دهد. MediaNotification از جلسه شما آگاه است و می تواند برای کنترل پخش هر برنامه دیگری که به همان جلسه متصل است استفاده شود.

به عنوان مثال، یک برنامه پخش موسیقی با استفاده از MediaSessionService یک MediaNotification ایجاد می کند که عنوان، هنرمند و هنر آلبوم را برای آیتم رسانه فعلی در حال پخش در کنار کنترل های پخش بر اساس پیکربندی MediaSession شما نمایش می دهد.

فراداده مورد نیاز را می توان در رسانه ارائه کرد یا به عنوان بخشی از آیتم رسانه ای در قطعه زیر اعلام کرد:

کاتلین

val mediaItem =
    MediaItem.Builder()
      .setMediaId("media-1")
      .setUri(mediaUri)
      .setMediaMetadata(
        MediaMetadata.Builder()
          .setArtist("David Bowie")
          .setTitle("Heroes")
          .setArtworkUri(artworkUri)
          .build()
      )
      .build()

mediaController.setMediaItem(mediaItem)
mediaController.prepare()
mediaController.play()

جاوا

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaId("media-1")
        .setUri(mediaUri)
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setArtist("David Bowie")
                .setTitle("Heroes")
                .setArtworkUri(artworkUri)
                .build())
        .build();

mediaController.setMediaItem(mediaItem);
mediaController.prepare();
mediaController.play();

چرخه عمر اعلان

به محض اینکه Player موارد MediaItem را در لیست پخش خود داشته باشد، اعلان ایجاد می شود.

همه به‌روزرسانی‌های اعلان‌ها به‌طور خودکار براساس وضعیت Player و MediaSession انجام می‌شوند.

هنگامی که سرویس پیش زمینه در حال اجرا است، اعلان حذف نمی شود. برای حذف فوری اعلان، باید با Player.release() تماس بگیرید یا لیست پخش را با استفاده از Player.clearMediaItems() پاک کنید.

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

سفارشی سازی اعلان

فراداده مورد در حال پخش را می توان با تغییر MediaItem.MediaMetadata سفارشی کرد. اگر می‌خواهید فراداده یک مورد موجود را به‌روزرسانی کنید، می‌توانید از Player.replaceMediaItem برای به‌روزرسانی فراداده بدون وقفه در پخش استفاده کنید.

همچنین می‌توانید برخی از دکمه‌های نشان‌داده‌شده در اعلان را با تنظیم تنظیمات برگزیده دکمه رسانه سفارشی برای کنترل‌های Android Media شخصی‌سازی کنید. درباره سفارشی کردن کنترل‌های Android Media بیشتر بخوانید .

برای سفارشی کردن بیشتر خود اعلان، یک MediaNotification.Provider با DefaultMediaNotificationProvider.Builder یا با ایجاد یک پیاده سازی سفارشی از رابط ارائه دهنده ایجاد کنید. ارائه دهنده خود را با setMediaNotificationProvider به MediaSessionService خود اضافه کنید.

از سرگیری پخش

پس از پایان یافتن MediaSessionService و حتی پس از راه‌اندازی مجدد دستگاه، امکان ازسرگیری پخش وجود دارد تا به کاربران اجازه دهد سرویس را مجدداً راه‌اندازی کنند و پخش را از جایی که متوقف کرده‌اند از سر بگیرند. به طور پیش فرض، از سرگیری پخش خاموش است. این بدان معنی است که کاربر نمی تواند پخش را در زمانی که سرویس شما اجرا نمی کند، از سر بگیرد. برای شرکت در این ویژگی، باید یک گیرنده دکمه رسانه را اعلام کنید و روش onPlaybackResumption را پیاده سازی کنید.

گیرنده دکمه رسانه Media3 را اعلام کنید

با اعلام MediaButtonReceiver در مانیفست خود شروع کنید:

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

اجرای مجدد تماس مجدد پخش

هنگامی که از سرگیری پخش توسط یک دستگاه بلوتوث یا ویژگی از سرگیری رابط کاربری سیستم اندروید درخواست می شود، روش پاسخ به تماس onPlaybackResumption() فراخوانی می شود.

کاتلین

override fun onPlaybackResumption(
    mediaSession: MediaSession,
    controller: ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
  val settable = SettableFuture.create<MediaItemsWithStartPosition>()
  scope.launch {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    val resumptionPlaylist = restorePlaylist()
    settable.set(resumptionPlaylist)
  }
  return settable
}

جاوا

@Override
public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption(
    MediaSession mediaSession,
    ControllerInfo controller
) {
  SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create();
  settableFuture.addListener(() -> {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

اگر پارامترهای دیگری مانند سرعت پخش، حالت تکرار یا حالت shuffle را ذخیره کرده‌اید، onPlaybackResumption() مکان خوبی برای پیکربندی پخش‌کننده با این پارامترها قبل از اینکه Media3 پخش‌کننده را آماده کند و پس از اتمام تماس مجدد شروع به پخش کند، است.

پیکربندی کنترلر پیشرفته و سازگاری با عقب

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

این مشتریان خارجی ممکن است از APIهایی مانند MediaControllerCompat کتابخانه قدیمی AndroidX یا android.media.session.MediaController پلت فرم Android استفاده کنند. Media3 کاملاً با کتابخانه قدیمی سازگار است و قابلیت همکاری با API پلتفرم Android را فراهم می کند.

از کنترلر اعلان رسانه استفاده کنید

درک این نکته مهم است که این کنترل‌کننده‌های قدیمی و پلتفرم حالت یکسانی دارند و قابلیت مشاهده توسط کنترلر قابل تنظیم نیست (برای مثال PlaybackState.getActions() و PlaybackState.getCustomActions() موجود). می توانید از کنترلر اعلان رسانه برای پیکربندی وضعیت تنظیم شده در جلسه رسانه پلت فرم برای سازگاری با این کنترلرهای قدیمی و پلت فرم استفاده کنید.

به عنوان مثال، یک برنامه می‌تواند اجرای MediaSession.Callback.onConnect() را برای تنظیم دستورات موجود و تنظیمات برگزیده دکمه رسانه به‌طور خاص برای جلسه پلتفرم به شرح زیر ارائه دهد:

کاتلین

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  if (session.isMediaNotificationController(controller)) {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(customCommandSeekBackward)
        .add(customCommandSeekForward)
        .build()
    val playerCommands =
      ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
        .remove(COMMAND_SEEK_TO_PREVIOUS)
        .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
        .remove(COMMAND_SEEK_TO_NEXT)
        .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
        .build()
    // Custom button preferences and commands to configure the platform session.
    return AcceptedResultBuilder(session)
      .setMediaButtonPreferences(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default button preferences for all other controllers.
  return AcceptedResultBuilder(session).build()
}

جاوا

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  if (session.isMediaNotificationController(controller)) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(customCommandSeekBackward)
            .add(customCommandSeekForward)
            .build();
    Player.Commands playerCommands =
        ConnectionResult.DEFAULT_PLAYER_COMMANDS
            .buildUpon()
            .remove(COMMAND_SEEK_TO_PREVIOUS)
            .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
            .remove(COMMAND_SEEK_TO_NEXT)
            .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
            .build();
    // Custom button preferences and commands to configure the platform session.
    return new AcceptedResultBuilder(session)
        .setMediaButtonPreferences(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands with default button preferences for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

مجوز Android Auto برای ارسال دستورات سفارشی

هنگام استفاده از MediaLibraryService و برای پشتیبانی از Android Auto با برنامه تلفن همراه، کنترل‌کننده Android Auto به دستورات موجود نیاز دارد، در غیر این صورت Media3 دستورات سفارشی دریافتی از آن کنترل‌کننده را رد می‌کند:

کاتلین

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  val sessionCommands =
    ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
      .add(customCommandSeekBackward)
      .add(customCommandSeekForward)
      .build()
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available session commands to accept incoming custom commands from Auto.
    return AcceptedResultBuilder(session)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands for all other controllers.
  return AcceptedResultBuilder(session).build()
}

جاوا

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  SessionCommands sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS
          .buildUpon()
          .add(customCommandSeekBackward)
          .add(customCommandSeekForward)
          .build();
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available commands to accept incoming custom commands from Auto.
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

برنامه نمایشی جلسه دارای یک ماژول خودرو است که پشتیبانی از سیستم عامل Automotive را نشان می دهد که به یک APK جداگانه نیاز دارد.