From a7ffcbff8369e2587edc927bdb8dcaca86e7c000 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sun, 17 May 2026 17:09:01 +0200 Subject: [PATCH] feat(voice): playback-speed toggle (1x/1.5x/2x) with per-user default --- .../src/components/AttachmentAudio.tsx | 31 +++++++++++++++++++ apps/desktop/src/lib/voiceSpeedSettings.ts | 29 +++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 apps/desktop/src/lib/voiceSpeedSettings.ts diff --git a/apps/desktop/src/components/AttachmentAudio.tsx b/apps/desktop/src/components/AttachmentAudio.tsx index cd6c099..4ee7a65 100644 --- a/apps/desktop/src/components/AttachmentAudio.tsx +++ b/apps/desktop/src/components/AttachmentAudio.tsx @@ -2,6 +2,7 @@ import { type AttachmentHandle, downloadAndDecryptAttachment } from '@chat-app/s import { useEffect, useMemo, useRef, useState } from 'react'; import { getCachedAttachment, putCachedAttachment } from '../lib/attachmentCache'; +import { getVoiceSpeed, setVoiceSpeed, VOICE_SPEEDS, type VoiceSpeed } from '../lib/voiceSpeedSettings'; import { supabase } from '../lib/supabase'; import { AlertIcon, MicIcon, SpinnerIcon } from './icons'; @@ -23,6 +24,7 @@ export function AttachmentAudio({ handle }: Props) { const [duration, setDuration] = useState(0); const [position, setPosition] = useState(0); const [playing, setPlaying] = useState(false); + const [speed, setSpeed] = useState(() => getVoiceSpeed()); const audioRef = useRef(null); useEffect(() => { @@ -105,6 +107,12 @@ export function AttachmentAudio({ handle }: Props) { }; }, [arrayBuf]); + useEffect(() => { + const el = audioRef.current; + if (!el) return; + el.playbackRate = speed; + }, [speed, blobUrl]); + const fallbackPeaks = useMemo( () => (peaks ? null : Array.from({ length: BAR_COUNT }, () => 0.5)), [peaks], @@ -154,6 +162,29 @@ export function AttachmentAudio({ handle }: Props) { )} +
+ {VOICE_SPEEDS.map((s) => { + const active = s === speed; + return ( + + ); + })} +