介绍
面向网络的实时通信,可以实现视频语音聊天室等。参考文档
开始
实现一个简单的视频聊天页面。
需要简单了解一下信令和 ice,可自行百度。
这里使用 socket.io 实现简单的信令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const express = require("express"); const path = require("path"); const app = express(); var http = require("http").Server(app); var io = require("socket.io")(http); app.use(express.static(path.join(__dirname, "/public"))); const port = 3000; // 所有的房间 const rooms = {}; io.on("connection", function (socket) { console.log("a user connected"); // 加入房间 一个房间只能加入两个客户端,需要一对多等可以自行百度 socket.on("join", (roomID) => { if (!rooms[roomID]) { rooms[roomID] = []; } rooms[roomID].push(socket); }); // 用户交换offer和answer socket.on("message", (e) => { let data = e rooms[data.id].forEach((item) => { item.emit("message", data); }); }); }); http.listen(port, function () { console.log("listening on http://localhost:3000"); });
|
发送端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <!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>send</title> </head> <style> .video { width: 100%; height: 300px; } </style> <body> me <video class="video video1" autoplay playsinline controls="false"></video> remote <video class="video video2" autoplay playsinline controls="false"></video> <!-- 解决浏览器兼容 --> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.11.0/qs.min.js"></script> <script src="http://localhost:3000/socket.io/socket.io.js"></script> <script> window.onload = created let localStream, id, socket async function created() { // url参数传入房间号 let params = Qs.parse(location.href.split('?')[1]) id = params.id || 123 const constraints = { video: true, audio: true }; localStream = await navigator.mediaDevices .getUserMedia(constraints) .catch(err => console.log(err)); let video = document.querySelector(".video1"); // 本地视频流 video.srcObject = localStream; socket = io(); // 加入房间 socket.emit('join', id) makeCall(localStream) } async function makeCall(stream, value) { // 可以自己搭建iceServer const configuration = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] } const peerConnection = new RTCPeerConnection(configuration); stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); }); peerConnection.addEventListener('track', async (event) => { const [remoteStream] = event.streams; // 远端媒体流 document.querySelector('.video2').srcObject = remoteStream }); socket.on('message', async (data) => { if (data.answer) { const remoteDesc = new RTCSessionDescription(data.answer); await peerConnection.setRemoteDescription(remoteDesc); } }) const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer).catch(err => console.log(err, 'send-setLocalDescription')); socket.emit('message', { 'offer': offer, id }) peerConnection.addEventListener('icecandidate', event => { if (event.candidate) { socket.emit('message', { 'iceCandidate': event.candidate, id }) } }); peerConnection.addEventListener('connectionstatechange', event => { if (peerConnection.connectionState === 'connected') { console.log('conn') } }); }
</script> </body> </html>
|
接收端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <!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>accept</title> </head> <style> .video { width: 100%; height: 300px; } </style>
<body> me <video class="video video1" autoplay playsinline controls="false"></video> remote <video class="video video2" autoplay playsinline controls="false"></video> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.14.7/vconsole.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.11.0/qs.min.js"></script> <script src="http://localhost:3000/socket.io/socket.io.js"></script> <script> var vConsole = new VConsole(); window.onload = created let ws, id, socket async function created() { let params = Qs.parse(location.href.split('?')[1]) id = params.id || 123 socket = io(); socket.emit('join', id) const constraints = { video: true, audio: true }; let stream = await navigator.mediaDevices .getUserMedia(constraints) .catch(err => console.log(err)); let video = document.querySelector(".video1"); video.srcObject = stream; const configuration = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] } const peerConnection = new RTCPeerConnection(configuration); stream.getTracks().forEach(track => { peerConnection.addTrack(track, stream); }); peerConnection.addEventListener('track', async (event) => { const [remoteStream] = event.streams; document.querySelector('.video2').srcObject = remoteStream }); socket.on('message', async (data) => { if (data.offer) { console.log('offer', data.offer) peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer)); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer).catch(err => console.log(err, 'accept-setLocalDescription'));; socket.emit('message', { 'answer': answer, id }) } if (data.iceCandidate) { console.log('iceCandidate') await peerConnection.addIceCandidate(data.iceCandidate); } }) } </script> </body> </html>
|
提示
本地测试需要使用localhost
线上测试需要是https
如果使用nginx为web服务器可能需要转发代理socket.io
1 2 3 4 5 6
| location /socket.io/ { proxy_pass http://127.0.0.1:3000/socket.io/; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; }
|
有兴趣可以去了解别人封装的webrtc库(webrtc.io)
效果展示
电脑谷歌浏览器
安卓微信内置浏览器(也可以使用火狐,谷歌等浏览器,uc和小米自带不支持)