// Top-level renderer for incoming DM call strips + ringtone audio. // // Mounted in Router.tsx inside `CallEmbedProvider`, rendered right before // `CallStatusRenderer` so the strip stacks above the in-call pill. // // KNOWN GAP §5.17: if the browser blocks `audio.play()` (cold page load, no // user gesture yet), the ring is silent — strip is still visible but user may // miss it. Fallback (click-to-enable, pulsing animation, Web Notifications) is // Phase 3 polish. import React, { useEffect, useRef } from 'react'; import { useAtomValue } from 'jotai'; import { Box } from 'folds'; import { incomingCallsAtom } from '../state/incomingCalls'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { IncomingCallStrip } from '../features/call-status'; // eslint-disable-next-line import/no-relative-packages import RingSoundOgg from '../../../public/sound/ring.ogg'; // eslint-disable-next-line import/no-relative-packages import RingSoundMp3 from '../../../public/sound/ring.mp3'; export function IncomingCallStripRenderer() { const mx = useMatrixClient(); const incoming = useAtomValue(incomingCallsAtom); const audioRef = useRef(null); const hasIncoming = incoming.size > 0; useEffect(() => { const audio = audioRef.current; if (!audio) return; if (hasIncoming) { audio.currentTime = 0; audio.play().catch(() => { // autoplay blocked — strip UI still visible }); } else { audio.pause(); audio.currentTime = 0; } }, [hasIncoming]); const entries = Array.from(incoming.values()); return ( <> {/* eslint-disable-next-line jsx-a11y/media-has-caption */} {hasIncoming && ( {entries.map((call) => { const room = mx.getRoom(call.roomId); if (!room) return null; return ( ); })} )} ); }