React中使用WebRTC

前言

  1. 有关WebRTC的一些概念可以参考另外一篇文章 WebRTC概念
  2. 我这里交换媒体信息、网络信息交换使用的是WebSocket,媒体信息是什么参考 WebRTC概念
  3. 以下的使用方法中,只有使用WebRTC传输通用数据跟音频流的,视频流要再自己配置一下
  4. 使用SFU结构,所以并没有用户与用户之间直接的信令交换,这些东西都给后台处理了,什么是SFU架构参考另外一篇文章 WebRTC中的SFU架构

usePeer.tsx

  1. 使用方法:userPeer导出一个localAudioRef,这个是本地音视频流的dom;还可以导出一个PeerRef,这是WebRTC要用的peer
  2. 其实localAudioRef好像不放这里面也是可以的,具体的情况实际使用的时候再决定吧
  3. 代码:
import { useEffect, useContext, useRef } from "react";
import { AppContext } from "../App";/*** peer socket 的初始化* @returns socket*/
const usePeer = () => {// 这是因为我的peerRef跟socketRef要在两个hook里面用到 所以就放全局了// 在实际使用的时候 可以return然后在调用usePeer的地方拿一下const { peerRef, socketRef } = useContext(AppContext)! const remoteAudioRef = useRef<HTMLDivElement>(null); // 其他用户的音视频domconst localAudioRef = useRef<HTMLAudioElement>(null) // 自己的音视频domconst createPeer = () => { // peer创建const peer = new RTCPeerConnection();peer.onicecandidate = (event) => { // 收到自己的candidate// 使用ws发送candidate 这里的ws自己写就好了}peer.ontrack = (event) => { // 收到对方的流轨道// 动态生成是为了一个房间有多个人 这里只用到音频 所以如果要视频的话可以在这里操作一下const audio = document.createElement('audio');audio.srcObject = event.streams[0];audio.autoplay = true;audio.controls = false;remoteAudioRef.current?.appendChild(audio);event.track.onmute = () => { // 静音audio.play();} event.streams[0].onremovetrack = () => { // 对象移除if(audio.parentNode) {audio.parentNode.removeChild(audio);}}}return peer;}const getLocalStream = async () => { // 打开视频音频流const stream = await navigator.mediaDevices.getUserMedia({audio: true,video: false, // 如果要视频这里可以打开})return stream;}const handleLocalStream = async () => { // 获取 处理本地音频流const stream = await getLocalStream();stream.getTracks().forEach((track) => {peerRef.current?.addTrack(track, stream);			})}// 我这里只初始化peer一次 具体使用的时候可以结合自己的需求进行peer的创建跟关闭处理useEffect(()=>{ handleLocalStream()peerRef.current = createPeer();// 这个是关闭peer的方法// peerRef.current.close();},[])return { localAudioRef }
}export default usePeer;

useDatachannel.tsx

import { useEffect, useRef, useContext } from "react";
import { AppContext } from "../App";/*** 数据通道初始化* return 想要的话dataChannel也可以传出去的 或者把有关处理都放这里面也行*/
const useDataChannel = () => {// 从全局拿到peerRef 如果不放全局的话 可以直接传进来const { peerRef } = useContext(AppContext)!const dataChannel = useRef<RTCDataChannel>();const createDataChannel = () => { // dataChannel创建// 创建数据通道const channel = peerRef.current!.createDataChannel("myDataChannel66666666_1395212519");channel.onopen = () => {console.log("[dataChannel open]");}channel.onmessage = (event) => {// 在这里接收通道数据}channel.onclose = () => {console.log("[dataChannel close]");}return channel}useEffect(()=>{ // 监听用户是否在房间中dataChannel.current = createDataChannel();// 这个是关闭通道的方法// dataChannel.current.close();// 这个是发送数据的方法// dataChannel.current.send()},[])}export default useDataChannel

useSocketHandle.tsx

  1. 因为不想useSocket太多代码了,所以分了一个这样的文件出来,主要是ws收到信息的函数
import { useRef, useContext, useEffect } from "react";
import { AppContext } from "../../App";const useHandleOffer = () => {const { peerRef, socketRef } = useContext(AppContext)!const handleOffer = async (offer: any) => { // 收到offer的处理const peer = peerRef.currentawait peer?.setRemoteDescription(offer); // 设置远端描述信息const answer = await peer?.createAnswer(); // 生成answerawait peer?.setLocalDescription(answer); // 设置本地描述信息socketRef.current?.send() // 按照跟后台约定好的格式发送自己的answer}const handleCandidate = (candidate: any) => { // 收到candidate的处理peerRef.current?.addIceCandidate(candidate); // 添加candidate}return { handleOffer, handleCandidate } 
}export default useHandleOffer;

useSocket.tsx

import { useEffect, useContext } from "react";
import { AppContext } from "../../App";
import useSocketHandle from "./useSocketHandle";const WS_URL = 'wss://xxx' // 服务地址const useSocket = () => {const { handleOffer, handleCandidate } = useSocketHandle(); // ws处理函数const { socketRef } = useContext(AppContext)! // 用全局的let heartTimer = 0; // 心跳定时器 IDconst heartCheck = (socket: WebSocket) => { // 心跳检查clearInterval(heartTimer); // 先清除之前的定时器heartTimer = setInterval(() => {socket.send('xxx'); // 约定好的心跳}, 30000);}const createSocket = () => { // socket创建if (socketRef.current) return;const socket = new WebSocket(`${WS_URL}`) // 信令服务器连接socket.onopen = () => { // 连接建立console.log("[ws open] 连接已建立");heartCheck(socket);// 心跳处理};socket.onmessage = async (event) => { // 接收到服务器的信息const msg = JSON.parse(event.data) // 这个主要看跟后台约定的格式switch (msg.event) {case 'offer': // 收到offerhandleOffer(JSON.parse(msg.data))break;case 'candidate': // 收到candidatehandleCandidate(JSON.parse(msg.data))break;}};socket.onclose = () => { // 连接关闭console.log('[ws close] 连接中断');socketRef.current = undefinedclearInterval(heartTimer); // 清除定时器};socket.onerror = (error) => { // 连接错误console.log(`[error] 连接错误 `, error);};return socket;}useEffect(() => { // 监听房间socketRef.current = createSocket();// 关闭socket的方法// socketRef.current.close();}, [])
}export default useSocket

使用方法

  1. 要注意的是,我这里只是提供了一个大概的框架,具体的一些细节,比如说跟后台交换candidate、offer、answer这种,还是需要自己去填写的
  2. 另外一个点,如果按照这里搞出来,answer、offer什么的都完成了交换,视频也不一定有的,你要自己加上一些视频的配置,比如说获取音视频流的时候video变为true,以及动态生成元素的时候也把video给生成一下。
import usePeer from '../../hooks/usePeer';
import useDataChannel from '../../hooks/useDataChannel';
import useSocket from '../../hooks/socket/useSocket';export default function Home() {const { localAudioRef } = usePeer()useSocket()useDataChannel()return (<div className='Home'><div className="remoteAudioContainer"></div><audio src="" ref={localAudioRef}></audio></div>)
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微短剧,会成为长视频的“救命稻草”吗?

职场社畜秒变霸道总裁&#xff0c;普通女孩穿越成为艳丽皇妃.......这样“狗血”的微短剧&#xff0c;最近不仅在国内各大视频平台上异常火爆&#xff0c;而且还直接火出了国外。 所谓微短剧&#xff0c;就是单集时长从几十秒到十几分钟的剧集&#xff0c;有着相对明确的主题和…

基于 OV2640 的以太网 RGMII 图像传输系统设计

文章目录 前言一、UDP 协议的特点二、图像数据编码原理三、系统总体设计四、图像编码模块介绍4.1、图像编码模块作用4.2、图像编码模块功能实现4.3、仿真五、其他涉及模块说明六、顶层模块七、下载与验证前言 本节主要讲述了一种对数据以行为单位的编码方法。该方法采用摄像头…

树莓派Pi4B简介

树莓派是什么&#xff1f;Raspberry Pi(中文名为“树莓派”,简写为RPi&#xff0c;或者RasPi/RPi)是为学生计算机编程教育而设计&#xff0c;只有信用卡大小的卡片式电脑&#xff0c;其系统基于Linux。 树莓派4B与树莓派3B/3B参数对比&#xff1a; 具体的实物图如下&#xff1a…

PDF控件Spire.PDF for .NET【安全】演示:在 PDF 中添加或删除数字签名

随着 PDF 文档在商业中越来越流行&#xff0c;确保其真实性已成为一个关键问题。使用基于证书的签名对 PDF 进行签名可以保护内容&#xff0c;还可以让其他人知道谁签署或批准了该文档。在本文中&#xff0c;您将了解如何使用不可见或可见签名对 PDF 进行数字签名&#xff0c;以…

2023年教程汇总 | 《小杜的生信笔记》

2023年总结 2023年即将结束&#xff0c;我们即将迎来2024年。2023年&#xff0c;我们做了什么呢&#xff1f;&#xff1f;这个是个值得深思的问题…? 12月份是个快乐且痛苦时间节点。前一段时间&#xff0c;单位需要提交2023年工作总结&#xff0c;真的是憋了好久才可以下笔…

国产编程语言MoonBit添加问号操作符

MoonBit更新 01. 添加内置类型 Result enum Result[T, E] {Ok(T)Err(E) }02. 添加问号操作符 新增了问号操作符&#xff0c;用于简化错误处理&#xff1a; fn may_fail() -> Option[Int] { ... }fn compose_may_fail() -> Option[String] {let x may_fail()?let y …

ioDraw AI:思维导图、流程图、序列图、类图、饼图,一应俱全

前言 在信息爆炸的时代&#xff0c;我们每天接收着大量的信息&#xff0c;如何高效地整理和呈现这些信息成为了一项重要的挑战。思维导图作为一种可视化思维工具&#xff0c;能够帮助我们快速构建和整理复杂的信息结构&#xff0c;便于我们理解和记忆。ioDraw AI绘图工具正是基…

RTOS_WDS

2023/12/25重启韦东山老师RTO 韦东山freeRTOS快速入门视频教程 P2 2-1堆的概念 堆 char heap_buf[1024]; int pos 0;void *my_malloc(int size) {int old_pos pos;pos size;return &heap_buf[old_pos]; }void my_free(void *buf) {/* err */ }int main(void) {char ch…

MySQL的聚簇索引和非聚簇索引的区别以及示例

MySQL的聚簇索引和非聚簇索引 聚簇索引 聚簇索引是一种索引结构&#xff0c;它与数据行存储在一起&#xff0c;即索引的叶子节点就是数据行本身。在MySQL中&#xff0c;主键索引就是一种典型的聚簇索引。 涉及情况 当查询需要按照主键或唯一索引进行精确查找时&#xff0c;…

react+koa全栈开发 以及 部署流程

前端开发后端开发部署 前端开发 前端使用react、sass、TS、vite、pnpm进行开发&#xff0c;太详细的这里就不展开说了项目创建可以参考我的另外一篇文章 优雅地创建一个前端项目 后端开发 后端使用node&#xff0c;使用koa框架进行开发&#xff0c;数据库我使用的是一个mys…

Android studio 使用greenDao根据实体类生成dao类

1.遇到的问题 使用android studio根据实体类生成dao其实也很简单&#xff0c;你只要实现 Parcelable Entity public class ConfigDataModel implements Parcelable {Id(autoincrement true)private Long id null; } 2.使用自带的方法生成 使用build-->make Project生成 …

【PostgreSQL】从零开始:(三十)数据类型-Arrays数组类型

数组 数组是一种数据结构&#xff0c;可以容纳多个相同类型的元素。数组可以存储基本数据类型&#xff08;如整数、浮点数等&#xff09;或者对象类型&#xff08;如字符串、自定义对象等&#xff09;。在大多数编程语言中&#xff0c;数组有固定的大小&#xff0c;一旦声明后…

学Java的第二天

一、常量 1.值不可以变化的量。 2. 分类&#xff1a; 字符串常量 用双引号括起来的多个字符&#xff0c;可以包含 0、1 或多个&#xff0c;例如 "a" 、 "abc" 、 " 中国 " 整数常量&#xff0c;例如&#xff1a; -10 、 0 、 88 小数常量&…

华为路由器ACL操作SSH接口

ACL的定义 访问控制列表&#xff08;Access Control Lists&#xff0c;ACL&#xff09;是应用在路由器接口的指令列表。这些指令列表用来告诉路由器哪些数据包可以收、哪些数据包需要拒绝。至于数据包是被接收还是拒绝&#xff0c;可以由类似于源地址、目的地址、端口号等的特…

链表总结篇

链表的理论基础 链表的种类主要为&#xff1a;单链表&#xff0c;双链表&#xff0c;循环链表链表的存储方式&#xff1a;链表的节点在内存中是分散存储的&#xff0c;通过指针连在一起。链表是如何进行增删改查的。数组和链表在不同场景下的性能分析。 链表经典题目 虚拟头…

在x64上构建智能家居(home assistant) (六) 安装Node-RED Companion Integration

点击HACS 搜索node-red 右侧单击后点击安装 安装完成后, 选设备

分别使用OVP-UVP和OFP-UFP算法以及AFD检测算法实现反孤岛检测simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 OVP-UVP算法 4.2 OFP-UFP算法 4.3 AFD检测算法 5.完整工程文件 1.课题概述 分别使用OVP-UVP和OFP-UFP算法以及AFD检测算法实现反孤岛检测simulink建模与仿真。 2.系统仿真结果 3.核心程序与模型…

Redis案例实战之Bitmap、Hyperloglog、GEO

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

【如何破坏单例模式(详解)】

✅如何破坏单例模式 &#x1f4a1;典型解析✅拓展知识仓✅反射破坏单例✅反序列化破坏单例✅ObjectlnputStream ✅总结✅如何避免单例被破坏✅ 避免反射破坏单例✅ 避免反序列化破坏单例 &#x1f4a1;典型解析 单例模式主要是通过把一个类的构造方法私有化&#xff0c;来避免重…

uniapp框架——vue3+uniFilePicker+fastapi实现文件上传(搭建ai项目第二步)

文章目录 ⭐前言&#x1f496; 小程序系列文章 ⭐uni-file-picker 组件&#x1f496; 绑定事件&#x1f496; uploadFile api&#x1f496; 自定义上传 ⭐后端fastapi定义上传接口⭐uniapp开启本地请求代理devServer⭐前后端联调⭐总结⭐结束 ⭐前言 大家好&#xff0c;我是ym…