目标
可以实现双方视频通话和聊天
效果
代码
呼叫方
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .wrapp { width: 100%; text-align: center; border: red solid 1px; } .videos { display: flex; } .video { display: flex; justify-content: center; align-items: center; width: 50%; border: red solid 1px; height: 120px; position: relative; } .msgs { background-color: pink; width: 100%; height: 200px; margin: auto; overflow-y: scroll; padding: 10px; box-sizing: border-box; } .ipt { padding: 20px; } .open-video { left: 10px; top: 10px; position: absolute; z-index: 2; } .msg-content { display: flex; margin-bottom: 10px; } .head { border-radius: 50%; width: 30px; height: 30px; } .me { background-color: white; margin-left: 8px; } .other { margin-right: 8px; background-color: green; } .msg-ctxtype-me { flex-direction: row-reverse; } .content { background-color: #95ec69; padding: 4px; width: 50%; border-radius: 4px; } </style> </head> <body> <h1>发起端</h1> <div class="wrapp"> <div class="videos"> <div class="video"> <video class="local-video" autoplay width="160" height="120"></video> </div> <div class="video"> <video class="remote-video" autoplay width="160" height="120"></video> </div> </div> <div class="msgs"></div> <div class="ipt"> <input type="text"> <button onclick="sendMsg()">发送</button> </div> </div> <script> // import {ws} from ‘./helper.js‘; const ws = new WebSocket("wss://dshvv.com:8888/my_ws"); let icecandidated = false; const lc = new RTCPeerConnection() /* * 处理文字聊天消息相关 */ const dc = lc.createDataChannel("channel"); const msgs = []; //所有的聊天内容 消息容器 const updateMsgs = () => { // 每当有新消息时,更新聊天展示 let el = ‘‘; msgs.forEach((msg) => { el += `<div class=‘msg-content msg-ctxtype-${msg.type}‘> <div class=‘head ${msg.type}‘></div> <div class=‘content‘>${msg.content}</div> </div>` }) msgsEl.innerHTML = el; document.querySelector(‘.msgs‘).lastChild.scrollIntoView(true); } // 收到新消息 dc.onmessage = e => { msgs.push({ type: ‘other‘, content: e.data }); updateMsgs(); } // 发送消息 const sendMsg = () => { const content = document.querySelector(‘input‘).value; msgs.push({ type: ‘me‘, content }); dc.send(content); updateMsgs(); } // 文字消息链接初始化 dc.onopen = e => { msgs.push({ type: ‘me‘, content: ‘初始化链接成功‘ }); updateMsgs(); } const msgsEl = document.querySelector(‘.msgs‘); /* * 认证唯一标识相关 */ ws.onopen = async () => { lc.onicecandidate = e => { if (!icecandidated) { ws.send(JSON.stringify({// 将LocalDescription发给别人 event: "offer", data: { sdp: lc.localDescription } })); icecandidated = true; } } lc.createOffer().then(o => lc.setLocalDescription(o)).then(a => console.log("set successfully!")) } ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.event === "answer") { const answer = msg.data.sdp; lc.setRemoteDescription(answer) } } /* * 视频推送 */ const openVideo = document.querySelector(‘.open-video‘); navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((localStream) => { document.querySelector(‘.local-video‘).srcObject = localStream; for (const track of localStream.getTracks()) { lc.addTrack(track, localStream); console.log(‘添加本地媒体流到本地peer connection‘); } }); lc.ontrack = async (event) => { const remoteVideo = document.querySelector(‘.remote-video‘); remoteVideo.srcObject = event.streams[0]; }; </script> </body> </html>
接收方
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .wrapp { width: 100%; text-align: center; border: red solid 1px; } .videos { display: flex; } .video { display: flex; justify-content: center; align-items: center; width: 50%; border: red solid 1px; height: 120px; position: relative; } .msgs { background-color: pink; width: 100%; height: 200px; margin: auto; overflow-y: scroll; padding: 10px; box-sizing: border-box; } .ipt { padding: 20px; } .open-video { left: 10px; top: 10px; position: absolute; z-index: 2; } .msg-content { display: flex; margin-bottom: 10px; } .head { border-radius: 50%; width: 30px; height: 30px; } .me { background-color: white; margin-left: 8px; } .other { margin-right: 8px; background-color: green; } .msg-ctxtype-me { flex-direction: row-reverse; } .content { background-color: #95ec69; padding: 4px; width: 50%; border-radius: 4px; } </style> </head> <body> <h1>接收端</h1> <div class="wrapp"> <div class="videos"> <div class="video"> <video class="local-video" autoplay width="160" height="120"></video> </div> <div class="video"> <video class="remote-video" autoplay width="160" height="120"></video> </div> </div> <div class="msgs"></div> <div class="ipt"> <input type="text"> <button onclick="sendMsg()">发送</button> </div> </div> <script> // import {ws} from ‘./helper.js‘; const ws = new WebSocket("wss://dshvv.com:8888/my_ws"); let icecandidated = false; const rc = new RTCPeerConnection(); /* * 认证唯一标识相关 */ rc.onicecandidate = e => { console.log(‘onicecandidate执行,回应answer‘); if (!icecandidated) { ws.send(JSON.stringify({ event: "answer", data: { sdp: rc.localDescription } })); icecandidated = true; } } ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.event === "offer") { const offer = msg.data.sdp; rc.setRemoteDescription(offer).then(a => console.log("offer set!")) rc.createAnswer().then(a => rc.setLocalDescription(a)).then(a => console.log("answer create!")) } } /* * 处理文字聊天消息相关 */ const msgs = []; //所有的聊天内容 消息容器 const msgsEl = document.querySelector(‘.msgs‘); const updateMsgs = () => { // 每当有新消息时,更新聊天展示 let el = ‘‘; msgs.forEach((msg) => { el += `<div class=‘msg-content msg-ctxtype-${msg.type}‘> <div class=‘head ${msg.type}‘></div> <div class=‘content‘>${msg.content}</div> </div>` }) msgsEl.innerHTML = el; document.querySelector(‘.msgs‘).lastChild.scrollIntoView(true); } rc.ondatachannel = e => { console.log(e); rc.dc = e.channel; rc.dc.onmessage = e => { console.log("new message from client!" + e.data); msgs.push({ type: ‘other‘, content: e.data }); updateMsgs(); } rc.dc.onopen = e => { console.log(‘初始化链接成功‘,e); msgs.push({ type: ‘me‘, content: ‘初始化链接成功‘ }); updateMsgs(); } } const sendMsg = () => { const content = document.querySelector(‘input‘).value; msgs.push({ type: ‘me‘, content }); rc.dc.send(content); updateMsgs(); } /* * 视频接收 */ navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((localStream) => { document.querySelector(‘.local-video‘).srcObject = localStream; for (const track of localStream.getTracks()) { rc.addTrack(track, localStream); console.log(‘添加本地媒体流到本地peer connection‘); } }); rc.ontrack = async (event) => { const remoteVideo = document.querySelector(‘.remote-video‘); remoteVideo.srcObject = event.streams[0]; }; </script> </body> </html>