Files
deskflow/docs/webrtc_demo_caller.html
2026-04-09 10:37:51 +08:00

339 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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">1.获取媒体信息</button>
<button id="callBtn" disabled>2.呼叫</button>
<button id="sendBtn" disabled>发送消息</button>
<input type="text" id="messageInput" placeholder="输入消息">
</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 callBtn = document.getElementById('callBtn');
const sendBtn = document.getElementById('sendBtn');
const messageInput = document.getElementById('messageInput');
const dataChannelLog = document.getElementById('dataChannelLog');
// 初始化WebSocket连接
function initWebSocket() {
// 先关闭旧连接
if (ws) {
try { ws.close() }
catch (e) { }
isWsConnected = false;
}
const user_id = 0;
const wsService = `wss://api.vlos.net/wsSignaling/${user_id}/`
// 创建新连接
// ws = new WebSocket(`ws://localhost:8000/wsSignaling/0/`);
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 'answer':
if (pc) {
await pc.setRemoteDescription(new RTCSessionDescription(message.data));
console.info(`22,请求方监听对方发送过来的answer到本地setRemoteDescription`)
}
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 {
console.info(`2,请求方捕获媒体文件`)
// 获取本地音视频流
// localStream = await navigator.mediaDevices.getUserMedia({video: true,audio: true});
// 获取屏幕共享
localStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
localVideo.srcObject = localStream;
addToLog(`捕获本地媒体`);
// 创建 PeerConnection
createPeerConnection();
addToLog(`创建 PeerConnection`);
// 启用呼叫按钮
startBtn.disabled = true;
callBtn.disabled = false;
} catch (error) {
console.error('获取媒体流失败:', error);
alert('无法访问摄像头/麦克风,请检查权限');
}
});
// 呼叫按钮 - 请求呼叫
callBtn.addEventListener('click', async () => {
try {
// 创建数据通道
dataChannel = pc.createDataChannel('chat');
setupDataChannel();
addToLog(`创建dataChannel`);
console.info(`7,请求方创建dataChannel`)
// 创建 offer
const offer = await pc.createOffer();
console.info(`8,请求方创建offer`)
await pc.setLocalDescription(offer);
console.info(`9,请求方保存自己生成的信令`)
addToLog(`创建offer`);
// 发送 offer 到信令服务器
ws.send(JSON.stringify({ type: 'offer', data: offer }));
addToLog(`发送offer`);
console.info(`10,请求方发送信令(offer)到远程端`)
// callBtn.disabled = true;
sendBtn.disabled = false;
messageInput.disabled = false;
} catch (error) {
console.error('请求呼叫失败:', error);
}
});
// 发送数据通道消息
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
addToLog(`dataChannel.readyState-> ${dataChannel.readyState}`);
if (!message || !dataChannel || dataChannel.readyState !== 'open') {
console.info(`message:${message}`)
console.info(`dataChannel:${dataChannel}`)
console.info(`dataChannel.readyState:${dataChannel.readyState}`)
return
};
dataChannel.send(message);
addToLog(`请求方: ${message}`);
messageInput.value = '';
});
// 创建 PeerConnection
async function createPeerConnection() {
// 配置 ICE 服务器 (使用免费的谷歌服务器)
const config = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
};
// 创建 PeerConnection
pc = new RTCPeerConnection(config);
console.info(`3,创建请求方PeerConnection`)
// 监听 ICE 候选
pc.onicecandidate = (event) => {
if (event.candidate) {
// 使用安全发送方法
clientCandidates.push(event.candidate);
// sendSignalingMessage({type: 'candidate',data: event.candidate});
}
};
console.info(`监听ICE 候选 ->${clientCandidates}`);
console.info(`6,请求方监听请求方自己信令ice,并发送给监听方`)
// 添加本地媒体流到PeerConnection
localStream.getTracks().forEach(track => { pc.addTrack(track, localStream) });
console.info(`4,请求方添加本地媒体到PeerConnection`)
// 监听远程流
pc.ontrack = (event) => { remoteVideo.srcObject = event.streams[0] };
console.info(`5,请求方监听远程媒体remoteVideo`)
// 监听连接状态变化
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;
callBtn.disabled = true;
sendBtn.disabled = true;
messageInput.disabled = true;
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>