|
@@ -1,50 +1,47 @@
|
|
|
<template>
|
|
|
<div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
|
|
|
- <div class="caller-info">
|
|
|
+ <div class="caller-info" v-if="rtcStore.streamType == 'audio'">
|
|
|
<img
|
|
|
- v-if="rtcStore.streamType == 'audio'"
|
|
|
class="avatar"
|
|
|
:src="rtcStore.imSate.callAvatar || defaultAvatar"
|
|
|
alt="头像"
|
|
|
/>
|
|
|
- <div v-if="rtcStore.streamType == 'video'">
|
|
|
- <!-- 本地视频 -->
|
|
|
- <spn>本地发起方</spn>
|
|
|
- <video
|
|
|
- ref="localVideo"
|
|
|
- autoplay
|
|
|
- playsinline
|
|
|
- muted
|
|
|
- class="local-video"
|
|
|
- ></video>
|
|
|
- <spn>远端</spn>
|
|
|
- <!-- 远程视频 -->
|
|
|
- <video
|
|
|
- ref="remoteVideo"
|
|
|
- autoplay
|
|
|
- playsinline
|
|
|
- class="remote-video"
|
|
|
- ></video>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- video id="localPreviewSender" width="700px" height="auto" autoPlay muted controls />
|
|
|
- <video id="remoteVideoSender" width="700px" height="auto" autoPlay muted controls /> -->
|
|
|
<div class="name">{{ rtcStore.imSate.callName || "未知用户" }}</div>
|
|
|
<div class="status">{{ statusText }}</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 视频通话 -->
|
|
|
+ <div v-if="rtcStore.streamType == 'video'" class="video-container">
|
|
|
+ <!-- 远端视频全屏 -->
|
|
|
+ <video
|
|
|
+ ref="remoteVideo"
|
|
|
+ autoplay
|
|
|
+ playsinline
|
|
|
+ class="remote-video"
|
|
|
+ ></video>
|
|
|
+
|
|
|
+ <!-- 本地视频小窗,可拖动 -->
|
|
|
+ <video
|
|
|
+ ref="localVideo"
|
|
|
+ autoplay
|
|
|
+ playsinline
|
|
|
+ muted
|
|
|
+ class="local-video-draggable"
|
|
|
+ :style="{ top: localVideoPos.top + 'px', left: localVideoPos.left + 'px' }"
|
|
|
+ @mousedown="startDrag"
|
|
|
+ ></video>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="btn-group">
|
|
|
- <!-- 拨打方,只显示挂断按钮 -->
|
|
|
<template v-if="rtcStore.isCaller">
|
|
|
<button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
</template>
|
|
|
|
|
|
- <!-- 来电方,显示接听和拒绝按钮(未通话中) -->
|
|
|
<template v-else-if="!inCall">
|
|
|
<button class="btn reject" @click="rejectCall">拒绝</button>
|
|
|
<button class="btn accept" @click="acceptCall">接听</button>
|
|
|
</template>
|
|
|
|
|
|
- <!-- 通话中显示挂断按钮 -->
|
|
|
<template v-else>
|
|
|
<button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
</template>
|
|
@@ -53,7 +50,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
+import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
|
|
import { useWebSocketStore } from "@/stores/modules/webSocketStore";
|
|
|
import { useWebRTCStore } from "@/stores/modules/webrtcStore";
|
|
|
import * as Constant from "@/common/constant/Constant";
|
|
@@ -67,6 +64,11 @@ const remoteVideo = ref(null);
|
|
|
const inCall = ref(false);
|
|
|
const defaultAvatar = "https://example.com/default-avatar.png";
|
|
|
|
|
|
+// 小窗位置
|
|
|
+const localVideoPos = ref({ top: 20, left: 20 });
|
|
|
+let dragOffset = { x: 0, y: 0 };
|
|
|
+let dragging = false;
|
|
|
+
|
|
|
const statusText = computed(() => {
|
|
|
if (inCall.value && rtcStore.remoteStream) {
|
|
|
return "通话中...";
|
|
@@ -82,13 +84,11 @@ function onMessage(message) {
|
|
|
rtcStore.imSate.videoCallModal = true;
|
|
|
rtcStore.imSate.callName = message.fromUserName || "未知用户";
|
|
|
rtcStore.imSate.callAvatar = message.fromUserAvatar || "";
|
|
|
- inCall.value = false; // 有来电,未接听
|
|
|
+ inCall.value = false;
|
|
|
break;
|
|
|
-
|
|
|
case Constant.ACCEPT_AUDIO_ONLINE:
|
|
|
- inCall.value = true; // 对方已接听,通话中
|
|
|
+ inCall.value = true;
|
|
|
break;
|
|
|
-
|
|
|
case Constant.REJECT_AUDIO_ONLINE:
|
|
|
case Constant.CANCELL_AUDIO_ONLINE:
|
|
|
case Constant.DIAL_MEDIA_END:
|
|
@@ -106,7 +106,6 @@ async function acceptCall() {
|
|
|
contentType: Constant.ACCEPT_AUDIO_ONLINE,
|
|
|
type: Constant.MESSAGE_TRANS_TYPE,
|
|
|
});
|
|
|
- // 可以调用 rtcStore 初始化本地流、连接等逻辑
|
|
|
} catch (err) {
|
|
|
console.error("接听失败", err);
|
|
|
inCall.value = false;
|
|
@@ -137,7 +136,6 @@ function hangupCall() {
|
|
|
watch(
|
|
|
() => rtcStore.remoteStream,
|
|
|
(val) => {
|
|
|
- console.log("rtcStore.streamType===", rtcStore.streamType)
|
|
|
if (val && rtcStore.streamType == "video" && remoteVideo.value) {
|
|
|
remoteVideo.value.srcObject = val;
|
|
|
}
|
|
@@ -148,7 +146,6 @@ watch(
|
|
|
watch(
|
|
|
() => rtcStore.localStream,
|
|
|
(val) => {
|
|
|
- console.log("rtcStore.streamType===22", rtcStore.streamType)
|
|
|
if (val && rtcStore.streamType == "video" && localVideo.value) {
|
|
|
localVideo.value.srcObject = val;
|
|
|
}
|
|
@@ -158,20 +155,41 @@ watch(
|
|
|
|
|
|
onMounted(() => {
|
|
|
wsStore.onMessageCallbacks.push(onMessage);
|
|
|
+ document.addEventListener("mousemove", onDrag);
|
|
|
+ document.addEventListener("mouseup", stopDrag);
|
|
|
});
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
|
|
|
(cb) => cb !== onMessage
|
|
|
);
|
|
|
+ document.removeEventListener("mousemove", onDrag);
|
|
|
+ document.removeEventListener("mouseup", stopDrag);
|
|
|
});
|
|
|
+
|
|
|
+// 拖动逻辑
|
|
|
+function startDrag(e) {
|
|
|
+ dragging = true;
|
|
|
+ dragOffset.x = e.clientX - localVideoPos.value.left;
|
|
|
+ dragOffset.y = e.clientY - localVideoPos.value.top;
|
|
|
+}
|
|
|
+
|
|
|
+function onDrag(e) {
|
|
|
+ if (!dragging) return;
|
|
|
+ localVideoPos.value.left = e.clientX - dragOffset.x;
|
|
|
+ localVideoPos.value.top = e.clientY - dragOffset.y;
|
|
|
+}
|
|
|
+
|
|
|
+function stopDrag() {
|
|
|
+ dragging = false;
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
.weixin-call-modal {
|
|
|
position: fixed;
|
|
|
inset: 0;
|
|
|
- background-color: rgba(0, 0, 0, 0.85);
|
|
|
+ background-color: black;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
@@ -192,7 +210,6 @@ onBeforeUnmount(() => {
|
|
|
height: 80px;
|
|
|
border-radius: 50%;
|
|
|
margin-bottom: 16px;
|
|
|
- border: 2px solid rgba(255, 255, 255, 0.2);
|
|
|
}
|
|
|
|
|
|
.name {
|
|
@@ -206,7 +223,33 @@ onBeforeUnmount(() => {
|
|
|
color: #ccc;
|
|
|
}
|
|
|
|
|
|
+.video-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.remote-video {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.local-video-draggable {
|
|
|
+ position: absolute;
|
|
|
+ width: 120px;
|
|
|
+ height: 160px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: black;
|
|
|
+ cursor: grab;
|
|
|
+ object-fit: cover;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
.btn-group {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 40px;
|
|
|
display: flex;
|
|
|
gap: 60px;
|
|
|
}
|
|
@@ -222,32 +265,14 @@ onBeforeUnmount(() => {
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
- user-select: none;
|
|
|
- outline: none;
|
|
|
- transition: background-color 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.btn.accept {
|
|
|
background-color: #4cd964;
|
|
|
}
|
|
|
|
|
|
-.btn.accept:hover {
|
|
|
- background-color: #40c150;
|
|
|
-}
|
|
|
-
|
|
|
-.btn.reject {
|
|
|
- background-color: #ff3b30;
|
|
|
-}
|
|
|
-
|
|
|
-.btn.reject:hover {
|
|
|
- background-color: #e02d22;
|
|
|
-}
|
|
|
-
|
|
|
+.btn.reject,
|
|
|
.btn.hangup {
|
|
|
background-color: #ff3b30;
|
|
|
}
|
|
|
-
|
|
|
-.btn.hangup:hover {
|
|
|
- background-color: #e02d22;
|
|
|
-}
|
|
|
</style>
|