client-v2.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. var pc = null;
  2. function negotiate() {
  3. pc.addTransceiver('video', { direction: 'recvonly' });
  4. pc.addTransceiver('audio', { direction: 'recvonly' });
  5. return pc.createOffer().then((offer) => {
  6. console.log('Created offer: type=' + offer.type + ', sdp_length=' + offer.sdp.length);
  7. return pc.setLocalDescription(offer).then(() => {
  8. console.log('Set local description complete, iceGatheringState=' + pc.iceGatheringState);
  9. });
  10. }).then(() => {
  11. // wait for ICE gathering to complete
  12. return new Promise((resolve, reject) => {
  13. if (pc.iceGatheringState === 'complete') {
  14. console.log('ICE gathering already complete');
  15. resolve();
  16. } else {
  17. const checkState = () => {
  18. if (pc.iceGatheringState === 'complete') {
  19. pc.removeEventListener('icegatheringstatechange', checkState);
  20. console.log('ICE gathering complete');
  21. resolve();
  22. }
  23. };
  24. pc.addEventListener('icegatheringstatechange', checkState);
  25. // 5秒超时
  26. setTimeout(() => {
  27. pc.removeEventListener('icegatheringstatechange', checkState);
  28. resolve(); // 继续执行,即使 ICE 汇集未完成
  29. }, 5000);
  30. }
  31. });
  32. }).then(() => {
  33. var offer = pc.localDescription;
  34. console.log('Sending offer: type=' + offer.type + ', sdp_length=' + offer.sdp.length);
  35. return fetch('/offer', {
  36. body: JSON.stringify({
  37. sdp: offer.sdp,
  38. type: offer.type,
  39. }),
  40. headers: {
  41. 'Content-Type': 'application/json'
  42. },
  43. method: 'POST'
  44. }).then((response) => {
  45. console.log('Received response status:', response.status);
  46. return response;
  47. });
  48. }).then((response) => {
  49. if (!response.ok) {
  50. throw new Error('服务器返回错误: ' + response.status);
  51. }
  52. return response.json();
  53. }).then((answer) => {
  54. console.log('Received answer: type=' + answer.type + ', sdp_length=' + answer.sdp.length);
  55. document.getElementById('sessionid').value = answer.sessionid
  56. return pc.setRemoteDescription(answer).then(() => {
  57. console.log('Successfully set remote description');
  58. });
  59. }).catch((e) => {
  60. console.error('Error in negotiate:', e);
  61. let errorMsg = '连接失败: ';
  62. if (e.name === 'InvalidStateError') {
  63. errorMsg += '连接状态异常,请刷新页面后重试';
  64. } else if (e.name === 'SyntaxError') {
  65. errorMsg += '服务器响应格式错误,请检查服务器状态';
  66. } else if (e.message && e.message.includes('state')) {
  67. errorMsg += '连接状态错误,请刷新页面后重试';
  68. } else if (e.message && e.message.includes('服务器返回错误')) {
  69. errorMsg += e.message + ',请检查后端日志';
  70. } else {
  71. errorMsg += e.message || e.toString();
  72. }
  73. alert(errorMsg);
  74. });
  75. }
  76. function start() {
  77. console.log('🚀 start() function called - initializing WebRTC connection...');
  78. var config = {
  79. sdpSemantics: 'unified-plan'
  80. };
  81. // 使用多个 STUN 服务器(提高连接成功率)
  82. config.iceServers = [
  83. { urls: ['stun:stun.l.google.com:19302'] },
  84. { urls: ['stun:stun1.l.google.com:19302'] },
  85. { urls: ['stun:stun.services.mozilla.com'] },
  86. { urls: ['stun:stun.voip.blackberry.com:3478'] }
  87. ];
  88. pc = new RTCPeerConnection(config);
  89. // 添加连接状态日志
  90. pc.addEventListener('connectionstatechange', function() {
  91. console.log('PeerConnection state:', pc.connectionState);
  92. // 将日志发送到后端
  93. fetch('/log', {
  94. method: 'POST',
  95. headers: { 'Content-Type': 'application/json' },
  96. body: JSON.stringify({ type: 'webrtc', message: 'PeerConnection state: ' + pc.connectionState })
  97. }).catch(() => {});
  98. // 连接成功时的提示
  99. if (pc.connectionState === 'connected') {
  100. console.log('✅ WebRTC connection established successfully!');
  101. } else if (pc.connectionState === 'failed') {
  102. console.error('❌ WebRTC connection failed!');
  103. alert('WebRTC 连接失败,请检查网络连接或刷新页面重试');
  104. }
  105. });
  106. pc.addEventListener('iceconnectionstatechange', function() {
  107. console.log('ICE connection state:', pc.iceConnectionState);
  108. fetch('/log', {
  109. method: 'POST',
  110. headers: { 'Content-Type': 'application/json' },
  111. body: JSON.stringify({ type: 'webrtc', message: 'ICE connection state: ' + pc.iceConnectionState })
  112. }).catch(() => {});
  113. // ICE 连接超时处理
  114. if (pc.iceConnectionState === 'disconnected') {
  115. console.warn('⚠️ ICE disconnected, attempting to reconnect...');
  116. } else if (pc.iceConnectionState === 'failed') {
  117. console.error('❌ ICE connection failed!');
  118. alert('ICE 连接失败,可能是网络防火墙阻止了 WebRTC 连接');
  119. }
  120. });
  121. pc.addEventListener('icegatheringstatechange', function() {
  122. console.log('ICE gathering state:', pc.iceGatheringState);
  123. fetch('/log', {
  124. method: 'POST',
  125. headers: { 'Content-Type': 'application/json' },
  126. body: JSON.stringify({ type: 'webrtc', message: 'ICE gathering state: ' + pc.iceGatheringState })
  127. }).catch(() => {});
  128. });
  129. // 收集 ICE 候选
  130. pc.addEventListener('icecandidate', function(event) {
  131. if (event.candidate) {
  132. console.log('ICE candidate:', event.candidate.candidate);
  133. fetch('/log', {
  134. method: 'POST',
  135. headers: { 'Content-Type': 'application/json' },
  136. body: JSON.stringify({ type: 'webrtc', message: 'ICE candidate: ' + event.candidate.candidate })
  137. }).catch(() => {});
  138. } else {
  139. console.log('ICE gathering complete');
  140. fetch('/log', {
  141. method: 'POST',
  142. headers: { 'Content-Type': 'application/json' },
  143. body: JSON.stringify({ type: 'webrtc', message: 'ICE gathering complete' })
  144. }).catch(() => {});
  145. }
  146. });
  147. // connect audio / video
  148. pc.addEventListener('track', (evt) => {
  149. console.log('📬 Received track event:', evt.track.kind);
  150. if (evt.track.kind == 'video') {
  151. const video = document.getElementById('video');
  152. if (video) {
  153. video.srcObject = evt.streams[0];
  154. console.log('✅ Video track attached successfully');
  155. console.log('Stream ID:', evt.streams[0].id);
  156. console.log('Video track ID:', evt.track.id);
  157. console.log('Video track label:', evt.track.label);
  158. // 尝试播放视频(处理浏览器自动播放策略)
  159. video.play().then(() => {
  160. console.log('✅ Video playback started');
  161. }).catch(e => {
  162. console.warn('⚠️ Video auto-play was prevented:', e);
  163. // 添加点击事件监听器来启动播放
  164. document.addEventListener('click', function startPlayback() {
  165. video.play();
  166. document.removeEventListener('click', startPlayback);
  167. }, { once: true });
  168. });
  169. // 触发连接成功回调
  170. if (window.onWebRTCConnected) {
  171. window.onWebRTCConnected();
  172. }
  173. } else {
  174. console.error('❌ Video element not found in DOM');
  175. }
  176. } else if (evt.track.kind == 'audio') {
  177. // 音频轨道会自动附加到 stream,由 video 元素一起播放
  178. console.log('✅ Audio track received, will play through video element');
  179. }
  180. });
  181. negotiate();
  182. }
  183. function stop() {
  184. // close peer connection
  185. setTimeout(() => {
  186. pc.close();
  187. }, 500);
  188. }
  189. window.onunload = function(event) {
  190. // 在这里执行你想要的操作
  191. setTimeout(() => {
  192. pc.close();
  193. }, 500);
  194. };
  195. window.onbeforeunload = function (e) {
  196. setTimeout(() => {
  197. pc.close();
  198. }, 500);
  199. e = e || window.event
  200. // 兼容IE8和Firefox 4之前的版本
  201. if (e) {
  202. e.returnValue = '关闭提示'
  203. }
  204. // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
  205. return '关闭提示'
  206. }