room.html 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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 = io();
  19. const peers = {};
  20. // 初始化WebSocket连接
  21. socket.on("connect", () => {
  22. socket.emit("join", { room: room_id });
  23. });
  24. // 获取麦克风权限
  25. async function init() {
  26. try {
  27. localStream = await navigator.mediaDevices.getUserMedia({
  28. audio: true,
  29. });
  30. document.getElementById("status").innerHTML = "麦克风已就绪";
  31. document.getElementById("micButton").disabled = false;
  32. } catch (err) {
  33. console.error("Error accessing microphone:", err);
  34. document.getElementById("status").innerHTML = "无法访问麦克风: " + err.message;
  35. document.getElementById("micButton").disabled = true;
  36. }
  37. }
  38. // 切换麦克风状态
  39. async function toggleMic() {
  40. if (!localStream) {
  41. console.error("No localStream available");
  42. document.getElementById("status").innerHTML = "麦克风未就绪,请刷新页面重试";
  43. return;
  44. }
  45. isMute = !isMute;
  46. const button = document.getElementById("micButton");
  47. button.textContent = isMute ? "🎤 点击说话" : "🔴 正在说话";
  48. localStream
  49. .getAudioTracks()
  50. .forEach((track) => (track.enabled = !isMute));
  51. if (!isMute) {
  52. createPeerConnections();
  53. }
  54. }
  55. // 创建与房间内其他用户的对等连接
  56. async function createPeerConnections() {
  57. for (let peerId in peers) {
  58. if (peerId !== socket.id) {
  59. const peer = new RTCPeerConnection();
  60. peers[peerId] = peer;
  61. localStream
  62. .getTracks()
  63. .forEach((track) => peer.addTrack(track, localStream));
  64. peer.onicecandidate = (e) => {
  65. if (e.candidate) {
  66. socket.emit("candidate", {
  67. candidate: e.candidate,
  68. room: room_id,
  69. target: peerId,
  70. });
  71. }
  72. };
  73. try {
  74. const offer = await peer.createOffer();
  75. await peer.setLocalDescription(offer);
  76. socket.emit("offer", {
  77. sdp: offer,
  78. room: room_id,
  79. target: peerId,
  80. });
  81. } catch (err) {
  82. console.error("Error creating or sending offer:", err);
  83. document.getElementById("status").innerHTML = "创建连接时出错,请刷新页面重试";
  84. }
  85. }
  86. }
  87. }
  88. // WebRTC信令处理
  89. socket.on("offer", async (data) => {
  90. if (data.sender === socket.id) return;
  91. const peer = new RTCPeerConnection();
  92. peers[data.sender] = peer;
  93. peer.onicecandidate = (e) => {
  94. if (e.candidate) {
  95. socket.emit("candidate", {
  96. candidate: e.candidate,
  97. target: data.sender,
  98. room: room_id,
  99. });
  100. }
  101. };
  102. peer.ontrack = (e) => {
  103. const audio = document.createElement("audio");
  104. audio.srcObject = e.streams[0];
  105. audio.autoplay = true;
  106. document.body.appendChild(audio);
  107. };
  108. await peer.setRemoteDescription(data.sdp);
  109. const answer = await peer.createAnswer();
  110. await peer.setLocalDescription(answer);
  111. socket.emit("answer", {
  112. sdp: answer,
  113. target: data.sender,
  114. room: room_id,
  115. });
  116. });
  117. socket.on("answer", (data) => {
  118. if (data.sender === socket.id) return;
  119. const peer = peers[data.sender];
  120. if (peer) {
  121. peer.setRemoteDescription(data.sdp);
  122. }
  123. });
  124. socket.on("candidate", (data) => {
  125. if (data.sender === socket.id) return;
  126. const peer = peers[data.sender];
  127. if (peer) {
  128. peer.addIceCandidate(new RTCIceCandidate(data.candidate));
  129. }
  130. });
  131. socket.on("user_joined", (userId) => {
  132. peers[userId] = null;
  133. if (!isMute) {
  134. createPeerConnections();
  135. }
  136. });
  137. socket.on("user_left", (userId) => {
  138. if (peers[userId]) {
  139. peers[userId].close();
  140. delete peers[userId];
  141. }
  142. });
  143. init();
  144. </script>
  145. </body>
  146. </html>