Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案

2024-04-17 04:12

本文主要是介绍Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文首发链接: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 可以实现点对点的音视频通信。

这篇关于Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/910765

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

SpringBoot3.X 整合 MinIO 存储原生方案

《SpringBoot3.X整合MinIO存储原生方案》本文详细介绍了SpringBoot3.X整合MinIO的原生方案,从环境搭建到核心功能实现,涵盖了文件上传、下载、删除等常用操作,并补充了... 目录SpringBoot3.X整合MinIO存储原生方案:从环境搭建到实战开发一、前言:为什么选择MinI

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被