diff --git a/components/DomVideoPlayer/DomVideoPlayer.vue b/components/DomVideoPlayer/DomVideoPlayer.vue index 5e9228a..b0a33c3 100644 --- a/components/DomVideoPlayer/DomVideoPlayer.vue +++ b/components/DomVideoPlayer/DomVideoPlayer.vue @@ -1,20 +1,10 @@ @@ -173,7 +171,11 @@ export default { loadingEl: null, // 延迟生效的函数 delayFunc: null, - renderProps: {} + renderProps: {}, + // 是否主动暂停(通过父组件调用 pause() 方法) + intentionalPause: false, + // 上次自动恢复的时间戳,用于冷却控制 + lastAutoRecoverTime: 0 } }, computed: { @@ -215,7 +217,9 @@ export default { videoEl.muted = muted videoEl.playbackRate = playbackRate videoEl.id = this.playerId - // videoEl.setAttribute('x5-video-player-type', 'h5') + // 开启 X5 内核的 H5 同层渲染,防止原生视频组件层级过高在拖拽时脱离或卡死 + videoEl.setAttribute('x5-video-player-type', 'h5') + videoEl.setAttribute('x5-video-player-fullscreen', 'false') videoEl.setAttribute('preload', 'auto') videoEl.setAttribute('playsinline', true) videoEl.setAttribute('webkit-playsinline', true) @@ -315,6 +319,18 @@ export default { key: 'playing', value: false }) + + // 自动恢复机制:仅在非主动暂停时触发,并带有 2 秒冷却期防止循环拖动 + const { loop, autoplay } = this.renderProps + const now = Date.now() + if (loop && autoplay && this.videoEl && !this.intentionalPause && (now - this.lastAutoRecoverTime > 2000)) { + this.lastAutoRecoverTime = now + setTimeout(() => { + if (this.videoEl && this.videoEl.paused && !this.intentionalPause) { + this.videoEl.play().catch(() => {}) + } + }, 200) + } } this.videoEl.removeEventListener('pause', pauseHandler) this.videoEl.addEventListener('pause', pauseHandler) @@ -367,7 +383,13 @@ export default { this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler) // 播放进度监听 + let lastTime = 0; const timeupdateHandler = (e) => { + const now = Date.now(); + // 增加节流阀,每 500ms 触发一次通信,极大地减少 renderjs 和逻辑层的通讯开销 + if (now - lastTime < 500) return; + lastTime = now; + const currentTime = e.target.currentTime this.$ownerInstance.callMethod('eventEmit', { event: 'timeupdate', @@ -457,6 +479,12 @@ export default { }, triggerCommand(eventType) { if (eventType) { + // 标记主动暂停/播放状态,用于自动恢复机制的判断 + if (eventType === 'pause') { + this.intentionalPause = true + } else if (eventType === 'play') { + this.intentionalPause = false + } this.$ownerInstance.callMethod('resetEventCommand') this.videoEl && this.videoEl[eventType]() } @@ -519,6 +547,16 @@ export default { randomNumChange(val) { this.num = val } + }, + // renderjs 层直接清理原生 video 元素, + // 确保组件被 v-if 销毁时视频立即停止播放和声音输出 + beforeUnmount() { + if (this.videoEl) { + this.videoEl.pause() + this.videoEl.src = '' + this.videoEl.load() + this.videoEl = null + } } } @@ -529,5 +567,7 @@ export default { height: 100%; padding: 0; position: relative; + /* 开启硬件加速,将视频提升为独立合成层 */ + transform: translateZ(0); } diff --git a/pages/index/index.nvue b/pages/index/index.nvue index 7766a69..9352ad8 100644 --- a/pages/index/index.nvue +++ b/pages/index/index.nvue @@ -276,7 +276,7 @@ const otherList = [{ path: "/pages/other/video-group-chat/video-group-chat" }, { - icon: "/static/image/index/qita/shipinqunliao.png", + icon: "/static/image/index/qita/danliao.png", name: "视频单聊", path: "/pages/other/video-chat/video-chat" }, diff --git a/pages/other/video-chat/video-chat.vue b/pages/other/video-chat/video-chat.vue index f3d968c..b0c3bbc 100644 --- a/pages/other/video-chat/video-chat.vue +++ b/pages/other/video-chat/video-chat.vue @@ -6,20 +6,136 @@ - - + + + + + + + + + + + + + + {{ videoData.chat.other.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ videoData.micOn ? '麦克风已开' : '麦克风已关' }} + + + + + + + + {{ videoData.cameraOn ? '摄像头已开' : '摄像头已关' }} + + + + + + 背景模糊 + + + + + + 翻转 + + + + + + + + + + + + + + + + + + + + class="bg-image" mode="aspectFill" autoplay loop :muted="data.isSwapped ? !videoData.speakerOn : true" + objectFit="cover" :controls="false" :show-play-btn="false">