前言
在上一篇文章打造基于MediaSessionCompat的音乐播放(一)中,已经简单地介绍了MediaSessionCompat框架以及它的具体使用。
今天这篇,主要介绍如何提供数据给播放器。
在这里我还是以谷歌官方的android-UniversalMusicPlayer为例,并结合我的实际(提供的是本地数据),如果你想看网络数据如何处理,可以参考上面谷歌的Demo。
下面进入正题
数据的连接
前面提到过,在Activity中通过MediaBrowserCompat和实现了MediaBrowserServiceCompat的service连接,再通过
MediaBrowserCompat.unsubscribe(mMediaId);
MediaBrowserCompat.subscribe(mMediaId,mSubscriptionCallback);
订阅数据,想要获取相应的音乐数据,就是通过客户端发送MediaId来获取,而数据则是通过MediaBrowserCompat.SubscriptionCallback来获取的。
在服务端的onLoadChildren方法中,我们拿到这个客户端发送过来的MediaId,对这个MediaId做相应的处理之后,根据MediaId的不同,返回不同的数据即可。
@Override
public void onLoadChildren(@NonNull String parentMediaId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
if (MEDIA_ID_EMPTY_ROOT.equals(parentMediaId)) {
result.sendResult(new ArrayList<MediaBrowserCompat.MediaItem>());
} else {
result.sendResult(mMusicProvider.getChildren(parentMediaId));
}
}
上一篇提到过这个,result.sendResult将结果返回给客户端。接下来我们详细地看一下这个mMusicProvider,它的作用是提供本地数据。它根据从客户端接收到的parentMediaId(即上面提到的客户端发出的MediaId)类型,返回数据。
本地数据分类与检索
在MusicProvider中,我将数据主要分成四大类:
private List<MediaMetadataCompat> mLocalMusicList;//本地所有的音乐
private final ConcurrentMap<String, MutableMediaMetadata> mMusicListById;//key是musicId
private ConcurrentMap<String, List<MediaMetadataCompat>> mMusicListByAlbum;//本地专辑集合
private ConcurrentMap<String,List<MediaMetadataCompat>> mMusicListByArtist;//本地歌手集合
MusicProvider需要先在Service中初始化,先对本地音乐数据进行检索,并将其转化为我们需要的数据,这里举个mMusicListById的例子
mLocalMusicList = mSongSource.getLocalList();
mSongSource是一个实现了自定义方法的接口,它的作用是提供本地数据
在它的getLocalList方法中,我们获取本地数据
@Override
public ArrayList<MediaMetadataCompat> getLocalList() {
getLocalSongList();
ArrayList<MediaMetadataCompat> tracks = new ArrayList<>();
for (int i = 0; i < mLocalSong.size(); i++) {
tracks.add(buildFromLocal(mLocalSong.get(i)));
}
return tracks;
}
数据的转换
将数据从媒体数据转换成我们需要的数据(MediaMetadataCompat)
同时通过buildFromLocal方法,将这些本地数据转化为我们能用的数据
private MediaMetadataCompat buildFromLocal(Song song){
String title = song.getTitle();
String album = song.getAlbum();
String artist = song.getArtist();
int duration = song.getDuration();
String source = song.getPath();
String strId = "";
if(title != null && artist != null){
strId = title + artist;
}
String id = String.valueOf(strId.hashCode());
String albumArt = song.getAlbumObj().getAlbumArt();
return new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID,id)
.putString(SongSource.CUSTOM_METADATA_TRACK_SOURCE, source)
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI,source)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM,album)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,artist)
.putString(MediaMetadataCompat.METADATA_KEY_ART_URI,albumArt)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION,duration)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.build();
}
通过上面的操作,我们的mLocalMusicList 就拿到了所有的本地音乐数据,再通过这个总数据,获取相应的另外三种数据。
Iterator<MediaMetadataCompat> tracks = mSongSource.iterator();
while (tracks.hasNext()) {
MediaMetadataCompat item = tracks.next();
String musicId = item.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
mMusicListById.put(musicId, new MutableMediaMetadata(musicId, item));
}
数据的分类返回
到目前为止,我们只是拥有了这些数据,接下来我们看怎么讲这些数据通过MediaId,获取相应的数据返回给客户端。
上面有提到过在service中返回数据的方法中有这行代码:
result.sendResult(mMusicProvider.getChildren(parentMediaId));
我们来看看mMusicProvider.getChildren(parentMediaId)是怎么实现的
/*
* 返回本地音乐数据
* */
public List<MediaBrowserCompat.MediaItem> getChildren(String mediaId){
List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
//所有的音乐
if(MediaIdHelper.MEDIA_ID_NORMAL.equals(mediaId)){
for (MediaMetadataCompat mediaMetadataCompat: mLocalMusicList) {
mediaItems.add(createMediaItem(mediaMetadataCompat));
}
}else if(MediaIdHelper.MEDIA_ID_ALBUM.equals(mediaId)){//专辑
for (String albumTitle :
getAlbumList()) {
mediaItems.add(createAlbumMediaItem(albumTitle));
}
}else if(mediaId.startsWith(MediaIdHelper.MEDIA_ID_ALBUM_DETAIL)){//专辑详情
String[] split = mediaId.split("&&");
List<MediaMetadataCompat> musicsByAlbum = getMusicsByAlbum(split[1]);
for (int i = 0; i < musicsByAlbum.size(); i++) {
mediaItems.add(createMediaItem(musicsByAlbum.get(i)));
}
}else if(MediaIdHelper.MEDIA_ID_ARTIST.equals(mediaId)){//歌手列表
for (String artistName :
getArtistList()) {
mediaItems.add(createArtistMediaItem(artistName));
}
}else if(mediaId.startsWith(MediaIdHelper.MEDIA_ID_ARTIST_DETAIL)){//歌手列表详情
String[] split = mediaId.split("&&");
List<MediaMetadataCompat> musicByArtist = getMusicByArtist(split[1]);
if(musicByArtist != null){
for (int i = 0; i < musicByArtist.size(); i++) {
mediaItems.add(createMediaItem(musicByArtist.get(i)));
}
}
}
return mediaItems;
}
public class MediaIdHelper {
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
public static final String MEDIA_ID_ROOT = "__ROOT__";//初次连接
public static final String MEDIA_ID_NORMAL = "__LOCAL_NORMAL__";//所有的本地音乐
public static final String MEDIA_ID_ALBUM = "__LOCAL_ALBUM__";//音乐专辑
public static final String MEDIA_ID_ALBUM_DETAIL = "__LOCAL_ALBUM_DETAIL__";//具体某个专辑的歌曲
public static final String MEDIA_ID_ARTIST = "__LOCAL_ARTIST__";
public static final String MEDIA_ID_ARTIST_DETAIL = "__LOCAL_ARTIST_DETAIL__";
}
结合自定义的MediaIdHelper 来看,我们客户端发送的mediaId,举个例子,
我们在客户端发送的MediaId是这样,MEDIA_ID_ARTIST意思是获取本地的所有歌手的数据
mMediaId = MEDIA_ID_ARTIST;
mediaBrowserProvider.getMediaBrowser().unsubscribe(mMediaId);
mediaBrowserProvider.getMediaBrowser().subscribe(mMediaId,mSubscriptionCallback);
服务端接收到后,在MusicProvider的getChildren方法中,对这个MediaId进行判断,执行到
else if(MediaIdHelper.MEDIA_ID_ARTIST.equals(mediaId)){//歌手列表
for (String artistName :
getArtistList()) {
mediaItems.add(createArtistMediaItem(artistName));
}
进入该分支,通过对所有歌手的遍历循环,构建符合规则的数据
/*
* 构建 歌手列表 数据
* */
private MediaBrowserCompat.MediaItem createArtistMediaItem(String artistName){
List<MediaMetadataCompat> songList = mMusicListByArtist.get(artistName);//获得这个歌手的所有歌曲
MediaMetadataCompat data = songList.get(0);
MediaDescriptionCompat descriptionCompat = new MediaDescriptionCompat.Builder()
.setMediaId(artistName)//歌手名称
.setTitle(String.valueOf(songList.size()))
.build();
return new MediaBrowserCompat.MediaItem(descriptionCompat,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
}
循环完毕后,将mediaItems数据列表返回给客户端,客户端拿到数据之后就可以展示了。
所有的数据都可以通过上述方式获得。
数据类型了解
最后我们来看一下,上面出现过的可能显得有点陌生的数据类:
1.MediaMetadataCompat:
它出现在将初始的本地音乐数据转化成MediaMetadataCompat,
源码的解释是:
Contains metadata about an item, such as the title, artist, etc.
包含关于item的元数据,如标题、艺术家等信息。具体看MediaMetadataCompat类,它的数据可以包含TITLE、ARTIST、DURATION、ALBUM、AUTHOR、WRITER等等,可以说,一首歌的所有需要显示的信息,它都可以设置。
2.MediaBrowserCompat.MediaItem
A class with information on a single media item for use in browsing/searching media.
MediaItems are application dependent so we cannot guarantee that they contain the right values.
在浏览/搜索媒体中使用的具有单个媒体项上的信息的类。
mediaitem依赖于应用程序,因此我们不能保证它们包含正确的值。
这个数据类是用于客户端和服务端之间传输使用的,我们在客户端拿到(服务端发送)的数据就是它的集合。
这个数据里面的内容也是需要我们自己本地构建,在我的项目中,我就是通过之前拿到的MediaMetadataCompat,将其转化为MediaBrowserCompat.MediaItem数据
3.MutableMediaMetadata
自定义的数据类,它的结构如下:
public MediaMetadataCompat metadata;
public final String trackId;
它的作用是封装了MediaMetadataCompat,通过键值对的方式检索MediaMetadataCompat数据。
写在最后:
关于数据这一块就差不多写完了,下面一篇应该会写一下对于耳机插拔、音乐控制等控制器的实现