"use client"; import { useEffect, useRef, useState } from "react"; function describeDeviceError(error: unknown): string { const name = error instanceof Error ? error.name : ""; if (name === "NotAllowedError" || name === "SecurityError") { return "Der Browser blockiert Kamera oder Mikrofon. Erlaube beide über das Schloss-Symbol neben der Adresse und versuche es danach erneut."; } if (name === "NotFoundError") { return "Es wurde keine Kamera oder kein Mikrofon gefunden. Prüfe, ob beide Geräte angeschlossen und in den Systemeinstellungen freigegeben sind."; } if (name === "NotReadableError" || name === "TrackStartError") { return "Kamera oder Mikrofon werden gerade von einer anderen App verwendet. Schließe zum Beispiel Zoom, Teams oder eine andere Browser-Registerkarte und versuche es erneut."; } return "Kamera oder Mikrofon konnten nicht gestartet werden. Du kannst dem Meeting trotzdem ohne sie beitreten."; } export function PreJoinScreen({ onContinue }: { onContinue: (tracks: MediaStreamTrack[]) => void }) { const [tracks, setTracks] = useState([]); const [error, setError] = useState(null); const [micOn, setMicOn] = useState(true); const [cameraOn, setCameraOn] = useState(true); const [speakerTested, setSpeakerTested] = useState(false); const [requestingDevices, setRequestingDevices] = useState(false); const videoRef = useRef(null); const tracksRef = useRef([]); const handedOffRef = useRef(false); const videoTrack = tracks.find((track) => track.kind === "video"); const audioTrack = tracks.find((track) => track.kind === "audio"); useEffect(() => { const videoElement = videoRef.current; if (!videoElement || !videoTrack || !cameraOn) return; videoElement.srcObject = new MediaStream([videoTrack]); void videoElement.play().catch(() => { // Autoplay may be restricted until the user interacts with the page. }); return () => { videoElement.srcObject = null; }; }, [cameraOn, videoTrack]); useEffect(() => { return () => { if (!handedOffRef.current) tracksRef.current.forEach((track) => track.stop()); }; }, []); async function requestDevices() { if (requestingDevices) return; setError(null); if (!navigator.mediaDevices?.getUserMedia) { setError("Dein Browser unterstützt keine Kamera- und Mikrofonfreigabe für Video-Calls."); return; } // Start the native browser request immediately while this click still has // user activation. LiveKit receives these tracks only after the preview. let request: Promise; try { request = navigator.mediaDevices.getUserMedia({ audio: { autoGainControl: true, echoCancellation: true, noiseSuppression: true }, video: { facingMode: "user", height: { ideal: 720 }, width: { ideal: 1280 } }, }); } catch (requestError) { setError(describeDeviceError(requestError)); return; } setRequestingDevices(true); try { const stream = await request; const nextTracks = stream.getTracks(); tracksRef.current.forEach((track) => track.stop()); tracksRef.current = nextTracks; setTracks(nextTracks); setMicOn(true); setCameraOn(true); } catch (requestError) { setError(describeDeviceError(requestError)); } finally { setRequestingDevices(false); } } function toggleMic() { const track = tracksRef.current.find((candidate) => candidate.kind === "audio"); if (!track) return; const next = !micOn; track.enabled = next; setMicOn(next); } function toggleCamera() { const track = tracksRef.current.find((candidate) => candidate.kind === "video"); if (!track) return; const next = !cameraOn; track.enabled = next; setCameraOn(next); } function testSpeaker() { const AudioContextClass = window.AudioContext || (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; if (!AudioContextClass) return; const context = new AudioContextClass(); const oscillator = context.createOscillator(); const gain = context.createGain(); oscillator.frequency.value = 660; gain.gain.value = 0.08; oscillator.connect(gain).connect(context.destination); oscillator.start(); oscillator.stop(context.currentTime + 0.25); oscillator.addEventListener("ended", () => void context.close()); setSpeakerTested(true); } function continueToCall() { handedOffRef.current = true; onContinue(tracksRef.current); } return (

Vorbereitung

Prüfe deine Geräte

Erlaube Kamera und Mikrofon, teste kurz den Ton und tritt dann dem geschützten Meeting bei.

{videoTrack && cameraOn ? (
{error &&

{error}

}
); }