Univeral Music Player 源码解析 -- 让人头疼的playback

文章集合:
Universal Music Player 源码解析(一)--MediaSession框架

Univeral Music Player 源码解析 -- 让人头疼的playback

Universal Music Player 源码解析(二)--MusicService 和 MediaController

Universal Music Player 源码分析 (三)-- 其他类分析

项目中的playback层是非常令人头痛的,因为他的回调之多,不亚于MediaSession框架使用的回调,而且这里是播放器主要的功能的看点所在,所以这里的理解也要比其他地方更加深刻才可以,下面,我从项目目录入手剖析一下:

看完这篇文章你将会知道这样几个部分:

  • LocalPlayback 和 PlaybackManager 的关系
  • QueueManager的介绍
  • MusicService 和 MediaSession的关系
image.png

除了castplayback其他的都需要仔细理解,因为这个是用于电视上播放需要的.
PlaybackManager 实现了Playback的callback接口,
LocalPlayback是本地音乐的播放类,实现了Playback接口,瞄一眼Playback接口的structure:

image.png

下面从LocalPlayback切入,跟着源码分析一下:

LocalPlayback

它的重要之处和难以理解之处主要在下面几个方面:LocalPlayback所需要实现的play方法:

 @Override
    public void play(QueueItem item) {
        mPlayOnFocusGain = true;
        tryToGetAudioFocus();
        registerAudioNoisyReceiver();
        String mediaId = item.getDescription().getMediaId();
        boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId);
        if (mediaHasChanged) {
            mCurrentMediaId = mediaId;
        }

//通过解析 MediaId中的hashcode 获取对应的 MetaData
 MediaMetadataCompat track =
                    mMusicProvider.getMusic(
                            MediaIDHelper.extractMusicIDFromMediaID(
                                    item.getDescription().getMediaId()));

//source 获取是一个url
            String source = track.getString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE);
            if (source != null) {
                source = source.replaceAll(" ", "%20"); // Escape spaces for URLs
            }

            if (mExoPlayer == null) {
                mExoPlayer =
                        ExoPlayerFactory.newSimpleInstance(
                                mContext, new DefaultTrackSelector(), new DefaultLoadControl());
                mExoPlayer.addListener(mEventListener);
            }

   
            final AudioAttributes audioAttributes = new AudioAttributes.Builder()
                    .setContentType(CONTENT_TYPE_MUSIC)
                    .setUsage(USAGE_MEDIA)
                    .build();
            mExoPlayer.setAudioAttributes(audioAttributes);

            // Produces DataSource instances through which media data is loaded
            // dataSource 是一个接口通过DefaultDataSourceFactory给予默认的实现
            DataSource.Factory dataSourceFactory =
                    new DefaultDataSourceFactory(
                            //Utils.getUserAgent返回一哥他包含包名version的字符串
                            mContext, Util.getUserAgent(mContext, "uamp"),
                            null);
            // Produces Extractor instances for parsing the media data.
            ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
            // The MediaSource represents the media to be played.
            MediaSource mediaSource =
                    new ExtractorMediaSource(
                            Uri.parse(source), dataSourceFactory, extractorsFactory, null, null);

            // Prepares media to play (happens on background thread) and triggers
            // {@code onPlayerStateChanged} callback when the stream is ready to play.
            mExoPlayer.prepare(mediaSource);
      }
 configurePlayerState();
}

解释一下各个类的作用

DataSource.Factory 数据可以被读取的接口
 Extractor : 从所属的格式中解析出媒体文件的数据

使用mExoPlayer 会触发 这个回调:

  @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            switch (playbackState) {
                case ExoPlayer.STATE_IDLE:
                case ExoPlayer.STATE_BUFFERING:
                case ExoPlayer.STATE_READY:
                    if (mCallback != null) {
                        mCallback.onPlaybackStatusChanged(getState());
                    }
                    break;
...从
}

这个mCallback是一个PlaybackManager的实例,函数的实现如下:


//updatePlaybackState()就是对state的一个管理 没有我们关心的关于播放的源码
    @Override
    public void onPlaybackStatusChanged(int state) {
        updatePlaybackState(null);
    }

上面说过mExoPlayer.prepare()ok之后会回调函数,但是在上面的代码片段的下面的configurePlayerState(); 才是关键,我们呢看看这个是什么:

private void configurePlayerState() {
        LogHelper.d(TAG, "configurePlayerState. mCurrentAudioFocusState=", mCurrentAudioFocusState);
        if (mCurrentAudioFocusState == AUDIO_NO_FOCUS_NO_DUCK) {
            // We don't have audio focus and can't duck, so we have to pause
            pause();
        } else {
            registerAudioNoisyReceiver();

            if (mCurrentAudioFocusState == AUDIO_NO_FOCUS_CAN_DUCK) {
                // We're permitted to play, but only if we 'duck', ie: play softly
                mExoPlayer.setVolume(VOLUME_DUCK);
            } else {
                mExoPlayer.setVolume(VOLUME_NORMAL);
            }

            // If we were playing when we lost focus, we need to resume playing.
            if (mPlayOnFocusGain) {
                mExoPlayer.setPlayWhenReady(true);
                mPlayOnFocusGain = false;
            }
        }
    }

其中有很多奇怪的名词,比如noisyReceiver DUCK 这些我会一一介绍,先让我们关注音乐的播放这里:

在上面的代码片段中,唯一和音乐播放扯得上关系的就是

//当getPlaybackState() == STATE_READY 时候继续播放
mExoPlayer.setPlayWhenReady(true);

事实上,ExoPlayer和我们想象的是由差别的,其中并没有play()方法,根据这篇博客来看只要调用

prepare()
setPlayWhenReady()

之后当player准备好了,就可以播放.

继续解释一下上面奇怪的名词:

noisyReceiver


    private final BroadcastReceiver mAudioNoisyReceiver =
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                        LogHelper.d(TAG, "Headphones disconnected.");
                        if (isPlaying()) {
                            Intent i = new Intent(context, MusicService.class);
                            i.setAction(MusicService.ACTION_CMD);
                            i.putExtra(MusicService.CMD_NAME, MusicService.CMD_PAUSE);
                            mContext.startService(i);
                        }
                    }
                }
            };

关键在于AudioManager.ACTION_AUDIO_BECOMING_NOISY这个action指的是当耳机被拔下来的时候会触发,这个也符合我们生活中的体验,当耳机被拔下来的时候会有zizizi的声音,如果是QQ音乐或者是网易云音乐的话,当耳机拔出是不会继续播放的,这个时候要交给MusicService继续判断一下,我们来看一下MusicService如何处理是否继续播放:

以下代码出自MusicService;

 @Override
    public int onStartCommand(Intent startIntent, int flags, int startId) {
        Log.d("start", "onStartCommand: ");
        if (startIntent != null) {
            String action = startIntent.getAction();
            String command = startIntent.getStringExtra(CMD_NAME);
            if (ACTION_CMD.equals(action)) {
                if (CMD_PAUSE.equals(command)) {
                    mPlaybackManager.handlePauseRequest();
                } else if (CMD_STOP_CASTING.equals(command)) {
                    CastContext.getSharedInstance(this).getSessionManager().endCurrentSession(true);
                }
            } else {
                // Try to handle the intent as a media button event wrapped by MediaButtonReceiver
                MediaButtonReceiver.handleIntent(mSession, startIntent);
            }
        }
....
}

我们传递的状态MusicService.CMD_PAUSE是暂停,符合预期,音乐播放也会暂停.

好,继续解释下一个名词:

DUCK

duck其实值得不是鸭子,换句话说,当AudioFocus资源被竞争的时候,需要降低一下音量,也就是"duck"一下

和DUCK相关的代码:

// The volume we set the media player to when we lose audio focus, but are
    // allowed to reduce the volume instead of stopping playback.
    public static final float VOLUME_DUCK = 0.2f;
    // The volume we set the media player when we have audio focus.
    public static final float VOLUME_NORMAL = 1.0f;

    // we don't have audio focus, and can't duck (play at a low volume)
    private static final int AUDIO_NO_FOCUS_NO_DUCK = 0;
    // we don't have focus, but can duck (play at a low volume)
    private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1;
    // we have full audio focus
    private static final int AUDIO_FOCUSED = 2;

这些代码在OnAudioChangedListener的回调中被处理,我们就看一下前面的几个case:

 public void onAudioFocusChange(int focusChange) {
                    LogHelper.d(TAG, "onAudioFocusChange. focusChange=", focusChange);
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            mCurrentAudioFocusState = AUDIO_FOCUSED;
                            break;
                        case 
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            //当audio的焦点有一个连续的失去的是哦后,但是开发者希望通过减小音量的方式而不让用户体验很差
                            mCurrentAudioFocusState = AUDIO_NO_FOCUS_CAN_DUCK;
                            break;
....//省略一些case


                    if (mExoPlayer != null) {
                        // Update the player state based on the change
                        configurePlayerState();
                    }
}

在configurePlayerState()中:

  if (mCurrentAudioFocusState == AUDIO_NO_FOCUS_CAN_DUCK) {
                // We're permitted to play, but only if we 'duck', ie: play softly
                mExoPlayer.setVolume(VOLUME_DUCK);
            } else {
                mExoPlayer.setVolume(VOLUME_NORMAL);
            }

我们根据LocalPlayback来分析UAM对AudioFoucs的处理:


    @Override
    public void play(QueueItem item) {
 ...
        tryToGetAudioFocus();
        registerAudioNoisyReceiver();
...
}

LocalPlayback 对Playback的实现方法play中使用了tryToGetAudioFocus(); 同时也把刚才实现的onAudioFocusChangeListener作为requestAudioFocus的一个参数

private void tryToGetAudioFocus() {
        LogHelper.d(TAG, "tryToGetAudioFocus");
        int result =
                mAudioManager.requestAudioFocus(
                        mOnAudioFocusChangeListener,
                        AudioManager.STREAM_MUSIC,
                        AudioManager.AUDIOFOCUS_GAIN);
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            mCurrentAudioFocusState = AUDIO_FOCUSED;
        } else {
            mCurrentAudioFocusState = AUDIO_NO_FOCUS_NO_DUCK;
        }
    }

同样在stop()中要及时的丢弃Audio focus,避免系统通知服务再次播放音频,比如在音频播放完成之后的某个时间段内,电话也结束了,此时没有丢弃音频焦点,系统又会通知自动播放


PlaybackManager

我们再来看一下PlayBackManager是做什么的:
开始的时候我不能理解为什么要PlaybackManager实现Playback.Callback接口,而让Playback实现Playback接口,其实仔细看一下Playback的方法命名,这个是很有道理的:

Playback中是play() pause() stop()这些比较具体的的抽象,理应应该交给一个具体的类去实现,而PlayBackManager中实现的Playback.Callback是比较抽象的方法,就像公司中的Manger只是发布命令,完成任务的是其他的员工这样.

我们来看看一下PlaybackManager:

 public PlaybackManager(PlaybackServiceCallback serviceCallback, Resources resources,
                           MusicProvider musicProvider, QueueManager queueManager,
                           Playback playback) {
        mMusicProvider = musicProvider;
        mServiceCallback = serviceCallback;
        mResources = resources;
        mQueueManager = queueManager;
        mMediaSessionCallback = new MediaSessionCallback();
        mPlayback = playback;
        mPlayback.setCallback(this);
    }

因为MusicService 中实现了PlayerServiceCallback接口,所以这个callback是一个MusicService对象,MusicService实现的回调其中的

 @Override
    public void onPlaybackStart() {
        mSession.setActive(true);
        startService(new Intent(getApplicationContext(), MusicService.class));
    }

是最关键的,开启了一个service,service的onCreate()方法,继续看看service oncreate方法.

注意

这里有一个问题,之前说过在BaseActivity中的 mediaBrowser.connect()会bind启动 service,这个地方应该不会再次调用onCreate()方法了吧?

使得确实不会,这个地方和前文中没有描述只是为了逻辑需要,请大家注意下,MusicSerivce早就已经onCreate过(在BaseActivity创建的时候),这个地方会直接执行onStartCommand()

image.png

继续看onCreate()方法,如果你仔细观察你就会发现MusicService和MediaSession 联系比较紧密,而在QueueManager的回调中大量的使用了有关于MediaSession的方法,我们以深度优先的方法看一下QueueManager内部的实现:

  QueueManager queueManager = new QueueManager(mMusicProvider, getResources(),
                new QueueManager.MetadataUpdateListener() {
                    @Override
                    public void onMetadataChanged(MediaMetadataCompat metadata) {
                        mSession.setMetadata(metadata);
                    }

                    @Override
                    public void onMetadataRetrieveError() {
                        mPlaybackManager.updatePlaybackState(
                                getString(R.string.error_no_metadata));
                    }

                    @Override
                    public void onCurrentQueueIndexUpdated(int queueIndex) {
                        mPlaybackManager.handlePlayRequest();
                    }

                    @Override
                    public void onQueueUpdated(String title,
                                               List newQueue) {
                        mSession.setQueue(newQueue);
                        mSession.setQueueTitle(title);
                    }
                });
    mSession.setMetadata(metadata);

    mSession.setQueue(newQueue);
    mSession.setQueueTitle(title);

以下代码出自QueueManager中:

这个是Metadata的获取,

public void updateMetadata() {
        MediaSessionCompat.QueueItem currentMusic = getCurrentMusic();
        if (currentMusic == null) {
            mListener.onMetadataRetrieveError();
            return;
        }
        final String musicId = MediaIDHelper.extractMusicIDFromMediaID(
                currentMusic.getDescription().getMediaId());
        MediaMetadataCompat metadata = mMusicProvider.getMusic(musicId);
        if (metadata == null) {
            throw new IllegalArgumentException("Invalid musicId " + musicId);
        }

        mListener.onMetadataChanged(metadata);
.....
        }
    }

容易看出,metaData来自之前分析过的MeidaProvider定义的map中,

  mSession.setQueue(newQueue);
    mSession.setQueueTitle(title);

也是如法炮制的.

我们回到MusicService中QueueManger,下面还有一个函数

   QueueManager queueManager = new QueueManager(mMusicProvider, getResources(),
                new QueueManager.MetadataUpdateListener() {
               ....
 
                    @Override
                    public void onCurrentQueueIndexUpdated(int queueIndex) {
                        mPlaybackManager.handlePlayRequest();
                    }
               ...
                });

调用了PlayManager的handlePlayRequest()

public void handlePlayRequest() {
      LogHelper.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());
      MediaSessionCompat.QueueItem currentMusic = mQueueManager.getCurrentMusic();
      if (currentMusic != null) {
          mServiceCallback.onPlaybackStart();
          mPlayback.play(currentMusic);
      }
  }

在这里QueueManger交出了QueueItem 以让音乐播放,所以总结一下:

image.png

还有以handlePlayRequest()为核心分析的PlaybackManager,LocalPlayback ,MusicService的联系

image.png

相关代码:
ExoPlayerListener

private final class ExoPlayerEventListener implements ExoPlayer.EventListener {
        @Override
        public void onTimelineChanged(Timeline timeline, Object manifest) {
            // Nothing to do.
        }

        @Override
        public void onTracksChanged(
                TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
            // Nothing to do.
        }

        @Override
        public void onLoadingChanged(boolean isLoading) {
            // Nothing to do.
        }

        @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            switch (playbackState) {
                case ExoPlayer.STATE_IDLE:
                case ExoPlayer.STATE_BUFFERING:
                case ExoPlayer.STATE_READY:
                    if (mCallback != null) {
                        mCallback.onPlaybackStatusChanged(getState());
                    }
                    break;
                case ExoPlayer.STATE_ENDED:
                    // The media player finished playing the current song.
                    if (mCallback != null) {
                        mCallback.  onCompletion();
                    }
                    break;
            }
        }

        @Override
        public void onPlayerError(ExoPlaybackException error) {
            final String what;
            switch (error.type) {
                case ExoPlaybackException.TYPE_SOURCE:
                    what = error.getSourceException().getMessage();
                    break;
                case ExoPlaybackException.TYPE_RENDERER:
                    what = error.getRendererException().getMessage();
                    break;
                case ExoPlaybackException.TYPE_UNEXPECTED:
                    what = error.getUnexpectedException().getMessage();
                    break;
                default:
                    what = "Unknown: " + error;
            }

            LogHelper.e(TAG, "ExoPlayer error: what=" + what);
            if (mCallback != null) {
                mCallback.onError("ExoPlayer error " + what);
            }
        }

        @Override
        public void onPositionDiscontinuity() {
            // Nothing to do.
        }

        @Override
        public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
            // Nothing to do.
        }

        @Override
        public void onRepeatModeChanged(int repeatMode) {
            // Nothing to do.
        }
    }

PlaybackManager实现的Playback.Callback中的onCompletion

@Override
    public void onCompletion() {
        // The media player finished playing the current song, so we go ahead
        // and start the next.
        if (mQueueManager.skipQueuePosition(1)) {
            handlePlayRequest();
            mQueueManager.updateMetadata();
        } else {
            // If skipping was not possible, we stop and release the resources:
            handleStopRequest(null);
        }
    }

在上面的代码中的onPlayerStateChanged中被调用

MusicService.setCallback()

  mSession.setCallback(mPlaybackManager.getMediaSessionCallback());

links
Android Audio Focus

你可能感兴趣的:(Univeral Music Player 源码解析 -- 让人头疼的playback)