博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
音频焦点问题
阅读量:4570 次
发布时间:2019-06-08

本文共 7455 字,大约阅读时间需要 24 分钟。

当我们在settings中试听铃声,这时候突然来了一个电话,那么会出现试听铃声和来电铃声同时播放的情况。当然,此情况同样适用于闹钟铃声,媒体音乐播放等。那么怎么解决这个问题呢?这就需要当音频焦点。---》

因为系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何交互,为了避免多个应用程序同时播放音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音乐。 您的应用程序在开始播放音频文件前,首先应该请求获得音频焦点,并且应该同时注册监听音频焦点的丢失通知,即如果音频焦点被系统或其他的应用程序抢占时,您的应用程序可以做出合适的响应。

首先,我要获取一个音频焦点并管理它。

private boolean requestFocus() {        // Request audio focus for playback        int result = mAudioManager.requestAudioFocus(afChangeListener,                AudioManager.STREAM_RING,                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);        return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;    }  OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {        public void onAudioFocusChange(int focusChange) {            if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT                    || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {                // Pause playback                if (mLocalPlayer !=null && mLocalPlayer.isPlaying()){                    mLocalPlayer.pause();                }            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {                // Resume playback                  startLocalPlayer();            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {                mAudioManager.abandonAudioFocus(afChangeListener);                // Stop playback                if (mLocalPlayer !=null && mLocalPlayer.isPlaying()){                    mLocalPlayer.stop();                }            }        }    };

可以很清晰的看见,上面的第一个方法是获取音频焦点,通过requestAudioFocus()来实现。而第二个方法就是对音频焦点进行监听并管理。在这里,要先知道以上几个值的含义:

  •      AUDIOFOCUS_GAIN_TRANSIENT:只是短暂获得,一会就释放焦点,比如你只是想发个notification时用下一秒不到的铃声。
  •      AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:只是背景获得,之前的音频焦点使用者无需释放焦点给我,我将与其共同使用。
  •      AUDIOFOCUS_GAIN:我要求完全获得焦点,其他人需要释放焦点。比如我要播放音乐了,这时就要抢占整个音频焦点。
  •      AUDIOFOCUS_LOSS:你会长时间的失去焦点,所以不要指望在短时间内能获得。请结束自己的相关音频工作并做好收尾工作。
  •     AUDIOFOCUS_LOSS_TRANSIENT:你会短暂的失去音频焦点,你可以暂停音乐,但不要释放资源,因为你一会就可以夺回焦点并继续使用。

知道了以上几个字段的含义,在对应的状态,我们就能做相应的处理。比如AUDIOFOCUS_LOSS_TRANSIENT短暂失去焦点,我们就暂停我们的音乐。AUDIOFOCUS_LOSS长期失去焦点,就直接停掉音乐。AUDIOFOCUS_GAIN我获取了焦点,那么我就要开始播放音乐了(由于我完全获取了焦点,其他音乐就无法播放了,自然当前就只有一个音乐进行播放)。

获取音频焦点,就要释放音频焦点:(在哪里释放,就看当时的代码吧)

private void destroyLocalPlayer() {        if (mLocalPlayer != null) {            mLocalPlayer.reset();            mLocalPlayer.release();            mLocalPlayer = null;            synchronized (sActiveRingtones) {                sActiveRingtones.remove(this);            }        }        mAudioManager.abandonAudioFocus(afChangeListener);    }

在解决这个问题的时候,我选择在每次播放试听铃声时,获取音频焦点(何时获取,也要看当时代码情况):

private void startLocalPlayer() {        if (mLocalPlayer == null) {            return;        }        synchronized (sActiveRingtones) {            sActiveRingtones.add(this);        }        mLocalPlayer.setOnCompletionListener(mCompletionListener);        if(requestFocus()){            mLocalPlayer.start();        }    }

成功获取到焦点,才可以播放当前的试听铃声哦!

================================================================================================

更新!更新!这样的改法果然引入了一个严重的bug,就是来电铃声不能播放!

为什么呢?首先看一下来电铃声播放的代码:

private void handlePlay(SomeArgs args) {        RingtoneFactory factory = (RingtoneFactory) args.arg1;        Call incomingCall = (Call) args.arg2;        args.recycle();        // don't bother with any of this if there is an EVENT_STOP waiting.        if (mHandler.hasMessages(EVENT_STOP)) {            return;        }        // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play        // anything.        if(Uri.EMPTY.equals(incomingCall.getRingtone())) {            mRingtone = null;            return;        }        ThreadUtil.checkNotOnMainThread();        if (mRingtone == null) {            mRingtone = factory.getRingtone(incomingCall);            if (mRingtone == null) {                Uri ringtoneUri = incomingCall.getRingtone();                String ringtoneUriString = (ringtoneUri == null) ? "null" :                        ringtoneUri.toSafeString();                Log.event(null, Log.Events.ERROR_LOG, "Failed to get ringtone from factory. " +                        "Skipping ringing. Uri was: " + ringtoneUriString);                return;            }        }        handleRepeat();    }    private void handleRepeat() {        if (mRingtone == null) {            return;        }        if (mRingtone.isPlaying()) {            Log.d(this, "Ringtone already playing.");        } else {            mRingtone.play();        }        // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.        synchronized(this) {            if (!mHandler.hasMessages(EVENT_REPEAT)) {                mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);            }        }    }

代码路径:packages\services\Telecomm\src\com\android\server\telecom\AsyncRingtonePlayer.java

来电铃声播放是mRingtone.play()这一句,也就是说,最终也是用到了Ringtone.java这个类,并且会调用到startLocalPlay()这个方法,而在这个方法里,刚才我们首先获取了音频焦点,并设定获取成功才能播放。我通过追加log发现,来电铃声的时候,音频焦点获取失败了,这让我很费解,为什么试听铃声就能够获取成功,来电铃声就不行呢?于是继续向下分析:

mAudioManager.requestAudioFocus(afChangeListener,                AudioManager.STREAM_RING,                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

首先进到AudioManager里看一看这个requestAudioFocus方法,最后一直追踪到AudioService里,看一看这里的代码:

public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,            IAudioPolicyCallback pcb) {        // permission checks        if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {            if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {                if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(                            android.Manifest.permission.MODIFY_PHONE_STATE)) {                    Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;                }            } else {                // only a registered audio policy can be used to lock focus                synchronized (mAudioPolicies) {                    if (!mAudioPolicies.containsKey(pcb.asBinder())) {                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");                        return AudioManager.AUDIOFOCUS_REQUEST_FAILED;                    }                }            }        }        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,                clientId, callingPackageName, flags);    }

代码路径:frameworks\base\services\core\java\com\android\server\audio\AudioService.java

上述方法中的AudioSystem.IN_VOICE_COMM_FOCUS_ID的注释是:

/**     * Constant to identify a focus stack entry that is used to hold the focus while the phone     * is ringing or during a call. Used by com.android.internal.telephony.CallManager when     * entering and exiting calls.     */    public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls";

这就显而易见了,原来来电铃声注册音频焦点就会失败。所以requestFocus的判断加在这里并不合适。所以我将代码改成:

private void startLocalPlayer() {        if (mLocalPlayer == null) {            return;        }        synchronized (sActiveRingtones) {            sActiveRingtones.add(this);        }        mLocalPlayer.setOnCompletionListener(mCompletionListener);        requestFocus();        mLocalPlayer.start();    }

判断是否成功什么的,去见鬼吧!

 

 

转载于:https://www.cnblogs.com/wangmengran/p/7526697.html

你可能感兴趣的文章
Java——变量
查看>>
定时关闭AWS上的EC2机器实例
查看>>
grep、awk、sed命令详解1
查看>>
Jenkins邮件配置
查看>>
MYSQL数据库的设计与调优
查看>>
在Apache下开启SSI配置
查看>>
居然有这种操作?各路公司面试题(作者:马克-to-win)
查看>>
Jmeter:图形界面压力测试工具
查看>>
java线程:Atomic(原子)
查看>>
环境准备—之—linux下安装python3和pip3
查看>>
做了这么久的 DBA,你真的认识 MySQL 数据安全体系?【转】
查看>>
Shell习题100例
查看>>
jquery操作select
查看>>
linq 左连接实现两个集合的合并
查看>>
MFC 线程中CWnd对象
查看>>
html文本太长显示为省略号的方法
查看>>
Flask + WSGI + Nginx 云部署
查看>>
站立会议05(冲刺2)
查看>>
Java学习(final、static关键词)
查看>>
怎样判断网址是否被微信封 微信域名检测接口的实现
查看>>