Files
tra-app/src/pages/sparring/components/talk-camera-安卓可用.nvue
T
2025-12-17 10:22:32 +08:00

322 lines
8.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="live-container" :style="{ top: `${topVal}px`, left: `${leftVal}px` }">
<cover-view class="pusher-container" @click.stop>
<live-pusher
id="livePusher"
class="livePusher"
:muted="true"
:enable-camera="true"
:auto-focus="true"
:beauty="0"
:whiteness="0"
aspect="9:16"
mode="SD"
></live-pusher>
</cover-view>
<view
class="drag-controller"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 响应式数据 - 直接使用px单位
const topVal = ref(40) // 初始顶部位置 (px)
const leftVal = ref(0) // 初始左侧位置 (px)
const livePusherContext = ref(null)
// 触摸相关数据
const startX = ref(0) // 触摸起始X坐标 (px)
const startY = ref(0) // 触摸起始Y坐标 (px)
const startLeft = ref(0) // 触摸起始时的left值 (px)
const startTop = ref(0) // 触摸起始时的top值 (px)
const isAnimating = ref(false) // 动画锁,防止多次触发
// 系统信息 - 直接使用px,无需rpx换算
const systemInfo = uni.getSystemInfoSync()
const screenWidthPx = systemInfo.windowWidth // 屏幕宽度(px
const screenHeightPx = systemInfo.windowHeight // 屏幕高度(px
// 容器尺寸 (px) - 直接定义像素尺寸
const CONTAINER_WIDTH = 135 // 对应原来的270rpx (750设计稿下 270/2=135px)
const CONTAINER_HEIGHT = 240 // 对应原来的480rpx (750设计稿下 480/2=240px)
console.log('系统信息:', {
screenWidthPx,
screenHeightPx,
CONTAINER_WIDTH,
CONTAINER_HEIGHT
})
// 初始化
const init = (componentProxy) => {
livePusherContext.value = uni.createLivePusherContext('livePusher', componentProxy)
}
// 切换摄像头
const switchCamera = () => {
if (!livePusherContext.value) return
livePusherContext.value.switchCamera({
success: (res) => {
console.log('livePusher.switchCamera:', JSON.stringify(res))
},
fail: (err) => {
console.error('切换摄像头失败:', err)
}
})
}
// 触摸开始事件
const handleTouchStart = (e) => {
console.log('=== 触摸开始 ===')
// 结束动画中和,不能开始
if (isAnimating.value) return
console.log('事件对象:', e)
console.log('触摸点数组:', e.touches)
try {
if (!e.touches || e.touches.length === 0) {
console.error('没有检测到触摸点')
return
}
const touch = e.touches[0]
// 安全地获取触摸坐标
const clientX = touch.screenX
const clientY = touch.screenY
startX.value = clientX
startY.value = clientY
// 记录当前容器的位置
startLeft.value = leftVal.value
startTop.value = topVal.value
console.log('记录的起始位置:', {
startX: startX.value,
startY: startY.value,
startLeft: startLeft.value,
startTop: startTop.value
})
} catch (error) {
console.error('触摸开始事件处理出错:', error)
}
}
// 触摸移动事件 - 移除rpx换算,直接使用px计算
const handleTouchMove = (e) => {
console.log('=== 触摸移动 ===')
// 防止事件穿透
if (e.stopPropagation) e.stopPropagation()
if (e.preventDefault) e.preventDefault()
// 动画中不响应触摸移动
if (isAnimating.value) return
try {
if (!e.touches || e.touches.length === 0) {
console.log('触摸移动:没有触摸点')
return
}
const touch = e.touches[0]
console.log('移动时的触摸点:', touch)
// 安全地获取当前触摸坐标
const currentX = touch.screenX
const currentY = touch.screenY
console.log('坐标比较:', {
startX: startX.value,
currentX: currentX,
startY: startY.value,
currentY: currentY
})
// 计算偏移量 (px) - 直接使用px,无需换算
const deltaX = currentX - startX.value
const deltaY = currentY - startY.value
console.log('偏移量计算:', { deltaX, deltaY })
// 检查偏移量是否有效
if (isNaN(deltaX) || isNaN(deltaY)) {
console.error('计算出 NaN 偏移量:', { deltaX, deltaY })
return
}
// 直接更新位置(px),无需rpx转换
const newLeft = startLeft.value + deltaX
const newTop = startTop.value + deltaY
// 边界检测 - 使用px单位计算边界
const maxLeft = screenWidthPx - CONTAINER_WIDTH
const maxTop = screenHeightPx - CONTAINER_HEIGHT
console.log('最终位置计算:', {
deltaX, deltaY,
newLeft, newTop,
maxLeft, maxTop,
screenWidthPx,
screenHeightPx
})
// 确保位置在有效范围内且不是 NaN
if (!isNaN(newLeft) && !isNaN(newTop)) {
leftVal.value = Math.max(0, Math.min(newLeft, maxLeft))
topVal.value = Math.max(0, Math.min(newTop, maxTop))
console.log('更新后的位置:', { left: leftVal.value, top: topVal.value })
} else {
console.error('计算出 NaN 位置:', { newLeft, newTop })
}
} catch (error) {
console.error('触摸移动事件处理出错:', error)
}
}
// 触摸结束事件 - 吸附逻辑改为px计算
const handleTouchEnd = (e) => {
console.log('=== 触摸结束 ===')
if (isAnimating.value) return
// 1. 计算核心参数(px单位)
const currentLeft = leftVal.value // 松手时小窗的左偏移
const windowCenterX = currentLeft + CONTAINER_WIDTH / 2 // 小窗中心的X坐标(px)
const screenMiddleX = screenWidthPx / 2 // 屏幕中线X坐标(px
// 2. 判断更近的边缘:中心在左半屏 → 吸附左侧;右半屏 → 吸附右侧
const targetLeft = windowCenterX < screenMiddleX ? 0 : screenWidthPx - CONTAINER_WIDTH
const targetTop = topVal.value // Y轴保持松手位置
// 3. 平滑动画过渡
const duration = 160 // 动画时长(ms
const frameCount = 60 // 动画帧数
const stepX = (targetLeft - currentLeft) / frameCount // 每帧X偏移量
const stepY = (targetTop - topVal.value) / frameCount // Y轴无偏移
let currentFrame = 0
isAnimating.value = true
const animationTimer = setInterval(() => {
currentFrame++
// 更新位置
leftVal.value += stepX
topVal.value += stepY
// 动画结束
if (currentFrame >= frameCount) {
clearInterval(animationTimer)
// 强制修正到目标位置(避免浮点误差)
leftVal.value = targetLeft
topVal.value = targetTop
isAnimating.value = false
console.log('吸附完成,最终位置:', { left: leftVal.value, top: topVal.value })
}
}, duration / frameCount)
// 仅重置触摸起始坐标
startX.value = 0
startY.value = 0
}
// 开启预览
const startPreview = () => {
console.log('摄像头开启预览')
if (!livePusherContext.value) return
livePusherContext.value.startPreview({
success: (res) => {
console.log('摄像头开启成功')
},
fail: (err) => {
console.error('开启预览失败:', err)
}
})
}
// 关闭预览
const stopPreview = () => {
console.log('关闭预览')
if (!livePusherContext.value) return
livePusherContext.value.stopPreview({
success: (res) => {
console.log('livePusher.stopPreview:', JSON.stringify(res))
},
fail: (err) => {
console.error('关闭预览失败:', err)
}
})
}
// 组件挂载后的初始化
onMounted(() => {
console.log('组件挂载完成')
// 初始化左侧位置为屏幕右侧(px单位)
leftVal.value = screenWidthPx - CONTAINER_WIDTH
console.log('初始化位置:', {
screenWidthPx,
leftVal: leftVal.value
})
})
// 暴露方法给父组件
defineExpose({
init,
switchCamera,
stopPreview,
startPreview
})
</script>
<style scoped>
.live-container {
position: absolute;
overflow: hidden;
border-radius: 10px;
width: 135px; /* 对应原来的270rpx */
height: 240px; /* 对应原来的480rpx */
background-color: red;
display: flex;
}
.livePusher{
width: 135px;
height: 240px;
border-radius: 10px;
overflow: hidden;
}
.pusher-container {
width: 135px; /* 对应原来的270rpx */
height: 240px; /* 对应原来的480rpx */
border-radius: 20px; /* 圆角大小,根据需求调整 */
overflow: hidden; /* 溢出裁剪,必须设置 */
/* 可选:添加阴影增强视觉效果 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.drag-controller {
width: 135px; /* 对应原来的270rpx */
height: 240px; /* 对应原来的480rpx */
position: absolute;
z-index: 550;
border-radius: 20px; /* 圆角大小,根据需求调整 */
overflow: hidden; /* 溢出裁剪,必须设置 */
/* 可选:添加阴影增强视觉效果 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>