文章集合:
Universal Music Player 源碼解析(一)--MediaSession框架
Univeral Music Player 源碼解析 -- 讓人頭疼的playback
Universal Music Player 源碼解析(二)--MusicService 和 MediaController
Universal Music Player 源碼分析 (三)-- 其他類分析
項目中的playback
層是非常令人頭痛的,因為他的回調(diào)之多,不亞于MediaSession框架使用的回調(diào),而且這里是播放器主要的功能的看點所在,所以這里的理解也要比其他地方更加深刻才可以,下面,我從項目目錄入手剖析一下:
看完這篇文章你將會知道這樣幾個部分:
- LocalPlayback 和 PlaybackManager 的關系
- QueueManager的介紹
- MusicService 和 MediaSession的關系
除了castplayback
其他的都需要仔細理解,因為這個是用于電視上播放需要的.
PlaybackManager
實現(xiàn)了Playback的callback接口,
LocalPlayback
是本地音樂的播放類,實現(xiàn)了Playback
接口,瞄一眼Playback
接口的structure:
下面從LocalPlayback切入,跟著源碼分析一下:
LocalPlayback
它的重要之處和難以理解之處主要在下面幾個方面:LocalPlayback所需要實現(xiàn)的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給予默認的實現(xiàn)
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 數(shù)據(jù)可以被讀取的接口
Extractor : 從所屬的格式中解析出媒體文件的數(shù)據(jù)
使用mExoPlayer 會觸發(fā) 這個回調(diào):
@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的實例,函數(shù)的實現(xiàn)如下:
//updatePlaybackState()就是對state的一個管理 沒有我們關心的關于播放的源碼
@Override
public void onPlaybackStatusChanged(int state) {
updatePlaybackState(null);
}
上面說過mExoPlayer.prepare()ok之后會回調(diào)函數(shù),但是在上面的代碼片段的下面的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 時候繼續(xù)播放
mExoPlayer.setPlayWhenReady(true);
事實上,ExoPlayer和我們想象的是由差別的,其中并沒有play()方法,根據(jù)這篇博客來看只要調(diào)用
prepare()
setPlayWhenReady()
之后當player準備好了,就可以播放.
繼續(xù)解釋一下上面奇怪的名詞:
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指的是當耳機被拔下來的時候會觸發(fā),這個也符合我們生活中的體驗,當耳機被拔下來的時候會有zizizi的聲音,如果是QQ音樂或者是網(wǎng)易云音樂的話,當耳機拔出是不會繼續(xù)播放的,這個時候要交給MusicService繼續(xù)判斷一下,我們來看一下MusicService
如何處理是否繼續(xù)播放:
以下代碼出自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);
}
}
....
}
我們傳遞的狀態(tài)MusicService.CMD_PAUSE
是暫停,符合預期,音樂播放也會暫停.
好,繼續(xù)解釋下一個名詞:
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的回調(diào)中被處理,我們就看一下前面的幾個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的焦點有一個連續(xù)的失去的是哦后,但是開發(fā)者希望通過減小音量的方式而不讓用戶體驗很差
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);
}
我們根據(jù)LocalPlayback來分析UAM對AudioFoucs的處理:
@Override
public void play(QueueItem item) {
...
tryToGetAudioFocus();
registerAudioNoisyReceiver();
...
}
LocalPlayback 對Playback的實現(xiàn)方法play中使用了tryToGetAudioFocus();
同時也把剛才實現(xiàn)的onAudioFocusChangeListener
作為requestAudioFocus的一個參數(shù)
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,避免系統(tǒng)通知服務再次播放音頻,比如在音頻播放完成之后的某個時間段內(nèi),電話也結束了,此時沒有丟棄音頻焦點,系統(tǒng)又會通知自動播放
PlaybackManager
我們再來看一下PlayBackManager是做什么的:
開始的時候我不能理解為什么要PlaybackManager實現(xiàn)Playback.Callback接口,而讓Playback實現(xiàn)Playback接口,其實仔細看一下Playback的方法命名,這個是很有道理的:
Playback中是play()
pause()
stop()
這些比較具體的的抽象,理應應該交給一個具體的類去實現(xiàn),而PlayBackManager中實現(xiàn)的Playback.Callback是比較抽象的方法,就像公司中的Manger只是發(fā)布命令,完成任務的是其他的員工這樣.
我們來看看一下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 中實現(xiàn)了PlayerServiceCallback接口,所以這個callback是一個MusicService對象,MusicService實現(xiàn)的回調(diào)其中的
@Override
public void onPlaybackStart() {
mSession.setActive(true);
startService(new Intent(getApplicationContext(), MusicService.class));
}
是最關鍵的,開啟了一個service,service的onCreate()方法,繼續(xù)看看service oncreate方法.
注意
這里有一個問題,之前說過在BaseActivity中的 mediaBrowser.connect()會bind啟動 service,這個地方應該不會再次調(diào)用onCreate()方法了吧?
使得確實不會,這個地方和前文中沒有描述只是為了邏輯需要
,請大家注意下,MusicSerivce早就已經(jīng)onCreate過(在BaseActivity創(chuàng)建的時候),這個地方會直接執(zhí)行onStartCommand()
繼續(xù)看onCreate()方法,如果你仔細觀察你就會發(fā)現(xiàn)MusicService和MediaSession 聯(lián)系比較緊密,而在QueueManager的回調(diào)中大量的使用了有關于MediaSession的方法,我們以深度優(yōu)先
的方法看一下QueueManager內(nèi)部的實現(xiàn):
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,下面還有一個函數(shù)
QueueManager queueManager = new QueueManager(mMusicProvider, getResources(),
new QueueManager.MetadataUpdateListener() {
....
@Override
public void onCurrentQueueIndexUpdated(int queueIndex) {
mPlaybackManager.handlePlayRequest();
}
...
});
調(diào)用了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 以讓音樂播放,所以總結一下:
還有以handlePlayRequest()為核心分析的PlaybackManager,LocalPlayback ,MusicService的聯(lián)系
相關代碼:
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實現(xiàn)的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中被調(diào)用
MusicService.setCallback()
mSession.setCallback(mPlaybackManager.getMediaSessionCallback());
links
Android Audio Focus