音频焦点问题

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

由于系统中可能会有多个应用程序会播放音频,因此须要考虑他们之间该如何交互,为了不多个应用程序同时播放音乐,Android 系统使用音频焦点来进行统一管理,即只有得到了音频焦点的应用程序才能够播放音乐。 您的应用程序在开始播放音频文件前,首先应该请求得到音频焦点,而且应该同时注册监听音频焦点的丢失通知,即若是音频焦点被系统或其余的应用程序抢占时,您的应用程序能够作出合适的响应。android

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

      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()来实现。而第二个方法就是对音频焦点进行监听并管理。在这里,要先知道以上几个值的含义:post

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

知道了以上几个字段的含义,在对应的状态,咱们就能作相应的处理。好比AUDIOFOCUS_LOSS_TRANSIENT短暂失去焦点,咱们就暂停咱们的音乐。AUDIOFOCUS_LOSS长期失去焦点,就直接停掉音乐。AUDIOFOCUS_GAIN我获取了焦点,那么我就要开始播放音乐了(因为我彻底获取了焦点,其余音乐就没法播放了,天然当前就只有一个音乐进行播放)。this

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

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

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

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

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

================================================================================================server

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

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

    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();
    }

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

相关文章
相关标签/搜索