vojo/src/app/pages/IncomingCallStripRenderer.tsx

69 lines
2.3 KiB
TypeScript

// 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<HTMLAudioElement>(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 */}
<audio ref={audioRef} loop preload="auto" style={{ display: 'none' }}>
<source src={RingSoundOgg} type="audio/ogg" />
<source src={RingSoundMp3} type="audio/mpeg" />
</audio>
{hasIncoming && (
<Box direction="Column" shrink="No">
{entries.map((call) => {
const room = mx.getRoom(call.roomId);
if (!room) return null;
return (
<IncomingCallStrip
key={`call_${call.callId}_${call.roomId}`}
call={call}
room={room}
/>
);
})}
</Box>
)}
</>
);
}