Universal播放器的原始碼學習筆記
以前也弄過音樂播放器的程式碼,自己寫起來非常的粗糙,感覺音樂播放器的難點包括歌曲列表的管理,音樂後臺服務跟主執行緒之間的通訊。當然,也看過別人封裝的比較好的音樂服務,通過aidl的方式來實現程式間通訊,比如這個MusicService.java,3000行程式碼把所有的內容都封裝好了。閱讀者看起來真的吃力。之後,看到谷歌的UniversalMusicPlayer播放器,用MediaBrowserService框架來實現播放器。
框架基本構成
UI端
UI端的角色有MediaBrowser和MediaController。
MediaBrowser
首先MediaBrowser需要去想辦法連線服務端,通過
mMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, MusicService.class), mConnectionCallback, null);
mMediaBrowser .connect();
其中mConnectionCallback是和服務端連線的回撥。
private final MediaBrowserCompat.ConnectionCallback mConnectionCallback =
new MediaBrowserCompat.ConnectionCallback() {
@Override
public void onConnected() {
//說明已經連線上了
try {
connectToSession(mMediaBrowser.getSessionToken());
} catch (RemoteException e) {
LogHelper.e(TAG, e, "could not connect media controller");
}
}
};
可以通過返回的MediaSessionCompat.Token獲取到MediaControllerCompat(控制器)
private void connectToSession(MediaSessionCompat.Token token) throws RemoteException {
MediaControllerCompat mediaController = new MediaControllerCompat(
FullScreenPlayerActivity.this, token);
MediaControllerCompat.setMediaController(FullScreenPlayerActivity.this, mediaController);
mediaController.registerCallback(mCallback);
PlaybackStateCompat state = mediaController.getPlaybackState();
//更新播放狀態()
MediaMetadataCompat metadata = mediaController.getMetadata();
if (metadata != null) {
//更新ui
}
}
MediaControllerCompat
- 控制播放,暫停,下一首,上一首等操作,通過TransportControls來實現
MediaControllerCompat controller = MediaControllerCompat.getMediaController(context);
MediaControllerCompat.TransportControls transport = controller.getTransportControls();
transport.play();
- 同時也監聽播放狀態跟metadata的變化。
mediaController.registerCallback(new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) {
LogHelper.d(TAG, "onPlaybackstate changed", state);
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
if (metadata != null) {
}
});
PlaybackStateCompat
mediaController回撥onPlaybackStateChanged方法,然後根據state.getState()來處理UI的變化
switch (state.getState()) {
case PlaybackStateCompat.STATE_PLAYING://正在播放
break;
case PlaybackStateCompat.STATE_PAUSED://暫停
break;
case PlaybackStateCompat.STATE_NONE:
break;
case PlaybackStateCompat.STATE_STOPPED://停止
break;
case PlaybackStateCompat.STATE_BUFFERING:
break;
default:
break;
}
MediaMetadataCompat
儲存了歌曲的資訊,包括uri,專輯名稱,歌手,時長等。
通過 builder = new MediaMetadataCompat.Builder();
builde.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title");儲存資訊
服務端
MediaBrowserServiceCompat
我們讓自己定義的service繼承MediaBrowserServiceCompat,同時在清單中註冊。然後處理連線的請求。服務端會在onGetRoot方法中收到請求,此時返回一個rootId就好了,例如:"_EMPTY",如果方法返回null,則拒絕連線
@Override
public BrowserRoot onGetRoot(@NonNull String s, int i, @Nullable Bundle bundle) {
return new BrowserRoot("__EMPTY_",null);
}
MediaSession
那麼什麼是MediaSessionCompat呢?
在MediaSession框架中,有受控端(一個)和控制端(可以有多個)。接下來為了保證受控端和控制端不串號(想象一個遙控器可以遙控同一型號的多臺電視),就有了SessionToken的概念,相當於我們在連線藍芽裝置時的配對碼,這樣就保證了不串號
當應用程式想要釋出媒體播放資訊或處理媒體金鑰時,應該建立MediaSession。MediaSessionCompat允許與媒體控制器、音量鍵、媒體按鈕和傳輸控制進行互動。一般來說,一個應用程式只需要一個會話來進行所有的播放(儘管可以建立多個會話來提供更好的媒體控制)
在MusicService中初始化MediaSession
mSession = new MediaSessionCompat(this, "MusicService");
setSessionToken(mSession.getSessionToken());
mSession.setCallback(mCallback);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
此處mCallback是MediaSessionCompat.Callback的子類,它的回撥方法有onPlay(),onPause(),onSkipToQueueItem(long queueId),onSkipToPrevious()。也就是說MediaControllerCompat.TransportControls呼叫播放,暫停等功能時會呼叫服務端MediaSession的onPlay(),onPause()等方法。
另外
mSession.setMetadata(metadata);
mSession.setPlaybackState(newState);
會回撥controller的onMetadataChanged,onPlaybackStateChanged方法。
一個MediaSession框架基本思路就完成了。
分析原始碼
Playback
定義一個播放器介面Playback
void start();
void stop(boolean notifyListeners);
void setState(int state);
int getState();
boolean isConnected();
boolean isPlaying();
long getCurrentStreamPosition();
void updateLastKnownStreamPosition();
void play(QueueItem item);
void pause();
void seekTo(long position);
void setCurrentMediaId(String mediaId);
String getCurrentMediaId();
具體的實現由MediaPlayer或exoPlayer完成
QueueManager
需要去定義一個佇列,它可以是單例的。它需要完成哪些功能呢?獲取當前播放的列表,獲取當前播放曲目,獲取當前下標,更換新列表。同時列表改變了,曲目改變了,下標改變了等等 需要讓MediaSession知道吧,畢竟mSession.setMetadata(metadata)
會回撥ui的變化,所以需要一個介面
public interface MetadataUpdateListener {
void onMetadataChanged(MediaMetadataCompat metadata);
void onMetadataRetrieveError();
void onCurrentQueueIndexUpdated(int queueIndex);
void onQueueUpdated(String title, List<MediaSessionCompat.QueueItem> newQueue);
}
所以在MusicService中有這樣的一段
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);
}
});
java中有個模式叫中介者模式,用來協調各角色之間的關係。將系統從網狀結構變成星型結構,為了協調player,queue,MusicService之間的關係,所以有了MusicPlayBackManager這個角色。首先MusicPlayBackManager需要知道播放器player的狀況。比如播放完成,播放出錯等,所以MusicPlayBackManager實現了
interface Callback {
/**
* On current music completed.
*/
void onCompletion();
/**
* on Playback status changed
* Implementations can use this callback to update
* playback state on the media sessions.
*/
void onPlaybackStatusChanged(int state);
/**
* @param error to be added to the PlaybackState
*/
void onError(String error);
/**
* @param mediaId being currently played
*/
void setCurrentMediaId(String mediaId);
}
這些方法都由MusicPlayBackManager來處理,具體的player用callback來回撥這些方法。
例如
@Override
public void play(QueueItem item) {
try {
loadMedia(item.getDescription().getMediaId(), true);
mPlaybackState = PlaybackStateCompat.STATE_BUFFERING;
if (mCallback != null) {
mCallback.onPlaybackStatusChanged(mPlaybackState);
}
} catch (JSONException e) {
LogHelper.e(TAG, "Exception loading media ", e, null);
if (mCallback != null) {
mCallback.onError(e.getMessage());
}
}
}
MusicPlayBackManager跟mediaseesion要產生聯絡,通過PlaybackManager.PlaybackServiceCallback
public interface PlaybackServiceCallback {
void onPlaybackStart();
void onNotificationRequired();
void onPlaybackStop();
void onPlaybackStateUpdated(PlaybackStateCompat newState);
}
MusicService實現了這些方法,例如
@Override
public void onPlaybackStateUpdated(PlaybackStateCompat newState) {
mSession.setPlaybackState(newState);
}
@Override
public void onPlaybackStop() {
mSession.setActive(false);
// Reset the delayed stop handler, so after STOP_DELAY it will be executed again,
// potentially stopping the service.
mDelayedStopHandler.removeCallbacksAndMessages(null);
mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
stopForeground(true);
}
同時MusicPlayBackManager 還要處理mSession.setCallback(mPlaybackManager.getMediaSessionCallback()),內部類的方式
private class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
LogHelper.d(TAG, "play");
if (mQueueManager.getCurrentMusic() == null) {
mQueueManager.setRandomQueue();
}
handlePlayRequest();
}
@Override
public void onSkipToQueueItem(long queueId) {
LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId);
mQueueManager.setCurrentQueueItem(queueId);
mQueueManager.updateMetadata();
}
...
}
基本的設計思路就是這樣,值得學習的專案
相關文章
- Angular Universal 學習筆記Angular筆記
- Retrofit原始碼學習筆記原始碼筆記
- jQuery原始碼學習筆記一jQuery原始碼筆記
- vue原始碼學習筆記1Vue原始碼筆記
- 學習筆記 sync/RWMutex原始碼筆記Mutex原始碼
- Redux 學習筆記 – 原始碼閱讀Redux筆記原始碼
- vue原始碼學習筆記2(resolveConstructorOptions)Vue原始碼筆記Struct
- Python 學習筆記 - socketserver原始碼剖析Python筆記Server原始碼
- JUC原始碼學習筆記6——ReentrantReadWriteLock原始碼筆記
- java.security.Provider 原始碼學習筆記JavaIDE原始碼筆記
- async-validator 原始碼學習筆記(四):validator原始碼筆記
- MyBatis原始碼學習筆記(一) 初遇篇MyBatis原始碼筆記
- async-validator 原始碼學習筆記(三):rule原始碼筆記
- Unity學習記錄-Universal RP渲染管道Unity
- UE4(5)逆向學習筆記(三)——UEDumper原始碼學習筆記原始碼
- 《Android原始碼設計模式》學習筆記之ImageLoaderAndroid原始碼設計模式筆記
- Flutter筆記——runApp發生了什麼(原始碼學習)Flutter筆記APP原始碼
- bootstrap-modal.js學習筆記(原始碼註釋)bootJS筆記原始碼
- async-validator 原始碼學習筆記(六):validate 方法原始碼筆記
- Qt Creator 原始碼學習筆記01,初識QTCQT原始碼筆記
- spring原始碼學習筆記之容器的基本實現(一)Spring原始碼筆記
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼一原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼二原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼三原始碼框架筆記MySql
- 【學習筆記】初次學習斜率最佳化的程式碼及筆記筆記
- numpy的學習筆記\pandas學習筆記筆記
- 基於C#的內網穿透學習筆記(附原始碼)C#內網穿透筆記原始碼
- Flutter筆記——幀繪製系列之一(原始碼學習)Flutter筆記原始碼
- Springcloud原始碼學習筆記1—— Zuul閘道器原理SpringGCCloud原始碼筆記Zuul
- JUC原始碼學習筆記2——AQS共享和Semaphore,CountDownLatch原始碼筆記AQSCountDownLatch
- Angular Universal 學習筆記 - 客戶端渲染和伺服器端渲染的區別Angular筆記客戶端伺服器
- SpringCloud 原始碼學習筆記2——Feign宣告式http客戶端原始碼分析SpringGCCloud原始碼筆記HTTP客戶端
- Python學習筆記—程式碼Python筆記
- Android學習筆記14-從原始碼分析Toast的建立過程Android筆記原始碼AST
- Adnroid原始碼學習筆記:Handler 執行緒間通訊原始碼筆記執行緒
- Qt Creator 原始碼學習筆記02,認識框架結構QT原始碼筆記框架
- 【初學】Spring原始碼筆記之零:閱讀原始碼Spring原始碼筆記
- shell指令碼學習筆記-1指令碼筆記