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); // 其他用户的音视频dom
  const localAudioRef = useRef<HTMLAudioElement>(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<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.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

使用方法

  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>
	)
}

相关推荐

  1. React使用WebRTC

    2023-12-26 08:10:02       41 阅读
  2. vue使用webrtc streamer linux

    2023-12-26 08:10:02       21 阅读
  3. React 使用 TS

    2023-12-26 08:10:02       14 阅读
  4. reactzustand的使用

    2023-12-26 08:10:02       18 阅读
  5. React使用antDesign框架

    2023-12-26 08:10:02       17 阅读
  6. React useReducer的使用

    2023-12-26 08:10:02       18 阅读
  7. reactuseReducer如何使用

    2023-12-26 08:10:02       17 阅读
  8. react 项目使用 iconfont

    2023-12-26 08:10:02       11 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-26 08:10:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-26 08:10:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-26 08:10:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-26 08:10:02       20 阅读

热门阅读

  1. $(sort $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/crc/*.c))

    2023-12-26 08:10:02       30 阅读
  2. SparkCore

    SparkCore

    2023-12-26 08:10:02      29 阅读
  3. python 图像处理ORB算法

    2023-12-26 08:10:02       45 阅读
  4. flutter项目从创建到运行,以及一些常用的命令

    2023-12-26 08:10:02       36 阅读
  5. [HADOOP]数据倾斜的避免和处理

    2023-12-26 08:10:02       41 阅读
  6. Hadoop——分布式计算

    2023-12-26 08:10:02       27 阅读
  7. 支持向量机(SVM)

    2023-12-26 08:10:02       31 阅读
  8. KafkaLog4jAppender

    2023-12-26 08:10:02       36 阅读
  9. Lukelabs OJ刷题——Z1013

    2023-12-26 08:10:02       32 阅读
  10. hive中struct相关函数总结

    2023-12-26 08:10:02       43 阅读