2 つ以上の 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
で音声フォーカスをリクエストします。
これらの条件が満たされると、オーディオ システムは最初のアプリをフェードアウトします。フェードアウトが終了すると、システムは最初のアプリにフォーカス喪失を通知します。アプリが音声フォーカスを再度リクエストするまで、アプリのプレーヤーはミュートされたままになります。
既存の音声フォーカスの動作
音声フォーカスの切り替えを伴う以下のようなケースについても把握しておく必要があります。
自動ダッキング
自動ダッキング(あるアプリの音声レベルを一時的に下げて、別のアプリの音声が明瞭に聞こえるようにする)は、Android 8.0(API レベル 26)で導入されました。
システムにダッキングを実装することで、アプリにダッキングを実装する必要はありません。
自動ダッキングは、音声通知が現在再生しているアプリからフォーカスを取得した場合にも発生します。通知の再生開始はダッキング ランプの最後と同期されます。
自動ダッキングは、次の条件を満たす場合に発生します。
現在再生中の 1 つ目のアプリは、以下の条件をすべて満たしています。
- アプリは、あらゆる種類のフォーカス ゲインで音声フォーカスを正常にリクエストしました。
- アプリはコンテンツ タイプが
AudioAttributes.CONTENT_TYPE_SPEECH
の音声を再生していません。 - アプリが
AudioFocusRequest.Builder.setWillPauseWhenDucked(true)
を設定していない。
2 つ目のアプリは
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
を使用して音声フォーカスをリクエストします。
これらの条件が満たされると、オーディオ システムは 1 つ目のアプリのすべてのアクティブなプレーヤーをダッキングし、2 つ目のアプリがフォーカスを保持します。2 つ目のアプリがフォーカスを放棄すると、ダッキングが解除されます。最初のアプリはフォーカスを喪失しても通知されないため、何もする必要はありません。
ユーザーが音声コンテンツを聞いているときは、番組の一部を聞き逃す可能性があるため、自動ダッキングは行われません。たとえば、運転ルートの音声案内はダッキングされません。
電話の着信時に現在の音声再生をミュートする
一部のアプリは正しく動作せず、通話中も音声が再生され続けます。このようなケースでは、ユーザーは通話を聞くために、問題のアプリを見つけてミュートするか、終了しなければなりません。これを防ぐため、着信中はシステムで他のアプリの音声をミュートできます。電話着信があり、アプリが次の条件を満たすと、システムがこの機能を呼び出します。
- アプリに
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
フィールドは必須ですが、他のフィールドはすべて省略可能です。
メソッド | 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)以降では、音量コントロール以外のオペレーションのストリーム タイプはサポートが終了しています。オーディオ プレーヤーで使用するものと同じ属性をフォーカス リクエストでも使用します(次の表の例をご覧ください)。
指定しない場合、 |
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()
を呼び出してアプリに通知します。
遅延フォーカス取得を処理するには、目的の動作を実装する 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()
を呼び出したときに指定した AudioFocusChangeListener
内の onAudioFocusChange()
メソッドの呼び出しを受け取ります。
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()
コールバックでこのメソッドを呼び出す必要があります。