当前位置:   article > 正文

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

uniapp安装uni-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

  1. package io.dcloud.uniplugin;
  2. import android.content.Context;
  3. import com.danikula.videocache.HttpProxyCacheServer;
  4. public class Singleton {
  5. private final static Singleton s = new Singleton();
  6. private Singleton(){
  7. }
  8. public static Singleton getInstance(){
  9. return s;
  10. }
  11. private HttpProxyCacheServer proxy;
  12. public static HttpProxyCacheServer getProxy(Context context) {
  13. return getInstance().proxy == null ? (getInstance().proxy = getInstance().newProxy(context)) : getInstance().proxy;
  14. }
  15. private HttpProxyCacheServer newProxy(Context context) {
  16. //1G缓存
  17. return new HttpProxyCacheServer.Builder(context)
  18. .maxCacheSize(1024 * 1024 * 1024)
  19. .build();
  20. }
  21. }

 MyVideoView.java 

  1. package io.dcloud.uniplugin;
  2. import android.Manifest;
  3. import android.annotation.SuppressLint;
  4. import android.app.Activity;
  5. import android.content.Context;
  6. import android.content.pm.PackageManager;
  7. import android.graphics.Color;
  8. import android.media.MediaPlayer;
  9. import android.os.Build;
  10. import android.os.Handler;
  11. import android.os.Message;
  12. import android.view.ViewGroup;
  13. import android.widget.MediaController;
  14. import android.widget.RelativeLayout;
  15. import android.widget.SeekBar;
  16. import android.widget.Toast;
  17. import android.widget.VideoView;
  18. import androidx.annotation.NonNull;
  19. import androidx.core.app.ActivityCompat;
  20. import com.danikula.videocache.HttpProxyCacheServer;
  21. import java.util.ArrayList;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import io.dcloud.feature.uniapp.UniSDKInstance;
  26. import io.dcloud.feature.uniapp.annotation.UniJSMethod;
  27. import io.dcloud.feature.uniapp.ui.action.AbsComponentData;
  28. import io.dcloud.feature.uniapp.ui.component.AbsVContainer;
  29. import io.dcloud.feature.uniapp.ui.component.UniComponent;
  30. import io.dcloud.feature.uniapp.ui.component.UniComponentProp;
  31. //实现了视频缓存功能
  32. public class MyVideoView extends UniComponent<RelativeLayout> {
  33. private VideoView mVideoView;
  34. private RelativeLayout mRelativeLayout;
  35. private MediaPlayer mMediaPlayer;
  36. private int mCurrentPosition=0;
  37. private String[] permissions = {
  38. Manifest.permission.READ_EXTERNAL_STORAGE,
  39. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  40. };
  41. private Activity mActivity;
  42. private Context mContext;
  43. private Boolean autoplay=false;
  44. private String src="";
  45. private static final int REQUEST_PERMISSIONS = 1001;
  46. private static final String TAG = "MyVideoView";
  47. public MyVideoView(UniSDKInstance instance, AbsVContainer parent, AbsComponentData basicComponentData) {
  48. super(instance, parent, basicComponentData);
  49. }
  50. //初始化控件
  51. @Override
  52. protected RelativeLayout initComponentHostView(Context context) {
  53. mContext = context;
  54. mActivity = (Activity) context;
  55. mRelativeLayout=new RelativeLayout(context);
  56. mRelativeLayout.setLayoutParams(new RelativeLayout.LayoutParams
  57. (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  58. mRelativeLayout.setBackgroundColor(Color.BLACK);
  59. setBackgroundColor("white");//不加这句mRelativeLayout的背景色显示不出来,设置颜色可随意,原因未知
  60. mVideoView = new VideoView(context);//VideoView 不能设置背景色,否则只有声音,无法显示视频
  61. // 设置播放控制面板
  62. MediaController controller = new MediaController(context);
  63. mVideoView.setMediaController(controller);
  64. RelativeLayout.LayoutParams lp1 = new RelativeLayout.LayoutParams
  65. (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  66. lp1.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);//垂直居中
  67. mRelativeLayout.addView(mVideoView, lp1);
  68. Map<String, Object> data = new HashMap<>();
  69. Map<String, Object> detail = new HashMap<>();
  70. detail.put("msg", "初始化完成");
  71. data.put("detail", detail);
  72. fireEvent("init", data);//回调JS控件初始化
  73. checkPermission();
  74. mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
  75. @Override
  76. public void onPrepared(MediaPlayer mp) {
  77. mMediaPlayer=mp;
  78. }
  79. });
  80. mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
  81. {
  82. @Override
  83. public void onCompletion(MediaPlayer mp)
  84. {
  85. mMediaPlayer=mp;
  86. //播放结束后的动作
  87. Map<String, Object> data = new HashMap<>();
  88. Map<String, Object> detail = new HashMap<>();
  89. detail.put("msg", "播放完成");
  90. data.put("detail", detail);
  91. fireEvent("ended", data);//回调JS
  92. }
  93. });
  94. mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
  95. @Override
  96. public boolean onError(MediaPlayer mp, int what, int extra) {
  97. mMediaPlayer=mp;
  98. // 处理播放错误
  99. Map<String, Object> data = new HashMap<>();
  100. Map<String, Object> detail = new HashMap<>();
  101. detail.put("errMsg", src+"播放错误");
  102. data.put("detail", detail);
  103. fireEvent("error", data);//回调JS
  104. mp.reset();//恢复,使得MediaPlayer重新返回到Idle状态
  105. return true; // 返回true表示错误已处理,不需要进一步处理
  106. }
  107. });
  108. return mRelativeLayout;
  109. }
  110. @UniComponentProp(name = "src")
  111. public void setSrc(String _src) {
  112. src=_src;
  113. HttpProxyCacheServer proxy = Singleton.getProxy(mContext);
  114. String proxyUrl = proxy.getProxyUrl(_src);
  115. mVideoView.setVideoPath(proxyUrl);
  116. play();
  117. }
  118. @UniJSMethod
  119. public void play() {
  120. mVideoView.start();
  121. Map<String, Object> params = new HashMap<>();
  122. Map<String, Object> number = new HashMap<>();
  123. number.put("msg","开始播放"+src);
  124. params.put("detail", number);
  125. fireEvent("play", params);
  126. }
  127. @Override
  128. public void onActivityResume() {
  129. if(mMediaPlayer!=null && mCurrentPosition>0){
  130. //跳到暂停位置并播放
  131. mMediaPlayer.seekTo(mCurrentPosition);
  132. mMediaPlayer.start();
  133. }
  134. super.onActivityResume();
  135. }
  136. @Override
  137. public void onActivityPause() {
  138. if(mMediaPlayer!=null && mMediaPlayer.isPlaying()){
  139. mMediaPlayer.pause();
  140. //记录暂停位置
  141. mCurrentPosition=mMediaPlayer.getCurrentPosition();
  142. }
  143. super.onActivityPause();
  144. }
  145. @Override
  146. public void onActivityDestroy() {
  147. //停止播放视频
  148. mVideoView.stopPlayback();
  149. //释放MediaPlayer
  150. releaseMediaPlayer();
  151. // 释放VideoView
  152. mVideoView.setOnPreparedListener(null);
  153. mVideoView.setOnCompletionListener(null);
  154. mVideoView.setOnErrorListener(null);
  155. mVideoView.destroyDrawingCache();
  156. super.onActivityDestroy();
  157. }
  158. private void releaseMediaPlayer(){//释放MediaPlayer
  159. if (mMediaPlayer != null) {
  160. mMediaPlayer.release();
  161. mMediaPlayer = null;
  162. }
  163. }
  164. private boolean checkPermission() {
  165. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  166. List<String> permissionList = new ArrayList<String>();
  167. for (String s : permissions) {
  168. if (ActivityCompat.checkSelfPermission(mActivity, s) != PackageManager.PERMISSION_GRANTED) {
  169. permissionList.add(s);
  170. }
  171. }
  172. if (permissionList.size() > 0) {
  173. startRequestPermission(mActivity, (String[]) permissionList.toArray(new String[permissionList.size()]));
  174. return false;
  175. }
  176. }
  177. return true;
  178. }
  179. private void startRequestPermission(final Activity a, String[] permissionList) {
  180. ActivityCompat.requestPermissions(a, permissionList, REQUEST_PERMISSIONS);
  181. }
  182. @Override
  183. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  184. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  185. switch (requestCode) {
  186. case REQUEST_PERMISSIONS:
  187. if (grantResults[2] != PackageManager.PERMISSION_GRANTED) {
  188. Toast.makeText(mActivity, "请到设置中打开应用的存储读权限", Toast.LENGTH_SHORT).show();
  189. return;
  190. }
  191. if (grantResults[3] != PackageManager.PERMISSION_GRANTED) {
  192. Toast.makeText(mActivity, "请到设置中打开应用的存储写权限", Toast.LENGTH_SHORT).show();
  193. return;
  194. }
  195. break;
  196. }
  197. }
  198. }

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改成下面的内容

  1. <template>
  2. <div>
  3. <video-view :src="src" @init="videoInitCallback"
  4. @play="videoPlayCallback" @error="videoErrorCallback" @ended="videoEndCallback"
  5. :style="{ height: videoHeight + 'px' }">
  6. </video-view>
  7. </div>
  8. </template>
  9. <script>
  10. //nvue官方文档https://uniapp.dcloud.io/tutorial/nvue-outline.html
  11. //不支持百分比布局,所以需要通过js来获取全屏尺寸
  12. //不支持this.global
  13. let t = null;
  14. export default {
  15. components: {},
  16. data() {
  17. return {
  18. videoHeight: 0,
  19. src: '',
  20. }
  21. },
  22. onLoad() {
  23. t = this;
  24. const res = uni.getSystemInfoSync();
  25. t.videoHeight = parseInt(res.windowHeight);
  26. t.src = 'https://www.runoob.com/try/demo_source/movie.mp4';
  27. },
  28. onUnload() {
  29. },
  30. methods: {
  31. videoInitCallback(e) { //控件初始化结束
  32. if (e.detail.msg) {
  33. uni.showToast({
  34. icon: 'none',
  35. title: e.detail.msg,
  36. })
  37. }
  38. },
  39. videoPlayCallback(e) { //控件绑定完资源准备开始播放
  40. if (e.detail.msg) {
  41. uni.showToast({
  42. icon: 'none',
  43. title: e.detail.msg,
  44. })
  45. }
  46. },
  47. videoErrorCallback(e) { //控件绑定完资源准备开始播放
  48. if (e.detail.msg) {
  49. uni.showToast({
  50. icon: 'none',
  51. title: e.detail.msg,
  52. })
  53. }
  54. },
  55. videoEndCallback(e) {
  56. if (e.detail.msg) {
  57. uni.showToast({
  58. icon: 'none',
  59. title: e.detail.msg,
  60. })
  61. }
  62. },
  63. }
  64. }
  65. </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'

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/617847
推荐阅读
相关标签
  

闽ICP备14008679号