Universal Music Player 源码解析(二)--UI层和playack层的交互

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

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

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

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

这篇文章主要承接上文,说明这几个问题:

  • 如何获得音乐数据?
  • UI 层如何和playback层交互?
  • 令人困惑的MediaId是什么? 他是怎么传到MediaPlayerActivity中的?

关于音乐数据的获得:

RemoteJSONSource中,发起了一个http请求,解析成为一个JSONObject

 private JSONObject fetchJSONFromUrl(String urlString)

之后:

private MediaMetadataCompat
 buildFromJSON(JSONObject json, String basePath){
 ...
 String id = String.valueOf(source.hashCode());
....
 return new MediaMetadataCompat.Builder()
                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
                .putString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE, source)
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
                .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl)
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
                .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber)
                .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount)
                .build();
....
}

使用Builder模式,将拿到的数据,初始化了一个MediaMetaData对象.

这个地方引入了一个新的概念:MediaMetaData是什么?
同样,还有后面会出现的概念MediaItem ,MediaDescription等 ,我一起总结一下:

MediaMetaData---音乐元数据,包含的是很多刚才从RemoteJSONSource中拿到的数据.
通过MediaMetaData getMediaDescription()可以很容易得到一个MediaDescription对象

MediaItemMediaBrowser的子类

image.png

主要用来表征这个item是browsableorplayable

另外,这里有个很重要的信息: getMediaID() ,同样具体意义暂且不表,后面会详细解释.

很容易可以看出,MediaItem MediaDescription作为MediaBrowser的子类的联系很强,同样getDescription()也可以获得MediaDescription

所以综上所述,MediaItem是对音乐数据的一个终极封装,但是详细的信息,比如:

getMediaUri()

getSubTitle/ title()

只能通过MediaItem.getDescription()再拿一次

音乐数据的播放:

刚才的RemoteJSONSource是继承自MusicProviderSource类的,实现了其中的iterator()

 @Override
    public Iterator iterator() {
    .....
            ArrayList tracks = new ArrayList<>();
            if (jsonObj != null) {
                JSONArray jsonTracks = jsonObj.getJSONArray(JSON_MUSIC);

                if (jsonTracks != null) {
                    for (int j = 0; j < jsonTracks.length(); j++) {
                        tracks.add(buildFromJSON(jsonTracks.getJSONObject(j), path));
                    }
                }
            }
      ....
    }

这个方法将会在MusicProvider中被使用:

 private synchronized void retrieveMedia() {
        try {
            if (mCurrentState == State.NON_INITIALIZED) {
                mCurrentState = State.INITIALIZING;

                Iterator tracks = mSource.iterator();
                while (tracks.hasNext()) {
                    MediaMetadataCompat item = tracks.next();
                    String musicId = item.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
                    mMusicListById.put(musicId, new MutableMediaMetadata(musicId, item));
                }
                buildListsByGenre();
                mCurrentState = State.INITIALIZED;
            }
        } finally {
            if (mCurrentState != State.INITIALIZED) {
                // Something bad happened, so we reset state to NON_INITIALIZED to allow
                // retries (eg if the network connection is temporary unavailable)
                mCurrentState = State.NON_INITIALIZED;
            }
        }
    }

于是我们获得了一个map,value是根据当时的jsonObject获取的hashcode,key是一个固定的 string

铺垫的部分已经结束了,正式来看一下音乐是如何播放的:
如果用户在界面上点了暂停/播放 这个是怎么实现的呢?

音乐播放的逻辑是结合MusicService 还有Playback层实现的,具体可以参考一下我这篇博客

如果为了增强阅读连贯性的读者可以看看我的总结,迅速上手:

image.png

还有关于handlePlayRequest和MusicService这几个类的联系:


播放的控制是由PlaybackControlsFragment 管理的 ,当点击播放/暂停的时候:

private final View.OnClickListener mButtonListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            MediaControllerCompat controller = MediaControllerCompat.getMediaController(getActivity());
            PlaybackStateCompat stateObj = controller.getPlaybackState();
            final int state = stateObj == null ?
                    PlaybackStateCompat.STATE_NONE : stateObj.getState();
            LogHelper.d(TAG, "Button pressed, in state " + state);
            switch (v.getId()) {
                case R.id.play_pause:
                    LogHelper.d(TAG, "Play button pressed, in state " + state);
                    if (state == PlaybackStateCompat.STATE_PAUSED ||
                            state == PlaybackStateCompat.STATE_STOPPED ||
                            state == PlaybackStateCompat.STATE_NONE) {
                        playMedia();
                    } else if (state == PlaybackStateCompat.STATE_PLAYING ||
                            state == PlaybackStateCompat.STATE_BUFFERING ||
                            state == PlaybackStateCompat.STATE_CONNECTING) {
                        pauseMedia();
                    }
                    break;
            }
        }
    };

通过conntroller对象获取到playbackState,调用playMedia()

 private void playMedia() {
        MediaControllerCompat controller = MediaControllerCompat.getMediaController(getActivity());
        if (controller != null) {
            controller.getTransportControls().play();
        }
    }

出乎意料的简洁有咩有!
我们知道MediaController是根据MediaSession.Token创建的,并且绑定在了context上,所以调用play()就可以控制播放,就会fire MediaSession中的回调,不信请看:

由于在MusicService 中

 mSession.setCallback(mPlaybackManager.getMediaSessionCallback());

看看Callback

private class MediaSessionCallback extends MediaSessionCompat.Callback {
        @Override
        public void onPlay() {
            LogHelper.d(TAG, "play");
            if (mQueueManager.getCurrentMusic() == null) {
                mQueueManager.setRandomQueue();
            }
            handlePlayRequest();
        }
....
}

最终还是调用handlexxxRequest()

同样的,跳到下一首:

 @Override
        public void onSkipToQueueItem(long queueId) {
            LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId);
            mQueueManager.setCurrentQueueItem(queueId);
            mQueueManager.updateMetadata();
        }

最终都会调用handlePlayRequest()

MediaID的获取

先看一下MusicService:

   @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                                 Bundle rootHints) {
        LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName,
                "; clientUid=" + clientUid + " ; rootHints=", rootHints);
        // To ensure you are not allowing any arbitrary app to browse your app's contents, you
        // need to check the origin:
        if (!mPackageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
            // If the request comes from an untrusted package, return an empty browser root.
            // If you return null, then the media browser will not be able to connect and
            // no further calls will be made to other media browsing methods.
            LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. "
                    + "Returning empty browser root so all apps can use MediaController."
                    + clientPackageName);
            return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null);
        }
        //noinspection StatementWithEmptyBody
 
        return new BrowserRoot(MEDIA_ID_ROOT, null);
    }

    @Override
    public void onLoadChildren(@NonNull final String parentMediaId,
                               @NonNull final Result> result) {
        LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId);
        if (MEDIA_ID_EMPTY_ROOT.equals(parentMediaId)) {
            result.sendResult(new ArrayList());
        } else if (mMusicProvider.isInitialized()) {
            // if music library is ready, return immediately
            result.sendResult(mMusicProvider.getChildren(parentMediaId, getResources()));
        } else {
            // otherwise, only return results when the music library is retrieved
            result.detach();
            mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() {
                @Override
                public void onMusicCatalogReady(boolean success) {
                    result.sendResult(mMusicProvider.getChildren(parentMediaId, getResources()));
                }
            });
        }
    }

MusicService 继承MediaBrowserServiceCompat 并且实现两个方法:onGetRoot() onLoadChildren()
通过onGetRoot()可以返回一个BrowserRoot实例,需要检查其他的应用有没有权限获取这个我们的应用中的数据,
onLoadChildren() 使用了一种特殊的机制返回返回值,这个函数的返回值是void , 我们通过在MediaBrowseFragment 中subscribe

mMediaFragmentListener.getMediaBrowser().unsubscribe(mMediaId);


   mMediaFragmentListener.getMediaBrowser().subscribe(mMediaId, mSubscriptionCallback);

看一下mSubscriptionCallback

private final MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback =
            //media description:contains the metadata of a song
            //media item 构造函数 (MediaDescription,int flags)
        new MediaBrowserCompat.SubscriptionCallback() {
            @Override
            public void onChildrenLoaded(@NonNull String parentId,
                                         @NonNull List children) {
                try {
                    LogHelper.d(TAG, "fragment onChildrenLoaded, parentId=" + parentId +
                        "  count=" + children.size());
                    checkForUserVisibleErrors(children.isEmpty());
                    mBrowserAdapter.clear();
                    for (MediaBrowserCompat.MediaItem item : children) {
                        mBrowserAdapter.add(item);
                    }
                    mBrowserAdapter.notifyDataSetChanged();
                } catch (Throwable t) {
                    LogHelper.e(TAG, "Error on childrenloaded", t);
                }
            }

            @Override
            public void onError(@NonNull String id) {
                LogHelper.e(TAG, "browse fragment subscription onError, id=" + id);
                Toast.makeText(getActivity(), R.string.error_loading_media, Toast.LENGTH_LONG).show();
                checkForUserVisibleErrors(true);
            }
        };

向adapter中添加内容,所以我们才可以浏览

你可能感兴趣的:(Universal Music Player 源码解析(二)--UI层和playack层的交互)