Files
uniapp-im-shop/pages/room/room.nvue
2026-02-07 12:52:36 +08:00

823 lines
18 KiB
Plaintext
Raw Permalink 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="content">
<!-- 顶部栏 -->
<view class="top-bar">
<view class="top-left">
<!-- 通话时间显示 -->
<view v-if="callState === 'dialogue'" class="call-time-container">
<uni-icons type="sound" size="16" color="#fff"></uni-icons>
<text class="call-time">{{ formattedTime }}</text>
</view>
</view>
</view>
<!-- 单人视频 -->
<view v-if="users.length <= 2">
<RongCloud-Call-RCUniCallView
class="bigVideoView"
:style="{
width: windowWidth + 'px',
height: windowHeight + 'px'
}"
ref="bigVideoView"
></RongCloud-Call-RCUniCallView>
<RongCloud-Call-RCUniCallView
class="smallVideoView"
:style="{ width: 400 + 'upx', height: 400 + 'upx' }"
ref="smallVideoView"
></RongCloud-Call-RCUniCallView>
</view>
<!-- 底部按钮 -->
<view class="nav-box">
<view class="control-row">
<view class="icon-item" @click="microphone">
<view class="control-icon">
<uni-icons
:type="isMicrophone ? 'mic' : 'micoff'"
size="24"
color="#fff"
></uni-icons>
</view>
<text class="btn-text">
{{ isMicrophone ? '关闭' : '开启' }}麦克风
</text>
</view>
<view class="icon-item" @click="closeCameraCur">
<view class="control-icon">
<uni-icons
:type="curCamera ? 'eye' : 'eye-slash'"
size="24"
color="#fff"
></uni-icons>
</view>
<text class="btn-text">
{{ curCamera ? '关闭' : '开启' }}摄像头
</text>
</view>
<view class="icon-item" @click="enableSpeaker">
<view class="control-icon">
<uni-icons
:type="isEnableSpeaker ? 'sound' : 'sound'"
size="24"
color="#fff"
></uni-icons>
</view>
<text class="btn-text">
{{ isEnableSpeaker ? '关闭' : '开启' }}扬声器
</text>
</view>
<view class="icon-item" @click="switchCamera">
<view class="control-icon">
<uni-icons type="refresh" size="24" color="#fff"></uni-icons>
</view>
<text class="btn-text">翻转</text>
</view>
</view>
<view class="control-row" :style="{ width: windowWidth + 'px' }">
<view class="hangup-btn icon-item" @click="hangup">
<view class="hangup-icon control-icon">
<uni-icons type="phone" size="28" color="#fff"></uni-icons>
</view>
<text class="hangup-text">挂断</text>
</view>
</view>
</view>
</view>
</template>
<script>
import * as call from '@/uni_modules/RongCloud-CallWrapper/lib/index'
// import * as im from "@/uni_modules/RongCloud-IMWrapper/js_sdk/index"
// import RCBeautyEngine from "@/uni_modules/RongCloud-BeautyWrapper/lib/RCBeautyEngine"
export default {
data() {
return {
mediaType: 'video',
callType: 'out',
callWay: 0, //呼叫方式 0 单聊 1 群聊
targetId: '',
bottomHeight: 0,
isConnected: false,
isSelf: false,
viewArr: [],
callSelect: 'single',
groupId: '',
userIds: [],
windowWidth: '',
windowHeight: '',
isMicrophone: true,
isEnableSpeaker: true,
curCamera: true,
backCamera: true,
isMe: true,
users: [],
currentCallSession: {},
isMark: false,
inviteUsersIds: '',
mediaTypeCur: 'audio',
isBeauty: false,
curIndex: '0',
curFilte: 0,
whiteVal: 0,
ruddyVal: 0,
buffingVal: 0,
brightnessVal: 5,
isChecked: false,
formattedTime: "00:00",
callState: "123",
callDuration: 0,
timer: null
}
},
onLoad: function () {
var _this = this
uni.getStorage({
key: 'room-parameters',
success: res => {
console.log('room-parameters: ',res);
this.mediaType = res.data.mediaType
this.callType = res.data.callType ? res.data.callType : 'in'
this.groupId = res.data.groupId ? res.data.groupId : ''
this.userIds = res.data.userIds ? res.data.userIds : ''
if (this.callType === 'out') {
console.log('呼出out')
this.targetId = res.data.targetId
this.startCall()
} else {
console.log('呼入接受')
this.accept()
}
}
})
uni.getSystemInfo({
success: function (res) {
_this.windowWidth = res.windowWidth
_this.windowHeight = res.windowHeight
}
})
uni.$on('OnCallConnected', this.onCallConnected)
uni.$on('OnCallDisconnected', this.onCallDisconnected)
},
beforeDestroy() {
uni.$off('OnCallDisconnected')
uni.$off('OnCallConnected')
},
onUnload() {
call.hangup()
this.stopTimer()
},
onHide() {
const session = call.getCurrentCallSession()
if (session) {
call.hangup()
}
uni.$off('OnCallDisconnected')
uni.$off('OnCallConnected')
this.stopTimer()
},
methods: {
/** 开始通话计时 */
startTimer(){
this.stopTimer() // 先停止之前的计时器
this.timer = setInterval(() => {
this.callDuration++
this.formatTime()
}, 1000)
},
/** 停止通话计时 */
stopTimer(){
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
/** 格式化时间显示 */
formatTime(){
const minutes = Math.floor(this.callDuration / 60)
const seconds = this.callDuration % 60
this.formattedTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
},
changeMediaType() {
if (this.mediaTypeCur == 'video') {
this.mediaTypeCur = 'audio'
} else {
this.mediaTypeCur = 'video'
}
call.changeMediaType(0)
},
inviteUsers(flag) {
if (flag) {
if (this.inviteUsersIds === '') {
uni.showToast({
title: '请输入被邀请者ID',
icon: 'error',
duration: 2000
})
return
}
let userIdsArr = this.inviteUsersIds.split(',')
call.inviteUsers(userIdsArr, [])
}
this.$refs.inviteInput.blur()
this.isMark = false
},
switchVideo() {
this.isMe = !this.isMe
let session = call.getCurrentCallSession()
if (this.isMe) {
switch (uni.getSystemInfoSync().platform) {
case 'android':
call.setVideoView(
session.targetId,
this.$refs.bigVideoView.ref,
0,
false
)
call.setVideoView(
session.mine.userId,
this.$refs.smallVideoView.ref,
0,
true
)
break
case 'ios':
call.setVideoView(
session.targetId,
this.$refs.bigVideoView.ref,
0
)
call.setVideoView(
session.mine.userId,
this.$refs.smallVideoView.ref,
0
)
break
default:
console.log('运行在开发者工具上')
break
}
} else {
switch (uni.getSystemInfoSync().platform) {
case 'android':
call.setVideoView(
session.mine.userId,
this.$refs.bigVideoView.ref,
0,
false
)
call.setVideoView(
session.targetId,
this.$refs.smallVideoView.ref,
0,
true
)
break
case 'ios':
call.setVideoView(
session.mine.userId,
this.$refs.bigVideoView.ref,
0
)
call.setVideoView(
session.targetId,
this.$refs.smallVideoView.ref,
0
)
break
default:
console.log('运行在开发者工具上')
break
}
}
},
closeCameraCur() {
this.curCamera = !this.curCamera
let camera = call.currentCamera()
call.enableCamera(this.curCamera, camera)
},
closeCameraBack() {
this.backCamera = !this.backCamera
call.enableCamera(this.backCamera, 1)
},
switchCamera() {
call.switchCamera()
},
microphone() {
this.isMicrophone = !this.isMicrophone
call.enableMicrophone(this.isMicrophone)
},
enableSpeaker() {
this.isEnableSpeaker = !this.isEnableSpeaker
call.enableSpeaker(this.isEnableSpeaker)
},
hangup() {
this.isSelf = true
call.hangup()
// uni.navigateBack({
// delta: 1
// })
},
accept() {
call.accept()
},
startCall() {
const type = this.mediaType === 'audio' ? 0 : 1
this.mediaTypeCur = this.mediaType
if (this.targetId.length > 0) {
call.enableSpeaker(true)
call.startSingleCall(this.targetId, type, null)
this.currentCallSession = call.getCurrentCallSession()
this.users = this.currentCallSession.users
? this.currentCallSession.users
: []
} else {
call.startGroupCall(this.groupId, this.userIds, [], type, '')
this.users = this.userIds
console.log(this.users)
this.currentCallSession = call.getCurrentCallSession()
this.users = this.currentCallSession.users
? this.currentCallSession.users
: []
}
console.log('this.users: ',this.users);
let _this = this
// im.getCurrentUserId(function(result){
// _this.systemInfoSync(result.userId,_this.$refs.bigVideoView.ref,false);
// })
if (this.currentCallSession) {
this.systemInfoSync(
this.currentCallSession.mine.userId,
this.$refs.bigVideoView.ref,
false
)
}
},
onCallConnected() {
let context = this
console.log('oncallconnected接收了')
console.log('call: ',call);
// 使用 Promise 确保 call 对象完全初始化
this.initializeCall(call)
.then(() => {
this.processCallSession()
})
.catch(error => {
console.error('初始化通话失败:', error)
})
},
async initializeCall(callObj) {
// 等待 call 对象就绪
await this.waitForCallReady(callObj)
// 设置扬声器
if (callObj.enableSpeaker && typeof callObj.enableSpeaker === 'function') {
try {
callObj.enableSpeaker(true)
} catch (error) {
console.warn('设置扬声器失败:', error)
}
}
return callObj
},
waitForCallReady(callObj, timeout = 2000) {
return new Promise((resolve, reject) => {
const startTime = Date.now()
const check = () => {
const now = Date.now()
if (callObj &&
callObj.enableSpeaker &&
callObj.getCurrentCallSession &&
typeof callObj.getCurrentCallSession === 'function') {
resolve(callObj)
} else if (now - startTime > timeout) {
reject(new Error('call 对象初始化超时'))
} else {
setTimeout(check, 50)
}
}
check()
})
},
processCallSession(){
this.callState = "dialogue"
this.startTimer()
// call.enableSpeaker(true)
this.mediaTypeCur = this.mediaType
this.currentCallSession = call.getCurrentCallSession()
this.callWay = this.currentCallSession.callType
this.users = this.currentCallSession.users
? this.currentCallSession.users
: []
let isHasMine = this.users.findIndex(item => {
return item.userId === this.currentCallSession.mine.userId
})
if (isHasMine === -1) {
this.users.push(this.currentCallSession.mine)
}
if (
this.currentCallSession &&
this.currentCallSession.users.length > 0
) {
//视频是两个的时候
if (this.currentCallSession.users.length <= 2) {
console.log("视频是两个的时候");
setTimeout(() => {
this.systemInfoSync(
this.currentCallSession.mine.userId,
this.$refs.smallVideoView.ref,
true
)
this.viewArr = this.currentCallSession.users.filter(
item => {
return (
item.userId !== this.currentCallSession.mine.userId
)
}
)
this.viewArr.forEach(itm => {
this.targetId = itm.userId
this.systemInfoSync(
itm.userId,
this.$refs.bigVideoView.ref,
false
)
})
}, 100)
} else {
// 视频超过三个
this.$nextTick(() => {
this.systemInfoSync(
this.currentCallSession.mine.userId,
this.$refs.bigVideoView.ref,
false
)
this.viewArr = this.currentCallSession.users.filter(
item => {
return (
item.userId !== this.currentCallSession.mine.userId
)
}
)
setTimeout(() => {
this.viewArr.forEach(itm => {
this.systemInfoSync(
itm.userId,
this.$refs[itm.userId][0].ref,
false
)
})
}, 100)
})
}
}
},
systemInfoSync(userId, ref, isZOrderOnTop) {
switch (uni.getSystemInfoSync().platform) {
case 'android':
call.setVideoView(userId, ref, 0, isZOrderOnTop)
break
case 'ios':
call.setVideoView(userId, ref, 0)
break
default:
console.log('运行在开发者工具上')
break
}
},
onCallDisconnected() {
this.isMe = true
if (!this.isSelf) {
uni.navigateBack({
delta: 1
})
}
},
switchChange() {
this.isChecked = !this.isChecked
if (!this.isChecked) {
this.reset()
} else {
this.setBeautyOption()
}
}
}
}
</script>
<style scoped lang="scss">
.top-bar {
position: fixed;
top: 30rpx;
left: 0;
width: 750rpx;
height: 120rpx;
flex-direction: row;
align-items: left;
padding-left: 40rpx;
padding-right: 40rpx;
padding-top: var(--safe-area-inset-top, 0);
z-index: 99;
box-sizing: border-box;
.top-left {
width: 140rpx;
height: 120rpx;
justify-content: center;
align-items: left;
.call-time-container {
flex-direction: row;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.15);
padding-left: 20rpx;
padding-right: 20rpx;
padding-top: 8rpx;
padding-bottom: 8rpx;
border-radius: 30rpx;
/* nvue不支持backdrop-filter可以改用透明度或移除 */
/* backdrop-filter: blur(10px); */
}
.call-time {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
margin-left: 10rpx;
}
}
}
.nav-box {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
/* height: 220rpx; */
width: 750rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 60rpx;
}
.hangup-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.hangup-icon {
width: 80rpx;
height: 80rpx;
background: #ff3b30 !important;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.hangup-text {
font-size: 14rpx;
color: #fff;
font-weight: 500;
}
.btn-text {
font-size: 14rpx;
color: #fff;
}
.control-row {
display: flex;
justify-content: center;
gap: 40rpx;
/* width: 100%; */
flex-direction: row;
margin-bottom: 20rpx;
}
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
margin: 10rpx 20rpx;
}
.control-icon {
width: 80rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
}
.content {
flex: 1;
flex-direction: column;
position: relative;
}
.bigVideoView {
background: #000;
}
.smallVideoView {
position: absolute;
right: 0;
top: 50upx;
width: 200upx;
height: 200upx;
}
.smallViews {
flex-wrap: wrap;
flex-direction: row;
background: #ccc;
}
.smallView {
width: 200upx;
height: 200upx;
margin-right: 20upx;
background: pink;
}
.container {
position: absolute;
bottom: 20upx;
height: 100upx;
flex-direction: row;
justify-content: center;
}
.camera {
background: #ccc;
}
.nav-com {
margin-top: 10upx;
color: #fff;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
.box-mark {
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
color: #fff;
}
.box-cen {
padding: 30upx;
width: 500upx;
height: 500upx;
background: #222831;
}
.box-des {
color: #fff;
}
.box-input {
margin-top: 30upx;
color: #fff;
border: 1upx solid #fff;
}
.box-tit {
margin-top: 30upx;
color: #fff;
}
.box-btn {
margin-top: 30upx;
}
.box-btns {
flex-direction: row;
justify-content: space-between;
padding: 0 30upx;
}
.box-btn {
padding: 0 20upx;
}
.beauty {
height: 500upx;
position: fixed;
bottom: 0;
background: #000;
}
.beauty-btn {
height: 100upx;
padding-right: 20upx;
flex-direction: row;
justify-content: space-between;
}
.change-btn {
padding-top: 20upx;
flex-direction: row;
}
.switch-btn {
border: 1upx;
border-color: #fff;
padding: 20upx;
background: blue;
color: #fff;
margin-left: 20upx;
margin-top: -10upx;
}
.ch-tit {
margin-top: 8upx;
color: #fff;
}
.ch-res {
margin-top: -10upx;
margin-left: 20upx;
color: #fff;
padding: 20upx;
border-radius: 10upx;
justify-content: center;
align-items: center;
background: #4e6ef2;
}
.close-btn {
padding-top: 20upx;
}
.close-tit {
color: #fff;
}
.beauty-view {
flex: 1;
background: #ccc;
}
.com-view {
padding-top: 40upx;
background: #ccc;
padding-left: 30upx;
padding-right: 30upx;
}
.beauty-tab {
border-top: 1px;
border-top-color: #000000;
padding: 0 20upx;
flex: 1;
background: #fff;
flex-direction: row;
justify-content: space-between;
padding-top: 40upx;
}
.filte-view {
}
.tab-item {
width: 100upx;
height: 100upx;
border: 1px;
border-color: #000000;
justify-content: center;
align-items: center;
}
.current {
background: yellow;
}
.white-view {
}
</style>