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 可以实现点对点的音视频通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/550327.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

InfiniGate自研网关实现思路一

1.HTTP请求会话协议处理 运用Netty对HTTP请求信息的协议转换&#xff0c;构建网关会话服务&#xff0c;简单响应HTTP请求信息到页面。 之所以称之为会话&#xff0c;是因为一次 HTTP 请求&#xff0c;就要完成&#xff1b;建立连接、协议转换、方法映射、泛化调用、返回结果等…

吃透2000-2024年600道真题和解析,科学高效通过2025年AMC8竞赛

为帮助孩子科学、有效备考AMC8竞赛&#xff0c;我整理了2000-2004年的全部AMC8真题&#xff08;完整版共600道&#xff0c;且修正了官方发布的原试卷中的少量bug&#xff09;&#xff0c;并且独家制作成多种在线练习&#xff0c;利用碎片化时间&#xff0c;8个多月的时间足以通…

【Redis 神秘大陆】008 常见Java客户端

八、Redis 的 Java 客户端 8.1 Jedis 连接池 单点连接池 Jedis 连接池基于 Common-Pool 连接池里面放置的是空闲连接&#xff0c;如果被使用 &#xff08;borrow&#xff09;掉&#xff0c;连接池就会少一个连接&#xff0c;连接使用完后进行放回 &#xff08;return&#…

HCIA-Datacom实验_05_实验三:OSPF路由协议基础实验

一、实验拓扑 二、实验步骤 (一)修改设备名称、配置接口IP、Loopback口IP R1 R2 R3 (二)配置OSPF R1 R2 R3 (三)配置认证 R1 R2 R3

【学习笔记八】EWM仓库流程类型的后台配置和前台测试

一、仓库流程类型概述 仓库流程类型&#xff08;Warehouse Process Types&#xff09;&#xff0c;简称WPT 在SAP扩展仓库管理(SAPEWM)中&#xff0c;WPT控制仓库内每个流程的活动或移动&#xff08;如收货、发货、过帐更改&#xff09;。仓库流程类型被分配给每个仓库请求项目…

【机器学习】贝叶斯算法在机器学习中的应用与实例分析

贝叶斯算法在机器学习中的应用与实例分析 一、贝叶斯算法原理及重要性二、朴素贝叶斯分类器的实现三、贝叶斯网络在自然语言处理中的应用四、总结与展望 在人工智能的浪潮中&#xff0c;机器学习以其独特的魅力引领着科技领域的创新。其中&#xff0c;贝叶斯算法以其概率推理的…

1、MYSQL系列-深入理解Mysql索引底层数据结构与算法

索引的本质 索引是帮助MySQL高效获取数据的排好序的数据结构 索引数据结构 二叉树红黑树Hash表BTree B-Tree B-Tree 叶节点具有相同的深度&#xff0c;叶节点的指针为空&#xff0c;所有索引元素不重复&#xff0c;节点中的数据索引从左到右递增排列 BTree(B-Tree变种) 非叶…

DataGrip2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 DataGrip是由JetBrains公司开发的一款强大的关系数据库集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为数据库开发人员和数据库管理员设计。它提供了一个统一的界面&#xff0c;用于管理和开发各种关系型数据库&#x…

Linux内核之WRITE_ONCE用法实例(四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

stm32实现hid键盘

前面的cubelmx项目配置参考 stm32实现hid鼠标-CSDN博客https://blog.csdn.net/anlog/article/details/137814494?spm1001.2014.3001.5502两个项目的配置完全相同。 代码 引用 键盘代码&#xff1a; 替换hid设备描述符 先屏蔽鼠标设备描述符 替换为键盘设备描述符 修改宏定…

深度学习 Lecture 7 迁移学习、精确率、召回率和F1评分

一、迁移学习&#xff08;Transfer learning) 用来自不同任务的数据来帮助我解决当前任务。 场景&#xff1a;比如现在我想要识别从0到9度手写数字&#xff0c;但是我没有那么多手写数字的带标签数据。我可以找到一个很大的数据集&#xff0c;比如有一百万张图片的猫、狗、汽…

基于Adaboost模型的数据预测和分类matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 AdaBoost&#xff08;Adaptive Boosting&#xff09;是一种集成学习方法&#xff0c;由Yoav Freund和Robert Schapire于1995年提出&#xff0c;主要用于提高弱分类…

UML 介绍

前言 UML 简介。 文章目录 前言一、简介1、事务2、关系1&#xff09;依赖2&#xff09;关联聚合组合 3&#xff09;泛化4&#xff09;实现 二、类图三、对象图四、用例图五、交互图1、序列图&#xff08;顺序图&#xff09;2、通信图 六、状态图七、活动图八、构件图&#xff0…

详解UART通信协议以及FPGA实现

文章目录 一、UART概述二、UART协议帧格式2.1 波特率2.2 奇校验ODD2.3 偶校验EVEN 三、UART接收器设计3.1 接收时序图3.2 Verilog代码3.3 仿真文件测试3.4 仿真结果3.5 上版测试 四、UART发送器设计4.1 发送时序图4.2 Verilog代码4.3 仿真文件测试4.4 仿真结果4.5 上板测试 五、…

HarmonyOS开发实战:【亲子拼图游戏】

概述 本篇Codelab是基于TS扩展的声明式开发范式编程语言编写的一个分布式益智拼图游戏&#xff0c;可以两台设备同时开启一局拼图游戏&#xff0c;每次点击九宫格内的图片&#xff0c;都会同步更新两台设备的图片位置。效果图如下&#xff1a; 说明&#xff1a; 本示例涉及使…

2016NOIP普及组真题 1. 金币

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1969 核心思想&#xff1a; 解法1、由于数据量只有 10000 天&#xff0c;估可以采用 模拟每一天 的方式。 #include <bits/stdc.h> using namespace std;int k 0;int main() {i…

SpringBoot项目基于java的教学辅助平台

采用技术 SpringBoot项目基于java的教学辅助平台的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 学生信息管理 教师信息管理 课程信息管理 科目分类管…

【面试经典 150 | 链表】K 个一组翻转链表

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;迭代 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行回顾…

xxl-job使用自动注册节点,ip不对,如何解决????

很明显这时我们本机的ip和我们xxl-job自动注册的ip是不一致的&#xff0c;此时该如何处理呢&#xff1f;&#xff1f;&#xff1f;&#xff1f; 方法一&#xff1a;在配置文件中&#xff0c;将我们的ip固定写好。 ### xxl-job executor server-info xxl.job.executor.ip写你的…

pandas基本用法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas的数据结构1、一维数组pd.Series1.1 pd.Series&#xff08;data,index,dtype&#xff09;示例1&#xff1a;不定义index示例2&#xff1a;自定义inde…