원문: Respecting Audio Focus
발표 동안에 떠드는 것은 무례합니다. 발표자에게 무례하고 관객을 짜증나게 하죠. 만약 애플리케이션이 오디오 포커스에 관한 규율을 존중하지 않으면 다른 애플리케이션을 무시하고 사용자를 짜증나게 하는 것입니다. 만약 오디오 포커스에 대해 들어본 적이 없다면 안드로이드 개발자 트레이닝 내용을 볼 필요가 있습니다.
만약 여러 앱에서 음악을 재생한다면 이들이 어떻게 상호작용할지 생각하는 것이 중요합니다. 모든 음악 앱들이 동시에 재생하는 것을 막기 위해 안드로이드는 재생을 조정하는 오디오 포커스를 사용합니다. 앱은 오디오 포커스를 가질 때만 음악을 재생해야 합니다. 이 글은 사용자에게 가능한 좋은 경험을 보장하기 위해 오디오 포커스의 변화를 적절히 다루는 몇가지 팁을 제공합니다.
오디오 포커스 요구하기
앱이 시작할 때 오디오 포커스를 요청해서는 안됩니다. (욕심내지 마세요.) 대신 애플리케이션이 오디오 스트림으로 무언가 할 때까지 요청을 미루세요. 애플리케이션은 원하는 포커스 수준을 지정할 수 있는 AUDIOFOCUS_GAIN* 상수 (표 1 참고)를 AudioManager 시스템 서비스에서 사용할 수 있습니다.
목록 1. 오디오 포커스 요청하기
1. AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 2. 3. int result = am.requestAudioFocus(mOnAudioFocusChangeListener, 4. // Hint: the music stream. 5. AudioManager.STREAM_MUSIC, 6. // Request permanent focus. 7. AudioManager.AUDIOFOCUS_GAIN); 8. if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 9. mState.audioFocusGranted = true; 10. } else if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 11. mState.audioFocusGranted = false; 12. }
7번째 줄 위에서 영속적인 오디오 포커스를 요청한 걸 볼 수 있습니다. 45초 미만의 오디오 포커스를 이용할 때는 AUDIOFOCUS_GAIN_TRANSIENT를 대신 사용하여 일시적인 포커스를 요청할 수 있습니다.
현재 오디오를 재생하는 다른 애플리케이션과 오디오 시스템을 공유하는데 적합한 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 대신 사용할 수도 있습니다. (예, 피트니스 애플리케이션에서 "계속 하라"는 메시지가 재생되며 그 동안 배경음악을 잠시 줄이고 싶을 때.) AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK를 호출하는 앱은 15초 이상 오디오 시스템의 포커스를 잡아선 안됩니다. (주: 더킹(ducking)은 다른 오디오 출력 크기를 잠시 작게 변경하여 같이 재생을 하는 것을 의미하는 말입니다.)
오디오 포커스 변화 다루기
오디오 포커스 변경 이벤트를 다루기 위해 애플리케이션은 OnAudioFocusChangeListener의 인스턴스를 생성해야 합니다. 이 리스너에서 애플리케이션은 theAUDIOFOCUS_GAIN* 이벤트와 AUDIOFOCUS_LOSS* 이벤트 (표 1)을 다룰 필요가 있습니다. AUDIOFOCUS_GAIN는 몇가지 미묘한 차이들을 가지는 데,아래의 목록 2에 강조된 것을 참고하세요.
목록 2. 오디오 포커스 변화 다루기
1. mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { 2. 3. @Override 4. public void onAudioFocusChange(int focusChange) { 5. switch (focusChange) { 6. case AudioManager.AUDIOFOCUS_GAIN: 7. mState.audioFocusGranted = true; 8. 9. if(mState.released) { 10. initializeMediaPlayer(); 11. } 12.13. switch(mState.lastKnownAudioFocusState) { 14. case UNKNOWN: 15. if(mState.state == PlayState.PLAY && !mPlayer.isPlaying()) { 16. mPlayer.start(); 17. } 18. break; 19. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 20. if(mState.wasPlayingWhenTransientLoss) { 21. mPlayer.start(); 22. } 23. break; 24. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 25. restoreVolume(); 26. break; 27. } 28.29. break; 30. case AudioManager.AUDIOFOCUS_LOSS: 31. mState.userInitiatedState = false; 32. mState.audioFocusGranted = false; 33. teardown(); 34. break; 35. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 36. mState.userInitiatedState = false; 37. mState.audioFocusGranted = false; 38. mState.wasPlayingWhenTransientLoss = mPlayer.isPlaying(); 39. mPlayer.pause(); 40. break; 41. case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 42. mState.userInitiatedState = false; 43. mState.audioFocusGranted = false; 44. lowerVolume(); 45. break; 46. } 47. mState.lastKnownAudioFocusState = focusChange; 48. } 49.};
AUDIOFOCUS_GAIN는 애플리케이션 코드의 두가지 분명한 영역에서 사용됩니다. 첫째로, 목록 1에서 보여진 것 처럼 오디오 포커스를 등록할 때 사용할 수 있습니다. 이것은 등록된 OnAudioFocusChangeListener를 위한 이벤트로 변환되지는 않습니다. 성공적인 오디오 포커스 요청이 될때 리스너는 등록에 쓰였던 AUDIOFOCUS_GAIN 이벤트를 얻지는 못한다는 거죠.
AUDIOFOCUS_GAIN은 또 OnAudioFocusChangeListener의 구현 내의 이벤트 상태로 쓰입니다. 위에 언급된 것 처럼, AUDIOFOCUS_GAIN 이벤트는 오디오 포커스 요청에 의해 작동되지는 않습니다. 대신 AUDIOFOCUS_LOSS* 이벤트가 나타난 이후에 AUDIOFOCUS_GAIN가 발생합니다. 표 1에 보이는 것 중 양쪽 모두(주: 등록할 때, 이벤트의 상태)에서 사용되는 상수는 AUDIOFOCUS_GAIN 밖에 없습니다.
포커스 변경 리스너를 제어할 네 가지 경우가 있습니다. 애플리케이션이 AUDIOFOCUS_LOSS를 받는 것은 일반적으로 포커스를 다시 받지 않을 것을 의미합니다. 이 경우 앱은 오디오 시스템에 관련된 에셋을 해제하고 재생을 멈춰야 합니다. 예를 들어 사용자가 음악을 앱을 사용하여 재생한 후, 음악 앱에서 오디오 포커스를 가져갈 게임을 수행한다고 상상해보세요. 사용자가 게임을 언제 종료할 지 예상할 수도 없습니다. 아마도 사용자는 홈 런쳐로 갈 수 있습니다 (게임을 백그라운드 모드로 내버려두고요.) 그리고 아예 다른 앱을 켜거나 음악앱으로 돌아와 재개시켜 오디오 포커스를 다시 요청할 수 있겠죠.
그러나 다른 경우들은 논쟁의 여지가 있습니다. (위에 설명된 것 처럼) 영구적으로 오디오 포커스를 잃는 것과 일시적으로 잃는 것에서는 차이가 있습니다. 애플리케이션이 AUDIOFOCUS_LOSS_TRANSIENT를 받으면 앱은 AUDIOFOCUS_GAIN 이벤트를 받을 때까지 오디오 시스템의 사용을 멈추어야 합니다. AUDIOFOCUS_LOSS_TRANSIENT가 발생하면 앱은 포커스를 일시적으로 잃는다고 기억해야 합니다. 이것이 오디오 포커스를 얻었을 때 올바른 행동이 무엇인지 추론할 수 있게 합니다. (목록 2의 13-27 행을 보세요.)
가끔 앱이 오디오 포커스를 잃고 (AUDIOFOCUS_LOSS를 받고) 끼어든 앱이 종료되거나 그렇지 않으면 오디오 포커스를 버리기도 합니다. 이 경우 오디오 포커스를 가졌던 이전 애플리케이션이 AUDIOFOCUS_GAIN 이벤트를 받기도 합니다. 이어지는 AUDIOFOCUS_GAIN 이벤트에서 앱은 임시적인 상실 뒤에 얻어서 이래저래 오디오 시스템을 재개할 수 있는지 영속적인 상실로 부터 복구해서 재생을 설정해야하는 지를 확인해야 합니다.
애플리케이션이 (45초 미만의) 짧은 시간 만 오디오를 사용한다면 AUDIOFOCUS_GAIN_TRANSIENT 포커스 요청을 사용하고 재생이나 캡쳐가 종료된 후 포커스를 풀어야 합니다. 오디오 포커스는 시스템에서 스택처럼 다루어집니다. 오디오 포커스를 요청한 가장 마지막 프로세스가 이기는 식이죠.
오디오 포커스를 얻으면 MediaPlayer나 MediaEncorder를 생성하고 리소스를 할당할 적절한 시기입니다. 비슷하게 앱이 AUDIOFOCUS_LOSS를 받으면 할당된 어떤 리소스를 정리하기에 좋은 차례입니다. 오디오 포커스를 얻는 것은 3가지 유형이 있는데 표 1에 나와있는 세가지 오디오 포커스 상실과 관련있습니다. OnAudioFocusChangeListener에서 모든 상실 경우를 명시적으로 다루는 것은 좋은 습관입니다.
표 1. 오디오 포커스 획득과 상실 결과
획득 | 상실 |
---|---|
AUDIOFOCUS_GAIN | AUDIOFOCUS_LOSS |
AUDIOFOCUS_GAIN_TRANSIENT | AUDIOFOCUS_LOSS_TRANSIENT |
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK | AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK |
노트: AUDIOFOCUS_GAIN은 두 부분에서 사용됩니다. 오디오 포커스를 요청할 때 AudioManager에 힌트로 전달되고, OnAudioFocusChangeListener의 이벤트 경우로 사용됩니다. 녹색으로 강조된 획특 이벤트는 오디오 포커스를 요청했을 때만 사용됩니다. 상실 이벤트는 오로지 OnAudioFocusChangeListener에서만 사용됩니다.
표 2. 오디오 스트림 타입
>스트림 타입 | 설명 |
---|---|
STREAM_ALARM | 알람을 의한 오디오 스트림The audio stream for alarms |
STREAM_DTMF | DTMF 톤을 위한 오디오 스트림 |
STREAM_MUSIC | "미디어"(음악, 팟캐스트, 비디오) 재생을 오디오 스트림 |
STREAM_NOTIFICATION | 알림을 위한 오디오 스트림 |
STREAM_RING | 폰 링을 위한 오디오 스트림 |
STREAM_SYSTEM | 시스템 사운드를 위한 오디오 스트림 |
애플리케이션은 AudioManager (목록 1 첫번째 줄)에 오디오 포커스를 요청합니다. (아래에 링크된 샘플 코드의 예제를 보세요.) 제공되는 3개의 인자는 오디오 포커스 체인지 리스너 객체 (선택적), 사용할 오디오 채널이 무엇인지 힌트 (표 2, 대부분의 앱은 STREAM_MUSIC를 써야 합니다.), 표 1 왼편의오디오 포커스 타입입니다. 오디오 포커스가 시스템에 의해 허용(AUDIOFOCUS_REQUEST_GRANTED) 되었다면 초기화를 하면 됩니다. (목록 1의 9번째 줄을 보세요.)
노트: 현재 프로세스에서 폰 전화가 있을 때 오디오 포커스가 허용되지 않으며 (AUDIOFOCUS_REQUEST_FAILED) 애플리케이션은 전화가 끝난 후에 AUDIOFOCUS_GAIN를 받지 않습니다.
OnAudioFocusChange() 구현에서 애플리케이션이 애플리케이션이 받는 onAudioFocusChange() 이벤트가 무엇인지는 표 3에 정리되어 있습니다.
오디오 포커스를 잃는 경우에 상실이 사실상 최종적인지 확인해야 합니다. 앱이 AUDIOFOCUS_LOSS_TRANSIENT, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK를 받으면 금방 다른 오디오 포커스 변경 이벤트가 있어서 생성할 미디어 리소스를 (relase()를 불러 해제하지 않고) 붙잡아 둘 수 있습니다. 앱은 어떤 상태 변수나 단순한 상태 머신등을 이용해서 임시적인 상실을 경험했다는 것을 기록해야 합니다.
앱이 AUDIOFOCUS_GAIN로 영구적인 오디오 포커스를 요청했고 AUDIOFOCUS_LOSS_TRANSIENT나 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 이벤트를 받았다면 애플리케이션에 적절한 행동은 (원래 볼륨 상태를 어딘가 확실히 저장해두고) 스트림의 볼륨을 낮추고 AUDIOFOCUS_GAIN 이베트를 받았을 때 볼륨을 다시 올리는 것입니다. (아래 그림 1을 보세요.)
표 3. 포커스 변경 종류에 따른 적절한 행동
포커스 변경 종류 | 적절한 행동 |
---|---|
AUDIOFOCUS_GAIN | 상실 이벤트 후 획득 이벤트. 애플리케이션에 의해 설정된 다른 상태가 없다면 재생을 재개한다. 예를 들어 상실 이베트 이전에 사용자가 명시적으로 일시 정지한 경우. |
AUDIOFOCUS_LOSS | 재생을 정지한다. 에셋을 해제한다. |
AUDIOFOCUS_LOSS_TRANSIENT | 재생을 일시정지하고 상실이 일시적이란 것의 상태를 유지한다. AUDIOFOCUS_GAIN 이벤트가 나타나면 적절히 재생을 재개할 수 있다. 에셋을 해제하지 마라. |
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK | 볼륨을 줄이거나 재생을 일시정지한다. AUDIOFOCUS_LOSS_TRANSIENT의 상태를 저장한다. 애셋을 해제하지 마라. |
결론과 더 읽을거리
안드로이드에서 좋은 오디오 시민 애플리케이션 법을 이해하는 것은 시스템 오디오 포커스 규칙과 각 상황에 적합하게 다루는 것을 존중하는 것을 의미합니다. 애플리케이션의 행동을 일관성있게 하고 사용자를 부정적으로 놀라게 하지 않게 노력하세요. 안드로이드 오디오 시스템에 대해 이야기할 더 많은 내용들이 있습니다. 아래에 자료들에서 추가적인 내용들을 찾으십시요.
- 안드로이드 개발자 트레이닝 클래스, 오디오 포커스 다루기 (Managing Audio Focus)
- 안드로이드 개발자 블로그의 Allowing applications to play nice(r) with each other: Handling remote control buttons
예제 소스 코드는 여기있습니다.