[Android] AudioFocus 알아보기(1) - 왜, 어떻게 사용하는가

개요

AudioFocus가 적용되어 있지 않은 애플리케이션에 AudioFocus를 적용해야만 하는 일이 생겼다. 지금까지는 쓸 일도, 그럴 필요도 없었기에 이 친구의 존재를 모르고 있었는데 적용할 일이 생겼으니 공부해 보자 싶었다. 이번 포스팅에서는 AudioFocus를 왜 써야 하는지, 그리고 어떻게 사용해야 하는지에 대해 적어보려고 한다.


AudioFocus가 뭐야?

Android OS에게 “나 지금 미디어 필요해! 쓴다!” 하고 통보하는 기능으로, 일종의 약속이다. AudioFocus를 요청할 때에는 어떤 용도로 오디오를 사용하는지에 대한 정보를 함께 전달해야 한다. 용도에 대한 정보는 AudioAttributes 인스턴스 생성 후 setUseage()setContentType() 를 통해 세팅할 수 있으며, 다음의 값 중 하나를 가진다. (번역은 셀프입니다. ㅎㅎ;😉

setUseage List

설명
USAGE_UNKNOWN Usage value to use when the usage is unknown.
USAGE_MEDIA Usage value to use when the usage is media, such as music, or movie soundtracks.
USAGE_VOICE_COMMUNICATION Usage value to use when the usage is voice communications, such as telephony or VoIP.
USAGE_VOICE_COMMUNICATION_SIGNALLING Usage value to use when the usage is in-call signalling, such as wite a “busy” beep, or DTMF tones.
USAGE_ALARM Usage value to use when the usage is an alarm (e.g. wake-up alarm).
USAGE_NOTIFICATION Usage value to use when the usage is notification. See other notification usages for more specialized uses.
USAGE_NOTIFICATION_RINGTONE Usage value to use when the usage is telephony ringtone.
USAGE_NOTIFICATION_COMMUNICATION_REQUEST Usage value to use when the usage is a request to enter/end a communication, such as a VoIP communication or video-conference.
USAGE_NOTIFICATION_COMMUNICATION_INSTANT Usage value to use when the usage is notification for an “instant” communication such as a chat, or SMS.
USAGE_NOTIFICATION_COMMUNICATION_DELAYED Usage value to use when the usage is notification for a non-immediate type of communication such as e-mail.
USAGE_NOTIFICATION_EVENT Usage value to use when the usage is to attract the user’s attention, such as a reminder or low battery warning.
USAGE_ASSISTANCE_ACCESSIBILITY Usage value to use when the usage is for accessibility, such as with* a screen reader.
USAGE_ASSISTANCE_NAVIGATION_GUIDANCE Usage value to use when the usage is driving or navigation directions.
USAGE_ASSISTANCE_SONIFICATION Usage value to use when the usage is sonification, such as with user interface sounds.
USAGE_GAME Usage value to use when the usage is for game audio.
USAGE_ASSISTANT Usage value to use for audio responses to user queries, audio instructions or help utterances.

setContentType List

설명
CONTENT_TYPE_UNKNOWN Content type value to use when the content type is unknown, or other than the ones defined.
CONTENT_TYPE_SPEECH Content type value to use when the content type is speech.
CONTENT_TYPE_MUSIC Content type value to use when the content type is music.
CONTENT_TYPE_MOVIE Content type value to use when the content type is a soundtrack, typically accompanying a movie or TV program.
CONTENT_TYPE_SONIFICATION Content type value to use when the content type is a sound used to accompany a user action, such as a beep or sound effect expressing a key click, or event, such as the type of a sound for a bonus being received in a game. These sounds are mostly synthesized or short Foley sounds.

AudioFocus를 뺏길 땐 어떻게 해?

Android는 다 계획이 있다, 이 말이야! 이 경우를 대비해서 Listener(AudioManager.OnAudioFocusChangeListener) 를 제공한다. 개발자는 이 Listener에서 처리할 수 있는 이벤트는 다음과 같다.

AudioFocus Change Listener Event List

Event 설명
AUDIOFOCUS_NONE Used to indicate no audio focus has been gained or lost, or requested.
AUDIOFOCUS_GAIN Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
AUDIOFOCUS_GAIN_TRANSIENT Used to indicate a temporary gain or request of audio focus, anticipated to last a short amount of time. Examples of temporary changes are the playback of driving directions, or an event notification.
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK Used to indicate a temporary request of audio focus, anticipated to last a short amount of time, and where it is acceptable for other audio applications to keep playing after having lowered their output level (also referred to as “ducking”).
Examples of temporary changes are the playback of driving directions where playback of music in the background is acceptable.
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE Used to indicate a temporary request of audio focus, anticipated to last a short amount of time, during which no other applications, or system components, should play anything.
Examples of exclusive and transient audio focus requests are voice memo recording and speech recognition, during which the system shouldn’t play any notifications, and media playback should have paused.
AUDIOFOCUS_LOSS Used to indicate a loss of audio focus of unknown duration.
AUDIOFOCUS_LOSS_TRANSIENT Used to indicate a transient loss of audio focus.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK Used to indicate a transient loss of audio focus where the loser of the audio focus can lower its output volume if it wants to continue playing (also referred to as “ducking”), as the new focus owner doesn’t require others to be silent.

어떻게 사용해?

음, Helper 라는 단어 사용을 지양해야 한다고 하는데 마땅한 단어가 떠오르지 않는다. 그래서 어쩔 수 없이 AudioFocusHelper.java class를 만들게 되었다. ^^; 필요한 곳에서 AudioFocuscontext 와 함께 생성한다. 이후, AudioFocus 가 필요할 때 requestAudioFocus() 함수를 호출하면 깔끔하게 완성!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
private static final String TAG = AudioFocusHelper.class.getSimpleName();
private static AudioManager audioManager;
private static AudioFocusRequest audioFocusRequest;

public AudioFocusHelper(@Nonnull Context context) {
Log.d(TAG, "Create AudioFocusHelper");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.setWillPauseWhenDucked(true)
.build();
}
}

public void requestAudioFocus() {
Log.d(TAG, "AudioFocus >> called requestAudioFocus() / Build.VERSION: " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d(TAG, "AudioFocus >> requestAudioFocus");
audioManager.requestAudioFocus(audioFocusRequest);
}
}

@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 이제부터 AudioFocus는 우리 앱의 소유!
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_GAIN");
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
// 일시적으로 AudioFocus를 가져온다.
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_GAIN_TRANSIENT");
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
// 15초 이상 점유하면 안 된다. 앱의 소리가 나지만, Background App의 사운드가 작게 들릴 수 있다.
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK");
break;
case AudioManager.AUDIOFOCUS_LOSS:
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_LOSS");
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 일시적인 LOSS로, 잠깐 사용한 App이 점유를 끝내면 가장 마지막에 GAIN한 App이 AudioFocus를 점유한다.
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_LOSS_TRANSIENT");
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 볼륨을 낮추는 것을 권장한다.
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
break;
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
// 요청 실패할 경우의 로직을 추가한다.
Log.d(TAG, "AudioFocus >> AUDIOFOCUS_REQUEST_FAILED");
break;
}
}
}

마무리하며

필수 요건이 아닌 약속이라고 표현한 이유가 있다. 사실상 AudioFocus 를 요청하지 않아도 사운드 출력에는 문제가 없다. Android 플랫폼 생태계를 무시하고, ‘난 무조건 재생해야 해!’ 라는 억지를 부리고 싶다면 AudioFocus 없이 미디어를 출력해 버리면 된다. 혹은 AUDIOFOCUS_LOSS 이벤트를 받자마자 다시 GAIN하는 방법도 있지만, 당연히 권장하지 않는다.

플랫폼 위에 올라가는 서비스 개발자는 플랫폼의 약속을 이행하는 것이 좋다. 이 자그마한 규약들은 각각의 앱들이 최대한 ‘정상 범주’ 내에서 동작할 수 있도록 만들어졌다. AudioFocus 를 무시하고 재생하는 앱이 하나일 경우에도 문제이지만, 여러 앱이 이것을 무시하고 재생한다면 UX가 와장창 깨지고 말 것이다. 알람도 울리고, 음악도 재생되고, 또 다른 앱에서 게임 소리도 나고, 또 다른 앱에서 영상이 재생되는 등의 복합 상황이 발생할 수 있다. 플랫폼의 약속은, 이런 복합 상황을 어느 정도 Android OS에게 '위임’한다는 의미를 포함한다.


Share