var pc = null; function negotiate() { pc.addTransceiver('video', { direction: 'recvonly' }); pc.addTransceiver('audio', { direction: 'recvonly' }); return pc.createOffer().then((offer) => { console.log('Created offer: type=' + offer.type + ', sdp_length=' + offer.sdp.length); return pc.setLocalDescription(offer).then(() => { console.log('Set local description complete, iceGatheringState=' + pc.iceGatheringState); }); }).then(() => { // wait for ICE gathering to complete return new Promise((resolve, reject) => { if (pc.iceGatheringState === 'complete') { console.log('ICE gathering already complete'); resolve(); } else { const checkState = () => { if (pc.iceGatheringState === 'complete') { pc.removeEventListener('icegatheringstatechange', checkState); console.log('ICE gathering complete'); resolve(); } }; pc.addEventListener('icegatheringstatechange', checkState); // 5秒超时 setTimeout(() => { pc.removeEventListener('icegatheringstatechange', checkState); resolve(); // 继续执行,即使 ICE 汇集未完成 }, 5000); } }); }).then(() => { var offer = pc.localDescription; console.log('Sending offer: type=' + offer.type + ', sdp_length=' + offer.sdp.length); return fetch('/offer', { body: JSON.stringify({ sdp: offer.sdp, type: offer.type, }), headers: { 'Content-Type': 'application/json' }, method: 'POST' }).then((response) => { console.log('Received response status:', response.status); return response; }); }).then((response) => { if (!response.ok) { throw new Error('服务器返回错误: ' + response.status); } return response.json(); }).then((answer) => { console.log('Received answer: type=' + answer.type + ', sdp_length=' + answer.sdp.length); document.getElementById('sessionid').value = answer.sessionid return pc.setRemoteDescription(answer).then(() => { console.log('Successfully set remote description'); }); }).catch((e) => { console.error('Error in negotiate:', e); let errorMsg = '连接失败: '; if (e.name === 'InvalidStateError') { errorMsg += '连接状态异常,请刷新页面后重试'; } else if (e.name === 'SyntaxError') { errorMsg += '服务器响应格式错误,请检查服务器状态'; } else if (e.message && e.message.includes('state')) { errorMsg += '连接状态错误,请刷新页面后重试'; } else if (e.message && e.message.includes('服务器返回错误')) { errorMsg += e.message + ',请检查后端日志'; } else { errorMsg += e.message || e.toString(); } alert(errorMsg); }); } function start() { var config = { sdpSemantics: 'unified-plan' }; // 使用多个 STUN 服务器(提高连接成功率) config.iceServers = [ { urls: ['stun:stun.l.google.com:19302'] }, { urls: ['stun:stun1.l.google.com:19302'] }, { urls: ['stun:stun.services.mozilla.com'] }, { urls: ['stun:stun.voip.blackberry.com:3478'] } ]; pc = new RTCPeerConnection(config); // 添加连接状态日志 pc.addEventListener('connectionstatechange', function() { console.log('PeerConnection state:', pc.connectionState); // 将日志发送到后端 fetch('/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'webrtc', message: 'PeerConnection state: ' + pc.connectionState }) }).catch(() => {}); // 连接成功时的提示 if (pc.connectionState === 'connected') { console.log('✅ WebRTC connection established successfully!'); document.getElementById('startBtn').style.display = 'none'; document.getElementById('stopBtn').style.display = 'inline-block'; } else if (pc.connectionState === 'failed') { console.error('❌ WebRTC connection failed!'); alert('WebRTC 连接失败,请检查网络连接或刷新页面重试'); } }); pc.addEventListener('iceconnectionstatechange', function() { console.log('ICE connection state:', pc.iceConnectionState); fetch('/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'webrtc', message: 'ICE connection state: ' + pc.iceConnectionState }) }).catch(() => {}); // ICE 连接超时处理 if (pc.iceConnectionState === 'disconnected') { console.warn('⚠️ ICE disconnected, attempting to reconnect...'); } else if (pc.iceConnectionState === 'failed') { console.error('❌ ICE connection failed!'); alert('ICE 连接失败,可能是网络防火墙阻止了 WebRTC 连接'); } }); pc.addEventListener('icegatheringstatechange', function() { console.log('ICE gathering state:', pc.iceGatheringState); fetch('/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'webrtc', message: 'ICE gathering state: ' + pc.iceGatheringState }) }).catch(() => {}); }); // 收集 ICE 候选 pc.addEventListener('icecandidate', function(event) { if (event.candidate) { console.log('ICE candidate:', event.candidate.candidate); fetch('/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'webrtc', message: 'ICE candidate: ' + event.candidate.candidate }) }).catch(() => {}); } else { console.log('ICE gathering complete'); fetch('/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'webrtc', message: 'ICE gathering complete' }) }).catch(() => {}); } }); // connect audio / video pc.addEventListener('track', (evt) => { if (evt.track.kind == 'video') { const video = document.getElementById('video'); if (video) { video.srcObject = evt.streams[0]; console.log('Video track attached successfully'); } else { console.error('Video element not found in DOM'); } } else if (evt.track.kind == 'audio') { const audio = document.getElementById('audio'); if (audio) { audio.srcObject = evt.streams[0]; console.log('Audio track attached successfully'); // 尝试播放音频(处理浏览器自动播放策略) audio.play().catch(e => { console.warn('Auto-play was prevented, user interaction needed:', e); }); } else { console.warn('Audio element not found in DOM, but continuing with video only'); } } }); document.getElementById('startBtn').style.display = 'none'; negotiate(); document.getElementById('stopBtn').style.display = 'inline-block'; } function stop() { document.getElementById('stopBtn').style.display = 'none'; // close peer connection setTimeout(() => { pc.close(); }, 500); } window.onunload = function(event) { // 在这里执行你想要的操作 setTimeout(() => { pc.close(); }, 500); }; window.onbeforeunload = function (e) { setTimeout(() => { pc.close(); }, 500); e = e || window.event // 兼容IE8和Firefox 4之前的版本 if (e) { e.returnValue = '关闭提示' } // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+ return '关闭提示' }