前言
- 有关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.current
await 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>
)
}