项目备份

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

View File

@@ -0,0 +1,340 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebRTC 接收方</title>
<style>
.video-container {
display: flex;
gap: 20px;
margin: 20px 0;
}
video {
width: 400px;
height: 300px;
border: 1px solid #ccc;
}
.controls {
margin: 20px 0;
}
button {
padding: 10px 20px;
margin-right: 10px;
}
#dataChannelLog {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
height: 240px;
overflow-y: auto;
}
</style>
</head>
<body>
<h1>WebRTC 接收方</h1>
<div class="video-container">
<div>
<h3>本地视频</h3>
<video id="localVideo" autoplay muted></video>
</div>
<div>
<h3>远程视频</h3>
<video id="remoteVideo" autoplay></video>
</div>
</div>
<div class="controls">
<button id="startBtn">3.获取媒体信息</button>
<button id="answerBtn" disabled>4.接听</button>
<button id="sendBtn" disabled>发送消息</button>
<input type="text" id="messageInput" placeholder="输入消息" disabled>
</div>
<div>
<h3>数据通道日志</h3>
<div id="dataChannelLog"></div>
</div>
<script>
// 全局变量
let localStream;
let pc;
let dataChannel;
let clientCandidates = [];
let ws; // 改为let方便重新创建
let isWsConnected = false; // WebSocket连接状态标记
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const startBtn = document.getElementById('startBtn');
const answerBtn = document.getElementById('answerBtn');
const sendBtn = document.getElementById('sendBtn');
const messageInput = document.getElementById('messageInput');
const dataChannelLog = document.getElementById('dataChannelLog');
let offerData = null;
// 初始化WebSocket连接
function initWebSocket() {
// 先关闭旧连接
if (ws) {
try { ws.close() }
catch (e) { }
}
const user_id = 1;
const wsService = `wss://api.vlos.net/wsSignaling/${user_id}/`
// 创建新连接
// ws = new WebSocket(`ws://localhost:8000/wsSignaling/1/`);
ws = new WebSocket(wsService);
// WebSocket连接成功
ws.onopen = () => {
console.info('WebSocket已连接');
isWsConnected = true;
addToLog('信令通道已建立');
};
// WebSocket 响应消息处理
ws.onmessage = async (event) => {
if (!isWsConnected) return;
const message = JSON.parse(event.data);
console.info('收到信令消息:', message.type);
try {
switch (message.type) {
case 'offer':
if (pc) {
offerData = message.data;
console.info("offerData", offerData)
// 如果已初始化,直接启用接听按钮
if (pc) { answerBtn.disabled = false; }
}
break;
case 'candidate':
if (message.data && pc) {
await pc.addIceCandidate(new RTCIceCandidate(message.data));
}
console.info(`17,发起方响应响应方发送过来的远程候选`)
break;
case 'hangup':
handleHangup();
break;
}
} catch (error) {
console.error('处理信令消息出错:', error);
addToLog(`处理信令错误: ${error.message}`);
}
};
// WebSocket关闭
ws.onclose = () => {
console.info('WebSocket已关闭');
isWsConnected = false;
addToLog('信令通道已关闭');
// 自动重连逻辑(可选)
setTimeout(() => {
if (!isWsConnected) {
addToLog('尝试重新连接信令服务器...');
initWebSocket();
}
}, 3000);
};
// WebSocket错误
ws.onerror = (error) => {
console.error('WebSocket错误:', error);
isWsConnected = false;
addToLog(`信令通道错误: ${error.message}`);
};
}
// 安全的WebSocket发送方法
function sendSignalingMessage(message) {
// 检查WebSocket状态
if (!ws || !isWsConnected || ws.readyState !== WebSocket.OPEN) {
addToLog('无法发送信令消息:信令通道未连接');
return false;
}
try {
ws.send(JSON.stringify(message));
return true;
} catch (error) {
console.error('发送信令消息失败:', error);
addToLog(`发送信令失败: ${error.message}`);
return false;
}
}
// 初始化WebSocket
initWebSocket();
// 开始按钮 - 获取本地媒体流
startBtn.addEventListener('click', async () => {
try {
// 获取本地音视频流
// localStream = await navigator.mediaDevices.getUserMedia({video: true,audio: true});
// 获取屏幕共享
localStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
localVideo.srcObject = localStream;
// 创建 PeerConnection
createPeerConnection();
// 如果已有 offer启用接听按钮
if (offerData) { answerBtn.disabled = false; }
console.info(`17,响应方响应发起方过来的offer`, offerData)
startBtn.disabled = true;
} catch (error) {
console.error('获取媒体流失败:', error);
alert('无法访问摄像头/麦克风,请检查权限');
}
});
// 接听按钮 - 应答呼叫
answerBtn.addEventListener('click', async () => {
try {
if (!offerData) return;
addToLog(`初始化answer`)
// 设置远程描述
await pc.setRemoteDescription(new RTCSessionDescription(offerData));
console.info(`18,响应方将发起方发送过来的offer设置到本地pc中的远程描述setRemoteDescription`)
// 创建 answer
const answer = await pc.createAnswer();
console.info(`19,响应方创建anser`)
await pc.setLocalDescription(answer);
console.info(`20,响应方设置answer到本地pc中setLocalDescription`)
// 发送 answer 到信令服务器
ws.send(JSON.stringify({ type: 'answer', data: answer }));
console.info(`21,响应方发送answer到发起方`)
// answerBtn.disabled = true;
sendBtn.disabled = false;
messageInput.disabled = false;
} catch (error) {
console.error('应答呼叫失败:', error);
}
});
// 发送数据通道消息
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
if (!message || !dataChannel || dataChannel.readyState !== 'open') return;
dataChannel.send(message);
addToLog(`响应方: ${message}`);
messageInput.value = '';
});
// 创建 PeerConnection
function createPeerConnection() {
// 配置 ICE 服务器
const config = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
};
// 创建 PeerConnection
pc = new RTCPeerConnection(config);
console.info(`12,响应方创建自身PeerConnection`)
// 添加本地媒体流到 PeerConnection
localStream.getTracks().forEach(track => { pc.addTrack(track, localStream) });
console.info(`13,响应方添加本地媒体流到 PeerConnection`)
// 响应远程流
pc.ontrack = (event) => { remoteVideo.srcObject = event.streams[0] };
console.info(`14,响应方响应远程流`)
// 响应数据通道
pc.ondatachannel = (event) => {
dataChannel = event.channel;
setupDataChannel();
};
console.info(`15,响应方响应数据通道`)
// 响应 ICE 候选
pc.onicecandidate = (event) => {
if (event.candidate) {
let ws_json = JSON.stringify({ type: 'candidate', data: event.candidate });
ws.send(ws_json);
console.info(`响应 ICE 候选->${ws_json}`)
}
};
console.info(`16,响应方响应响应方自身ICE候选,并发送到发起方`)
// 响应连接状态变化
pc.oniceconnectionstatechange = () => {
console.info('ICE 连接状态:', pc.iceConnectionState);
if (pc.iceConnectionState === 'disconnected') {
handleHangup();
}
};
}
// 设置数据通道
function setupDataChannel() {
// 数据通道打开
dataChannel.onopen = () => {
addToLog('数据通道已打开');
};
// 收到数据通道消息
dataChannel.onmessage = (event) => {
addToLog(`对方: ${event.data}`);
};
// 数据通道关闭
dataChannel.onclose = () => {
addToLog('数据通道已关闭');
sendBtn.disabled = true;
messageInput.disabled = true;
};
// 数据通道错误
dataChannel.onerror = (error) => {
console.error('数据通道错误:', error);
addToLog(`数据通道错误: ${error.message}`);
};
}
// 处理挂断
function handleHangup() {
if (pc) {
pc.close();
pc = null;
}
if (localStream) {
localStream.getTracks().forEach(
track => track.stop()
);
localStream = null;
}
localVideo.srcObject = null;
remoteVideo.srcObject = null;
startBtn.disabled = false;
answerBtn.disabled = true;
sendBtn.disabled = true;
messageInput.disabled = true;
offerData = null;
addToLog('连接已断开');
}
// 添加日志
function addToLog(message) {
const time = new Date().toLocaleTimeString();
dataChannelLog.innerHTML += `[${time}] ${message}<br>`;
dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
}
// 页面关闭时清理
window.addEventListener('beforeunload', () => {
ws.send(JSON.stringify({ type: 'hangup' }));
if (pc) pc.close();
if (localStream) localStream.getTracks().forEach(track => track.stop());
});
</script>
</body>
</html>