项目备份

This commit is contained in:
林觅
2026-04-09 10:37:51 +08:00
parent 7ed8d2dcb4
commit 4eab443148
32 changed files with 9468 additions and 0 deletions

609
module/newWebRtcSync.js Normal file
View File

@@ -0,0 +1,609 @@
/**
* 【发起方】
1. 创建 PeerConnection
2. 采集本地流并添加到 PeerConnection
3. 调用 createOffer() → 生成Offer
4. 调用 setLocalDescription(offer) → 开始收集ICE候选
5. 通过信令发送Offer给接收方
6. 监听 onicecandidate → 发送ICE候选给接收方
7. 接收接收方的Answer → 调用 setRemoteDescription(answer)
8. 接收接收方的ICE候选 → 调用 addIceCandidate(candidate)
【接收方】
1. 创建 PeerConnection
2. 接收发起方的Offer → 调用 setRemoteDescription(offer)
3. 调用 createAnswer() → 生成Answer
4. 调用 setLocalDescription(answer) → 开始收集ICE候选
5. 通过信令发送Answer给发起方
6. 监听 onicecandidate → 发送ICE候选给发起方
7. 接收发起方的ICE候选 → 调用 addIceCandidate(candidate)
*/
/**
* WebRTC 核心封装类(ES6 模块化)
* 支持音视频互通、自定义数据传输、外部流管理
* 开箱即用,无需额外依赖
*/
export class WebRTC_ModuleSimple {
/**
* 构造函数
* @param {Object} options 初始化配置
* @param {HTMLElement} options.localVideo 本地视频播放元素
* @param {HTMLElement} options.remoteVideo 远端视频播放元素
* @param {string} options.dataChannelLabel 数据通道标签(默认 'chat')
* @param {Function} receivepeerCallback 回调函数
*/
constructor(options = {}) {
this.current_time = new Date();
// 基础配置
this.config = {
localVideo: options.localVideo,
remoteVideo: options.remoteVideo,
dataChannelLabel: options.dataChannelLabel || "chat",
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun.cloudflare.com:3478" },
],
};
// WebRTC 核心对象
this.peerConnection = null;
this.dataChannel = null;
// 媒体流管理
this.localStream = null; // 本地流(可外部传入/获取)
this.remoteStream = null; // 远端流(可外部获取)
// 事件回调(外部可自定义)
this.callbacks = {
onIceCandidate: (candidate) => {}, // ICE 候选生成
onConnectSuccess: () => {}, // 连接成功
onConnectFailed: () => {}, // 连接失败
onRemoteStream: (stream) => {}, // 收到远端流
onMessage: (data) => {}, // 收到自定义消息
onClose: () => {}, // 连接关闭
onState: (state) => {},
};
// 初始化 PeerConnection
this._initPeerConnection();
}
/**
* 初始化 RTCPeerConnection 核心对象
* @private
*/
_initPeerConnection() {
try {
// 销毁旧实例
if (this.peerConnection) {
this.peerConnection.close();
}
// 浏览器兼容处理
const RTCPeerConnection =
window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection;
this.peerConnection = new RTCPeerConnection({
iceServers: this.config.iceServers,
});
// 监听信令协商状态
this.signalingStateListener();
// 监听远程信息
this.peerConnectionLisioner();
} catch (error) {
console.error("WebRTC 初始化失败:", error);
this.callbacks.onConnectFailed();
}
}
// 监听信令状态变化(Offer/Answer/Ice 协商)
signalingStateListener() {
// 初始状态打印
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}初始信令状态:`,
this.peerConnection.signalingState,
);
// 监听信令状态变化(Offer/Answer 协商)
this.peerConnection.onsignalingstatechange = () => {
/**
* 信令状态枚举值(全阶段)
* new 初始状态
* have-local-offer 本地已设置 Offer 发起者:已调用 createOffer() + setLocalDescription(offer)
* have-remote-offer 远端已设置 Offer 应答者:已调用 setRemoteDescription(offer)
* have-local-answer 本地已设置 Answer 应答者:已调用 createAnswer() + setLocalDescription(answer)
* have-remote-answer 远端已设置 Answer 发起者:已调用 setRemoteDescription(answer)
* stable 协商完成,稳定状态
* closed PeerConnection 已关闭
*/
const state = this.peerConnection.signalingState;
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}初始信令变更:`,
state,
);
state && this.callbacks.onState(state);
// 根据状态执行不同逻辑
switch (state) {
case "new":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】未开始协商,可创建 Offer`,
);
break;
case "have-local-offer":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】发起者已发 Offer,等待应答者 Answer`,
);
// 可触发 ICE 候选者发送逻辑
break;
case "have-remote-offer":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】应答者已收 Offer,可创建 Answer`,
);
break;
case "stable":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】Offer/Answer 协商完成,进入 ICE 配对阶段`,
);
break;
case "closed":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】PeerConnection 已关闭,协商终止`,
);
break;
default:
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【状态】协商中:`,
state,
);
}
};
// ICE 候选者收集 / 配对 / 连接
this.peerConnection.onicegatheringstatechange = () => {
/**
* ICE 连接状态iceConnectionState
* new 初始状态,未开始连接
* checking 正在检查候选者配对
* connected 已建立连接P2P 成功)
* completed 连接完成(所有候选者检查完毕)
* failed ICE 协商失败
* disconnected 连接断开(临时)
* closed PeerConnection 已关闭
*/
const state = this.peerConnection.iceGatheringState;
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}ICE 收集状态变更:`,
state,
);
state && this.callbacks.onState(state);
switch (state) {
case "gathering":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【ICE】开始收集候选者(host/srflx/relay)`,
state,
);
break;
case "complete":
console.warn(
`[webRTC]${this.current_time.toLocaleTimeString()}【ICE】候选者收集完成`,
state,
);
break;
}
};
}
peerConnectionLisioner() {
if (!this.peerConnection) {
return;
}
// 监听远端媒体流
this.peerConnection.ontrack = (e) => {
this.remoteStream = e.streams[0];
// 绑定远端视频
if (this.config.remoteVideo) {
this.config.remoteVideo.srcObject = this.remoteStream;
this.config.remoteVideo.play().catch(() => {});
}
this.callbacks.onRemoteStream(this.remoteStream);
};
// 监听peer连接状态
this.peerConnection.onconnectionstatechange = () => {
const state = this.peerConnection.connectionState;
if (state === "connected") {
this.callbacks.onConnectSuccess();
} else if (state === "failed") {
this.callbacks.onConnectFailed();
} else if (state === "closed") {
this.callbacks.onClose();
}
};
// 监听远端创建的数据通道
this.peerConnection.ondatachannel = (e) => {
this.dataChannel = e.channel;
this.dataChannelLisioner();
};
// 监听 ICE 候选(需通过信令服务器发给对方)
this.peerConnection.onicecandidate = (e) => {
e.candidate && this.callbacks.onIceCandidate(e.candidate);
};
// 监听 ICE 连接状态,排查失败原因
this.peerConnection.oniceconnectionstatechange = () => {
const state = this.peerConnection.iceConnectionState;
console.info("ICE 连接状态:", state);
// 捕获 ICE failed 状态,给出明确提示
if (state === "failed") {
console.error("ICE 协商失败!请检查 TURN 服务器配置或网络环境");
// 可选:触发重新协商
this.createOffer();
}
};
// 新监听 ICE 候选者生成错误
this.peerConnection.onicecandidateerror = (event) => {
console.error(
"ICE 候选者生成失败:",
event.errorText,
"错误码:",
event.errorCode,
);
};
}
/**
* 初始化数据通道(自定义消息传输)
* @private
*/
dataChannelLisioner() {
if (!this.dataChannel) return;
// 数据通道打开
this.dataChannel.onopen = () => console.info("数据通道已就绪");
// 接收自定义消息(自动解析JSON)
this.dataChannel.onmessage = (e) => {
let data = e.data;
try {
data = JSON.parse(data);
} catch (err) {}
this.callbacks.onMessage(data);
this.dataChannelSendMsg(data);
};
// 数据通道错误
this.dataChannel.onerror = (error) => console.error("数据通道异常:", error);
// 数据通道关闭
this.dataChannel.onclose = () => console.info("数据通道关闭");
}
/**
* 采集本地音视频流(一键调用)
* @param {Object} constraints 媒体约束(默认开启音视频)
* @returns {Promise<MediaStream>} 本地媒体流
*/
async captureLocalStream(constraints = { video: true, audio: true }) {
try {
// getUserMedia 设备硬件:摄像头(视频)、麦克风(音频)
// getDisplayMedia 屏幕 / 窗口 / 标签页:显示器内容(视频),可选采集系统音频
this.localStream =
await navigator.mediaDevices.getDisplayMedia(constraints);
// 绑定本地视频
if (this.config.localVideo) {
this.config.localVideo.srcObject = this.localStream;
this.config.localVideo.muted = true; // 静音避免回声
this.config.localVideo.play().catch(() => {});
}
// 将流添加到连接
this.localStream.getTracks().forEach((track) => {
this.peerConnection.addTrack(track, this.localStream);
});
return this.localStream;
} catch (error) {
console.error("采集本地流失败:", error);
throw new Error(`媒体权限不足或设备不可用: ${error.message}`);
}
}
/**
* 外部传入本地媒体流(支持屏幕共享等自定义流)
* @param {MediaStream} stream 本地媒体流
*/
setLocalStream(stream) {
if (!stream) throw new Error("媒体流不能为空");
this.localStream = stream;
// 绑定视频(可选)
if (this.config.localVideo) {
this.config.localVideo.srcObject = stream;
this.config.localVideo.muted = true;
this.config.localVideo.play().catch(() => {});
}
// 添加到连接
// stream.getTracks().forEach(track => {
// this.peerConnection.addTrack(track, stream);
// });
}
/**
* 获取本地媒体流
* @returns {MediaStream|null} 本地流
*/
getLocalStream() {
return this.localStream;
}
/**
* 获取远端媒体流
* @returns {MediaStream|null} 远端流
*/
getRemoteStream() {
return this.remoteStream;
}
/**
* 发起方:创建 Offer 并开启数据通道
* @returns {Promise<RTCSessionDescription>} Offer 对象
*/
async createOffer() {
// 重置数据通道
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}
// 创建数据通道(发起方主动创建)
this.dataChannel = this.peerConnection.createDataChannel(
this.config.dataChannelLabel,
);
// 监听数据通道
this.dataChannelLisioner();
const offer = await this.peerConnection.createOffer();
// 设置本地offer
await this.peerConnection.setLocalDescription(offer);
return offer;
}
/**
* 接收方:处理远端 Offer 并创建 Answer
* @param {RTCSessionDescription} offer 远端 Offer
* @returns {Promise<RTCSessionDescription>} Answer 对象
*/
async handleRemoteOffer(offer) {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(offer),
);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
return answer;
}
/**
* 发起方:处理远端 Answer
* @param {RTCSessionDescription} answer 远端 Answer
*/
async handleAnswer(answer) {
try {
// 检查当前信令状态,仅在非 stable 时设置
if (this.peerConnection.signalingState !== "stable") {
await this.peerConnection.setRemoteDescription(
new RTCSessionDescription(answer),
);
console.info("远端 Answer 设置成功");
} else {
console.warn("PeerConnection 已处于稳定状态,无需重复设置 Answer");
}
} catch (error) {
console.error("设置远端 Answer 失败:", error);
}
}
/**
* 添加远端 ICE 候选
* @param {RTCIceCandidate} candidate ICE 候选对象
*/
async addIceCandidate(candidate) {
if (!candidate) return;
try {
if (this.peerConnection && candidate) {
await this.peerConnection.addIceCandidate(
new RTCIceCandidate(candidate),
);
console.info("ICE 候选者添加成功", candidate);
} else return;
} catch (error) {
console.info(
"操作失败,需要先设置【远端的SDP】:步骤1先设置远端 Offer(发起者的 SDP)步骤2再添加 ICE 候选者(此时已有 remoteDescription)步骤3创建并设置本地 Answer(依赖 remoteDescription)",
);
console.error(error);
if (error.message.includes("Unknown ufrag")) {
// ufrag 不匹配,说明是旧会话候选者,直接丢弃
console.warn("ICE 候选者 ufrag 不匹配,丢弃:", candidate);
} else {
// 其他错误(如未设置 remoteDescription),缓存候选者
console.info("缓存 ICE 候选者,待会话就绪后处理");
}
}
}
/**
* 发送自定义数据(支持字符串/对象)
* @param {string|Object} data 要发送的数据
*/
dataChannelSendMsg(data) {
if (!this.dataChannel || this.dataChannel.readyState !== "open") {
throw new Error("数据通道未连接,无法发送消息");
}
const sendData = typeof data === "object" ? JSON.stringify(data) : data;
this.dataChannel.send(sendData);
}
/**
* 关闭连接并清理所有资源
*/
close() {
// 关闭数据通道
if (this.dataChannel) {
this.dataChannel.close();
this.dataChannel = null;
}
// 关闭 PeerConnection
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
// 停止媒体流
if (this.localStream) {
this.localStream.getTracks().forEach((track) => track.stop());
this.localStream = null;
}
// 清空视频
if (this.config.localVideo) this.config.localVideo.srcObject = null;
if (this.config.remoteVideo) this.config.remoteVideo.srcObject = null;
this.callbacks.onClose();
console.log("WebRTC 连接已完全关闭");
}
/**
* 同步暂停指定毫秒数(阻塞主线程) 延迟一段时间运行 await this.delay(100);
* @param {number} ms 暂停毫秒数
*/
delayAsync(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 同步暂停指定毫秒数(阻塞主线程)
* @param {number} ms 暂停毫秒数
*/
delaySync(ms) {
const start = Date.now();
// 循环等待,直到时间差达到指定毫秒数
while (Date.now() - start < ms) {
// 空循环,消耗时间
}
}
/**
* 注册事件回调
* @param {Object} handlers 回调函数集合
* 示例rtc.on({ onMessage: (data) => console.log(data) })
*/
on(handlers) {
Object.keys(handlers).forEach((key) => {
if (this.callbacks[key] && typeof handlers[key] === "function") {
this.callbacks[key] = handlers[key];
}
});
}
}
// 封装websocket
export class webSocket_ModuleSimple {
constructor(url, receiveMessageCallback = null) {
this.url = url;
this.socket = null;
this.heartbeatInterval = 30000; // 心跳间隔 30s
this.reconnectInterval = 10000; // 重连间隔 10s
this.maxReconnectAttempts = 5;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.stopWs = false;
this.receiveMessageCallback = receiveMessageCallback; // 接收消息回调函数
this.callbacks = {
onMessage: (data) => {}, // 收到自定义消息
};
}
connect() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) return;
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log("连接成功", this.url);
this.reconnectAttempts = 0;
this.startHeartbeat();
};
this.socket.onmessage = (event) => {
// console.log('收到消息:', event.data);
this.receiveMessage(event);
this.startHeartbeat(); // 收到消息重置心跳
};
this.socket.onclose = () => {
console.log("连接关闭", this.url);
if (!this.stopWs) this.handleReconnect();
};
this.socket.onerror = () => {
console.log("连接错误", this.url);
this.closeHeartbeat();
};
}
/**
* 同步暂停指定毫秒数(阻塞主线程) 延迟一段时间运行 await this.delay(100);
* @param {number} ms 暂停毫秒数
*/
delayAsync(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 同步暂停指定毫秒数(阻塞主线程)
* @param {number} ms 暂停毫秒数
*/
delaySync(ms) {
const start = Date.now();
// 循环等待,直到时间差达到指定毫秒数
while (Date.now() - start < ms) {
// 空循环,消耗时间
}
}
async send(message) {
if (this.socket?.readyState === WebSocket.OPEN) {
await this.delayAsync(500);
this.socket.send(JSON.stringify(message));
} else {
console.error("WebSocket 未连接");
}
}
startHeartbeat() {
this.closeHeartbeat();
this.heartbeatTimer = setInterval(() => {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ type: "heartBeat" }));
// console.log('发送心跳包');
}
}, this.heartbeatInterval);
}
closeHeartbeat() {
clearInterval(this.heartbeatTimer);
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
console.log(`尝试重连 (${++this.reconnectAttempts})`);
this.connect();
}, this.reconnectInterval);
} else {
console.warn("达到最大重连次数,停止重连");
}
}
close() {
this.stopWs = true;
this.socket?.close();
this.closeHeartbeat();
}
/**
* 接收到消息
*/
receiveMessage(event) {
// 根据业务自行处理
// console.info('[webSocket_ModuleSimple]receiveMessage:', event.data)
this.receiveMessageCallback && this.receiveMessageCallback(event.data);
this.callbacks.onMessage(event.data);
}
/**
* 注册事件回调
* @param {Object} handlers 回调函数集合
* 示例rtc.on({ onMessage: (data) => console.log(data) })
*/
on(handlers) {
Object.keys(handlers).forEach((key) => {
if (this.callbacks[key] && typeof handlers[key] === "function") {
this.callbacks[key] = handlers[key];
}
});
}
}
// ---------------------------------------------->
// 验证与使用
// const receiveMessage = (res) => {
// console.log('接收消息回调:', res)
// }
// const wsClient = new WebSocketClient('`ws://localhost:8000/ws/0/`', receiveMessage);
// wsClient.connect();
// 发送业务消息
// wsClient.send({ type: 'message', data: 'Hello Server' });