|
@@ -0,0 +1,272 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
|
|
+
|
|
|
+ <title>webchat即时在线聊天室,视频通话</title>
|
|
|
+
|
|
|
+ <link rel="stylesheet" href="/css/common/common.css">
|
|
|
+ <link rel="stylesheet" href="/css/client/chat.css">
|
|
|
+ <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
|
|
|
+
|
|
|
+ <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
|
|
|
+ <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
|
|
|
+
|
|
|
+ <style>
|
|
|
+ #hangUpBtn {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 80px;
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 100px;
|
|
|
+ background-color: #fb5c5c;
|
|
|
+ box-shadow: 0px 0px 10px #fb8d8d;
|
|
|
+ }
|
|
|
+ #videos {
|
|
|
+ position: absolute;
|
|
|
+ left: 0px;
|
|
|
+ top: 0px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: whitesmoke;
|
|
|
+ }
|
|
|
+ #videos video {
|
|
|
+ position: relative;
|
|
|
+ width: 267px;
|
|
|
+ height: 200px;
|
|
|
+ background-color: black;
|
|
|
+ border-radius: 1px;
|
|
|
+ float: left;
|
|
|
+ border-right: 2px solid white;
|
|
|
+ border-bottom: 2px solid white;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<center>
|
|
|
+ <div id = "videos"></div>
|
|
|
+ <div class = "row text-center">
|
|
|
+ <div class = "col-md-12">
|
|
|
+ <button id = "hangUpBtn" class="btn-danger btn">
|
|
|
+ <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>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</center>
|
|
|
+</body>
|
|
|
+<script src="/js/client/video.chat.js" type="text/javascript"></script>
|
|
|
+<script type="text/javascript">
|
|
|
+ var wsHost = document.domain;
|
|
|
+ var wsPort = 8101;
|
|
|
+
|
|
|
+ function getUserParamByName(key) {
|
|
|
+ var url = window.location.search;
|
|
|
+ var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)");
|
|
|
+ var result = url.substr(1).match(reg);
|
|
|
+ return result ? decodeURIComponent(result[2]) : "";
|
|
|
+ }
|
|
|
+ var userId = getUserParamByName("userId");
|
|
|
+ var targetUserId = getUserParamByName("targetUserId");
|
|
|
+ var connectedUserIds = new Array();
|
|
|
+ var videos = document.querySelector("#videos");
|
|
|
+ // RTCPeerConnection管理
|
|
|
+ const peerConnections = {};
|
|
|
+ var configuration = {
|
|
|
+ "iceServers": [
|
|
|
+ {
|
|
|
+ "urls": "stun:stun.l.google.com:19302"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ var PeerConnection = (window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection || undefined);
|
|
|
+ var RTCSessionDescription = (window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription || undefined);
|
|
|
+ navigator.getUserMedia = (navigator.getUserMedia ||
|
|
|
+ navigator.webkitGetUserMedia ||
|
|
|
+ navigator.mozGetUserMedia ||
|
|
|
+ navigator.msGetUserMedia);
|
|
|
+
|
|
|
+ // 初始化WebSocket连接对象,用于信令服务器通信
|
|
|
+ let localStream;
|
|
|
+ var conn;
|
|
|
+ function initSignalServerConnection() {
|
|
|
+ conn = new WebSocket("ws://" + wsHost + ":" + wsPort + "/ws/group-video-chat/" + targetUserId + "/" + userId);
|
|
|
+ conn.onopen = function () {
|
|
|
+ console.log("Connected to the signaling server");
|
|
|
+ send({
|
|
|
+ type: "call",
|
|
|
+ userId: userId,
|
|
|
+ targetUserId: targetUserId,
|
|
|
+ groupId: targetUserId
|
|
|
+ })
|
|
|
+ };
|
|
|
+ conn.onmessage = function (msg) {
|
|
|
+ var data = JSON.parse(msg.data);
|
|
|
+ console.log("Got message =====> ", data);
|
|
|
+ switch (data.type) {
|
|
|
+ case "online":
|
|
|
+ handleOnline(data);
|
|
|
+ break;
|
|
|
+ case "offer":
|
|
|
+ handleOffer(data.offer, data.userId);
|
|
|
+ break;
|
|
|
+ case "answer":
|
|
|
+ handleAnswer(data.answer, data.userId);
|
|
|
+ break;
|
|
|
+ case "candidate":
|
|
|
+ handleCandidate(data.candidate, data.userId);
|
|
|
+ break;
|
|
|
+ case "leave":
|
|
|
+ handleLeave(data.userId);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ conn.onerror = function (err) {
|
|
|
+ console.log("Got error", err);
|
|
|
+ initSignalServerConnection();
|
|
|
+ };
|
|
|
+ conn.onclose = function (err) {
|
|
|
+ initSignalServerConnection();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ startMedia();
|
|
|
+ function startMedia() {
|
|
|
+ // 初始化本地视频流,并绑定到页面当前客户端视频标签上
|
|
|
+ navigator.getUserMedia({ video: true, audio: true }, function (stream) {
|
|
|
+ localStream = stream;
|
|
|
+ // 创建视频元素并绑定到页面上
|
|
|
+ var video = document.createElement('video');
|
|
|
+ video.autoplay = true;
|
|
|
+ video.muted = true;
|
|
|
+ video.id = "video-" + userId;
|
|
|
+ // 将本地流绑定到本地视频元素
|
|
|
+ video.srcObject = localStream;
|
|
|
+ // 将视频元素添加到页面上
|
|
|
+ videos.appendChild(video);
|
|
|
+ initSignalServerConnection();
|
|
|
+ }, function (error) {
|
|
|
+ console.log(error);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function send(message) {
|
|
|
+ conn.send(JSON.stringify(message));
|
|
|
+ }
|
|
|
+
|
|
|
+ function getPeerId(uid) {
|
|
|
+ let peerKeyArr = [userId, uid];
|
|
|
+ return peerKeyArr.sort().join('-');
|
|
|
+ }
|
|
|
+
|
|
|
+ function getPeerConnection(uid) {
|
|
|
+ let peerKey = getPeerId(uid);
|
|
|
+ if (peerConnections[peerKey]) {
|
|
|
+ return peerConnections[peerKey];
|
|
|
+ }
|
|
|
+ var peerConnection = new PeerConnection(configuration);
|
|
|
+ peerConnections[peerKey] = peerConnection;
|
|
|
+ return peerConnection;
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleOnline(message) {
|
|
|
+
|
|
|
+ for (let i = 0; i < message.userIds.length; i++) {
|
|
|
+ var uid = message.userIds[i];
|
|
|
+ if (connectedUserIds.includes(uid)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ connectedUserIds.push(uid);
|
|
|
+ if (uid == userId) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ var peerConnection = getPeerConnection(uid);
|
|
|
+ let video = document.createElement('video');
|
|
|
+ video.controls = true;
|
|
|
+ video.autoplay = true;
|
|
|
+ video.id = "video-" + uid;
|
|
|
+ // 将视频元素添加到页面上
|
|
|
+ videos.appendChild(video);
|
|
|
+ peerConnection.addStream(localStream);
|
|
|
+ peerConnection.onaddstream = function(event){
|
|
|
+ video.srcObject = event.stream;
|
|
|
+ };
|
|
|
+ peerConnection.onicecandidate = function (event) {
|
|
|
+ if (event.candidate) {
|
|
|
+ send({
|
|
|
+ type: "candidate",
|
|
|
+ candidate: event.candidate,
|
|
|
+ userId: userId,
|
|
|
+ targetUserId: uid,
|
|
|
+ groupId: targetUserId
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 创建一个offer描述
|
|
|
+ peerConnection.createOffer(function (offer) {
|
|
|
+ send({
|
|
|
+ type: "offer",
|
|
|
+ offer: offer,
|
|
|
+ userId: userId,
|
|
|
+ targetUserId: uid,
|
|
|
+ groupId: targetUserId
|
|
|
+ });
|
|
|
+ peerConnection.setLocalDescription(offer);
|
|
|
+ }, function (error) {
|
|
|
+ alert("Error when creating an offer");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleOffer(offer, from) {
|
|
|
+ var peerConnection = getPeerConnection(from);
|
|
|
+ peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
|
|
|
+ peerConnection.createAnswer(function (answer) {
|
|
|
+ peerConnection.setLocalDescription(answer);
|
|
|
+ send({
|
|
|
+ type: "answer",
|
|
|
+ answer: answer,
|
|
|
+ userId: userId,
|
|
|
+ targetUserId: from,
|
|
|
+ groupId: targetUserId
|
|
|
+ });
|
|
|
+ }, function (error) {
|
|
|
+ alert("Error when creating an answer");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleAnswer(answer, from) {
|
|
|
+ var peerConnection = getPeerConnection(from);
|
|
|
+ peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理收到的ICE候选
|
|
|
+ function handleCandidate(candidate, from) {
|
|
|
+ var peerConnection = getPeerConnection(from);
|
|
|
+ peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleLeave(from) {
|
|
|
+ getPeerConnection(from).close();
|
|
|
+ peerConnections[getPeerId(from)] = null;
|
|
|
+ document.getElementById("video-" + from).remove();
|
|
|
+ connectedUserIds = connectedUserIds.filter(function (uid) { return uid !== from; });
|
|
|
+ }
|
|
|
+
|
|
|
+ hangUpBtn.addEventListener("click", function () {
|
|
|
+ connectedUserIds.forEach(function (user) {
|
|
|
+ send({
|
|
|
+ type: "leave",
|
|
|
+ userId: userId,
|
|
|
+ targetUserId: targetUserId,
|
|
|
+ groupId: targetUserId
|
|
|
+ });
|
|
|
+ handleLeave(user);
|
|
|
+ });
|
|
|
+ });
|
|
|
+</script>
|
|
|
+</html>
|