前言
如何實現一個音樂播放App,然後讓其可以被第三方的Android app開啟,並獲取其中的歌單,曲目列表,同時控制其播放呢?現有應用市場上,已經有相應的實現。比如百度CarLife對QQ音樂,喜馬拉雅等的呼叫。
在百度的Carlife App中,我們可以看到,只要我們本地的裝了QQ音樂App,其就可以喚起,然後獲取其中的歌曲資料,然後進行播放,這個是如何實現的呢?
需求
- 可以獲取音樂播放器的歌曲列表
- 可以控制音樂播放器的播放
- 可以將音樂播放器的狀態同步到第三方App
- 能夠和第三方App間進行相互通訊
類似於CarLife 對音樂App的喚起,首先第三方App開啟後,即可拉起音樂App,然後獲取其中的歌單,開啟歌單之後,獲取歌單內的歌曲列表,點選進行播放,可以進行播放,暫停,下一首,上一首的控制。
技術實現
谷歌官方提供了MediaBroswerService,通過其可以幫助我們實現上述的需求。
MediaBroswerService
- Android多媒體架構
Android多媒體播放採用client,server架構,一個server可以對應多個client,client在使用的時候需要先連線到server,雙方通過設定的一些callback來進行狀態的同步。
使用MediaBrowserService播放
客戶端需要建立MediaBrowser,服務端需要實現MediaBrowserService,在建立連線後,兩端之間的互動主要通過MediaController和MediaSession。兩個類之間通過預先定義的callback進行互動,MediaSession控制著播放器的播放,MediaController來控制著UI的變化。
- Media session
一個session持有了播放器的狀態和關於正在播放的一些資訊,一個seesion可以接收來自一個或多個媒體播放器的callback。這使得通過其它裝置來控制成為可能。
- Media controller
我們的UI只是和Media controller互動,而不是Player 本身,Media controller會將一些控制資訊傳遞給Media Session,它也會在seesion發生變化的時候,得到來自session的回撥,一個media controller一次只可以連線一個session。當使用一個media contoller和Session的時候,我們可以在執行期部署多個播放器,在其執行的時候根據裝置去修改app的外觀。
使用MediaBrowserService可以讓Android Wear, Auto非常容易找我們的App,連線它,瀏覽它的內容,控制其播放,而完全不需要接觸我們的UI Activity。
服務端實現
- 服務端基礎配置
mainfeat 配置
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
複製程式碼
MediaPlaybackService的初始化
public class MediaPlaybackService extends MediaBrowserServiceCompat {
@Override
public void onCreate() {
super.onCreate();
// 1. 初始化 MediaSession
mSession = new MediaSessionCompat(this, "MusicService");
// 2. 設定 MedisSessionCallback
mSession.setCallback(mSessionCallback);
// 3. 開啟 MediaButton 和 TransportControls 的支援
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
// 4. 初始化 PlaybackState
mStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE);
mSession.setPlaybackState(mStateBuilder.build());
// 5. 關聯 SessionToken
setSessionToken(mSession.getSessionToken());
}
}
複製程式碼
根據包名做許可權判斷之後,返回根路徑
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
// 根據包名對每個訪問端做一些訪問許可權判斷等,返回為空,連線將會斷開
}
複製程式碼
用來根據mediaID來返回第三放App所需要獲得媒體資料
@Override
public void onLoadChildren(final String parentMediaId,
final Result<List<MediaItem>> result) {
// 根據parentMediaId返回播放列表相關資訊
}
複製程式碼
客戶端連線
private void initMediaBrowser() {
//1.待連線的服務
ComponentName componentName = new ComponentName("com.example.android.uamp","com.example.android.uamp.MusicService");
//2.建立MediaBrowser
mMediaBrowser = new MediaBrowserCompat(this, componentName, mConnectionCallbacks, null);
//3.建立連線
mMediaBrowser.connect();
}
複製程式碼
設定相應的callback,連線Callback,資料變化Callback
連線狀態同步
資料變化Callback設定
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
new MediaBrowserCompat.ConnectionCallback() {
@Override
public void onConnected() {
//連線成功回撥
}
@Override
public void onConnectionSuspended() {
//連線中斷回撥
}
@Override
public void onConnectionFailed() {
//連線失敗回撥
}
};
複製程式碼
MediaControllerCompat.Callback controllerCallback =
new MediaControllerCompat.Callback() {
public void onSessionDestroyed() {
//Session銷燬
}
@Override
public void onRepeatModeChanged(int repeatMode) {
//迴圈模式發生變化
}
@Override
public void onShuffleModeChanged(int shuffleMode) {
//隨機模式發生變化
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
//資料變化
}
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
//播放狀態變化
}
};
複製程式碼
客戶端與服務端資料互動
MediaBrowser通過呼叫subscribe,會回撥到MediaService的onLoadChildren,在這裡做一個判斷然後構造相應的列表將列表資料返回。返回資料之後。
- 根據MediaID獲取資料
客戶端通過呼叫subscribe方法,傳遞MediaID,在SubscriptionCallback的方法中進行處理。
mMediaBrowser.subscribe("ID", new MediaBrowserCompat.SubscriptionCallback() {
@Override
public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowserCompat.MediaItem> children) {
//children 為來自Service的列表資料
}
});
複製程式碼
服務端和客戶端之間傳遞的資料為MediaItem列表。MediaItem中具備的欄位:MediaId,Title,SubTitle,Description,Icon,IconUri,MediaUri等欄位。通過其可以幫助我們攜帶一些資料來進行歌曲的展示和播放。
@Override
public void onLoadChildren(@NonNull final String parentMediaId,
@NonNull final Result<List<MediaItem>> result) {
List<MediaItem> items = new ArrayList<>();
//根據MediaID做資料填充
switch (parentMediaId) {
case:
default: break;
}
result.sendResult(items);
}
複製程式碼
- 傳送自定義資料獲取內容
客戶端通過呼叫sendCustomAction,根據與服務端的協商,制定相應的action型別,進行資料的傳遞互動。
mMediaBrowser.sendCustomAction(action, extras, new MediaBrowserCompat.CustomActionCallback() {
@Override
public void onProgressUpdate(String action, Bundle extras, Bundle data) {
super.onProgressUpdate(action, extras, data);
}
@Override
public void onResult(String action, Bundle extras, Bundle resultData) {
super.onResult(action, extras, resultData);
}
@Override
public void onError(String action, Bundle extras, Bundle data) {
super.onError(action, extras, data);
}
});
複製程式碼
服務端實現onCustomAction,根據action型別返回相應的資料
@Override
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result) {
//分支判斷
if (GET_LIST.equals(action)) {
Bundle bundle = new Bundle();
ArrayList<String> list = new ArrayList<>();
//填充資料
bundle.putStringArrayList(LIST_NAMES, list);
result.sendResult(bundle);
}
}
複製程式碼
播放控制
- 客戶端
客戶端通過getMediaController getTransportControls()來進行播放,暫停,上一首,下一首的控制。
//獲取播放狀態
int pbState = MediaControllerCompat.getMediaController(MainActivity.this).getPlaybackState().getState();
//根據播放狀態進行播放控制
if (pbState == PlaybackStateCompat.STATE_PLAYING) {
MediaControllerCompat.getMediaController(MainActivity.this).getTransportControls().pause();
} else {
MediaControllerCompat.getMediaController(MainActivity.this).getTransportControls().play();
}
複製程式碼
- 服務端
在服務端為MediaSession設定SessionCallback,來實現相應的播放功能。
mSession.setCallback(mSessionCallback);
複製程式碼
客戶端通過MediaController可以進行播放,暫停,根據MediaID播放下一個音樂,音樂播放快進等。所有的操作會回撥到服務端的MediaSessionCallback的play,seekTo等方法,需要我們自己實現,在其中控制播放佇列,然後根據列表播放的情況來動態的變更佇列。
播放狀態同步
對於播放狀態的同步,比如當前播放到哪一個歌曲,當前是暫停還是播放中。客戶端通過Controller回撥就可以得到相應的變化,但是,變化狀態,服務端如何傳送呢?
setMetadata(android.media.MediaMetadata));
setPlaybackState(android.media.session.PlaybackState));
複製程式碼
設定當前的歌曲資訊,設定當前的播放狀態。設定之後,客戶端將會得到更新。
獲取手機內的媒體服務
private void discoverBrowseableMediaApps(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
List<ResolveInfo> services = packageManager.queryIntentServices(intent, 0);
for (ResolveInfo resolveInfo : services) {
if (resolveInfo.serviceInfo != null && resolveInfo.serviceInfo.applicationInfo != null) {
ApplicationInfo applicationInfo = resolveInfo.serviceInfo.applicationInfo;
String label = (String) packageManager.getApplicationLabel(applicationInfo);
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
String className = resolveInfo.serviceInfo.name;
String packageName = resolveInfo.serviceInfo.packageName;
MusicService service = new MusicService();
service.icon = icon;
service.lable = label;
service.className = className;
service.packageName = packageName;
musicServiceList.add(service);
}
}
}
複製程式碼
部分機型出現獲取不到,需要App開啟,才可以獲取的到。
結語
通過本篇文章,對MediaBroswerService做了一個簡單的介紹,但對於播放器的具體實現,特別是在服務端還是比較複雜的,需要維護歌曲佇列,進行播放,同時負責狀態的更新。