【Android Audio Focus 音频焦点】

介绍

Android 中的音频焦点(Audio Focus)是一种机制,用于管理应用程序之间的音频资源竞争。当多个应用程序同时请求使用音频设备时,通过音频焦点机制可以确保最终用户的体验不受影响。

两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了"音频焦点"的概念。 一次只能有一个应用获得音频焦点。

当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。

音频焦点采用合作模式。我们建议应用遵守音频焦点准则,但系统不会强制执行这些准则。如果应用想要在失去音频焦点后继续大声播放,系统无法阻止它。这是一种不好的体验,用户很可能会卸载具有这种不良行为的应用。


焦点事件

  • AUDIOFOCUS_GAIN
    说明 : 应用获得了音频焦点,可以继续播放音频。
    处理方式: 在这个状态下,应用可以正常播放音频。在失去焦点后再次获取焦点时,通常需要在这个状态下恢复播放。

  • AUDIOFOCUS_LOSS
    说明 : 【永久性失去焦点 】应用失去了音频焦点,需要停止音频播放。
    处理方式: 当收到 AUDIOFOCUS_LOSS 时,应用应该停止播放音频,并释放相关资源。这种情况通常是由于其他应用请求了长时间的音频焦点,例如开始播放长时间的音频文件。不要立即尝试重新获取焦点。

  • AUDIOFOCUS_LOSS_TRANSIENT
    说明 : 【暂时性失去焦点 】应用短暂失去了音频焦点,但可以在稍后重新获取焦点。
    处理方式: 当收到 AUDIOFOCUS_LOSS_TRANSIENT 时,应用应该暂停音频播放。这种情况通常是由于短暂的通知声、铃声等情况导致的。应用通常会在稍后重新获取焦点并恢复播放。

  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
    说明 : 【暂时性失去焦点 】应用短暂失去了音频焦点,但可以降低音量继续播放。稍后可以重新获取焦点。
    处理方式: 当收到 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 时,应用可以选择降低音量而不是完全停止音频播放。这种情况通常用于在接收短暂通知时,应用可以继续以较低的音量播放。

暂时性失去焦点:

在暂时性失去音频焦点时,您应该继续监控音频焦点的变化,并准备好在重新获得焦点后恢复正常播放。当抢占焦点的应用放弃焦点时,您会收到一个回调 (AUDIOFOCUS_GAIN)。此时,您可以在此回调中,将音量恢复到正常水平或重新开始播放。

永久性失去焦点:

如果是永久性失去音频焦点 (AUDIOFOCUS_LOSS),则其他应用会播放音频。您的应用应立即暂停播放,因为它不会收到 AUDIOFOCUS_GAIN 回调。要想重新开始播放,用户必须执行明确的操作,例如在通知或应用界面中按播放传输控件。


代码示例

Android 8.0 及更高版本中的音频焦点:

从 Android 8.0(API 级别 26)开始,当您调用 requestAudioFocus() 时,必须提供 AudioFocusRequest 参数。要释放音频焦点,请调用 abandonAudioFocusRequest() 方法,该方法也接受 AudioFocusRequest 作为参数。在请求和放弃焦点时,应使用相同的 AudioFocusRequest 实例。

要创建 AudioFocusRequest,请使用 AudioFocusRequest.Builder。由于焦点请求始终必须指定请求的类型,因此此类型会包含在构建器的构造函数中。使用构建器的方法来设置请求的其他字段。

kotlin 复制代码
    audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
        setAudioAttributes(AudioAttributes.Builder().run {
            setUsage(AudioAttributes.USAGE_MEDIA)//or AudioAttributes.USAGE_GAME
            setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            build()
        })
        setAcceptsDelayedFocusGain(true)
        setOnAudioFocusChangeListener(afChangeListener, handler)
        build()
    }

    val focusLock = Any()

    var playbackDelayed = false
    var playbackNowAuthorized = false

    // ...
    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
        }
    }

	val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
        when (focusChange) {
            AudioManager.AUDIOFOCUS_LOSS -> {
                // Permanent loss of audio focus. Pause playback immediately
                synchronized(focusLock) {
                    resumeOnFocusGain = false
                    playbackDelayed = false
                }
                pausePlayback()
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                // Pause playback
                synchronized(focusLock) {
                    resumeOnFocusGain = true
                    playbackDelayed = false
                }
                pausePlayback()
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                // ... pausing or Lower the volume depends on your app
                
            }
            AudioManager.AUDIOFOCUS_GAIN -> {
                // Your app has been granted audio focus again
                // Raise volume to normal, restart playback if necessary
                if (playbackDelayed || resumeOnFocusGain) {
                    synchronized(focusLock) {
                        playbackDelayed = false
                        resumeOnFocusGain = false
                    }
                    playbackNow()
                }
            }
        }
    }

官方文档链接管理音频焦点

相关推荐
阿巴斯甜4 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker5 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95276 小时前
Andorid Google 登录接入文档
android
黄林晴7 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab19 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android