Fix: Stream-Link Auto-Join Race Condition + Autoplay
- remoteStreamRef speichert MediaStream aus ontrack, damit er nicht verloren geht wenn das <video>-Element noch nicht gemountet ist - useEffect verbindet Stream mit Video sobald beides bereit ist - Explizites play() mit Mute-Fallback bei Autoplay-Blockierung (neuer Tab ohne User-Interaktion, z.B. Discord-Link) - Debug-Logging aus Notification-Config entfernt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bc6ec39dfd
commit
1c674191c9
2 changed files with 33 additions and 4 deletions
|
|
@ -80,6 +80,7 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
const remoteVideoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const peerConnectionsRef = useRef<Map<string, RTCPeerConnection>>(new Map());
|
||||
const viewerPcRef = useRef<RTCPeerConnection | null>(null);
|
||||
const remoteStreamRef = useRef<MediaStream | null>(null);
|
||||
const pendingCandidatesRef = useRef<Map<string, RTCIceCandidateInit[]>>(new Map());
|
||||
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const reconnectDelayRef = useRef(1000);
|
||||
|
|
@ -161,12 +162,27 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
// ── Attach remote stream to video element (handles autoplay) ──
|
||||
const attachRemoteStream = useCallback((videoEl: HTMLVideoElement, stream: MediaStream) => {
|
||||
videoEl.srcObject = stream;
|
||||
// Explicit play() to handle autoplay restrictions (e.g. fresh tab from Discord link)
|
||||
const playPromise = videoEl.play();
|
||||
if (playPromise) {
|
||||
playPromise.catch(() => {
|
||||
// Autoplay blocked (no user interaction yet) → mute and retry
|
||||
videoEl.muted = true;
|
||||
videoEl.play().catch(() => {});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ── Viewer cleanup (only viewer PC, keeps broadcaster intact) ──
|
||||
const cleanupViewer = useCallback(() => {
|
||||
if (viewerPcRef.current) {
|
||||
viewerPcRef.current.close();
|
||||
viewerPcRef.current = null;
|
||||
}
|
||||
remoteStreamRef.current = null;
|
||||
if (remoteVideoRef.current) remoteVideoRef.current.srcObject = null;
|
||||
// Only clear viewer-related pending candidates (not broadcaster ones)
|
||||
}, []);
|
||||
|
|
@ -275,7 +291,15 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
viewerPcRef.current = pc;
|
||||
|
||||
pc.ontrack = (ev) => {
|
||||
if (remoteVideoRef.current && ev.streams[0]) remoteVideoRef.current.srcObject = ev.streams[0];
|
||||
const stream = ev.streams[0];
|
||||
if (!stream) return;
|
||||
// Store stream in ref so it survives even if video element isn't mounted yet
|
||||
remoteStreamRef.current = stream;
|
||||
const videoEl = remoteVideoRef.current;
|
||||
if (videoEl) {
|
||||
attachRemoteStream(videoEl, stream);
|
||||
}
|
||||
// else: useEffect below will attach once video element is ready
|
||||
setViewing(prev => prev ? { ...prev, phase: 'connected' } : prev);
|
||||
};
|
||||
|
||||
|
|
@ -512,6 +536,14 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// ── Attach remote stream when video element becomes available ──
|
||||
// Handles the race condition where ontrack fires before React renders the <video>
|
||||
useEffect(() => {
|
||||
if (viewing && remoteStreamRef.current && remoteVideoRef.current && !remoteVideoRef.current.srcObject) {
|
||||
attachRemoteStream(remoteVideoRef.current, remoteStreamRef.current);
|
||||
}
|
||||
}, [viewing, attachRemoteStream]);
|
||||
|
||||
// ── Auto-join from URL ?viewStream=... ──
|
||||
const pendingViewStreamRef = useRef<string | null>(null);
|
||||
|
||||
|
|
@ -613,7 +645,6 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
}, [isAdmin, loadNotifyConfig]);
|
||||
|
||||
const toggleChannelEvent = useCallback((channelId: string, channelName: string, guildId: string, guildName: string, event: string) => {
|
||||
console.log('[Notifications] Toggle:', channelId, channelName, event);
|
||||
setNotifyConfig(prev => {
|
||||
const existing = prev.find(c => c.channelId === channelId);
|
||||
if (existing) {
|
||||
|
|
@ -632,7 +663,6 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
}, []);
|
||||
|
||||
const saveNotifyConfig = useCallback(async () => {
|
||||
console.log('[Notifications] Saving config, notifyConfig:', JSON.stringify(notifyConfig));
|
||||
setConfigSaving(true);
|
||||
try {
|
||||
const resp = await fetch('/api/notifications/config', {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue