|
@@ -1,6 +1,11 @@
|
|
|
import { defineStore } from "pinia";
|
|
|
-import { MSG_TYPE, MESSAGE_TYPE_USER, MESSAGE_TYPE_GROUP } from "@/common/constant/msgType";
|
|
|
+import {
|
|
|
+ MSG_TYPE,
|
|
|
+ MESSAGE_TYPE_USER,
|
|
|
+ MESSAGE_TYPE_GROUP,
|
|
|
+} from "@/common/constant/msgType";
|
|
|
import { useWebSocketStore } from "@/stores/modules/webSocketStore";
|
|
|
+import * as Constant from "@/common/constant/Constant";
|
|
|
|
|
|
export const useWebRTCStore = defineStore("webrtc", {
|
|
|
state: () => ({
|
|
@@ -9,13 +14,17 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
|
|
|
// ICE 候选信息
|
|
|
iceCandidates: [],
|
|
|
+ pendingIceCandidates: [], // 缓存未处理的候选
|
|
|
|
|
|
// 连接状态
|
|
|
connectionState: "disconnected",
|
|
|
|
|
|
// 媒体流
|
|
|
- localStream: null,
|
|
|
- remoteStream: null,
|
|
|
+ localStream: null, // 本地媒体流
|
|
|
+ remoteStream: null, // 远端媒体流
|
|
|
+
|
|
|
+ // 是否是发起方
|
|
|
+ isCaller: false,
|
|
|
|
|
|
// 配置项
|
|
|
config: {
|
|
@@ -24,11 +33,33 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
// 可以添加更多 STUN/TURN 服务器
|
|
|
],
|
|
|
},
|
|
|
- }),
|
|
|
+ }),
|
|
|
actions: {
|
|
|
+ bindRemoteAudio() {
|
|
|
+ // 如果已经存在 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(MESSAGE_TYPE) {
|
|
|
+ initConnection(isCaller) {
|
|
|
this.cleanup();
|
|
|
+ this.isCaller = isCaller;
|
|
|
+
|
|
|
const wsStore = useWebSocketStore();
|
|
|
try {
|
|
|
this.peerConnection = new RTCPeerConnection();
|
|
@@ -36,21 +67,14 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
// 设置事件监听: 对等方收到ice信息后,通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理
|
|
|
this.peerConnection.onicecandidate = (event) => {
|
|
|
if (event.candidate) {
|
|
|
- // if (MESSAGE_TYPE === MESSAGE_TYPE_USER) {
|
|
|
- // let candidate = {
|
|
|
- // type: "offer_ice",
|
|
|
- // iceCandidate: event.candidate,
|
|
|
- // };
|
|
|
- // wsStore.sendMessage({
|
|
|
- // contentType: MSG_TYPE.AUDIO_ONLINE,
|
|
|
- // type: "webrtc",
|
|
|
- // messageType: MESSAGE_TYPE,
|
|
|
- // content: JSON.stringify(candidate),
|
|
|
- // });
|
|
|
- // }
|
|
|
- // if (MESSAGE_TYPE === MESSAGE_TYPE_GROUP) {
|
|
|
-
|
|
|
- // }
|
|
|
+ let candidate = {
|
|
|
+ type: this.isCaller ? "offer_ice" : "answer_ice",
|
|
|
+ iceCandidate: event.candidate,
|
|
|
+ };
|
|
|
+ wsStore.sendMessage({
|
|
|
+ content: JSON.stringify(candidate),
|
|
|
+ type: Constant.MESSAGE_TRANS_TYPE,
|
|
|
+ });
|
|
|
this.iceCandidates.push(event.candidate);
|
|
|
}
|
|
|
};
|
|
@@ -69,6 +93,21 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
event.streams[0].getTracks().forEach((track) => {
|
|
|
this.remoteStream.addTrack(track);
|
|
|
});
|
|
|
+ // 绑定音频
|
|
|
+ this.bindRemoteAudio()
|
|
|
+ };
|
|
|
+
|
|
|
+ // 监听 ICE 连接状态(关键修复!)
|
|
|
+ this.peerConnection.oniceconnectionstatechange = () => {
|
|
|
+ const state = this.peerConnection.iceConnectionState;
|
|
|
+ console.log("ICE 连接状态:", state);
|
|
|
+
|
|
|
+ if (state === "connected") {
|
|
|
+ console.log("✅ P2P 连接成功,可以开始语音通话!");
|
|
|
+ } else if (state === "failed") {
|
|
|
+ console.error("❌ ICE 连接失败,尝试重启...");
|
|
|
+ this.restartICE();
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
console.log("WebRTC 连接初始化成功");
|
|
@@ -123,36 +162,54 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 设置远程 Description
|
|
|
+ // 设置远程描述后处理缓存
|
|
|
async setRemoteDescription(desc) {
|
|
|
- if (!this.peerConnection) {
|
|
|
- throw new Error("WebRTC 连接未初始化");
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
-
|
|
|
- // 设置远程描述
|
|
|
- await this.peerConnection.setRemoteDescription(desc);
|
|
|
- } catch (error) {
|
|
|
- console.error("设置远程 Description 失败:", error);
|
|
|
- throw error;
|
|
|
+ await this.peerConnection.setRemoteDescription(desc);
|
|
|
+ // 处理缓存的候选
|
|
|
+ while (this.pendingIceCandidates.length > 0) {
|
|
|
+ const candidate = this.pendingIceCandidates.shift();
|
|
|
+ await this.peerConnection
|
|
|
+ .addIceCandidate(candidate)
|
|
|
+ .catch((e) => console.error(e));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 添加 ICE 候选
|
|
|
async addIceCandidate(candidate) {
|
|
|
- if (!this.peerConnection) {
|
|
|
- throw new Error("WebRTC 连接未初始化");
|
|
|
+ if (!candidate) {
|
|
|
+ console.warn("收到空的 ICE 候选");
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ // 如果是候选结束信号(candidate:null)
|
|
|
+ if (candidate.candidate === "") {
|
|
|
+ console.log("ICE 候选收集完成");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
+ // 确保 PeerConnection 和远程描述已就绪
|
|
|
+ if (!this.peerConnection) {
|
|
|
+ this.pendingIceCandidates.push(candidate);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Answer 方必须等待远程描述
|
|
|
+ if (!this.peerConnection.remoteDescription && !this.isCaller) {
|
|
|
+ this.pendingIceCandidates.push(candidate);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
await this.peerConnection.addIceCandidate(candidate);
|
|
|
+ console.log("✅ 成功添加 ICE 候选:", candidate.candidate);
|
|
|
} catch (error) {
|
|
|
- console.error("添加 ICE 候选失败:", error);
|
|
|
- throw error;
|
|
|
+ console.error("❌ 添加 ICE 候选失败:", error);
|
|
|
+ // 失败后重试缓存
|
|
|
+ this.pendingIceCandidates.push(candidate);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 清理资源
|
|
|
+ // 清理资源:挂断
|
|
|
cleanup() {
|
|
|
if (this.peerConnection) {
|
|
|
this.peerConnection.close();
|
|
@@ -169,3 +226,5 @@ export const useWebRTCStore = defineStore("webrtc", {
|
|
|
},
|
|
|
},
|
|
|
});
|
|
|
+
|
|
|
+
|