行业知识
React中使用WebRTC
2026-11-04 07:40:09
来源:互联网转载
前言
- 有关WebRTC的一些概念可以参考另外一篇文章 WebRTC概念
- 我这里交换媒体信息、网络信息交换使用的是WebSocket,媒体信息是什么参考 WebRTC概念
- 以下的使用方法中,只有使用WebRTC传输通用数据跟音频流的,视频流要再自己配置一下
- 使用SFU结构,所以并没有用户与用户之间直接的信令交换,这些东西都给后台处理了,什么是SFU架构参考另外一篇文章 WebRTC中的SFU架构
usePeer.tsx
- 使用方法:userPeer导出一个localAudioRef,这个是本地音视频流的dom;还可以导出一个PeerRef,这是WebRTC要用的peer
- 其实localAudioRef好像不放这里面也是可以的,具体的情况实际使用的时候再决定吧
- 代码:
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(null); // 其他用户的音视频dom const localAudioRef = useRef(null) // 自己的音视频dom const 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(); 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
- 因为不想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.current await peer?.setRemoteDescription(offer); // 设置远端描述信息 const answer = await peer?.createAnswer(); // 生成answer await 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; // 心跳定时器 ID const 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': // 收到offer handleOffer(JSON.parse(msg.data)) break; case 'candidate': // 收到candidate handleCandidate(JSON.parse(msg.data)) break; } }; socket.onclose = () => { // 连接关闭 console.log('[ws close] 连接中断'); socketRef.current = undefined clearInterval(heartTimer); // 清除定时器 }; socket.onerror = (error) => { // 连接错误 console.log(`[error] 连接错误 `, error); }; return socket; } useEffect(() => { // 监听房间 socketRef.current = createSocket(); // 关闭socket的方法 // socketRef.current.close(); }, [])}export default useSocket 使用方法
- 要注意的是,我这里只是提供了一个大概的框架,具体的一些细节,比如说跟后台交换candidate、offer、answer这种,还是需要自己去填写的
- 另外一个点,如果按照这里搞出来,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 (localAudioRef})} ()()
上一篇:香港服务器违法怎么举报?
下一篇:完美世界怎么选服务器人数?