前言
VideoView是Android主要的视频播放View,它其实是对MediaPlayer的再次封装.如果你已经了解过MediaPlayer在使用VideoView是十分简单的.如果你想先了解MediaPlayer可以参考我的博客:https://www.cnblogs.com/guanxinjing/p/11019662.html
在没有复杂的要求下使用VideoView播放视频是十分快速且方便的选择.并且不需要苦恼视频尺寸的计算(说到视频尺寸计算,个人瞎折腾出了一个计算方法.虽然也可以将视频适配的很完美,但是总的还是没有VideoView内置的视频尺寸适配厉害,有兴趣的可以阅读一下源代码里VideoView的视频适配,这个才是精华部分...)
实现流程
- 获取权限
- 保持屏幕常亮
- xml布局里添加VideoView
- 初始化配置VideoView
- 播放视频
- 暂停视频
- 获取播放时间信息
- 停止视频
获取权限
这个是播放本地视频的demo,所以只添加了需要SD卡权限.另外VideoView是支持网络视频播放的如果你需要播放网络视频则还需要添加网络权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
保持屏幕常亮
音视频开发的基本操作,在xml的根布局上添加下面这个属性
android:keepScreenOn="true"
xml布局里添加VideoView
<VideoView android:id="@+id/video_view" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/>
基本操作,但是这里有一个你可能会忽视的VideoView的高宽配置.了解这个你可以看出VideoView自带的视频尺寸适配还是十分完美的.
1.第一种当你android:layout_width="wrap_content" 和 android:layout_height="wrap_content" 都设置为wrap_content时,这个时候View适配视频是一个在当前设备屏幕下最合适的一个比例. 虽然比例是最合适的但是View宽和高并不会一定铺满屏幕.这种方式适合在一些悬浮的小窗口播放时使用.
2.第二种就是满足屏幕的一边,满足屏幕的一边的意思是让VideoView始终铺满设备屏幕的宽或者高, 这里有一个原则就是始终满足最小的那个一个边.
我们在竖屏的情况下首先要满足宽,所以属性应该配置成 android:layout_width="match_parent" android:layout_height="wrap_content"
反之如果我们在横屏的情况就要满足高度.所以属性应该配置成 android:layout_width="wrap_content" android:layout_height="match_parent"
这样我们的视频才不会变形拉伸.重点!这种方式是我们最适用在视频播放时的使用.(如果你是动态切换横竖屏,我们也可以在代码里重新设置VideoView的layout_width和layout_height)
3.第三种 layout_width和layout_height属性都是match_parent,这个将无法避免视频的变形拉伸,不建议这样配置.
4.第四种 layout_width和layout_height属性都自己设置固定大小的值.这个也将无法避免视频的变形拉伸.如果,你还是一定需要用小窗口播放,这里可以给出一个思路:
就是在VideoView外面在套一个小窗口的黑色背景View,而你只要遵守第二条情况满足这小窗口的黑色背景View的最短的一边.就可以完美的适配视频了.按自动比例缩小视频,其他地方会变成电影一样的黑边效果.布局参考如下:
<View android:id="@+id/video_bg" android:layout_width="100dp" android:layout_height="50dp" android:background="@color/fontBlack1" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/> <VideoView android:id="@+id/video_view2" android:layout_width="wrap_content" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="@id/video_bg" app:layout_constraintBottom_toBottomOf="@id/video_bg" app:layout_constraintLeft_toLeftOf="@id/video_bg" app:layout_constraintRight_toRightOf="@id/video_bg"/>
初始化配置VideoView
private void initVideoView(){ File file = new File(getExternalCacheDir(),"demo.mp4"); if (!file.exists()){ Toast.makeText(this, "视频不存在", Toast.LENGTH_SHORT).show(); finish(); return; } mVideoView.setVideoPath(file.getAbsolutePath());//设置视频文件 mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { //视频加载完成,准备好播放视频的回调 } }); mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { //视频播放完成后的回调 } }); mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { //异常回调 return false;//如果方法处理了错误,则为true;否则为false。返回false或根本没有OnErrorListener,将导致调用OnCompletionListener。 } }); mVideoView.setOnInfoListener(new MediaPlayer.OnInfoListener() { @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { //信息回调 // what 对应返回的值如下 // public static final int MEDIA_INFO_UNKNOWN = 1; 媒体信息未知 // public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; 媒体信息视频跟踪滞后 // public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; 媒体信息\视频渲染\开始 // public static final int MEDIA_INFO_BUFFERING_START = 701; 媒体信息缓冲启动 // public static final int MEDIA_INFO_BUFFERING_END = 702; 媒体信息缓冲结束 // public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; 媒体信息网络带宽(703) // public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; 媒体-信息-坏-交错 // public static final int MEDIA_INFO_NOT_SEEKABLE = 801; 媒体信息找不到 // public static final int MEDIA_INFO_METADATA_UPDATE = 802; 媒体信息元数据更新 // public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; 媒体信息不支持字幕 // public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; 媒体信息字幕超时 return false; //如果方法处理了信息,则为true;如果没有,则为false。返回false或根本没有OnInfoListener,将导致丢弃该信息。 } }); }
如果是Raw目录下的视频
val roastedDuck = Uri.parse("android.resource://你的应用包名/" + R.raw.big_buck_bunny)
mBinding.video.setVideoURI(roastedDuck)
播放视频
mVideoView.start();
注意,播放视频时一定要确定setOnPreparedListener返回了已经视频准备完成的回调.
暂停视频
mVideoView.pause();
获取播放时间信息
mVideoView.getDuration();//获取视频的总时长 mVideoView.getCurrentPosition();//获取视频的当前播放位置
这2个方法本质上还是调用MediaPlayer获取的,获取到时长后你可以配合Handler与SeekBar配合来实现自定义进度条功能.这里就不在啰嗦了.有兴趣可以参考我的一个简单的demo(虽然demo里是用MediaPlayer实现,但是原理一致):https://www.cnblogs.com/guanxinjing/p/11024195.html
停止释放
mVideoView.stopPlayback();//停止播放视频,并且释放 mVideoView.suspend();//在任何状态下释放媒体播放器
两个方法都有停止并且释放内存的作用,但是stopPlayback()看底层代码并没有重置,所以应该只是释放内存,但是没有释放配置资源(但是想不在配一次视频路径应该是需要调用mMediaPlayer.prepareAsync();方法,看不懂这个?可以参考我MediaPlayer入门的博客).
而suspend则更加彻底的释放了所有配置信息和内存.
其他Api介绍
- mVideoView.canPause(); //是否可以暂停
- mVideoView.canSeekBackward(); //视频是否可以向后调整播放位置
- mVideoView.canSeekForward(); //视频是否可以向前调整播放位置
- mVideoView.getBufferPercentage(); //获取视频缓冲百分比
- mVideoView.resolveAdjustedSize(); //获取自动解析后VideoView的大小
- mVideoView.resume(); //重新开始播放
- mVideoView.isPlaying(); //是否在播放中
- mVideoView.setMediaController(); //设置多媒体控制器
- mVideoView.onKeyDown(); //发送物理按键值
- mVideoView.setVideoURI(); //播放网络视频,参数为网络地址
end
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/11177739.html