转载请注明地址: http://blog.csdn.net/shenxiaolei507
本文是在 android视频播放(二) 利用android原生的MediaPlayer+SurfaceView的基础上,进行的一些功能上再次提高和一些需求的实现,如果对android利用MediaPlayer+SurfaceView播放视频不熟悉,可以看下这篇文章。
众所周知,我们在开发视频播放的时候,往往不光是简单的对视频进行播放管理,还会有一些其它的需求和高级功能,比如切换到屏幕时我们要保持视频,屏幕旋转,视频大小模式,视频截图等等,好了,下面我们就开始讲解一部分功能的实现。
一、屏幕旋转处理
在视频播放的时候,我们一般都会采用横屏的形式来播放视频,但是手机在用户没有关闭自动旋转的情况下,屏幕会在横竖屏之间切换,用户体验会非常不好,所以我们要对这种情况做处理。我们只需要在AndroidManifest的文件中,对播放视频的Activity加上这样的配置,我们就会一直处于横屏状态。
<span style="font-size:14px;">android:screenOrientation="userLandscape"
android:configChanges="keyboardHidden|orientation|screenSize"</span>
对上述参数介绍:
android:screenOrientation 为设置屏幕方向参数,userLandescape为横屏,其中参数有很多,这里不是咱们介绍重点,如果有兴趣大家可以自己摸索。这里推荐一篇文章 http://www.cnblogs.com/bluestorm/archive/2012/05/07/2486998.html
android:configChanges 为配置变化参数。其中keyboardHidden为键盘的可用性发生改变,orientation是屏幕的方向发生改变,screeSize为屏幕的大小发生改变,设置本参数的目的就是为了防止屏幕旋转时重新调用Activity的各个生命周期,关于android:configChanges我们要注意以下几点:
(1)不设置android:configChanges时屏幕旋转会重新调用activity的各个生命周期,切横屏时会调用一次,切竖屏时调用两次。
(2)只设置android:configChanges="orientation"时屏幕旋转会重新调用activity的各个生命周期,但是横竖屏时都只会调用一次。
(3)设置android:configChanges="orientation|keyboardHidden"时屏幕旋转不会调用activity各个生命周期,只会调用Activity的onConfigurationChanged方法,但在Android API13以后,因为屏幕的大小screeSize会随着屏幕的切换而改变,Android默认会再调用Activity的各个生命周期,所以我们也加上screeSize的属性,最后为了兼容API上下版本我们设置android:configChanges="keyboardHidden|orientation|screenSize"
二、视频界面大小模式处理
我们都知道视频播放的时候,有的用户习惯在全屏模式下观看,也有的用户习惯在窗口模式下观看,所以这样的需求在视频播放方面也比较多,下面我们就来看下具体的处理方法,
/**
* 改变视频的显示大小,全屏,窗口,内容
*/
public void changeVideoSize() {
// 改变视频大小
String videoSizeString = videoSizeButton.getText().toString();
// 获取视频的宽度和高度
int width = mediaPlayer.getVideoWidth();
int height = mediaPlayer.getVideoHeight();
// 如果按钮文字为窗口则设置为窗口模式
if ("窗口".equals(videoSizeString)) {
/*
* 如果为全屏模式则改为适应内容的,前提是视频宽高小于屏幕宽高,如果大于宽高 我们要做缩放
* 如果视频的宽高度有一方不满足我们就要进行缩放. 如果视频的大小都满足就直接设置并居中显示。
*/
if (width > screenWidth || height > screenHeight) {
// 计算出宽高的倍数
float vWidth = (float) width / (float) screenWidth;
float vHeight = (float) height / (float) screenHeight;
// 获取最大的倍数值,按大数值进行缩放
float max = Math.max(vWidth, vHeight);
// 计算出缩放大小,取接近的正值
width = (int) Math.ceil((float) width / max);
height = (int) Math.ceil((float) height / max);
}
// 设置SurfaceView的大小并居中显示
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width,
height);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
surfaceView.setLayoutParams(layoutParams);
videoSizeButton.setText("全屏");
} else if ("全屏".equals(videoSizeString)) {
// 设置全屏
// 设置SurfaceView的大小并居中显示
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(screenWidth,
screenHeight);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
surfaceView.setLayoutParams(layoutParams);
videoSizeButton.setText("窗口");
}
}
关于上述方法我主要讲解以下几点:
(1)通过MediaPlayer的getVideoWidth,getVideoHeight方法,可以获取到视频的原始大小。
(2)在视频的原始大小超出手机屏幕的范围的情况下,我们要对视频的大小进行等比例缩放,通过计算宽高的倍数,根据最大的倍数,算出缩放以后的宽高。
(3)设置视频的宽高大小是通过为SurfaceView设置LayoutParams来实现的,关于使用那个类下面的LayoutParams,主要参考SurfaceView的父布局。而surfaceHolder.setFixedSize(?, ?);是设置分辨率,视频窗口的大小是由surfaceView的大小决定的。
三、视频截图
关于视频截图的需求,在很多视频播放器上,都有涉及。而且很多开源的视频播放框架,也会提供这样的功能。关于网络视频截图的实现有一定的技术难度,本文暂时不介绍,现在来介绍一种本地视频截图的方法实现,采用的是android系统提供的API,下面来看代码
/**
* 保存视频截图.该方法只能支持本地视频文件
*
* @param time视频当前位置
*/
public void savaScreenShot(long time) {
// 标记是否保存成功
boolean isSave = false;
// 获取文件路径
String path = null;
// 文件名称
String fileName = null;
if (time >= 0) {
try {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(pathString);
// 获取视频的播放总时长单位为毫秒
String timeString = mediaMetadataRetriever
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
// 转换格式为微秒
long timelong = Long.parseLong(timeString) * 1000;
// 计算当前视频截取的位置
long index = (time * timelong) / mediaPlayer.getDuration();
// 获取当前视频指定位置的截图,时间参数的单位是微秒,做了*1000处理
// 第二个参数为指定位置,意思接近的位置截图
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(time * 1000,
MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
// 释放资源
mediaMetadataRetriever.release();
// 判断外部设备SD卡是否存在
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// 存在获取外部文件路径
path = Environment.getExternalStorageDirectory().getPath();
} else {
// 不存在获取内部存储
path = Environment.getDataDirectory().getPath();
}
// 设置文件名称 ,以事件毫秒为名称
fileName = Calendar.getInstance().getTimeInMillis() + ".jpg";
// 设置保存文件
File file = new File(path + "/shen/" + fileName);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
bitmap.compress(CompressFormat.JPEG, 100, fileOutputStream);
isSave = true;
} catch (Exception e) {
e.printStackTrace();
}
// 保存成功以后,展示图片
if (isSave) {
ImageView imageView = new ImageView(this);
imageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
imageView.setImageBitmap(BitmapFactory.decodeFile(path + "/shen/" + fileName));
new AlertDialog.Builder(this).setView(imageView).show();
}
}
}
关于视频的实现,主要通过以下代码实现的
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(pathString);
// 获取当前视频指定位置的截图,时间参数的单位是微秒,做了*1000处理
// 第二个参数为指定位置,意思接近的位置截图
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(time * 1000,
MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
(1)通过setDataSource();设置播放视频的路径,该路径为本地路径。
(2)通过getFrameAtTime();方法获取本地截图返回Bitmap对象。该方法有两个参数第一个参数为指定的时间的位置,该时间位置的单位为微妙,比毫秒还小一级,1毫秒等于1000微秒。通过MediaPlayer.getCurrentPosition()方法获取的播放时间位置是毫秒,所以要乘以1000。第二个参数为与该位置的位置,该参数有很多方式,都是MediaMetadataRetriever的常量数值,其中有OPTION_PREVIOUS_SYNC表示返回指定位置稍早的截图,OPTION_NEXT_SYNC表示返回指定位置稍晚的截图,
OPTION_CLOSEST_SYNC表示返回接近指定位置的截图,
OPTION_CLOSEST表示返回比指定位置早更多的截图,
OPTION_CLOSEST表示返回比指定位置晚更多的截图,而其中的OPTION_CLOSEST_SYNC为其中的默认参数设置。
四、关于一些MediaPlayer的一些接口的解释
/**
* 视频播放完成以后调用的回调函数
*/
@Override
public void onCompletion(MediaPlayer mp) {
}
该方法为视频播放视频播放完毕监听。具体功能实现看具体需求。
/**
* 视频播放发生错误时调用的回调函数
*/
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
switch (what){
case MediaPlayer.MEDIA_ERROR_UNKNOWN:
Log.e("text","发生未知错误");
break;
case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
Log.e("text","媒体服务器死机");
break;
default:
Log.e("text","onError+"+what);
break;
}
switch (extra){
case MediaPlayer.MEDIA_ERROR_IO:
//io读写错误
Log.e("text","文件或网络相关的IO操作错误");
break;
case MediaPlayer.MEDIA_ERROR_MALFORMED:
//文件格式不支持
Log.e("text","比特流编码标准或文件不符合相关规范");
break;
case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
//一些操作需要太长时间来完成,通常超过3 - 5秒。
Log.e("text","操作超时");
break;
case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
//比特流编码标准或文件符合相关规范,但媒体框架不支持该功能
Log.e("text","比特流编码标准或文件符合相关规范,但媒体框架不支持该功能");
break;
default:
Log.e("text","onError+"+extra);
break;
}
//如果未指定回调函数, 或回调函数返回假,VideoView 会通知用户发生了错误。
return false;
}
该方法的为播放错误监听方法,具体的错误信息,在代码中已经很有详细的注释,这里不在进行解释。
/**
* 视频缓存大小监听,当视频播放以后 在started状态会调用
*/
public void onBufferingUpdate(MediaPlayer mp, int percent) {
// TODO Auto-generated method stub
// percent 表示缓存加载进度,0为没开始,100表示加载完成,在加载完成以后也会一直调用该方法
Log.e("text", "onBufferingUpdate-->" + percent);
}
该方法为视频缓存大小监听大小方法,该方法是在视频处于Started(已经播放)状态后开始起作用。参数percent 表示缓存加载进度,0为没开始,100表示加载完成。在加载完成以后也会一直调用该方法,所以在处理的时候也要特别注意。
好了关于Android利用原生MediaPlayer+SurfaceView播放视频的提高和更多功能实现,就讲解到这里。欢迎大家多多指正!!!。
源码地址:Android利用MediaPlayer+SurfaceView播放网络视频