feat(call): remove live-captions feature (privacy-inconsistent with E2E, unused)

This commit is contained in:
byGalax
2026-05-17 01:39:42 +02:00
parent 7f704e80f6
commit c9a64bf898
7 changed files with 0 additions and 428 deletions
@@ -1,55 +0,0 @@
import { useEffect, useState } from 'react';
import type { ConversationSummary } from '@chat-app/shared/chat';
import { useCall } from '../context/CallContext';
interface Props {
conversation: ConversationSummary;
}
const STALE_AFTER_MS = 5000;
/** Discord-style live-captions overlay. Pinned to the bottom-center of the
* call surface; renders the most recent caption per participant, fading
* entries out after `STALE_AFTER_MS` of silence. Self-captions are shown
* too so the speaker can sanity-check what's being broadcast. */
export function CallCaptionsOverlay({ conversation }: Props) {
const { captions } = useCall();
// Re-render every second so stale entries fade without needing the data
// channel to fire — captions module just stores timestamps.
const [, setNow] = useState(Date.now());
useEffect(() => {
const id = window.setInterval(() => setNow(Date.now()), 1000);
return () => window.clearInterval(id);
}, []);
const now = Date.now();
const visible = Object.entries(captions)
.filter(([, v]) => now - v.timestamp < STALE_AFTER_MS)
.sort(([, a], [, b]) => a.timestamp - b.timestamp);
if (visible.length === 0) return null;
return (
<div className="pointer-events-none absolute inset-x-0 bottom-24 z-30 flex flex-col items-center gap-1.5 px-6">
{visible.map(([identity, c]) => {
const member = conversation.members.find((m) => m.userId === identity);
const name = member?.profile?.displayName ?? '?';
const age = now - c.timestamp;
const opacity = age > 3500 ? 1 - (age - 3500) / (STALE_AFTER_MS - 3500) : 1;
return (
<div
key={identity}
style={{ opacity: Math.max(0, opacity) }}
className="max-w-[760px] rounded-lg bg-black/72 px-3.5 py-1.5 text-sm text-white shadow-md backdrop-blur-md transition-opacity"
>
<span className="mr-2 text-[11px] font-semibold uppercase tracking-wide text-white/55">
{name}
</span>
<span className={c.final ? '' : 'italic text-white/85'}>{c.text}</span>
</div>
);
})}
</div>
);
}
@@ -1,7 +1,6 @@
import { useTranslation } from 'react-i18next';
import {
CaptionsIcon,
HeadphonesIcon,
HeadphonesOffIcon,
MicIcon,
@@ -32,10 +31,6 @@ interface Props {
/** Toggle the in-call soundboard popover. Active = panel currently open. */
onToggleSoundboard?: () => void;
soundboardOpen?: boolean;
/** Discord-style live-captions toggle. Optional — pages that don't support
* SpeechRecognition (Firefox) skip the prop and the button is hidden. */
onToggleCaptions?: () => void;
captionsOn?: boolean;
participantsOpen?: boolean;
/** Compact variant used inside the docked call (36px buttons). */
compact?: boolean;
@@ -59,8 +54,6 @@ export function CallControls({
onOpenParticipants,
onToggleSoundboard,
soundboardOpen = false,
onToggleCaptions,
captionsOn = false,
participantsOpen = false,
compact = false,
glass = false,
@@ -148,22 +141,6 @@ export function CallControls({
<MusicIcon className="h-5 w-5" />
</CallButton>
)}
{onToggleCaptions && (
<CallButton
label={
captionsOn
? t('app:call.captions_off', { defaultValue: 'Untertitel aus' })
: t('app:call.captions_on', { defaultValue: 'Untertitel an' })
}
active={captionsOn}
activeTone="accent"
onClick={onToggleCaptions}
glass={glass}
className={btnSize}
>
<CaptionsIcon className="h-5 w-5" />
</CallButton>
)}
{onOpenParticipants && (
<CallButton
label={t('app:call.participants', { defaultValue: 'Teilnehmer' })}
@@ -15,14 +15,7 @@ import {
listSounds,
subscribeSoundboardChanges,
} from '../lib/soundboardStorage';
import {
getLiveCaptionsSettings,
isLiveCaptionsSupported,
subscribeLiveCaptionsSettings,
updateLiveCaptionsSettings,
} from '../lib/liveCaptions';
import { useActiveSpeakers } from '../lib/useActiveSpeakers';
import { CallCaptionsOverlay } from './CallCaptionsOverlay';
import { CallControls } from './CallControls';
import { CallParticipantTile, type TileConnectionQuality } from './CallParticipantTile';
import { CallStatsOverlay } from './CallStatsOverlay';
@@ -119,17 +112,6 @@ export function InCallPanel({ conversation }: Props) {
const [sharePickerOpen, setSharePickerOpen] = useState(false);
// Discord-style debug stats overlay (Ctrl+Shift+S toggles).
const [statsOverlayOpen, setStatsOverlayOpen] = useState(false);
// Live-captions toggle — mirror of getLiveCaptionsSettings().enabled so the
// controls bar can show an "active" state without polling. Captions
// broadcasting is wired in CallContext via useLiveCaptions; this only
// tracks the toggle state for the button.
const [captionsEnabled, setCaptionsEnabled] = useState<boolean>(
() => getLiveCaptionsSettings().enabled,
);
useEffect(
() => subscribeLiveCaptionsSettings((s) => setCaptionsEnabled(s.enabled)),
[],
);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
// Match the modifier exactly to avoid clobbering other Ctrl+Shift combos.
@@ -362,15 +344,6 @@ export function InCallPanel({ conversation }: Props) {
soundboardOpen,
}
: {})}
// Live-Captions only when SpeechRecognition is available in the
// runtime — Firefox lacks it, would just show a dead button.
{...(isLiveCaptionsSupported()
? {
onToggleCaptions: () =>
updateLiveCaptionsSettings({ enabled: !captionsEnabled }),
captionsOn: captionsEnabled,
}
: {})}
onHangup={() => void hangup()}
compact={callMode !== 'fullscreen'}
glass={callMode === 'fullscreen'}
@@ -499,7 +472,6 @@ export function InCallPanel({ conversation }: Props) {
onClose={() => setStatsOverlayOpen(false)}
/>
)}
{captionsEnabled && <CallCaptionsOverlay conversation={conversation} />}
</>
);
}
@@ -644,8 +616,6 @@ export function InCallPanel({ conversation }: Props) {
onClose={() => setStatsOverlayOpen(false)}
/>
)}
{captionsEnabled && <CallCaptionsOverlay conversation={conversation} />}
</section>
);
}
-11
View File
@@ -160,17 +160,6 @@ function EyeIconInner(props: IconProps) {
}
export const EyeIcon = memo(EyeIconInner);
function CaptionsIconInner(props: IconProps) {
return (
<Base {...props}>
<rect x="3" y="6" width="18" height="12" rx="2" />
<path d="M7 13a2 2 0 1 1 0-2" />
<path d="M14 13a2 2 0 1 1 0-2" />
</Base>
);
}
export const CaptionsIcon = memo(CaptionsIconInner);
function PinOffIconInner(props: IconProps) {
return (
<Base {...props}>