android AudioFocus处理流程

android系统允许2个或以上的android应用同时向同一个输出流播放音频,系统会将所有的系统会将所有音频流混合在一起。

以下分析是基于android-10.0.0_r36代码

AudioFocus采用合作模式,行为恰当的音频应用应根据以下一般准则来管理音频焦点

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

关于android AudioFocus的使用,先看官网的一段代码说明

  1. 获取AudioManager对象
  2. 实现AudioFocusChangeListener接口
  3. 构造AudioFocusRequest对象
  4. 调用audioManager.requestAudioFocus方法来获取焦点并处理结果
    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;
            }
        }
    }
    

关于onAudioFocusChange回调的说明:

调用requestAudioFocus的时候,如果获取成功,是立即返回的,不会触发onAudioFocusChange里面的AudioManager.AUDIOFOCUS_GAIN这个场景;onAudioFocusChange是这之后的状态发生变化的时候才会触发,比较说主动调用abandonAudioFocus、或者是其它应用也调用了requestAudioFocus,相应的状态变化的时候才触发

相关代码路径

frameworks/base/media/java/android/media/AudioFocusRequest.java
frameworks/base/media/java/android/media/AudioAttributes.java
frameworks/base/media/java/android/media/AudioManager.java
AudioManager.OnAudioFocusChangeListener
frameworks/base/media/java/android/media/AudioSystem.java
frameworks/base/media/java/android/media/AudioFocusInfo.java

frameworks/base/services/core/java/com/android/server/audio/AudioService.java
frameworks/base/services/core/java/com/android/server/audio/FocusRequest.java
frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java

先了解一些基础定义

支持的stream

    public static final String[] STREAM_NAMES = new String[] {
        "STREAM_VOICE_CALL",
        "STREAM_SYSTEM",
        "STREAM_RING",
        "STREAM_MUSIC",
        "STREAM_ALARM",
        "STREAM_NOTIFICATION",
        "STREAM_BLUETOOTH_SCO",
        "STREAM_SYSTEM_ENFORCED",
        "STREAM_DTMF",
        "STREAM_TTS",
        "STREAM_ACCESSIBILITY"
    };

获取音频焦点状态可能的结果,具体的使用看代码里面的说明,很清楚

其中GAIN有4种

  • AUDIOFOCUS_GAIN 永久获取焦点
  • AUDIOFOCUS_GAIN_TRANSIENT 临时获取焦点
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 临时获取焦点,其它应用可以降低音量
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 临时获取焦点,不会让出

LOSS有3种

  • AUDIOFOCUS_LOSS 永久失去焦点
  • AUDIOFOCUS_LOSS_TRANSIENT 临时失去焦点
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 临时失去焦点,可以不静音
    /**
     * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
     */
    public static final int AUDIOFOCUS_GAIN = 1;
    /**
     * Used to indicate a temporary gain or request of audio focus, anticipated to last a short
     * amount of time. Examples of temporary changes are the playback of driving directions, or an
     * event notification.
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
    /**
     * Used to indicate a temporary request of audio focus, anticipated to last a short
     * amount of time, and where it is acceptable for other audio applications to keep playing
     * after having lowered their output level (also referred to as "ducking").
     * Examples of temporary changes are the playback of driving directions where playback of music
     * in the background is acceptable.
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
    /**
     * Used to indicate a temporary request of audio focus, anticipated to last a short
     * amount of time, during which no other applications, or system components, should play
     * anything. Examples of exclusive and transient audio focus requests are voice
     * memo recording and speech recognition, during which the system shouldn't play any
     * notifications, and media playback should have paused.
     */
    public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
    /**
     * Used to indicate a loss of audio focus of unknown duration.
     */
    public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN;
    /**
     * Used to indicate a transient loss of audio focus.
     */
    public static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT;
    /**
     * Used to indicate a transient loss of audio focus where the loser of the audio focus can
     * lower its output volume if it wants to continue playing (also referred to as "ducking"), as
     * the new focus owner doesn't require others to be silent.
     */
    public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK =
            -1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;

关键属性和方法说明

  • AudioFocusRequest代表一个音频焦点请求
  • AudioPolicy 提供音频输出和焦点管理
    • AudioPolicyFocusListener mFocusListener;
  • FocusRequestInfo 对AudioFocusRequest的一个封装,
  • FocusRequester 用来处理和audio focus相关的所有信息的类
    • IAudioFocusDispatcher mFocusDispatcher 可能为空,用来回调注册的AudioFocusChangeListener
  • MediaFocusControl 真正实现音频焦点控制的类
    • Stack mFocusStack 用来保存请求的栈

调用流程说明

AudioManager.requestAudioFocus --> AudioService.requestAudioFocus --> MediaFocusControl.requestAudioFocus

先看一下AudioManager.requestAudioFocus方法,里面有一个地方要关注,就是我们实现的Listner对象保存在mAudioFocusIdListenerMap里面

    private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap =
            new ConcurrentHashMap<String, FocusRequestInfo>();
            
    public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
        // 将Listener保存到mAudioFocusIdListenerMap里面
        registerAudioFocusRequest(afr);
        final IAudioService service = getService();
        final int status;

        final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        final BlockingFocusResultReceiver focusReceiver;
        synchronized (mFocusRequestsLock) {
            try {
                // 调用AudioService的requestAudioFocus
                status = service.requestAudioFocus(afr.getAudioAttributes(),
                        afr.getFocusGain(), mICallBack,
                        mAudioFocusDispatcher,
                        clientId,
                        getContext().getOpPackageName() /* package name */, afr.getFlags(),
                        ap != null ? ap.cb() : null,
                        sdk);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
                // default path with no external focus policy
                return status;
            }
        }
    }

最终的实现在MediaFocusControl里面

   protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
            IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
            int flags, int sdk, boolean forceDuck) {
        //1.做前期的一些检查动作
        if (!cb.pingBinder()) {
            Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
        }

        if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
                callingPackageName) != AppOpsManager.MODE_ALLOWED) {
            return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
        }

        synchronized(mAudioFocusLock) {
            // 2.超过stack的最大值100,则直接返回失败
            if (mFocusStack.size() > MAX_STACK_SIZE) {
                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
           // 3.判断通话状态
            boolean enteringRingOrCall = !mRingOrCallActive
                    & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
            if (enteringRingOrCall) { mRingOrCallActive = true; }

            final AudioFocusInfo afiForExtPolicy;
            if (mFocusPolicy != null) {
                // construct AudioFocusInfo as it will be communicated to audio focus policy
                afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
                        clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
                        flags, sdk);
            } else {
                afiForExtPolicy = null;
            }

            // 4.判断是否需要延时处理
            boolean focusGrantDelayed = false;
            if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                    // granted right now, so the requester will be inserted in the focus stack
                    // to receive focus later
                    focusGrantDelayed = true;
                }
            }

            // 5.如果外部的焦点policy,则直接返回
            if (mFocusPolicy != null) {
                if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
                    // stop handling focus request here as it is handled by external audio
                    // focus policy (return code will be handled in AudioManager)
                    return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
                } else {
                    // an error occured, client already dead, bail early
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }
            // 6.如果当前栈顶的应用和请求的客户端是一致的,则直接返回AUDIOFOCUS_REQUEST_GRANTED
            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                // if focus is already owned by this client and the reason for acquiring the focus
                // hasn't changed, don't do anything
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    // unlink death handler so it can be gc'ed.
                    // linkToDeath() creates a JNI global reference preventing collection.
                    cb.unlinkToDeath(afdh, 0);
                    notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
                // the reason for the audio focus request has changed: remove the current top of
                // stack and respond as if we had a new focus owner
                if (!focusGrantDelayed) {
                    mFocusStack.pop();
                    // the entry that was "popped" is the same that was "peeked" above
                    fr.release();
                }
            }

            // 7.如果在栈中已经存在,则删除
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);

            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
            if (focusGrantDelayed) {
                // focusGrantDelayed being true implies we can't reassign focus right now
                // which implies the focus stack is not empty.
                final int requestResult = pushBelowLockedFocusOwners(nfr);
                if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                }
                return requestResult;
            } else {
                // 8.先回调栈中Request的Listener来处理焦点失去的结果,然后把当前的请求添加到栈顶
                if (!mFocusStack.empty()) {
                    propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
                }
                // push focus requester at the top of the audio focus stack
                mFocusStack.push(nfr);
                nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            }
            notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            // 9.如果是通话状态,先延时100ms,然后再检查通话状态,如果是在通话中,则mutePlayersForCall
            if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
                runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
            }
        }//synchronized(mAudioFocusLock)

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

里面定义了各种场景下的处理原则:

  • 如果是外部自定义的AudioPolicy,交给自定义的AudioPolicy来处理并返回
  • 如果是同一个应用的多次请求且当前栈顶对象就是请求的对象,分2种场景:
    • focusChangeHint和flags没有变化,则直接返回AUDIOFOCUS_REQUEST_GRANTED
    • 如果发生变化,则把当前的栈顶对象出栈,后续把新的对象加入栈中
  • 如果栈中存在该client之前的请求,则先删除栈中已经存在的请求对象
  • 如果有延时,则把请求对象加入到栈中pushBelowLockedFocusOwners,位于当前焦点的下一个
  • 如果没有延时
    • 当栈中有其它对象时,通知Audio loss给栈中的其它对象,会回调注册的监听器的onAudioFocusChange方法,来处理焦点变化 ;如果是AudioManager.AUDIOFOCUS_LOSS,则把栈中的对象删除掉(因为是长时间失去焦点,不应该保存在focus stack里面,下次需要获取的时候再申请)
    • 将当前请求加入栈中,并处理handleFocusGainFromRequest,最终调用的是unduckPlayers
  • 如果是通话状态,则mutePlayersForCall

另外propagateFocusLossFromGain_syncAf方法里面的功能需要重点看一下,和其它应用协调焦点控制的一个关键点。此方法的调用是在mFocusStack.push(nfr)之前的,所以调用requestAudioFocus的请求client不在栈中,根据handleFocusLossFromGain的结果来决定是否从栈中删除其它的request对象,如果是AUDIOFOCUS_LOSS(长时间失去焦点),则从focusStack里面移除

    private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
            boolean forceDuck) {
        final List<String> clientsToRemove = new LinkedList<String>();
        // going through the audio focus stack to signal new focus, traversing order doesn't
        // matter as all entries respond to the same external focus gain
        for (FocusRequester focusLoser : mFocusStack) {
            final boolean isDefinitiveLoss =
                    focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
            if (isDefinitiveLoss) {
                clientsToRemove.add(focusLoser.getClientId());
            }
        }
        for (String clientToRemove : clientsToRemove) {
            removeFocusStackEntry(clientToRemove, false /*signal*/,
                    true /*notifyFocusFollowers*/);
        }
    }

removeFocusStackEntry方法里面的实现说明:

  • 当client和栈顶对象一样,如主动调用abandonAudioFocus的时候,则把栈顶的对象出栈;如果signal为true,则通知当前栈顶的对象获取焦点,回调onAudioFocusChange
  • 如果是client和栈顶对象不一样,则删除栈中的对象
    private void removeFocusStackEntry(String clientToRemove, boolean signal,
            boolean notifyFocusFollowers) {
        // is the current top of the focus stack abandoning focus? (because of request, not death)
        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
        {
            //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
            FocusRequester fr = mFocusStack.pop();
            fr.release();
            if (notifyFocusFollowers) {
                final AudioFocusInfo afi = fr.toAudioFocusInfo();
                afi.clearLossReceived();
                notifyExtPolicyFocusLoss_syncAf(afi, false);
            }
            if (signal) {
                // notify the new top of the stack it gained focus
                notifyTopOfAudioFocusStack();
            }
        } else {
            // focus is abandoned by a client that's not at the top of the stack,
            // no need to update focus.
            // (using an iterator on the stack so we can safely remove an entry after having
            //  evaluated it, traversal order doesn't matter here)
            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();
                    // stack entry not used anymore, clear references
                    fr.release();
                }
            }
        }
    }

具体的过程可以参考一下如下Log打印

// 录像的时候,首次调用requestAudioFocus的时候,mFocusStack.empty()为true
08-22 18:42:17.318  1282  1751 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3381 clientId=android.media.AudioManager@32cf82c callingPack=com.android.camera2 req=1 flags=0x0 sdk=28

// 第7步中stack为空,直接返回
08-22 18:42:17.323  1282  1751 W MediaFocusControl: removeFocusStackEntry clientToRemove=android.media.AudioManager@32cf82c ; signal = false ; notifyFocusFollowers = false
// 走到第8步,调用mFocusStack.push(nfr),把当前请求加入栈中
08-22 18:42:17.326  1282  1751 W MediaFocusControl: mFocusStack.push(nfr)   source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags:  -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
// push之后打印stack里面的元素,只有上一步添加的
08-22 18:42:17.327  1282  1751 W MediaFocusControl: after push itr.next() =    source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags:  -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28

// 再次录像的时候,调用requestAudioFocus,走的第6步,当前栈顶和请求的是同一个client,直接返回了
08-22 18:42:25.012  1282  1777 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3381 clientId=android.media.AudioManager@32cf82c callingPack=com.android.camera2 req=1 flags=0x0 sdk=28
08-22 18:42:25.014  1282  1777 W MediaFocusControl: mFocusStack.peek() =   source:android.os.BinderProxy@a2d47f3 -- pack: com.android.camera2 -- client: android.media.AudioManager@32cf82c -- gain: GAIN -- flags:  -- loss: none -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28


// 调用jcvideoplayer播放之前录制的视频
08-22 18:43:01.777  1313  2746 I MediaFocusControl: requestAudioFocus() from uid/pid 10105/3961 clientId=android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 callingPack=com.xghotplay.bluedo req=2 flags=0x0 sdk=28

// 在第7步前添加了打印,新的请求没有加入的时候,栈中只有一个元素
08-22 18:43:01.779  1313  2746 V MediaFocusControl: dispatching LOSS_TRANSIENT to android.media.AudioManager@1ea53f5
08-22 18:43:01.779  1313  2746 W MediaFocusControl: mFocusStack.push(nfr)   source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags:  -- loss: none -- notified: true -- uid: 10105 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
08-22 18:43:01.779  1313  2746 V AudioService.PlaybackActivityMonitor: unduckPlayers: uids winner=10105
08-22 18:43:01.779  1313  2746 V AudioService.PlaybackActivityMonitor: DuckingManager: unduckUid() uid:10105

// 把jcvideoplayer的client添加到stack里面,里面有2个元素,因为jcvideoplayer请求的是LOSS_TRANSIENT ,所以在propagateFocusLossFromGain_syncAf没有删除,所以有2个元素
08-22 18:43:01.780  1313  2746 W MediaFocusControl: after push itr.next() =    source:android.os.BinderProxy@5b076bc -- pack: com.android.camera2 -- client: android.media.AudioManager@1ea53f5 -- gain: GAIN -- flags:  -- loss: LOSS_TRANSIENT -- notified: true -- uid: 10090 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28
08-22 18:43:01.780  1313  2746 W MediaFocusControl: after push itr.next() =    source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags:  -- loss: none -- notified: true -- uid: 10105 -- attr: AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_MUSIC flags=0x800 tags= bundle=null -- sdk:28

// jcvideoplayer再次请求,因为当前栈栈元素就是jcvideoplayer的,直接返回了
08-22 18:43:12.358  1313  2868 I MediaFocusControl: requestAudioFocus() from uid/pid 10105/3961 clientId=android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 callingPack=com.xghotplay.bluedo req=2 flags=0x0 sdk=28
08-22 18:43:12.359  1313  2868 W MediaFocusControl: mFocusStack.peek() =   source:android.os.BinderProxy@e8cbb7b -- pack: com.xghotplay.bluedo -- client: android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8 -- gain: GAIN_TRANSIENT -- flags:  -- loss

// camera再次请求焦点
08-22 18:43:42.734  1313  2868 I MediaFocusControl: requestAudioFocus() from uid/pid 10090/3453 clientId=android.media.AudioManager@1ea53f5 callingPack=com.android.camera2 req=1 flags=0x0 sdk=28

// propagateFocusLossFromGain_syncAf里面把之前的jcvideoplayer的请求删除,因为是LOSS 
08-22 18:43:42.737  1313  2868 I MediaFocusControl: AudioFocus  removeFocusStackEntry(): removing entry for android.media.AudioManager@1ea53f5
08-22 18:43:42.737  1313  2868 V MediaFocusControl: dispatching LOSS to android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8

// 回调JCVideoPlayer的onAudioFocusChange
08-22 18:43:42.738  3961  3961 D AudioManager: dispatching onAudioFocusChange(-1) to android.media.AudioManager@31a3c1bfm.jiecao.jcvideoplayer_lib.JCVideoPlayer$1@85e01b8

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值