文章集合:
Universal Music Player 源码解析(一)--MediaSession框架
Univeral Music Player 源码解析 -- 让人头疼的playback
Universal Music Player 源码解析(二)--MusicService 和 MediaController
Universal Music Player 源码分析 (三)-- 其他类分析
项目中的playback
层是非常令人头痛的,因为他的回调之多,不亚于MediaSession框架使用的回调,而且这里是播放器主要的功能的看点所在,所以这里的理解也要比其他地方更加深刻才可以,下面,我从项目目录入手剖析一下:
看完这篇文章你将会知道这样几个部分:
- LocalPlayback 和 PlaybackManager 的关系
- QueueManager的介绍
- MusicService 和 MediaSession的关系
除了castplayback
其他的都需要仔细理解,因为这个是用于电视上播放需要的.
PlaybackManager
实现了Playback的callback接口,
LocalPlayback
是本地音乐的播放类,实现了Playback
接口,瞄一眼Playback
接口的structure:
下面从LocalPlayback切入,跟着源码分析一下:
LocalPlayback
它的重要之处和难以理解之处主要在下面几个方面:LocalPlayback所需要实现的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给予默认的实现
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 数据可以被读取的接口
Extractor : 从所属的格式中解析出媒体文件的数据
使用mExoPlayer 会触发 这个回调:
@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的实例,函数的实现如下:
//updatePlaybackState()就是对state的一个管理 没有我们关心的关于播放的源码
@Override
public void onPlaybackStatusChanged(int state) {
updatePlaybackState(null);
}
上面说过mExoPlayer.prepare()ok之后会回调函数,但是在上面的代码片段的下面的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 时候继续播放
mExoPlayer.setPlayWhenReady(true);
事实上,ExoPlayer和我们想象的是由差别的,其中并没有play()方法,根据这篇博客来看只要调用
prepare()
setPlayWhenReady()
之后当player准备好了,就可以播放.
继续解释一下上面奇怪的名词:
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指的是当耳机被拔下来的时候会触发,这个也符合我们生活中的体验,当耳机被拔下来的时候会有zizizi的声音,如果是QQ音乐或者是网易云音乐的话,当耳机拔出是不会继续播放的,这个时候要交给MusicService继续判断一下,我们来看一下MusicService
如何处理是否继续播放:
以下代码出自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);
}
}
....
}
我们传递的状态MusicService.CMD_PAUSE
是暂停,符合预期,音乐播放也会暂停.
好,继续解释下一个名词:
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的回调中被处理,我们就看一下前面的几个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的焦点有一个连续的失去的是哦后,但是开发者希望通过减小音量的方式而不让用户体验很差
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);
}
我们根据LocalPlayback来分析UAM对AudioFoucs的处理:
@Override
public void play(QueueItem item) {
...
tryToGetAudioFocus();
registerAudioNoisyReceiver();
...
}
LocalPlayback 对Playback的实现方法play中使用了tryToGetAudioFocus();
同时也把刚才实现的onAudioFocusChangeListener
作为requestAudioFocus的一个参数
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,避免系统通知服务再次播放音频,比如在音频播放完成之后的某个时间段内,电话也结束了,此时没有丢弃音频焦点,系统又会通知自动播放
PlaybackManager
我们再来看一下PlayBackManager是做什么的:
开始的时候我不能理解为什么要PlaybackManager实现Playback.Callback接口,而让Playback实现Playback接口,其实仔细看一下Playback的方法命名,这个是很有道理的:
Playback中是play()
pause()
stop()
这些比较具体的的抽象,理应应该交给一个具体的类去实现,而PlayBackManager中实现的Playback.Callback是比较抽象的方法,就像公司中的Manger只是发布命令,完成任务的是其他的员工这样.
我们来看看一下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 中实现了PlayerServiceCallback接口,所以这个callback是一个MusicService对象,MusicService实现的回调其中的
@Override
public void onPlaybackStart() {
mSession.setActive(true);
startService(new Intent(getApplicationContext(), MusicService.class));
}
是最关键的,开启了一个service,service的onCreate()方法,继续看看service oncreate方法.
注意
这里有一个问题,之前说过在BaseActivity中的 mediaBrowser.connect()会bind启动 service,这个地方应该不会再次调用onCreate()方法了吧?
使得确实不会,这个地方和前文中没有描述只是为了逻辑需要
,请大家注意下,MusicSerivce早就已经onCreate过(在BaseActivity创建的时候),这个地方会直接执行onStartCommand()
继续看onCreate()方法,如果你仔细观察你就会发现MusicService和MediaSession 联系比较紧密,而在QueueManager的回调中大量的使用了有关于MediaSession的方法,我们以深度优先
的方法看一下QueueManager内部的实现:
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 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,下面还有一个函数
QueueManager queueManager = new QueueManager(mMusicProvider, getResources(),
new QueueManager.MetadataUpdateListener() {
....
@Override
public void onCurrentQueueIndexUpdated(int queueIndex) {
mPlaybackManager.handlePlayRequest();
}
...
});
调用了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的联系
相关代码:
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实现的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中被调用
MusicService.setCallback()
mSession.setCallback(mPlaybackManager.getMediaSessionCallback());
links
Android Audio Focus