room.html 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <!-- templates/room.html -->
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <title>语音聊天室 - {{ room_id }}</title>
  6. <!-- 中文-->
  7. <meta charset="UTF-8"></meta>
  8. <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
  9. </head>
  10. <body>
  11. <h1>语音聊天室 {{ room_id }}</h1>
  12. <button id="micButton" onclick="toggleMic()">🎤 点击说话</button>
  13. <div id="status"></div>
  14. <script>
  15. const room_id = "{{ room_id }}";
  16. let isMute = true;
  17. let localStream;
  18. let socket;
  19. const peers = {};
  20. // 初始化WebSocket连接
  21. function initSocket() {
  22. socket = io('https://chatroom.tianyunperfect.cn', {
  23. transports: ['websocket', 'polling'],
  24. upgrade: true,
  25. reconnection: true,
  26. reconnectionAttempts: 5,
  27. path: '/socket.io'
  28. });
  29. socket.on('connect', () => {
  30. console.log('Connected to server');
  31. document.getElementById('status').innerHTML = "已连接到服务器";
  32. socket.emit("join", { room: room_id });
  33. });
  34. socket.on('connect_error', (error) => {
  35. console.error('Connection error:', error);
  36. document.getElementById('status').innerHTML = "连接错误: " + error.message;
  37. });
  38. socket.on('disconnect', (reason) => {
  39. console.log('Disconnected:', reason);
  40. document.getElementById('status').innerHTML = "已断开连接: " + reason;
  41. });
  42. // 其他事件处理...
  43. }
  44. initSocket();
  45. // 添加错误处理
  46. window.onerror = function(message, source, lineno, colno, error) {
  47. console.error('Caught error:', error);
  48. document.getElementById('status').innerHTML = "发生错误: " + message;
  49. };
  50. // 获取麦克风权限
  51. async function init() {
  52. try {
  53. localStream = await navigator.mediaDevices.getUserMedia({
  54. audio: true,
  55. });
  56. document.getElementById("status").innerHTML = "麦克风已就绪";
  57. document.getElementById("micButton").disabled = false;
  58. } catch (err) {
  59. console.error("Error accessing microphone:", err);
  60. document.getElementById("status").innerHTML = "无法访问麦克风: " + err.message;
  61. document.getElementById("micButton").disabled = true;
  62. }
  63. }
  64. // 切换麦克风状态
  65. async function toggleMic() {
  66. if (!localStream) {
  67. console.error("No localStream available");
  68. document.getElementById("status").innerHTML = "麦克风未就绪,请刷新页面重试";
  69. return;
  70. }
  71. isMute = !isMute;
  72. const button = document.getElementById("micButton");
  73. button.textContent = isMute ? "🎤 点击说话" : "🔴 正在说话";
  74. localStream
  75. .getAudioTracks()
  76. .forEach((track) => (track.enabled = !isMute));
  77. if (!isMute) {
  78. createPeerConnections();
  79. }
  80. }
  81. // 创建与房间内其他用户的对等连接
  82. async function createPeerConnections() {
  83. for (let peerId in peers) {
  84. if (peerId !== socket.id) {
  85. const peer = new RTCPeerConnection();
  86. peers[peerId] = peer;
  87. localStream
  88. .getTracks()
  89. .forEach((track) => peer.addTrack(track, localStream));
  90. peer.onicecandidate = (e) => {
  91. if (e.candidate) {
  92. socket.emit("candidate", {
  93. candidate: e.candidate,
  94. room: room_id,
  95. target: peerId,
  96. });
  97. }
  98. };
  99. try {
  100. const offer = await peer.createOffer();
  101. await peer.setLocalDescription(offer);
  102. socket.emit("offer", {
  103. sdp: offer,
  104. room: room_id,
  105. target: peerId,
  106. });
  107. } catch (err) {
  108. console.error("Error creating or sending offer:", err);
  109. document.getElementById("status").innerHTML = "创建连接时出错,请刷新页面重试";
  110. }
  111. }
  112. }
  113. }
  114. // WebRTC信令处理
  115. socket.on("offer", async (data) => {
  116. if (data.sender === socket.id) return;
  117. const peer = new RTCPeerConnection();
  118. peers[data.sender] = peer;
  119. peer.onicecandidate = (e) => {
  120. if (e.candidate) {
  121. socket.emit("candidate", {
  122. candidate: e.candidate,
  123. target: data.sender,
  124. room: room_id,
  125. });
  126. }
  127. };
  128. peer.ontrack = (e) => {
  129. const audio = document.createElement("audio");
  130. audio.srcObject = e.streams[0];
  131. audio.autoplay = true;
  132. document.body.appendChild(audio);
  133. };
  134. await peer.setRemoteDescription(data.sdp);
  135. const answer = await peer.createAnswer();
  136. await peer.setLocalDescription(answer);
  137. socket.emit("answer", {
  138. sdp: answer,
  139. target: data.sender,
  140. room: room_id,
  141. });
  142. });
  143. socket.on("answer", (data) => {
  144. if (data.sender === socket.id) return;
  145. const peer = peers[data.sender];
  146. if (peer) {
  147. peer.setRemoteDescription(data.sdp);
  148. }
  149. });
  150. socket.on("candidate", (data) => {
  151. if (data.sender === socket.id) return;
  152. const peer = peers[data.sender];
  153. if (peer) {
  154. peer.addIceCandidate(new RTCIceCandidate(data.candidate));
  155. }
  156. });
  157. socket.on("user_joined", (userId) => {
  158. peers[userId] = null;
  159. if (!isMute) {
  160. createPeerConnections();
  161. }
  162. });
  163. socket.on("user_left", (userId) => {
  164. if (peers[userId]) {
  165. peers[userId].close();
  166. delete peers[userId];
  167. }
  168. });
  169. init();
  170. </script>
  171. </body>
  172. </html>