From 6aad00bff13645e98674a863e83a0ec6670ab62b Mon Sep 17 00:00:00 2001 From: my_mac <1317811579@qq.com> Date: Tue, 7 Jul 2026 22:26:29 -0400 Subject: [PATCH 1/4] feat: raise playback-speed cap to 100x Chromium hard-caps HTMLMediaElement.playbackRate at 16, so preview and the real-time audio-export capture cannot exceed 16x. This takes both paths off the media element only above 16x, leaving the <=16x behaviour untouched: - Preview: >16x segments frame-step (a virtual clock forward-seeks the muted element each frame); <=16x plays natively exactly as before. - Audio export: a timeline with any >16x segment renders offline with a streaming WSOLA pitch-preserving time-stretch; <=16x keeps the proven real-time capture path. - Video export already retimed frames offline with no ceiling - unchanged. - Timeline-segment math is extracted to timelineSegments.ts so the video decoder and the offline audio path derive identical segments (A/V sync). - UI: the custom-speed input now accepts up to 100x (presets unchanged), with a hint that >16x previews as muted frame-stepping while export is unaffected. Closes #79 Co-Authored-By: Claude Opus 4.8 --- src/components/video-editor/SettingsPanel.tsx | 13 +- src/components/video-editor/VideoPlayback.tsx | 33 +- .../video-editor/customPlaybackSpeed.test.ts | 21 +- src/components/video-editor/types.ts | 7 +- .../videoPlayback/videoEventHandlers.ts | 162 ++++++++- src/i18n/locales/ar/settings.json | 3 +- src/i18n/locales/en/settings.json | 3 +- src/i18n/locales/es/settings.json | 3 +- src/i18n/locales/fr/settings.json | 3 +- src/i18n/locales/it/settings.json | 3 +- src/i18n/locales/ja-JP/settings.json | 3 +- src/i18n/locales/ko-KR/settings.json | 3 +- src/i18n/locales/pt-BR/settings.json | 3 +- src/i18n/locales/ru/settings.json | 3 +- src/i18n/locales/tr/settings.json | 3 +- src/i18n/locales/vi/settings.json | 3 +- src/i18n/locales/zh-CN/settings.json | 3 +- src/i18n/locales/zh-TW/settings.json | 3 +- src/lib/exporter/audioEncoder.ts | 317 +++++++++++++++++- src/lib/exporter/audioTimeStretch.test.ts | 144 ++++++++ src/lib/exporter/audioTimeStretch.ts | 306 +++++++++++++++++ src/lib/exporter/streamingDecoder.ts | 77 +---- src/lib/exporter/timelineSegments.test.ts | 113 +++++++ src/lib/exporter/timelineSegments.ts | 102 ++++++ src/lib/exporter/videoExporter.test.ts | 10 + src/lib/exporter/videoExporter.ts | 1 + 26 files changed, 1245 insertions(+), 100 deletions(-) create mode 100644 src/lib/exporter/audioTimeStretch.test.ts create mode 100644 src/lib/exporter/audioTimeStretch.ts create mode 100644 src/lib/exporter/timelineSegments.test.ts create mode 100644 src/lib/exporter/timelineSegments.ts diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 5e0cc8062..282a295fd 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -89,6 +89,8 @@ import type { import { DEFAULT_WEBCAM_MIRRORED, DEFAULT_WEBCAM_REACTIVE_ZOOM, + MAX_NATIVE_PLAYBACK_RATE, + MAX_PLAYBACK_SPEED, MAX_ZOOM_SCALE, MIN_ZOOM_SCALE, ROTATION_3D_PRESET_ORDER, @@ -1214,7 +1216,9 @@ export function SettingsPanel({ onSpeedChange?.(val)} - onError={() => toast.error(t("speed.maxSpeedError"))} + onError={() => + toast.error(t("speed.maxSpeedError", { max: MAX_PLAYBACK_SPEED })) + } /> ) : (
@@ -1225,6 +1229,13 @@ export function SettingsPanel({
)} + {selectedSpeedId && + selectedSpeedValue != null && + selectedSpeedValue > MAX_NATIVE_PLAYBACK_RATE && ( +

+ {t("speed.previewFrameSteppingHint", { native: MAX_NATIVE_PLAYBACK_RATE })} +

+ )} {selectedSpeedId && (