微信视频通话

This commit is contained in:
tangxinyue 2026-06-18 13:42:30 +08:00
parent 82e922eab0
commit 7aec27d81b
7 changed files with 317 additions and 66 deletions

View File

@ -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>

View File

@ -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"
}, },

View File

@ -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

View File

@ -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 {