欢迎关注微信公众号:FSA全栈行动 👋
一、简介
微信小程序播放教育类视频要求具备有相关资质,但这些资质一般公司很难短时间申请下来(甚至有的公司压根就申请不了),而【短视频播放器小程序插件】含有《信息网络传播视听节目许可证》的资质证书备案,可以利用该插件来解决资质问题,相关截图如下:
图片来源:https://cloud.tencent.com/document/product/266/36849
采购流程于技术无关,以下内容着重讲解如何集成该微信小程序插件。
注:【短视频播放器小程序插件】授权费 3 万/年(有 14 天试用 Licence),如果有购买腾讯云其他服务的话,满足一定条件,会赠送 1 年 免费使用 Licence,详情找腾讯云客服咨询(2022 年 04 月如此,赠送情况可能随时会变)。
二、使用
- 激活:在腾讯云控制台激活插件 Licence 之后,才能正常使用该播放器插件。
- appid:后面插件用到的 appid 需要在【腾讯云控制台】>账号信息中查看获取。
- 云点播短视频播放器-开发文档:https://mp.weixin.qq.com/wxopen/plugindevdoc?appid=wx116d0dd5e6a39ac7&lang=zh_CN
1、绑定插件
因为微信小程序插件没有实质代码或 SDK,所以无法在本地添加集成,需要在微信小程序平台,将 小程序AppID
与 插件AppID
进行绑定(即给小程序添加插件),开发者工具在编译时会自动引入,绑定有 2 种方式:
- 方式 1:登录微信小程序平台,在
设置-第三方设置
中找到添加插件
,输入插件 AppID(wx116d0dd5e6a39ac7
)搜索并添加:
- 方式 2:在 云点播短视频播放器文档 页面直接点击
添加插件
:
云点播短视频播放器文档:https://mp.weixin.qq.com/wxopen/pluginbasicprofile?action=intro&appid=wx116d0dd5e6a39ac7&lang=zh_CN
2、集成插件
微信小程序原生工程需要 在 app.json 里声明使用的插件及版本
,对应到 uniapp 工程,则是在 manifest.json
文件中微信小程序特有配置(即 mp-weixin
节点) 处,进行 plugins
配置声明:
// manifest.json 源码视图
{/* 小程序特有相关 */"mp-weixin": {"appid": "wxxxxxxxxxxx","plugins": {// 云点播短视频播放器// 文档:https://mp.weixin.qq.com/wxopen/plugindevdoc?appid=wx116d0dd5e6a39ac7&token=1835838344&lang=zh_CN"cloudPlayer": {"version": "0.1.2","provider": "wx116d0dd5e6a39ac7"}}}
}
manifest.json 配置项说明:https://uniapp.dcloud.io/collocation/manifest.html
3、页面内使用播放器
微信小程序原生工程需要 在 页面的 xxxx.json 里声明
,对应到 uniapp 工程,则是在 pages.json
文件中,在需要使用插件的 页面的 style
的微信小程序特有配置(即 mp-weixin
节点)处,进行 usingComponents
配置声明:
// pages.json
{"pages": [...,{"path": "pages/course/course","style": {"navigationBarTitleText": "课程","mp-weixin": {// 云点播短视频播放器"usingComponents": {"cloud-player": "plugin://cloudPlayer/player"}}}}],"globalStyle": {...}
}
pages.json 配置项说明:https://uniapp.dcloud.io/collocation/pages.html#style
声明完哪个页面需要使用插件播放器之后,就可以在那个页面的布局文件中使用插件播放器了:
// 在wxml里插入
<cloud-playerappid="xxxxx"fileid="xxxxxxxx"playerid="myPlayerId"
></cloud-player>
注:微信小程序原生工程中,页面是
wxml
文件,uniapp 工程中是vue
文件。另外,目前这种声明方式只对单个配置过的页面有效,也就是说,如果其他页面也需要使用插件播放器,还需要在其他页面的style
中单独进行配置,这就很麻烦了,不过现在不用烦恼,后面会解决这个问题。
4、组件内使用播放器
为了功能复用,以及方便代码维护,实际开发中,往往会自定义组件,对常用的布局、功能进行封装。微信小程序原生工程可以在自定义组件的 json 文件中进行配置,跟页面相同的 usingComponents
配置即可:
官方的 云点播短视频播放器-开发文档 只说明在了如何在网页中使用插件,但没有对组件中如何使用插件进行说明,很无语,希望后续官方能完善一下。另外,这是我发起工单询问之后,腾讯技术售后给我的 demo 工程中的代码,是否有效暂不确定 -_-!
uniapp 遵循 vue 规范,想要在自定义组件要使用其他自定义组件,需要在 vue 文件中的 <script>
标签中,配置 components
,例如:
<script>
import leadHeader from "./lead-header.vue";
export default {components: {leadHeader,},
};
</script>
那么依葫芦画瓢,是否也可以这样配置插件播放器呢?例如:
<script>
import cloudPlayer from "plugin://cloudPlayer/player";
export default {components: {cloudPlayer,},
};
</script>
可惜不行,编译时会报错 Error: Can't resolve 'plugin://cloudPlayer/player'
,而且 uniapp 也没有提供对应的配置项。不过呢,uniapp 是可以直接使用微信小程序自定义组件的,这是否意味着,可以将用到插件的自定义组件改用 wxml+wcss 的方式进行编写,然后再引入到 uniapp 工程中呢?
uniapp 使用小程序原生组件:https://uniapp.dcloud.io/tutorial/miniprogram-subject.html#小程序自定义组件支持
仔细想想,这个方案是有问题的。首先,对不熟悉微信小程序原生开发的人很不友好,其次,wxcomponents
目录下的小程序组件,要使用的话,还需要在 pages.json
中进行配置,这意味着 uniapp 自定义组件中是不能直接使用小程序组件的,无法解决 组件中引入组件
的情况,所以,这个方案不行。难道 uniapp 对此就无解了吗?非也,仔细阅读上面的 uniapp 官方文档,可以找到这么一句:当需要在 vue 组件中使用小程序组件时,注意在 pages.json 的 globalStyle 中配置 usingComponents,而不是页面级配置
。
于是,在 pages.json
文件中做如下修改:
// pages.json
{"pages": [...,{"path": "pages/course/course","style": {"navigationBarTitleText": "课程",// "mp-weixin": {// "usingComponents": {// "cloud-player": "plugin://cloudPlayer/player"// }// }}}],"globalStyle": {// #ifdef MP-WEIXIN"usingComponents": {"cloud-player": "plugin://cloudPlayer/player"},// #endif...}
}
可以发现,我把页面 style
下的 mp-weixin
配置给注释掉了,原因是在 globalStyle
下配置了 usingComponents
之后,就可以全局使用插件播放器,不管是页面或是组件中,都不需要再单独去配置 usingComponents
,这样就可以在项目中随心所欲地使用播放器插件了,nice~
5、获取播放器 Context
当需要在业务逻辑中控制视频播放或暂停时,会用到 videoContext
,如果使用默认的 <video>
标签,那么可以通过 uni.createVideoContext(videoId, this)
来获取视频播放器上下文,再通过上下文执行 play()
及 pause()
等方法,即可控制视频播放,详细说明见 uniapp 官方文档:
createVideoContext:https://uniapp.dcloud.io/api/media/video-context.html#createvideocontext
但是,uni.createVideoContext(videoId, this)
对腾讯云点播插件无效,需改用 requirePlugin(pluginName).getContext(videoId)
来获取,例如:
const plugin = requirePlugin("cloudPlayer");
let player = plugin.getContext("myVideo");
该解决方案源自一篇社区帖子 《腾讯云点播 wx.createVideoContext(“myVideo”).pause()无法暂停》:https://developers.weixin.qq.com/community/develop/doc/0004eae9e6cd08e31d6d827e657800
三、封装
腾讯云点播插件 <cloud-player>
与默认的 <video>
标签在使用上差异不多,就以下几点:
<cloud-player>
使用时需要配置appid
属性。<cloud-player>
使用时需要配置width
和height
属性。<cloud-player>
视频源属性是fileid
,<video>
视频源属性是src
。<cloud-player>
id 属性是playerid
,<video>
id 属性是id
。<cloud-player>
上下文通过requirePlugin(pluginName).getContext(videoId)
获取,<video>
上下文通过uni.createVideoContext(videoId, this)
获取。
所以,为了代码可维护性,统一模板代码,我们可以自定义组件(名为 video-mix
)对两者进行封装,用法上跟 <video>
标签差不多:
<video-mixvideoId="videoPlayer"width="710rpx"height="400rpx":fileid="curPlayEpisode.code"src="http://xxxx/video1.mp4":poster="curPlayEpisode.cover_img":controls="true":autoplay="true":show-progress="showProgress"@error="onVideoError"@controlstoggle="onVideoControlsToggle"
></video-mix>
注:我个人设想在微信小程序上使用腾讯云点播插件播放视频,在其他平台上还是继续使用
<video>
标签,于是设计为fileid
和src
共存。
以下是 video-mix.vue
的完整代码:
// video-mix.vue
<template><view class="video-mix-container"><!-- #ifdef MP-WEIXIN --><cloud-playerappid="GitLqr亲自打码":id="videoId":playerid="videoId":width="width":height="height":fileid="fileid":autoplay="autoplay":loop="loop":muted="muted":controls="controls":danmu-list="danmuList":danmu-btn="danmuBtn":enable-danmu="enableDanmu":page-gesture="pageGesture":show-progress="showProgress":show-fullscreen-btn="showFullscreenBtn":show-play-btn="showPlayBtn":show-center-play-btn="showCenterPlayBtn":enable-progress-gesture="enableProgressGesture":object-fit="objectFit":poster="poster":show-mute-btn="showMuteBtn":title="title":play-btn-position="playBtnPosition":enable-play-gesture="enablePlayGesture":auto-pause-if-navigate="autoPauseIfNavigate":auto-pause-if-open-native="autoPauseIfOpenNative":vslide-gesture="vslideGesture":vslide-gesture-in-fullscreen="vslideGestureInFullscreen":ad-unit-id="adUnitId":poster-for-crawler="posterForCrawler"@play="onPlay"@pause="onPause"@ended="onEnded"@timeupdate="onTimeUpdate"@fullscreenchange="onFullScreenChange"@waiting="onWaiting"@error="onError"@progress="onProgress"@loadedmetadata="onLoadedMetaData"@controlstoggle="onControlsToggle"></cloud-player><!-- #endif --><!-- #ifndef MP-WEIXIN --><video:id="videoId":style="{ width: width, height: height }":src="src":autoplay="autoplay":loop="loop":muted="muted":controls="controls":danmu-list="danmuList":danmu-btn="danmuBtn":enable-danmu="enableDanmu":page-gesture="pageGesture":show-progress="showProgress":show-fullscreen-btn="showFullscreenBtn":show-play-btn="showPlayBtn":show-center-play-btn="showCenterPlayBtn":enable-progress-gesture="enableProgressGesture":object-fit="objectFit":poster="poster":show-mute-btn="showMuteBtn":title="title":play-btn-position="playBtnPosition":enable-play-gesture="enablePlayGesture":auto-pause-if-navigate="autoPauseIfNavigate":auto-pause-if-open-native="autoPauseIfOpenNative":vslide-gesture="vslideGesture":vslide-gesture-in-fullscreen="vslideGestureInFullscreen":ad-unit-id="adUnitId":poster-for-crawler="posterForCrawler"@play="onPlay"@pause="onPause"@ended="onEnded"@timeupdate="onTimeUpdate"@fullscreenchange="onFullScreenChange"@waiting="onWaiting"@error="onError"@progress="onProgress"@loadedmetadata="onLoadedMetaData"@controlstoggle="onControlsToggle"></video><!-- #endif --></view>
</template><script>
export default {name: "video-mix",props: {videoId: {type: String,default: "",},width: {type: String,default: "750rpx",},height: {type: String,default: "420rpx",},fileid: {type: String,default: "",},src: {type: String,default: "",},autoplay: {type: Boolean,default: false,},loop: {type: Boolean,default: false,},muted: {type: Boolean,default: false,},initialTime: {type: Number,default: 0,},controls: {type: Boolean,default: true,},danmuList: {type: Array,default() {return [];},},danmuBtn: {type: Boolean,default: false,},enableDanmu: {type: Boolean,default: false,},pageGesture: {type: Boolean,default: false,},// direction: {// type: Number,// default: undefined,// },showProgress: {type: Boolean,default: true,},showFullscreenBtn: {type: Boolean,default: true,},showPlayBtn: {type: Boolean,default: true,},showCenterPlayBtn: {type: Boolean,default: true,},enableProgressGesture: {type: Boolean,default: true,},objectFit: {type: String,default: "contain",},poster: {type: String,default: "",},showMuteBtn: {type: Boolean,default: false,},title: {type: String,default: "",},playBtnPosition: {type: String,default: "bottom",},enablePlayGesture: {type: Boolean,default: false,},autoPauseIfNavigate: {type: Boolean,default: true,},autoPauseIfOpenNative: {type: Boolean,default: true,},vslideGesture: {type: Boolean,default: false,},vslideGestureInFullscreen: {type: Boolean,default: true,},adUnitId: {type: String,default: "",},posterForCrawler: {type: String,default: "",},},emits: ["play","pause","ended","timeupdate","fullscreenchange","waiting","error","progress","loadedmetadata","controlstoggle",],data() {return {isVideoPlaying: false,videoContext: null,};},methods: {onPlay(e) {this.isVideoPlaying = true;this.$emit("play", e);},onPause(e) {this.isVideoPlaying = false;this.$emit("pause", e);},onEnded(e) {this.isVideoPlaying = false;this.$emit("ended", e);},onTimeUpdate(e) {this.$emit("timeupdate", e);},onFullScreenChange(e) {this.$emit("fullscreenchange", e);},onWaiting(e) {this.$emit("waiting", e);},onError(e) {this.isVideoPlaying = false;this.$emit("error", e);},onProgress(e) {this.$emit("progress", e);},onLoadedMetaData(e) {this.$emit("loadedmetadata", e);},onControlsToggle(e) {this.$emit("controlstoggle", e);},isPlaying() {return this.isVideoPlaying;},play() {this._log("play");this._fetchVideoContext().then(() => {this.videoContext.play();});},pause() {this._log("pause");this._fetchVideoContext().then(() => {this.videoContext.pause();});},stop() {this._log("stop");this._fetchVideoContext().then(() => {this.videoContext.stop();});},_fetchVideoContext() {const operation = () =>new Promise((resolve, reject) => {if (!this.videoContext) {// #ifdef MP-WEIXIN// 这里的cloudPlayer是在json配置上引入的插件子组件名const plugin = requirePlugin("cloudPlayer");console.log('requirePlugin("cloudPlayer"): ', plugin);this.videoContext = plugin.getContext(this.videoId);console.log(`plugin.getContext(${this.videoId}): `,this.videoContext);// #endif// #ifndef MP-WEIXIN// this是在自定义组件下,当前组件实例的this,以操作组件内 video 组件(在自定义组件中药加上this,如果是普通页面即不需要加)this.videoContext = uni.createVideoContext(this.videoId, this);console.log("uni.createVideoContext(this.videoId, this): ",this.videoContext);// #endif}if (this.videoContext) {resolve(this.videoContext);} else {reject("videoContext is empty");}});return new Promise((resolve, reject) => {this.$utils.promiseRetry(operation, 500, 3).then(resolve).catch(reject);});},_log(msg) {console.log(`video-mix : ${msg}`);},},
};
</script><style lang="scss" scoped>
.video-mix-container {
}
</style>
上述代码中用到的工具方法:
// utils.js
/* Promise 包装好的 setTimeout */
export const promiseWait = (ms) => new Promise((r) => setTimeout(r, ms));
/*** Promise 重试* @param {Function} operation 操作函数* @param {Number} delay 时间间隔* @param {Number} retries 重试次数*/
export const promiseRetry = (operation, delay, retries) =>new Promise((resolve, reject) => {return operation().then(resolve).catch((reason) => {if (retries > 0) {return promiseWait(delay).then(promiseRetry.bind(null, operation, delay, retries - 1)).then(resolve).catch(reject);}return reject(reason);});});
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~