video-chat.html 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  6. <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
  7. <title>webchat即时在线聊天室,视频通话</title>
  8. <link rel="stylesheet" href="/css/common/common.css">
  9. <link rel="stylesheet" href="/css/client/chat.css">
  10. <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
  11. <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
  12. <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
  13. <style>
  14. #remoteVideo {
  15. position: absolute;
  16. left: 0px;
  17. top: 0px;
  18. width: 450px;
  19. height: 620px;
  20. }
  21. #localVideo {
  22. position: absolute;
  23. right: 50px;
  24. top: 50px;
  25. width: 100px;
  26. border-radius: 10px;
  27. height: 75px;
  28. box-shadow: 0px 0px 10px #c8c7c7;
  29. }
  30. #hangUpBtn {
  31. position: absolute;
  32. bottom: 80px;
  33. left: 200px;
  34. width: 50px;
  35. height: 50px;
  36. border: none;
  37. border-radius: 100px;
  38. background-color: #fb5c5c;
  39. box-shadow: 0px 0px 10px #fb8d8d;
  40. }
  41. </style>
  42. </head>
  43. <body>
  44. <center>
  45. <video id = "localVideo" autoplay></video>
  46. <video id = "remoteVideo" autoplay></video>
  47. <div class = "row text-center">
  48. <div class = "col-md-12">
  49. <button id = "hangUpBtn" class="btn-danger btn">
  50. <svg t="1730296252524" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4292" width="30" height="30"><path d="M115.2 115.2c19.2-32 44.8-64 76.8-83.2 32-19.2 70.4-32 115.2-32H320c70.4 0 134.4 76.8 153.6 198.4 12.8 57.6 6.4 121.6-19.2 160-12.8 32-38.4 51.2-70.4 51.2l-12.8 12.8v12.8c19.2 38.4 38.4 76.8 64 108.8 19.2 32 44.8 64 70.4 96 6.4 6.4 12.8 6.4 19.2 6.4h6.4c12.8-19.2 32-38.4 57.6-44.8 44.8-6.4 115.2 12.8 179.2 57.6 96 70.4 140.8 166.4 108.8 230.4l-6.4 6.4c-19.2 38.4-44.8 70.4-83.2 89.6-160 96-409.6-38.4-576-307.2-128-198.4-166.4-428.8-96-563.2z m288 217.6c12.8-32 19.2-76.8 12.8-128-12.8-83.2-64-147.2-96-147.2h-12.8c-32 0-64 6.4-89.6 25.6-19.2 12.8-38.4 32-51.2 57.6-64 115.2-25.6 326.4 89.6 512 147.2 230.4 371.2 364.8 499.2 288 25.6-12.8 44.8-38.4 57.6-70.4 0 0 0-6.4 6.4-6.4v-6.4c19.2-32-19.2-102.4-89.6-153.6-44.8-32-102.4-51.2-134.4-44.8-12.8 6.4-19.2 12.8-19.2 19.2 0 0-12.8 25.6-44.8 32-19.2 6.4-44.8-6.4-64-25.6l-6.4-6.4c-25.6-32-51.2-64-76.8-102.4-25.6-38.4-44.8-83.2-64-121.6V448c-6.4-19.2-6.4-38.4 0-57.6 12.8-25.6 44.8-38.4 44.8-38.4h6.4c19.2 0 25.6-12.8 32-19.2z m217.6 12.8c6.4-12.8 19.2-19.2 32-12.8 32 6.4 51.2 25.6 64 51.2 12.8 25.6 12.8 51.2 6.4 76.8-6.4 12.8-19.2 25.6-38.4 19.2-12.8-6.4-25.6-19.2-19.2-38.4 6.4-12.8 0-25.6 0-32-6.4-12.8-19.2-19.2-25.6-25.6-19.2 0-25.6-19.2-19.2-38.4zM672 256c6.4-12.8 19.2-19.2 32-12.8 96 32 147.2 134.4 115.2 230.4-6.4 12.8-19.2 25.6-38.4 19.2-12.8-6.4-25.6-19.2-19.2-38.4 19.2-64-12.8-134.4-76.8-153.6-12.8-6.4-19.2-25.6-12.8-44.8 0 6.4 0 0 0 0z m25.6-115.2c6.4-12.8 19.2-19.2 32-12.8 76.8 25.6 140.8 83.2 179.2 153.6 38.4 76.8 44.8 160 19.2 236.8-6.4 12.8-19.2 25.6-38.4 19.2-12.8-6.4-25.6-19.2-19.2-38.4 19.2-64 19.2-134.4-12.8-192-32-57.6-83.2-102.4-147.2-128-12.8 0-19.2-19.2-12.8-38.4 0 6.4 0 0 0 0z m0 0" p-id="4293" fill="#ffffff"></path></svg>
  51. </button>
  52. </div>
  53. </div>
  54. </center>
  55. </body>
  56. <script src="/js/client/video.chat.js" type="text/javascript"></script>
  57. <script type="text/javascript">
  58. var wsHost = document.domain;
  59. // 如果你修改了application-dev.yml 中的端口号,这里的ws端口也要一块修改
  60. var wsPort = 8101;
  61. // 获取URL请求参数
  62. function getUserParamByName(key) {
  63. var url = window.location.search;
  64. var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)");
  65. var result = url.substr(1).match(reg);
  66. return result ? decodeURIComponent(result[2]) : "";
  67. }
  68. // 从URL参数中获取当前用户ID和目标用户ID
  69. var userId = getUserParamByName("userId");
  70. var targetUserId = getUserParamByName("targetUserId");
  71. var connectedUser = targetUserId;
  72. // 初始化WebSocket连接对象,用于信令服务器通信
  73. var conn = new WebSocket("ws://"+wsHost+":"+wsPort+"/ws/video-chat/"+userId);
  74. conn.onopen = function () {
  75. console.log("Connected to the signaling server");
  76. };
  77. // 处理从信令服务器接收到的消息
  78. // 当WebSocket接收到信令服务器发送的消息时,触发此事件处理函数
  79. conn.onmessage = function (msg) {
  80. console.log("Got message", msg.data); // 打印接收到的消息内容到控制台,用于调试
  81. // 解析从服务器接收到的JSON格式的消息数据
  82. var data = JSON.parse(msg.data);
  83. // 根据消息类型进行不同的处理
  84. switch(data.type) {
  85. // 当消息类型为"offer"时,表示远端用户希望建立连接,并发送了他们的SDP(会话描述协议)信息
  86. case "offer":
  87. // 调用handleOffer函数处理offer,设置远端描述,并创建answer响应
  88. handleOffer(data.offer);
  89. break;
  90. // 当消息类型为"answer"时,表示远端用户对我们之前发送的offer做出了响应,并发送了他们的SDP信息
  91. case "answer":
  92. // 调用handleAnswer函数处理answer,设置远端描述
  93. handleAnswer(data.answer);
  94. break;
  95. // 当消息类型为"candidate"时,表示远端用户发送了ICE候选信息,用于NAT穿透和连接建立
  96. case "candidate":
  97. // 调用handleCandidate函数处理ICE候选信息,添加到RTCPeerConnection中
  98. handleCandidate(data.candidate);
  99. break;
  100. // 当消息类型为"leave"时,表示远端用户希望结束通话
  101. case "leave":
  102. // 调用handleLeave函数处理离开事件,关闭连接并清理资源
  103. handleLeave();
  104. break;
  105. // 默认情况,不进行任何操作
  106. default:
  107. break;
  108. }
  109. };
  110. // 处理WebSocket连接错误
  111. conn.onerror = function (err) {
  112. console.log("Got error", err);
  113. };
  114. // 发送消息到信令服务器
  115. function send(message) {
  116. conn.send(JSON.stringify(message));
  117. }
  118. // 获取页面上的按钮和视频元素
  119. var hangUpBtn = document.querySelector("#hangUpBtn");
  120. var localVideo = document.querySelector("#localVideo");
  121. var remoteVideo = document.querySelector("#remoteVideo");
  122. // 声明RTCPeerConnection对象和流对象
  123. var peerConnection;
  124. var stream;
  125. // 检测浏览器是否支持WebRTC API
  126. var PeerConnection = (window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection || undefined);
  127. var RTCSessionDescription = (window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription || undefined);
  128. navigator.getUserMedia = (navigator.getUserMedia ||
  129. navigator.webkitGetUserMedia ||
  130. navigator.mozGetUserMedia ||
  131. navigator.msGetUserMedia);
  132. // 请求用户的音视频流
  133. navigator.getUserMedia({ video: true, audio: true }, function (myStream) {
  134. stream = myStream;
  135. localVideo.srcObject = stream; // 将本地流绑定到本地视频元素
  136. var configuration = {
  137. "iceServers": [
  138. {
  139. "urls": "stun:stun.l.google.com:19302"
  140. }
  141. ]
  142. };
  143. peerConnection = new PeerConnection(configuration); // 创建RTCPeerConnection对象
  144. peerConnection.addStream(stream); // 将本地流添加到连接中
  145. peerConnection.onaddstream = function (e) {
  146. remoteVideo.srcObject = e.stream; // 将远程流绑定到远程视频元素
  147. };
  148. peerConnection.onicecandidate = function (event) {
  149. if (event.candidate) {
  150. send({
  151. type: "candidate",
  152. candidate: event.candidate,
  153. userId: userId,
  154. targetUserId: targetUserId
  155. });
  156. }
  157. };
  158. // 创建一个offer描述
  159. peerConnection.createOffer(function (offer) {
  160. send({
  161. type: "offer",
  162. offer: offer,
  163. userId: userId,
  164. targetUserId: targetUserId
  165. });
  166. peerConnection.setLocalDescription(offer);
  167. }, function (error) {
  168. alert("Error when creating an offer");
  169. });
  170. }, function (error) {
  171. console.log(error);
  172. });
  173. // 处理收到的offer
  174. function handleOffer(offer) {
  175. peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
  176. peerConnection.createAnswer(function (answer) {
  177. peerConnection.setLocalDescription(answer);
  178. send({
  179. type: "answer",
  180. answer: answer,
  181. userId: userId,
  182. targetUserId: targetUserId
  183. });
  184. }, function (error) {
  185. alert("Error when creating an answer");
  186. });
  187. }
  188. // 处理收到的answer
  189. function handleAnswer(answer) {
  190. peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
  191. }
  192. // 处理收到的ICE候选
  193. function handleCandidate(candidate) {
  194. peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
  195. }
  196. // 挂断按钮事件监听器
  197. hangUpBtn.addEventListener("click", function () {
  198. send({
  199. type: "leave",
  200. userId: userId,
  201. targetUserId: targetUserId
  202. });
  203. handleLeave();
  204. });
  205. // 处理离开事件
  206. function handleLeave() {
  207. connectedUser = null;
  208. remoteVideo.src = null;
  209. peerConnection.close(); // 关闭RTCPeerConnection
  210. peerConnection.onicecandidate = null;
  211. peerConnection.onaddstream = null;
  212. layer.closeAll(); // 关闭所有层(可能是第三方库函数)
  213. }
  214. </script>
  215. </html>