Android VideoView播放网络视频简介(转)

摘要:
我们不是说过MediaMetadataRetriever吗?我们可以基于所获得的第一帧的大小来确定视频的大小。MediaMetadataRetriever还可以使用intvideoWidth=retrier.METADATA_KEY_VIDEO_WIDTH;intvideoHeight=检索器METADATA_KEY_视频_高度;确定视频的大小。因此,我们可以动态设置VideoView的大小。呃,我们怎么能用这么大的控件显示这么小的视频?小视频适应大控件的原理远远不止于此。我也是百度。普通LayoutParams只能调整控件的大小。当视频小于控件时,视频只能显示较大的默认大小,但如何调整?

最近项目中用到了很多视频播放的地方,不管是聊天发送的视频消息,还是类似内涵段子的视频列表,都会涉及这些知识,不过网上的知识都很零散,一会找缓存方法,一会找预览图片的方法,一会找视频动态修改尺寸的方法,总之找的人好烦,所以自己写一篇来记录这些知识点,也方便别人查阅

获取视频首帧当预览图(MediaMetadataRetriever)
在VideoView中,如果直接设置播放路径,然后seekTo(1)当然也能产生预览效果,但是,如果VideoView较多,设置播放路径的方法会产生几个问题,设置路径后VideoView会取网上拉取视频(缓冲池大小),这样造成流量浪费,而且,多个VideoView会造成显示首帧非常非常慢,且有严重的卡顿

那如何解决这个问题,我的想法是,还是用首帧当预览图,不过我是在ImageView里面显示预览图,所以预览的时候不用VideoView了,获取预览图也是变的简单化,省流量,还快捷,下来我们了解下MediaMetadataRetriever类如何获取视频的首帧。MediaMetadataRetriever类不但可以获取视频首帧,还可以获取标题,时长,作者等信息,大家根据需要可以获取,我在这里就不一一举例,在获取到首帧后,我们做下缓存处理,以便下一次预览不用每次从网上拉取,然后用Glide加载显示

ThreadPoolUtils.execute(new Runnable() {
@Override
public void run() {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bitmap = null;
try {
//这里要用FileProvider获取的Uri
if (url.contains("http")) {
retriever.setDataSource(url, new HashMap<String, String>());
} else {
retriever.setDataSource(url);
}
bitmap = retriever.getFrameAtTime();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
showImageMessage(bitmap, positionTag, vv);
}
});
后记:其实还有一种办法来做预览显示,就是让后台将预览图处理好,然后拿到图片地址直接用Glide显示,都不用自己缓存,而且后台可以生成GIF,也可以用Glide显示,且显得高大上

预览图加载完毕后,点击预览图,然后我们可以做各种处理,如隐藏ImageView且显示VideoView,或者跳到视频播放界面等,各种加载逻辑大家可以发挥自己得想象

VideoView加载一个网络视频
VideoView加载视频其实很简单,我们直接看代码吧

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<VideoView
android:id="@+id/mVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

/**
* 香港卫视:http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8
* CCTV1高清:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
* CCTV3高清:http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
* CCTV5高清:http://ivi.bupt.edu.cn/hls/cctv5hd.m3u8
* CCTV5+高清:http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8
* CCTV6高清:http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
* 苹果提供的测试源(点播):http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8
*/
private void initView() {
String url="http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8";
VideoView videoView=findViewById(R.id.mVideoView);
videoView.setVideoPath(url);
videoView.requestFocus();
videoView.start();
}
这样一个网络视频就可以播放了

视频控件长宽的大小调整
视频是播放出来了,怎么看都有点不和谐,大白边框太丑了,那缩小吧,一不小心缩变形了,看起来更别扭,怎么才能按照视频的比例来显示呢?我们上面不是讲过MediaMetadataRetriever吗?我们可以根据获取的首帧图片的大小确定视频的大小,MediaMetadataRetriever还可以采用

int videoWidth=retriever.METADATA_KEY_VIDEO_WIDTH;
int videoHeight=retriever.METADATA_KEY_VIDEO_HEIGHT;
来确定视频的大小。从而动态设置VideoView的大小,咦,设置那么大的控件,怎么才显示那么小的视频?哈哈,问题来了,小视频怎么动态适配控件大小?

小视频适配大控件(动态调整视频显示的大小)
不说原理了,我也是百度的,普通的LayoutParams只能调整控件的大小,当视频比控件小时,视频只能显示大默认大小,可是怎么来调整呢?请看代码↓

自定义VideoView控件CustomVideoView.java

/**
* @author Created by MrRight on 2017/10/24.
*/
public class CustomVideoView extends VideoView{
private Context mContext;
final int defaultHeight=200; //单位DP

public CustomVideoView(Context context) {
super(context);
mContext=context;
}

public CustomVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
}

public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext=context;
}

//widthMeasureSpec 和 heightMeasureSpec的值 由父容器决定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super .onMeasure(widthMeasureSpec,heightMeasureSpec);
// 默认高度,为了自动获取到focus
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width;
// 这个之前是默认的拉伸图像
if (this.width > 0 && this.height > 0) {
width = this.width;
height = this.height;
}
setMeasuredDimension(width, height);
}


private int width,height;

public void setMeasure(int width, int height) {
this.width = width;
this.height = height;
}
}
怎么用呢!!很简单,继续看代码↓

videoViewParent.post(new Runnable() {
@Override
public void run() {
int[] widthAndHeight=getWidthAndHeight(holder.videoViewParent,dynamicsBean.getWeight(),dynamicsBean.getHeight());
videoView.getHolder().setFixedSize(widthAndHeight[0], widthAndHeight[1]);
// 重绘VideoView大小,这个方法是在重写VideoView时对外抛出方法
videoView.setMeasure(widthAndHeight[0], widthAndHeight[1]);
// 请求调整
videoView.requestLayout();
}
});
就这样,视频可以按你的需求行进动态调整了!!

VideoView的常用监听和作用
VideoView有好多监听,真的是好多,许多监听是重复的,至于怎么重复的?为什么重复?有兴趣的自己去看看!首先看第一个非常重要的一个监听:点击事件和双击事件的监听,你们有没有试过设置OnClick事件?是不是没有什么用啊?没用就对了,点击事件的正确姿势是↓↓↓

/*
* 对VideoView setOnClickListener时,发现无效,搜索一番后找到解决方案;
* 同时监听VideoView的点击双击和滑动事件,通过对VideoView的OnTouchListener设置进行监听,
* 首先实例化一个手势识别器,并返回它的onTouchEvent。
* 然后初始化GestureDetector ,这里面有一个坑,如果单纯的设置OnGestureListener,发现当onDown的返回值为true的
* 时候可以响应单击长摁和滑动事件,为false的时候只会响应长摁事件;如果想要监听双击事件,就要对GestureDetector设
* 置OnDoubleTapListener,需要注意的的是,在OnGestureListener的onDown返回值为false的时候OnDoubleTapListener
* 里面所有的回调是不会去响应的
*/
holder.videoView.setOnTouchListener(new View.OnTouchListener() {
GestureDetector mGesture;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mGesture == null) {
mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
//返回false的话只能响应长摁事件
return true;
}

@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
mGesture.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
controlLayoutShowAndHiden(holder.controlLayout,holder.cancheImage);
return true;
}

@Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
});
}

return mGesture.onTouchEvent(event);
}
});

OK!点击事件看完之后,我们看下剩下的其他的监听方法,剩下的比较简单,光看名字就知道是干什么用的,我们只写下方法和作用,不再赘述

videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
if(what==MediaPlayer.MEDIA_ERROR_UNKNOWN //未指定的媒体播放器错误。
||what==MediaPlayer.MEDIA_ERROR_SERVER_DIED //媒体服务器死了。在这种情况下,应用程序必须释放
||what==MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK//视频流,其容器对逐行扫描无效。
||what==MediaPlayer.MEDIA_ERROR_MALFORMED//文件或网络操作错误
||what==MediaPlayer.MEDIA_ERROR_UNSUPPORTED//比特流符合相关的编码标准或文件规范,但 媒体框架不支持该功能。
||what==MediaPlayer.MEDIA_ERROR_TIMED_OUT//超时
||what==MediaPlayer.MEDIA_ERROR_IO){ //IO刘错误
if(controlImageBig.getVisibility()==View.VISIBLE){
controlImageBig.setBackgroundResource(R.drawable.vodeo_retry);
}
}
return true;//如果设置true就可以防止他弹出错误的提示框!
}
});


videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what==MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START){
controlImageBig.setVisibility(View.GONE);
cancheImage.setVisibility(View.GONE);
controlImageBig.setBackgroundResource(R.drawable.eventdynamics_play_big);
}
LogUtils.i(TAG," extra is "+extra
+" what is "+what);
return false;
}
});


videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG,"onPrepared methmod is called and position is "+position);
int duration=holder.videoView.getDuration();
totleTime.setText(intTimeToString(duration));
seekBar.setMax(duration);
videoViewParent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
controlLayoutShowAndHiden(holder.controlLayout,holder.cancheImage);
}
});
}
});


videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
threadPoolUtils.shutDownNow();
if(cancheImage.getVisibility()==View.GONE){
cancheImage.setVisibility(View.VISIBLE);
}
if(controlImageBig.getVisibility()==View.GONE){
controlImageBig.setVisibility(View.VISIBLE);
}
playControl.setImageResource(R.drawable.eventdynamics_play);
seekBar.setProgress(0);
}
});

好了,大概就这么多,后续有新东西还会持续更新,大家有什么好的建议也可以留言交流
---------------------
作者:baoolong
来源:CSDN
原文:https://blog.csdn.net/baoolong/article/details/79607393
版权声明:本文为博主原创文章,转载请附上博文链接!

免责声明:文章转载自《Android VideoView播放网络视频简介(转)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇图片大小以及dp和px关系一览表,logo尺寸SpringBoot整合kafka(实现producer和consumer)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

企业微信如何实现会话消息本地存档

最近企业微信已经开放了会话存档的接口,可以针对一些需要进行数据留痕的行业将企业微信所有聊天记录拉取到本地服务器上,进行消息的备份存档。测试调用成功后,先记录下过程。如果有客户需要,也可以咨询奥创软件研究院交流探讨。 首先在“管理工具”下打开“会话内容存档”接口   选择试用开通,目前可以试用30天,试用期结束后需要付费使用。 开通后需要填写几个参数用于接口...

ROS知识(5)----消息与服务的示例

ROS中已经定义了较多的标准类型的消息,你可以用在这些标准类型的消息上再自定义自己的消息类型。这个在复杂数据传输很有用,例如节点和服务器进行交互时,就可能用到传输多个参数到服务器,并返回相应的结果。为了保证例子的完整,将详述每一步。 基本思路和创建talker和listener的例子类似,步骤如下: 建立工作空间workspace(类似于vs下的解决方案,...

流媒体协议(二):RTMP协议

一、概念与摘要 RTMP协议从属于应用层,被设计用来在适合的传输协议(如TCP)上复用和打包多媒体传输流(如音频、视频和互动内容)。RTMP提供了一套全双工的可靠的多路复用消息服务,类似于TCP协议[RFC0793],用来在一对结点之间并行传输带时间戳的音频流,视频流,数据流。通常情况下,不同类型的消息会被分配不同的优先级,当网络传输能力受限时,优先级用来...

Android中的Handler机制

在android主线程中做太耗时的操作会引起ANR崩溃,为了进行线程间通信,就需要用到handler消息处理机制。消息传递分为两类,一种是从MainThread向WorkerThread传递消息,而另外一种是从WorkerThread向MainThread传递消息。由于主线程主要负责UI相关的事件,如用户的点击事件,屏幕触摸事件等,当捕捉到用户动作后将会分...

在MDK中使用 printf 函数

microlib 提供了一个有限的 stdio 子系统,它仅支持未缓冲的 stdin、stdout 和 stderr。 这样,即可使用 printf() 来显示应用程序中的诊断消息。 要使用高级 I/O 函数,您必须提供自己实现的以下基本函数,以便与您自己的 I/O 设备配合使用。 fputc()  为所有输出函数实现此基本函数。 例如,fprintf(...

Windows程序运行原理 之 WNDCLASS详解

#include <windows.h>#include <stdio.h>//包含应用程序中数据类型和数据结构的定义LRESULT CALLBACK WinSunProc(  HWND hwnd,      // handle to window  UINT uMsg,      // message identifier  WP...