複数の Android アプリから同じ出力ストリームに対して 同時に音声を再生できます。この場合、システムはすべてをミックスします。これは 技術者から見るとすばらしいことですが、ユーザーにとっては厄介な場合があります。そこで、それぞれの 音楽アプリで一斉に再生が行われるのを避けるために、Android では「音声 フォーカス」という概念が導入されています。一度に 1 つのアプリのみが音声フォーカスを保持できる、という概念です。
アプリで音声を出力する必要がある場合は、音声フォーカスをリクエストします。フォーカスを取得すると、 音声を再生できます。ただし、音声フォーカスを取得しても、再生が完了するまで保持できない場合があります。別のアプリによってフォーカスがリクエストされ、保持している音声フォーカスが プリエンプトされる可能性があるからです。その場合は、アプリで再生を一時停止するか音量を下げて、新しい音声ソースがユーザーに聴こえやすくなるようにします。
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 つ目の(現在再生している)アプリが次のすべての条件を満たしている。
- アプリに
AudioAttributes.USAGE_MEDIAまたはAudioAttributes.USAGE_GAMEの使用属性がある。 - アプリが
AudioManager.AUDIOFOCUS_GAINを使用して正常に音声フォーカスをリクエストしている。 - アプリがコンテンツ タイプ
AudioAttributes.CONTENT_TYPE_SPEECHの音声を再生していない。
- アプリに
2 つ目のアプリが、
AudioManager.AUDIOFOCUS_GAINを使用して音声フォーカスをリクエストしている。
これらの条件が満たされると、オーディオ システムが 1 つ目のアプリをフェードアウトさせます。フェードアウトが 終了すると、1 つ目のアプリはフォーカス喪失を通知されます。1 つ目のアプリが音声フォーカスを再度リクエストするまで、アプリの プレーヤーはミュート状態のままになります。
既存の音声フォーカスの動作
音声フォーカスの切り替えを伴う以下のようなケースについても把握しておく必要があります。
自動ダッキング
自動ダッキング(あるアプリの音声レベルを一時的に下げて、 別のアプリの音声が明瞭に聞こえるようにする機能)は、Android 8.0(API レベル 26)で導入されました。
システムによりダッキングの実装が行われるため、デベロッパーがアプリにダッキングを実装する必要はありません。
自動ダッキングは、音声通知が現在再生しているアプリからフォーカスを取得した場合にも発生します。通知の再生開始はダッキング ランプの最後と同期されます。
自動ダッキングは、次の条件を満たす場合に発生します。
1 つ目の(現在再生している)アプリが次のすべての条件を満たしている。
- あらゆるタイプのフォーカス ゲインを使用して、正常に音声フォーカスをリクエストしている。
- コンテンツ タイプが
AudioAttributes.CONTENT_TYPE_SPEECHの音声を再生していない。 - アプリで
AudioFocusRequest.Builder.setWillPauseWhenDucked(true)が設定されていない。
2 つ目のアプリが、
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKを使用して音声フォーカスをリクエストしている。
これらの条件が満たされると、オーディオ システムは、2 つ目のアプリがフォーカスを保持している間、 1 つ目のアプリのすべてのアクティブなプレーヤーをダッキングします。2 つ目のアプリがフォーカスを破棄すると、1 つ目のアプリのダッキングが解除されます。1 つ目のアプリはフォーカスの喪失時に通知を受けないため、 何もする必要はありません。
ユーザーが 話し声のコンテンツを聞いている場合、プログラムの一部を聞き逃す可能性があるため、自動ダッキングは行われません(例: 運転ナビの音声ガイダンスはダッキングされません)。
着信時に現在の音声再生をミュートする
一部のアプリは正常に動作せず、通話中も音声を再生し続けます。 そのため、ユーザーは通話が聞こえるようにするために、問題のアプリを見つけてミュートするか、 終了する必要があります。これを防ぐため、着信中は他の アプリの音声をミュートできます。着信があり、アプリが次の条件を満たしている場合、この機能が呼び出されます。
- アプリに
AudioAttributes.USAGE_MEDIAまたはAudioAttributes.USAGE_GAMEの使用属性がある。 - 音声フォーカス(あらゆるフォーカス ゲイン)を正常にリクエストし、音声を再生している。
通話中にアプリが再生を続けると、通話が終了するまで再生はミュートされます。ただし、通話中にアプリが再生を開始した場合、ユーザーが意図的に再生を開始したと見なされるため、そのプレーヤーは ミュートされません。
Android 8.0 ~ Android 11 での音声フォーカス
Android 8.0(API レベル 26)以降では、
requestAudioFocus()
を呼び出すときに AudioFocusRequest パラメータを指定する必要があります。AudioFocusRequest
には、アプリの音声コンテキストと機能に関する情報が含まれています。
システムによる音声フォーカスの得失の管理は、この情報を使用して
自動的に行われます。音声フォーカスを解放するときも、やはり
abandonAudioFocusRequest()
を引数に取るメソッドを呼び出します。AudioFocusRequestフォーカスをリクエストするときと解放するときは、同じ
AudioFocusRequest インスタンスを使用します。
AudioFocusRequest を作成するには、
AudioFocusRequest.Builder を使用します。フォーカスリクエストでは常にリクエストのタイプを指定する必要があるため、ビルダーのコンストラクタにそのタイプを含めます。このビルダーのメソッドを使用して、リクエストの他のフィールドを設定します。
FocusGain フィールドは必須ですが、他のフィールドはすべて省略可能です。
| メソッド | メモ |
|---|---|
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)以降では、
音量コントロール以外の操作のストリーム タイプはサポートが終了しています。オーディオ プレーヤーで使用するフォーカス リクエストと同じ属性を使用します(
この表の下の例を参照)。
まず
指定しない場合、 |
setWillPauseWhenDucked()
|
別のアプリから
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKを使用したフォーカス リクエストがなされた場合、通常、フォーカスを保持するアプリは
onAudioFocusChange()
コールバックを受信しません。これは、システムによる自動ダッキングが可能なためです。音量を下げる代わりに再生を一時停止する必要がある場合は、setWillPauseWhenDucked(true) を呼び出し、
OnAudioFocusChangeListener を作成して設定します。これは、自動
ダッキングで説明されています。
|
setAcceptsDelayedFocusGain()
|
フォーカスが別のアプリにロックされていると、音声フォーカスのリクエストが失敗する可能性があります。
このメソッドにより、遅延フォーカス取得、つまりフォーカスが利用可能になったときに非同期に取得する機能を使用できます。
なお、遅延フォーカス取得は、音声リクエストで
|
setOnAudioFocusChangeListener()
|
OnAudioFocusChangeListener は、リクエストで
willPauseWhenDucked(true) または setAcceptsDelayedFocusGain(true) も指定する場合にのみ必要です。
このリスナーを設定するメソッドには、ハンドラ引数を使用するものと使用しないものの
2 種類があります。ハンドラとは、リスナーが実行されるスレッドです。ハンドラを指定しない場合は、メインの
|
次の例は、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() が呼び出されてアプリに通知されます。
遅延フォーカス取得を処理するには、目的の動作を実装する
OnAudioFocusChangeListenerコールバック メソッドを使用してonAudioFocusChange()を作成し、
setOnAudioFocusChangeListener()を呼び出してリスナーを登録する必要があります。
Android 7.1 以前での音声フォーカス
`requestAudioFocus()` を呼び出すときは、継続時間のヒントを指定する必要があります。このヒントは、現在フォーカスを保持して再生を行っている別のアプリによって尊重される可能性があります。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の登録が解除されます。一時的なフォーカスをリクエストした場合は、
一時停止またはダッキングしたアプリに対し、再生の継続または
音量の復元が可能になったことが通知されます。
音声フォーカスの変化への応答
アプリで音声フォーカスを取得した場合は、別のアプリが音声フォーカスを
リクエストしたときに、それを解放できなければなりません。この場合、アプリが
が呼び出されます。
onAudioFocusChange()
メソッドは、AudioFocusChangeListener
を呼び出したときに指定したrequestAudioFocus()です。
onAudioFocusChange() に渡される focusChange パラメータは、発生中の変化の種類を示しています。これは、フォーカスを取得しようとしているアプリの継続時間のヒントに対応します。これに対し、アプリでは
適切に応答する必要があります。
- フォーカスの一時的な喪失
-
フォーカスの変化が一時的なもの(
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKまたはAUDIOFOCUS_LOSS_TRANSIENT)の場合、アプリではダッキング(自動ダッキングに依存していない場合)するか、再生を一時停止する必要がありますが、それ以外は同じ状態を維持します。音声フォーカスが一時的に失われている間は、音声フォーカスの変化を監視し続け、フォーカスを取り戻したときに通常の再生を再開できるよう備える必要があります。ブロック元のアプリからフォーカスが解放されると、こちらのアプリではコールバック (
AUDIOFOCUS_GAIN)を受け取ります。この時点で、音量を通常のレベルに復元するか 、再生を再開できます。 - フォーカスの永続的な喪失
-
音声フォーカスを永続的に失う(
AUDIOFOCUS_LOSS)のは、別のアプリで 音声を再生する場合です。アプリではすぐに再生を一時停止するようにし、AUDIOFOCUS_GAINコールバックは受信しません。再生を再開するには、ユーザーが明示的なアクション(通知やアプリの UI で再生トランスポート コントロールを押すなど)を行う必要があります。
次のコード スニペットは、
OnAudioFocusChangeListener とその onAudioFocusChange() コールバックの実装方法を示しています。音声フォーカスが永続的に失われた場合、
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() コールバックでこのメソッドを呼び出します。