关于uniapp视频播放器遇到的一些问题,mark下。
中途遇到了很多问题,如果有相同的伙伴遇到了类似的,欢迎交流
- 官方的video播放器在app上不友好,有以下功能不支持。
- @loadedmetadata、@controlstoggle不支持导致只能手写控制层。
- 不支持外挂字幕,因为video在app上运行时,是采用的ijkplayer库来实现的,ijkplayer目前也不支持外挂字幕
- 使用subNVues或者cover-view来自定义绘制视频界面覆盖(因为cover-view不支持嵌套,所以最后采用使用subNVues)
- video在h5需要播放时采用的是html的video标签,但是不支持m3u8格式,最后采用的是dplayer播放器+hls来实现的m3u8播放(也可以通过m3u8格式转换后再进行播放)
我这边的想要的是自定义绘制视频界面,这样做的话就需要将video原有的control相关的功能重写(播放、暂停、快进、快退、全屏、音量等),全屏和非全屏两套样式,通过官方提供的uni.createVideoContext(videoId, this)可以获取到video的实例,然后通过实例调用video的api,比如play、pause等。
最终我这边实现后的播放器支持以下功能:
- app端自定义界面,播放、暂停、快进、快退、全屏、音量、选集、倍速、清晰度切换、手势(快进、后退、亮度调节、音量调节)。
- h5采用的是dplayer播放器+hls支持m3u8格式播放。
- 支持外挂字幕(SRT通过)
下面说下具体的实现和遇到的问题:
1. 配置subNVues子窗体
-
这里注意的一点是path路径是从根目录开始的,不是对应的父窗体的相对路径。
-
文件格式一定是要nvue格式。
配置完之后子窗体就会在对应的页面上显示。
2.配置video播放界面
- 因为我们需要自定义界面所以video的controls需设置为false
-
全屏和非全屏后界面有些许差异,比如全屏后才可以选集等其他功能,那需要设计出两套样式。
通过ref拿到video实例
videoCTX = this.$refs.video
全屏和取消全屏事件
// 全屏/退出全屏fullOrExitScreen() {if (this.fullScreenStatus) {videoCTX.exitFullScreen()} else {videoCTX.requestFullScreen()}},
3. 设置手势操作
手势操作分为向上向下向左向右四个方位
- 向左向右:快进、后退
- 屏幕左侧上下滑动:亮度调节
- 屏幕右侧上下滑动:音量调节
使用touchStart、touchMove、touchend来实现此功能
touchStart时存储首次的坐标点
this.oldTouchs = e.changedTouches[0]
我这边定义的touchType为三种类型,currentTime进度、luminance亮度、volume音量
- 首先先判断是否为上下滑动,如果上下的话就只能是亮度和音量调节,接着获取屏幕的宽度,判断手势移动的x坐标是否大于屏幕宽度的1/2,如果是就为音量调节,否则为亮度调节。
- 如果为进度条的话则先保存当前进度,视频暂停,通过x轴移动的距离计算出当前进度,然后通过video的seek方法跳转到当前进度。
然后再touchEnd设置video进度
- 音量调节也是一样的 算出滑动的距离后通过plus.device.setVolume设置
- 亮度调节也是一样的 算出滑动的距离后通过plus.screen.setBrightness设置
4. 配置字幕
- 如果是VVT格式可以采用video中track标签实现,.vtt 格式文件属于 Web Video Text Tracks Format(WebVTT),是一种基于文本 UTF-8 编码的格式,为 Web 媒体元素提供字幕数据文件。WebVTT 的 MIME 格式是 text/vtt。
- 我这边项目中是用的是SRT格式的,但是video无法解析出来,所以想到的办法是将SRT字幕格式转换成js可以解析的json格式。
下面是SRT原格式
-
通过以下函数将数据转换为数组
createSrtArr(srt) {let arr = srt.split(/\n\n|\r\r|\r\n\r\n/)let result = []arr.forEach(item => {let srtObj = {}let containArr = item.split(/\n/)if (containArr[1]) {//此处正则用于取出00:00:00,213 --> 00:00:00,213这种结构,防止可能存在于此行的x,y坐标对后续处理造成影响var reg = /^(\d{2}):(\d{2}):(\d{2})[\.,](\d{1,3}) --\> (\d{2}):(\d{2}):(\d{2})[\.,](\d{1,3})/glet regResult = reg.exec(containArr[1])if (regResult) {let timeSplit = regResult[0].split(' --> ')srtObj.from = this.getSeconds(timeSplit[0])srtObj.to = this.getSeconds(timeSplit[1])//这里的两个replace主要是将srt中可能携带的font标签转化为易用的span,当时遇到了如果是font标签的话没办法继承父元素的字体大小的问题,题外话:本次字母字体大小采用了媒体查询进行设置srtObj.htmlText = containArr.slice(2).join(`\n`).replace(/\<font/g, '<span').replace(/\<\/font/g,'</span')result.push(srtObj)}}})return result}
转换后的数据格式如下
- 通过currentTime时长和获取到的subtitlesArray字幕数组获取到对应时间的字幕。
getSubtitle(currentTime, arr) {let left = 0;let right = arr.length - 1;while (left <= right) {let mid = Math.floor((left + right) / 2);if (currentTime >= arr[mid].from && currentTime <= arr[mid].to) {return arr[mid].htmlText;} else if (currentTime < arr[mid].from) {right = mid - 1;} else {left = mid + 1;}}return null; // 如果未找到匹配的时间段,则返回null或者其他您认为合适的值}
PS: 这个里有注意事项,因为我这边是需要兼容h5和app端,通过时长获取到的字幕数据有可能携带标签,所以需要富文本展示,h5和app上均需要做处理。
<!-- #ifdef H5 --><view class="subtitles" v-html='subtitles'></view><!-- #endif --><!-- #ifdef APP-PLUS --><rich-text class="subtitles" :nodes="nodes" :style="videoBox"><!-- #endif --></rich-text>
const res = this.getSubtitle(value,this.subtitlesArray) || ''if (this.subtitlesArray.length) {// #ifdef APP-PLUSthis.nodes[0].children[0].text =res// #endif// #ifdef H5this.subtitles = res// #endif}
这里先简单记录下,因为时间有限,代码已上传到线上,有需要的伙伴可以下载下来参考下。传送门