当前位置: 首页 > news >正文

uniapp 使用renderjs 封装 video-player 视频播放器, html5视频播放器-解决视频层级、覆盖、播放卡顿

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: '',// 父组件向子组件传递的事件指令(video的原生事件)eventCommand: null,// 父组件传递过来的,对 renderjs 层的函数执行(对视频控制的自定义事件)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}`},// 聚合视图层的所有数据变化,传给renderjs的渲染层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)},// 修改view视图层的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 = videoEl// 开始监听视频相关事件this.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.playerId// videoEl.setAttribute('x5-video-player-type', 'h5')videoEl.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%'// 插入视频元素// document.getElementById(this.wrapperId).appendChild(videoEl)const playerWrapper = document.getElementById(this.wrapperId)playerWrapper.insertBefore(videoEl, playerWrapper.firstChild)// 插入loading 元素(遮挡安卓的默认加载过程中的黑色播放按钮)this.createLoading()},// 创建 loadingcreateLoading() {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)// 创建 loading 动画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)// 创建 loading 动画所需的 keyframesconst 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)// loadedmetadata 事件监听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 {// optimize: timeout 延迟调用是为了规避控制台的`https://goo.gl/LdLk22`这个报错/*** 原因:chromium 内核中,谷歌协议规定,视频不允许在非静音状态下进行自动播放* 解决:在自动播放时,先将视频静音,然后延迟调用 play 方法,播放视频* 说明:iOS 的 Safari 内核不会有这个,仅在 Android 设备出现,即使有这个报错也不影响的,所以不介意控制台报错的话是可以删掉这个 timeout 的*/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'
// 将xx秒转为 xx:xx 分秒格式
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>
http://www.dtcms.com/a/581192.html

相关文章:

  • 基于深度对比学习的分析化学结构注释TOP1匹配率提升研究
  • MFA MACOS 安装流程
  • Ubuntu 上部署 Microsoft SQL Server 详细教程
  • 网站上面怎么做链接微信网站合同
  • 关于网站建设与维护的参考文献phpcms 恢复网站
  • 圆角边框+阴影
  • Android14 init.environ.rc详解
  • 网段并网,打通网络
  • VBA之Word应用第四章第四节:段落集合Paragraphs对象的方法(二)
  • 深耕蓝牙物联网十年:北京桂花网 2015-2025 发展大事件全景
  • MCU微控制器,N32H47x高性能MCU机器人关节控制方案
  • 年销 1.3 亿的 AI 商业操盘手陈灏陈厂长确认登陆创客匠人万人峰会
  • 用户体验 网站重庆便民服务网站APP
  • 教育类网站建站建筑工程网格化管理的目的和意义
  • Flink Source源码解析
  • 春招准备之MyBatis框架篇
  • 华为交换机上配置基于 IP 地址的 ACL
  • 【C++练习】31. C++计算最大公约数(GCD)
  • 从普通屏到 明基RD320U:一台显示器如何提升我的编码效率?
  • 从 ACID 到 MVCC,MySQL 事务与隔离级别超详解
  • 植物生理研究的精准量化:光合作用测定仪的应用与前景
  • Win电脑文字转语音,不限使用次数和字数!可将文字文本内容转换为朗读配音的音频文件!多功能语音合成,内置多语种、多角色语音配音模型,支持普通话标准发音和方言!
  • 网页设计与网站建设作业答案淘宝宝贝关键词排名查询工具
  • 2025年CSP-X复赛真题及题解(山东):T2IOI串
  • 基于网易CodeWave智能开发平台构建宝可梦图鉴
  • Ubuntu2204降内核版本
  • 数据在网络上的转发过程
  • 跨地域传文件太麻烦?Nginx+cpolar 让本地服务直接公网访问
  • ASP.NET MVC 数据验证进阶:用 IValidatableObject 实现自定义验证逻辑 引言:为什么需要 “自定义验证”?
  • 网站流量报表摄像头怎么做直播网站