liming 4 өдөр өмнө
parent
commit
8f3b9b43f0

+ 23 - 22
src/stores/modules/webrtcStore.js

@@ -23,7 +23,7 @@ export const useWebRTCStore = defineStore("webrtc", {
     localStream: null, // 本地媒体流
     remoteStream: null, // 远端媒体流
 
-    streamType:  "video", //"audio",
+    streamType:  "audio", //"audio",
 
     // 视频元素引用
     localVideoElement: null,
@@ -55,27 +55,27 @@ export const useWebRTCStore = defineStore("webrtc", {
   }),
   actions: {
     //
-    // bindRemoteAudio() {
-    //   // video  && audio
-    //   // 如果已经存在 audio 元素,先移除旧的
-    //   const oldAudio = document.getElementById(`remote-audio`);
-    //   if (oldAudio) {
-    //     oldAudio.remove();
-    //   }
-
-    //   // 创建新的 <audio> 元素
-    //   const audioElement = document.createElement("audio");
-    //   audioElement.id = `remote-audio`;
-    //   audioElement.autoplay = true; // 自动播放
-    //   audioElement.muted = false; // 取消静音
-    //   audioElement.controls = true; // 显示控制条(可选)
-    //   audioElement.srcObject = this.remoteStream;
-
-    //   // 添加到 DOM(可以放在任意位置,比如 body)
-    //   document.body.appendChild(audioElement);
-
-    //   console.log("✅ 远程音频已绑定到 <audio> 元素");
-    // },
+    bindRemoteAudio() {
+      // video  && audio
+      // 如果已经存在 audio 元素,先移除旧的
+      const oldAudio = document.getElementById(`remote-audio`);
+      if (oldAudio) {
+        oldAudio.remove();
+      }
+
+      // 创建新的 <audio> 元素
+      const audioElement = document.createElement("audio");
+      audioElement.id = `remote-audio`;
+      audioElement.autoplay = true; // 自动播放
+      audioElement.muted = false; // 取消静音
+      audioElement.controls = true; // 显示控制条(可选)
+      audioElement.srcObject = this.remoteStream;
+
+      // 添加到 DOM(可以放在任意位置,比如 body)
+      document.body.appendChild(audioElement);
+
+      console.log("✅ 远程音频已绑定到 <audio> 元素");
+    },
 
     // 初始化 WebRTC 连接
     initConnection(isCaller) {
@@ -126,6 +126,7 @@ export const useWebRTCStore = defineStore("webrtc", {
 
           if (state === "connected") {
             console.log("✅ P2P 连接成功,可以开始语音通话!");
+            this.bindRemoteAudio();
           } else if (state === "failed") {
             console.error("❌ ICE 连接失败,尝试重启...");
             this.restartICE();

+ 200 - 190
src/views/im/components/CallController/index.vue

@@ -1,204 +1,214 @@
 <template>
-    <div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
-      <div class="caller-info">
-        <img
-          class="avatar"
-          :src="rtcStore.imSate.callAvatar || defaultAvatar"
-          alt="头像"
-        />
-        <div class="name">{{ rtcStore.imSate.callName || "未知用户" }}</div>
-        <div class="status">{{ statusText }}</div>
-      </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>
-      </div>
+  <div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
+    <div class="caller-info">
+      <img
+        class="avatar"
+        :src="rtcStore.imSate.callAvatar || defaultAvatar"
+        alt="头像"
+      />
+      <div class="name">{{ rtcStore.imSate.callName || "未知用户" }}</div>
+      <div class="status">{{ statusText }}</div>
     </div>
-  </template>
-  
-  
-  <script setup>
-  import { ref, computed, onMounted, onBeforeUnmount } from "vue";
-  import { useWebSocketStore } from "@/stores/modules/webSocketStore";
-  import { useWebRTCStore } from "@/stores/modules/webrtcStore";
-  import * as Constant from "@/common/constant/Constant";
-  
-  const wsStore = useWebSocketStore();
-  const rtcStore = useWebRTCStore();
-  
-  const inCall = ref(false);
-  const defaultAvatar = "https://example.com/default-avatar.png";
-  
-  const statusText = computed(() => {
-    return inCall.value ? "通话中..." : "正在语音通话请求...";
-  });
-  
-  function onMessage(message) {
-    if (message.type !== Constant.MESSAGE_TRANS_TYPE) return;
-  
-    switch (message.contentType) {
-      case Constant.DIAL_AUDIO_ONLINE:
-        rtcStore.imSate.videoCallModal = true;
-        rtcStore.imSate.callName = message.fromUserName || "未知用户";
-        rtcStore.imSate.callAvatar = message.fromUserAvatar || "";
-        inCall.value = false; // 有来电,未接听
-        break;
-  
-      case Constant.ACCEPT_AUDIO_ONLINE:
-        inCall.value = true; // 对方已接听,通话中
-        break;
-  
-      case Constant.REJECT_AUDIO_ONLINE:
-      case Constant.CANCELL_AUDIO_ONLINE:
-      case Constant.DIAL_MEDIA_END:
-        rtcStore.imSate.videoCallModal = false;
-        inCall.value = false;
-        break;
-    }
-  }
-  
-  async function acceptCall() {
-    try {
-      inCall.value = true;
-      wsStore.sendMessage({
-        messageType: Constant.MESSAGE_TYPE_USER,
-        contentType: Constant.ACCEPT_AUDIO_ONLINE,
-        type: Constant.MESSAGE_TRANS_TYPE,
-      });
-      // 可以调用 rtcStore 初始化本地流、连接等逻辑
-    } catch (err) {
-      console.error("接听失败", err);
+    <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>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onBeforeUnmount } from "vue";
+import { useWebSocketStore } from "@/stores/modules/webSocketStore";
+import { useWebRTCStore } from "@/stores/modules/webrtcStore";
+import * as Constant from "@/common/constant/Constant";
+
+const wsStore = useWebSocketStore();
+const rtcStore = useWebRTCStore();
+
+const inCall = ref(false);
+const defaultAvatar = "https://example.com/default-avatar.png";
+
+const statusText = computed(() => {
+  if(inCall.value && rtcStore.remoteStream){
+    return "通话中..."
+  }
+  return inCall.value ? "链接中..." : "正在语音通话请求...";
+});
+
+function onMessage(message) {
+  if (message.type !== Constant.MESSAGE_TRANS_TYPE) return;
+
+  switch (message.contentType) {
+    case Constant.DIAL_AUDIO_ONLINE:
+      rtcStore.imSate.videoCallModal = true;
+      rtcStore.imSate.callName = message.fromUserName || "未知用户";
+      rtcStore.imSate.callAvatar = message.fromUserAvatar || "";
+      inCall.value = false; // 有来电,未接听
+      break;
+
+    case Constant.ACCEPT_AUDIO_ONLINE:
+      inCall.value = true; // 对方已接听,通话中
+      break;
+
+    case Constant.REJECT_AUDIO_ONLINE:
+    case Constant.CANCELL_AUDIO_ONLINE:
+    case Constant.DIAL_MEDIA_END:
+      rtcStore.imSate.videoCallModal = false;
       inCall.value = false;
-    }
+      break;
   }
-  
-  function rejectCall() {
+}
+
+async function acceptCall() {
+  try {
+    inCall.value = true;
     wsStore.sendMessage({
       messageType: Constant.MESSAGE_TYPE_USER,
-      contentType: Constant.REJECT_AUDIO_ONLINE,
+      contentType: Constant.ACCEPT_AUDIO_ONLINE,
       type: Constant.MESSAGE_TRANS_TYPE,
     });
-    rtcStore.imSate.videoCallModal = false;
+    // 可以调用 rtcStore 初始化本地流、连接等逻辑
+  } catch (err) {
+    console.error("接听失败", err);
     inCall.value = false;
   }
-  
-  function hangupCall() {
-    rtcStore.cleanup();
-    wsStore.sendMessage({
-      messageType: Constant.MESSAGE_TYPE_USER,
-      contentType: Constant.CANCELL_AUDIO_ONLINE,
-      type: Constant.MESSAGE_TRANS_TYPE,
-    });
-    rtcStore.imSate.videoCallModal = false;
-    inCall.value = false;
-  }
-  
-  onMounted(() => {
-    wsStore.onMessageCallbacks.push(onMessage);
+}
+
+function rejectCall() {
+  wsStore.sendMessage({
+    messageType: Constant.MESSAGE_TYPE_USER,
+    contentType: Constant.REJECT_AUDIO_ONLINE,
+    type: Constant.MESSAGE_TRANS_TYPE,
   });
-  
-  onBeforeUnmount(() => {
-    wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
-      (cb) => cb !== onMessage
-    );
+  rtcStore.imSate.videoCallModal = false;
+  inCall.value = false;
+}
+
+function hangupCall() {
+  rtcStore.cleanup();
+  wsStore.sendMessage({
+    messageType: Constant.MESSAGE_TYPE_USER,
+    contentType: Constant.CANCELL_AUDIO_ONLINE,
+    type: Constant.MESSAGE_TRANS_TYPE,
   });
-  </script>
-  
-  <style scoped lang="less">
-  .weixin-call-modal {
-    position: fixed;
-    inset: 0;
-    background-color: rgba(0, 0, 0, 0.85);
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    z-index: 9999;
-    color: white;
-  }
-  
-  .caller-info {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    margin-bottom: 80px;
-  }
-  
-  .avatar {
-    width: 80px;
-    height: 80px;
-    border-radius: 50%;
-    margin-bottom: 16px;
-    border: 2px solid rgba(255, 255, 255, 0.2);
-  }
-  
-  .name {
-    font-size: 20px;
-    font-weight: bold;
-    margin-bottom: 8px;
-  }
-  
-  .status {
-    font-size: 14px;
-    color: #ccc;
-  }
-  
-  .btn-group {
-    display: flex;
-    gap: 60px;
-  }
+  rtcStore.imSate.videoCallModal = false;
+  inCall.value = false;
+}
+
+watch(
+  () => rtcStore.remoteStream,
+  (val) => {
+    if (val && rtcStore.streamType == "audio") {
   
-  .btn {
-    width: 64px;
-    height: 64px;
-    border-radius: 50%;
-    border: none;
-    font-size: 14px;
-    color: white;
-    display: flex;
-    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.hangup {
-    background-color: #ff3b30;
-  }
-  
-  .btn.hangup:hover {
-    background-color: #e02d22;
+    }
   }
-  </style>
-  
+);
+ 
+onMounted(() => {
+  wsStore.onMessageCallbacks.push(onMessage);
+});
+
+onBeforeUnmount(() => {
+  wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
+    (cb) => cb !== onMessage
+  );
+});
+</script>
+
+<style scoped lang="less">
+.weixin-call-modal {
+  position: fixed;
+  inset: 0;
+  background-color: rgba(0, 0, 0, 0.85);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+  color: white;
+}
+
+.caller-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 80px;
+}
+
+.avatar {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  margin-bottom: 16px;
+  border: 2px solid rgba(255, 255, 255, 0.2);
+}
+
+.name {
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.status {
+  font-size: 14px;
+  color: #ccc;
+}
+
+.btn-group {
+  display: flex;
+  gap: 60px;
+}
+
+.btn {
+  width: 64px;
+  height: 64px;
+  border-radius: 50%;
+  border: none;
+  font-size: 14px;
+  color: white;
+  display: flex;
+  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.hangup {
+  background-color: #ff3b30;
+}
+
+.btn.hangup:hover {
+  background-color: #e02d22;
+}
+</style>

+ 2 - 0
src/views/im/hook/messagesHook.js

@@ -84,6 +84,7 @@ export const handleMessageHook = (message, state) => {
   ) {
     console.log("收到播号")
     const rtcStore = useWebRTCStore();
+    rtcStore.isCaller = false
     rtcStore.imSate = {
       videoCallModal: true,
       callName: message.fromUsername,
@@ -100,6 +101,7 @@ export const handleMessageHook = (message, state) => {
     console.log("音频在线:取消")
     const rtcStore = useWebRTCStore();
     rtcStore.imSate.videoCallModal = false; 
+    rtcStore.cleanup()
     return;
   }
   // 音频在线:拒接