Android 音视频焦点 处理

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

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

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

  3. 行为恰当的音频应用应根据以下一般准则来管理音频焦点:

  • 在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 [AUDIOFOCUS_REQUEST_GRANTED](https://developer.android.google.cn/reference/android/media/AudioManager#AUDIOFOCUS_REQUEST_GRANTED)。如果按照本指南中的说明设计应用,则应在媒体会话的 onPlay() 回调中调用 requestAudioFocus()
  • 在其他应用获得音频焦点时,停止或暂停播放,或降低音量。
  • 播放停止后,放弃音频焦点。

运行的 Android 版本不同,音频焦点的处理方式也会不同:

  • 从 Android 2.2(API 级别 8)开始,应用通过调用 [requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener,%20int,%20int))[abandonAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener)) 来管理音频焦点。应用还必须为这两个调用注册 [AudioManager.OnAudioFocusChangeListener](https://developer.android.google.cn/reference/android/media/AudioManager.OnAudioFocusChangeListener),以便接收回调并管理自己的音量。
  • 对于以 Android 5.0(API 级别 21)及更高版本为目标平台的应用,音频应用应使用 [AudioAttributes](https://developer.android.google.cn/reference/android/media/AudioAttributes) 来描述应用正在播放的音频类型。例如,播放语音的应用应指定 [CONTENT_TYPE_SPEECH](https://developer.android.google.cn/reference/android/media/AudioAttributes#CONTENT_TYPE_SPEECH)
  • 面向 Android 8.0(API 级别 26)或更高版本的应用应使用 [requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager#requestAudioFocus(android.media.AudioFocusRequest)) 方法,该方法会接受 [AudioFocusRequest](https://developer.android.google.cn/reference/android/media/AudioFocusRequest) 参数。AudioFocusRequest 包含有关应用的音频上下文和功能的信息。系统使用这些信息来自动管理音频焦点的得到和失去。
    处理音频焦点一些规则
    以下是官方建议的处理音频焦点应该遵循的一些规则:
  1. 在开始播放之前,调用**requestAudioFocus()**方法,并检查返回值是否是AUDIOFOCUS_REQUEST_GRANTED,若成功获取,则开始播放。
    当App失去音频焦点时,根据失去的焦点类型,应该暂停播放,或者将音量调低。
    当播放结束时,释放音频焦点。
  2. 音频焦点如何处理
  • 处理音频焦点都是通过AudioManager这个类,如下是获得该类实例的方法:
    AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    下面介绍音频焦点处理相关的一些方法(不同的Android版本处理音频焦点的方式略有差别,以下内容基于Android 4.4)
requestAudioFocus():申请音频焦点
abandonAudioFocus(): 释放音频焦点
AudioManager.OnAudioFocusChangeListener接口,提供了onAudioFocusChange()方法来监听音频焦点变化

  • 5.1 requestAudioFocus
    参数:
    AudioManager.OnAudioFocusChangeListener
    用于监听音频焦点变化,从而可以进行适当的操作,例如暂停播放等。
    streamType
    申请音频焦点处理的音频类型,例如,当播放音乐时,可以传入STREAM_MUSIC;当播放铃声时,可以传入STREAM_RING。表中列出了所有的可选值:durationHint
    可选值有以下五个:
    (1) AUDIOFOCUS_GAIN: 此参数表示希望申请一个永久的音频焦点,并且希望上一个持有音频焦点的App停止播放;例如在需要播放音乐时。
    (2) AUDIOFOCUS_GAIN_TRANSIENT:表示申请一个短暂的音频焦点,并且马上就会被释放,此时希望上一个持有音频焦点的App暂停播放。例如播放一个提醒声音。
    (3) AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:效果同AUDIOFOCUS_GAIN_TRANSIENT,只是希望上一个持有焦点的App减小其播放声音(但仍可以播放),此时会混音播放。例如导航播报。
    (4) AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 表示申请一个短暂的音频焦点,并且会希望系统不要播放任何突然的声音(例如通知,提醒等),例如用户在录音。
    返回值:AUDIOFOCUS_REQUEST_GRANTED或者AUDIOFOCUS_REQUEST_FAILED
  • 5.2 abandonAudioFocus(OnAudioFocusChangeListener l)
    5.2.1 播放完成后,请调用 abandonAudioFocus()
    audioManager.abandonAudioFocus(afChangeListener)

5.2.2 这会通知系统您不再需要焦点,并注销关联的 OnAudioFocusChangeListener。如果您请求的是暂时性焦点,则会通知已暂停或降低音量的应用它可以继续播放或恢复其音量。

  • 5.3 AudioManager.OnAudioFocusChangeListener
    当音频焦点发生变化时,可以在OnAudioFocusChangeListener的onAudioFocusChange(int focusChange)方法中监听到,下面详细说明该方法。
    onAudioFocusChange(int focusChange)
  • 参数:focusChange可以表明当前音频焦点发生的是何种变化,需要根据该参数状态做出正确的响应。
    分为获得和丢失两种情况:
    获得:AUDIOFOCUS_GAIN
  • 5.3.1 表示获得音频焦点,此时应该开始播放,丢失音频焦点,这时分为以下两种情况:
  • 5.3.2 短暂的丢失:
    如果focusChange的值是AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK或者 AUDIOFOCUS_LOSS_TRANSIENT时,你的App需要降低播放音量 或者暂停播放(但是需要记录当前的播放状态,以便后续恢复播放)。
    在短暂丢失焦点期间,你的App应该持续关注音频焦点的变化,当再次获得焦点时,恢复播放。
  • 5.3.3 永久的丢失
    若值为AUDIOFOCUS_LOSS,你的App应该立即停止播放,并且当再次获得音频焦点时,也不会恢复播放,只有当用户主动播放时,再开始播放
  1. 构建 AudioFocusRequest来请求和放弃音频焦点:
    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();
    mediaPlayer = new MediaPlayer();
    final Object focusLock = new Object();

    boolean playbackDelayed = false;
    boolean playbackNowAuthorized = false;

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

    // ...
    @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) {
                    resumeOnFocusGain = true;
                    playbackDelayed = false;
                }
                pausePlayback();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                // ... pausing or ducking depends on your app
                break;
            }
        }
    }

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值