Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-14 19:04:44 +00:00
parent 10a0c83584
commit 63852195fc
+14 -10
View File
@@ -1,6 +1,7 @@
import { import {
joinSignalingChannel, joinSignalingChannel,
sendSignal, sendSignal,
waitForSignalingReady,
ICE_SERVERS, ICE_SERVERS,
type SignalMessage, type SignalMessage,
} from './signaling'; } from './signaling';
@@ -17,6 +18,7 @@ export async function joinAsViewer(
code: string, code: string,
videoEl: HTMLVideoElement, videoEl: HTMLVideoElement,
onState: (s: 'connecting' | 'waiting' | 'live' | 'error', detail?: string) => void, onState: (s: 'connecting' | 'waiting' | 'live' | 'error', detail?: string) => void,
onFallbackFrame?: (dataUrl: string) => void,
): Promise<ViewerHandle> { ): Promise<ViewerHandle> {
const viewerId = crypto.randomUUID(); const viewerId = crypto.randomUUID();
let pc: RTCPeerConnection | null = null; let pc: RTCPeerConnection | null = null;
@@ -25,18 +27,24 @@ export async function joinAsViewer(
onState('connecting'); onState('connecting');
const handleMessage = async (msg: SignalMessage) => { const handleMessage = async (msg: SignalMessage) => {
if (msg.type === 'frame') {
onFallbackFrame?.(msg.dataUrl);
onState('live');
return;
}
if (msg.type === 'offer' && msg.viewerId === viewerId) { if (msg.type === 'offer' && msg.viewerId === viewerId) {
pc = new RTCPeerConnection({ iceServers: ICE_SERVERS }); pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
pc.ontrack = (ev) => { pc.ontrack = (ev) => {
videoEl.srcObject = ev.streams[0]; videoEl.srcObject = ev.streams[0] ?? new MediaStream([ev.track]);
videoEl.play().catch(() => {/* autoplay may need user gesture */}); videoEl.play().catch(() => {/* autoplay may need user gesture */});
onState('live'); onState('live');
}; };
pc.onicecandidate = (ev) => { pc.onicecandidate = (ev) => {
if (ev.candidate && channel) { if (ev.candidate && channel) {
sendSignal(channel, { void sendSignal(channel, {
type: 'ice', type: 'ice',
viewerId, viewerId,
from: 'viewer', from: 'viewer',
@@ -54,7 +62,7 @@ export async function joinAsViewer(
await pc.setRemoteDescription(msg.sdp); await pc.setRemoteDescription(msg.sdp);
const answer = await pc.createAnswer(); const answer = await pc.createAnswer();
await pc.setLocalDescription(answer); await pc.setLocalDescription(answer);
if (channel) sendSignal(channel, { type: 'answer', viewerId, sdp: answer }); if (channel) await sendSignal(channel, { type: 'answer', viewerId, sdp: answer });
} else if (msg.type === 'ice' && msg.viewerId === viewerId && msg.from === 'broadcaster') { } else if (msg.type === 'ice' && msg.viewerId === viewerId && msg.from === 'broadcaster') {
if (pc) { if (pc) {
try { try {
@@ -67,18 +75,14 @@ export async function joinAsViewer(
}; };
channel = joinSignalingChannel(code, handleMessage); channel = joinSignalingChannel(code, handleMessage);
await waitForSignalingReady(channel);
// Wait a tick for subscribe, then announce join await sendSignal(channel, { type: 'viewer-join', viewerId });
setTimeout(() => {
if (channel) {
sendSignal(channel, { type: 'viewer-join', viewerId });
onState('waiting'); onState('waiting');
}
}, 600);
const stop = async () => { const stop = async () => {
if (channel) { if (channel) {
try { sendSignal(channel, { type: 'viewer-leave', viewerId }); } catch {} try { await sendSignal(channel, { type: 'viewer-leave', viewerId }); } catch {}
const { supabase } = await import('@/integrations/supabase/client'); const { supabase } = await import('@/integrations/supabase/client');
await supabase.removeChannel(channel); await supabase.removeChannel(channel);
} }