room.html 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. const peer = new RTCPeerConnection();
  53. peers[socket.id] = peer;
  54. localStream
  55. .getTracks()
  56. .forEach((track) => peer.addTrack(track, localStream));
  57. peer.onicecandidate = (e) => {
  58. if (e.candidate) {
  59. socket.emit("candidate", {
  60. candidate: e.candidate,
  61. room: room_id,
  62. target: socket.id, // 添加目标标识
  63. });
  64. }
  65. };
  66. try {
  67. const offer = await peer.createOffer();
  68. await peer.setLocalDescription(offer);
  69. socket.emit("offer", {
  70. sdp: offer,
  71. room: room_id, // 使用room字段
  72. });
  73. } catch (err) {
  74. console.error("Error creating or sending offer:", err);
  75. document.getElementById("status").innerHTML = "创建连接时出错,请刷新页面重试";
  76. }
  77. }
  78. }
  79. // WebRTC信令处理
  80. socket.on("offer", async (data) => {
  81. // 添加发送者过滤
  82. if (data.sender === socket.id) return;
  83. const peer = new RTCPeerConnection();
  84. peers[data.sender] = peer;
  85. peer.onicecandidate = (e) => {
  86. if (e.candidate) {
  87. socket.emit("candidate", {
  88. candidate: e.candidate,
  89. target: data.sender,
  90. room: room_id,
  91. });
  92. }
  93. };
  94. peer.ontrack = (e) => {
  95. const audio = document.createElement("audio");
  96. audio.srcObject = e.streams[0];
  97. audio.play();
  98. };
  99. await peer.setRemoteDescription(data.sdp);
  100. const answer = await peer.createAnswer();
  101. await peer.setLocalDescription(answer);
  102. socket.emit("answer", {
  103. sdp: answer,
  104. target: data.sender, // 指定目标用户
  105. room: room_id,
  106. });
  107. });
  108. socket.on("answer", (data) => {
  109. if (data.sender === socket.id) return;
  110. const peer = peers[data.sender];
  111. if (peer) {
  112. peer.setRemoteDescription(data.sdp);
  113. }
  114. });
  115. // 修改candidate处理逻辑
  116. socket.on("candidate", (data) => {
  117. if (data.sender === socket.id) return;
  118. const peer = peers[data.sender];
  119. peer.addIceCandidate(new RTCIceCandidate(data.candidate));
  120. });
  121. init();
  122. </script>
  123. </body>
  124. </html>