From ed08e375ab52a5f6d3c8a4f6027f99b2f04299b2 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:42:36 +0000 Subject: [PATCH 1/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/webrtc/broadcaster.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/webrtc/broadcaster.ts b/src/lib/webrtc/broadcaster.ts index fa4b7f6..4a0fb54 100644 --- a/src/lib/webrtc/broadcaster.ts +++ b/src/lib/webrtc/broadcaster.ts @@ -12,6 +12,8 @@ export interface BroadcasterHandle { code: string; stop: () => Promise; onViewerCountChange: (cb: (n: number) => void) => () => void; + /** Swap the outgoing video track on every active peer (e.g. when the XR mirror canvas becomes available). */ + replaceVideoTrack: (newStream: MediaStream) => Promise; } const MAX_VIEWERS = 5; From 5ebab186d85e520b91baf4e380e2c426b7c7e7ce Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:42:51 +0000 Subject: [PATCH 2/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/webrtc/broadcaster.ts | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/lib/webrtc/broadcaster.ts b/src/lib/webrtc/broadcaster.ts index 4a0fb54..d4aa4a3 100644 --- a/src/lib/webrtc/broadcaster.ts +++ b/src/lib/webrtc/broadcaster.ts @@ -97,10 +97,18 @@ export async function startBroadcast(stream: MediaStream): Promise { - t.addEventListener('ended', () => stop()); - }); + const attachEndedListeners = (s: MediaStream) => { + s.getTracks().forEach((t) => { + t.addEventListener('ended', () => { + if (s === activeStream) stop(); + }); + }); + }; + attachEndedListeners(stream); let stopped = false; const stop = async () => { @@ -113,9 +121,27 @@ export async function startBroadcast(stream: MediaStream): Promise { + const newVideo = newStream.getVideoTracks()[0]; + if (!newVideo) return; + activeStream = newStream; + attachEndedListeners(newStream); + for (const pc of peers.values()) { + const sender = pc.getSenders().find((s) => s.track && s.track.kind === 'video'); + if (sender) { + try { + await sender.replaceTrack(newVideo); + } catch (e) { + console.warn('[broadcaster] replaceTrack failed', e); + } + } + } + }; + return { code, stop, + replaceVideoTrack, onViewerCountChange: (cb) => { listeners.add(cb); cb(peers.size); From 5c2c0f60067129177fd8342f56204c0a2aa8f063 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:43:04 +0000 Subject: [PATCH 3/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/webrtc/broadcaster.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/webrtc/broadcaster.ts b/src/lib/webrtc/broadcaster.ts index d4aa4a3..d337bc8 100644 --- a/src/lib/webrtc/broadcaster.ts +++ b/src/lib/webrtc/broadcaster.ts @@ -34,6 +34,7 @@ export async function startBroadcast(stream: MediaStream): Promise listeners.forEach((cb) => cb(peers.size)); let channel: RealtimeChannel | null = null; + let activeStream: MediaStream = stream; const handleMessage = async (msg: SignalMessage) => { if (msg.type === 'viewer-join') { @@ -41,7 +42,7 @@ export async function startBroadcast(stream: MediaStream): Promise pc.addTrack(t, stream)); + activeStream.getTracks().forEach((t) => pc.addTrack(t, activeStream)); pc.onicecandidate = (ev) => { if (ev.candidate && channel) { From e42f7ad1dbb9f7a3c9a5832810da18530a1c6851 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:43:11 +0000 Subject: [PATCH 4/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/webrtc/broadcaster.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/webrtc/broadcaster.ts b/src/lib/webrtc/broadcaster.ts index d337bc8..10d5203 100644 --- a/src/lib/webrtc/broadcaster.ts +++ b/src/lib/webrtc/broadcaster.ts @@ -98,9 +98,6 @@ export async function startBroadcast(stream: MediaStream): Promise { s.getTracks().forEach((t) => { From 8dbcfc6c43a892b0a65c9e3c1d7c0ac5c75db971 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:43:17 +0000 Subject: [PATCH 5/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/ShareButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ShareButton.tsx b/src/components/ShareButton.tsx index 5a5d648..70c8f8e 100644 --- a/src/components/ShareButton.tsx +++ b/src/components/ShareButton.tsx @@ -4,7 +4,7 @@ import { QRCodeSVG } from 'qrcode.react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { startBroadcast, type BroadcasterHandle } from '@/lib/webrtc/broadcaster'; -import { getBroadcastSource } from '@/lib/webrtc/broadcastSource'; +import { getBroadcastSource, onBroadcastSourceChange } from '@/lib/webrtc/broadcastSource'; import { toast } from 'sonner'; interface ShareButtonProps { From 64b62701884c2f999dfe95a0d9219a574afb6910 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:43:31 +0000 Subject: [PATCH 6/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/ShareButton.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/ShareButton.tsx b/src/components/ShareButton.tsx index 70c8f8e..d0d7a66 100644 --- a/src/components/ShareButton.tsx +++ b/src/components/ShareButton.tsx @@ -91,6 +91,24 @@ export function ShareButton({ variant = 'default', autoOpen, onHandleChange, onV // eslint-disable-next-line react-hooks/exhaustive-deps }, [handle]); + // When the XR mirror canvas appears/changes (entering or exiting AR), swap the + // outgoing video track so viewers see the opaque mirror instead of the transparent main canvas. + useEffect(() => { + if (!handle) return; + const swapTo = (canvas: HTMLCanvasElement | null) => { + if (!canvas || typeof canvas.captureStream !== 'function') return; + const newStream = canvas.captureStream(24); + const oldStream = streamRef.current; + streamRef.current = newStream; + handle.replaceVideoTrack(newStream).catch((e) => console.warn('replaceVideoTrack', e)); + if (oldStream) oldStream.getTracks().forEach((t) => t.stop()); + }; + // Apply current value immediately if mirror is already up + const current = getBroadcastSource(); + if (current) swapTo(current); + return onBroadcastSourceChange(swapTo); + }, [handle]); + // Cleanup on unmount useEffect(() => { return () => { From ce3ae14c840dc805acd74d048e1214898acad77d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:43:37 +0000 Subject: [PATCH 7/7] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/webrtc/broadcastSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/webrtc/broadcastSource.ts b/src/lib/webrtc/broadcastSource.ts index 163b2cd..5a15131 100644 --- a/src/lib/webrtc/broadcastSource.ts +++ b/src/lib/webrtc/broadcastSource.ts @@ -16,5 +16,5 @@ export function getBroadcastSource(): HTMLCanvasElement | null { export function onBroadcastSourceChange(cb: (c: HTMLCanvasElement | null) => void) { listeners.add(cb); - return () => listeners.delete(cb); + return () => { listeners.delete(cb); }; }