mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-01-31 00:18:55 +01:00
162 lines
4.0 KiB
TypeScript
162 lines
4.0 KiB
TypeScript
|
import { baseUrl } from "@/api/baseUrl";
|
||
|
import { useCallback, useEffect, useRef } from "react";
|
||
|
|
||
|
type WebRtcPlayerProps = {
|
||
|
camera: string;
|
||
|
width?: number;
|
||
|
height?: number;
|
||
|
};
|
||
|
|
||
|
export default function WebRtcPlayer({
|
||
|
camera,
|
||
|
width,
|
||
|
height,
|
||
|
}: WebRtcPlayerProps) {
|
||
|
const pcRef = useRef<RTCPeerConnection | undefined>();
|
||
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||
|
const PeerConnection = useCallback(
|
||
|
async (media: string) => {
|
||
|
if (!videoRef.current) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const pc = new RTCPeerConnection({
|
||
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||
|
});
|
||
|
|
||
|
const localTracks = [];
|
||
|
|
||
|
if (/camera|microphone/.test(media)) {
|
||
|
const tracks = await getMediaTracks("user", {
|
||
|
video: media.indexOf("camera") >= 0,
|
||
|
audio: media.indexOf("microphone") >= 0,
|
||
|
});
|
||
|
tracks.forEach((track) => {
|
||
|
pc.addTransceiver(track, { direction: "sendonly" });
|
||
|
if (track.kind === "video") localTracks.push(track);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (media.indexOf("display") >= 0) {
|
||
|
const tracks = await getMediaTracks("display", {
|
||
|
video: true,
|
||
|
audio: media.indexOf("speaker") >= 0,
|
||
|
});
|
||
|
tracks.forEach((track) => {
|
||
|
pc.addTransceiver(track, { direction: "sendonly" });
|
||
|
if (track.kind === "video") localTracks.push(track);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (/video|audio/.test(media)) {
|
||
|
const tracks = ["video", "audio"]
|
||
|
.filter((kind) => media.indexOf(kind) >= 0)
|
||
|
.map(
|
||
|
(kind) =>
|
||
|
pc.addTransceiver(kind, { direction: "recvonly" }).receiver.track
|
||
|
);
|
||
|
localTracks.push(...tracks);
|
||
|
}
|
||
|
|
||
|
videoRef.current.srcObject = new MediaStream(localTracks);
|
||
|
return pc;
|
||
|
},
|
||
|
[videoRef]
|
||
|
);
|
||
|
|
||
|
async function getMediaTracks(
|
||
|
media: string,
|
||
|
constraints: MediaStreamConstraints
|
||
|
) {
|
||
|
try {
|
||
|
const stream =
|
||
|
media === "user"
|
||
|
? await navigator.mediaDevices.getUserMedia(constraints)
|
||
|
: await navigator.mediaDevices.getDisplayMedia(constraints);
|
||
|
return stream.getTracks();
|
||
|
} catch (e) {
|
||
|
return [];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const connect = useCallback(
|
||
|
async (ws: WebSocket, aPc: Promise<RTCPeerConnection | undefined>) => {
|
||
|
if (!aPc) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pcRef.current = await aPc;
|
||
|
|
||
|
ws.addEventListener("open", () => {
|
||
|
pcRef.current?.addEventListener("icecandidate", (ev) => {
|
||
|
if (!ev.candidate) return;
|
||
|
const msg = {
|
||
|
type: "webrtc/candidate",
|
||
|
value: ev.candidate.candidate,
|
||
|
};
|
||
|
ws.send(JSON.stringify(msg));
|
||
|
});
|
||
|
|
||
|
pcRef.current
|
||
|
?.createOffer()
|
||
|
.then((offer) => pcRef.current?.setLocalDescription(offer))
|
||
|
.then(() => {
|
||
|
const msg = {
|
||
|
type: "webrtc/offer",
|
||
|
value: pcRef.current?.localDescription?.sdp,
|
||
|
};
|
||
|
ws.send(JSON.stringify(msg));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
ws.addEventListener("message", (ev) => {
|
||
|
const msg = JSON.parse(ev.data);
|
||
|
if (msg.type === "webrtc/candidate") {
|
||
|
pcRef.current?.addIceCandidate({ candidate: msg.value, sdpMid: "0" });
|
||
|
} else if (msg.type === "webrtc/answer") {
|
||
|
pcRef.current?.setRemoteDescription({
|
||
|
type: "answer",
|
||
|
sdp: msg.value,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
[]
|
||
|
);
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (!videoRef.current) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const url = `${baseUrl.replace(
|
||
|
/^http/,
|
||
|
"ws"
|
||
|
)}live/webrtc/api/ws?src=${camera}`;
|
||
|
const ws = new WebSocket(url);
|
||
|
const aPc = PeerConnection("video+audio");
|
||
|
connect(ws, aPc);
|
||
|
|
||
|
return () => {
|
||
|
if (pcRef.current) {
|
||
|
pcRef.current.close();
|
||
|
pcRef.current = undefined;
|
||
|
}
|
||
|
};
|
||
|
}, [camera, connect, PeerConnection, pcRef, videoRef]);
|
||
|
|
||
|
return (
|
||
|
<div>
|
||
|
<video
|
||
|
ref={videoRef}
|
||
|
autoPlay
|
||
|
playsInline
|
||
|
controls
|
||
|
muted
|
||
|
width={width}
|
||
|
height={height}
|
||
|
/>
|
||
|
</div>
|
||
|
);
|
||
|
}
|