Univeral Music Player 源码解析 -- 让

作者: kolibreath | 来源:发表于2018-05-05 21:07 被阅读0次

    文章集合:
    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<MediaSessionCompat.QueueItem> 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 源码解析 -- 让

        本文链接:https://www.haomeiwen.com/subject/ydilrftx.html