"use client"; import { useEffect, useRef, useState } from "react"; import { createLocalTracks, Track, type LocalTrack } from "livekit-client"; 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: LocalTrack[]) => 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 handedOffRef = useRef(false); const videoTrack = tracks.find((track) => track.kind === Track.Kind.Video); const audioTrack = tracks.find((track) => track.kind === Track.Kind.Audio); useEffect(() => { const videoElement = videoRef.current; if (!videoElement || !videoTrack) return; videoTrack.attach(videoElement); return () => { videoTrack.detach(videoElement); }; }, [videoTrack]); useEffect(() => { return () => { if (!handedOffRef.current) tracks.forEach((track) => track.stop()); }; }, [tracks]); async function requestDevices() { if (requestingDevices) return; setError(null); setRequestingDevices(true); try { // This must originate from the button click. Some privacy-focused // browsers deliberately ignore a getUserMedia request from useEffect. const nextTracks = await createLocalTracks({ audio: true, video: true }); tracks.forEach((track) => track.stop()); setTracks(nextTracks); setMicOn(true); setCameraOn(true); } catch (requestError) { setError(describeDeviceError(requestError)); } finally { setRequestingDevices(false); } } async function toggleMic() { if (!audioTrack) return; if (micOn) await audioTrack.mute(); else await audioTrack.unmute(); setMicOn(!micOn); } async function toggleCamera() { if (!videoTrack) return; if (cameraOn) await videoTrack.mute(); else await videoTrack.unmute(); setCameraOn(!cameraOn); } 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(tracks); } 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}

}
); }