一直在做安卓的项目,想着找个时间总结一下,可能太懒了,一直没总结。
代码参考了许多Blog和Github,修修补补改改挺多地方,记录一下,侵删私信或注明出处。
播放
主要的类
三个Interface:
- IMyPlayer:定义了
设置资源
、播放
、暂停
、停止
、获取播放时长
、当前位置
、定位到
、以及Activity状态
等函数方法。 - IMyDisplay:定义了获取播放视图(View),播放的SurfaveHolder的方法。
- IMyPlayerListener:封装了播放,停止,恢复等方法。
- MyPlayerException:封装了异常。
- MySimpleDisplay:简单实现了IMyDisplay。
- MediaPlayerHelper:实现了IMyPlayer,并实现MediaPlayer相对应的一些方法。
播放流程
(1)初始化辅助类
(2)设置播放的View
(3)设置播放资源
(4)播放、暂停、停止
代码实现
IMyPlayer
public interface IMyPlayer {
/**
* 设置资源
*
* @param url 资源路径
* @throws MyPlayerException
*/
void setSource(String url) throws MyPlayerException;
/**
* 设置显示视频的载体
*
* @param display 视频播放的载体及相关界面
*/
void setDisplay(IMyDisplay display);
/**
* 播放视频
*
* @throws MyPlayerException
*/
void play() throws MyPlayerException;
/**
* 暂停视频
*/
void pause();
/**
* 停止播放
*/
void stop();
/**
* 获取资源的总时长
*
* @return 时长毫秒数
*/
int getSourceDuration() throws MyPlayerException;
/**
* 获取当前播放的位置
*
* @return 毫秒数
*/
int getCurrentPosition() throws MyPlayerException;
/**
* <p>移动播放位置</p>
* <p>若当前是播放状态,则<font color='red'>暂停</font>播放</p>
*
* @param time 移动到的位置,毫秒数表示
*/
void seekTo(long time) throws MyPlayerException;
/**
* 用于Activity的onPause状态
*/
void onPause();
/**
* 用于Activity的onResume状态
*/
void onResume();
/**
* 用于Activity的onDestroy状态
*/
void onDestroy();
}
IMyDisplay
public interface IMyDisplay extends IMyPlayerListener {
/**
* @return 获取播放载体的View, SurfaceView或TextureView或SurfaceTexture
*/
View getDisplayView();
/**
* @return 获取SurfaceHolder
*/
SurfaceHolder getHolder();
}
IMyPlayerListener
public interface IMyPlayerListener {
void onStart(IMyPlayer player);
void onPause(IMyPlayer player);
void onResume(IMyPlayer player);
void onComplete(IMyPlayer player);
}
MySimpleDisplay(简单实现接口)
public class MySimpleDisplay implements IMyDisplay {
private SurfaceView surfaceView;
public MySimpleDisplay(SurfaceView surfaceView) {
this.surfaceView = surfaceView;
}
@Override
public View getDisplayView() {
return surfaceView;
}
@Override
public SurfaceHolder getHolder() {
return surfaceView.getHolder();
}
@Override
public void onStart(IMyPlayer player) {
LogUtil.getInstance().d("MySimpleDisplay", "播放开始");
}
@Override
public void onPause(IMyPlayer player) {
LogUtil.getInstance().d("MySimpleDisplay", "播放暂停");
}
@Override
public void onResume(IMyPlayer player) {
LogUtil.getInstance().d("MySimpleDisplay", "播放恢复");
}
@Override
public void onComplete(IMyPlayer player) {
LogUtil.getInstance().d("MySimpleDisplay", "播放完毕");
}
}
MediaPlayerHelper
重点类,基于MediaPlayer。
(1)初始化MediaPlayer
/**
* 创建MediaPlayer
*/
private void createPlayer() {
if (null == mediaPlayer) {
mediaPlayer = new MediaPlayer();
mediaPlayer.setScreenOnWhilePlaying(true);
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnVideoSizeChangedListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnSeekCompleteListener(this);
mediaPlayer.setOnErrorListener(this);
}
}
(2)播放相关API
/**
* 开始播放
*/
private void playStart() {
if (isVideoSizeMeasured && isMediaPrepared && isSurfaceCreated && isUserWantToPlay && isResumed) {
mediaPlayer.setDisplay(display.getHolder());
mediaPlayer.start();
isCompleted = false;
log("视频开始播放");
display.onStart(this);
if (myPlayerListener != null) {
myPlayerListener.onStart(this);
}
}
}
/**
* 暂停播放
*/
private void playPause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
display.onPause(this);
if (myPlayerListener != null) {
myPlayerListener.onPause(this);
}
}
}
/**
* 停止播放,并释放资源
*/
private void playStop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
display.onComplete(this);
if (myPlayerListener != null) {
myPlayerListener.onComplete(this);
}
}
}
/**
* 播放前检查资源状态
*
* @return 播放资源是否准备好
*/
private boolean checkSource() {
return videoSource != null && videoSource.length() != 0;
}
/**
* 视频状态
*
* @return 视频是否正在播放
*/
public boolean isPlaying() {
return mediaPlayer != null && mediaPlayer.isPlaying();
}
/**
* 判断是否播放完毕
*/
public boolean isCompleted() {
return isCompleted;
}
Activity状态绑定
@Override
public void onPause() {
isResumed = false;
playPause();
}
@Override
public void onResume() {
isResumed = true;
playStart();
}
@Override
public void onDestroy() {
if (mediaPlayer != null) {
mediaPlayer.release();
}
}
视频播放窗口大小相关
/**
* 设置是否裁剪视频,若裁剪,则视频按照DisplayView的父布局大小显示。
* 若不裁剪,视频居中于DisplayView的父布局显示
*
* @param isCrop 是否裁剪视频
*/
public void setCrop(boolean isCrop) {
this.mIsCrop = isCrop;
if (display != null && currentVideoWidth > 0 && currentVideoHeight > 0) {
tryResetSurfaceSize(display.getDisplayView(), currentVideoWidth, currentVideoHeight);
}
}
/**
* 是否被裁剪
*/
public boolean isCrop() {
return mIsCrop;
}
/**
* 根据设置和视频尺寸,调整视频播放区域的大小
*
* @param view 播放控件
* @param videoWidth 视频宽度
* @param videoHeight 视频高度
*/
private void tryResetSurfaceSize(final View view, int videoWidth, int videoHeight) {
ViewGroup parent = (ViewGroup) view.getParent();
int width = parent.getWidth();
int height = parent.getHeight();
if (width > 0 && height > 0) {
final RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
if (mIsCrop) {
float scaleVideo = videoWidth / (float) videoHeight;
float scaleSurface = width / (float) height;
if (scaleVideo < scaleSurface) {
params.width = width;
params.height = (int) (width / scaleVideo);
params.setMargins(0, (height - params.height) / 2, 0, (height - params.height) / 2);
} else {
params.height = height;
params.width = (int) (height * scaleVideo);
params.setMargins((width - params.width) / 2, 0, (width - params.width) / 2, 0);
}
} else {
if (videoWidth > width || videoHeight > height) {
float scaleVideo = videoWidth / (float) videoHeight;
float scaleSurface = width / (float) height;
if (scaleVideo > scaleSurface) {
params.width = width;
params.height = (int) (width / scaleVideo);
params.setMargins(0, (height - params.height) / 2, 0, (height - params.height) / 2);
} else {
params.height = height;
params.width = (int) (height * scaleVideo);
params.setMargins((width - params.width) / 2, 0, (width - params.width) / 2, 0);
}
}
}
view.setLayoutParams(params);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// 通知大小改变
surfaceSizeChangeListener.sizeChange();
}
});
}
}
重点是tryResetSurfaceSize方法的实现,根据视频的大小和View的大小,判断纵横比
(1)View更长,则将视频横充满,高度居中(设置Margin)。
(2)View更宽,则将视频高充满,宽度居中(设置Margin)。
设置的Margin,如横充满View,则用横乘上视频的纵横比(videoHeight/videoWidth),View的高度减去该值,偏移除以2就可以,若View不是在顶部,则用Y坐标+这个值即可。
实现的MediaPlayer的接口
MediaPlayer.OnCompletionListener
@Override
public void onCompletion(MediaPlayer mp) {
display.onComplete(this);
if (myPlayerListener != null) {
myPlayerListener.onComplete(this);
}
isCompleted = true;
}
MediaPlayer.OnVideoSizeChangedListener
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
log("视频大小被改变-> [" + width + "," + height + "]");
if (width > 0 && height > 0) {
this.currentVideoWidth = width;
this.currentVideoHeight = height;
tryResetSurfaceSize(display.getDisplayView(), width, height);
isVideoSizeMeasured = true;
playStart();
}
}
MediaPlayer.OnPreparedListener
@Override
public void onPrepared(MediaPlayer mp) {
log("视频准备完成");
isMediaPrepared = true;
playStart();
}
MediaPlayer.OnSeekCompleteListener
@Override
public void onSeekComplete(MediaPlayer mp) {
log("定位完成");
}
MediaPlayer.OnErrorListener
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
log("OnError - Error code: " + what + " Extra code: " + extra);
switch (what) {
case -1004:
log("MEDIA_ERROR_IO");
break;
case -1007:
log("MEDIA_ERROR_MALFORMED");
break;
case 200:
log("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK");
break;
case 100:
log("MEDIA_ERROR_SERVER_DIED");
break;
case -110:
log("MEDIA_ERROR_TIMED_OUT");
break;
case 1:
log("MEDIA_ERROR_UNKNOWN");
break;
case -1010:
log("MEDIA_ERROR_UNSUPPORTED");
break;
}
switch (extra) {
case 800:
log("MEDIA_INFO_BAD_INTERLEAVING");
break;
case 702:
log("MEDIA_INFO_BUFFERING_END");
break;
case 701:
log("MEDIA_INFO_METADATA_UPDATE");
break;
case 802:
log("MEDIA_INFO_METADATA_UPDATE");
break;
case 801:
log("MEDIA_INFO_NOT_SEEKABLE");
break;
case 1:
log("MEDIA_INFO_UNKNOWN");
break;
case 3:
log("MEDIA_INFO_VIDEO_RENDERING_START");
break;
case 700:
log("MEDIA_INFO_VIDEO_TRACK_LAGGING");
break;
}
return false;
}
根据Stack Overflow上的Question以及CSDN博客,补充的错误代码。
注:如果播放不了且代码-1,请尝试降低播放视频的分辨率。
SurfaceHolder.Callback
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (display != null && holder == display.getHolder()) {
isSurfaceCreated = true;
//此举保证以下操作下,不会黑屏。(或许还是会有手机黑屏)
//暂停,然后切入后台,再切到前台,保持暂停状态
if (mediaPlayer != null) {
mediaPlayer.setDisplay(holder);
//不加此句360f4不会黑屏、小米note1会黑屏,其他机型未测
mediaPlayer.seekTo(mediaPlayer.getCurrentPosition());
}
log("surface被创建");
playStart();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
log("surface大小改变:" + "[" + width + "," + height + "]");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (display != null && holder == display.getHolder()) {
log("surface被销毁");
isSurfaceCreated = false;
}
}
总结
(1)视频播放不变形,通过视频纵横比与View纵横比进行对应的改变。
(2)Activity的状态绑定,防止后台前台切换,程序出错。
(3)视频Seek只能定位关键帧,但MediaCoder可以设置I帧间隔为0,每一帧都是关键帧(视频较大),gitHub有修改的Seek库,利用FFmpeg实现精准定位。
(4)播放还可以用VideoView,但封装太死,不灵活。
(5)播放还可以用MediaCodec进行硬解码,然后YUV转RGB(慢)进行播放。
(6)视频播放也可以用TextureView或TextureSurface,但SurfaceView具有双缓冲,可能速度比较快。