백그라운드에서 미디어 재생

애플리케이션이 화면에 표시되지 않는 경우에도 백그라운드에서 미디어를 재생할 수 있습니다(예: 사용자가 다른 애플리케이션과 상호작용하는 동안).

이렇게 하려면 MediaPlayer를 MediaBrowserServiceCompat 서비스에 삽입하고 다른 활동에서 MediaBrowserCompat와 상호작용하게 합니다.

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

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

비동기 실행

Activity와 마찬가지로 Service의 모든 작업은 기본적으로 단일 스레드에서 실행됩니다. 실제로 동일한 애플리케이션에서 활동과 서비스를 실행하면 기본적으로 동일한 스레드('기본 스레드')를 사용합니다.

서비스는 들어오는 인텐트를 빠르게 처리하고 인텐트에 응답할 때 너무 긴 계산을 실행해서는 안 됩니다. 과도한 작업이나 호출 차단은 직접 구현한 다른 스레드에서 또는 프레임워크의 여러 비동기 처리 기능을 사용하여 비동기로 실행해야 합니다.

예를 들어 기본 스레드에서 MediaPlayer를 사용하는 경우 다음을 실행해야 합니다.

예를 들면 다음과 같습니다.

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오류 상태로 이동합니다. 재사용하려면 재설정해야 합니다. 자세한 내용은 MediaPlayer 클래스의 전체 상태 다이어그램을 참고하세요.

wake lock 사용

백그라운드에서 음악을 재생하거나 스트리밍할 때는 기기를 절전 모드로 전환하는 등 시스템이 재생을 방해하지 못하도록 하기 위해 wake lock을 사용해야 합니다.

wake lock은 애플리케이션이 휴대전화가 유휴 상태인 경우에도 계속 사용 가능해야 하는 기능을 이용하고 있다고 시스템에 알리는 신호입니다.

MediaPlayer가 재생되는 동안 CPU가 계속 실행되도록 하려면 MediaPlayer를 초기화할 때 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를 유지해야 할 수 있습니다.

자세히 알아보기

Jetpack Media3는 앱에서 미디어를 재생하는 데 권장되는 솔루션입니다. 자세한 내용은 자세히 알아보세요.

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