手把手教你uniapp原生插件开发-(无限制缓存)video控件的实现

        最近开始利用零碎时间整理以往项目中的比较有亮点的技术,先前有使用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'

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 很高兴听到您对RISC-V处理器的设计感兴趣。以下是一些基本步骤,可以帮助您开始设计自己的RISC-V处理器: 1. 确定处理器的需求和规格:在设计处理器之前,您需要确定处理器的需求和规格。这包括处理器的性能、功耗、指令集等方面。 2. 学习RISC-V指令集架构:RISC-V是一种开放的指令集架构,您需要学习它的指令集和编码方式。您可以在RISC-V官方网站上找到相关的文档和程。 3. 设计处理器的微架构:微架构是处理器的实现细节,包括指令流水线、寄存器文件、ALU等。您需要设计一个合适的微架构来实现RISC-V指令集。 4. 编写Verilog代码:Verilog是一种硬件描述语言,您需要使用Verilog编写处理器的代码。您可以使用Verilog模拟器来验证您的代码是否正确。 5. 进行综合和布局布线:综合是将Verilog代码转换为门级电路的过程,布局布线是将门级电路布置在芯片上的过程。您可以使用EDA工具来完成这些步骤。 6. 进行仿真和验证:最后,您需要对处理器进行仿真和验证,以确保它能够正确地执行RISC-V指令集。 希望这些步骤可以帮助您开始设计自己的RISC-V处理器。祝您好运! ### 回答2: 随着计算机技术的不断发展,处理器作为计算机的中央处理单元,一直处于不断更新和迭代的状态。在这个过程中,越来越多的人开始将目光投向自己动手设计处理器的领域,以提高对计算机结构的理解和掌握能力。而RISC-V处理器则成为了越来越受欢迎的处理器设计体系结构之一。下面,我们就来手把手你设计RISC-V处理器。 首先,需要了解RISC-V处理器的体系结构和指令集,掌握其特点,以便更好地进行设计。RISC-V架构采用精简指令集(Reduced Instruction Set Computing,RISC)的思想,指令集清晰简单,易于扩展和实现,同时提供了不同的指令长度和地址宽度,满足多种应用场景的需求。 其次,需要明确设计RISC-V处理器的目的和需求。例如,设计一款高性能处理器,需要考虑运算速度、处理带宽、低功耗等方面的需求,而设计一款嵌入式处理器,则需要考虑尺寸、功耗、集成度等方面的需求。在确定需求后,可以选择适合的设计方法和实现方式。 接着,需要进行设计和仿真。采用硬件描述语言(如Verilog或VHDL)进行设计,利用仿真软件进行仿真调试,逐步完善处理器的各项功能。需要注意的是,设计时需要清晰明确每一阶段的功能和相应的接口,保证设计的可扩展性。 最后,进行硬件实现和验证。将设计好的RTL电路转换为FPGA或ASIC中的物理实现,进行性能测试和功能验证,发布仿真测试结果和设计文档,确保设计能够满足预期的性能和功能要求,并能够进一步优化和升级。 在以上步骤中,需要掌握的知识包括计算机体系结构、数字电路设计、硬件描述语言的使用等。需要长期的学习和实践,才能够熟练掌握处理器设计的各个环节,并能够设计出具备高性能、低功耗、灵活可扩展等特点的处理器。 ### 回答3: RISC-V是一个由加州大学伯克利分校推出的开源指令集架构,它的设计理念是简化指令集,更加注重可扩展性、可定制性和易于实现。设计RISC-V处理器需要了解计算机体系结构以及数字电路原理,下面将手把手你设计CPU。 第一步,需要确定处理器的架构。RISC-V处理器一般采用五级流水线结构,包括取指、译码、执行、访存和写回。在这个流水线结构中,每个阶段都有对应的功能,可以保证指令的按序执行。 第二步,需要确定指令集架构。RISC-V有基础指令集和标准扩展指令集,需要根据使用需求选择相应的扩展指令集并实现相应的操作。 第三步,需要进行处理器的逻辑设计。包括指令寄存器(IR)、程序计数器(PC)、指令存储器(IM)、寄存器堆、ALU(算数逻辑单元)、数据存储器(DM)等,这些模块通过总线相互连接构成处理器的基本结构。 第四步,需要进行数字电路的设计。处理器逻辑的实现需要用到器件和电路,需要根据设计的结构和功能实现相应的数字电路。 第五步,进行验证和调试。在设计完成后,需要进行仿真验证和调试工作,以保证设计的正确性和稳定性。 总的来说,设计RISC-V处理器需要掌握计算机体系结构、数字电路原理和基础编程知识,需要进行详细、全面的规划和设计。设计过程中需要不断地验证和调整,确保设计的正确性和稳定性,最终完成一个高质量且符合需求的处理器设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝莲花-爸爸去哪儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值