轉(zhuǎn)載請注明出處:http://www.itdecent.cn/p/fc07935a0b04
項目代碼地址:https://github.com/Android-Jungle/android-jungle-mediaplayer
?
1、MediaPlayer 簡介
????????首先,我們基于系統(tǒng)的 android.media.MediaPlayer 實現(xiàn)我們的 BaseMediaPlayer。當(dāng)然,我們也可以使用其他第三方的 Player 來實現(xiàn),具體就在實際使用中按需求來做。
????????那么,我們需要先對 MediaPlayer 做一些必要的了解。通過網(wǎng)上的資料搜索,我認(rèn)為這篇文章對 MediaPlayer 的各種狀態(tài)遷移和使用規(guī)范做了較好的闡述:
????????我從這篇文章里面借了一張圖如下:
????????通過對上圖的理解,結(jié)合播放器的功能特性,抽離出了主要的狀態(tài)遷移如下:
????????我們著重需要關(guān)注的方法列表如下:
| 方法 | 特性 |
|---|---|
new MediaPlayer()創(chuàng)建一個 MediaPlayer,處于 Idle 狀態(tài) | |
| reset() | 重置 MediaPlayer,使其切換到 Idle 狀態(tài) |
| setDataSource(context, uri) | 設(shè)置視頻數(shù)據(jù)源,切換到 Initialized 狀態(tài) |
| prepare() | 準(zhǔn)備數(shù)據(jù),同步方法,完成后將切換到 Prepared 狀態(tài) |
| prepareAsync() | 異步準(zhǔn)備數(shù)據(jù),處于 Preparing 狀態(tài),準(zhǔn)備完畢后通過回調(diào) OnPreparedListener.onPrepared 方法,處于 Prepared 狀態(tài) |
| start() | 開始/恢復(fù)播放,切換到 Started 狀態(tài) |
| seekTo(millSeconds) | 跳轉(zhuǎn)進(jìn)度 |
| pause() | 暫停播放,切換到 Paused 狀態(tài) |
| stop() | 停止播放,切換到 Stopped 狀態(tài) |
| release() | 釋放播放器資源,切換到終止 End 狀態(tài),這時候不允許對 MediaPlayer 做任何操作了 |
| setDisplay(surfaceHolder) | 設(shè)置畫面展示 SurfaceHolder |
2、關(guān)注的回調(diào)
???????? MediaPlayer 有眾多回調(diào) Listener,我們需要著重關(guān)注:
| 回調(diào) | 特性 |
|---|---|
| MediaPlayer.OnPreparedListener | 視頻通過 prepareAsync 異步準(zhǔn)備好之后回調(diào) |
| MediaPlayer.OnCompletionListener | 視頻播放完成后的回調(diào) |
| MediaPlayer.OnVideoSizeChangedListener | 視頻畫面大小更改的回調(diào),我們一般通過這個回調(diào)更改 SurfaceView 的大小及位置,以達(dá)到等比例縮放播放的效果 |
| MediaPlayer.OnBufferingUpdateListener | 視頻緩沖進(jìn)度更改回調(diào),一般我們通過這個回調(diào)更改 SeekBar 的 SecondaryProgress |
| MediaPlayer.OnErrorListener | 播放器內(nèi)部發(fā)生錯誤回調(diào),一般是錯誤的調(diào)用或者其他錯誤引起 |
3、使用 MediaPlayer 必需的步驟
????????接下來我們基于系統(tǒng)的 MediaPlayer 實現(xiàn) SystemMediaPlayerImpl,它繼承自 BaseMediaPlayer。一般使用 MediaPlayer 有如下步驟:
| 步驟 | 內(nèi)容 |
|---|---|
| 初始化 | new MediaPlayer |
| 設(shè)置一系列 Listener | OnPreparedListener 等等 |
| 設(shè)置數(shù)據(jù)源 | setDataSource. 可以是本地文件,也可以是網(wǎng)絡(luò)數(shù)據(jù) Url |
| 處理 OnPreparedListener.onPrepared 回調(diào) | 設(shè)置畫面展示 SurfaceHolder,并調(diào)用 start 開始播放。如果需要從視頻的某個位置播放,可在這里調(diào)用 seekTo(millSeconds) 直接跳轉(zhuǎn) |
| 處理 OnVideoSizeChangedListener.onVideoSizeChanged 回調(diào) | 在此通過獲取到的視頻畫面大小調(diào)整 SurfaceView 的位置及大小,達(dá)到等比例縮放的效果 |
| 處理 OnBufferingUpdateListener.onBufferingUpdate 回調(diào) | 再次可以更新界面上的進(jìn)度條的緩沖進(jìn)度 |
| 處理 OnCompletionListener.onCompletion 回調(diào) | 在此處理播放完成通知,可以按需求來做按章節(jié)連續(xù)播放或者退出頁面等等上層業(yè)務(wù)邏輯 |
| 處理 OnErrorListener.onError 回調(diào) | 處理錯誤,進(jìn)行 UI 的展示更新等等 |
4、SystemMediaPlayerImpl
????????我們根據(jù)上面的步驟,結(jié)合 上一篇文章 中介紹的 BaseMediaPlayer,實現(xiàn)出 SystemMediaPlayerImpl 如下:
/**
* SystemMediaPlayerImpl.java
*
* @author arnozhang
* @email zyfgood12@163.com
* @date 2015.9.25
*/
public class SystemMediaPlayerImpl extends BaseMediaPlayer {
private MediaPlayer mMediaPlayer;
public SystemMediaPlayerImpl(Context context, SurfaceView surfaceView) {
super(context, surfaceView);
init();
}
private void init() {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(onPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);
mMediaPlayer.setOnErrorListener(mOnErrorListener);
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
}
@Override
public void play(VideoInfo videoInfo) {
if (mMediaPlayer == null) {
return;
}
super.play(videoInfo);
// Clear old display and Reset.
mMediaPlayer.setDisplay(null);
mMediaPlayer.reset();
try {
mMediaPlayer.setDataSource(mContext, Uri.parse(videoInfo.getCurrentStreamUrl()));
} catch (IOException e) {
e.printStackTrace();
}
// Prepare For Play.
mMediaPlayer.prepareAsync();
notifyStartPlay();
notifyLoading();
}
@Override
public void pause() {
if (mMediaPlayer == null || !mMediaPlayerIsPrepared) {
return;
}
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
notifyPaused();
}
}
@Override
public void resume() {
if (mMediaPlayer == null || !mMediaPlayerIsPrepared) {
return;
}
mMediaPlayer.start();
notifyResumed();
}
@Override
public void stop() {
if (mMediaPlayer == null) {
return;
}
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
}
notifyStopped();
}
@Override
public void seekTo(int millSeconds) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(millSeconds);
}
}
@Override
public void setVolume(float volume) {
if (mMediaPlayer != null) {
mMediaPlayer.setVolume(volume, volume);
}
}
@Override
public int getDuration() {
return mMediaPlayer != null ? mMediaPlayer.getDuration() : 0;
}
@Override
public int getCurrentPosition() {
return mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0;
}
@Override
public void doDestroy() {
clearLoadingFailed();
mMediaPlayer.release();
mMediaPlayer = null;
}
@Override
public boolean isPlaying() {
return mMediaPlayer != null && mMediaPlayer.isPlaying();
}
@Override
protected void playWithDisplayHolder(SurfaceHolder holder) {
mMediaPlayer.setDisplay(holder);
mMediaPlayer.setScreenOnWhilePlaying(true);
mMediaPlayer.start();
mMediaPlayer.seekTo(0);
trySeekToStartPosition();
}
private void clearLoadingFailed() {
ThreadManager.getInstance().getUIHandler().removeCallbacks(mLoadingFailedRunnable);
}
MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer player) {
mAutoPlayWhenHolderCreated = false;
mMediaPlayerIsPrepared = true;
mIsLoading = false;
clearLoadingFailed();
notifyFinishLoading();
// Start Play.
SurfaceHolder holder = mSurfaceView.getHolder();
if (holder == null) {
mAutoPlayWhenHolderCreated = true;
} else {
playWithDisplayHolder(holder);
}
}
};
private void trySeekToStartPosition() {
int seekToPosition = mVideoInfo.getCurrentPosition();
if (mMediaPlayer != null && mMediaPlayerIsPrepared && mVideoSizeInitialized && seekToPosition > 0) {
mMediaPlayer.seekTo(seekToPosition);
}
}
MediaPlayer.OnCompletionListener mOnCompletionListener =
new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer player) {
player.seekTo(0);
notifyPlayComplete();
}
};
MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener =
new MediaPlayer.OnVideoSizeChangedListener() {
public void onVideoSizeChanged(MediaPlayer player, int width, int height) {
mVideoWidth = player.getVideoWidth();
mVideoHeight = player.getVideoHeight();
updateSurfaceSize();
mVideoSizeInitialized = true;
trySeekToStartPosition();
}
};
MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener =
new MediaPlayer.OnBufferingUpdateListener() {
public void onBufferingUpdate(MediaPlayer player, int percent) {
mBufferPercent = percent;
}
};
MediaPlayer.OnErrorListener mOnErrorListener = new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer player, int what, int extra) {
String errorWhat;
switch (what) {
case 1:
errorWhat = "MEDIA_ERROR_UNKNOWN";
break;
case 100:
errorWhat = "MEDIA_ERROR_SERVER_DIED";
break;
default:
errorWhat = "!";
}
String errorExtra;
switch (extra) {
case -1010:
errorExtra = "MEDIA_ERROR_UNSUPPORTED";
break;
case -1007:
errorExtra = "MEDIA_ERROR_MALFORMED";
break;
case -1004:
errorExtra = "MEDIA_ERROR_IO";
break;
case -110:
errorExtra = "MEDIA_ERROR_TIMED_OUT";
break;
default:
errorExtra = "!";
}
String msg = String.format("what:%#x,%s extra:%#x,%s", what, errorWhat, extra, errorExtra);
notifyError(what, msg);
return true;
}
};
}
????????下一篇我們將介紹手勢檢測與控制。