上一篇 說明了完成聊天室必備的基礎觀念,接著就來進行實作吧!
視訊情境
裝置 A 開啟了一個聊天室,接著裝置 B 連接該聊天室進行視訊聊天
圖片參考:MDN
流程解析(搭配 STUN 協定)
- 裝置 A 透過 STUN server 取得本地私有 IP 以及公有 IP
- 裝置 B 透過 STUN server 取得本地私有 IP 以及公有 IP
- 裝置 A 發送 offer SDP,並設定為本地 SDP
- 裝置 B 收到 offer SDP,並設定為遠端 SDP
- 裝置 B 發送 answer SDP,並設定為本地 SDP
- 裝置 A 收到 answer SDP,並設定為遠端 SDP
- 裝置 A 傳送 ICE 候選位址
- 裝置 B 寫入裝置 A ICE 候選位址
- 裝置 B 傳送 ICE 候選位址
- 裝置 A 寫入裝置 B ICE 候選位址
- P2P 連線建立,進行即時媒體串流(音訊、視訊)
廣告
視訊實作
實作分為 Server(Signaling Server)以及 Client(視訊聊天室)進行
檔案結構如下:
server.js chat.html chat.js
|
Server(server.js)
套件安裝(NPM)
- Node.js 框架 Express
- Websocket 套件 Socket.io
套件引入
const express = require('express'); const app = express(); const http = require('http'); const socket = require('socket.io');
|
啟動伺服器
port 設定 3000,因此伺服器連線網址為 http://localhost:3000
const server = http.createServer(app).listen('3000');
|
連接 Client 頁面(轉換為絕對路徑 __dirname)
app.get('/chat', function(req, res) { es.sendfile(`${__dirname}/chat.html`); });
app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) { console.log(__dirname); res.sendfile(`${__dirname}/${req.params[0]}.${req.params[1]}`); });
|
加入 Socket.io
socket.on()
監聽 join, offer, answer, ice_candidate, hangup 等 Client 自訂事件
socket.emit()
傳遞事件給 Client
const io = socket(server);
io.on('connection', (socket) => { console.log('connection');
socket.on('join', (room) => { console.log('join'); socket.join(room); socket.to(room).emit('ready', '準備通話'); });
socket.on('offer', (room, description) => { socket.to(room).emit('offer', description); });
socket.on('answer', (room, desc) => { socket.to(room).emit('answer', description); });
socket.on('ice_candidate', (room, data) => { socket.to(room).emit('ice_candidate', data); });
socket.on('hangup', (room) => { console.log('hangup'); socket.leave(room); }); });
|
Client(視訊聊天室)
套件安裝(CDN)
- WebRTC Adapter(解決相容性問題)
- Socket.io-client
HTML(chat.html)
- 加入 Video Dom
- #localVideo 接收本地媒體串流
- #remoteVideo 接收遠端媒體串流
- CDN WebRTC Adapter 以及 Socket.io-client
- 引入 chat.js
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1"> <title>chat</title> <style lang="scss"> video { transform: scaleX(-1); } </style> </head> <body> <div> <video muted="false" width="320" autoplay playsinline id="localVideo"></video> <video width="320" autoplay playsinline id="remoteVideo"></video> <button type="button" id="call">call</button> <button type="button" id="hangup">hangup</button> </div> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/socket.io.js"></script> <script src="chat.js" type="module" async></script> </body> </html>
|
Javascript(chat.js)
任務步驟拆分
- 連接 Socket Server(Signaling Server)
透過 Socket 監聽 Offer SDP、Answer SDP、ICE Candidate
- getUserMedia 取得本地媒體串流
- RTCPeerConnection 建立 P2P 連線,與 RTCPeerConnection 事件監聽
定義常數與變數
const localVideo = document.querySelector('#localVideo'); const remoteVideo = document.querySelector('#remoteVideo'); const callBtn = document.querySelector('#call'); const hangupBtn = document.querySelector('#hangup'); let peerConnection; let socket; let localStream; const room = 'room1';
|
連接 Socket Server(Signaling Server)
透過 Socket 監聽 Offer SDP、Answer SDP、ICE Candidate
socket.emit()
以及 socket.on()
傳遞與接收資料給 Server
const socketConnect = () => { socket = io('ws://localhost:3000');
socket.emit('join', room);
socket.on('ready', (msg) => { sendSDP('offer'); });
socket.on('offer', async (desc) => { await peerConnection.setRemoteDescription(desc); await sendSDP('answer'); });
socket.on('answer', (desc) => { peerConnection.setRemoteDescription(desc) });
socket.on('ice_candidate', (data) => { const candidate = new RTCIceCandidate({ sdpMLineIndex: data.label, candidate: data.candidate }); peerConnection.addIceCandidate(candidate); }); };
|
處理 Offer SDP / Answer SDP
const sendSDP = async (type) => { try { if (!peerConnection) { console.log('尚未開啟視訊'); return; }
const method = type === 'offer' ? 'createOffer' : 'createAnswer'; const offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
const localSDP = await peerConnection[method](offerOptions);
await peerConnection.setLocalDescription(localSDP);
socket.emit(type, room, peerConnection.localDescription); } catch (err) { console.log('error: ', err); } };
|
const createStream = async () => { try { const constraints = { audio: true, video: true };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
localVideo.srcObject = stream;
localStream = stream; } catch (err) { console.log('getUserMedia error: ', err.message, err.name); } };
|
RTCPeerConnection 建立 P2P 連線,與 RTCPeerConnection 事件監聽
- RTCPeerConnection 內的
iceServers
用來建立兩台裝置點對點連線的伺服器
getTracks()
:取得媒體串流(MediaStream)中媒體軌道(MediaStreamTrack)陣列
addTrack(MediaStreamTrack, MediaStream)
:增加媒體軌道(MediaStreamTrack)到 RTCPeerConnection 中指定的媒體串流(MediaStream)
const createPeerConnection = () => { const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }; peerConnection = new RTCPeerConnection(configuration);
localStream.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream); });
peerConnection.onicecandidate = (e) => { if (e.candidate) { socket.emit('ice_candidate', room, { label: e.candidate.sdpMLineIndex, id: e.candidate.sdpMid, candidate: e.candidate.candidate, }); } };
peerConnection.oniceconnectionstatechange = (e) => { if (e.target.iceConnectionState === 'disconnected') { hangup(); } };
peerConnection.onaddstream = ({ stream }) => { remoteVideo.srcObject = stream; }; };
|
點擊按鈕開始連線/關閉連線
createStream();
const call = () => { socketConnect(); createPeerConnection(); };
const hangup = () => { peerConnection.onicecandidate = null; peerConnection.onnegotiationneeded = null;
peerConnection.close(); peerConnection = null;
socket.emit('hangup', room); socket = null;
remoteVideo.srcObject = null; };
callBtn.addEventListener('click', call); hangupBtn.addEventListener('click', hangup);
|
這樣一對一聊天室就完成囉,後續會說明如何達成多方視訊功能
參考文章:
https://ithelp.ithome.com.tw/articles/10251454
https://ithelp.ithome.com.tw/articles/10209193
https://ithelp.ithome.com.tw/articles/10278727
https://medium.com/@jedy05097952/初探-webrtc-手把手建立線上視訊-1-5e9d4702e8e8
廣告
評論