557 lines
12 KiB
Plaintext
557 lines
12 KiB
Plaintext
<template>
|
||
<view class="video-call-container">
|
||
<!-- 顶部状态栏 -->
|
||
<view class="status-bar">
|
||
<view class="status-left">
|
||
<text class="status-duration">{{callDuration}}</text>
|
||
<!-- <view class="network-status">
|
||
<view class="network-dot"></view>
|
||
<text>良好</text>
|
||
</view> -->
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 主视频区域 -->
|
||
<view class="main-video-area">
|
||
<!-- 对方视频 -->
|
||
<view class="remote-video"></view>
|
||
|
||
<!-- 用户信息 -->
|
||
<view class="user-info">
|
||
<view class="user-avatar">
|
||
<uni-icons type="contact" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
<view class="user-details">
|
||
<text class="user-name">{{targetUser}}</text>
|
||
<text class="call-status">通话中</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 本地预览 -->
|
||
<view class="local-preview" @click="switchVideo">
|
||
<view class="local-video"></view>
|
||
<view class="preview-mask">
|
||
<uni-icons type="reload" size="14" color="#fff"></uni-icons>
|
||
<text>切换</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部控制栏 -->
|
||
<view class="control-bar">
|
||
<!-- 第一行控制按钮 -->
|
||
<view class="control-row">
|
||
<view class="control-item" @click="microphone">
|
||
<view class="control-icon">
|
||
<uni-icons :type="isMicrophoneOn ? 'mic' : 'micoff'" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="control-text">{{isMicrophoneOn ? '关闭' : '开启'}}麦克风</text>
|
||
</view>
|
||
|
||
<view class="control-item" @click="closeCameraCur">
|
||
<view class="control-icon">
|
||
<uni-icons :type="isCameraOn ? 'eye' : 'eye-slash'" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="control-text">{{isCameraOn ? '关闭' : '开启'}}摄像头</text>
|
||
</view>
|
||
|
||
<view class="control-item" @click="enableSpeaker">
|
||
<view class="control-icon">
|
||
<uni-icons :type="isSpeakerOn ? 'sound' : 'sound-filled'" size="24" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="control-text">{{isSpeakerOn ? '开启' : '关闭'}}扬声器</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 第二行控制按钮 -->
|
||
<view class="control-row">
|
||
<view class="control-item" @click="changeMediaType">
|
||
<view class="control-icon">
|
||
<uni-icons :type="mediaType === 'video' ? 'phone' : 'videocam'" size="28"
|
||
color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="control-text">{{mediaType === 'video' ? '切换语音' : '切换视频'}}</text>
|
||
</view>
|
||
|
||
<view class="hangup-btn" @click="hangup">
|
||
<view class="hangup-icon">
|
||
<uni-icons type="phone" size="28" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="hangup-text">挂断</text>
|
||
</view>
|
||
|
||
<view class="control-item" @click="switchCamera">
|
||
<view class="control-icon">
|
||
<uni-icons type="refresh" size="28" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="control-text">翻转</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 来电接听界面 -->
|
||
<view class="incoming-call" v-if="isIncomingCall">
|
||
<view class="caller-info">
|
||
<view class="caller-avatar">
|
||
<uni-icons type="contact-filled" size="40" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="caller-name">{{callerName}}</text>
|
||
<text class="call-type">视频通话邀请</text>
|
||
</view>
|
||
<view class="incoming-controls">
|
||
<view class="incoming-control" @click="declineCall">
|
||
<view class="call-btn decline-btn">
|
||
<uni-icons type="phone" size="28" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="btn-text">拒绝</text>
|
||
</view>
|
||
<view class="incoming-control" @click="acceptCall">
|
||
<view class="call-btn accept-btn">
|
||
<uni-icons type="phone" size="28" color="#fff"></uni-icons>
|
||
</view>
|
||
<text class="btn-text">接听</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
onMounted
|
||
} from 'vue'
|
||
import {
|
||
onLoad
|
||
} from '@dcloudio/uni-app'
|
||
// #ifdef APP-PLUS
|
||
import * as call from "@/nativeplugins/RongCloud-CallWrapper/lib/index";
|
||
// #endif
|
||
|
||
// 状态变量
|
||
const isConnected = ref(true)
|
||
const isMicrophoneOn = ref(true)
|
||
const isCameraOn = ref(true)
|
||
const isSpeakerOn = ref(true)
|
||
const isIncomingCall = ref(false)
|
||
const mediaType = ref('video')
|
||
const callDuration = ref('00:00')
|
||
const targetUser = ref('张伟')
|
||
const callerName = ref('李娜')
|
||
// 获取屏幕的高度
|
||
const windowWidth = ref(0)
|
||
const windowHeight = ref(0)
|
||
// 通话信息
|
||
const callform = ref({
|
||
mediaType: "video",
|
||
callType: "out",
|
||
groupId: "",
|
||
userIds: "",
|
||
targetId: "", //呼叫对象id
|
||
callWay: 0, //呼叫方式 0 单聊 1 群聊
|
||
mediaTypeCur: "audio"
|
||
})
|
||
|
||
// 初始化
|
||
onLoad(() => {
|
||
uni.getSystemInfo({
|
||
success: function(res) {
|
||
windowWidth.value = res.windowWidth;
|
||
windowHeight.value = res.windowHeight;
|
||
}
|
||
})
|
||
uni.getStorage({
|
||
key: "room-parameters",
|
||
success: (res) => {
|
||
callform.value.mediaType = res.data.mediaType;
|
||
callform.value.callType = res.data.callType ? res.data.callType : 'in';
|
||
callform.value.groupId = res.data.groupId ? res.data.groupId : '';
|
||
callform.value.userIds = res.data.userIds ? res.data.userIds : '';
|
||
if (callform.value.callType === 'out') {
|
||
console.log('呼出out')
|
||
callform.value.targetId = res.data.targetId;
|
||
startCall();
|
||
} else {
|
||
console.log('呼入接受')
|
||
// this.accept();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 添加事件监听
|
||
// uni.$on('OnCallConnected', this.onCallConnected)
|
||
// uni.$on('OnCallDisconnected', this.onCallDisconnected)
|
||
})
|
||
|
||
function startCall(){
|
||
const type = callform.value.mediaType === 'audio' ? 0 : 1;
|
||
callform.value.mediaTypeCur = callform.value.mediaType;
|
||
if (callform.value.targetId.length > 0) {
|
||
call.enableSpeaker(true);
|
||
call.startSingleCall(callform.value.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 : [];
|
||
}
|
||
// 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);
|
||
// }
|
||
}
|
||
|
||
// 通话计时
|
||
let timer = null
|
||
const startCallTimer = () => {
|
||
let seconds = 0
|
||
timer = setInterval(() => {
|
||
seconds++
|
||
const minutes = Math.floor(seconds / 60)
|
||
const secs = seconds % 60
|
||
callDuration.value = `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||
}, 1000)
|
||
}
|
||
|
||
// 切换麦克风
|
||
const microphone = () => {
|
||
isMicrophoneOn.value = !isMicrophoneOn.value
|
||
}
|
||
|
||
// 切换摄像头
|
||
const closeCameraCur = () => {
|
||
isCameraOn.value = !isCameraOn.value
|
||
}
|
||
|
||
// 切换扬声器
|
||
const enableSpeaker = () => {
|
||
isSpeakerOn.value = !isSpeakerOn.value
|
||
}
|
||
|
||
// 切换通话类型
|
||
const changeMediaType = () => {
|
||
mediaType.value = mediaType.value === 'video' ? 'audio' : 'video'
|
||
}
|
||
|
||
// 切换摄像头方向
|
||
const switchCamera = () => {
|
||
// 实现摄像头切换逻辑
|
||
}
|
||
|
||
// 切换视频画面
|
||
const switchVideo = () => {
|
||
// 实现画面切换逻辑
|
||
}
|
||
|
||
// 接听来电
|
||
const acceptCall = () => {
|
||
isIncomingCall.value = false
|
||
isConnected.value = true
|
||
}
|
||
|
||
// 拒绝来电
|
||
const declineCall = () => {
|
||
isIncomingCall.value = false
|
||
}
|
||
|
||
// 挂断通话
|
||
const hangup = () => {
|
||
if (isIncomingCall.value) {
|
||
declineCall()
|
||
} else {
|
||
isConnected.value = false
|
||
clearInterval(timer)
|
||
uni.showToast({
|
||
title: '通话已结束',
|
||
icon: 'none'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
}
|
||
}
|
||
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* 原有样式保持不变,仅需将px单位改为rpx */
|
||
.video-call-container {
|
||
width: 750rpx;
|
||
height: 100vh;
|
||
position: relative;
|
||
background: #000;
|
||
color: #fff;
|
||
}
|
||
|
||
.status-bar {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 88rpx;
|
||
padding-top: 44rpx;
|
||
padding-left: 20rpx;
|
||
padding-right: 20rpx;
|
||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 20;
|
||
}
|
||
|
||
.status-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.status-duration {
|
||
font-size: 18rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.network-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
font-size: 14rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.network-dot {
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
background: #4cd964;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.main-video-area {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #000;
|
||
position: relative;
|
||
}
|
||
|
||
.remote-video {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.user-info {
|
||
position: absolute;
|
||
top: 100rpx;
|
||
left: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.user-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.call-status {
|
||
font-size: 14rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.local-preview {
|
||
position: absolute;
|
||
top: 120rpx;
|
||
right: 20rpx;
|
||
width: 120rpx;
|
||
height: 160rpx;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.local-video {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.preview-mask {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 30rpx;
|
||
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4rpx;
|
||
font-size: 12rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
.control-bar {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
// height: 220rpx;
|
||
width: 750rpx;
|
||
background: linear-gradient(to top, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.3));
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-bottom: 40rpx;
|
||
z-index: 30;
|
||
}
|
||
|
||
.control-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 40rpx;
|
||
width: 100%;
|
||
margin-bottom: 30rpx;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.control-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.control-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.control-text {
|
||
font-size: 24rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
.hangup-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.hangup-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
background: #ff3b30;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.hangup-text {
|
||
font-size: 24rpx;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.incoming-call {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: #1a1a1a;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 100;
|
||
}
|
||
|
||
.caller-info {
|
||
text-align: center;
|
||
margin-bottom: 60rpx;
|
||
}
|
||
|
||
.caller-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 auto 20rpx;
|
||
}
|
||
|
||
.caller-name {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.call-type {
|
||
font-size: 16rpx;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
.incoming-controls {
|
||
display: flex;
|
||
gap: 60rpx;
|
||
}
|
||
|
||
.incoming-control {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.call-btn {
|
||
width: 70rpx;
|
||
height: 70rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.decline-btn {
|
||
background: #ff3b30;
|
||
}
|
||
|
||
.accept-btn {
|
||
background: #4cd964;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 14rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
</style> |