原文首发链接:Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案
大家好,我是码农先森。
引言
这次实现音视频实时通信的方案是基于 WebRTC 技术的,它是一种点对点的通信技术,通过浏览器之间建立对等连接,实现音频和视频流数据的传输。
在 WebRTC 技术中通常使用 WebSocket 服务来协调浏览器之间的通信,建立 WebRTC 通信的信道,传输通信所需的元数据信息,如:SDP、ICE 候选项等。
WebRTC 技术在实时通信领域中得到了广泛应用,包括在线会议、视频聊天、远程协作等,例如:腾讯在线会议就是基于此技术实现的。
技术实现
index.html 作为首页,这里提供了发起方、接收方的操作入口。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>p2p webrtc</title><style>.container {width: 250px;margin: 100px auto;padding: 10px 30px;border-radius: 4px;border: 1px solid #ebeef5;box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);color: #303133;}</style>
</head>
<body><div class="container"><p>流程:</p><ul><li>打开<a href="/p2p?type=answer" target="_blank">接收方页面</a>;</li><li>打开<a href="/p2p?type=offer" target="_blank">发起方页面</a>;</li><li>确认双方都已建立 WebSocket 连接;</li><li>发起方点击 开始 按钮。</li></ul></div>
</body>
</html>
p2p.html 作为视频展示页面,且实现了调取摄像头及音频权限的功能,再将连接数据推送到 WebSocket 服务端,最后渲染远程端的音视频数据到本地。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title></title><style>* {padding: 0;margin: 0;box-sizing: border-box;}.container {width: 100%;display: flex;display: -webkit-flex;justify-content: space-around;padding-top: 20px;}.video-box {position: relative;width: 330px;height: 550px;}#remote-video {width: 100%;height: 100%;display: block;object-fit: cover;border: 1px solid #eee;background-color: #F2F6FC;}#local-video {position: absolute;right: 0;bottom: 0;width: 140px;height: 200px;object-fit: cover;border: 1px solid #eee;background-color: #EBEEF5;}.start-button {position: absolute;left: 50%;top: 50%;width: 100px;display: none;line-height: 40px;outline: none;color: #fff;background-color: #409eff;border: none;border-radius: 4px;cursor: pointer;transform: translate(-50%, -50%);}</style>
</head>
<body><div class="container"><div class="video-box"><video id="remote-video"></video><video id="local-video" muted></video><button class="start-button" onclick="startLive()">开始</button></div></div><script>const target = location.search.slice(6);const localVideo = document.querySelector('#local-video');const remoteVideo = document.querySelector('#remote-video');const button = document.querySelector('.start-button');localVideo.onloadeddata = () => {console.log('播放本地视频');localVideo.play();}remoteVideo.onloadeddata = () => {console.log('播放对方视频');remoteVideo.play();}document.title = target === 'offer' ? '发起方' : '接收方';console.log('信令通道(WebSocket)创建中......');const socket = new WebSocket('ws://127.0.0.1:9502');socket.onopen = () => {console.log('信令通道创建成功!');target === 'offer' && (button.style.display = 'block');}socket.onerror = () => console.error('信令通道创建失败!');socket.onmessage = e => {const { type, sdp, iceCandidate } = JSON.parse(e.data)if (type === 'answer') {peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));} else if (type === 'answer_ice') {peer.addIceCandidate(iceCandidate);} else if (type === 'offer') {startLive(new RTCSessionDescription({ type, sdp }));} else if (type === 'offer_ice') {peer.addIceCandidate(iceCandidate);}};const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;!PeerConnection && console.error('浏览器不支持WebRTC!');const peer = new PeerConnection();peer.ontrack = e => {if (e && e.streams) {console.log('收到对方音频/视频流数据...');remoteVideo.srcObject = e.streams[0];}};peer.onicecandidate = e => {if (e.candidate) {console.log('搜集并发送候选人');socket.send(JSON.stringify({type: `${target}_ice`,iceCandidate: e.candidate}));} else {console.log('候选人收集完成!');}};async function startLive (offerSdp) {target === 'offer' && (button.style.display = 'none');let stream;try {console.log('尝试调取本地摄像头/麦克风');stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });console.log('摄像头/麦克风获取成功!');localVideo.srcObject = stream;} catch {console.error('摄像头/麦克风获取失败!');return;}console.log(`------ WebRTC ${target === 'offer' ? '发起方' : '接收方'}流程开始 ------`);console.log('将媒体轨道添加到轨道集');stream.getTracks().forEach(track => {peer.addTrack(track, stream);});if (!offerSdp) {console.log('创建本地SDP');const offer = await peer.createOffer();await peer.setLocalDescription(offer);console.log(`传输发起方本地SDP`);socket.send(JSON.stringify(offer));} else {console.log('接收到发送方SDP');await peer.setRemoteDescription(offerSdp);console.log('创建接收方(应答)SDP');const answer = await peer.createAnswer();console.log(`传输接收方(应答)SDP`);socket.send(JSON.stringify(answer));await peer.setLocalDescription(answer);}}</script>
</body>
</html>
在 http_server.php 文件中实现了一个 Web 服务,并根据不同的路由返回对应的 HTML 页面服务,主要是用于提供视频页面的展示。
<?php// 创建一个 HTTP 服务
$http = new Swoole\Http\Server("0.0.0.0", 9501);// 监听客户端请求
$http->on('request', function ($request, $response) {$path = $request->server['request_uri'];switch ($path) {case '/':$html = file_get_contents("index.html");$response->header("Content-Type", "text/html");$response->end($html);break;case '/p2p':$html = file_get_contents("p2p.html");$response->header("Content-Type", "text/html");$response->end($html);break;default:$response->status(404);$response->end("Page Not Found");break;}
});// 启动 HTTP 服务
$http->start();
在 websocket_server.php 文件中实现了一个 WebSocket 服务,并设置了 onOpen、onMessage 和 onClose 回调函数。在 onMessage 回调函数中,遍历所有连接,将消息发送给除当前连接外的其他连接。
<?php// 创建 WebSocket 服务
$server = new Swoole\WebSocket\Server("0.0.0.0", 9502);// 监听 WebSocket 连接事件
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {echo "新的客户端连接: {$request->fd}\n";
});// 监听 WebSocket 消息事件
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {echo "收到消息来自 {$frame->fd}: {$frame->data}, 广播给其他的客户端\n";// 广播给其他的客户端foreach ($server->connections as $fd) {if ($fd != $frame->fd) {$server->push($fd, $frame->data);}}
});// 监听 WebSocket 关闭事件
$server->on('close', function ($ser, $fd) {echo "客户端 {$fd} 关闭连接\n";
});// 启 WebSocket 服务
$server->start();
总结
音视频通信技术方案是基于 WebRTC 实现的,Swoole 在其中的作用是提供了页面的 Web 服务及协调浏览器之间通信的 WebSocket 服务。
WebRTC 是一项重要的技术,它使得实时音视频通信变得简单而高效。通过基于浏览器的 API,WebRTC 可以实现点对点的音视频通信。