微信视频通话
This commit is contained in:
parent
82e922eab0
commit
7aec27d81b
|
|
@ -1,20 +1,10 @@
|
||||||
<!-- eslint-disable -->
|
<!-- eslint-disable -->
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view class="player-wrapper" :id="videoWrapperId" :parentId="id" :randomNum="randomNum"
|
||||||
class="player-wrapper"
|
:change:randomNum="domVideoPlayer.randomNumChange" :viewportProps="viewportProps"
|
||||||
:id="videoWrapperId"
|
:change:viewportProps="domVideoPlayer.viewportChange" :videoSrc="videoSrc"
|
||||||
:parentId="id"
|
:change:videoSrc="domVideoPlayer.initVideoPlayer" :command="eventCommand"
|
||||||
:randomNum="randomNum"
|
:change:command="domVideoPlayer.triggerCommand" :func="renderFunc" :change:func="domVideoPlayer.triggerFunc" />
|
||||||
: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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -158,6 +148,14 @@ export default {
|
||||||
params: { sec, isDelay }
|
params: { sec, isDelay }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 组件销毁前自动清理 renderjs 层的原生 video 元素,
|
||||||
|
// 防止 v-if 移除组件后视频仍在后台播放声音("僵尸视频"问题)
|
||||||
|
beforeUnmount() {
|
||||||
|
this.renderFunc = {
|
||||||
|
name: 'removeHandler',
|
||||||
|
params: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -173,7 +171,11 @@ export default {
|
||||||
loadingEl: null,
|
loadingEl: null,
|
||||||
// 延迟生效的函数
|
// 延迟生效的函数
|
||||||
delayFunc: null,
|
delayFunc: null,
|
||||||
renderProps: {}
|
renderProps: {},
|
||||||
|
// 是否主动暂停(通过父组件调用 pause() 方法)
|
||||||
|
intentionalPause: false,
|
||||||
|
// 上次自动恢复的时间戳,用于冷却控制
|
||||||
|
lastAutoRecoverTime: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -215,7 +217,9 @@ export default {
|
||||||
videoEl.muted = muted
|
videoEl.muted = muted
|
||||||
videoEl.playbackRate = playbackRate
|
videoEl.playbackRate = playbackRate
|
||||||
videoEl.id = this.playerId
|
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('preload', 'auto')
|
||||||
videoEl.setAttribute('playsinline', true)
|
videoEl.setAttribute('playsinline', true)
|
||||||
videoEl.setAttribute('webkit-playsinline', true)
|
videoEl.setAttribute('webkit-playsinline', true)
|
||||||
|
|
@ -315,6 +319,18 @@ export default {
|
||||||
key: 'playing',
|
key: 'playing',
|
||||||
value: false
|
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.removeEventListener('pause', pauseHandler)
|
||||||
this.videoEl.addEventListener('pause', pauseHandler)
|
this.videoEl.addEventListener('pause', pauseHandler)
|
||||||
|
|
@ -367,7 +383,13 @@ export default {
|
||||||
this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler)
|
this.videoEl.addEventListener('loadedmetadata', loadedMetadataHandler)
|
||||||
|
|
||||||
// 播放进度监听
|
// 播放进度监听
|
||||||
|
let lastTime = 0;
|
||||||
const timeupdateHandler = (e) => {
|
const timeupdateHandler = (e) => {
|
||||||
|
const now = Date.now();
|
||||||
|
// 增加节流阀,每 500ms 触发一次通信,极大地减少 renderjs 和逻辑层的通讯开销
|
||||||
|
if (now - lastTime < 500) return;
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
const currentTime = e.target.currentTime
|
const currentTime = e.target.currentTime
|
||||||
this.$ownerInstance.callMethod('eventEmit', {
|
this.$ownerInstance.callMethod('eventEmit', {
|
||||||
event: 'timeupdate',
|
event: 'timeupdate',
|
||||||
|
|
@ -457,6 +479,12 @@ export default {
|
||||||
},
|
},
|
||||||
triggerCommand(eventType) {
|
triggerCommand(eventType) {
|
||||||
if (eventType) {
|
if (eventType) {
|
||||||
|
// 标记主动暂停/播放状态,用于自动恢复机制的判断
|
||||||
|
if (eventType === 'pause') {
|
||||||
|
this.intentionalPause = true
|
||||||
|
} else if (eventType === 'play') {
|
||||||
|
this.intentionalPause = false
|
||||||
|
}
|
||||||
this.$ownerInstance.callMethod('resetEventCommand')
|
this.$ownerInstance.callMethod('resetEventCommand')
|
||||||
this.videoEl && this.videoEl[eventType]()
|
this.videoEl && this.videoEl[eventType]()
|
||||||
}
|
}
|
||||||
|
|
@ -519,6 +547,16 @@ export default {
|
||||||
randomNumChange(val) {
|
randomNumChange(val) {
|
||||||
this.num = val
|
this.num = val
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// renderjs 层直接清理原生 video 元素,
|
||||||
|
// 确保组件被 v-if 销毁时视频立即停止播放和声音输出
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.videoEl) {
|
||||||
|
this.videoEl.pause()
|
||||||
|
this.videoEl.src = ''
|
||||||
|
this.videoEl.load()
|
||||||
|
this.videoEl = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -529,5 +567,7 @@ export default {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* 开启硬件加速,将视频提升为独立合成层 */
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,7 @@ const otherList = [{
|
||||||
path: "/pages/other/video-group-chat/video-group-chat"
|
path: "/pages/other/video-group-chat/video-group-chat"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "/static/image/index/qita/shipinqunliao.png",
|
icon: "/static/image/index/qita/danliao.png",
|
||||||
name: "视频单聊",
|
name: "视频单聊",
|
||||||
path: "/pages/other/video-chat/video-chat"
|
path: "/pages/other/video-chat/video-chat"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,136 @@
|
||||||
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
|
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
|
||||||
</liu-drag-button>
|
</liu-drag-button>
|
||||||
</view>
|
</view>
|
||||||
<view class="container">
|
<!-- 未接通内容 -->
|
||||||
<view style="height: 100vh;position: fixed;width: 100vw;object-fit: cover;"
|
<view v-if="!isAnswered" class="container">
|
||||||
:style="{ background: !(data.isSwapped ? videoData.chat.other.isShowVideo : videoData.cameraOn) ? ('url(' + (data.isSwapped ? videoData.chat.other.avatar : videoData.chat.me.avatar) + ') no-repeat center/cover') : 'transparent' }">
|
<view style="height: 100vh;position: fixed;width: 100vw;object-fit: cover;overflow: hidden;">
|
||||||
|
<image v-if="!videoData.cameraOn" :src="videoData.chat.other.avatar" mode="aspectFill"
|
||||||
|
style="position: absolute; width: 100%; height: 100%; filter: blur(20px); transform: scale(1.2);">
|
||||||
|
</image>
|
||||||
|
<DomVideoPlayer v-if="videoData.cameraOn" ref="mainVideoRef"
|
||||||
|
style="position: absolute; width: 100vw;height: 100vh;object-fit: cover;display: block;"
|
||||||
|
:src="getVideoUrl(videoData.chat.me.videoUrl)" class="bg-image" mode="aspectFill" autoplay loop
|
||||||
|
objectFit="cover" :controls="false" :show-play-btn="false" :muted="true">
|
||||||
|
</DomVideoPlayer>
|
||||||
|
<view v-if="!videoData.cameraOn"
|
||||||
|
style="position: absolute; background: rgba(0,0,0,0.8); width: 100%; height: 100%;">
|
||||||
|
</view>
|
||||||
|
<view class="top-view">
|
||||||
|
|
||||||
|
<image style="height: 136rpx;width:136rpx;object-fit: cover;" :src="videoData.chat.other.avatar"
|
||||||
|
mode="aspectFill">
|
||||||
|
</image>
|
||||||
|
<text class="nickname">{{ videoData.chat.other.name }}</text>
|
||||||
|
<view class="dot-view">
|
||||||
|
<text class="dot" :style="{ animationDelay: i === 1 ? '0s' : i === 2 ? '-1s' : '-0.5s' }"
|
||||||
|
v-for="i in 3" :key="i"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 导航栏 placeholder -->
|
||||||
|
<NavBar bgColor="transparent" tipLayerType="video-group-chat-tip" isTipLayer tipLayerText="修改聊天信息"
|
||||||
|
@button-click="util.clickTitlePopupButton" :buttonGroup="buttonGroup">
|
||||||
|
|
||||||
|
<!-- 通过作用域插槽接管按钮渲染 -->
|
||||||
|
<template #button="{ button }">
|
||||||
|
<!-- 如果是开关按钮,拦截点击事件冒泡,防止触发顶层关闭弹窗,并避免两次触发 click -->
|
||||||
|
<view v-if="button.isSwitch" @click.stop
|
||||||
|
style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">
|
||||||
|
<text>{{ button.name }}</text>
|
||||||
|
<switch :checked="button.value" @change="button.click"
|
||||||
|
style="transform: scale(0.7); margin-left: 10rpx;"></switch>
|
||||||
|
</view>
|
||||||
|
<!-- 普通按钮,不拦截冒泡,直接走外层 button-box 的 click -->
|
||||||
|
<view v-else
|
||||||
|
style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">
|
||||||
|
<text>{{ button.name }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="nav-content">
|
||||||
|
<view class="left">
|
||||||
|
<image class="icon" style="width: 152rpx;height: 68rpx;"
|
||||||
|
src="/static/image/other/video-call/hulue.png">
|
||||||
|
</image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</NavBar>
|
||||||
|
|
||||||
|
<!-- 底部控制栏 -->
|
||||||
|
<view class="control-bar" style="padding-left: 48rpx;padding-right: 48rpx;">
|
||||||
|
<view class="control-buttons" style="display: flex;justify-content: space-between;">
|
||||||
|
<!-- 麦克风 -->
|
||||||
|
<view class="control-item">
|
||||||
|
<image class="control-btn" :class="{ active: videoData.micOn }" @click="changeInfo('micOn')"
|
||||||
|
:src="videoData.micOn ? '/static/image/other/video-call/mic-on.png' : '/static/image/other/video-call/mic-off.png'">
|
||||||
|
</image>
|
||||||
|
<!-- <view class="" :class="{ active: videoData.micOn }" >
|
||||||
|
|
||||||
|
</view> -->
|
||||||
|
<text class="control-label">{{ videoData.micOn ? '麦克风已开' : '麦克风已关' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 摄像头 -->
|
||||||
|
<view class="control-item">
|
||||||
|
<image class="control-btn" :class="{ active: videoData.cameraOn }" @click="changeInfo('cameraOn')"
|
||||||
|
:src="videoData.cameraOn ? '/static/image/other/video-call/camera-on.png' : '/static/image/other/video-call/camera-off.png'">
|
||||||
|
</image>
|
||||||
|
<!-- <view class="control-btn" :class="{ active: videoData.cameraOn }" @click="changeInfo('cameraOn')">
|
||||||
|
|
||||||
|
</view> -->
|
||||||
|
<text class="control-label">{{ videoData.cameraOn ? '摄像头已开' : '摄像头已关' }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 背景模糊 -->
|
||||||
|
<view class="control-item">
|
||||||
|
<image class="control-btn" style="background: #00000080;"
|
||||||
|
src="/static/image/other/video-call/xvnibeijing.png">
|
||||||
|
</image>
|
||||||
|
<text class="control-label">背景模糊</text>
|
||||||
|
</view>
|
||||||
|
<!-- 旋转摄像头 -->
|
||||||
|
<view class="control-item">
|
||||||
|
<image class="control-btn" style="background: #00000080;"
|
||||||
|
src="/static/image/other/video-call/xuanzhuan.png">
|
||||||
|
</image>
|
||||||
|
<text class="control-label">翻转</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="control-buttons" style="margin-bottom: 0;display: flex;justify-content: space-between;">
|
||||||
|
|
||||||
|
<!-- 挂断按钮 -->
|
||||||
|
<image class="hangup-btn" style="margin: 0;" @click="hangup"
|
||||||
|
src="/static/image/other/video-call/hangup.png">
|
||||||
|
</image>
|
||||||
|
|
||||||
|
<image class="hangup-btn" style="margin: 0; background: #00000080;"
|
||||||
|
src="/static/image/other/video-call/jieting.png" @click="answerCall">
|
||||||
|
</image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 接通电话后 -->
|
||||||
|
<view v-else class="container">
|
||||||
|
<view style="height: 100vh;position: fixed;width: 100vw;object-fit: cover;overflow: hidden;">
|
||||||
|
<image v-if="!(data.isSwapped ? videoData.chat.other.isShowVideo : videoData.cameraOn)"
|
||||||
|
:src="data.isSwapped ? videoData.chat.other.avatar : videoData.chat.me.avatar" mode="aspectFill"
|
||||||
|
style="position: absolute; width: 100%; height: 100%; filter: blur(20px); transform: scale(1.2);">
|
||||||
|
</image>
|
||||||
<DomVideoPlayer ref="mainVideoRef"
|
<DomVideoPlayer ref="mainVideoRef"
|
||||||
v-if="data.isSwapped ? videoData.chat.other.isShowVideo : videoData.cameraOn"
|
v-if="data.isSwapped ? videoData.chat.other.isShowVideo : videoData.cameraOn"
|
||||||
style="width: 100vw;height: 100vh;object-fit: cover;display: block;"
|
style="position: absolute; width: 100vw;height: 100vh;object-fit: cover;display: block;"
|
||||||
:src="data.isSwapped ? getVideoUrl(videoData.chat.other.videoUrl) : getVideoUrl(videoData.chat.me.videoUrl)"
|
:src="data.isSwapped ? getVideoUrl(videoData.chat.other.videoUrl) : getVideoUrl(videoData.chat.me.videoUrl)"
|
||||||
class="bg-image" mode="aspectFill" autoplay loop muted objectFit="cover" :controls="false"
|
class="bg-image" mode="aspectFill" autoplay loop :muted="data.isSwapped ? !videoData.speakerOn : true"
|
||||||
:show-play-btn="false">
|
objectFit="cover" :controls="false" :show-play-btn="false">
|
||||||
</DomVideoPlayer>
|
</DomVideoPlayer>
|
||||||
|
|
||||||
<template v-if="!data.isSwapped ? !videoData.cameraOn : !videoData.chat.other.isShowVideo">
|
<template v-if="!data.isSwapped ? !videoData.cameraOn : !videoData.chat.other.isShowVideo">
|
||||||
<view
|
<view style="position: absolute; background: rgba(0,0,0,0.8); width: 100%; height: 100%;">
|
||||||
style="background: rgba(0,0,0,0.8);width: 100%;height: 100%;border-radius: 20rpx;backdrop-filter: blur(20px);">
|
|
||||||
</view>
|
</view>
|
||||||
<image class="avatar-1" style="height: 136rpx;width:136rpx;object-fit: cover;border-radius: 12rpx;"
|
<image class="avatar-1" style="height: 136rpx;width:136rpx;object-fit: cover;border-radius: 12rpx;"
|
||||||
:src="!data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar" mode="aspectFill">
|
:src="!data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar" mode="aspectFill">
|
||||||
|
|
@ -71,24 +187,28 @@
|
||||||
</NavBar>
|
</NavBar>
|
||||||
|
|
||||||
<!-- 视频画面区域 -->
|
<!-- 视频画面区域 -->
|
||||||
|
<!-- :style="{ background: !(data.isSwapped ? videoData.cameraOn : videoData.chat.other.isShowVideo) ? 'url(' + (data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar) + ') no-repeat center/cover' : 'transparent' }" -->
|
||||||
<movable-area class="video-container">
|
<movable-area class="video-container">
|
||||||
<movable-view class="bg-2-movable"
|
<movable-view class="bg-2-movable" direction="all" :x="data.bg2X" :y="data.bg2Y" :animation="true"
|
||||||
:style="{ background: !(data.isSwapped ? videoData.cameraOn : videoData.chat.other.isShowVideo) ? 'url(' + (data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar) + ') no-repeat center/cover' : 'transparent' }"
|
@change="onBg2Change" @touchend="onBg2TouchEnd" @click="swapVideo"
|
||||||
direction="all" :x="data.bg2X" :y="data.bg2Y" :animation="true" @change="onBg2Change"
|
style="height: 408rpx; width: 188rpx; z-index: 10;border-radius: 20rpx;overflow: hidden;">
|
||||||
@touchend="onBg2TouchEnd" @click="swapVideo"
|
|
||||||
style="height: 408rpx; width: 188rpx; z-index: 10;border-radius: 20rpx;">
|
<image v-if="!(data.isSwapped ? videoData.cameraOn : videoData.chat.other.isShowVideo)"
|
||||||
|
:src="data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar" mode="aspectFill"
|
||||||
|
style="position: absolute; width: 100%; height: 100%; filter: blur(15px); transform: scale(1.2);">
|
||||||
|
</image>
|
||||||
<DomVideoPlayer ref="subVideoRef"
|
<DomVideoPlayer ref="subVideoRef"
|
||||||
v-if="data.isSwapped ? videoData.cameraOn : videoData.chat.other.isShowVideo" class="bg-2"
|
v-if="data.isSwapped ? videoData.cameraOn : videoData.chat.other.isShowVideo" class="bg-2"
|
||||||
style="height: 100%;width: 100%;object-fit: cover;border-radius: 20rpx;display: block;"
|
style="height: 100%;width: 100%;object-fit: cover;border-radius: 20rpx;display: block;"
|
||||||
:src="data.isSwapped ? getVideoUrl(videoData.chat.me.videoUrl) : getVideoUrl(videoData.chat.other.videoUrl)"
|
:src="data.isSwapped ? getVideoUrl(videoData.chat.me.videoUrl) : getVideoUrl(videoData.chat.other.videoUrl)"
|
||||||
mode="aspectFill" autoplay loop muted objectFit="cover" :controls="false" :show-play-btn="false">
|
mode="aspectFill" autoplay loop :muted="data.isSwapped ? true : !videoData.speakerOn"
|
||||||
|
objectFit="cover" :controls="false" :show-play-btn="false">
|
||||||
</DomVideoPlayer>
|
</DomVideoPlayer>
|
||||||
|
|
||||||
<template v-if="data.isSwapped ? !videoData.cameraOn : !videoData.chat.other.isShowVideo">
|
<template v-if="data.isSwapped ? !videoData.cameraOn : !videoData.chat.other.isShowVideo">
|
||||||
<view
|
<view class="mask" style="position: absolute;">
|
||||||
style="background: rgba(0,0,0,0.8);width: 100%;height: 100%;border-radius: 20rpx;backdrop-filter: blur(10px);">
|
|
||||||
</view>
|
</view>
|
||||||
<image class="avatar-2" style="height: 92rpx;width:92rpx;object-fit: cover;border-radius: 12rpx;"
|
<image class="avatar-2" style="height: 92rpx;width:92rpx;object-fit: cover;"
|
||||||
:src="data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar"
|
:src="data.isSwapped ? videoData.chat.me.avatar : videoData.chat.other.avatar"
|
||||||
mode="aspectFill">
|
mode="aspectFill">
|
||||||
</image>
|
</image>
|
||||||
|
|
@ -172,13 +292,22 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 通话信息编辑弹窗 -->
|
<!-- 通话信息编辑弹窗 -->
|
||||||
<view v-if="data.showCallInfoPopup" class="popup-overlay" @click="data.showCallInfoPopup = false">
|
<view v-if="data.showCallInfoPopup" class="popup-overlay" @click="closeCallInfoPopup">
|
||||||
<view class="popup-content" style="width: 600rpx;" @click.stop>
|
<view class="popup-content" style="width: 600rpx;" @click.stop>
|
||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">编辑通话信息</text>
|
<text class="popup-title">编辑通话信息</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="popup-body" style="padding: 20rpx 40rpx; max-height: 60vh; overflow-y: auto;">
|
<view class="popup-body" style="padding: 20rpx 40rpx; max-height: 60vh; overflow-y: auto;">
|
||||||
|
|
||||||
|
<view style="margin-bottom: 30rpx;">
|
||||||
|
<view style="font-size: 28rpx; color: #333; margin-bottom: 10rpx; font-weight: bold;">对方昵称</view>
|
||||||
|
<view style="display: flex; align-items: center; justify-content: space-between;">
|
||||||
|
<input
|
||||||
|
style="flex: 1; border: 1px solid #eee; padding: 10rpx 20rpx; border-radius: 8rpx; font-size: 28rpx;height: 40px;"
|
||||||
|
v-model="videoData.chat.other.name" placeholder="请输入对方昵称" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view style="margin-bottom: 30rpx;">
|
<view style="margin-bottom: 30rpx;">
|
||||||
<view style="font-size: 28rpx; color: #333; margin-bottom: 10rpx; font-weight: bold;">我方通话头像</view>
|
<view style="font-size: 28rpx; color: #333; margin-bottom: 10rpx; font-weight: bold;">我方通话头像</view>
|
||||||
<view style="display: flex; align-items: center; justify-content: space-between;">
|
<view style="display: flex; align-items: center; justify-content: space-between;">
|
||||||
|
|
@ -224,7 +353,7 @@
|
||||||
</view>
|
</view>
|
||||||
<view class="popup-footer">
|
<view class="popup-footer">
|
||||||
<button class="btn-save" style="width: 100%; border-radius: 0 0 16rpx 16rpx;"
|
<button class="btn-save" style="width: 100%; border-radius: 0 0 16rpx 16rpx;"
|
||||||
@click="data.showCallInfoPopup = false">完成</button>
|
@click="closeCallInfoPopup">完成</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -304,7 +433,7 @@ const data = reactive({
|
||||||
micOn: true,
|
micOn: true,
|
||||||
speakerOn: true,
|
speakerOn: true,
|
||||||
cameraOn: true,
|
cameraOn: true,
|
||||||
timeText: '125:22',
|
timeText: '25:22',
|
||||||
chat: {
|
chat: {
|
||||||
me: {
|
me: {
|
||||||
videoUrl: "",
|
videoUrl: "",
|
||||||
|
|
@ -314,6 +443,7 @@ const data = reactive({
|
||||||
other: {
|
other: {
|
||||||
videoUrl: "",
|
videoUrl: "",
|
||||||
imagePath: "",
|
imagePath: "",
|
||||||
|
name: "大宝贝",
|
||||||
avatar: "/static/image/shopping/pdd/avatars/avatars2.jpg",
|
avatar: "/static/image/shopping/pdd/avatars/avatars2.jpg",
|
||||||
isShowVideo: true,
|
isShowVideo: true,
|
||||||
}
|
}
|
||||||
|
|
@ -344,11 +474,18 @@ const data = reactive({
|
||||||
// bg-2拖动状态
|
// bg-2拖动状态
|
||||||
bg2X: 9999,
|
bg2X: 9999,
|
||||||
bg2Y: 0,
|
bg2Y: 0,
|
||||||
bg2CurrentX: 9999,
|
|
||||||
bg2CurrentY: 0,
|
|
||||||
isSwapped: true // 是否交换了展示画面
|
isSwapped: true // 是否交换了展示画面
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 非响应式变量用于记录拖动位置,防止高频触发页面重绘
|
||||||
|
let currentDragPos = { x: 9999, y: 0 }
|
||||||
|
|
||||||
|
const isAnswered = ref(false)
|
||||||
|
const answerCall = () => {
|
||||||
|
isAnswered.value = true
|
||||||
|
autoPlayVideos()
|
||||||
|
}
|
||||||
|
|
||||||
// 定时器引用,用于页面卸载时清理
|
// 定时器引用,用于页面卸载时清理
|
||||||
let statusBarTimer = null
|
let statusBarTimer = null
|
||||||
let timingTimer = null // 计时定时器
|
let timingTimer = null // 计时定时器
|
||||||
|
|
@ -400,8 +537,8 @@ const getItemStyle = (index) => {
|
||||||
|
|
||||||
const onBg2Change = (e) => {
|
const onBg2Change = (e) => {
|
||||||
if (e.detail.source === 'touch') {
|
if (e.detail.source === 'touch') {
|
||||||
data.bg2CurrentX = e.detail.x
|
currentDragPos.x = e.detail.x
|
||||||
data.bg2CurrentY = e.detail.y
|
currentDragPos.y = e.detail.y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,10 +557,10 @@ const onBg2TouchEnd = () => {
|
||||||
const imageWidthPx = 188 * rpx2px
|
const imageWidthPx = 188 * rpx2px
|
||||||
const maxXPx = containerWidthPx - imageWidthPx
|
const maxXPx = containerWidthPx - imageWidthPx
|
||||||
|
|
||||||
let targetX = data.bg2CurrentX > maxXPx / 2 ? maxXPx : 0
|
let targetX = currentDragPos.x > maxXPx / 2 ? maxXPx : 0
|
||||||
|
|
||||||
data.bg2X = data.bg2CurrentX
|
data.bg2X = currentDragPos.x
|
||||||
data.bg2Y = data.bg2CurrentY
|
data.bg2Y = currentDragPos.y
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
data.bg2X = targetX
|
data.bg2X = targetX
|
||||||
}, 50)
|
}, 50)
|
||||||
|
|
@ -438,11 +575,26 @@ onMounted(() => {
|
||||||
const containerWidthPx = windowWidth - 32 * rpx2px
|
const containerWidthPx = windowWidth - 32 * rpx2px
|
||||||
const imageWidthPx = 188 * rpx2px
|
const imageWidthPx = 188 * rpx2px
|
||||||
data.bg2X = containerWidthPx - imageWidthPx
|
data.bg2X = containerWidthPx - imageWidthPx
|
||||||
data.bg2CurrentX = data.bg2X
|
currentDragPos.x = data.bg2X
|
||||||
})
|
})
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
// 强制干掉旧缓存,确保能顺利读取您代码中新写的初始数据
|
||||||
|
// uni.removeStorageSync('videoChatData')
|
||||||
|
|
||||||
const videoData = uni.getStorageSync('videoChatData') || data.videoData
|
const videoData = uni.getStorageSync('videoChatData') || data.videoData
|
||||||
|
|
||||||
|
const config = uni.getStorageSync('config')
|
||||||
|
|
||||||
|
console.log("---config---", config);
|
||||||
|
let videoPath = config.config['client.uniapp.video_path'].video_call;
|
||||||
|
if (videoData.chat.me.videoUrl == '') {
|
||||||
|
videoData.chat.me.videoUrl = videoPath + 'video_call_boy_001.mp4'
|
||||||
|
}
|
||||||
|
if (videoData.chat.other.videoUrl == '') {
|
||||||
|
videoData.chat.other.videoUrl = videoPath + 'video_call_girl_001.mp4'
|
||||||
|
}
|
||||||
|
|
||||||
console.log('videoData1', videoData)
|
console.log('videoData1', videoData)
|
||||||
const videoDataNew = {
|
const videoDataNew = {
|
||||||
...videoData
|
...videoData
|
||||||
|
|
@ -451,6 +603,10 @@ onLoad(() => {
|
||||||
data.videoData = videoDataNew
|
data.videoData = videoDataNew
|
||||||
console.log('videoData2', data.videoData)
|
console.log('videoData2', data.videoData)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 进入视频群聊页面埋点
|
// 进入视频群聊页面埋点
|
||||||
proxy.$apiUserEvent('all', {
|
proxy.$apiUserEvent('all', {
|
||||||
type: 'event',
|
type: 'event',
|
||||||
|
|
@ -472,7 +628,7 @@ onShow(() => {
|
||||||
autoPlayVideos()
|
autoPlayVideos()
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
util.setAndroidSystemBarColor('#232323')
|
util.setAndroidSystemBarColor('#FFFFFF')
|
||||||
// 保存定时器引用,以便在页面卸载时清理
|
// 保存定时器引用,以便在页面卸载时清理
|
||||||
statusBarTimer = setTimeout(() => {
|
statusBarTimer = setTimeout(() => {
|
||||||
plus.navigator.setStatusBarStyle("light");
|
plus.navigator.setStatusBarStyle("light");
|
||||||
|
|
@ -498,11 +654,6 @@ onHide(() => {
|
||||||
data.dragState.isDragging = false
|
data.dragState.isDragging = false
|
||||||
data.dragState.draggingIndex = -1
|
data.dragState.draggingIndex = -1
|
||||||
|
|
||||||
// 保存当前计时
|
|
||||||
if (data.timing) {
|
|
||||||
data.videoData.timeText = data.timing
|
|
||||||
uni.setStorageSync('videoChatData', data.videoData)
|
|
||||||
}
|
|
||||||
|
|
||||||
stopTimer() // 停止计时
|
stopTimer() // 停止计时
|
||||||
console.log('🚪 页面隐藏,已清理定时器和状态')
|
console.log('🚪 页面隐藏,已清理定时器和状态')
|
||||||
|
|
@ -515,13 +666,6 @@ onUnmounted(() => {
|
||||||
clearTimeout(statusBarTimer)
|
clearTimeout(statusBarTimer)
|
||||||
statusBarTimer = null
|
statusBarTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存当前计时
|
|
||||||
if (data.timing) {
|
|
||||||
data.videoData.timeText = data.timing
|
|
||||||
uni.setStorageSync('videoChatData', data.videoData)
|
|
||||||
}
|
|
||||||
|
|
||||||
stopTimer() // 停止计时
|
stopTimer() // 停止计时
|
||||||
|
|
||||||
// 清理长按定时器
|
// 清理长按定时器
|
||||||
|
|
@ -612,6 +756,13 @@ const closeTimeEditPopup = () => {
|
||||||
data.showTimeEditPopup = false
|
data.showTimeEditPopup = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭并保存通话信息编辑弹窗
|
||||||
|
const closeCallInfoPopup = () => {
|
||||||
|
// 保存所有更改(包括刚输入的对方昵称)到缓存
|
||||||
|
uni.setStorageSync('videoChatData', data.videoData)
|
||||||
|
data.showCallInfoPopup = false
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否为临时文件路径
|
// 判断是否为临时文件路径
|
||||||
const isTempFilePath = (filePath) => {
|
const isTempFilePath = (filePath) => {
|
||||||
if (!filePath) return false
|
if (!filePath) return false
|
||||||
|
|
@ -1256,7 +1407,7 @@ const stopTimer = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-label {
|
.control-label {
|
||||||
font-size: 12px;
|
font-size: 22rpx;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1264,7 +1415,6 @@ const stopTimer = () => {
|
||||||
width: 120rpx;
|
width: 120rpx;
|
||||||
height: 120rpx;
|
height: 120rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #ff3b30;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -1467,4 +1617,60 @@ const stopTimer = () => {
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 350rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
line-height: 40rpx;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-block;
|
||||||
|
width: 18rpx;
|
||||||
|
height: 18rpx;
|
||||||
|
max-height: 18rpx;
|
||||||
|
max-width: 18rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 16px 6rpx;
|
||||||
|
animation: dotBlink 0.9s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotBlink {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
33.32% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
33.33%,
|
||||||
|
66.65% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
66.66%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
width: 190rpx;
|
||||||
|
height: 410rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -52,13 +52,16 @@
|
||||||
btnWidth: 0,
|
btnWidth: 0,
|
||||||
btnHeight: 0,
|
btnHeight: 0,
|
||||||
x: 10000,
|
x: 10000,
|
||||||
y: 10000,
|
y: 10000
|
||||||
old: {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
// 将拖动过程坐标存储在非响应式属性中,避免高频触发 vue 重绘和跨层通讯
|
||||||
|
this.old = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getSysInfo()
|
this.getSysInfo()
|
||||||
},
|
},
|
||||||
|
|
@ -128,6 +131,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
/* 预创建 GPU 合成层,避免首次拖动时动态创建导致视频渲染上下文丢失 */
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animation-info {
|
.animation-info {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue