diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-27 01:21:20 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-27 01:21:20 +0200 |
| commit | 783e9ac8462751adc14d78b070095fd63e45bda0 (patch) | |
| tree | 9e28a0937b9155c6b791af836c973f39ced9d541 /apps/web/src/timeline/components | |
| parent | 30a36e45686db503fded68e4da5d4c465a06f978 (diff) | |
| download | opencut-783e9ac8462751adc14d78b070095fd63e45bda0.tar.gz | |
feat: voice-over recording into the timeline
MediaRecorder mic capture (defensive mimeType picking, permission/no-mic
toasts) with elapsed display in the timeline toolbar. Stop produces an
audio asset and inserts it at the playhead on an audio track as ONE
BatchCommand undo step, freeze-frame precedent. voice-over action
registered, no default keybinding. 20 tests; smoke-tested in Chromium
with a fake mic end to end.
Diffstat (limited to 'apps/web/src/timeline/components')
| -rw-r--r-- | apps/web/src/timeline/components/timeline-toolbar.tsx | 26 |
1 files changed, 26 insertions, 0 deletions
diff --git a/apps/web/src/timeline/components/timeline-toolbar.tsx b/apps/web/src/timeline/components/timeline-toolbar.tsx index 6208ef27..47906931 100644 --- a/apps/web/src/timeline/components/timeline-toolbar.tsx +++ b/apps/web/src/timeline/components/timeline-toolbar.tsx @@ -43,12 +43,17 @@ import { Layers01Icon, Chart03Icon, Unlink02Icon, + MicIcon, + StopIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { OcRippleIcon } from "@/components/icons"; import { GraphEditorPopover } from "./graph-editor/popover"; import { PopoverTrigger } from "@/components/ui/popover"; import { useGraphEditorController } from "./graph-editor/use-controller"; +import { formatElapsedSeconds } from "@/voiceover/recorder"; +import { useRecordingElapsedSeconds } from "@/voiceover/use-recording-elapsed"; +import { useVoiceOverStore } from "@/voiceover/voiceover-store"; export function TimelineToolbar({ zoomLevel, @@ -95,6 +100,10 @@ function ToolbarLeftSection() { const isCurrentlyBookmarked = useEditor((e) => e.scenes.isBookmarked({ time: e.playback.getCurrentTime() }), ); + const isRecordingVoiceOver = useVoiceOverStore( + (state) => state.status === "recording", + ); + const voiceOverElapsedSeconds = useRecordingElapsedSeconds(); const canFreezeFrame = useEditor((e) => { const time = e.playback.getCurrentTime(); const { overlay, main } = e.scenes.getActiveScene().tracks; @@ -203,6 +212,23 @@ function ToolbarLeftSection() { /> <ToolbarButton + icon={ + <HugeiconsIcon icon={isRecordingVoiceOver ? StopIcon : MicIcon} /> + } + tooltip={ + isRecordingVoiceOver ? "Stop voice-over" : "Record voice-over" + } + isActive={isRecordingVoiceOver} + onClick={({ event }) => handleAction({ action: "voice-over", event })} + /> + + {isRecordingVoiceOver && ( + <span className="text-destructive min-w-9 text-center text-xs tabular-nums"> + {formatElapsedSeconds({ seconds: voiceOverElapsedSeconds })} + </span> + )} + + <ToolbarButton icon={<HugeiconsIcon icon={Delete02Icon} />} tooltip="Delete element" onClick={({ event }) => |
