赞
踩
最近开始利用零碎时间整理以往项目中的比较有亮点的技术,先前有使用uniapp原生插件开发的方式实现过video控件,个人认为比较经典,拿来和大家分享。
官方原先已经有video控件了,为何还要自己开发。因为项目中有个需求就是可以自定义视频缓存大小,就是当视频大小小于设定的缓存大小时,重播视频不需要再请求网络资源。然而官方的控件,限制死了缓存大小是100M(见下图),而客户的要求是300M,只能自己来搞了。
uniapp原生插件开发,必须同时掌握uniapp和原生的开发技术。因为这个项目只需要在安卓设备上运行,所以原生部分只需要安卓开发技术。
下面按我的介绍一步步进行原生插件开发:
官方有个介绍原生插件开发的链接:
对于有搞过安卓开发的,只需要下载App离线SDK。Android 离线SDK - 正式版 | uni小程序SDK
SDK包中有个UniPlugin-Hello-AS项目,把他解压出来,用android studio打开、
这个项目中有个文件夹unipluginDemo,把他拖入HBuilderX
我们简要研究一下官方demo,他提供了component(UI相关)和module(UI无关)两种模式,我们的video控件适合用前者。
我们看一下ext-component.nvue代码,他实现了一个自定义的文本框控件
我们看一下原生部分的代码
简要说一下他的功能:
1、当控件初始化完毕,他会执行原生下的setTel方法,把H5中的tel属性值赋给原生的TextView控件,赋值完毕会触发onTel的回调,执行H5中的onTel事件。
2、他可以响应单击事件,调用原生下的clearTel方法,清空掉原生的TextView控件内容。
因为官方在基座中加入了app_id校验,我们需要配置一下才能跑起来。
双击manifest.json,基础配置中的appid要重新获取
然后去开发者后台配置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重建
搞了这么久,终于做完了运行前的所有准备工作。如果Gradle重建没有报错,就可以看到顶部出现了运行的按钮。我们选择一下左边的调试设备,点击运行。
首页点击【扩展component】
出现了文本框控件,我们点击一下这个控件,发现字没了,说明执行了清空内容事件。
官方的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" }
我们将原来的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或配置错误】
可以尝试将
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'
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。