room.html 7.1 KB

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