MediaPlayer 개요

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

이 문서에서는 우수한 성능과 쾌적한 사용자 환경을 얻기 위해 MediaPlayer를 사용하여 사용자 및 시스템과 상호작용하는 미디어 재생 애플리케이션을 작성하는 방법을 보여줍니다. 또는 MediaPlayer에서 사용할 수 없는 고성능 기능을 지원하는 맞춤설정 가능한 오픈소스 라이브러리인 ExoPlayer를 사용할 수도 있습니다.

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

기본사항

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

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

매니페스트 선언

MediaPlayer를 사용하여 애플리케이션 개발을 시작하기 전에 매니페스트에 관련 기능을 사용할 수 있는 적절한 선언이 있는지 확인합니다.

  • 인터넷 권한 - 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.create(context, R.raw.sound_file_1)
mediaPlayer.start() // no need to call prepare(); create() does that for you

Java

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().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(applicationContext, myUri)
    prepare()
    start()
}

Java

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

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

Kotlin

val url = "http://........" // your URL here
val mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(url)
    prepare() // might take long! (for buffering, etc)
    start()
}

Java

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

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

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

비동기 준비

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

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

상태 관리

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

MediaPlayer 클래스의 문서는 어떤 메서드가 MediaPlayer를 한 상태에서 다른 상태로 이동하는지 명확히 보여주는 전체 상태 다이어그램을 보여줍니다. 예를 들어 새 MediaPlayer를 만들면 유휴 상태입니다. 이때 setDataSource()를 호출하여 초기화하고 초기화 상태로 가져와야 합니다. 그런 다음 prepare() 또는 prepareAsync() 메서드를 사용하여 준비해야 합니다. MediaPlayer 준비가 완료되면 Prepared 상태로 전환됩니다. 즉, start()를 호출하여 미디어를 재생할 수 있습니다. 이 시점에서 다이어그램에 나와 있는 것처럼 start(), pause(), seekTo() 등의 메서드를 호출하여 Started, Pause, PlaybackCompleted 상태 간에 이동할 수 있습니다. 그러나 stop()를 호출하면 MediaPlayer를 다시 준비할 때까지 start()를 다시 호출할 수 없습니다.

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

MediaPlayer 해제

MediaPlayer는 소중한 시스템 리소스를 소비할 수 있습니다. 따라서 항상 추가 예방 조치를 통해 필요 이상으로 오래 MediaPlayer 인스턴스를 유지하지 않도록 해야 합니다. 작업을 마쳤으면 항상 release()를 호출하여 할당된 시스템 리소스가 제대로 해제되었는지 확인해야 합니다. 예를 들어 MediaPlayer를 사용 중이고 활동이 onStop() 호출을 수신하는 경우 MediaPlayer를 해제해야 합니다. 활동이 사용자와 상호작용하지 않는 동안 (다음 섹션에서 설명하는 것처럼 백그라운드에서 미디어를 재생하는 경우 제외) 계속 유지하는 것이 합리적이기 때문입니다. 물론 활동이 재개되거나 다시 시작되면 새 MediaPlayer를 만들고 다시 준비한 후 재생을 재개해야 합니다.

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

Kotlin

mediaPlayer?.release()
mediaPlayer = null

Java

mediaPlayer.release();
mediaPlayer = null;

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

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

서비스에서 MediaPlayer 사용

애플리케이션이 화면에 표시되지 않을 때도 백그라운드에서 미디어를 재생하려는 경우(즉, 사용자가 다른 애플리케이션과 상호작용하는 동안 미디어가 계속 재생되도록 하려면) 서비스를 시작하고 거기에서 MediaPlayer 인스턴스를 제어해야 합니다. MediaPlayer를 MediaBrowserServiceCompat 서비스에 삽입하고 다른 활동의 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()
    }
}

Java

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

Java

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

오류가 발생하면 MediaPlayerError 상태로 전환되며 (전체 상태 다이어그램은 MediaPlayer 클래스 문서 참고) 다시 사용하려면 먼저 재설정해야 합니다.

wake lock 사용

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

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

참고: wake lock은 항상 드물게 사용하고 실제로 필요한 기간 동안만 유지해야 합니다. 기기의 배터리 수명이 크게 줄어들기 때문입니다.

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

Kotlin

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

Java

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

그러나 이 예에서 습득한 wake lock은 CPU의 절전모드가 해제된 상태만을 보장합니다. 네트워크를 통해 미디어를 스트리밍하고 Wi-Fi를 사용하는 경우 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()

Java

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

wifiLock.acquire();

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

Kotlin

wifiLock.release()

Java

wifiLock.release();

정리 실행

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

Kotlin

class MyService : Service() {

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

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

Java

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

Java

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.OnDrmInfoListener와 플레이어를 시작하는 MediaPlayer.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()
}

Java

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의 경우 공통 암호화 체계 (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()

Java

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 {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    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.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...

자세히 알아보기

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