MediaPlayer 개요

Android 멀티미디어 프레임워크는 다양한 일반 미디어 유형의 재생을 지원하므로 오디오, 동영상, 이미지를 애플리케이션에 쉽게 통합할 수 있습니다. 애플리케이션 리소스(원시 리소스)에 저장된 미디어 파일, 파일 시스템의 독립형 파일 또는 네트워크 연결을 통해 들어오는 데이터 스트림에서 모두 MediaPlayer API를 사용하여 오디오 또는 동영상을 재생할 수 있습니다.

이 문서에서는 우수한 성능과 쾌적한 사용자 환경을 얻기 위해 사용자 및 시스템과 상호작용하는 미디어 재생 애플리케이션을 작성하는 방법을 보여줍니다.

참고: 오디오 데이터는 표준 출력 기기로만 재생할 수 있습니다. 현재 표준 출력 기기에는 휴대기기 스피커 또는 블루투스 헤드셋이 있습니다. 통화 중에는 대화 오디오에서 사운드 파일을 재생할 수 없습니다.

기본 사항

다음 클래스는 Android 프레임워크에서 사운드 및 동영상을 재생하는 데 사용됩니다.

MediaPlayer
이 클래스는 사운드 및 동영상을 재생하는 기본 API입니다.
AudioManager
이 클래스는 기기의 오디오 소스 및 오디오 출력을 관리합니다.

manifest 선언

MediaPlayer를 사용하여 애플리케이션 개발을 시작하기 전에 manifest에 적절한 선언이 있어서 관련 기능을 사용할 수 있는지 확인하세요.

  • 인터넷 권한 - MediaPlayer를 사용하여 네트워크 기반 콘텐츠를 스트리밍하는 경우 애플리케이션에서 네트워크 액세스를 요청해야 합니다.
        <uses-permission android:name="android.permission.INTERNET" />
        
  • Wake Lock 권한 - 플레이어 애플리케이션에서 화면이 어두워지는 것이나 프로세서의 절전 모드를 방지해야 한다면 또는 MediaPlayer.setScreenOnWhilePlaying()이나 MediaPlayer.setWakeMode() 메서드를 사용한다면 이 권한을 요청해야 합니다.
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        

MediaPlayer 사용

미디어 프레임워크의 가장 중요한 구성요소 중 하나는 MediaPlayer 클래스입니다. 이 클래스의 객체는 최소한의 설정으로 오디오와 동영상을 모두 가져오고 디코딩하며 재생할 수 있습니다. 다음과 같은 여러 미디어 소스를 지원합니다.

  • 로컬 리소스
  • 콘텐츠 리졸버에서 가져올 수 있는 것과 같은 내부 URI
  • 외부 URL(스트리밍)

Android에서 지원하는 미디어 형식 목록은 지원되는 미디어 형식 페이지를 참조하세요.

다음 예는 애플리케이션의 res/raw/ 디렉터리에 저장된 로컬 원시 리소스로 사용 가능한 오디오를 재생하는 방법을 보여줍니다.

Kotlin

    var mediaPlayer: MediaPlayer? = MediaPlayer.create(context, R.raw.sound_file_1)
    mediaPlayer?.start() // no need to call prepare(); create() does that for you
    

자바

    MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
    mediaPlayer.start(); // no need to call prepare(); create() does that for you
    

이 경우 '원시' 리소스는 시스템에서 특정 방법으로 파싱을 시도하지 않는 파일입니다. 그러나 이 리소스의 콘텐츠는 원시 오디오가 아니어야 합니다. 지원되는 형식 중 하나로 올바르게 인코딩되고 형식이 지정된 미디어 파일이어야 합니다.

다음은 시스템에서 로컬로 사용 가능한 URI에서 재생할 수 있는 방법(예: 콘텐츠 리졸버를 통해 획득)입니다.

Kotlin

    val myUri: Uri = .... // initialize Uri here
    val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
        setAudioStreamType(AudioManager.STREAM_MUSIC)
        setDataSource(applicationContext, myUri)
        prepare()
        start()
    }
    

자바

    Uri myUri = ....; // initialize Uri here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(getApplicationContext(), myUri);
    mediaPlayer.prepare();
    mediaPlayer.start();
    

HTTP 스트리밍을 통해 원격 URL에서 재생하는 방법은 다음과 같습니다.

Kotlin

    val url = "http://........" // your URL here
    val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
        setAudioStreamType(AudioManager.STREAM_MUSIC)
        setDataSource(url)
        prepare() // might take long! (for buffering, etc)
        start()
    }
    

자바

    String url = "http://........"; // your URL here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.prepare(); // might take long! (for buffering, etc)
    mediaPlayer.start();
    

참고: URL을 전달하여 온라인 미디어 파일을 스트리밍하는 경우 파일을 점진적으로 다운로드할 수 있어야 합니다.

주의: setDataSource()를 사용할 때 IllegalArgumentExceptionIOException을 포착하거나 전달해야 합니다. 참조하는 파일이 존재하지 않을 수 있기 때문입니다.

비동기 준비

MediaPlayer를 사용하는 것은 원칙적으로 간단할 수 있습니다. 그러나 일반 Android 애플리케이션과 올바르게 통합하려면 몇 가지가 더 필요하다는 것을 유의해야 합니다. 예를 들어 prepare() 호출은 실행하는 데 오랜 시간이 걸릴 수 있습니다. 미디어 데이터를 가져오고 디코딩해야 할 수 있기 때문입니다. 따라서 실행에 오랜 시간이 걸릴 수 있는 메서드의 경우와 마찬가지로 애플리케이션의 UI 스레드에서 호출해서는 안 됩니다. UI 스레드에서 호출하면 메서드가 반환될 때까지 UI가 중단되므로 매우 나쁜 사용자 환경이 되고 ANR(애플리케이션 응답 없음) 오류가 발생할 수 있습니다. 리소스가 빨리 로드될 것으로 예상하더라도 UI에서 응답에 10분의 1초 이상 걸리는 것이 있으면 눈에 띄는 일시중지가 발생하고 사용자에게 애플리케이션이 느리다는 인상을 줍니다.

UI 스레드가 중단되는 것을 방지하려면 다른 스레드를 생성하여 MediaPlayer를 준비하고 완료되면 기본 스레드에 알립니다. 그러나 스레딩 로직을 직접 작성할 수는 있지만 이 패턴은 MediaPlayer를 사용할 때 너무 일반적이므로 프레임워크에서 prepareAsync() 메서드를 사용하여 이 작업을 실행하는 편리한 방법을 제공합니다. 이 메서드는 백그라운드에서 미디어 준비를 시작하고 즉시 돌아옵니다. 미디어 준비가 완료되면 setOnPreparedListener()를 통해 구성된 MediaPlayer.OnPreparedListeneronPrepared() 메서드가 호출됩니다.

상태 관리

MediaPlayer의 또 다른 측면은 상태 기반이라는 점입니다. 즉, MediaPlayer에는 코드를 작성할 때 항상 알고 있어야 하는 내부 상태가 있습니다. 특정 작업은 플레이어가 특정 상태에 있을 때만 유효하기 때문입니다. 잘못된 상태에서 작업을 실행하면 시스템에서 예외나 다른 바람직하지 않은 동작이 발생할 수 있습니다.

MediaPlayer 클래스의 문서는 전체 상태 다이어그램을 보여주므로 어떤 메서드가 를 한 상태에서 다른 상태로 이동하는지 명확히 알 수 있습니다. 예를 들어 새로운 MediaPlayer를 만드는 경우 이것은 유휴 상태에 있습니다. 이 시점에서 setDataSource()를 호출하여 초기화하고 초기화 상태로 가져와야 합니다. 그리고 나서 prepare() 또는 prepareAsync() 메서드를 사용하여 준비해야 합니다. MediaPlayer의 준비가 완료되면 준비 상태로 들어갑니다. 즉, start()를 호출하여 미디어를 재생할 수 있습니다. 이 시점에서 다이어그램과 같이 start(), pause(), seekTo()와 같은 메서드를 호출하여 시작, 일시중지, PlaybackCompleted 상태 사이를 이동할 수 있습니다. 그러나 stop()을 호출하면 MediaPlayer를 다시 준비할 때까지 start()를 다시 호출할 수 없습니다.

MediaPlayer 객체와 상호작용하는 코드를 작성할 때 상태 다이어그램을 항상 기억하세요. 잘못된 상태에서 메서드를 호출하는 것이 버그가 발생하는 일반적인 원인이기 때문입니다.

MediaPlayer 해제

MediaPlayer는 귀중한 시스템 리소스를 소비할 수 있습니다. 따라서 항상 특히 주의를 기울여 필요 이상으로 오래 MediaPlayer 인스턴스를 유지하지 않는지 확인해야 합니다. 작업이 끝나면 항상 release()를 호출하여 할당된 시스템 리소스가 올바르게 해제되었는지 확인해야 합니다. 예를 들어 MediaPlayer를 사용 중이고 활동이 onStop() 호출을 수신하면 MediaPlayer를 해제해야 합니다. 활동이 사용자와 상호작용하지 않는 동안 유지하는 것은 합리적이지 않기 때문입니다. 단 다음 섹션에서 설명하는 백그라운드에서 미디어를 재생하는 경우는 예외입니다. 물론 활동이 재개되거나 다시 시작되면 새로운 MediaPlayer를 만들고 준비한 후 재생을 다시 시작해야 합니다.

다음은 MediaPlayer를 해제한 다음 무효화하는 방법입니다.

Kotlin

    mediaPlayer?.release()
    mediaPlayer = null
    

자바

    mediaPlayer.release();
    mediaPlayer = null;
    

예를 들어 활동이 중지될 때 MediaPlayer를 해제하지 않고 활동이 다시 시작될 때 새로운 MediaPlayer를 만든 경우 발생할 수 있는 문제를 생각해보세요. 사용자가 화면 방향을 변경하거나 기기 설정을 다른 방식으로 변경하면 시스템은 기본적으로 활동을 다시 시작하여 이를 처리하므로 사용자가 기기를 세로 모드와 가로 모드로 자주 회전하면 모든 시스템 리소스를 빠르게 소비할 수 있습니다. 방향을 변경할 때마다 해제하지 않는 새로운 MediaPlayer를 만들기 때문입니다. 런타임 다시 시작에 관한 자세한 내용은 런타임 변경 처리를 참조하세요.

사용자가 활동을 떠날 때에도 내장된 음악 애플리케이션이 동작하는 것과 동일한 방식으로 '백그라운드 미디어'를 계속 재생하려는 경우 어떤 일이 발생하는지 궁금할 수 있습니다. 이 경우 필요한 것은 서비스에서 제어하는 MediaPlayer입니다. 다음 섹션에서 설명합니다.

서비스에서 MediaPlayer 사용

애플리케이션이 화면에 없는 경우에도 백그라운드에서 미디어가 재생되게 하려면 즉, 사용자가 다른 앱과 상호작용하는 동안 계속 미디어가 재생되게 하려면 서비스를 시작하고 서비스에서 MediaPlayer 인스턴스를 제어해야 합니다. MediaBrowserServiceCompat 서비스에 MediaPlayer를 삽입하고 다른 활동에서 MediaBrowserCompat와 상호작용하게 해야 합니다.

이 클라이언트/서버 설정에 주의해야 합니다. 백그라운드 서비스에서 실행되는 플레이어가 나머지 시스템과 상호작용하는 데는 예상되는 방식이 있습니다. 애플리케이션이 이러한 예상을 충족하지 못하면 사용자 환경이 좋지 않을 수 있습니다. 자세한 내용은 오디오 앱 빌드를 참조하세요.

이 섹션에서는 서비스 내부에서 구현될 때 MediaPlayer를 관리하는 특수 안내를 설명합니다.

비동기 실행

우선 Activity와 같이 Service의 모든 작업은 기본적으로 단일 스레드에서 실행됩니다. 사실 동일한 애플리케이션에서 활동과 서비스를 실행하면 기본적으로 동일한 스레드('기본 스레드')를 사용합니다. 따라서 서비스는 들어오는 인텐트를 빠르게 처리하고 인텐트에 응답할 때 너무 긴 계산을 실행해서는 안 됩니다. 과도한 작업이나 호출 차단이 예상되는 경우에는 직접 구현한 다른 스레드에서 또는 프레임워크의 여러 비동기 처리 기능을 사용하여 이러한 작업을 비동기로 실행해야 합니다.

예를 들어 기본 스레드에서 MediaPlayer를 사용하는 경우 준비가 완료되고 재생을 시작할 수 있을 때 알림을 받으려면 prepare()보다는 prepareAsync()를 호출하고 MediaPlayer.OnPreparedListener를 구현해야 합니다. 예:

Kotlin

    private const val ACTION_PLAY: String = "com.example.action.PLAY"

    class MyService: Service(), MediaPlayer.OnPreparedListener {

        private var mMediaPlayer: MediaPlayer? = null

        override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
            ...
            val action: String = intent.action
            when(action) {
                ACTION_PLAY -> {
                    mMediaPlayer = ... // initialize it here
                    mMediaPlayer?.apply {
                        setOnPreparedListener(this@MyService)
                        prepareAsync() // prepare async to not block main thread
                    }

                }
            }
            ...
        }

        /** Called when MediaPlayer is ready */
        override fun onPrepared(mediaPlayer: MediaPlayer) {
            mediaPlayer.start()
        }
    }
    

자바

    public class MyService extends Service implements MediaPlayer.OnPreparedListener {
        private static final String ACTION_PLAY = "com.example.action.PLAY";
        MediaPlayer mediaPlayer = null;

        public int onStartCommand(Intent intent, int flags, int startId) {
            ...
            if (intent.getAction().equals(ACTION_PLAY)) {
                mediaPlayer = ... // initialize it here
                mediaPlayer.setOnPreparedListener(this);
                mediaPlayer.prepareAsync(); // prepare async to not block main thread
            }
        }

        /** Called when MediaPlayer is ready */
        public void onPrepared(MediaPlayer player) {
            player.start();
        }
    }
    

비동기 오류 처리

동기 작업에서는 오류가 일반적으로 예외 또는 오류 코드와 함께 표시되지만 비동기식 리소스를 사용할 때는 언제라도 애플리케이션이 적절하게 오류에 관한 알림을 받도록 해야 합니다. MediaPlayer의 경우 MediaPlayer.OnErrorListener를 구현하고 MediaPlayer 인스턴스에서 설정하여 이 작업을 실행할 수 있습니다.

Kotlin

    class MyService : Service(), MediaPlayer.OnErrorListener {

        private var mediaPlayer: MediaPlayer? = null

        fun initMediaPlayer() {
            // ...initialize the MediaPlayer here...
            mediaPlayer?.setOnErrorListener(this)
        }

        override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
            // ... react appropriately ...
            // The MediaPlayer has moved to the Error state, must be reset!
        }
    }
    

자바

    public class MyService extends Service implements MediaPlayer.OnErrorListener {
        MediaPlayer mediaPlayer;

        public void initMediaPlayer() {
            // ...initialize the MediaPlayer here...
            mediaPlayer.setOnErrorListener(this);
        }

        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            // ... react appropriately ...
            // The MediaPlayer has moved to the Error state, must be reset!
        }
    }
    

오류가 발생하면 MediaPlayer오류 상태로 이동(전체 상태 다이어그램은 클래스 문서 참조)하므로 재설정해야만 다시 사용할 수 있습니다.

wake lock 사용

백그라운드에서 미디어를 재생하는 애플리케이션을 디자인할 때 서비스가 실행되는 동안 기기가 절전 모드로 전환될 수 있습니다. 기기가 절전 모드일 때 Android 시스템은 배터리를 절약하려고 하므로 시스템은 CPU 및 Wi-Fi 하드웨어를 비롯하여 불필요한 휴대전화 기능을 모두 종료하려고 합니다. 그러나 서비스에서 음악을 재생하거나 스트리밍하고 있다면 시스템이 재생을 방해하는 것을 방지하는 것이 좋습니다.

이러한 조건에서 서비스를 계속 실행하려면 'wake lock'을 사용해야 합니다. wake lock은 애플리케이션이 휴대전화가 유휴 상태인 경우에도 계속 사용 가능해야 하는 기능을 이용하고 있다고 시스템에 알리는 방법입니다.

참고: wake lock은 항상 예외적인 경우에만 사용하고 반드시 필요할 때만 유지해야 합니다. 기기의 배터리 수명을 크게 줄이기 때문입니다.

MediaPlayer가 재생되는 동안 CPU가 계속 실행되도록 하려면 를 초기화할 때 setWakeMode() 메서드를 호출합니다. 호출하고 나면 MediaPlayer는 재생하는 동안 지정된 잠금을 유지하고 일지중지 또는 중지될 때 잠금을 해제합니다.

Kotlin

    mediaPlayer = MediaPlayer().apply {
        // ... other initialization here ...
        setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
    }
    

자바

    mediaPlayer = new MediaPlayer();
    // ... other initialization here ...
    mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    

그러나 이 예에서 습득한 wake lock은 CPU의 절전모드가 해제된 상태만을 보장합니다. 네트워크를 통해 미디어를 스트리밍하고 Wi-Fi를 사용하고 있다면 WifiLock도 유지하는 것이 좋을 수 있습니다. WifiLock은 수동으로 가져오고 해제해야 합니다. 원격 URL로 MediaPlayer 준비를 시작하면 Wi-Fi 잠금을 만들고 가져와야 합니다. 예:

Kotlin

    val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiLock: WifiManager.WifiLock =
        wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

    wifiLock.acquire()
    

자바

    WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
        .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

    wifiLock.acquire();
    

미디어를 일시중지 또는 중지하거나 네트워크가 더 이상 필요하지 않으면 잠금을 해제해야 합니다.

Kotlin

    wifiLock.release()
    

자바

    wifiLock.release();
    

정리 실행

앞에서 언급했듯이 MediaPlayer 객체는 상당량의 시스템 리소스를 소비할 수 있으므로 필요할 때만 유지하고 작업이 끝나면 release()를 호출해야 합니다. 시스템 가비지 컬렉션을 사용하기보다는 이 정리 메서드를 명시적으로 호출하는 것이 중요합니다. 가비지 컬렉터는 메모리 요구에만 민감하고 다른 미디어 관련 리소스의 부족에는 민감하지 않아서 MediaPlayer를 회수하는 데 시간이 걸리기 때문입니다. 따라서 서비스를 사용하는 경우에는 항상 onDestroy() 메서드를 재정의하여 MediaPlayer를 해제해야 합니다.

Kotlin

    class MyService : Service() {

        private var mediaPlayer: MediaPlayer? = null
        // ...

        override fun onDestroy() {
            super.onDestroy()
            mediaPlayer?.release()
        }
    }
    

자바

    public class MyService extends Service {
       MediaPlayer mediaPlayer;
       // ...

       @Override
       public void onDestroy() {
           super.onDestroy();
           if (mediaPlayer != null) mediaPlayer.release();
       }
    }
    

종료할 때 해제하는 것 외에 MediaPlayer를 해제하는 다른 기회를 항상 찾아야 합니다. 예를 들어 오랫동안 미디어를 재생할 수 없을 것으로 예상한다면(예: 오디오 포커스를 잃은 경우) 기존 MediaPlayer를 확실히 해제하고 나중에 다시 만들어야 합니다. 반면 아주 짧은 시간만 재생을 중지할 것으로 예상한다면 다시 만들고 준비하는 오버헤드를 방지하기 위해 MediaPlayer를 유지해야 할 수 있습니다.

디지털 권리 관리(DRM)

Android 8.0(API 레벨 26)부터 MediaPlayer에는 DRM으로 보호되는 자료의 재생을 지원하는 API가 포함됩니다. MediaDrm에서 제공되는 낮은 수준의 API와 유사하지만 더 높은 수준에서 작동하고 기본 추출기, DRM, 암호화 객체를 노출하지 않습니다.

MediaPlayer DRM API는 MediaDrm의 전체 기능을 제공하지는 않지만 가장 일반적인 사용 사례를 지원합니다. 현재 구현에서는 다음 콘텐츠 유형을 처리할 수 있습니다.

  • Widevine으로 보호되는 로컬 미디어 파일
  • Widevine으로 보호되는 원격/스트리밍 미디어 파일

다음 코드 스니펫은 간단한 동기 구현에서 새로운 DRM MediaPlayer 메서드를 사용하는 방법을 보여줍니다.

DRM 제어 미디어를 관리하려면 아래와 같이 일반적인 MediaPlayer 호출 흐름과 함께 새로운 메서드를 포함해야 합니다.

Kotlin

    mediaPlayer?.apply {
        setDataSource()
        setOnDrmConfigHelper() // optional, for custom configuration
        prepare()
        drmInfo?.also {
            prepareDrm()
            getKeyRequest()
            provideKeyResponse()
        }

        // MediaPlayer is now ready to use
        start()
        // ...play/pause/resume...
        stop()
        releaseDrm()
    }
    

자바

    setDataSource();
    setOnDrmConfigHelper(); // optional, for custom configuration
    prepare();
    if (getDrmInfo() != null) {
      prepareDrm();
      getKeyRequest();
      provideKeyResponse();
    }

    // MediaPlayer is now ready to use
    start();
    // ...play/pause/resume...
    stop();
    releaseDrm();
    

여느 때처럼 MediaPlayer 객체를 초기화하고 setDataSource()를 사용하여 소스를 설정하는 것으로 시작하세요. 그리고 나서 DRM을 사용하려면 다음 단계를 따르세요.

  1. 앱에서 맞춤 구성을 실행하려면 OnDrmConfigHelper 인터페이스를 정의하고 setOnDrmConfigHelper()를 사용하여 플레이어에 연결합니다.
  2. prepare()를 호출합니다.
  3. getDrmInfo()를 호출합니다. 소스에 DRM 콘텐츠가 있으면 메서드는 null이 아닌 MediaPlayer.DrmInfo 값을 반환합니다.

MediaPlayer.DrmInfo가 있으면 다음 단계를 따르세요.

  1. 사용 가능한 UUID의 지도를 검사하고 하나를 선택합니다.
  2. prepareDrm()을 호출하여 현재 소스의 DRM 구성을 준비합니다.
    • OnDrmConfigHelper 콜백을 만들고 등록했다면 prepareDrm()이 실행되는 동안 호출됩니다. 그러면 DRM 세션을 열기 전에 DRM 속성의 맞춤 구성을 실행할 수 있습니다. 콜백은 prepareDrm()을 호출한 스레드에서 동기적으로 호출됩니다. DRM 속성에 액세스하려면 getDrmPropertyString()setDrmPropertyString()을 호출합니다. 너무 긴 작업은 실행하지 마세요.
    • 기기가 아직 프로비저닝되지 않은 경우 prepareDrm()은 또한 프로비저닝 서버에 액세스하여 기기를 프로비저닝합니다. 네트워크 연결에 따라 이 작업에 걸리는 시간은 다를 수 있습니다.
  3. 불투명한 키 요청 바이트 배열을 가져와 라이선스 서버로 전송하려면 getKeyRequest()를 호출합니다.
  4. 라이선스 서버에서 수신한 키 응답을 DRM 엔진에 알리려면 provideKeyResponse()를 호출합니다. 결과는 키 요청 유형에 따라 달라집니다.
    • 응답이 오프라인 키 요청에 관한 것이면 결과는 키 세트 식별자입니다. 키 세트 식별자를 restoreKeys()와 함께 사용하여 키를 새로운 세션으로 복원할 수 있습니다.
    • 응답이 스트리밍 또는 해제 요청에 관한 것이면 결과는 null입니다.

비동기적으로 prepareDrm() 실행

기본적으로 prepareDrm()은 동기적으로 실행되어 준비가 완료될 때까지 차단됩니다. 그러나 새로운 기기에서 맨 첫 번째 DRM 준비에는 prepareDrm()에서 내부적으로 처리되는 프로비저닝이 필요할 수도 있으며 관련된 네트워크 작업으로 인해 완료되는 데 시간이 좀 걸릴 수 있습니다. MediaPlayer.OnDrmPreparedListener를 정의 및 설정하여 prepareDrm()에서 차단을 방지할 수 있습니다.

OnDrmPreparedListener를 설정하면 prepareDrm()이 백그라운드에서 프로비저닝(필요한 경우)과 준비를 실행합니다. 프로비저닝과 준비가 완료되면 리스너가 호출됩니다. 호출 시퀀스나 리스너가 실행되는 스레드에 관해 어떤 가정도 해서는 안 됩니다. 리스너가 핸들러 스레드에 등록된 경우는 예외입니다. 리스너는 prepareDrm()이 반환되기 전이나 후에 호출될 수 있습니다.

비동기적으로 DRM 설정

DRM 준비를 위한 MediaPlayer.OnDrmInfoListenerMediaPlayer.OnDrmPreparedListener를 만들고 등록하여 DRM을 비동기적으로 초기화해 플레이어를 시작할 수 있습니다. 다음과 같이 prepareAsync()와 함께 작동합니다.

Kotlin

    setOnPreparedListener()
    setOnDrmInfoListener()
    setDataSource()
    prepareAsync()
    // ...

    // If the data source content is protected you receive a call to the onDrmInfo() callback.
    override fun onDrmInfo(mediaPlayer: MediaPlayer, drmInfo: MediaPlayer.DrmInfo) {
        mediaPlayer.apply {
            prepareDrm()
            getKeyRequest()
            provideKeyResponse()
        }
    }

    // When prepareAsync() finishes, you receive a call to the onPrepared() callback.
    // If there is a DRM, onDrmInfo() sets it up before executing this callback,
    // so you can start the player.
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
    

자바

    setOnPreparedListener();
    setOnDrmInfoListener();
    setDataSource();
    prepareAsync();
    // ...

    // If the data source content is protected you receive a call to the onDrmInfo() callback.
    onDrmInfo() {
      prepareDrm();
      getKeyRequest();
      provideKeyResponse();
    }

    // When prepareAsync() finishes, you receive a call to the onPrepared() callback.
    // If there is a DRM, onDrmInfo() sets it up before executing this callback,
    // so you can start the player.
    onPrepared() {

    start();
    }
    

암호화된 미디어 처리

Android 8.0(API 레벨 26)부터 MediaPlayer는 기본 스트림 유형 H.264 및 AAC에 관해 Common Encryption Scheme(CENC) 및 HLS 샘플 레벨 암호화된 미디어(METHOD=SAMPLE-AES)를 복호화할 수도 있습니다. 전체 세그먼트 암호화된 미디어(METHOD=AES-128)는 이전에 지원되었습니다.

ContentResolver에서 미디어 검색

미디어 플레이어 애플리케이션에서 유용할 수 있는 또 다른 기능은 사용자가 기기에 가지고 있는 음악을 검색하는 기능입니다. 외부 미디어의 ContentResolver를 쿼리하여 검색할 수 있습니다.

Kotlin

    val resolver: ContentResolver = contentResolver
    val uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    val cursor: Cursor? = resolver.query(uri, null, null, null, null)
    when {
        cursor == null -> {
            // query failed, handle error.
        }
        !cursor.moveToFirst() -> {
            // no media on the device
        }
        else -> {
            val titleColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE)
            val idColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID)
            do {
                val thisId = cursor.getLong(idColumn)
                val thisTitle = cursor.getString(titleColumn)
                // ...process entry...
            } while (cursor.moveToNext())
        }
    }
    cursor?.close()
    

자바

    ContentResolver contentResolver = getContentResolver();
    Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    Cursor cursor = contentResolver.query(uri, null, null, null, null);
    if (cursor == null) {
        // query failed, handle error.
    } else if (!cursor.moveToFirst()) {
        // no media on the device
    } else {
        int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
        int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
        do {
           long thisId = cursor.getLong(idColumn);
           String thisTitle = cursor.getString(titleColumn);
           // ...process entry...
        } while (cursor.moveToNext());
    }
    

MediaPlayer로 이 기능을 사용하려면 다음을 실행하면 됩니다.

Kotlin

    val id: Long = /* retrieve it from somewhere */
    val contentUri: Uri =
        ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id )

    mediaPlayer = MediaPlayer().apply {
        setAudioStreamType(AudioManager.STREAM_MUSIC)
        setDataSource(applicationContext, contentUri)
    }

    // ...prepare and start...
    

자바

    long id = /* retrieve it from somewhere */;
    Uri contentUri = ContentUris.withAppendedId(
            android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

    mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(getApplicationContext(), contentUri);

    // ...prepare and start...
    

샘플 코드

BasicMediaDecoderDeviceOwner 샘플은 이 페이지에서 다룬 API 사용을 보여줍니다.

자세히 알아보기

이 페이지에서는 오디오와 동영상 녹음/녹화, 저장 및 재생과 관련된 주제를 다룹니다.