通過上一篇的學習實踐蔼夜,我們了解了ExoPlayer的優(yōu)缺點以及基本用法,今天我們進入ExoPlayer的音頻播放實踐压昼,我們來一起實現(xiàn)一個簡單的音頻播放器求冷。
目錄
- 媒體播放框架MediaSession
- MediaSession框架+ExoPlayer 簡單音樂播放器實踐
- 播放網絡音樂
- 播放/暫停
- 歌曲切換
- 倍速播放
- 資料
- 收獲
一、媒體播放框架MediaSession
音頻播放器并不總是需要使其UI可見窍霞。一旦開始播放音頻匠题,播放器就可以作為后臺任務運行。用戶可以切換到另一個應用程序但金,并繼續(xù)聽韭山。
要在Android中實現(xiàn)這一設計,您可以使用兩個組件構建一個音頻應用程序: activity(展示所用) 和播放器service冷溃。如果用戶切換到另一個應用程序钱磅,則該service可以在后臺運行。通過將音頻應用程序的兩個部分分解為單獨的組件似枕,每個組件可以獨立運行盖淡。與播放器相比,UI通常是短暫的菠净,可能會在沒有UI的情況下運行很長時間禁舷。
在設計音樂播放器APP架構時彪杉,有幾種常用的做法
方案一
- 注冊Service,用于數(shù)據(jù)設置牵咙、音樂控制派近,在Service中自定義播放器的一些狀態(tài)值和回調接口用于流程控制
- 通過廣播、aidl等實現(xiàn)和頁面層邏輯的通信洁桌,使得用戶可以通過界面控制音樂的播放渴丸、暫停、切換另凌、seek等操作
- 使用RemoteControlClient(低版本)或者MediaSession(>5.0或者MediaSessionCompat)進行多端設備或者跨APP媒體會話
方案二
Android5.0時推出的MediaSession框架(Supprot包中MediaSessionCompat也對低版本做了支持)谱轨,專門用來解決媒體播放時界面和Service通信的問題,在結構低耦合方面的設計做的比較好
支持庫提供了兩個類來實現(xiàn)此客戶端/服務器方法:MediaBrowserService和MediaBrowser吠谢。該服務組件被實現(xiàn)為包含媒體會話及其播放器的MediaBrowserService的子類土童。使用UI和媒體控制器的活動應包括與MediaBrowserService進行通信的MediaBrowser。
使用MediaBrowserService可以讓隨身設備(如Android Auto and Wear)輕松發(fā)現(xiàn)您的應用工坊,連接到它献汗,瀏覽內容和控制播放,而無需訪問您的Activity
我們今天的學習實踐是基于方案二的MediaSession的框架
圖片來自 媒體應用架構概覽
MediaBrowser
用來連接MediaBrowserService和訂閱數(shù)據(jù)王污,通過他的回調可以獲取和Service的連接狀態(tài)以及獲取在Service中異步獲取的音樂數(shù)據(jù)(這個一般不在Service中進行獲取罢吃,因為涉及到的是具體的業(yè)務邏輯)
MediaBrowserService
是一個Service,封裝了媒體相關的一些功能昭齐,通過onGetRoot的返回值決定是否允許客戶端連接尿招。onLoadChildren回調在Sercive中異步獲取的數(shù)據(jù)給到MediaBrowser。也包含媒體播放器實例(比如我們本篇實踐的ExoPlayer)
MediaSession
一般在MediaBrowserService的onCreate中創(chuàng)建阱驾,通過MediaSession.CallBack回調接收MediaController發(fā)來的指令就谜,觸發(fā)對應的播放器相關的操作
MediaController
MediaContoller的創(chuàng)建需要MediaSession的配對令牌,在MediaBrowser連接服務成功之后創(chuàng)建里覆。MediaController可以主動的發(fā)送指令或者被動的接收MediaController.Callback回調來改變播放狀態(tài)和界面刷新吁伺。
更詳細的介紹請參考官方文檔或者Android 媒體播放框架MediaSession分析與實踐
二、 簡單實踐
下面我們看下如何使用MediaSession框架實現(xiàn)簡單的音頻播放
2.1 Server端實現(xiàn)
首先我們繼承MediaBrowserServiceCompat實現(xiàn)和注冊Service
public class MusicService extends MediaBrowserServiceCompat {
private static final String TAG = "MusicService";
private SimpleExoPlayer exoPlayer;
private MediaSessionCompat mediaSession;
/**
* 當服務收到onCreate()生命周期回調方法時租谈,它應該執(zhí)行以下步驟:
* 1. 創(chuàng)建并初始化media session
* 2. 設置media session回調
* 3. 設置media session token
*/
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
//1. 創(chuàng)建并初始化MediaSession
mediaSession = new MediaSessionCompat(getApplicationContext(), TAG);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID |
PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH |
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SEEK_TO)
.build();
mediaSession.setPlaybackState(playbackState);
//2. 設置mediaSession回調
mediaSession.setCallback(new MyMediaSessionCallBack());
//3. 設置mediaSessionToken
setSessionToken(mediaSession.getSessionToken());
//創(chuàng)建播放器實例
exoPlayer = new SimpleExoPlayer.Builder(getApplicationContext()).build();
}
}
MediaSessionCompat.Callback的回調用于接收業(yè)務成通過mediaController.getTransportControls進行播放相關操作(播放篮奄、暫停、seek割去、倍速等等)的回調
/**
* 用于接收由MediaControl觸發(fā)的改變窟却,內部封裝實現(xiàn)播放器和播放狀態(tài)的改變
*/
private class MyMediaSessionCallBack extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
super.onPlay();
Log.i(TAG, "onPlay: ");
exoPlayer.play();
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause: ");
exoPlayer.pause();
}
@Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
Log.i(TAG, "onSeekTo: pos=" + pos);
exoPlayer.seekTo(pos);
}
...
}
MediaBrowserServiceCompat有兩個回調方法onGetRoot和onLoadChildren。其中onGetRoot用于告訴MediaBrowser是否連接連接成功呻逆;onLoadChildren則是加載音視頻數(shù)據(jù)夸赫。
具體使用如下:
@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
Log.i(TAG, "onGetRoot: clientPackageName=" + clientPackageName + " clientUid=" + clientUid + " pid=" + Binder.getCallingPid()
+ " uid=" + Binder.getCallingUid());
//返回非空,表示連接成功
return new BrowserRoot("media_root_id", null);
}
//獲取音視頻信息(這個更應該是在業(yè)務層處理事情)
@Override
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
Log.i(TAG, "onLoadChildren: parentId=" + parentId);
List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
if (TextUtils.equals("media_root_id", parentId)) {
}
ArrayList<MusicEntity> musicEntityList = getMusicEntityList();
for (int i = 0; i < musicEntityList.size(); i++) {
MusicEntity musicEntity = musicEntityList.get(i);
MediaMetadataCompat metadataCompat = buildMediaMetadata(musicEntity);
if (i == 0) {
mediaSession.setMetadata(metadataCompat);
}
mediaItems.add(new MediaBrowserCompat.MediaItem(metadataCompat.getDescription(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
exoPlayer.addMediaItem(MediaItem.fromUri(musicEntity.source));
}
//當設置多首歌曲組成隊列時報錯
// IllegalStateException: sendResult() called when either sendResult() or sendError() had already been called for: media_root_id
//原因咖城,之前在for處理了茬腿,應該在設置好mediaItems列表后呼奢,統(tǒng)一設置result
result.sendResult(mediaItems);
Log.i(TAG, "onLoadChildren: addMediaItem");
initExoPlayerListener();
exoPlayer.prepare();
Log.i(TAG, "onLoadChildren: prepare");
}
private void initExoPlayerListener() {
exoPlayer.addListener(new Player.EventListener() {
@Override
public void onPlaybackStateChanged(int state) {
long currentPosition = exoPlayer.getCurrentPosition();
long duration = exoPlayer.getDuration();
//狀態(tài)改變(播放器內部發(fā)生狀態(tài)變化的回調,
// 包括
// 1. 用戶觸發(fā)的 比如: 手動切歌曲切平、暫停握础、播放、seek等悴品;
// 2. 播放器內部觸發(fā) 比如: 播放結束禀综、自動切歌曲等)
//該如何通知給ui業(yè)務層吶?苔严?好些只能通過回調
//那有該如何 --》查看源碼得知通過setPlaybackState設置
Log.i(TAG, "onPlaybackStateChanged: currentPosition=" + currentPosition + " duration=" + duration + " state=" + state);
int playbackState;
switch (state) {
default:
case Player.STATE_IDLE:
playbackState = PlaybackStateCompat.STATE_NONE;
break;
case Player.STATE_BUFFERING:
playbackState = PlaybackStateCompat.STATE_BUFFERING;
break;
case Player.STATE_READY:
if(exoPlayer.getPlayWhenReady()){
playbackState = PlaybackStateCompat.STATE_PLAYING;
}else {
playbackState = PlaybackStateCompat.STATE_PAUSED;
}
break;
case Player.STATE_ENDED:
playbackState = PlaybackStateCompat.STATE_STOPPED;
break;
}
//播放器的狀態(tài)變化定枷,通過mediasession告訴在ui業(yè)務層注冊的MediaControllerCompat.Callback進行回調
setPlaybackState(playbackState);
}
private void setPlaybackState(int playbackState) {
float speed = exoPlayer.getPlaybackParameters() == null ? 1f : exoPlayer.getPlaybackParameters().speed;
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder().setState(playbackState, exoPlayer.getCurrentPosition(), speed).build());
}
@NotNull
private ArrayList<MusicEntity> getMusicEntityList() {
ArrayList<MusicEntity> list = new ArrayList<MusicEntity>();
...
MusicEntity musicEntity2 = new MusicEntity();
musicEntity2.id = "wake_up_02";
musicEntity2.title = "Geisha";
musicEntity2.album = "Wake Up";
musicEntity2.artist = "Media Right Productions";
musicEntity2.genre = "Electronic";
musicEntity2.source = "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/02_-_Geisha.mp3";
musicEntity2.image = "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg";
musicEntity2.trackNumber = 2;
musicEntity2.totalTrackCount = 13;
musicEntity2.duration = 267;
musicEntity2.site = "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/";
list.add(musicEntity2);
return list;
}
2.2 Client端實現(xiàn)
下面我們再來看下Client端的實現(xiàn)
public class ExoSimpleAudioPlayerActivity extends Activity implements View.OnClickListener {
private MediaBrowserCompat mediaBrowser;
private MediaBrowserCompat.ConnectionCallback mConnectionCallbacks = new MyConnectionCallback();
private MediaControllerCompat.Callback mMediaControllerCallback;
private MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_audio);
...
//mConnectionCallbacks 是C-S連接的callback
mediaBrowser = new MediaBrowserCompat(this, new ComponentName(this, MusicService.class),
mConnectionCallbacks, null);
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart: ");
//發(fā)出C-S連接請求 創(chuàng)建MusicService,收到onGetRoot回調值不為空說明建立連接成功--》然后觸發(fā)MyConnectionCallback的回調onConnected
mediaBrowser.connect();
// subscribe();
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop: ");
mediaBrowser.disconnect();
}
}
MediaBrowserCompat.ConnectionCallback用于接收與Server端連接的狀態(tài)回調
public class MyConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
@Override
public void onConnected() {
super.onConnected();
Log.i(TAG, "onConnected: MyConnectionCallback");
//MediaBrowser和MediaBrowerService建立連接之后會回調該方法
MediaSessionCompat.Token sessionToken = mediaBrowser.getSessionToken();
//建立連接之后再創(chuàng)建MediaController
mediaController = new MediaControllerCompat(ExoSimpleAudioPlayerActivity.this, sessionToken);
MediaControllerCompat.setMediaController(ExoSimpleAudioPlayerActivity.this, mediaController);
subscribe();
//MediaController發(fā)送命令
buildTransportControls();
if (mMediaControllerCallback == null) {
//這個callback 是Controller的callback届氢,即用戶觸發(fā)了播放欠窒、暫停,后發(fā)生狀態(tài)變化的回調退子。
//像播放結束贱迟、自動切歌,則無法收到該回調(那該如何處理吶絮供?)
mMediaControllerCallback = new MediaControllerCompat.Callback() {
//這里的回調,只有用戶觸發(fā)的才會有相應的回調茶敏。
//播放結束 這里沒有
//ExoPlayer getDuration : https://stackoverflow.com/questions/35298125/exoplayer-getduration
// Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
Log.i(TAG, "onPlaybackStateChanged: state=" + state.getState());
if (PlaybackStateCompat.STATE_PLAYING == state.getState()) {
playButton.setText("暫停");
} else {
playButton.setText("播放");
}
updatePlaybackState(state);
MediaMetadataCompat metadata = mediaController.getMetadata();
updateDuration(metadata);
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
super.onMetadataChanged(metadata);
durationSet = false;
Log.i(TAG, "onMetadataChanged: metadata=" + metadata.toString());
updateDuration(metadata);
}
}
mediaController.registerCallback(mMediaControllerCallback);
PlaybackStateCompat state = mediaController.getPlaybackState();
updatePlaybackState(state);
updateProgress();
if (state != null && (state.getState() == PlaybackStateCompat.STATE_PLAYING ||
state.getState() == PlaybackStateCompat.STATE_BUFFERING)) {
scheduleSeekbarUpdate();
}
//通過mediaController獲取MediaMetadataCompat
MediaMetadataCompat metadata = mediaController.getMetadata();
updateDuration(metadata);
}
@Override
public void onConnectionFailed() {
super.onConnectionFailed();
}
}
2.3 基本功能
歌曲播放播放暫停
當用戶點擊了播放/暫停按鈕后壤靶,獲取當前的播放狀態(tài),通過mediaController.getTransportControls給到通過Binder給到mediaSession惊搏,在service中MediaSessionCompat.Callback改變Exoplayer的播放狀態(tài)贮乳,exoplayer的onPlaybackStateChanged收到播放狀態(tài)改變的通知后觸發(fā),給mediasession設置mediaSession.setPlaybackState
對應關鍵代碼如下:
client端用戶點擊事件處理
//ExoSimpleAudioPlayerActivity.java
PlaybackStateCompat playbackState = mediaController.getPlaybackState();
int state = playbackState.getState();
Log.i(TAG, "onClick: state=" + state);
//通過 mediaController.getTransportControls 觸發(fā)MediaSessionCompat.Callback回調--》進行播放控制
if (state == PlaybackStateCompat.STATE_PLAYING) {
mediaController.getTransportControls().pause();
} else {
mediaController.getTransportControls().play();
}
//Server端MediasessionCallback實現(xiàn)恬惯,接收mediaController.getTransportControls()的事件
//com.example.myplayer.audio.MusicService.MyMediaSessionCallBack
@Override
public void onPlay() {
super.onPlay();
Log.i(TAG, "onPlay: ");
exoPlayer.play();
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause: ");
exoPlayer.pause();
}
//server端 exoplayer狀態(tài)變化監(jiān)聽
//com.example.myplayer.audio.MusicService#initExoPlayerListener
exoPlayer.addListener(new Player.EventListener() {
@Override
public void onPlaybackStateChanged(int state) {
long currentPosition = exoPlayer.getCurrentPosition();
long duration = exoPlayer.getDuration();
//狀態(tài)改變(播放器內部發(fā)生狀態(tài)變化的回調向拆,
// 包括
// 1. 用戶觸發(fā)的 比如: 手動切歌曲、暫停酪耳、播放浓恳、seek等;
// 2. 播放器內部觸發(fā) 比如: 播放結束碗暗、自動切歌曲等)
//該如何通知給ui業(yè)務層吶颈将??好些只能通過回調
//那有該如何 --》查看源碼得知通過setPlaybackState設置
Log.i(TAG, "onPlaybackStateChanged: currentPosition=" + currentPosition + " duration=" + duration + " state=" + state);
int playbackState;
switch (state) {
default:
case Player.STATE_IDLE:
playbackState = PlaybackStateCompat.STATE_NONE;
break;
case Player.STATE_BUFFERING:
playbackState = PlaybackStateCompat.STATE_BUFFERING;
break;
case Player.STATE_READY:
if(exoPlayer.getPlayWhenReady()){
playbackState = PlaybackStateCompat.STATE_PLAYING;
}else {
playbackState = PlaybackStateCompat.STATE_PAUSED;
}
break;
case Player.STATE_ENDED:
playbackState = PlaybackStateCompat.STATE_STOPPED;
break;
}
//播放器的狀態(tài)變化言疗,通過mediasession告訴在ui業(yè)務層注冊的MediaControllerCompat.Callback進行回調
setPlaybackState(playbackState);
}
}
private void setPlaybackState(int playbackState) {
float speed = exoPlayer.getPlaybackParameters() == null ? 1f : exoPlayer.getPlaybackParameters().speed;
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder().setState(playbackState, exoPlayer.getCurrentPosition(), speed).build());
}
雖然知道了怎么使用晴圾,但是整個流程是怎樣的吶?
其中用到了Handler和Binder的線程和進程通信相關的知識噪奄,后續(xù)我們專題單獨深入學習實踐下死姚,這里我們先順著流程畫下播放/暫停的流程圖人乓,從用戶按下按鈕到播放器開始播放以及頁面更新的整個流程是怎樣的。
上一首下一首切換
歌曲切換流程個上面的播放流程基本上一致都毒,
//com.example.myplayer.audio.ExoSimpleAudioPlayerActivity#onClick
if (id == R.id.prev) {
if (mediaController != null) {
mediaController.getTransportControls().skipToPrevious();
}
} else if (id == R.id.next) {
if (mediaController != null) {
mediaController.getTransportControls().skipToNext();
}
}
區(qū)別在于 沒有觸發(fā)ExoPlayer的播放回調色罚,需要再sessionCallback中調用exoplayer的next/prev進行歌曲切換,并且設置新的playstate狀態(tài)給到mession
//com.example.myplayer.audio.MusicService.MyMediaSessionCallBack
@Override
public void onSkipToNext() {
super.onSkipToNext();
Log.i(TAG, "onSkipToNext: ");
exoPlayer.next();
exoPlayer.setPlayWhenReady(true);
setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_NEXT);
mediaSession.setMetadata(getMediaMetadata(1));
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
Log.i(TAG, "onSkipToPrevious: ");
exoPlayer.previous();
exoPlayer.setPlayWhenReady(true);
setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS);
mediaSession.setMetadata(getMediaMetadata(0));
}
最終MediaControllerCallback的onPlaybackStateChanged收到回調温鸽,根據(jù)狀態(tài)進行
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
...
if (state.getState() == PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS || state.getState() == PlaybackStateCompat.STATE_SKIPPING_TO_NEXT) {
updateShowMediaInfo(description);
}
}
private void updateShowMediaInfo(MediaDescriptionCompat description) {
if (description == null) return;
titleView.setText(description.getTitle());
artistView.setText(description.getSubtitle());
Glide.with(ExoSimpleAudioPlayerActivity.this).load(description.getIconUri().toString()).into(iconView);
Uri mediaUri = description.getMediaUri();
Uri iconUri = description.getIconUri();
Log.i(TAG, "onChildrenLoaded: title=" + description.getTitle() + " subtitle=" + description.getSubtitle()
+ " mediaUri=" + mediaUri + " iconUri=" + iconUri);
}
倍速
//com.example.myplayer.audio.ExoSimpleAudioPlayerActivity#onClick
if (id == R.id.speed) {
if (mediaController != null) {
float speed = getSpeed();
speedView.setText("倍速 " + speed);
mediaController.getTransportControls().setPlaybackSpeed(speed);
}
}
float[] speedArray = new float[]{0.5f, 1f, 1.5f, 2f};
int curSpeedIndex = 1;
private float getSpeed() {
if (curSpeedIndex > 3) {
curSpeedIndex = 0;
}
return speedArray[curSpeedIndex++];
}
然后再MediaSessionCallBack中實現(xiàn)onSetPlaybackSpeed回調保屯,進行播放倍速設置以及mession的設置
//com.example.myplayer.audio.MusicService.MyMediaSessionCallBack
@Override
public void onSetPlaybackSpeed(float speed) {
super.onSetPlaybackSpeed(speed);
Log.i(TAG, "onSetPlaybackSpeed: speed=" + speed);
PlaybackParameters playParams = new PlaybackParameters(speed);
exoPlayer.setPlaybackParameters(playParams);
//重新設置mediaSession.setPlaybackState 告知 監(jiān)聽者 speed變化
setPlaybackState(exoPlayer.getPlaybackState());
}
private void setPlaybackState(int playbackState) {
float speed = exoPlayer.getPlaybackParameters() == null ? 1f : exoPlayer.getPlaybackParameters().speed;
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder().setState(playbackState, exoPlayer.getCurrentPosition(), speed).build());
}
需要注意
播放狀態(tài) MediaSession框架和ExoPlayer的不同與聯(lián)系
//android.support.v4.media.session.PlaybackStateCompat
TATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM
//com.google.android.exoplayer2.Player.State
STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED
2.4 存在的問題
上面的實踐中存在一些問題,比如數(shù)據(jù)如何交互涤垫,我們看到Activity直接和Service通過MediaSession框架中的各種回調進行通信姑尺,播放器ExoPlayer封裝在Service內,數(shù)據(jù)的獲取也在Service中蝠猬。這明顯和真實的場景有差異切蟋。
另外播放管理相關的沒有分離,播放隊列的維護榆芦,播放狀態(tài)的管理等等沒有統(tǒng)一的管理柄粹,不利于擴展擴展更換播放器等。
下一篇我們來分析umap的實現(xiàn)匆绣,它是如何進行架構的藻三,如何解決上面的問題的。
完整代碼已上傳至 github https://github.com/ayyb1988/mediajourney
三盅粪、資料
ExoPlayer
- Android開發(fā)之ExoPlayer的學習和使用(音頻)講解
- Media streaming with ExoPlayer
- ExoPlayer blog
- ExoPlayer developer guide
- Easy Audio Focus with ExoPlayer
UAMP相關
- Android 解讀開源項目UniversalMusicPlayer(播放控制層)
- Android 媒體播放框架MediaSession分析與實踐
- Android媒體應用(一)
- 音頻應用概覽
- 打造基于MediaSessionCompat的音樂播放(一)
- 打造基于MediaSessionCompat的音樂播放(二)
音頻播放器相關開源項目
其他
網絡接口以及歌曲來源
來自google官方的uamp開源項目
http://storage.googleapis.com/automotive-media/music.json
https://storage.googleapis.com/uamp/catalog.json
Music provided by the [Free Music Archive](http://freemusicarchive.org/).
- [Irsen's Tale](http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/) by
[Kai Engel](http://freemusicarchive.org/music/Kai_Engel/).
- [Wake Up](http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/) by
[The Kyoto Connection](http://freemusicarchive.org/music/The_Kyoto_Connection/).
長音頻:https://v.typlog.com/oohomechat/8385162738_706123.mp3
四咏删、收獲
通過本篇的學習實踐,
- 了解媒體播放框架MediaSession
- 使用MediaSession框架實現(xiàn)簡單的音頻播放器(播放/暫停拣凹、切歌森爽、倍速)
- 了解原理、具體實踐以及流程分析嚣镜,我們基本了解MediaSession的框架以及ExoPlayer簡單實用爬迟。
但是一個音頻播放器以下功能也是基本功能:邊緩存變播放、播放隊列菊匿、淡入淡出付呕、音頻焦點、后臺播放跌捆,該如何比較好的實現(xiàn)吶凡涩?在具體實踐之前我們先來學習分析下uamp這個google開源的音頻播放器是如何架構的,看看在數(shù)據(jù)源設置以及播放管理方面是否可以學習借鑒疹蛉。
感謝你的閱讀
下一篇我們繼續(xù)學習實踐ExoPlayer活箕,分析uamp的設計與實現(xiàn),歡迎關注公眾號“音視頻開發(fā)之旅”,一起學習成長育韩。
歡迎交流