前言
最近开发应用需要用到视频播放功能,在使用了原生的VideoView之后发现自己能力有限无法实现公司的需求。所以只能寄希望以第三方开源库,当在GitHub看到JiaoZiVideoPlayer高达9K+的Star之后果断的使用了这个,事实也如作者所说:“可以完全自定义UI和任何功能。”
先上GitHub地址:https://github.com/lipangit/JiaoZiVideoPlayer](https://github.com/lipangit/JiaoZiVideoPlayer
版本使用的是:7.0.4
参考文章
https://juejin.im/post/5cee364e5188251bc6234313
也可以加入GitHub里面作者留下的QQ群,里面有大佬会解答一些问题
正文
功能需求:
1.显示自定义标题和视频播放前的浏览页面
2.可以小屏/全屏播放
3.全屏状态下显示返回按钮、标题、电量、时间、进度条等基本功能
4.全屏状态上下滑动右边屏幕可以增减音量,滑动左边屏幕可以调整亮度,横向滑动可以调整播放进度
5.全屏状态需要有锁屏、截屏功能
实现
功能需求上的前4个JiaoZiVideoPlayer都已经自带了,此处主要讲解第5条需求的实现。
1.阅读该库的教程文档:https://juejin.im/post/5cee364e5188251bc6234313
2.在布局中引用控件,此处使用的是库中自带的控件,如果不需要实现第5条需求,直接引用即可实现功能。注意需要在控件的外围再套一个布局,为什么?(I don't know.)
3.直接在代码中获取控件然后填入视频的链接地址和标题即可缓存就是在退出页面回来后再次回来是否继续从退出的位置继续播放,true 是,false 否;显示默认图片就是在视频播放的之前显示的预览图片,作者推荐使用Glide显示,因此我也是使用Glide自己封装了工具类。
注意:"video"即是通过findViewById获取的控件;"video.thumbImageView"是库中自带的预览图片显示的ImageView控件。
通过前3步就可以使用基本的视频播放功能了,下面实现第5个需求的功能步骤。
4.在使用完成前3步之后视频可以正确播放了,则表示第三方库导入基本成功了,如果无法播放视频或者使用一直在加载中,请检查视频的Url是否正确且能在浏览器中直接播放,如果确定Url没有问题,可能是播放器内核不支持播放需要更换浏览器内核,第1步的文章当中有相信的更换步骤,请查看。
接下来开始实现锁屏和截屏功能。
要增加功能必然需要增加执行功能的按钮,要增加按钮就需要重写第三方库的布局,找出第三方库的布局"jz_layout_std.xml",直接在Activity Studio中双击Shift弹出的窗口中搜索就能找到该布局文件,找到之后复制到自己项目的layout下,然后增加两个按钮和一个ImageView。
注意:"jz_layout_std.xml"文件可以重命名,但是不能更改或者删除里面已经有的布局以及控件的ID。只能增加控件而不能删除或者修改原有的控件
5.在布局文件创建成功后创建一个类继承"JzvdStd"把两个构造方法都实现了,然后重写getLayoutId()方法,返回第4步的布局。
@Override
public int getLayoutId() {
// 自定义页面需要复制库里面的布局出来使用,只可增加自己的不可以删除或修改原有的
return R.layout.jz_layout_std;
}
6.重写init(Context context)方法,在方法中查找控件,并且添加按钮监听
@Override
public void init(Context context) {
super.init(context);
// 获取自定义添加的控件
ibLock = findViewById(R.id.ib_jz_layout_std_lock);
ibScreen = findViewById(R.id.ib_jz_layout_std_screen);
ivScreen = findViewById(R.id.iv_jz_layout_std_screen);
ibLock.setOnClickListener(this);
ibScreen.setOnClickListener(this);
}
7.重写onClick(View v)方法,并编写锁屏以及截屏的代码,changeUiToPlayingShow()方法是库中自带的方法,下面讲解
@Override
public void onClick(View v) {
// 控件的监听,获取的是父类的。
super.onClick(v);
switch (v.getId()) {
case R.id.ib_jz_layout_std_lock:
ivScreen.setVisibility(GONE);
if (locked) {
// 已经上锁,再次点击解锁
changeUiToPlayingShow();
ibLock.setImageResource(R.drawable.jz_click_lock_selector);
ToastUtils.showShort(getContext().getString(R.string.common_video_unlock));
} else {
// 上锁
changeUiToPlayingClear();
ibLock.setImageResource(R.drawable.jz_click_locked_selector);
ToastUtils.showShort(getContext().getString(R.string.common_video_locked));
}
locked = !locked;
break;
case R.id.ib_jz_layout_std_screen:
Bitmap bitmap = textureView.getBitmap();
if (bitmap == null) {
ToastUtils.showShort("图片获取失败");
return;
}
// 保存图片
boolean b = MyImageUtils.compressAndSaveImage(getContext(), bitmap, AppConstant.IMG_SELECTOR_DIR_PATH_OF_COMPRESS, 100);
MyImageUtils.showImageForBitmap(getContext(), bitmap, ivScreen);
ivScreen.setVisibility(VISIBLE);
ToastUtils.showShort(getContext().getString(b ? R.string.common_save_img_succeed : R.string.common_save_img_failed));
break;
}
}
以上已经完成了基本的功能,下面做按钮的显示和隐藏处理,在小屏播放的时候不需要显示锁屏和截屏功能
8.重写一些库中的方法,都带有详细注释,请认真查看
//这里是播放的时候点击屏幕出现的UI
@Override
public void changeUiToPlayingShow() {
// 此处做锁屏功能的按钮显示,判断是否锁屏状态,并且需要注意当前屏幕状态
if (!locked) {
super.changeUiToPlayingShow();
// 判断是否全屏
if (screen == SCREEN_FULLSCREEN)
ibScreen.setVisibility(View.VISIBLE);
}
if (screen == SCREEN_FULLSCREEN)
ibLock.setVisibility(ibLock.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 用户滑动屏幕的操作,返回true来屏蔽音量、亮度、进度的滑动功能
case MotionEvent.ACTION_MOVE:
if (locked)
return true;
}
return super.onTouch(v, event);
}
//这里是播放的时候屏幕上面UI消失 只显示下面底部的进度条UI
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 点击暂停按钮执行的回调
@Override
public void onStatePause() {
super.onStatePause();
ibLock.setVisibility(View.INVISIBLE);
}
//这里是暂停的时候屏幕出现的UI
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
if (screen == SCREEN_FULLSCREEN) {
ibScreen.setVisibility(View.VISIBLE);
}
}
//这里是暂停的时候点击屏幕消失的UI,只显示下面底部的进度条UI
@Override
public void changeUiToPauseClear() {
super.changeUiToPauseClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
//这里是出错的UI
@Override
public void changeUiToError() {
super.changeUiToError();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 点击屏幕会出现所有控件,一定时间后消失的回调
@Override
public void dissmissControlView() {
super.dissmissControlView();
// 需要在UI线程进行隐藏
post(() -> {
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
});
}
注意:"screen"是库中已经存在的变量,表示当前屏幕状态;"SCREEN_FULLSCREEN"表示全屏状态,"SCREEN_NORMAL"表示小屏状态;截图功能是根据获取当前播放的帧数拿到Bitmap方法然后保存Bitmap到本地,然后通知系统相册刷新图片。
其中需要注意的是在dissmissControlView()方法中隐藏控件需要在UI线程中进行操作;
怎样获取当前帧数的Bitmap呢?在经历了一个下午的坑坑坑之后还是无法解决,无奈只能直接询问作者,幸好作者回复了我(此处表示真的非常感谢~)
获取当前帧的方法:在库的Jzvd类中存在一个变量"JZTextureView textureView"其中有一个getBitmap()方法可以直接获取当前帧画面。
9.自定义JzvdStd后,在布局中引用自定义的JzvdStd,在查找控件的时候变量类型定义成自定义的JzvdStd即可,调用方法跟第3步是一样的。其中使用Glide显示图片和保存Bitmap的工具类就不提供了,百度一下就有了。
代码
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.babybath2.R;
import com.babybath2.constants.AppConstant;
import com.babybath2.utils.MyImageUtils;
import com.blankj.utilcode.util.ToastUtils;
import cn.jzvd.JzvdStd;
/**
* 自定义的集成第三方的播放控件,增加了锁屏和截屏功能
*/
public class MyJzvStd extends JzvdStd {
private ImageButton ibLock;
private ImageButton ibScreen;
private ImageView ivScreen;
/**
* 是否上锁
*/
private boolean locked;
public MyJzvStd(Context context) {
super(context);
}
public MyJzvStd(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onStatePreparing() {
super.onStatePreparing();
thumbImageView.setVisibility(View.GONE);
}
@Override
public void onAutoCompletion() {
super.onAutoCompletion();
// 播放完之后不显示预览图片
thumbImageView.setVisibility(View.GONE);
}
@Override
public int getLayoutId() {
// 自定义页面需要复制库里面的布局出来使用,只可增加自己的不可以删除或修改原有的
return R.layout.jz_layout_std;
}
@Override
public void init(Context context) {
super.init(context);
// 获取自定义添加的控件
ibLock = findViewById(R.id.ib_jz_layout_std_lock);
ibScreen = findViewById(R.id.ib_jz_layout_std_screen);
ivScreen = findViewById(R.id.iv_jz_layout_std_screen);
ibLock.setOnClickListener(this);
ibScreen.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// 控件的监听,获取的是父类的。
super.onClick(v);
switch (v.getId()) {
case R.id.ib_jz_layout_std_lock:
ivScreen.setVisibility(GONE);
if (locked) {
// 已经上锁,再次点击解锁
changeUiToPlayingShow();
ibLock.setImageResource(R.drawable.jz_click_lock_selector);
ToastUtils.showShort(getContext().getString(R.string.common_video_unlock));
} else {
// 上锁
changeUiToPlayingClear();
ibLock.setImageResource(R.drawable.jz_click_locked_selector);
ToastUtils.showShort(getContext().getString(R.string.common_video_locked));
}
locked = !locked;
break;
case R.id.ib_jz_layout_std_screen:
Bitmap bitmap = textureView.getBitmap();
if (bitmap == null) {
ToastUtils.showShort("图片获取失败");
return;
}
// 保存图片
boolean b = MyImageUtils.compressAndSaveImage(getContext(), bitmap, AppConstant.IMG_SELECTOR_DIR_PATH_OF_COMPRESS, 100);
MyImageUtils.showImageForBitmap(getContext(), bitmap, ivScreen);
ivScreen.setVisibility(VISIBLE);
ToastUtils.showShort(getContext().getString(b ? R.string.common_save_img_succeed : R.string.common_save_img_failed));
break;
}
}
//这里是播放的时候点击屏幕出现的UI
@Override
public void changeUiToPlayingShow() {
// 此处做锁屏功能的按钮显示,判断是否锁屏状态,并且需要注意当前屏幕状态
if (!locked) {
super.changeUiToPlayingShow();
// 判断是否全屏
if (screen == SCREEN_FULLSCREEN)
ibScreen.setVisibility(View.VISIBLE);
}
if (screen == SCREEN_FULLSCREEN)
ibLock.setVisibility(ibLock.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// 用户滑动屏幕的操作,返回true来屏蔽音量、亮度、进度的滑动功能
case MotionEvent.ACTION_MOVE:
if (locked)
return true;
}
return super.onTouch(v, event);
}
//这里是播放的时候屏幕上面UI消失 只显示下面底部的进度条UI
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 点击暂停按钮执行的回调
@Override
public void onStatePause() {
super.onStatePause();
ibLock.setVisibility(View.INVISIBLE);
}
//这里是暂停的时候屏幕出现的UI
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
if (screen == SCREEN_FULLSCREEN) {
ibScreen.setVisibility(View.VISIBLE);
}
}
//这里是暂停的时候点击屏幕消失的UI,只显示下面底部的进度条UI
@Override
public void changeUiToPauseClear() {
super.changeUiToPauseClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
//这里是出错的UI
@Override
public void changeUiToError() {
super.changeUiToError();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 点击屏幕会出现所有控件,一定时间后消失的回调
@Override
public void dissmissControlView() {
super.dissmissControlView();
// 需要在UI线程进行隐藏
post(() -> {
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
});
}
}
End
第一次写这么长的博客,有错误希望各位大佬指出。