앱이 포그라운드에 있지 않을 때 미디어를 재생하는 것이 좋습니다. 예를 들어 음악 플레이어는 일반적으로 사용자가 기기를 잠그거나 다른 앱을 사용할 때도 음악을 계속 재생합니다. Media3 라이브러리는 백그라운드 재생을 지원할 수 있는 일련의 인터페이스를 제공합니다.
MediaSessionService 사용
백그라운드 재생을 사용 설정하려면 Player
및 MediaSession
를 별도의 서비스 내에 포함해야 합니다.
이렇게 하면 앱이 포그라운드에 있지 않은 동안에도 기기가 미디어를 계속 제공할 수 있습니다.

MediaSessionService
를 사용하면 미디어 세션을 앱의 활동과 별도로 실행할 수 있습니다.서비스 내에서 플레이어를 호스팅할 때는 MediaSessionService
를 사용해야 합니다.
이렇게 하려면 MediaSessionService
를 확장하는 클래스를 만들고 그 안에 미디어 세션을 만듭니다.
MediaSessionService
를 사용하면 Google 어시스턴트, 시스템 미디어 컨트롤, 주변기기의 미디어 버튼, Wear OS와 같은 호환 기기와 같은 외부 클라이언트가 앱의 UI 활동에 전혀 액세스하지 않고도 서비스를 검색하고, 앱에 연결하고, 재생을 제어할 수 있습니다. 실제로 동일한 MediaSessionService
에 여러 클라이언트 앱을 동시에 연결할 수 있으며 이때 각 앱에는 자체 MediaController
가 있습니다.
서비스 수명 주기 구현
서비스의 두 가지 수명 주기 메서드를 구현해야 합니다.
onCreate()
는 첫 번째 컨트롤러가 연결되고 서비스가 인스턴스화되고 시작될 때 호출됩니다.Player
및MediaSession
를 빌드하기에 가장 좋은 위치입니다.onDestroy()
는 서비스가 중지될 때 호출됩니다. 플레이어와 세션을 비롯한 모든 리소스를 해제해야 합니다.
원하는 경우 onTaskRemoved(Intent)
를 재정의하여 사용자가 최근 작업에서 앱을 닫을 때 발생하는 작업을 맞춤설정할 수 있습니다. 기본적으로 재생이 진행 중인 경우 서비스는 계속 실행되고 그렇지 않은 경우에는 중지됩니다.
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() } // 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(); } }
백그라운드에서 재생을 계속하는 대신 사용자가 앱을 닫을 때마다 서비스를 중지할 수 있습니다.
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
자바
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
onTaskRemoved
의 다른 수동 구현의 경우 isPlaybackOngoing()
를 사용하여 재생이 진행 중으로 간주되고 포그라운드 서비스가 시작되었는지 확인할 수 있습니다.
미디어 세션에 대한 액세스 제공
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
및 FOREGROUND_SERVICE_MEDIA_PLAYBACK
권한이 필요합니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
또한 매니페스트에서 MediaSessionService
인텐트 필터와 mediaPlayback
가 포함된 foregroundServiceType
를 사용하여 Service
클래스를 선언해야 합니다.
<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
를 사용하여 재생 제어
플레이어 UI가 포함된 활동 또는 프래그먼트에서 MediaController
를 사용하여 UI와 미디어 세션 간에 링크를 설정할 수 있습니다. UI는 미디어 컨트롤러를 사용하여 UI에서 세션 내 플레이어로 명령어를 전송합니다. MediaController
만들기 및 사용에 관한 자세한 내용은 MediaController
만들기 가이드를 참고하세요.
MediaController
명령어 처리
MediaSession
는 MediaSession.Callback
를 통해 컨트롤러로부터 명령어를 수신합니다. MediaSession
를 초기화하면 MediaController
가 플레이어에게 전송하는 모든 명령을 자동으로 처리하는 MediaSession.Callback
의 기본 구현이 생성됩니다.
알림
MediaSessionService
는 대부분의 경우 작동하는 MediaNotification
를 자동으로 만듭니다. 게시된 알림은 기본적으로 미디어 세션의 최신 정보로 업데이트되고 재생 컨트롤을 표시하는 MediaStyle
알림입니다. MediaNotification
는 세션을 인식하며 동일한 세션에 연결된 다른 앱의 재생을 제어하는 데 사용할 수 있습니다.
예를 들어 MediaSessionService
를 사용하는 음악 스트리밍 앱은 MediaSession
구성에 따라 재생 컨트롤과 함께 재생 중인 현재 미디어 항목의 제목, 아티스트, 앨범 아트를 표시하는 MediaNotification
를 만듭니다.
필수 메타데이터는 미디어에 제공하거나 다음 스니펫과 같이 미디어 항목의 일부로 선언할 수 있습니다.
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();
알림 수명 주기
Player
의 재생목록에 MediaItem
인스턴스가 생성되는 즉시 알림이 생성됩니다.
모든 알림 업데이트는 Player
및 MediaSession
상태를 기반으로 자동으로 이루어집니다.
포그라운드 서비스가 실행 중인 동안에는 알림을 삭제할 수 없습니다. 알림을 즉시 삭제하려면 Player.release()
를 호출하거나 Player.clearMediaItems()
를 사용하여 재생목록을 지워야 합니다.
더 이상 사용자 상호작용 없이 10분 넘게 플레이어가 일시중지되거나 중지되거나 실패하면 서비스가 포그라운드 서비스 상태에서 자동으로 전환되므로 시스템에서 삭제할 수 있습니다. 재생 재개를 구현하여 사용자가 서비스 수명 주기를 다시 시작하고 나중에 재생을 재개할 수 있도록 할 수 있습니다.
알림 맞춤설정
현재 재생 중인 항목에 관한 메타데이터는 MediaItem.MediaMetadata
를 수정하여 맞춤설정할 수 있습니다. 기존 항목의 메타데이터를 업데이트하려면 Player.replaceMediaItem
를 사용하여 재생을 중단하지 않고 메타데이터를 업데이트하면 됩니다.
Android 미디어 컨트롤에 맞춤 미디어 버튼 환경설정을 설정하여 알림에 표시되는 일부 버튼을 맞춤설정할 수도 있습니다. Android 미디어 컨트롤 맞춤설정에 대해 자세히 알아보기
알림 자체를 추가로 맞춤설정하려면 DefaultMediaNotificationProvider.Builder
를 사용하여 MediaNotification.Provider
를 만들거나 제공자 인터페이스의 맞춤 구현을 만드세요. 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>
재생 재개 콜백 구현
블루투스 기기 또는 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 세션 데모 앱은 이러한 시나리오를 구현하는 앱의 예입니다.
이러한 외부 클라이언트는 기존 AndroidX 라이브러리의 MediaControllerCompat
또는 Android 플랫폼의 android.media.session.MediaController
와 같은 API를 사용할 수 있습니다. Media3는 기존 라이브러리와 완전히 하위 호환되며 Android 플랫폼 API와의 상호 운용성을 제공합니다.
미디어 알림 컨트롤러 사용
이러한 기존 및 플랫폼 컨트롤러는 동일한 상태를 공유하며 컨트롤러별로 공개 상태를 맞춤설정할 수 없습니다 (예: 사용 가능한 PlaybackState.getActions()
및 PlaybackState.getCustomActions()
). 미디어 알림 컨트롤러를 사용하여 이러한 기존 및 플랫폼 컨트롤러와 호환되도록 플랫폼 미디어 세션에 설정된 상태를 구성할 수 있습니다.
예를 들어 앱은 다음과 같이 플랫폼 세션에만 사용할 수 있는 명령어와 미디어 버튼 환경설정을 설정하는 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 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가 해당 컨트롤러에서 수신되는 맞춤 명령어를 거부합니다.
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 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(); }
세션 데모 앱에는 별도의 APK가 필요한 Automotive OS 지원을 보여주는 automotive 모듈이 있습니다.