前言
有关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" ;
const usePeer = ( ) => { const { peerRef, socketRef } = useContext ( AppContext) ! const remoteAudioRef = useRef < HTMLDivElement> ( null ) ; const localAudioRef = useRef < HTMLAudioElement> ( null ) const createPeer = ( ) => { const peer = new RTCPeerConnection ( ) ; peer. onicecandidate = ( event) => { } 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) ; } ) } useEffect ( ( ) => { handleLocalStream ( ) peerRef. current = createPeer ( ) ; } , [ ] ) return { localAudioRef }
} export default usePeer;
useDatachannel.tsx
import { useEffect, useRef, useContext } from "react" ;
import { AppContext } from "../App" ;
const useDataChannel = ( ) => { const { peerRef } = useContext ( AppContext) ! const dataChannel = useRef < RTCDataChannel> ( ) ; const createDataChannel = ( ) => { 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 ( ) ; } , [ ] ) } 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 ) => { const peer = peerRef. currentawait peer?. setRemoteDescription ( offer) ; const answer = await peer?. createAnswer ( ) ; await peer?. setLocalDescription ( answer) ; socketRef. current?. send ( ) } const handleCandidate = ( candidate: any ) => { peerRef. current?. addIceCandidate ( 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 ( ) ; const { socketRef } = useContext ( AppContext) ! let heartTimer = 0 ; const heartCheck = ( socket: WebSocket) => { clearInterval ( heartTimer) ; heartTimer = setInterval ( ( ) => { socket. send ( 'xxx' ) ; } , 30000 ) ; } const createSocket = ( ) => { 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' : handleOffer ( JSON . parse ( msg. data) ) break ; case '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 ( ) ; } , [ ] )
} 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 ( < div className= 'Home' > < div className= "remoteAudioContainer" > < / div> < audio src= "" ref= { localAudioRef} > < / audio> < / div> )
}