Android 从头搭建视频播放器——概述
Posted 牧秦丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 从头搭建视频播放器——概述相关的知识,希望对你有一定的参考价值。
【android】 从头搭建视频播放器(1)——概述
转载请注明出处:http://blog.csdn.net/arnozhang12/article/details/48731443
近来有做播放器方面的需求,在搭建过程中,逐渐对 Android 上面视频播放器的实现有了一些初步的了解,在此总结一下,在 Android 上面,如何从头考虑设计并最终实现一个功能完备的视频播放器。
1、功能 & 思路
我们通常看到一个通用的播放器如下:
在点击全屏按钮或者旋转屏幕后,可以展开到全屏:
我们可以看出,一个通用的播放器有如下一些功能点:
- 播放/暂停
- 全屏切换
- SeekBar 进度调节
- 手势调节屏幕亮度/音量/播放进度
- 屏幕旋转支持
其中,播放器的基础功能我们可以使用系统的 MediaPlayer
或者第三方的一些 Player 实现。全屏切换可以通过更改 ScreenOrientation
和屏幕布局
来完成。其他的手势调节可以通过 GestureDetector
来完成。屏幕旋转通过 OrientationEventListener
来实现。
2、基础设计
有了一个大致的功能描述以及实现思路之后,我们需要设计底层的基本交互及类。经过功能提炼及交互划分,首先的模块设计如下:
BaseMediaPlayer
:定义了一个播放器应该具备的基础接口;你可以跟进这个接口再加播放器底层,来实现不同的播放器;只提供播放的接口,不提供用户 UI 交互;SystemMediaPlayerImpl
:继承自 BaseMediaPlayer,是基于 Android 系统MediaPlayer
的一份实现;StrawMediaPlayer
:继承自 FrameLayout,是 Android 上的一个布局 View,封装了播放器的所有操作,用来进行可视化和用户 UI 交互;PlayerBottomControl
:播放器底部控件,用于控制播放、进度、全屏调节等;MediaPlayerGestureController
:播放器手势控制器,用于手势识别和相应的控制;ScreenOrientationSwitcher
:屏幕方向切换控制器。
基础的模块就这么一些,其中应该还有一些用于展示屏幕亮度、声音、进度的小的 View,自己可以轻松实现,在此没有列出来。
StrawMediaPlayer
就是我们最终提供出去的播放器控件,上层可以直接使用这个控件。
3、播放器布局
整个 StrawMediaPlayer 的布局如下:
通过结合功能点进行初步设计,整个播放器层级如下:
FrameLayout
:用于容纳所有的 View;SurfaceView
:用于展示视频内容;LoadingLayout
:在视频加载的时候,用于展示 Loading 画面;TopBarControl
:顶部的 Bar,用于展示视频信息、返回按钮等等;PlayerBottomControl
:底部的 Bar,用于展示视频控制按钮、播放进度、全屏切换等 View。
4、BaseMediaPlayer
结合模块设计及布局划分,我们先定义 BaseMediaPlayer
应该具备的接口。必须具备的接口如下:
接口 | 功能 |
---|---|
BaseMediaPlayer(context, surfaceView) | 构造函数 |
play(videoInfo) | 播放 |
addPlayerListener(listener) | 添加播放回调 Listener |
pause() | 暂停 |
resume() | 恢复播放 |
stop() | 停止播放 |
seekTo(millSeconds) | 跳转到某个进度播放 |
setVolume(volume) | 调节音量 |
getDuration() | 获取整个视频的时间,返回 ms |
getCurrentPosition() | 获取视频当前播放的进度,返回 ms |
doDestroy() | 销毁播放器,释放系统资源 |
isPlaying() | 是否正在播放 |
isLoading() | 是否正在加载视频 |
getBufferPercent() | 获取视频缓冲百分比 |
5、BaseMediaPlayerListener
外部有可能需要监听一系列 Player 的事件,这时候,我们需要通过 addPlayerListener
添加一个 Listener,用于捕获事件。该接口定义如下:
6、BaseMediaPlayer 代码片段
/**
* BaseMediaPlayer.java
*
* @author arnozhang
* @email zyfgood12@163.com
* @date 2015.9.25
*/
public abstract class BaseMediaPlayer
protected static final String TAG = "MediaPlayer";
protected static interface NotifyListenerRunnable
void run(BaseMediaPlayerListener listener);
protected Context mContext;
protected SurfaceView mSurfaceView;
protected SurfaceHolder.Callback mSurfaceCallback;
protected List<BaseMediaPlayerListener> mPlayerListeners = new ArrayList<>();
protected VideoInfo mVideoInfo;
protected int mVideoWidth;
protected int mVideoHeight;
protected boolean mIsLoading;
protected boolean mMediaPlayerIsPrepared;
protected boolean mVideoSizeInitialized;
protected int mBufferPercent;
protected int mVideoContainerZoneWidth;
protected boolean mAutoPlayWhenHolderCreated;
public BaseMediaPlayer(Context context, SurfaceView surfaceView)
mContext = context;
mSurfaceView = surfaceView;
initSurfaceCallback();
SurfaceHolder videoHolder = mSurfaceView.getHolder();
videoHolder.addCallback(mSurfaceCallback);
public void play(VideoInfo videoInfo)
if (!PlayerUtils.isNetworkAvailable())
StrawToast.makeText(mContext, R.string.network_connection_failed).show();
return;
mIsLoading = true;
mVideoSizeInitialized = false;
mMediaPlayerIsPrepared = false;
mVideoInfo = videoInfo;
Handler handler = ThreadManager.getInstance().getUIHandler();
handler.removeCallbacks(mLoadingFailedRunnable);
handler.postDelayed(mLoadingFailedRunnable, 30 * 1000);
public void addPlayerListener(BaseMediaPlayerListener listener)
mPlayerListeners.add(listener);
public abstract void pause();
public abstract void resume();
public abstract void stop();
public abstract void seekTo(int millSeconds);
public abstract void setVolume(float volume);
public abstract int getDuration();
public abstract int getCurrentPosition();
public abstract void doDestroy();
public abstract boolean isPlaying();
protected abstract void playWithDisplayHolder(SurfaceHolder holder);
public boolean isLoading()
return mIsLoading;
public boolean isLoadingOrPlaying()
return isLoading() || isPlaying();
public int getBufferPercent()
return mBufferPercent;
public Context getContext()
return mContext;
private void initSurfaceCallback()
mSurfaceCallback = new SurfaceHolder.Callback()
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
public void surfaceCreated(SurfaceHolder holder)
if (mAutoPlayWhenHolderCreated)
mAutoPlayWhenHolderCreated = false;
playWithDisplayHolder(holder);
public void surfaceDestroyed(SurfaceHolder holder)
if (isPlaying())
stop();
;
protected void updateSurfaceSize()
updateSurfaceSize(mVideoContainerZoneWidth);
public void updateSurfaceSize(int containerWidth)
if (mVideoContainerZoneWidth == containerWidth)
return;
mVideoContainerZoneWidth = containerWidth;
if (mVideoWidth == 0 || mVideoHeight == 0)
return;
final float ratio = (float) mVideoWidth / (float) mVideoHeight;
int width = 0;
int height = (int) (containerWidth / ratio);
if (height > mVideoHeight)
width = containerWidth;
else
height = mVideoHeight;
width = (int) (mVideoWidth * ratio);
ViewGroup.LayoutParams params = mSurfaceView.getLayoutParams();
params.width = width;
params.height = height;
mSurfaceView.setLayoutParams(params);
protected Runnable mLoadingFailedRunnable = new Runnable()
@Override
public void run()
notifyLoadFailed();
;
protected void notifyListener(NotifyListenerRunnable runnable)
for (BaseMediaPlayerListener listener : mPlayerListeners)
runnable.run(listener);
protected void notifyLoading()
LogUtils.e(TAG, "MediaPlayer Loading...");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onLoading();
);
protected void notifyFinishLoading()
LogUtils.e(TAG, "MediaPlayer Finish Loading!");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onFinishLoading();
);
protected void notifyLoadFailed()
LogUtils.e(TAG, "MediaPlayer Load **Failed**!!");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onLoadFailed();
);
protected void notifyError(final int what, final String message)
LogUtils.e(TAG, "MediaPlayer Error. what = %d, message = %s.", what, message);
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onError(what, message);
);
protected void notifyStartPlay()
LogUtils.e(TAG, "MediaPlayer Will Play!");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onStartPlay();
);
protected void notifyPlayComplete()
LogUtils.e(TAG, "MediaPlayer Play Current Complete!");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onPlayComplete();
);
protected void notifyPaused()
LogUtils.e(TAG, "MediaPlayer Paused.");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onPaused();
);
protected void notifyResumed()
LogUtils.e(TAG, "MediaPlayer Resumed.");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onResumed();
);
protected void notifyStopped()
LogUtils.e(TAG, "MediaPlayer Stopped!");
notifyListener(new NotifyListenerRunnable()
@Override
public void run(BaseMediaPlayerListener listener)
listener.onStopped();
);
以上是关于Android 从头搭建视频播放器——概述的主要内容,如果未能解决你的问题,请参考以下文章
Android 从头搭建视频播放器——SystemMediaPlayerImpl