오디오 포커스 관리

두 개 이상의 Android 앱이 동일한 출력 스트림으로 오디오를 동시에 재생할 수 있으며, 시스템은 모든 것을 함께 믹싱합니다. 이는 기술적으로는 인상적이지만 사용자에게는 매우 악화될 수 있습니다. 모든 음악 앱이 동시에 재생되지 않도록 Android에서는 오디오 포커스라는 아이디어를 도입합니다. 한 번에 하나의 앱만 오디오 포커스를 유지할 수 있습니다.

앱이 오디오를 출력해야 하는 경우 오디오 포커스를 요청해야 합니다. 포커스가 있으면 소리를 재생할 수 있습니다. 그러나 오디오 포커스를 획득한 후에는 재생을 완료할 때까지 오디오 포커스를 유지하지 못할 수 있습니다. 다른 앱에서 포커스를 요청할 수 있으며 이 경우 오디오 포커스의 보유 상태를 선점합니다. 이 경우 앱에서 재생을 일시중지하거나 볼륨을 낮춰 사용자가 새 오디오 소스를 더 쉽게 들을 수 있도록 해야 합니다.

Android 12 (API 수준 31) 이전에는 오디오 포커스가 시스템에서 관리되지 않습니다. 따라서 앱 개발자는 오디오 포커스 가이드라인을 준수하는 것이 좋지만 Android 11 (API 수준 30) 이하를 실행하는 기기에서 오디오 포커스가 손실된 후에도 앱이 계속해서 큰 소리로 재생된다면 시스템에서 이를 방지할 수 없습니다. 그러나 이 앱 동작은 사용자 환경을 악화시키고 사용자가 오작동하는 앱을 제거하게 만드는 경우가 많습니다.

잘 디자인된 오디오 앱은 다음과 같은 일반 가이드라인에 따라 오디오 포커스를 관리해야 합니다.

  • 재생을 시작하기 직전에 requestAudioFocus()를 호출하여 호출이 AUDIOFOCUS_REQUEST_GRANTED를 반환하는지 확인합니다. 미디어 세션의 onPlay() 콜백에서 requestAudioFocus()를 호출합니다.

  • 다른 앱이 오디오 포커스를 받으면 재생을 중지 또는 일시중지하거나 볼륨을 낮춥니다.

  • 재생이 중지되면 (예: 앱에 재생할 항목이 없을 때) 오디오 포커스를 포기합니다. 사용자가 재생을 일시중지하지만 나중에 재생을 다시 시작할 수도 있는 경우 앱은 오디오 포커스를 포기할 필요가 없습니다.

  • AudioAttributes를 사용하여 앱이 재생하는 오디오 유형을 설명합니다. 예를 들어 음성을 재생하는 앱의 경우 CONTENT_TYPE_SPEECH를 지정합니다.

오디오 포커스는 실행 중인 Android 버전에 따라 다르게 처리됩니다.

Android 12 (API 수준 31) 이상
오디오 포커스는 시스템에서 관리합니다. 시스템은 다른 앱이 오디오 포커스를 요청하면 특정 앱의 오디오 재생이 페이드아웃되도록 강제합니다. 또한 수신 전화가 수신되면 시스템은 오디오 재생을 음소거합니다.
Android 8.0 (API 수준 26)~Android 11 (API 수준 30)
오디오 포커스는 시스템에서 관리되지 않지만, Android 8.0 (API 수준 26)부터 도입된 일부 변경사항을 포함합니다.
Android 7.1 (API 수준 25) 이하
오디오 포커스는 시스템에서 관리되지 않으며 앱은 requestAudioFocus()abandonAudioFocus()를 사용하여 오디오 포커스를 관리합니다.

Android 12 이상의 오디오 포커스

오디오 포커스를 사용하는 미디어 앱이나 게임 앱은 포커스가 손실된 후 오디오를 재생해서는 안 됩니다. Android 12 (API 수준 31) 이상에서는 시스템에서 이 동작을 적용합니다. 다른 앱이 포커스를 보유하여 재생되는 동안 앱이 오디오 포커스를 요청하면 시스템은 재생 중인 앱을 강제로 페이드 아웃합니다. 페이드 아웃을 추가하면 한 앱에서 다른 앱으로 이동할 때 더 부드러운 전환이 가능합니다.

이 페이드 아웃 동작은 다음 조건이 충족될 때 발생합니다.

  1. 현재 재생 중인 첫 번째 앱은 다음 기준을 모두 충족합니다.

  2. 두 번째 앱은 AudioManager.AUDIOFOCUS_GAIN를 사용하여 오디오 포커스를 요청합니다.

이러한 조건이 충족되면 오디오 시스템은 첫 번째 앱을 페이드 아웃합니다. 페이드 아웃이 끝나면 시스템은 첫 번째 앱에 포커스 손실을 알립니다. 앱 플레이어는 앱이 오디오 포커스를 다시 요청할 때까지 음소거 상태로 유지됩니다.

기존 오디오 포커스 동작

오디오 포커스 전환과 관련된 다음과 같은 기타 사례도 알아야 합니다.

자동 볼륨 낮추기

자동 볼륨 낮추기는 Android 8.0(API 수준 26)에서 도입되었습니다.

시스템에서 볼륨 낮추기를 구현하도록 하면 앱에서 볼륨 낮추기를 구현하지 않아도 됩니다.

자동 볼륨 낮추기는 오디오 알림이 재생 중인 앱에서 포커스를 가져올 때도 발생합니다. 알림 재생의 시작은 볼륨 낮추기 램프의 끝과 동기화됩니다.

자동 볼륨 낮추기는 다음 조건이 충족될 때 발생합니다.

  1. 현재 재생 중인 첫 번째 앱은 다음 기준을 모두 충족합니다.

  2. 두 번째 앱은 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 사용하여 오디오 포커스를 요청합니다.

이러한 조건이 충족되면 오디오 시스템은 두 번째 앱에 포커스가 있는 동안 첫 번째 앱의 모든 활성 플레이어의 볼륨을 낮춥니다. 두 번째 앱이 포커스를 포기하면 포커스를 되돌립니다. 첫 번째 앱은 포커스를 잃을 때 알림을 받지 않으므로 아무 작업도 할 필요가 없습니다.

사용자가 음성 콘텐츠를 들을 때는 자동 볼륨 낮추기가 실행되지 않습니다. 사용자가 일부 프로그램을 놓칠 수 있기 때문입니다. 예를 들어 운전 경로에 대한 음성 안내는 볼륨 낮추지 않습니다.

수신 전화의 현재 오디오 재생 음소거

일부 앱은 전화 통화 중에 제대로 작동하지 않고 계속 오디오가 재생됩니다. 이 상황에서는 통화를 들으려면 문제가 되는 앱을 찾아 음소거하거나 종료해야 합니다. 이를 방지하기 위해 시스템은 수신 전화가 있는 동안 다른 앱의 오디오를 음소거할 수 있습니다. 수신 전화가 수신되고 앱이 다음 조건을 충족하면 시스템이 이 기능을 호출합니다.

  • 앱에 AudioAttributes.USAGE_MEDIA 또는 AudioAttributes.USAGE_GAME 사용 속성이 있습니다.
  • 앱이 오디오 포커스 (포커스 게인)를 성공적으로 요청했으며 오디오를 재생합니다.

통화 중에 앱이 계속 재생되면 통화가 끝날 때까지 재생이 음소거됩니다. 그러나 통화 중에 앱이 재생을 시작하면 해당 플레이어는 사용자가 의도적으로 재생을 시작했다는 가정하에 음소거되지 않습니다.

Android 8.0~Android 11의 오디오 포커스

Android 8.0 (API 수준 26)부터는 requestAudioFocus()를 호출할 때 AudioFocusRequest 매개변수를 제공해야 합니다. AudioFocusRequest에는 앱의 오디오 컨텍스트와 기능에 관한 정보가 포함되어 있습니다. 시스템은 이 정보를 사용하여 오디오 포커스의 획득 및 손실을 자동으로 관리합니다. 오디오 포커스를 해제하려면 AudioFocusRequest도 인수로 사용하는 abandonAudioFocusRequest() 메서드를 호출합니다. 포커스를 요청하고 포기할 때 동일한 AudioFocusRequest 인스턴스를 사용하세요.

AudioFocusRequest를 만들려면 AudioFocusRequest.Builder를 사용하세요. 포커스 요청은 항상 요청 유형을 지정해야 하므로 유형은 빌더의 생성자에 포함됩니다. 요청의 다른 필드를 설정하려면 빌더의 메서드를 사용합니다.

FocusGain 필드는 필수사항이고 다른 필드는 모두 선택사항입니다.

메서드Notes
setFocusGain() 이 필드는 모든 요청에서 필수사항입니다. 이 값은 Android 8.0 이전 requestAudioFocus() 호출에 사용된 durationHint와 동일한 값(AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 또는 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)을 사용합니다.
setAudioAttributes() AudioAttributes은 앱의 사용 사례를 설명합니다. 시스템은 앱이 오디오 포커스를 얻거나 잃을 때 이를 확인합니다. 속성은 스트림 유형의 개념을 대체합니다. Android 8.0 (API 수준 26) 이상에서 볼륨 조절 이외의 작업의 스트림 유형은 지원 중단됩니다. 이 표 다음에 나오는 예와 같이 오디오 플레이어에서 사용하는 포커스 요청에서 동일한 속성을 사용합니다.

AudioAttributes.Builder를 사용하여 먼저 속성을 지정한 후 이 메서드를 사용하여 요청에 속성을 할당합니다.

지정하지 않으면 AudioAttributes의 기본값은 AudioAttributes.USAGE_MEDIA입니다.

setWillPauseWhenDucked() 다른 앱이 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK로 포커스를 요청하면 포커스가 있는 앱은 일반적으로 onAudioFocusChange() 콜백을 수신하지 않습니다. 시스템이 자체적으로 볼륨 낮추기를 실행할 수 있기 때문입니다. 볼륨을 낮추는 대신 재생을 일시중지해야 한다면 자동 볼륨 낮추기에 설명된 대로 setWillPauseWhenDucked(true)를 호출하고 OnAudioFocusChangeListener를 만들고 설정합니다.
setAcceptsDelayedFocusGain() 다른 앱에서 포커스를 잠그면 오디오 포커스 요청이 실패할 수 있습니다. 이 메서드는 사용할 수 있을 때 비동기식으로 포커스를 획득하는 기능인 포커스 획득 지연을 사용 설정합니다.

오디오 요청에서 AudioManager.OnAudioFocusChangeListener를 지정한 경우에만 지연된 포커스 획득이 작동합니다. 포커스가 부여되었음을 파악하려면 앱이 콜백을 수신해야 하기 때문입니다.

setOnAudioFocusChangeListener() OnAudioFocusChangeListener는 요청에서 willPauseWhenDucked(true) 또는 setAcceptsDelayedFocusGain(true)도 지정하는 경우에만 필요합니다.

리스너를 설정하는 메서드는 두 가지가 있습니다. 하나는 핸들러 인수가 없는 메서드이고 다른 하나는 핸들러 인수가 없는 메서드입니다. 핸들러는 리스너가 실행되는 스레드입니다. 핸들러를 지정하지 않으면 기본 Looper와 연결된 핸들러가 사용됩니다.

다음 예는 AudioFocusRequest.Builder를 사용하여 AudioFocusRequest를 빌드하고 오디오 포커스를 요청하고 포기하는 방법을 보여줍니다.

Kotlin

// initializing variables for audio focus and playback management
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// requesting audio focus and processing the response
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying()
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... pausing or ducking depends on your app
        }
    }
}

Java

// initializing variables for audio focus and playback management
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(playbackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, handler)
        .build();
final Object focusLock = new Object();

boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;

// requesting audio focus and processing the response
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        playbackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        playbackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
        playbackDelayed = true;
        playbackNowAuthorized = false;
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false;
                    resumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(focusLock) {
                resumeOnFocusGain = false;
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying();
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

자동 볼륨 낮추기

Android 8.0 (API 수준 26)에서 다른 앱이 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK로 포커스를 요청하면 시스템은 앱의 onAudioFocusChange() 콜백을 호출하지 않고도 볼륨을 낮추고 복원할 수 있습니다.

자동 볼륨 낮추기는 음악 및 동영상 재생 앱에서는 허용되는 동작이지만 오디오북 앱과 같이 음성 콘텐츠를 재생할 때는 유용하지 않습니다. 이 경우에는 대신 앱을 일시중지해야 합니다.

볼륨 낮추기를 요청했을 때 볼륨을 낮추는 대신 앱이 일시중지되도록 하려면 원하는 일시중지/재개 동작을 구현하는 onAudioFocusChange() 콜백 메서드로 OnAudioFocusChangeListener를 만듭니다. setOnAudioFocusChangeListener()를 호출하여 리스너를 등록하고 setWillPauseWhenDucked(true)를 호출하여 자동 볼륨 낮추기를 실행하는 대신 콜백을 사용하도록 시스템에 알립니다.

포커스 획득 지연

통화 도중과 같이 다른 앱에 의해 포커스가 '잠겨' 있어서 시스템이 오디오 포커스 요청을 허용할 수 없는 경우가 있습니다. 이 경우 requestAudioFocus()AUDIOFOCUS_REQUEST_FAILED를 반환합니다. 이 경우 앱은 포커스를 얻지 못했으므로 오디오 재생을 진행해서는 안 됩니다.

setAcceptsDelayedFocusGain(true) 메서드를 사용하면 앱에서 포커스 요청을 비동기식으로 처리할 수 있습니다. 이 플래그를 설정하면 포커스가 잠겨 있을 때 이루어진 요청이 AUDIOFOCUS_REQUEST_DELAYED를 반환합니다. 전화 통화가 종료되는 경우와 같이 오디오 포커스를 잠그는 조건이 더 이상 존재하지 않으면 시스템은 대기 중인 포커스 요청을 부여하고 onAudioFocusChange()를 호출하여 앱에 알립니다.

지연된 포커스 획득을 처리하려면 원하는 동작을 구현하는 onAudioFocusChange() 콜백 메서드로 OnAudioFocusChangeListener를 만들고 setOnAudioFocusChangeListener()를 호출하여 리스너를 등록해야 합니다.

Android 7.1 이하의 오디오 포커스

requestAudioFocus()를 호출할 때 지속 시간 힌트를 지정해야 합니다. 이 힌트는 현재 포커스를 보유하고 재생 중인 다른 앱에서 적용될 수 있습니다.

  • 가까운 장래에 오디오를 재생할 계획 (예: 음악 재생 시)이고 이전 오디오 포커스 소유자가 재생을 중지할 것으로 예상되면 영구 오디오 포커스 (AUDIOFOCUS_GAIN)를 요청합니다.
  • 짧은 시간 동안만 오디오를 재생할 것으로 예상되고 이전 보유자가 재생을 일시중지할 것으로 예상되는 경우 일시적인 포커스 (AUDIOFOCUS_GAIN_TRANSIENT)를 요청합니다.
  • 짧은 시간 동안만 오디오를 재생할 것으로 예상되며 이전 포커스 소유자가 오디오 출력을 '낮추는'(낮춰) 계속 재생해도 괜찮다는 것을 나타내려면 볼륨 낮추기(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)를 사용하여 일시적인 포커스를 요청합니다. 두 오디오 출력 모두 오디오 스트림에 혼합됩니다. 볼륨 낮추기는 들을 수 있는 운전 경로 등 간헐적으로 오디오 스트림을 사용하는 앱에 특히 적합합니다.

requestAudioFocus() 메서드에는 AudioManager.OnAudioFocusChangeListener도 필요합니다. 이 리스너는 미디어 세션을 소유한 동일한 활동 또는 서비스에서 만들어야 합니다. 이 메서드는 다른 앱이 오디오 포커스를 획득하거나 포기할 때 앱이 수신하는 콜백 onAudioFocusChange()를 구현합니다.

다음 스니펫은 STREAM_MUSIC 스트림에 영구 오디오 포커스를 요청하고 OnAudioFocusChangeListener를 등록하여 오디오 포커스의 후속 변경사항을 처리합니다. 변경 리스너는 오디오 포커스 변경에 응답에서 설명합니다.

Kotlin

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener

...
// Request audio focus for playback
val result: Int = audioManager.requestAudioFocus(
        afChangeListener,
        // Use the music stream.
        AudioManager.STREAM_MUSIC,
        // Request permanent focus.
        AudioManager.AUDIOFOCUS_GAIN
)

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Java

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
                             // Use the music stream.
                             AudioManager.STREAM_MUSIC,
                             // Request permanent focus.
                             AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

재생이 끝나면 abandonAudioFocus()을 호출합니다.

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

이렇게 하면 포커스가 더 이상 필요하지 않음을 시스템에 알리고 연결된 OnAudioFocusChangeListener의 등록을 취소합니다. 일시적인 포커스를 요청한 경우 일시중지되거나 볼륨을 낮춘 앱에 계속 재생하거나 볼륨을 복원할 수 있다고 알립니다.

오디오 포커스 변경에 응답

앱이 오디오 포커스를 획득하면 다른 앱이 직접 오디오 포커스를 요청할 때 이를 해제할 수 있어야 합니다. 이 경우 앱은 requestAudioFocus()를 호출할 때 지정된 AudioFocusChangeListeneronAudioFocusChange() 메서드 호출을 수신합니다.

onAudioFocusChange()에 전달된 focusChange 매개변수는 진행 중인 변경의 종류를 나타냅니다. 이는 포커스를 획득하는 앱에서 사용하는 지속 시간 힌트에 상응합니다. 앱은 적절하게 응답해야 합니다.

일시적인 포커스 손실
포커스 변경이 일시적 (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 또는 AUDIOFOCUS_LOSS_TRANSIENT)인 경우 앱은 볼륨을 낮추거나 (자동 볼륨 낮추기를 사용하지 않는 경우) 재생을 일시중지하지만 다른 방식으로는 동일한 상태를 유지해야 합니다.

오디오 포커스가 일시적으로 손실된 동안 오디오 포커스의 변화를 계속 모니터링하고 포커스를 다시 얻을 때 일반 재생을 재개할 준비를 해야 합니다. 차단 앱이 포커스를 포기하면 콜백(AUDIOFOCUS_GAIN)을 수신합니다. 이 시점에서 볼륨을 일반 수준으로 복원하거나 재생을 다시 시작할 수 있습니다.

영구적인 포커스 손실
오디오 포커스 손실이 영구적 (AUDIOFOCUS_LOSS)이면 다른 앱이 오디오를 재생하고 있는 것입니다. 앱은 AUDIOFOCUS_GAIN 콜백을 수신하지 않으므로 즉시 재생을 일시중지해야 합니다. 재생을 다시 시작하려면 사용자가 알림이나 앱 UI에서 재생 전송 컨트롤을 누르는 등 명시적인 작업을 실행해야 합니다.

다음 코드 스니펫은 OnAudioFocusChangeListeneronAudioFocusChange() 콜백을 구현하는 방법을 보여줍니다. Handler를 사용하여 오디오 포커스가 영구적으로 손실될 때 중지 콜백을 지연합니다.

Kotlin

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            // Permanent loss of audio focus
            // Pause playback immediately
            mediaController.transportControls.pause()
            // Wait 30 seconds before stopping playback
            handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            // Pause playback
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // Lower the volume, keep playing
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            // Your app has been granted audio focus again
            // Raise volume to normal, restart playback if necessary
        }
    }
}

Java

private Handler handler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        handler.postDelayed(delayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };

핸들러는 다음과 같은 Runnable을 사용합니다.

Kotlin

private var delayedStopRunnable = Runnable {
    mediaController.transportControls.stop()
}

Java

private Runnable delayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};

사용자가 재생을 다시 시작할 때 지연된 중지가 시작되지 않도록 하려면 상태 변경에 대한 응답으로 mHandler.removeCallbacks(mDelayedStopRunnable)를 호출합니다. 예를 들어 콜백의 onPlay(), onSkipToNext() 등에서 removeCallbacks()를 호출합니다. 서비스에서 사용하는 리소스를 삭제할 때 서비스의 onDestroy() 콜백에서도 이 메서드를 호출해야 합니다.