自定義的 JiaoZiVideoPlayer 集成了鎖屏和截屏功能

前言

最近開發(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

第一次寫這么長的博客,有錯誤希望各位大佬指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容