CarAudio(四)系統(tǒng)音頻焦點(diǎn)

概念

在手機(jī)的場(chǎng)景中,我們會(huì)看到晓猛,當(dāng)使用一個(gè)音樂播放器播放音樂時(shí),再打開另一個(gè)音樂播放器播放音樂凡辱,這時(shí)候戒职,上一個(gè)應(yīng)用的音樂會(huì)暫停,這就是音頻焦點(diǎn)器的作用透乾。應(yīng)用可以通過調(diào)用AudioManager的requestAudioFocus方法進(jìn)行焦點(diǎn)的請(qǐng)求洪燥,并注冊(cè)O(shè)nAudioFocusChangeListener監(jiān)聽得知焦點(diǎn)的變化。從而控制音頻的播放乳乌,暫停捧韵,資源釋放等邏輯。

1.應(yīng)用請(qǐng)求焦點(diǎn)方法

音頻焦點(diǎn)為了解決同時(shí)處理多個(gè)音頻播放汉操,音頻混亂播放問題制定的邏輯再来。我們應(yīng)用可以通過獲取AudioManager服務(wù),調(diào)用requestAudioFocus方法進(jìn)行調(diào)用磷瘤。下面為一個(gè)音樂播放器請(qǐng)求焦點(diǎn)示例:

1).獲取AudioManager服務(wù)芒篷。

mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

2).設(shè)置音頻屬性。設(shè)置音頻的音頻類型及使用場(chǎng)景采缚,在CarAudio中可通過設(shè)置的使用場(chǎng)景分配焦點(diǎn)针炉。

mAudioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();

3).創(chuàng)建一個(gè)用來播放音頻的MediaPlayer,將音頻屬性AudioAttributes傳入MediaPlayer中扳抽。

if (mediaPlayer == null){
            mediaPlayer = new MediaPlayer();
        }
        mediaPlayer.setAudioAttributes(mAudioAttributes);

4).實(shí)現(xiàn)OnAudioFocusChangeListener監(jiān)聽篡帕,用來監(jiān)聽焦點(diǎn)變化

 AudioManager.OnAudioFocusChangeListener changeListener = focusChange -> {
        switch (focusChange){
            case AudioManager.AUDIOFOCUS_LOSS://長(zhǎng)期失去焦點(diǎn),停止播放贸呢,可進(jìn)行音頻資源釋放操作
                closeMedia();
                break;
            case AudioManager.AUDIOFOCUS_GAIN://獲取到了當(dāng)前焦點(diǎn)镰烧,繼續(xù)播放音頻
                playMedia();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://暫時(shí)失去焦點(diǎn),暫停播放
                stopMedia();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://暫時(shí)失去焦點(diǎn)贮尉,可播放聲音拌滋,但聲音需減低
                break;
        }
    };

5).構(gòu)造焦點(diǎn)請(qǐng)求,傳入我們申請(qǐng)的焦點(diǎn)類型FocusGain猜谚,音頻屬性和焦點(diǎn)監(jiān)聽器败砂。

 mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(mAudioAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener(changeListener)
                .build();

6).調(diào)用requestAudioFocus請(qǐng)求焦點(diǎn)赌渣,根據(jù)請(qǐng)求到的結(jié)果,進(jìn)行音頻播放

 int res =  mAudioManager.requestAudioFocus(mFocusRequest);
        switch (res){
            case AudioManager.AUDIOFOCUS_REQUEST_FAILED://申請(qǐng)焦點(diǎn)失敗昌犹,不執(zhí)行播放
                break;
            case AudioManager.AUDIOFOCUS_REQUEST_GRANTED://申請(qǐng)焦點(diǎn)成功坚芜,執(zhí)行播放
                isPlayDelayed = false;
                playMedia();
                break;
            case AudioManager.AUDIOFOCUS_REQUEST_DELAYED://申請(qǐng)焦點(diǎn)延時(shí),不執(zhí)行播放斜姥,我們播放音樂時(shí)會(huì)有這種狀態(tài)鸿竖,
            //如果返回值為delay時(shí),后續(xù)的播放可在收到對(duì)應(yīng)的焦點(diǎn)變化監(jiān)聽OnAudioFocusChangeListener中執(zhí)行铸敏。
                isPlayDelayed = true;
                break;
        }

音頻進(jìn)行焦點(diǎn)的申請(qǐng)缚忧,大致就分為這幾步,我們需要在焦點(diǎn)申請(qǐng)成功失敗后杈笔,及焦點(diǎn)丟失和重新獲取時(shí)對(duì)音頻進(jìn)行播放暫停處理恐锣。系統(tǒng)的音頻焦點(diǎn)屬于弱管理逗旁,并不是一定得申請(qǐng)焦點(diǎn)后蚤吹,才能進(jìn)行播放娃兽,只是作為建議,建議應(yīng)用做這樣操作禁筏,從而獲得更好的用戶體驗(yàn)持钉。

2.AudioFocusRequest介紹

申請(qǐng)焦點(diǎn)最重要的是構(gòu)建一個(gè)焦點(diǎn)請(qǐng)求對(duì)象AudioFocusRequest,AudioFocusRequest封裝了申請(qǐng)音頻焦點(diǎn)信息篱昔,通過Builder的方式進(jìn)行創(chuàng)建每强, AudioFocusRequest. Builder提供了以下一些方法。

1.Builder(int focusGain):Builder構(gòu)造器創(chuàng)建的時(shí)候必須先傳入一個(gè)focusGain必須先傳入申請(qǐng)焦點(diǎn)的類型旱爆,表示應(yīng)用需要請(qǐng)求一個(gè)什么類型的焦點(diǎn)focusGain類型分為以下幾種:

  • AUDIOFOCUS_GAIN:長(zhǎng)時(shí)間獲取焦點(diǎn)舀射,一般用于音視頻。
  • AUDIOFOCUS_GAIN_TRANSIENT:短暫獲得怀伦,一般用于電話脆烟,語音助理等
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:混音,一般用于導(dǎo)航
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:與AUDIOFOCUS_GAIN_TRANSIENT類似房待,表示一個(gè)短暫的獲取焦點(diǎn)邢羔,一般用于語音識(shí)別什么的,很少用桑孩。

2.setFocusGain(int focusGain):一樣的是傳入focusGain申請(qǐng)的焦點(diǎn)類型

3.setOnAudioFocusChangeListener(@NonNull OnAudioFocusChangeListener listener):設(shè)置焦點(diǎn)變化的監(jiān)聽器OnAudioFocusChangeListener

4.setOnAudioFocusChangeListenerInt(OnAudioFocusChangeListener listener, Handler handler):設(shè)置焦點(diǎn)變化的監(jiān)聽器拜鹤,同時(shí)傳入一個(gè)Handler,指定listenter回調(diào)到handler線程流椒。代表可以將焦點(diǎn)變化的監(jiān)聽回調(diào)到handler中執(zhí)行敏簿。而沒有handler只能默認(rèn)回調(diào)到與我們初始化AudioManager的那個(gè)線程,容易導(dǎo)致listener的回調(diào)卡頓,這樣造成的后果就是我們要停止的聲音沒有及時(shí)停止而造成短暫的混音惯裕。

5.setWillPauseWhenDucked(boolean pauseOnDuck):當(dāng)有一個(gè)音頻申請(qǐng)的焦點(diǎn)類型為AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK時(shí)温数,之前棧頂?shù)囊纛l焦點(diǎn)的音頻,系統(tǒng)會(huì)自動(dòng)進(jìn)行降音處理蜻势,而這個(gè)方法設(shè)為true時(shí)就表示撑刺,取消系統(tǒng)的降音處理,自己通過焦點(diǎn)監(jiān)聽器變AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK狀態(tài)時(shí)握玛,應(yīng)用自行進(jìn)行降音處理够傍。

6.setAcceptsDelayedFocusGain(boolean acceptsDelayedFocusGain):設(shè)置是否延遲請(qǐng)求焦點(diǎn)。只有設(shè)置為true挠铲,requestAudioFocus才能返回AUDIOFOCUS_REQUEST_DELAYED延時(shí)請(qǐng)求冕屯,之后才可在焦點(diǎn)監(jiān)聽器中進(jìn)行處理。否則需要進(jìn)行延時(shí)處理時(shí)市殷,焦點(diǎn)將直接申請(qǐng)失敗愕撰。

7.setLocksFocus(boolean focusLocked):設(shè)置是否鎖定焦點(diǎn),需要設(shè)置為true時(shí)醋寝,需要給AudioManager傳入一個(gè)AudioPolicy才能使用。普通應(yīng)用不可使用带迟。

8.setForceDucking(boolean forceDucking):當(dāng)使用場(chǎng)景是USAGE_ASSISTANCE_ACCESSIBILITY且申請(qǐng)的是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK焦點(diǎn)類型是音羞,會(huì)強(qiáng)制降低其他音源。主要用于無障礙服務(wù)中仓犬。

9.setAudioAttributes(@NonNull AudioAttributes attributes):設(shè)置音頻屬性AudioAttributes 嗅绰。AudioAttributes音頻屬性定義音頻系統(tǒng)如何處理指定源的路由、音量和焦點(diǎn)決策的屬性搀继。系統(tǒng)Audio中很多地方都使用了這個(gè)音頻屬性窘面。AudioArrtibutes是同過也是通過Builder構(gòu)造器進(jìn)行參數(shù)設(shè)置的。 AudioAttributes主要的屬性為usagecontentType叽躯。

3 AudioAttributes介紹

代碼路徑:/frameworks/base/media/java/android/media/AudioAttributes.java

用來定義音頻系統(tǒng)如何處理指定源的路由财边、音量和焦點(diǎn)決策的屬性。系統(tǒng)Audio中很多地方都使用了這個(gè)音頻屬性点骑。

AudioArrtibutes是同過Builder方法進(jìn)行構(gòu)造的酣难,AudioArrtibutes.Builder類內(nèi)進(jìn)行如下方法構(gòu)建。

方法 描述
setUsage(@AttributeSdkUsage int usage) 設(shè)置音頻使用場(chǎng)景黑滴,用來描述為什么要播放這個(gè)音頻憨募,是用來導(dǎo)航/鬧鐘/播放媒體?
setSystemUsage(@AttributeSystemUsage int systemUsage) 設(shè)置為系統(tǒng)使用的音頻
setContentType(@AttributeContentType int contentType) 設(shè)置音頻內(nèi)容袁辈,用來描述這個(gè)音頻的內(nèi)容是什么菜谣,是語音/音樂....
setFlags(int flags) 設(shè)置標(biāo)志的組合,F(xiàn)lag用來描述這個(gè)音頻的輸出標(biāo)識(shí),比如:低時(shí)延輸出尾膊、強(qiáng)制可聽……
setHotwordModeEnabled(boolean enable) 是否在hotword模式下請(qǐng)求捕獲甘磨。實(shí)際上是添加一個(gè)FLAG_HW_HOTWORDflag
setAllowedCapturePolicy(@CapturePolicy int capturePolicy) 指定音頻是否可以被其他應(yīng)用程序或系統(tǒng)捕獲
addBundle(@NonNull Bundle bundle) 攜帶Bundle數(shù)據(jù)
addTag(String tag) 添加標(biāo)志
setLegacyStreamType(int streamType) 使用舊的streamType的類型,對(duì)應(yīng)usage眯停,和setUsage不可同時(shí)使用
setCapturePreset(int preset) 設(shè)置捕獲預(yù)設(shè)济舆。在AudioRecord錄音時(shí)使用
setInternalCapturePreset(int preset) hotword模式下進(jìn)行捕獲預(yù)設(shè)
setHapticChannelsMuted(boolean muted) 設(shè)置播放音頻時(shí),是否可震動(dòng)
  • setUsage:音頻使用場(chǎng)景莺债,用來描述為什么要播放這個(gè)音頻滋觉,是用來導(dǎo)航/鬧鐘/播放媒體?

             *     {@link AttributeSdkUsage#USAGE_MEDIA},
             *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION},
             *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING},
             *     {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_INSTANT},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_DELAYED},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANT},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION},
             *     {@link AttributeSdkUsage#USAGE_GAME}.
    
  • setContentType:用來描述這個(gè)音頻的內(nèi)容是什么齐邦,是語音/音樂....

             *     {@link AudioAttributes#CONTENT_TYPE_MOVIE},
             *     {@link AudioAttributes#CONTENT_TYPE_MUSIC},
             *     {@link AudioAttributes#CONTENT_TYPE_SONIFICATION},
             *     {@link AudioAttributes#CONTENT_TYPE_SPEECH},
             *     {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}.
    
  • setAllowedCapturePolicy:指定音頻是否可以被其他應(yīng)用程序或系統(tǒng)捕獲椎侠。定義了如下三個(gè)值:

    public static final int ALLOW_CAPTURE_BY_ALL = 1;//可被任何應(yīng)用截取
    public static final int ALLOW_CAPTURE_BY_SYSTEM = 2;//只能被系統(tǒng)應(yīng)用截取
    public static final int ALLOW_CAPTURE_BY_NONE = 3;//表示任何應(yīng)用都不能進(jìn)行截取
    

4.應(yīng)用申請(qǐng)音頻焦點(diǎn)的代碼流程

焦點(diǎn)的申請(qǐng)經(jīng)過AudioManager.java-->AudioService.java --> MediaFocusControl.java的流程來申請(qǐng)的。現(xiàn)在我們看下每個(gè)類里面都進(jìn)行了哪些操作措拇。先看AudioManager.java中做了什么我纪。

1.AudioManager.requestAudioFocus

代碼路徑:/frameworks/base/media/java/android/media/AuidoManager.java

    public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest) {
        return requestAudioFocus(focusRequest, null /* no AudioPolicy*/);
    }
@SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
         ...
        //1.進(jìn)行l(wèi)ocksFocus檢測(cè),在Builder時(shí)設(shè)置了setLocksFocus(true)但AudioPolicy為null丐吓,則不能進(jìn)行焦點(diǎn)申請(qǐng)浅悉。
        if (afr.locksFocus() && ap == null) {
            throw new IllegalArgumentException(
                    "Illegal null audio policy when locking audio focus");
        }
        //2.注冊(cè)監(jiān)聽,構(gòu)造FocusRequestInfo券犁,將焦點(diǎn)變化監(jiān)聽器的哈希值做為key术健,如果沒有,則直接以當(dāng)前AudioManager的哈希值作為key粘衬,
        //FocusRequestInfo作為value荞估,添加到mAudioFocusIdListenerMap監(jiān)聽列表里
        registerAudioFocusRequest(afr);
        
        //3.獲取AudioService,sdk稚新,clientId(與mAudioFocusIdListenerMap中的key對(duì)應(yīng))
        final IAudioService service = getService();
        final int status;
        int sdk;...
        final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        final BlockingFocusResultReceiver focusReceiver;
        synchronized (mFocusRequestsLock) {
            try {
                //4.調(diào)用到AudioService的requestAudioFocus方法勘伺,傳入mAudioFocusDispatcher和clientId,
                //mAudioFocusDispatcher作為AudioManager中的焦點(diǎn)監(jiān)聽褂删,進(jìn)行應(yīng)用焦點(diǎn)監(jiān)聽分配回調(diào)
                status = service.requestAudioFocus(afr.getAudioAttributes(),
                        afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,
                        getContext().getOpPackageName() /* package name */, afr.getFlags(),
                        ap != null ? ap.cb() : null,sdk);
            ...
            //5.返回的狀態(tài)不為AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY時(shí)飞醉,正常返回
            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
                // default path with no external focus policy
                return status;
            }
            //6.status為AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY時(shí),
            //將通過BlockingFocusResultReceiver等待調(diào)用setFocusRequestResult之后拿到結(jié)果返回
            //該情況用于車載的CarAudio焦點(diǎn)分配笤妙。需要等待時(shí)創(chuàng)建一個(gè)BlockingFocusResultReceiver等待線程冒掌,
            //focusReceiver為等待列表,以clientId為key蹲盘,等待200ms后如果焦點(diǎn)狀態(tài)還未在BlockingFocusResultReceiver
            //中賦值股毫,則超時(shí)報(bào)錯(cuò),否則正常返回申請(qǐng)結(jié)果召衔。
            if (mFocusRequestsAwaitingResult == null) {
                mFocusRequestsAwaitingResult =
                        new HashMap<String, BlockingFocusResultReceiver>(1);
            }
            focusReceiver = new BlockingFocusResultReceiver(clientId);
            mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
        }
        focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
        if (DEBUG && !focusReceiver.receivedResult()) {
            Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
        }
        synchronized (mFocusRequestsLock) {
            mFocusRequestsAwaitingResult.remove(clientId);
        }
        return focusReceiver.requestResult();
    }
//2.注冊(cè)監(jiān)聽铃诬,構(gòu)造FocusRequestInfo,將焦點(diǎn)變化監(jiān)聽器的哈希值做為key,如果沒有趣席,則直接以當(dāng)前AudioManager的哈希值作為key兵志,
        //FocusRequestInfo作為value,添加到mAudioFocusIdListenerMap監(jiān)聽列表里    
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
        final Handler h = afr.getOnAudioFocusChangeListenerHandler();
        final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :
            new ServiceEventHandlerDelegate(h).getHandler());
        final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        mAudioFocusIdListenerMap.put(key, fri);
    }

總結(jié)宣肚,在AuidoManager中存在一個(gè)mAudioFocusDispatcher進(jìn)行焦點(diǎn)變化監(jiān)聽想罕,一個(gè)mAudioFocusIdListenerMap集合管理所有的焦點(diǎn)請(qǐng)求FocusRequestInfo,并通過OnAudioFocusChangeListener的哈希值作為key進(jìn)行關(guān)聯(lián)霉涨。具體請(qǐng)求結(jié)果再調(diào)用到AudioService中按价,我們?cè)诳聪翧udioService.java中的代碼。

2.AudioService.requestAudioFocus

frameworks/base/services/core/java/com/android/server/audio/AuidoService.java

    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
            IAudioPolicyCallback pcb, int sdk) {
        final int uid = Binder.getCallingUid();
        ...
        //做權(quán)限檢測(cè)笙瑟,clientId等非空判斷楼镐,提前返回焦點(diǎn)申請(qǐng)失敗
        ...
        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                clientId, callingPackageName, flags, sdk,
                forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
    }

在AudioService中并沒有做過多的操作,只是進(jìn)行了一些參數(shù)檢測(cè)往枷,權(quán)限檢測(cè)框产,提前返回焦點(diǎn)申請(qǐng)失敗,具體的邏輯是調(diào)用MediaFocusControl中的requestAudioFocus错洁,將其返回的status直接返回給AudioManager秉宿。

3.MediaFocusControl.requestAudioFocus

MediaFocusControl為焦點(diǎn)請(qǐng)求的關(guān)鍵類。MediaFocusControl為焦點(diǎn)申請(qǐng)的關(guān)鍵類墓臭,其中包含焦點(diǎn)請(qǐng)求堆棧mFocusStack

protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
            IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
            int flags, int sdk, boolean forceDuck, int testUid) {
       ...
        synchronized(mAudioFocusLock) {
           //1.判斷焦點(diǎn)申請(qǐng)個(gè)數(shù)是否已滿蘸鲸。mFocusStack為焦點(diǎn)<FocusRequester>的堆棧,棧頂則為當(dāng)前占用的焦點(diǎn)窿锉。
           //最大個(gè)數(shù)為100個(gè)。超過100個(gè)返回焦點(diǎn)請(qǐng)求失敗
            if (mFocusStack.size() > MAX_STACK_SIZE) {
                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
            //2.判斷是否是系統(tǒng)手機(jī)電話音頻膝舅。設(shè)置mRingOrCallActive標(biāo)志為true
            //AudioSystem.IN_VOICE_COMM_FOCUS_ID這個(gè)類型是只用于AudioManager.requestAudioFocusForCall專門進(jìn)行電話音頻焦點(diǎn)申請(qǐng)嗡载。
            boolean enteringRingOrCall = !mRingOrCallActive
                    & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
            if (enteringRingOrCall) { mRingOrCallActive = true; }
            
            //3.重點(diǎn),這里會(huì)根據(jù)mFocusPolicy是否為null仍稀,也就是IAudioPolicyCallback是否為null洼滚,構(gòu)造一個(gè)AudioFocusInfo,
            //用于比如車載的音頻焦點(diǎn)技潘,在CarAudioService創(chuàng)建時(shí)就會(huì)IAudioPolicyCallback傳入遥巴,mFocusPolicy就為CarAudioService中的AudioPolicy。
            //在mFocusPolicy不為null的情況下享幽,將會(huì)返回AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY標(biāo)志铲掐,在AudioManager中等待CarAudio中
            //進(jìn)行完焦點(diǎn)分配后,setFocusRequestResult返回給AudioManager值桩,最后返回給應(yīng)用摆霉。
            final AudioFocusInfo afiForExtPolicy;
           
            if (mFocusPolicy != null) {
                // construct AudioFocusInfo as it will be communicated to audio focus policy
                afiForExtPolicy = new AudioFocusInfo(aa, uid,
                        clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
                        flags, sdk);
            } else {
                afiForExtPolicy = null;
            }
            
            //4.判斷申請(qǐng)結(jié)果是否需要delay,如果當(dāng)前音頻焦點(diǎn)被通話或手機(jī)鈴聲占用時(shí)且請(qǐng)求的焦點(diǎn)設(shè)置了可延時(shí)獲取,
            //則返回AUDIOFOCUS_REQUEST_DELAYED告知焦點(diǎn)請(qǐng)求dealy,否則返回焦點(diǎn)申請(qǐng)失敗携栋。
            boolean focusGrantDelayed = false;
            if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    focusGrantDelayed = true;
                }
            }
            // 接著3的流程搭盾,mFocusPolicy不為null時(shí),調(diào)用notifyExtFocusPolicyFocusRequest_syncAf方法婉支。
            if (mFocusPolicy != null) {
                if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
                    return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
                } else {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }
            //5. 判斷請(qǐng)求焦點(diǎn)的客戶端是否已經(jīng)死亡鸯隅,如果已經(jīng)死亡,則返回焦點(diǎn)請(qǐng)求失敗
            AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
            try {
                cb.linkToDeath(afdh, 0);
            } catch (RemoteException e) {
                // client has already died!
                Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
            //6.排重向挖,如果請(qǐng)求的焦點(diǎn)和當(dāng)前正在占用焦點(diǎn)的clientId是一樣的蝌以,那么不進(jìn)行焦點(diǎn)的修改。直接返回焦點(diǎn)申請(qǐng)成功户誓。
            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    cb.unlinkToDeath(afdh, 0);
                    notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
                if (!focusGrantDelayed) {
                    mFocusStack.pop();
                    fr.release();
                }
            }
            //7.該方法從mFocusStack棧內(nèi)查找是否存在這個(gè)clientId的焦點(diǎn)請(qǐng)求饼灿,有的話進(jìn)行移除,并釋放資源帝美。
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
            
            //8.新建一個(gè)FocusRequester碍彭,用于成功后push到棧頂
            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, uid, this, sdk);
                    
            //9.這段代碼的邏輯為當(dāng)mMultiAudioFocusEnabled這個(gè)變量為true,將并且申請(qǐng)的焦點(diǎn)類型為一個(gè)長(zhǎng)期占用的焦點(diǎn)類型時(shí)悼潭,
            //則通過mMultiAudioFocusList列表進(jìn)行焦點(diǎn)管理庇忌。當(dāng)前申請(qǐng)焦點(diǎn)用于call通話時(shí),直接將前面列表中的所有焦點(diǎn)都變?yōu)長(zhǎng)OSS狀態(tài)舰褪,否則
            //直接返回焦點(diǎn)申請(qǐng)成功皆疹,并添加到mMultiAudioFocusList列表。該模式下占拍,都進(jìn)行混音
            //mMultiAudioFocusEnabled可通過Settings.System.putIntForUser(cr,
            //    Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0, cr.getUserId())進(jìn)行配置略就。
            if (mMultiAudioFocusEnabled
                    && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
                if (enteringRingOrCall) {
                    if (!mMultiAudioFocusList.isEmpty()) {
                        for (FocusRequester multifr : mMultiAudioFocusList) {
                            multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
                        }
                    }
                } else {
                    boolean needAdd = true;
                    if (!mMultiAudioFocusList.isEmpty()) {
                        for (FocusRequester multifr : mMultiAudioFocusList) {
                            if (multifr.getClientUid() == Binder.getCallingUid()) {
                                needAdd = false;
                                break;
                            }
                        }
                    }
                    if (needAdd) {
                        mMultiAudioFocusList.add(nfr);
                    }
                    nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
            }
            if (focusGrantDelayed) {
            //10.如果是焦點(diǎn)申請(qǐng)需要延時(shí),在pushBelowLockedFocusOwners方法中判斷焦點(diǎn)堆棧中如果有被鎖定的焦點(diǎn)
            //(setLocksFocus(true)或 通話和鈴聲焦點(diǎn))且 在棧頂晃酒,則焦點(diǎn)可直接申請(qǐng)成功表牢,否則返回焦點(diǎn)申請(qǐng)需Delay。
                final int requestResult = pushBelowLockedFocusOwners(nfr);
                if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                }
                return requestResult;
            } else {
                //11.返回焦點(diǎn)申請(qǐng)成功贝次,通知之前的焦點(diǎn)FocusRequester失去焦點(diǎn)崔兴,將當(dāng)前焦點(diǎn)push到mFocusStack棧頂
                propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
                mFocusStack.push(nfr);
                nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            }
            // 12. 給AudioPolicy更新焦點(diǎn)申請(qǐng)成功回調(diào)
            notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            //13.ENFORCE_MUTING_FOR_RING_OR_CALL設(shè)置為true時(shí),當(dāng)前申請(qǐng)的焦點(diǎn)為通話時(shí)蛔翅,強(qiáng)制其他所有音頻靜音敲茄,直到通話結(jié)束后恢復(fù)       
            if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
                runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
            }
        }//synchronized(mAudioFocusLock)

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

MediaFocusControlrequestAudioFocus流程中我們注意到,在焦點(diǎn)申請(qǐng)成功的時(shí)候山析,會(huì)執(zhí)行propagateFocusLossFromGain_syncAfq方法去通知當(dāng)前的焦點(diǎn)持有者丟失焦點(diǎn)堰燎。代碼如下:

 @GuardedBy("mAudioFocusLock")
    private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
                                                   boolean forceDuck) {
        final List<String> clientsToRemove = new LinkedList<String>();
        if (!mFocusStack.empty()) {
            for (FocusRequester focusLoser : mFocusStack) {
                final boolean isDefinitiveLoss =
                        focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
                if (isDefinitiveLoss) {
                    clientsToRemove.add(focusLoser.getClientId());
                }
            }
        }
        if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
            for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
                final boolean isDefinitiveLoss =
                        multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
                if (isDefinitiveLoss) {
                    clientsToRemove.add(multifocusLoser.getClientId());
                }
            }
        }
        for (String clientToRemove : clientsToRemove) {
            removeFocusStackEntry(clientToRemove, false /*signal*/,
                    true /*notifyFocusFollowers*/);
        }
    }

其主要邏輯就是迭代焦點(diǎn)棧,執(zhí)行handleFocusLossFromGain把狀態(tài)變?yōu)長(zhǎng)OSS的焦點(diǎn)對(duì)象移除棧內(nèi)盖腿。

而在焦點(diǎn)申請(qǐng)需要Delay時(shí)爽待,則是去執(zhí)行pushBelowLockedFocusOwners方法直接將當(dāng)前焦點(diǎn)對(duì)象插入棧頂后一個(gè)位置损同,這樣當(dāng)當(dāng)前的焦點(diǎn)持有者釋放焦點(diǎn)后,焦點(diǎn)申請(qǐng)者能被push到棧頂鸟款,獲取到焦點(diǎn)膏燃,進(jìn)行音頻播放。代碼如下:

        private int pushBelowLockedFocusOwners(FocusRequester nfr) {
        int lastLockedFocusOwnerIndex = mFocusStack.size();
        for (int index = mFocusStack.size()-1; index >= 0; index--) {
            if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                lastLockedFocusOwnerIndex = index;
            }
        }
        if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
            // this should not happen, but handle it and log an error
            Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                    new Exception());
            propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
            mFocusStack.push(nfr);
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
        }
    }

其主要邏輯為:先迭代焦點(diǎn)棧何什,如果焦點(diǎn)棧內(nèi)存在被鎖定的焦點(diǎn)组哩,則將當(dāng)前焦點(diǎn)插入到鎖定了焦點(diǎn)者后面,等被鎖定的焦點(diǎn)釋放了焦點(diǎn)后才能獲取焦點(diǎn)处渣。如果不存在伶贰,就直接返回申請(qǐng)成功。這一部在我看來其實(shí)是一個(gè)再次檢測(cè)的過程罐栈,因?yàn)槲覀冊(cè)趫?zhí)行這個(gè)方法之前是已經(jīng)檢查過當(dāng)前焦點(diǎn)持有者是否被鎖定黍衙,是的話才走到這。所以大部分的還是會(huì)執(zhí)行insertElementAt這個(gè)方法插入到棧內(nèi)荠诬,等待上一個(gè)焦點(diǎn)釋放琅翻。

最后焦點(diǎn)申請(qǐng)者申請(qǐng)成功,焦點(diǎn)持有者丟失焦點(diǎn)柑贞,會(huì)分別執(zhí)行FocusRequester對(duì)象的 handleFocusGainFromRequesthandleFocusLossFromGain方法方椎。

  • handleFocusGainFromRequest方法比較簡(jiǎn)單,就是執(zhí)行restoreVShapedPlayers方法钧嘶,通知底層可進(jìn)行音頻播放棠众。

        @GuardedBy("MediaFocusControl.mAudioFocusLock")
        void handleFocusGainFromRequest(int focusRequestResult) {
            if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                mFocusController.restoreVShapedPlayers(this);
            }
        }
    
  • handleFocusLossFromGain方法中調(diào)用focusLossForGainRequest方法,根據(jù)上一次焦點(diǎn)的狀態(tài)有决,和當(dāng)前焦點(diǎn)申請(qǐng)者的請(qǐng)求的焦點(diǎn)類型闸拿,返回焦點(diǎn)變化狀態(tài),在handleFocusLoss方法中執(zhí)行IAudioFocusDispatcher(AudioManager中傳入的監(jiān)聽器)的dispatchAudioFocusChange方法书幕,最終將焦點(diǎn)狀態(tài)變化回調(diào)到應(yīng)用的監(jiān)聽器中胸墙。

      @GuardedBy("MediaFocusControl.mAudioFocusLock")
        boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
        {
            final int focusLoss = focusLossForGainRequest(focusGain);
            handleFocusLoss(focusLoss, frWinner, forceDuck);
            return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
        }
        //mFocusLossReceived為當(dāng)前焦點(diǎn)的上一次變化記錄
      private int focusLossForGainRequest(int gainRequest) {
            switch(gainRequest) {
              //請(qǐng)求的焦點(diǎn)類型為長(zhǎng)期占用時(shí),焦點(diǎn)狀態(tài)變?yōu)閬G失LOSS
                case AudioManager.AUDIOFOCUS_GAIN:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        case AudioManager.AUDIOFOCUS_LOSS:
                        case AudioManager.AUDIOFOCUS_NONE:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
                //請(qǐng)求的焦點(diǎn)類型為短期占用按咒,
              //上次焦點(diǎn)狀態(tài)為短暫丟失或混音時(shí),則焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)OSS_TRANSIENT短暫丟失但骨。    
              //上次焦點(diǎn)狀態(tài)為丟失時(shí)励七,則焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)OSS丟失          
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        case AudioManager.AUDIOFOCUS_NONE:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
              //請(qǐng)求的焦點(diǎn)類型為混音
              //上次焦點(diǎn)狀態(tài)為混音時(shí),焦點(diǎn)狀態(tài)變?yōu)镃AN_DUCK可混音狀態(tài)
              //上次焦點(diǎn)狀態(tài)為短暫丟失時(shí)奔缠,焦點(diǎn)狀態(tài)保持LOSS_TRANSIENT
              //上次焦點(diǎn)狀態(tài)為丟失時(shí)掠抬,焦點(diǎn)狀態(tài)保持LOSS
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_NONE:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
                default:
                    Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
                            return AudioManager.AUDIOFOCUS_NONE;
            }
        }
    

5.釋放焦點(diǎn)abandonAudioFocusRequest

看完焦點(diǎn)的申請(qǐng)流程,在看下焦點(diǎn)的釋放流程校哎,在申請(qǐng)成功焦點(diǎn)播放音頻后两波,需要將焦點(diǎn)再次釋放瞳步,否則焦點(diǎn)不會(huì)自動(dòng)釋放。焦點(diǎn)釋放調(diào)用AudioManagerabandonAudioFocusRequest方法腰奋,將申請(qǐng)焦點(diǎn)時(shí)構(gòu)造的AudioFocusRequest傳入单起。和申請(qǐng)焦點(diǎn)的流程類似,最后也是在MediaFocusControl.java中執(zhí)行核心代碼劣坊。

protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
            String callingPackageName) {
       ...
        try {
            synchronized(mAudioFocusLock) {
                if (mFocusPolicy != null) {
                    //mFocusPolicy不為null時(shí)嘀倒,同樣的將焦點(diǎn)釋放給到AudioPolicy中釋放
                    final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
                            clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
                            0 /*flags*/, 0 /* sdk n/a here*/);
                    if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
                        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                    }
                }
                ...
                // 移除焦點(diǎn)
                removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
                //通話焦點(diǎn)則恢復(fù)之前的音頻
                if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
                    runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
                }
            }
        } catch (java.util.ConcurrentModificationException cme) {
            Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
            cme.printStackTrace();
        }
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

關(guān)鍵方法為removeFocusStackEntry,傳入signal,notifyFocusFollowerstrue局冰,接著看removeFocusStackEntry中的代碼邏輯

@GuardedBy("mAudioFocusLock")
    private void removeFocusStackEntry(String clientToRemove, boolean signal,
            boolean notifyFocusFollowers) {
        AudioFocusInfo abandonSource = null;
        //釋放的焦點(diǎn)正處于棧頂测蘑,直接pop出棧
        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
        {
            FocusRequester fr = mFocusStack.pop();
            fr.maybeRelease();
            if (notifyFocusFollowers) {
                abandonSource = fr.toAudioFocusInfo();
            }
            //signal為true時(shí),更新當(dāng)前焦點(diǎn)出棧后的棧頂焦點(diǎn)為AUDIOFOCUS_GAIN
            if (signal) {
                //在該方法中康二,將棧內(nèi)的焦點(diǎn)peek出來碳胳,再執(zhí)行handleFocusGain方法。
                notifyTopOfAudioFocusStack();
            }
        } else {
        //釋放的焦點(diǎn)不在棧頂時(shí)沫勿,直接從棧內(nèi)移除挨约。
         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
         while(stackIterator.hasNext()) {
                FocusRequester fr = stackIterator.next();
                if(fr.hasSameClient(clientToRemove)) {
                    Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
                            + clientToRemove);
                    stackIterator.remove();
                    if (notifyFocusFollowers) {
                        abandonSource = fr.toAudioFocusInfo();
                    }
                    fr.maybeRelease();
                }
           
        }
        if (abandonSource != null) {
            abandonSource.clearLossReceived();
            //通知AudioPolicy當(dāng)前焦點(diǎn)已經(jīng)被釋放
            notifyExtPolicyFocusLoss_syncAf(abandonSource, false);
        }
        ...
    }

6.總結(jié)

至此,我們的audio焦點(diǎn)生命周期已經(jīng)大概都已經(jīng)知曉藕帜。對(duì)以上流程我們進(jìn)行歸納總結(jié):

  • 1.系統(tǒng)音頻焦點(diǎn)在用戶場(chǎng)景上烫罩,只做通話和非通話音頻區(qū)分。
  • 2.系統(tǒng)音頻焦點(diǎn)由mFocusStack堆棧進(jìn)行管理洽故。焦點(diǎn)申請(qǐng)成功贝攒,push到棧頂;焦點(diǎn)釋放时甚,pop出棧隘弊,焦點(diǎn)需延時(shí),inster插入棧內(nèi)荒适。焦點(diǎn)狀態(tài)變?yōu)長(zhǎng)oss梨熙,從棧內(nèi)移除。
  • 3.mFocusPolicy不為null時(shí)刀诬,系統(tǒng)Audio不處理焦點(diǎn)分配咽扇,交由自定義的AudioPolicy去進(jìn)行分配。比如CarAudioFocus
  • 4.當(dāng)前焦點(diǎn)持有者為通話時(shí)陕壹,焦點(diǎn)返回Delay质欲,知道通話焦點(diǎn)釋放,重新獲取焦點(diǎn)糠馆。

場(chǎng)景一:

1.音頻A申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN嘶伟,焦點(diǎn)申請(qǐng)成功后,音頻A播放又碌。

2.音頻B也申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN九昧,焦點(diǎn)申請(qǐng)成功绊袋,音頻B的焦點(diǎn)被push入棧頂。音頻B播放

3.音頻A的焦點(diǎn)被移出Stack內(nèi)铸鹰, A焦點(diǎn)監(jiān)聽器收到AUDIOFOCUS_LOSS 狀態(tài)癌别,再次播放,需重新申請(qǐng)焦點(diǎn)掉奄。

場(chǎng)景二:

1.音頻A為通話音頻规个,申請(qǐng)了焦點(diǎn),通話中姓建。

2.音頻B也申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN诞仓,并設(shè)置了可延時(shí)申請(qǐng),焦點(diǎn)申請(qǐng)delay速兔,音頻B被插入棧內(nèi)墅拭。

3.音頻A的通話結(jié)束,釋放焦點(diǎn)涣狗,音頻A被pop出棧谍婉,音頻B來到棧頂。音頻B監(jiān)聽器收到AUDIOFOCUS_GAIN狀態(tài)镀钓,

可進(jìn)行音頻播放穗熬。

場(chǎng)景三:

1.音頻A申請(qǐng)了一個(gè)長(zhǎng)期焦點(diǎn)AUDIOFOCUS_GAIN,焦點(diǎn)申請(qǐng)成功后丁溅,音頻A播放唤蔗。

2.音頻B申請(qǐng)了一個(gè)短期焦點(diǎn)AUDIOFOCUS_GAIN_TRANSIENT,焦點(diǎn)申請(qǐng)成功窟赏,音頻B的焦點(diǎn)被push入棧頂妓柜。音頻B播放

3.音頻A的焦點(diǎn)監(jiān)聽器中收到AUDIOFOCUS_LOSS_TRANSIENT狀態(tài)。如果音頻B處于stack 0位置涯穷,音頻A焦點(diǎn)則于Stack 1的位置棍掐,還在棧內(nèi)。

4.音頻B停止播放拷况,同時(shí)釋放焦點(diǎn)蝇刀,音頻B的焦點(diǎn)出棧壮啊。

5.音頻A成為了棧頂焦點(diǎn)寺晌,監(jiān)聽器中將收到AUDIOFOCUS_GAIN狀態(tài)噪沙,可繼續(xù)音頻播放吆视。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末映砖,一起剝皮案震驚了整個(gè)濱河市旦棉,隨后出現(xiàn)的幾起案子矗漾,更是在濱河造成了極大的恐慌服爷,老刑警劉巖杜恰,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获诈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡心褐,警方通過查閱死者的電腦和手機(jī)舔涎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逗爹,“玉大人亡嫌,你說我怎么就攤上這事【蚨” “怎么了挟冠?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)袍睡。 經(jīng)常有香客問我知染,道長(zhǎng),這世上最難降的妖魔是什么斑胜? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任控淡,我火速辦了婚禮,結(jié)果婚禮上止潘,老公的妹妹穿的比我還像新娘掺炭。我一直安慰自己,他們只是感情好凭戴,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布涧狮。 她就那樣靜靜地躺著,像睡著了一般簇宽。 火紅的嫁衣襯著肌膚如雪勋篓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天魏割,我揣著相機(jī)與錄音譬嚣,去河邊找鬼。 笑死钞它,一個(gè)胖子當(dāng)著我的面吹牛拜银,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遭垛,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼尼桶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锯仪?” 一聲冷哼從身側(cè)響起泵督,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庶喜,沒想到半個(gè)月后小腊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體救鲤,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年秩冈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了本缠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡入问,死狀恐怖丹锹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芬失,我是刑警寧澤楣黍,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站麸折,受9級(jí)特大地震影響锡凝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垢啼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一窜锯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芭析,春花似錦锚扎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惯疙,卻和暖如春翠勉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霉颠。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工对碌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒿偎。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓朽们,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親诉位。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骑脱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容