记录一下在uni-app项目中遇到的一些问题。
一、选择视频并上传,播放
展示:
全屏播放:
1、<video>在非H5端图层太高的问题
安卓APP中,由于<video>是原生组件,层级高于普通前端组件,覆盖其需要使用<cover-view>、<cover-image>,但要在<video>开始结束标签之间。
自定义播放按钮,删除按钮都可以使用这些标签实现。
但展示选择的视频时,弹出选择框(如picker),就无法用这些标签实现层级高于<video>。
实现方式:展示时用view,播放时用<video>全屏播放。
html部分
<!-- 展示视频 -->
<!-- #ifdef APP-PLUS || H5 -->
<view :id="`${item.id}`" class="video">
<image class="video-poster" :src="item.posterUrl"/>
<image v-if="clearable" class="video-clear-btn" src="/static/clear.png" @click="deleteFile(item, index)"/>
<image class="video-play-btn" src="/static/play.png" @click="toPlayVideo(item.url)"/>
</view>
<!-- #endif -->
<!-- 全屏播放视频 -->
<video
class="full-play-video"
id="fullScreenVideo"
v-if="videoUrl"
:src="videoUrl"
object-fit="contain"
@fullscreenchange="fullscreenchange"
@error="onVideoError"
/>
js部分
mounted() {
// #ifdef APP-PLUS || H5
this.videoContext = uni.createVideoContext('fullScreenVideo')
// #endif
// #ifdef MP-WEIXIN
this.videoContext = uni.createVideoContext('fullScreenVideo', this)
// #endif
},
beforeDestroy() {
clearTimeout(this.timer)
this.timer = null
},
methods: {
toPlayVideo(url) {
this.videoUrl = url //.split('?')[0]
this.timer = setTimeout(() =>{
// #ifdef APP-PLUS || H5
this.videoContext = uni.createVideoContext('fullScreenVideo')
// #endif
// #ifdef MP-WEIXIN
this.videoContext = uni.createVideoContext('fullScreenVideo', this)
// #endif
// 微信开发工具不生效,要真机测试
this.videoContext.requestFullScreen({
direction: 0
})
this.videoContext.play()
}, 100)
},
fullscreenchange(e) {
if(!e.detail.fullScreen) {
this.videoContext.stop()
this.videoUrl = false
}
},
onVideoError(err) {
console.log('播放视频失败:', err)
},
deleteFile(file, index) {
uni.showModal({
title: '提示',
content: '您确定要删除此项吗?',
success: res => {
if (res.confirm) {
this.$emit('delete-item', { file, index })
}
}
})
},
}
2、获取视频的封面
展示视频的view里面,要用一个图片作为视频的封面,可以用视频的首帧。
获取视频的封面,用到renderjs,通过canvas实现。
html部分
<!-- #ifdef APP-VUE || H5 -->
<view :prop="videos" :change:prop="rdVideo.getAllPoster" style="opacity: 0;"></view>
<!-- #endif -->
js部分
<!-- #ifdef APP-VUE || H5 -->
<script module="rdVideo" lang="renderjs">
import mixinVideo from './video-poster.js'
// 监听videos, 有更改调用rdVideo.getAllPoster方法获取视频封面
export default {
mixins: [mixinVideo]
}
</script>
<script>
export default {
data() {
return {
videos: []
}
}
methods: {
// 获取视频封面后,调用此方法更新视频数据
updateVideos(newVal) {
this.videos = newVal
},
}
}
</script>
<!-- #endif -->
video-poster.js
const mixinVideo = {
methods: {
getVideoPoster(url) {
return new Promise(function (resolve, reject) {
let video = document.createElement('video')
video.setAttribute('crossOrigin', 'anonymous') // 处理跨域,H5需后台支持,请求的视频资源响应投标需有Access-Control-Allow-Origin
video.setAttribute('src', url)
video.setAttribute('width', 400)
video.setAttribute('height', 400)
video.setAttribute('preload', 'auto')
// uni.chooseVideo选择视频,当选用手机拍摄的视频时,地址是相对地址,如 _doc/uniapp_temp_1650594368317/camera/1650594390147.mp4
// 可播放,但是loadeddata一直不执行,会触发error事件,视频加载失败
// 应先转换成本地地址
video.addEventListener('loadeddata', function () {
console.log('视频第一帧加载完')
let canvas = document.createElement('canvas')
let width = video.width // canvas的尺寸和图片一样
let height = video.height
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(video, 0, 0, width, height) // 绘制canvas
const dataURL = canvas.toDataURL('image/jpeg') // 转换为base64
console.log('getVideoPoster-dataURL', dataURL.slice(0, 16))
resolve(dataURL)
})
video.addEventListener('error', err => {
console.log('视频加载失败', err)
reject(err)
})
})
},
async getAllPoster(newVal, oldVal, owner, instance) {
console.log('执行getAllPoster')
// renderjs中,监听的属性videos是一个数组,存放的是选取的视频信息
// 删除,或updateVideos更新后(长度一样)
if(newVal.length <= oldVal.length) return
// 有默认值或添加时
const newList = []
for(const item of newVal) {
// 已获取视频封面的不再重复获取
if(item.posterUrl !== undefined) {
newList.push({ ...item })
continue
}
try {
let url = item.url
// 拍摄视频:_doc/uniapp_temp_1650594368317/camera/1650594390147.mp4
// 网络视频:https://
// 本地视频:file://
if(!item.url.includes('file://') && !item.url.includes('https://')) {
// 将本地URL路径转换成平台绝对路径
// 如输入url为“_doc/a.png”:
// Android平台转换后的路径为“/storage/sdcard0/Android/data/io.dcloud.HBuilder/.HBuilder/apps/HBuilder/doc/a.png”;
// #ifdef APP-VUE
url = 'file://' + plus.io.convertLocalFileSystemURL(item.url)
// #endif
}
const dataUrl = await this.getVideoPoster(url)
newList.push({ ...item, posterUrl: dataUrl })
} catch (err) {
newList.push({ ...item })
}
}
this.$ownerInstance.callMethod('updateVideos', newList)
}
}
}
export default mixinVideo
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。