uniApp 项目中,uniapp 提供的的 video 原生视频组件层级太高,难以遮挡;该视频播放器可以被其他元素进行覆盖、遮挡,解决video原生视频播放视频会出现卡顿问题。
代码示例
<template><viewclass="player-wrapper":id="videoWrapperId":parentId="id":randomNum="randomNum":change:randomNum="domVideoPlayer.randomNumChange":viewportProps="viewportProps":change:viewportProps="domVideoPlayer.viewportChange":videoSrc="videoSrc":change:videoSrc="domVideoPlayer.initVideoPlayer":command="eventCommand":change:command="domVideoPlayer.triggerCommand":func="renderFunc":change:func="domVideoPlayer.triggerFunc"/>
</template><script>
export default {props: {src: {type: String,default: '',},autoplay: {type: Boolean,default: false,},loop: {type: Boolean,default: false,},controls: {type: Boolean,default: false,},objectFit: {type: String,default: 'contain',},muted: {type: Boolean,default: false,},playbackRate: {type: Number,default: 1,},isLoading: {type: Boolean,default: false,},poster: {type: String,default: '',},id: {type: String,default: '',},},data() {return {randomNum: Math.floor(Math.random() * 100000000),videoSrc: '',eventCommand: null,renderFunc: {name: null,params: null,},currentTime: 0,duration: 0,playing: false,}},watch: {src: {handler(val) {if (!val) returnsetTimeout(() => {this.videoSrc = val}, 0)},immediate: true,},},computed: {videoWrapperId() {return `video-wrapper-${this.randomNum}`},viewportProps() {return {autoplay: this.autoplay,muted: this.muted,controls: this.controls,loop: this.loop,objectFit: this.objectFit,poster: this.poster,isLoading: this.isLoading,playbackRate: this.playbackRate,}},},methods: {eventEmit({ event, data }) {this.$emit(event, data)},setViewData({ key, value }) {key && this.$set(this, key, value)},resetEventCommand() {this.eventCommand = null},play() {this.eventCommand = 'play'},pause() {this.eventCommand = 'pause'},resetFunc() {this.renderFunc = {name: null,params: null,}},remove(params) {this.renderFunc = {name: 'removeHandler',params,}},fullScreen(params) {this.renderFunc = {name: 'fullScreenHandler',params,}},toSeek(sec, isDelay = false) {this.renderFunc = {name: 'toSeekHandler',params: { sec, isDelay },}},},
}
</script><script module="domVideoPlayer" lang="renderjs">
const PLAYER_ID = 'DOM_VIDEO_PLAYER'
export default {data() {return {num: '',videoEl: null,loadingEl: null,delayFunc: null,renderProps: {}}},computed: {playerId() {return `${PLAYER_ID}_${this.num}`},wrapperId() {return `video-wrapper-${this.num}`}},methods: {isApple() {const ua = navigator.userAgent.toLowerCase()return ua.indexOf('iphone') !== -1 || ua.indexOf('ipad') !== -1},async initVideoPlayer(src) {this.delayFunc = nullawait this.$nextTick()if (!src) returnif (this.videoEl) {if (!this.isApple() && this.loadingEl) {this.loadingEl.style.display = 'block'}this.videoEl.src = srcreturn}const videoEl = document.createElement('video')this.videoEl = videoElthis.listenVideoEvent()const { autoplay, muted, controls, loop, playbackRate, objectFit, poster } = this.renderPropsvideoEl.src = srcvideoEl.autoplay = autoplayvideoEl.controls = controlsvideoEl.loop = loopvideoEl.muted = mutedvideoEl.playbackRate = playbackRatevideoEl.id = this.playerIdvideoEl.setAttribute('preload', 'auto')videoEl.setAttribute('playsinline', true)videoEl.setAttribute('webkit-playsinline', true)videoEl.setAttribute('crossorigin', 'anonymous')videoEl.setAttribute('controlslist', 'nodownload')videoEl.setAttribute('disablePictureInPicture', true)videoEl.style.objectFit = objectFitposter && (videoEl.poster = poster)videoEl.style.width = '100%'videoEl.style.height = '100%'const playerWrapper = document.getElementById(this.wrapperId)playerWrapper.insertBefore(videoEl, playerWrapper.firstChild)this.createLoading()},createLoading() {const { isLoading } = this.renderPropsif (!this.isApple() && isLoading) {const loadingEl = document.createElement('div')this.loadingEl = loadingElloadingEl.className = 'loading-wrapper'loadingEl.style.position = 'absolute'loadingEl.style.top = '0'loadingEl.style.left = '0'loadingEl.style.zIndex = '1'loadingEl.style.width = '100%'loadingEl.style.height = '100%'loadingEl.style.backgroundColor = 'black'document.getElementById(this.wrapperId).appendChild(loadingEl)const animationEl = document.createElement('div')animationEl.className = 'loading'animationEl.style.zIndex = '2'animationEl.style.position = 'absolute'animationEl.style.top = '50%'animationEl.style.left = '50%'animationEl.style.marginTop = '-15px'animationEl.style.marginLeft = '-15px'animationEl.style.width = '30px'animationEl.style.height = '30px'animationEl.style.border = '2px solid #FFF'animationEl.style.borderTopColor = 'rgba(255, 255, 255, 0.2)'animationEl.style.borderRightColor = 'rgba(255, 255, 255, 0.2)'animationEl.style.borderBottomColor = 'rgba(255, 255, 255, 0.2)'animationEl.style.borderRadius = '100%'animationEl.style.animation = 'circle infinite 0.75s linear'loadingEl.appendChild(animationEl)const style = document.createElement('style')const keyframes = `@keyframes circle {0% {transform: rotate(0);}100% {transform: rotate(360deg);}}`style.type = 'text/css'if (style.styleSheet) {style.styleSheet.cssText = keyframes} else {style.appendChild(document.createTextNode(keyframes))}document.head.appendChild(style)}},listenVideoEvent() {const playHandler = () => {this.$ownerInstance.callMethod('eventEmit', { event: 'play' })this.$ownerInstance.callMethod('setViewData', {key: 'playing',value: true})if (this.loadingEl) {this.loadingEl.style.display = 'none'}}this.videoEl.removeEventListener('play', playHandler)this.videoEl.addEventListener('play', playHandler)const pauseHandler = () => {this.$ownerInstance.callMethod('eventEmit', { event: 'pause' })this.$ownerInstance.callMethod('setViewData', {key: 'playing',value: false})}this.videoEl.removeEventListener('pause', pauseHandler)this.videoEl.addEventListener('pause', pauseHandler)const endedHandler = () => {this.$ownerInstance.callMethod('eventEmit', { event: 'ended' })this.$ownerInstance.callMethod('resetEventCommand')}this.videoEl.removeEventListener('ended', endedHandler)this.videoEl.addEventListener('ended', endedHandler)const canPlayHandler = () => {this.$ownerInstance.callMethod('eventEmit', { event: 'canplay' })this.execDelayFunc()}this.videoEl.removeEventListener('canplay', canPlayHandler)this.videoEl.addEventListener('canplay', canPlayHandler)const errorHandler = (e) => {if (this.loadingEl) {this.loadingEl.style.display = 'block'}this.$ownerInstance.callMethod('eventEmit', { event: 'error' })}this.videoEl.removeEventListener('error', errorHandler)this.videoEl.addEventListener('error', errorHandler)const loadedMetadataHandler = () => {this.$ownerInstance.callMethod('eventEmit', { event: 'loadedmetadata' })const duration = this.videoEl.durationthis.$ownerInstance.callMethod('eventEmit', {event: 'durationchange',data: duration})this.$ownerInstance.callMethod('setViewData', {key: 'duration',value: duration})this.loadFirstFrame()}this.videoEl.removeEventListener('loadedmetadata', loadedMetadataHandler)this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler)const timeupdateHandler = (e) => {const currentTime = e.target.currentTimethis.$ownerInstance.callMethod('eventEmit', {event: 'timeupdate',data: currentTime})this.$ownerInstance.callMethod('setViewData', {key: 'currentTime',value: currentTime})}this.videoEl.removeEventListener('timeupdate', timeupdateHandler)this.videoEl.addEventListener('timeupdate', timeupdateHandler)const ratechangeHandler = (e) => {const playbackRate = e.target.playbackRatethis.$ownerInstance.callMethod('eventEmit', {event: 'ratechange',data: playbackRate})}this.videoEl.removeEventListener('ratechange', ratechangeHandler)this.videoEl.addEventListener('ratechange', ratechangeHandler)if (this.isApple()) {const webkitbeginfullscreenHandler = () => {const presentationMode = this.videoEl.webkitPresentationModelet isFullScreen = nullif (presentationMode === 'fullscreen') {isFullScreen = true} else {isFullScreen = false}this.$ownerInstance.callMethod('eventEmit', {event: 'fullscreenchange',data: isFullScreen})}this.videoEl.removeEventListener('webkitpresentationmodechanged', webkitbeginfullscreenHandler)this.videoEl.addEventListener('webkitpresentationmodechanged', webkitbeginfullscreenHandler)} else {const fullscreenchangeHandler = () => {let isFullScreen = nullif (document.fullscreenElement) {isFullScreen = true} else {isFullScreen = false}this.$ownerInstance.callMethod('eventEmit', {event: 'fullscreenchange',data: isFullScreen})}document.removeEventListener('fullscreenchange', fullscreenchangeHandler)document.addEventListener('fullscreenchange', fullscreenchangeHandler)}},loadFirstFrame() {let { autoplay, muted } = this.renderPropsif (this.isApple()) {this.videoEl.play()if (!autoplay) {this.videoEl.pause()}} else {this.videoEl.muted = truesetTimeout(() => {this.videoEl.play()this.videoEl.muted = mutedif (!autoplay) {setTimeout(() => {this.videoEl.pause()}, 100)}}, 10)}},triggerCommand(eventType) {if (eventType) {this.$ownerInstance.callMethod('resetEventCommand')this.videoEl && this.videoEl[eventType]()}},triggerFunc(func) {const { name, params } = func || {}if (name) {this[name](params)this.$ownerInstance.callMethod('resetFunc')}},removeHandler() {if (this.videoEl) {this.videoEl.pause()this.videoEl.src = ''this.$ownerInstance.callMethod('setViewData', {key: 'videoSrc',value: ''})this.videoEl.load()}},fullScreenHandler() {if (this.isApple()) {this.videoEl.webkitEnterFullscreen()} else {this.videoEl.requestFullscreen()}},toSeekHandler({ sec, isDelay }) {const func = () => {if (this.videoEl) {this.videoEl.currentTime = sec}}if (isDelay) {this.delayFunc = func} else {func()}},execDelayFunc() {this.delayFunc && this.delayFunc()this.delayFunc = null},viewportChange(props) {this.renderProps = propsconst { autoplay, muted, controls, loop, playbackRate } = propsif (this.videoEl) {this.videoEl.autoplay = autoplaythis.videoEl.controls = controlsthis.videoEl.loop = loopthis.videoEl.muted = mutedthis.videoEl.playbackRate = playbackRate}},randomNumChange(val) {this.num = val}}
}
</script><style scoped>
.player-wrapper {overflow: hidden;height: 100%;padding: 0;position: relative;
}
</style>
使用示例
<template><view><view style="width: 750rpx"><DomVideoPlayerref="domVideoPlayer"object-fit="contain":controls="controls":autoplay="autoplay":loop="loop":src="src":playback-rate="playbackRate"@play="onPlay"@pause="onPause"@ended="onEnded"@durationchange="onDurationChange"@timeupdate="onTimeUpdate"@ratechange="onRateChange"@fullscreenchange="onFullscreenChange"/></view><!-- video的属性值 --><view class="action-box"><view>播放进度: {{ progress }}</view><view>播放时间: {{ showPlayTime }}</view><view>当前时长: {{ currentTime }}</view><view>总时长: {{ duration }}</view><view>播放倍速: {{ playbackRate }}</view><view>播放控制器: {{ controls }}</view><view>循环播放: {{ loop }}</view><view>自动播放: {{ autoplay }}</view></view><!-- 操作 --><view class="action-box"><h3>事件调用</h3><!-- 单个按钮控制播放/暂停 --><button @tap="doPlaying">单个按钮控制:<text v-if="!playing">播放</text><text v-else>暂停</text></button><!-- 分别控制播放/暂停 --><button @tap="doPlay">播放</button><button @tap="doPause">暂停</button></view><!-- 属性操作 --><view class="action-box"><h3>更改属性</h3><button @tap="switchRate">切换到{{ playbackRate === 1 ? 2 : 1 }}倍速播放</button><button @tap="switchControls">切换视频控制栏:{{ !controls ? '显示' : '隐藏' }}</button></view><!-- 自定义操作 --><view class="action-box"><h3>自定义事件</h3><button @tap="doSeek(-15)">快退15秒</button><button @tap="doSeek(15)">快进15秒</button><button @tap="doFullScreen">全屏播放</button><button @tap="doRemove">移除视频</button><button @tap="doUpdateSrc">更换src</button></view></view>
</template><script setup>
import { ref, computed } from 'vue'
import DomVideoPlayer from './DomVideoPlayer.vue'
const formatSec2Time = (time) => {const min = Math.floor(time / 60)const sec = Math.floor(time % 60)return `${min}:${sec < 10 ? '0' + sec : sec}`
}const src = ref('https://env-00jxt6hwsqjo.normal.cloudstatic.cn/2023%E5%93%81%E7%89%8C%E5%AE%A3%E4%BC%A0%E7%89%87.mp4',
)
const playing = ref(false)
const loop = ref(false)
const controls = ref(true)
const autoplay = ref(false)
const playbackRate = ref(1)
const currentTime = ref(0)
const duration = ref(0)
const domVideoPlayer = ref(null)const progress = computed(() => {const percent = (currentTime.value / duration.value) * 100return percent.toFixed(2) + '%'
})const showPlayTime = computed(() => {const curr = formatSec2Time(currentTime.value)const dur = formatSec2Time(duration.value)return `${curr} / ${dur}`
})const onPlay = () => {console.log('onPlay')playing.value = true
}const onPause = () => {console.log('onPause')playing.value = false
}const onEnded = () => {console.log('onEnded')playing.value = false
}const onDurationChange = (e) => {console.log('onDurationChange', e)duration.value = e
}const onTimeUpdate = (e) => {currentTime.value = e
}const onRateChange = (e) => {console.log('onRateChange', e)playbackRate.value = e
}const onFullscreenChange = (e) => {console.log('onFullScreenChange', e)
}const doPlaying = () => {if (domVideoPlayer.value.playing) {doPause()} else {doPlay()}
}const doPlay = () => {domVideoPlayer.value.play()
}const doPause = () => {domVideoPlayer.value.pause()
}const doSeek = (time) => {time += domVideoPlayer.value.currentTimedomVideoPlayer.value.toSeek(time)
}const doFullScreen = () => {domVideoPlayer.value.fullScreen()
}const doRemove = () => {src.value = ''domVideoPlayer.value.remove()
}const doUpdateSrc = () => {src.value ='https://env-00jxt6hwsqjo.normal.cloudstatic.cn/2023%E5%93%81%E7%89%8C%E5%AE%A3%E4%BC%A0%E7%89%87.mp4'
}const switchRate = () => {playbackRate.value = playbackRate.value === 1 ? 2 : 1
}const switchControls = () => {controls.value = !controls.value
}
</script><style scoped>
.action-box {margin-top: 30rpx;padding: 0 60rpx;
}.action-box button {margin-top: 10rpx;
}
</style>