最近开始利用零碎时间整理以往项目中的比较有亮点的技术,先前有使用uniapp原生插件开发的方式实现过video控件,个人认为比较经典,拿来和大家分享。
项目背景
官方原先已经有video控件了,为何还要自己开发。因为项目中有个需求就是可以自定义视频缓存大小,就是当视频大小小于设定的缓存大小时,重播视频不需要再请求网络资源。然而官方的控件,限制死了缓存大小是100m(见下图),而客户的要求是300m,只能自己来搞了。

uniapp原生插件开发,必须同时掌握uniapp和原生的开发技术。因为这个项目只需要在安卓设备上运行,所以原生部分只需要安卓开发技术。
下面按我的介绍一步步进行原生插件开发:
一、下载离线sdk
官方有个介绍原生插件开发的链接:

对于有搞过安卓开发的,只需要下载app离线sdk。android 离线sdk - 正式版 | uni小程序sdk
sdk包中有个uniplugin-hello-as项目,把他解压出来,用android studio打开、
 
这个项目中有个文件夹uniplugindemo,把他拖入hbuilderx


  
二、研究官方demo
我们简要研究一下官方demo,他提供了component(ui相关)和module(ui无关)两种模式,我们的video控件适合用前者。

我们看一下ext-component.nvue代码,他实现了一个自定义的文本框控件

我们看一下原生部分的代码

简要说一下他的功能:
1、当控件初始化完毕,他会执行原生下的settel方法,把h5中的tel属性值赋给原生的textview控件,赋值完毕会触发ontel的回调,执行h5中的ontel事件。
2、他可以响应单击事件,调用原生下的cleartel方法,清空掉原生的textview控件内容。
三、运行官方demo
因为官方在基座中加入了app_id校验,我们需要配置一下才能跑起来。
(一)、获取appid
双击manifest.json,基础配置中的appid要重新获取


(二)、获取appkey
然后去开发者后台配置dcloud_appkey,通过appid搜索刚才的应用

点击【应用名称】(蓝色字体)进入应用信息,点击【各平台信息】->【新增】

所属平台android app
包名com.android.uniplugin
sha1【填自己打包证书的sha1】
sha256【填自己打包证书的sha256】

提交后出现一条记录,点击【创建】

 然后点击【查看】
android:后面的内容就是app key,复制一下
 
替换掉原生工程中androidmanifest.xml中的对应位置

(三)、生成离线打包资源
uniapp项目需要打包成原生项目下可用的资源,右击项目名称【发行】、【原生app-本地打包】、【生成本地打包app资源】

控制台下面会出现打包进度,最终会生成一个链接,点击进行会跳转到对应目录

 
我们把这个文件夹剪切移动到原生项目的\app\src\main\assets\apps位置下面,同时删掉__uni__e文件夹(这个没用了)

我们调整一下\app\src\main\assets\data\dcloud_control.xml文件中appid的值

(四)、配置调试证书
我个人习惯调试证书和打包证书是同个证书,我们在build.gradle中对signingconfigs进行调整
config {
    storefile file('证书路径')
    storepassword '密钥库密码'
    keyalias '证书别名'
    keypassword '证书密码'
} 

点击sync now进行gradle重建

(五)、运行官方demo
搞了这么久,终于做完了运行前的所有准备工作。如果gradle重建没有报错,就可以看到顶部出现了运行的按钮。我们选择一下左边的调试设备,点击运行。

首页点击【扩展component】

出现了文本框控件,我们点击一下这个控件,发现字没了,说明执行了清空内容事件。

四、参照官方demo开发video控件
官方的demo是封装一个原生textview,我们的目标是封装一个原生videoview。不过我在实际项目中是封装了一个relativelayout,背景设置为黑色,在里面加入了videoview(垂直居中于父容器)。因为我试过,如果是单纯封装videoview,他在播放视频的时候会缩到顶部,并且留下很大一块白色区域。
(一)、原生部分
1、在uniplugin_component模块中src\main\java\io\dcloud增加两个文件:

singleton.java
package io.dcloud.uniplugin;
import android.content.context;
import com.danikula.videocache.httpproxycacheserver;
public class singleton {
    private final static singleton s = new singleton();
    private singleton(){
    }
    public static singleton getinstance(){
        return s;
    }
    private httpproxycacheserver proxy;
    public static httpproxycacheserver getproxy(context context) {
        return getinstance().proxy == null ? (getinstance().proxy = getinstance().newproxy(context)) : getinstance().proxy;
    }
    private httpproxycacheserver newproxy(context context) {
        //1g缓存
        return new httpproxycacheserver.builder(context)
                .maxcachesize(1024 * 1024 * 1024)
                .build();
    }
}
myvideoview.java
package io.dcloud.uniplugin;
import android.manifest;
import android.annotation.suppresslint;
import android.app.activity;
import android.content.context;
import android.content.pm.packagemanager;
import android.graphics.color;
import android.media.mediaplayer;
import android.os.build;
import android.os.handler;
import android.os.message;
import android.view.viewgroup;
import android.widget.mediacontroller;
import android.widget.relativelayout;
import android.widget.seekbar;
import android.widget.toast;
import android.widget.videoview;
import androidx.annotation.nonnull;
import androidx.core.app.activitycompat;
import com.danikula.videocache.httpproxycacheserver;
import java.util.arraylist;
import java.util.hashmap;
import java.util.list;
import java.util.map;
import io.dcloud.feature.uniapp.unisdkinstance;
import io.dcloud.feature.uniapp.annotation.unijsmethod;
import io.dcloud.feature.uniapp.ui.action.abscomponentdata;
import io.dcloud.feature.uniapp.ui.component.absvcontainer;
import io.dcloud.feature.uniapp.ui.component.unicomponent;
import io.dcloud.feature.uniapp.ui.component.unicomponentprop;
//实现了视频缓存功能
public class myvideoview extends unicomponent<relativelayout> {
    private videoview mvideoview;
    private relativelayout mrelativelayout;
    private mediaplayer mmediaplayer;
    private int mcurrentposition=0;
    private string[] permissions = {
            manifest.permission.read_external_storage,
            manifest.permission.write_external_storage,
    };
    private activity mactivity;
    private context mcontext;
    private boolean autoplay=false;
    private string src="";
    private static final int request_permissions = 1001;
    private static final string tag = "myvideoview";
    public myvideoview(unisdkinstance instance, absvcontainer parent, abscomponentdata basiccomponentdata) {
        super(instance, parent, basiccomponentdata);
    }
    //初始化控件
    @override
    protected relativelayout initcomponenthostview(context context) {
        mcontext = context;
        mactivity = (activity) context;
        mrelativelayout=new relativelayout(context);
        mrelativelayout.setlayoutparams(new relativelayout.layoutparams
                (viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent));
        mrelativelayout.setbackgroundcolor(color.black);
        setbackgroundcolor("white");//不加这句mrelativelayout的背景色显示不出来,设置颜色可随意,原因未知
        mvideoview = new videoview(context);//videoview 不能设置背景色,否则只有声音,无法显示视频
        // 设置播放控制面板
        mediacontroller controller = new mediacontroller(context);
        mvideoview.setmediacontroller(controller);
        relativelayout.layoutparams lp1 = new relativelayout.layoutparams
                (viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent);
        lp1.addrule(relativelayout.center_in_parent, relativelayout.true);//垂直居中
        mrelativelayout.addview(mvideoview, lp1);
        map<string, object> data = new hashmap<>();
        map<string, object> detail = new hashmap<>();
        detail.put("msg", "初始化完成");
        data.put("detail", detail);
        fireevent("init", data);//回调js控件初始化
        checkpermission();
        mvideoview.setonpreparedlistener(new mediaplayer.onpreparedlistener() {
            @override
            public void onprepared(mediaplayer mp) {
                mmediaplayer=mp;
            }
        });
        mvideoview.setoncompletionlistener(new mediaplayer.oncompletionlistener()
        {
            @override
            public void oncompletion(mediaplayer mp)
            {
                mmediaplayer=mp;
                //播放结束后的动作
                map<string, object> data = new hashmap<>();
                map<string, object> detail = new hashmap<>();
                detail.put("msg", "播放完成");
                data.put("detail", detail);
                fireevent("ended", data);//回调js
            }
        });
        mvideoview.setonerrorlistener(new mediaplayer.onerrorlistener() {
            @override
            public boolean onerror(mediaplayer mp, int what, int extra) {
                mmediaplayer=mp;
                // 处理播放错误
                map<string, object> data = new hashmap<>();
                map<string, object> detail = new hashmap<>();
                detail.put("errmsg", src+"播放错误");
                data.put("detail", detail);
                fireevent("error", data);//回调js
                mp.reset();//恢复,使得mediaplayer重新返回到idle状态
                return true; // 返回true表示错误已处理,不需要进一步处理
            }
        });
        return mrelativelayout;
    }
    @unicomponentprop(name = "src")
    public void setsrc(string _src) {
        src=_src;
        httpproxycacheserver proxy = singleton.getproxy(mcontext);
        string proxyurl = proxy.getproxyurl(_src);
        mvideoview.setvideopath(proxyurl);
        play();
    }
    @unijsmethod
    public void play() {
        mvideoview.start();
        map<string, object> params = new hashmap<>();
        map<string, object> number = new hashmap<>();
        number.put("msg","开始播放"+src);
        params.put("detail", number);
        fireevent("play", params);
    }
    @override
    public void onactivityresume() {
        if(mmediaplayer!=null && mcurrentposition>0){
            //跳到暂停位置并播放
            mmediaplayer.seekto(mcurrentposition);
            mmediaplayer.start();
        }
        super.onactivityresume();
    }
    @override
    public void onactivitypause() {
        if(mmediaplayer!=null && mmediaplayer.isplaying()){
            mmediaplayer.pause();
            //记录暂停位置
            mcurrentposition=mmediaplayer.getcurrentposition();
        }
        super.onactivitypause();
    }
    @override
    public void onactivitydestroy() {
        //停止播放视频
        mvideoview.stopplayback();
        //释放mediaplayer
        releasemediaplayer();
        // 释放videoview
        mvideoview.setonpreparedlistener(null);
        mvideoview.setoncompletionlistener(null);
        mvideoview.setonerrorlistener(null);
        mvideoview.destroydrawingcache();
        super.onactivitydestroy();
    }
    private void releasemediaplayer(){//释放mediaplayer
        if (mmediaplayer != null) {
            mmediaplayer.release();
            mmediaplayer = null;
        }
    }
    private boolean checkpermission() {
        if (build.version.sdk_int >= build.version_codes.m) {
            list<string> permissionlist = new arraylist<string>();
            for (string s : permissions) {
                if (activitycompat.checkselfpermission(mactivity, s) != packagemanager.permission_granted) {
                    permissionlist.add(s);
                }
            }
            if (permissionlist.size() > 0) {
                startrequestpermission(mactivity, (string[]) permissionlist.toarray(new string[permissionlist.size()]));
                return false;
            }
        }
        return true;
    }
    private void startrequestpermission(final activity a, string[] permissionlist) {
        activitycompat.requestpermissions(a, permissionlist, request_permissions);
    }
    @override
    public void onrequestpermissionsresult(int requestcode, @nonnull string[] permissions, @nonnull int[] grantresults) {
        super.onrequestpermissionsresult(requestcode, permissions, grantresults);
        switch (requestcode) {
            case request_permissions:
                if (grantresults[2] != packagemanager.permission_granted) {
                    toast.maketext(mactivity, "请到设置中打开应用的存储读权限", toast.length_short).show();
                    return;
                }
                if (grantresults[3] != packagemanager.permission_granted) {
                    toast.maketext(mactivity, "请到设置中打开应用的存储写权限", toast.length_short).show();
                    return;
                }
                break;
        }
    }
}
2、在build.gradle引入androidvideocache
//引入androidvideocache implementation 'com.danikula:videocache:2.7.1'

3、在dcloud_uniplugins.json加入插件配置
{
  "type": "component",
  "name": "video-view",
  "class": "io.dcloud.uniplugin.myvideoview"
} 

(二)、h5部分
我们将原来的ext-component.nvue改成下面的内容
<template>
	<div>
		<video-view :src="src" @init="videoinitcallback"
			@play="videoplaycallback" @error="videoerrorcallback" @ended="videoendcallback"
			:style="{ height: videoheight  + 'px' }">
		</video-view>
	</div>
</template>
<script>
	//nvue官方文档https://uniapp.dcloud.io/tutorial/nvue-outline.html
	//不支持百分比布局,所以需要通过js来获取全屏尺寸
	//不支持this.global
	let t = null;
	export default {
		components: {},
		data() {
			return {
				videoheight: 0,
				src: '',
			}
		},
		onload() {
			t = this;
			const res = uni.getsysteminfosync();
			t.videoheight = parseint(res.windowheight);
			t.src = 'https://www.runoob.com/try/demo_source/movie.mp4';
		},
		onunload() {
		},
		methods: {
			videoinitcallback(e) { //控件初始化结束
				if (e.detail.msg) {
					uni.showtoast({
						icon: 'none',
						title: e.detail.msg,
					})
				}
			},
			videoplaycallback(e) { //控件绑定完资源准备开始播放
				if (e.detail.msg) {
					uni.showtoast({
						icon: 'none',
						title: e.detail.msg,
					})
				}
			},
			videoerrorcallback(e) { //控件绑定完资源准备开始播放
				if (e.detail.msg) {
					uni.showtoast({
						icon: 'none',
						title: e.detail.msg,
					})
				}
			},
			videoendcallback(e) {
				if (e.detail.msg) {
					uni.showtoast({
						icon: 'none',
						title: e.detail.msg,
					})
				}
			},
		}
	}
</script>(三)、运行项目
我们可以看到自己封装的控件可以正常播放视频了。

五、项目源码
项目源码也已经上传,。请自行下载后用android studio打开,里面的uniapp示例工程源码/uniplugindemo请用hbuilderx打开,参照【三、运行官方demo】里面的步骤对项目进行重新配置后才能运行,否则会提示【未配置appkey或配置错误】

附:一些问题解决方法:
(一)、uniplugin-hello-as项目gradle builder失败。
可以尝试将
distributionurl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
改为
distributionurl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
(二)、在原项目中正常,到了自己的项目中无法使用插件。
因为还有一些配置,也要同步到自己的项目中。
1、build.gradle加入一句话
// 添加uni-app插件
implementation project(':uniplugin_component') 

2、settings.gradle加入一句话
include ':uniplugin_component'

 
             我要评论
我要评论 
                                             
                                             
                                            
发表评论