【Android】 从头搭建视频播放器(1)——概述
来源:互联网 发布:asp编程要点 编辑:程序博客网 时间:2024/04/29 03:16
【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
应该具备的接口。必须具备的接口如下:
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】 从头搭建视频播放器(1)——概述
- 【Android】 从头搭建视频播放器(2)——SystemMediaPlayerImpl
- 【Android】 从头搭建视频播放器(3)——手势检测 & 控制
- 【Android】 从头搭建视频播放器(4)——屏幕旋转处理
- 【Android】 从头搭建视频播放器(5)——将所有放在一起
- Android:本地视频播放器开发 — 搜索本地视频(1)
- Android——视频播放器
- 27、从头学Android之多媒体--使用VideoView播放视频
- 27、从头学Android之多媒体--使用VideoView播放视频
- Android (视频播放器)
- AVPlayer自定制视频播放器(1)——视频播放器基本实现
- Android:本地视频播放器开发 — 搜索本地视频(2)
- Android 视频播放器
- android 视频播放器
- android视频播放器
- Android 视频播放器
- android 视频播放器
- android视频播放器
- [LeetCode]Search a 2D Matrix
- JavaScript的数组和Java的数组的差别
- 模型组合(Model Combining)之Boosting与Gradient Boosting
- TopicModelCode
- UI27_多长时间移除
- 【Android】 从头搭建视频播放器(1)——概述
- SDI的部署及简单使用
- 反素数(题目Divisors)
- 线性判别分析(LDA), 主成分分析(PCA)
- MapReduce基础
- FOJ 题目1894 志愿者选拔 (单调队列)
- 强大的矩阵奇异值分解(SVD)及其应用
- 小记
- STL系列之七 快速计算x的n次幂 power()的实现