AndroidX Media3 이전 가이드

현재 독립형 com.google.android.exoplayer2 라이브러리와 androidx.media를 사용 중인 앱은 androidx.media3로 이전해야 합니다. 이전 스크립트를 사용하여 Gradle 빌드 파일, 자바 및 Kotlin 소스 파일, XML 레이아웃 파일을 ExoPlayer 2.19.1에서 AndroidX Media3 1.1.1로 이전합니다.

개요

이전하기 전에 다음 섹션을 검토하여 새 API의 이점, 이전할 API, 앱 프로젝트가 충족해야 하는 기본 요건을 자세히 알아보세요.

Jetpack Media3로 이전하는 이유

  • 이는 ExoPlayer의 새로운 홈이지만 com.google.android.exoplayer2는 지원 중단되었습니다.
  • MediaBrowser/MediaController를 사용하여 구성요소/프로세스 전반에서 Player API에 액세스합니다.
  • MediaSessionMediaController API의 확장된 기능을 사용합니다.
  • 세분화된 액세스 제어로 재생 기능을 알립니다.
  • MediaSessionConnectorPlayerNotificationManager를 삭제하여 앱을 간소화합니다.
  • media-compat 클라이언트 API와 하위 호환(MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat)

AndroidX Media3으로 이전할 미디어 API

  • ExoPlayer 및 확장 프로그램
    여기에는 중단된 mediasession 모듈을 제외한 기존 ExoPlayer 프로젝트의 모든 모듈이 포함됩니다. com.google.android.exoplayer2의 패키지에 종속된 앱 또는 모듈은 이전 스크립트를 사용하여 이전할 수 있습니다.
  • MediaSessionConnector (androidx.media:media:1.4.3+androidx.media.* 패키지에 따라 다름)
    MediaSessionConnector를 삭제하고 대신 androidx.media3.session.MediaSession를 사용합니다.
  • MediaBrowserServiceCompat (androidx.media:media:1.4.3+androidx.media.* 패키지에 따라 다름)
    androidx.media.MediaBrowserServiceCompat의 서브클래스를 androidx.media3.session.MediaLibraryService로 이전하고 MediaBrowserCompat.MediaItem를 사용하여 코드를 androidx.media3.common.MediaItem로 이전합니다.
  • MediaBrowserCompat (androidx.media:media:1.4.3+android.support.v4.media.* 패키지에 따라 다름)
    MediaBrowserCompat 또는 MediaControllerCompat를 사용하여 클라이언트 코드를 이전하여 androidx.media3.session.MediaBrowserandroidx.media3.common.MediaItem와 함께 사용합니다.

기본 요건

  1. 프로젝트에 소스 제어가 적용되는지 확인

    스크립트로 작성된 이전 도구로 적용한 변경사항을 쉽게 되돌릴 수 있어야 합니다. 아직 소스 제어를 받고 있지 않다면 지금 프로젝트를 시작하는 것이 좋습니다. 이를 원하지 않으면 마이그레이션을 시작하기 전에 프로젝트의 백업 사본을 만드세요.

  2. 앱 업데이트

    • 최신 버전의 ExoPlayer 라이브러리를 사용하도록 프로젝트를 업데이트하고 지원 중단된 메서드 호출을 삭제하는 것이 좋습니다. 마이그레이션에 스크립트를 사용하려면 업데이트할 버전을 스크립트에서 처리하는 버전과 일치해야 합니다.

    • 앱의 compileSdkVersion을 32 이상으로 늘립니다.

    • 위에서 업데이트된 종속 항목과 호환되는 최신 버전으로 Gradle 및 Android 스튜디오 Gradle 플러그인을 업그레이드합니다. 예를 들면 다음과 같습니다.

      • Android Gradle 플러그인 버전: 7.1.0
      • Gradle 버전: 7.4
    • asterix(*)를 사용하고 정규화된 가져오기 문을 사용하는 모든 와일드 카드 가져오기 문 바꾸기: 와일드 카드 가져오기 문을 삭제하고 Android 스튜디오를 사용하여 정규화된 문 (F2 - Alt/Enter, F2 - Alt/Enter, ...)을 가져옵니다.

    • com.google.android.exoplayer2.PlayerView에서 com.google.android.exoplayer2.StyledPlayerView로 이전합니다. 이는 AndroidX Media3에 com.google.android.exoplayer2.PlayerView에 상응하는 항목이 없기 때문에 필요합니다.

스크립트 지원으로 ExoPlayer 이전

이 스크립트는 com.google.android.exoplayer2에서 androidx.media3 아래의 새 패키지 및 모듈 구조로 쉽게 이동할 수 있습니다. 스크립트는 프로젝트에 일부 유효성 검사 검사를 적용하고 유효성 검사가 실패하면 경고를 출력합니다. 그 외의 경우에는 자바나 Kotlin으로 작성된 Android Gradle 프로젝트의 리소스에 이름이 바뀐 클래스 및 패키지의 매핑을 적용합니다.

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

이전 스크립트 사용

  1. GitHub의 ExoPlayer 프로젝트 태그에서 앱을 업데이트한 버전에 해당하는 이전 스크립트를 다운로드합니다.

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. 스크립트를 실행 가능하게 만듭니다.

    chmod 744 media3-migration.sh
    
  3. --help로 스크립트를 실행하여 옵션에 대해 알아봅니다.

  4. -l로 스크립트를 실행하여 이전하도록 선택된 파일 세트를 나열합니다 (-f를 사용하여 경고 없이 등록정보를 강제 적용).

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. -m로 스크립트를 실행하여 패키지, 클래스, 모듈을 Media3에 매핑합니다. -m 옵션을 사용하여 스크립트를 실행하면 선택한 파일에 변경사항이 적용됩니다.

    • 수정 없이 유효성 검사 오류 발생 시 중지
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • 강제 실행

    스크립트가 기본 요건을 위반하면 -f 플래그를 사용하여 강제로 이전할 수 있습니다.

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

-m 옵션을 사용하여 스크립트를 실행한 후 다음 수동 단계를 완료합니다.

  1. 스크립트가 코드를 어떻게 변경했는지 확인: diff 도구를 사용하여 잠재적인 문제를 해결합니다. -f 옵션을 전달하지 않고 발생한 일반적인 문제가 스크립트에 있다고 생각되면 버그를 신고하는 것이 좋습니다.
  2. 프로젝트 빌드: ./gradlew clean build를 사용하거나 Android 스튜디오에서 File > Sync Project with Gradle Files를 선택한 다음 Build > Clean project를 선택하고 Build > Rebuild project를 선택합니다 (Android 스튜디오의'Build - Build Output' 탭에서 빌드를 모니터링).

권장되는 후속 조치 단계:

  1. 불안정한 API 사용과 관련된 오류 선택을 해결합니다.
  2. 지원 중단된 API 호출 대체: 제안된 대체 API를 사용합니다. Android 스튜디오의 경고 위에 마우스 포인터를 올려놓고 지원 중단된 기호의 JavaDoc을 참조하여 지정된 호출 대신 사용할 내용을 확인합니다.
  3. 가져오기 문 정렬: Android 스튜디오에서 프로젝트를 열고 프로젝트 뷰어에서 패키지 폴더 노드를 마우스 오른쪽 버튼으로 클릭하고 변경된 소스 파일이 포함된 패키지에서 가져오기 최적화를 선택합니다.

MediaSessionConnectorandroidx.media3.session.MediaSession로 바꿉니다.

기존 MediaSessionCompat 환경에서 MediaSessionConnector는 플레이어 상태를 세션 상태와 동기화하고 적절한 플레이어 메서드에 위임이 필요한 컨트롤러로부터 명령어를 수신하는 역할을 했습니다. AndroidX Media3에서는 커넥터가 없어도 MediaSession에서 직접 이 작업을 실행합니다.

  1. MediaSessionConnector의 모든 참조 및 사용 삭제: 자동화된 스크립트를 사용하여 ExoPlayer 클래스와 패키지를 이전했다면 스크립트로 인해 해결할 수 없는 MediaSessionConnector와 관련하여 코드를 컴파일할 수 없는 상태가 되었을 수 있습니다. Android 스튜디오는 앱을 빌드하거나 시작하려고 할 때 손상된 코드를 표시합니다.

  2. 종속 항목을 유지관리하는 build.gradle 파일에서 AndroidX Media3 세션 모듈에 구현 종속 항목을 추가하고 기존 종속 항목을 삭제합니다.

    implementation "androidx.media3:media3-session:1.2.1"
    
  3. MediaSessionCompatandroidx.media3.session.MediaSession로 바꿉니다.

  4. 기존 MediaSessionCompat를 만든 코드 사이트에서 androidx.media3.session.MediaSession.Builder를 사용하여 MediaSession를 빌드합니다. 플레이어를 전달하여 세션 빌더를 구성합니다.

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player)
        .setSessionCallback(MySessionCallback())
        .build()
    
  5. 앱의 필요에 따라 MySessionCallback를 구현합니다. 이는 선택사항입니다. 컨트롤러가 플레이어에 미디어 항목을 추가하도록 허용하려면 MediaSession.Callback.onAddMediaItems()를 구현합니다. 이전 버전과 호환되는 방식으로 재생할 수 있도록 플레이어에 미디어 항목을 추가하는 다양한 현재 및 기존 API 메서드를 제공합니다. 여기에는 Media3 컨트롤러의 MediaController.set/addMediaItems() 메서드와 기존 API의 TransportControls.prepareFrom*/playFrom* 메서드가 포함됩니다. onAddMediaItems의 샘플 구현은 세션 데모 앱의 PlaybackService에서 확인할 수 있습니다.

  6. 이전하기 전에 세션을 폐기한 코드 사이트에서 미디어 세션을 해제합니다.

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    

Media3의 MediaSessionConnector 기능

다음 표는 이전에 MediaSessionConnector에 구현된 기능을 처리하는 Media3 API를 보여줍니다.

MediaSessionConnectorAndroidX 미디어3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setCustomLayout()
PlaybackPreparer MediaSession.Callback.onAddMediaItems()(prepare()는 내부적으로 호출됨)
QueueNavigator ForwardingPlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

MediaBrowserServiceMediaLibraryService로 이전

AndroidX Media3에 MediaBrowserServiceCompat를 대체하는 MediaLibraryService가 도입되었습니다. MediaLibraryService의 JavaDoc과 슈퍼 클래스 MediaSessionService는 API 및 서비스의 비동기 프로그래밍 모델에 관한 유용한 소개를 제공합니다.

MediaLibraryServiceMediaBrowserService와 하위 호환됩니다. MediaBrowserCompat 또는 MediaControllerCompat를 사용하는 클라이언트 앱은 MediaLibraryService에 연결할 때 코드 변경 없이 계속 작동합니다. 클라이언트의 경우 앱이 MediaLibraryService를 사용하는지 아니면 레거시 MediaBrowserServiceCompat를 사용하는지가 투명합니다.

서비스, 활동, 외부 앱이 포함된 앱 구성요소 다이어그램
그림 1: 미디어 앱 구성요소 개요
  1. 이전 버전과의 호환성이 작동하려면 AndroidManifest.xml에서 서비스에 두 서비스 인터페이스를 모두 등록해야 합니다. 이렇게 하면 클라이언트가 필요한 서비스 인터페이스로 서비스를 찾습니다.

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. 종속 항목을 유지관리하는 build.gradle 파일에서 AndroidX Media3 세션 모듈에 구현 종속 항목을 추가하고 기존 종속 항목을 삭제합니다.

    implementation "androidx.media3:media3-session:1.2.1"
    
  3. MediaBrowserService 대신 MediaLibraryService에서 상속하도록 서비스를 변경합니다. 앞서 언급했듯이 MediaLibraryService는 기존 MediaBrowserService와 호환됩니다. 따라서 서비스가 클라이언트에 제공하는 더 광범위한 API는 여전히 동일합니다. 따라서 앱은 MediaBrowserService를 구현하고 이를 새 MediaLibraryService에 맞게 조정하는 데 필요한 대부분의 로직을 유지할 수 있습니다.

    기존 MediaBrowserServiceCompat와의 주요 차이점은 다음과 같습니다.

    • 서비스 수명 주기 메서드 구현: 서비스 자체에서 재정의해야 하는 메서드는 onCreate/onDestroy입니다. 여기서 앱은 라이브러리 세션, 플레이어 및 기타 리소스를 할당/해제합니다. 표준 서비스 수명 주기 메서드 외에도 앱은 onGetSession(MediaSession.ControllerInfo)를 재정의하여 onCreate에서 빌드된 MediaLibrarySession를 반환해야 합니다.

    • MediaLibraryService.MediaLibrarySessionCallback 구현: 세션을 빌드하려면 실제 도메인 API 메서드를 구현하는 MediaLibraryService.MediaLibrarySessionCallback가 필요합니다. 따라서 기존 서비스의 API 메서드를 재정의하는 대신 대신 MediaLibrarySession.Callback의 메서드를 재정의합니다.

      그런 다음 이 콜백은 MediaLibrarySession를 빌드하는 데 사용됩니다.

      mediaLibrarySession =
            MediaLibrarySession.Builder(this, player, MySessionCallback())
               .build()
      

      API 문서에서 MediaLibrarySessionCallback의 전체 API를 찾아보세요.

    • MediaSession.Callback.onAddMediaItems() 구현: 콜백 onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>)는 이전 버전과 호환되는 방식으로 재생할 미디어 항목을 플레이어에 추가하는 다양한 현재 및 기존 API 메서드를 제공합니다. 여기에는 Media3 컨트롤러의 MediaController.set/addMediaItems() 메서드 및 기존 API의 TransportControls.prepareFrom*/playFrom* 메서드가 포함됩니다. 콜백의 샘플 구현은 세션 데모 앱의 PlaybackService에서 확인할 수 있습니다.

    • AndroidX Media3은 MediaBrowserCompat.MediaItemMediaMetadataCompat 대신 androidx.media3.common.MediaItem를 사용합니다. 기존 클래스에 연결된 코드의 일부를 적절하게 변경하거나 Media3 MediaItem에 매핑해야 합니다.

    • MediaBrowserServiceCompat의 분리 가능한 Result 접근 방식과 달리 일반적인 비동기 프로그래밍 모델이 Futures로 변경되었습니다. 서비스 구현은 결과를 분리하는 대신 비동기 ListenableFuture를 반환하거나 즉시 Future를 반환하여 값을 직접 반환할 수 있습니다.

PlayerNotificationManager 삭제

MediaLibraryService는 미디어 알림을 자동으로 지원하며 MediaLibraryService 또는 MediaSessionService를 사용할 때 PlayerNotificationManager를 삭제할 수 있습니다.

앱은 onCreate()DefaultMediaNotificationProvider를 대체하는 맞춤 MediaNotification.Provider를 설정하여 알림을 맞춤설정할 수 있습니다. 그러면 MediaLibraryService가 필요에 따라 포그라운드에서 서비스를 시작합니다.

MediaLibraryService.updateNotification()를 재정의하면 앱은 필요에 따라 알림을 게시하고 포그라운드에서 서비스를 시작/중지하는 완전한 소유권을 추가로 보유할 수 있습니다.

MediaBrowser를 사용하여 클라이언트 코드 이전

AndroidX Media3에서 MediaBrowserMediaController/Player 인터페이스를 구현하며, 미디어 라이브러리 탐색 외에 미디어 재생을 제어하는 데 사용할 수 있습니다. 레거시 환경에서 MediaBrowserCompatMediaControllerCompat를 만들어야 했다면 Media3에서 MediaBrowser만 사용하면 됩니다.

MediaBrowser를 빌드하고 설정되는 서비스에 대한 연결을 기다립니다.

scope.launch {
    val sessionToken =
        SessionToken(context, ComponentName(context, MusicService::class.java)
    browser =
        MediaBrowser.Builder(context, sessionToken))
            .setListener(BrowserListener())
            .buildAsync()
            .await()
    // Get the library root to start browsing the library.
    root = browser.getLibraryRoot(/* params= */ null).await();
    // Add a MediaController.Listener to listen to player state events.
    browser.addListener(playerListener)
    playerView.setPlayer(browser)
}

백그라운드에서 재생을 제어하는 MediaController를 만드는 방법을 알아보려면 미디어 세션에서 재생 제어를 살펴보세요.

추가 단계 및 정리

불안정한 API 오류

Media3으로 이전한 후 불안정한 API 사용에 관한 린트 오류가 표시될 수 있습니다. 이러한 API는 안전하게 사용할 수 있으며 린트 오류는 새로운 바이너리 호환성 보장의 부산물입니다. 엄격한 바이너리 호환성이 필요하지 않으면 @OptIn 주석을 사용하여 이러한 오류를 안전하게 억제할 수 있습니다.

배경

ExoPlayer v1과 v2 모두 후속 버전 간 라이브러리의 바이너리 호환성을 엄격하게 보장하지 않습니다. ExoPlayer API 노출 영역은 앱에서 재생의 거의 모든 측면을 맞춤설정할 수 있도록 설계상 매우 큽니다. 후속 버전의 ExoPlayer에서는 기호 이름 바꾸기 또는 기타 브레이킹 체인지 (예: 인터페이스에 새로운 필수 메서드)를 도입하는 경우가 있습니다. 대부분의 경우 개발자가 사용을 이전할 시간을 제공하기 위해 일부 버전에서 이전 기호를 지원 중단하고 새 기호를 도입하여 이러한 중단을 완화할 수 있었지만 항상 가능한 것은 아니었습니다.

이러한 브레이킹 체인지로 인해 ExoPlayer v1 및 v2 라이브러리 사용자에게 두 가지 문제가 발생했습니다.

  1. ExoPlayer 버전으로 업그레이드하면 코드 컴파일이 중지될 수 있습니다.
  2. 직접적이든 중간 라이브러리를 통해 ExoPlayer에 종속된 앱은 두 종속 항목이 동일한 버전인지 확인해야 했습니다. 그러지 않으면 바이너리 비호환성으로 인해 런타임 비정상 종료가 발생할 수 있습니다.

Media3의 개선사항

Media3은 API 노출 영역의 하위 집합에 대한 바이너리 호환성을 보장합니다. 바이너리 호환성을 보장하지 않는 부분은 @UnstableApi로 표시합니다. 이러한 구분을 명확히 하기 위해 불안정한 API 기호를 사용하면 @OptIn 주석이 달리지 않는 한 린트 오류가 발생합니다.

ExoPlayer v2에서 Media3으로 이전한 후 안정적인 API 린트 오류가 많이 표시될 수 있습니다. 이로 인해 Media3이 ExoPlayer v2보다 '덜 안정적'인 것처럼 보일 수 있습니다. 호출은 건너뛸 수 없습니다. Media3 API의 '불안정한' 부분은 ExoPlayer v2 API 노출 영역의 전체와 동일한 수준의 안정성을 가지며 안정적인 Media3 API 노출 영역의 보장은 ExoPlayer v2에서 전혀 사용할 수 없습니다. 차이점은 이제 린트 오류가 다양한 수준의 안정성을 알려준다는 것입니다.

불안정한 API 린트 오류 처리

불안정한 API 린트 오류를 처리하는 방법에는 두 가지가 있습니다.

  • 동일한 결과를 얻는 안정적인 API를 사용하도록 전환합니다.
  • 불안정한 API를 계속 사용하고 @OptIn 주석을 추가합니다.

    import androidx.annotation.OptIn
    import androidx.media3.common.util.UnstableApi
    
    @OptIn(UnstableApi::class)
    fun functionUsingUnstableApi() {
      // Do something useful.
    }
    

    사용해서는 안 되는 kotlin.OptIn 주석도 있습니다. 이를 위해 androidx.annotation.OptIn 주석을 계속 사용하는 것이 중요합니다.

    스크린샷: 수신 동의 주석을 추가하는 방법
    그림 2: Android 스튜디오에서 @androidx.annotations.OptIn 주석 추가

전체 패키지는 package-info.java를 추가하여 선택할 수 있습니다.

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

lint.xml에서 특정 린트 오류를 억제하여 전체 프로젝트를 선택할 수 있습니다. 자세한 내용은 UnstableApi 주석의 JavaDoc을 참고하세요.

지원 중단된 API

지원 중단된 API 호출이 Android 스튜디오에서 취소되는 것을 확인할 수 있습니다. 이러한 호출을 적절한 대안으로 바꾸는 것이 좋습니다. 기호 위로 마우스를 가져가면 대신 사용할 API를 알려주는 JavaDoc을 확인할 수 있습니다.

스크린샷: 지원 중단된 메서드의 대안과 함께 JavaDoc을 표시하는 방법
그림 3: Android 스튜디오의 JavaDoc 도움말은 지원 중단된 기호의 대안을 제안합니다.

코드 샘플 및 데모 앱