339 lines
10 KiB
HTML
339 lines
10 KiB
HTML
<!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> |