MediaSessionService를 사용한 백그라운드 재생

앱이 포그라운드에 있지 않을 때 미디어를 재생하는 것이 바람직한 경우가 많습니다. 대상 예를 들어 음악 플레이어는 일반적으로 사용자가 기기를 잠그면 다른 앱을 사용하고 있는지 확인할 수 있습니다. Media3 라이브러리는 이 인터페이스를 통해 백그라운드 재생을 지원할 수 있습니다.

MediaSessionService 사용

백그라운드 재생을 사용 설정하려면 Player 및 별도의 ServiceMediaSession 이렇게 하면 앱이 있습니다.

<ph type="x-smartling-placeholder">
</ph> MediaSessionService를 사용하면 미디어 세션을 별도로 실행할 수 있습니다.
  앱 활동에서
그림 1: MediaSessionService는 미디어를 허용함 앱의 활동과 별도로 실행되도록 합니다.

서비스 내에서 플레이어를 호스팅할 때는 MediaSessionService를 사용해야 합니다. 이렇게 하려면 MediaSessionService` 를 확장하는 클래스를 만들고 찾을 수 있습니다.

MediaSessionService를 사용하면 Google과 같은 외부 클라이언트가 가능합니다. 어시스턴트, 시스템 미디어 컨트롤 또는 Wear OS와 같은 호환 기기 연결하고, 연결하고, 재생을 제어할 수 있습니다. UI 활동이 전혀 없습니다. 실제로 여러 개의 클라이언트 앱이 같은 MediaSessionService에 동시에 연결할 수 있으며, 각 앱은 자체적인 MediaController

서비스 수명 주기 구현

서비스의 세 가지 수명 주기 메서드를 구현해야 합니다.

  • 첫 번째 컨트롤러가 연결되려고 할 때 onCreate()가 호출되고 시작됩니다 Kubernetes는 PlayerMediaSession
  • onTaskRemoved(Intent)는 사용자가 최근 할 일 목록을 표시합니다. 재생이 진행 중인 경우 앱에서 서비스를 유지하도록 선택할 수 있습니다. 포그라운드에서 실행되는 것을 볼 수 있습니다 플레이어가 일시중지되면 서비스는 중지되어야 합니다.
  • onDestroy()는 서비스가 중지되는 동안 호출됩니다. 모든 리소스 해제해야 합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

Kotlin

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

  // The user dismissed the app from the recent tasks
  override fun onTaskRemoved(rootIntent: Intent?) {
    val player = mediaSession?.player!!
    if (!player.playWhenReady
        || player.mediaItemCount == 0
        || player.playbackState == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf()
    }
  }

  // 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();
  }

  // The user dismissed the app from the recent tasks
  @Override
  public void onTaskRemoved(@Nullable Intent rootIntent) {
    Player player = mediaSession.getPlayer();
    if (!player.getPlayWhenReady()
        || player.getMediaItemCount() == 0
        || player.getPlaybackState() == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf();
    }
  }

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

백그라운드에서 재생을 계속 진행하는 대신 앱에서 사용자가 앱을 닫을 때 서비스를 중지합니다.

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  val player = mediaSession.player
  if (player.playWhenReady) {
    // Make sure the service is not in foreground.
    player.pause()
  }
  stopSelf()
}

자바

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  Player player = mediaSession.getPlayer();
  if (player.getPlayWhenReady()) {
    // Make sure the service is not in foreground.
    player.pause();
  }
  stopSelf();
}

미디어 세션에 대한 액세스 제공

onGetSession() 메서드를 재정의하여 다른 클라이언트에 미디어 액세스 권한을 부여합니다. 기본 세션을 제공합니다.

Kotlin

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 권한이 있어야 하며, API 34 및 위 FOREGROUND_SERVICE_MEDIA_PLAYBACK:

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

매니페스트에서 인텐트 필터를 사용하여 Service 클래스도 선언해야 합니다. 총 MediaSessionService페이지입니다.

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

먼저 foregroundServiceType 드림 앱이 Android가 설치된 기기에서 실행될 때 mediaPlayback가 포함됩니다. 10 (API 수준 29) 이상

MediaController를 사용하여 재생 제어

플레이어 UI가 포함된 활동 또는 프래그먼트에서 링크를 설정할 수 있습니다. MediaController를 사용하여 UI와 미디어 세션 간 UI 사용 미디어 컨트롤러를 사용하여 UI에서 플레이어로 명령을 보낼 수 있습니다. 세션입니다. 자세한 내용은 MediaController 만들기 MediaController 생성 및 사용에 관한 자세한 내용은 가이드를 참고하세요.

UI 명령어 처리

MediaSessionMediaSession.Callback MediaSession를 초기화하면 기본값이 생성됨 모든 코드를 자동으로 처리하는 MediaSession.Callback 구현 MediaController가 플레이어에 전송하는 명령어를 실행합니다.

알림

MediaSessionService는 자동으로 MediaNotification를 생성합니다. 대부분의 경우 잘 작동합니다. 기본적으로 게시된 알림은 MediaStyle 알림 항상 최신 정보를 재생 컨트롤을 표시합니다. MediaNotification 에서 세션을 인식하고 다른 앱의 재생을 제어하는 데 사용될 수 있습니다. 동일한 세션에 연결된 여러 광고 그룹을 만듭니다

예를 들어 MediaSessionService를 사용하는 음악 스트리밍 앱은 MediaNotification - 다음의 제목, 아티스트, 앨범아트를 표시합니다. 현재 미디어 항목을 재생 컨트롤과 함께 재생 중인 MediaSession 구성

필수 메타데이터는 미디어에 제공되거나 미디어 항목을 호출합니다.

Kotlin

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

앱은 Android 미디어 컨트롤의 명령 버튼을 맞춤설정할 수 있습니다. 자세히 알아보기 Android 미디어 맞춤설정에 관한 컨트롤을 참고하세요.

알림 맞춤설정

알림을 맞춤설정하려면 MediaNotification.Provider 드림 대상: DefaultMediaNotificationProvider.Builder 또는 제공자 인터페이스의 맞춤형 구현을 생성하여 제공할 수 있습니다. 제공업체를 MediaSessionService에 제공합니다. setMediaNotificationProvider

재생 재개

미디어 버튼은 Android 기기 및 기타 주변기기에서 볼 수 있는 하드웨어 버튼입니다. (예: 블루투스 헤드셋의 재생 또는 일시중지 버튼) 미디어3 서비스가 실행 중일 때 미디어 버튼 입력을 자동으로 처리합니다.

Media3 미디어 버튼 수신기 선언

Media3에는 사용자가 재개할 수 있는 API가 포함되어 있습니다. 앱이 종료된 후, 심지어 기기가 종료된 후에도 시작됩니다 기본적으로 재생 재개는 사용 중지되어 있습니다. 즉, 사용자는 서비스가 실행되고 있지 않을 때는 재생을 다시 시작할 수 없습니다. 선택하려면 먼저 매니페스트에서 MediaButtonReceiver을 선언합니다.

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

재생 재개 콜백 구현

블루투스 기기 또는 재생 재개를 요청하는 경우 Android 시스템 UI 재개 기능 onPlaybackResumption() 콜백 메서드가 호출됩니다.

Kotlin

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

재생 속도, 반복 모드 또는 셔플 모드인 경우 onPlaybackResumption()를 사용하여 플레이어를 구성하는 것이 좋습니다. 이러한 매개변수로 Media3이 플레이어를 준비하고 콜백이 완료됩니다.

고급 컨트롤러 구성 및 이전 버전과의 호환성

일반적인 시나리오는 앱 UI에서 MediaController를 사용하여 표시됩니다. 동시에 세션은 모바일 또는 TV의 어시스턴트, Android 미디어 컨트롤과 같은 외부 클라이언트와 시계용 Wear OS와 자동차용 Android Auto Media3 세션 데모 앱 은 이러한 시나리오를 구현하는 앱의 예입니다.

이러한 외부 클라이언트는 기존 API의 MediaControllerCompat와 같은 API를 사용할 수 있습니다. AndroidX 라이브러리 또는 Android의 android.media.session.MediaController 프레임워크입니다 Media3은 기존 라이브러리와 완전히 호환되며 Android 프레임워크 API와의 상호운용성을 제공합니다.

미디어 알림 컨트롤러 사용

이러한 레거시 또는 프레임워크 컨트롤러는 PlaybackState.getActions() 프레임워크의 동일한 값 PlaybackState.getCustomActions() 다음에 대한 작업 및 맞춤 작업을 결정하기 위해 프레임워크 세션에서 앱은 미디어 알림 컨트롤러를 사용할 수 있음 사용 가능한 명령어와 맞춤 레이아웃을 설정합니다. 서비스가 미디어를 연결함 알림을 보내고, 이 세션에서는 구성을 위해 콜백의 onConnect()에서 ConnectionResult를 반환함 작업 및 사용자 지정 작업을 모두 수행할 수 있습니다.

모바일 전용 시나리오에서는 앱이 MediaSession.Callback.onConnect(): 사용 가능한 명령어 설정 맞춤 레이아웃을 설정하는 것입니다.

Kotlin

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 layout and available commands to configure the legacy/framework session.
    return AcceptedResultBuilder(session)
      .setCustomLayout(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session.
    return new AcceptedResultBuilder(session)
        .setCustomLayout(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Android Auto에서 맞춤 명령을 전송하도록 승인

MediaLibraryService를 사용하는 경우 모바일 앱에서 Android Auto를 지원하기 위해 필요한 명령어가 필요하며, 그러지 않으면 Media3에서 이를 거부 해당 컨트롤러에서 수신되는 커스텀 명령어를

Kotlin

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 with default custom layout 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 without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

세션 데모 앱에는 자동차 모듈 는 별도의 APK가 필요한 Automotive OS 지원을 보여줍니다.