前言
最近開發(fā)應(yīng)用需要用到視頻播放功能,在使用了原生的VideoView之后發(fā)現(xiàn)自己能力有限無法實現(xiàn)公司的需求。所以只能寄希望以第三方開源庫,當(dāng)在GitHub看到JiaoZiVideoPlayer高達(dá)9K+的Star之后果斷的使用了這個,事實也如作者所說:“可以完全自定義UI和任何功能?!?/p>
先上GitHub地址:https://github.com/lipangit/JiaoZiVideoPlayer](https://github.com/lipangit/JiaoZiVideoPlayer
版本使用的是:7.0.4
參考文章
https://juejin.im/post/5cee364e5188251bc6234313
也可以加入GitHub里面作者留下的QQ群,里面有大佬會解答一些問題
正文
功能需求:
1.顯示自定義標(biāo)題和視頻播放前的瀏覽頁面
2.可以小屏/全屏播放
3.全屏狀態(tài)下顯示返回按鈕、標(biāo)題、電量、時間、進(jìn)度條等基本功能
4.全屏狀態(tài)上下滑動右邊屏幕可以增減音量,滑動左邊屏幕可以調(diào)整亮度,橫向滑動可以調(diào)整播放進(jìn)度
5.全屏狀態(tài)需要有鎖屏、截屏功能
實現(xiàn)
功能需求上的前4個JiaoZiVideoPlayer都已經(jīng)自帶了,此處主要講解第5條需求的實現(xiàn)。
1.閱讀該庫的教程文檔:https://juejin.im/post/5cee364e5188251bc6234313
2.在布局中引用控件,此處使用的是庫中自帶的控件,如果不需要實現(xiàn)第5條需求,直接引用即可實現(xiàn)功能。注意需要在控件的外圍再套一個布局,為什么?(I don't know.)

3.直接在代碼中獲取控件然后填入視頻的鏈接地址和標(biāo)題即可緩存就是在退出頁面回來后再次回來是否繼續(xù)從退出的位置繼續(xù)播放,true 是,false 否;顯示默認(rèn)圖片就是在視頻播放的之前顯示的預(yù)覽圖片,作者推薦使用Glide顯示,因此我也是使用Glide自己封裝了工具類。
注意:"video"即是通過findViewById獲取的控件;"video.thumbImageView"是庫中自帶的預(yù)覽圖片顯示的ImageView控件。

通過前3步就可以使用基本的視頻播放功能了,下面實現(xiàn)第5個需求的功能步驟。
4.在使用完成前3步之后視頻可以正確播放了,則表示第三方庫導(dǎo)入基本成功了,如果無法播放視頻或者使用一直在加載中,請檢查視頻的Url是否正確且能在瀏覽器中直接播放,如果確定Url沒有問題,可能是播放器內(nèi)核不支持播放需要更換瀏覽器內(nèi)核,第1步的文章當(dāng)中有相信的更換步驟,請查看。
接下來開始實現(xiàn)鎖屏和截屏功能。
要增加功能必然需要增加執(zhí)行功能的按鈕,要增加按鈕就需要重寫第三方庫的布局,找出第三方庫的布局"jz_layout_std.xml",直接在Activity Studio中雙擊Shift彈出的窗口中搜索就能找到該布局文件,找到之后復(fù)制到自己項目的layout下,然后增加兩個按鈕和一個ImageView。
注意:"jz_layout_std.xml"文件可以重命名,但是不能更改或者刪除里面已經(jīng)有的布局以及控件的ID。只能增加控件而不能刪除或者修改原有的控件

5.在布局文件創(chuàng)建成功后創(chuàng)建一個類繼承"JzvdStd"把兩個構(gòu)造方法都實現(xiàn)了,然后重寫getLayoutId()方法,返回第4步的布局。
@Override
public int getLayoutId() {
// 自定義頁面需要復(fù)制庫里面的布局出來使用,只可增加自己的不可以刪除或修改原有的
return R.layout.jz_layout_std;
}
6.重寫init(Context context)方法,在方法中查找控件,并且添加按鈕監(jiān)聽
@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) {
// 控件的監(jiān)聽,獲取的是父類的。
super.onClick(v);
switch (v.getId()) {
case R.id.ib_jz_layout_std_lock:
ivScreen.setVisibility(GONE);
if (locked) {
// 已經(jīng)上鎖,再次點擊解鎖
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;
}
}
以上已經(jīng)完成了基本的功能,下面做按鈕的顯示和隱藏處理,在小屏播放的時候不需要顯示鎖屏和截屏功能
8.重寫一些庫中的方法,都帶有詳細(xì)注釋,請認(rèn)真查看
//這里是播放的時候點擊屏幕出現(xiàn)的UI
@Override
public void changeUiToPlayingShow() {
// 此處做鎖屏功能的按鈕顯示,判斷是否鎖屏狀態(tài),并且需要注意當(dāng)前屏幕狀態(tài)
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來屏蔽音量、亮度、進(jìn)度的滑動功能
case MotionEvent.ACTION_MOVE:
if (locked)
return true;
}
return super.onTouch(v, event);
}
//這里是播放的時候屏幕上面UI消失 只顯示下面底部的進(jìn)度條UI
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 點擊暫停按鈕執(zhí)行的回調(diào)
@Override
public void onStatePause() {
super.onStatePause();
ibLock.setVisibility(View.INVISIBLE);
}
//這里是暫停的時候屏幕出現(xiàn)的UI
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
if (screen == SCREEN_FULLSCREEN) {
ibScreen.setVisibility(View.VISIBLE);
}
}
//這里是暫停的時候點擊屏幕消失的UI,只顯示下面底部的進(jìn)度條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);
}
// 點擊屏幕會出現(xiàn)所有控件,一定時間后消失的回調(diào)
@Override
public void dissmissControlView() {
super.dissmissControlView();
// 需要在UI線程進(jìn)行隱藏
post(() -> {
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
});
}
注意:"screen"是庫中已經(jīng)存在的變量,表示當(dāng)前屏幕狀態(tài);"SCREEN_FULLSCREEN"表示全屏狀態(tài),"SCREEN_NORMAL"表示小屏狀態(tài);截圖功能是根據(jù)獲取當(dāng)前播放的幀數(shù)拿到Bitmap方法然后保存Bitmap到本地,然后通知系統(tǒng)相冊刷新圖片。
其中需要注意的是在dissmissControlView()方法中隱藏控件需要在UI線程中進(jìn)行操作;
怎樣獲取當(dāng)前幀數(shù)的Bitmap呢?在經(jīng)歷了一個下午的坑坑坑之后還是無法解決,無奈只能直接詢問作者,幸好作者回復(fù)了我(此處表示真的非常感謝~)
獲取當(dāng)前幀的方法:在庫的Jzvd類中存在一個變量"JZTextureView textureView"其中有一個getBitmap()方法可以直接獲取當(dāng)前幀畫面。
9.自定義JzvdStd后,在布局中引用自定義的JzvdStd,在查找控件的時候變量類型定義成自定義的JzvdStd即可,調(diào)用方法跟第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();
// 播放完之后不顯示預(yù)覽圖片
thumbImageView.setVisibility(View.GONE);
}
@Override
public int getLayoutId() {
// 自定義頁面需要復(fù)制庫里面的布局出來使用,只可增加自己的不可以刪除或修改原有的
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) {
// 控件的監(jiān)聽,獲取的是父類的。
super.onClick(v);
switch (v.getId()) {
case R.id.ib_jz_layout_std_lock:
ivScreen.setVisibility(GONE);
if (locked) {
// 已經(jīng)上鎖,再次點擊解鎖
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;
}
}
//這里是播放的時候點擊屏幕出現(xiàn)的UI
@Override
public void changeUiToPlayingShow() {
// 此處做鎖屏功能的按鈕顯示,判斷是否鎖屏狀態(tài),并且需要注意當(dāng)前屏幕狀態(tài)
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來屏蔽音量、亮度、進(jìn)度的滑動功能
case MotionEvent.ACTION_MOVE:
if (locked)
return true;
}
return super.onTouch(v, event);
}
//這里是播放的時候屏幕上面UI消失 只顯示下面底部的進(jìn)度條UI
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
}
// 點擊暫停按鈕執(zhí)行的回調(diào)
@Override
public void onStatePause() {
super.onStatePause();
ibLock.setVisibility(View.INVISIBLE);
}
//這里是暫停的時候屏幕出現(xiàn)的UI
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
if (screen == SCREEN_FULLSCREEN) {
ibScreen.setVisibility(View.VISIBLE);
}
}
//這里是暫停的時候點擊屏幕消失的UI,只顯示下面底部的進(jìn)度條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);
}
// 點擊屏幕會出現(xiàn)所有控件,一定時間后消失的回調(diào)
@Override
public void dissmissControlView() {
super.dissmissControlView();
// 需要在UI線程進(jìn)行隱藏
post(() -> {
ibLock.setVisibility(View.INVISIBLE);
ibScreen.setVisibility(View.INVISIBLE);
});
}
}
End
第一次寫這么長的博客,有錯誤希望各位大佬指出。