mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
Respect device speed bounds in clip controls
This commit is contained in:
@@ -91,7 +91,7 @@ import {
|
||||
} from "./types";
|
||||
import { fromCursorSwaySliderValue, toCursorSwaySliderValue } from "./videoPlayback/cursorSway";
|
||||
import { isZeroPadding } from "./videoPlayback/layoutUtils";
|
||||
import { supportsPreviewPlaybackRate } from "./videoPlayback/playbackRate";
|
||||
import { getPreviewPlaybackRateRange } from "./videoPlayback/playbackRate";
|
||||
import {
|
||||
cursorSetAssets,
|
||||
getCursorStyleSizeMultiplier,
|
||||
@@ -1089,12 +1089,7 @@ export function SettingsPanel({
|
||||
const { preference: themePreference, setPreference: setThemePreference } = useTheme();
|
||||
const isBackgroundPanel = panelMode === "background";
|
||||
const initialEditorPreferences = useMemo(() => loadEditorPreferences(), []);
|
||||
const maxClipSpeed = useMemo(() => {
|
||||
for (let speed = 30; speed >= 1; speed -= 0.25) {
|
||||
if (supportsPreviewPlaybackRate(speed)) return speed;
|
||||
}
|
||||
return 1;
|
||||
}, []);
|
||||
const clipSpeedRange = useMemo(getPreviewPlaybackRateRange, []);
|
||||
const [builtInWallpapers, setBuiltInWallpapers] =
|
||||
useState<BuiltInWallpaper[]>(BUILT_IN_WALLPAPERS);
|
||||
const [wallpaperPreviewPaths, setWallpaperPreviewPaths] = useState<string[]>([]);
|
||||
@@ -2441,6 +2436,12 @@ export function SettingsPanel({
|
||||
className="data-[state=checked]:bg-[#2563EB] scale-75"
|
||||
/>
|
||||
</div>
|
||||
{selectedClipSpeed != null &&
|
||||
(selectedClipSpeed < clipSpeedRange.min || selectedClipSpeed > clipSpeedRange.max) && (
|
||||
<p className="text-[11px] text-muted-foreground" role="status">
|
||||
{selectedClipSpeed}× — {tSettings("speed.unsupported", "Not supported for preview on this device")}
|
||||
</p>
|
||||
)}
|
||||
<label className="flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-2">
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{tSettings("captions.textColor", "Text color")}
|
||||
@@ -3011,22 +3012,26 @@ export function SettingsPanel({
|
||||
</section>
|
||||
);
|
||||
|
||||
|
||||
|
||||
const clipSectionContent = (
|
||||
<section className="flex flex-col gap-3">
|
||||
<SectionLabel>{tSettings("clip.title", "Clip")}</SectionLabel>
|
||||
<SliderControl
|
||||
label={tSettings("speed.label", "Speed")}
|
||||
value={selectedClipSpeed ?? 1}
|
||||
value={Math.min(clipSpeedRange.max, Math.max(clipSpeedRange.min, selectedClipSpeed ?? 1))}
|
||||
defaultValue={1}
|
||||
min={0.25}
|
||||
max={maxClipSpeed}
|
||||
min={clipSpeedRange.min}
|
||||
max={clipSpeedRange.max}
|
||||
step={0.25}
|
||||
onChange={(value) => onClipSpeedChange?.(value)}
|
||||
formatValue={(value) => `${value}×`}
|
||||
parseInput={(text) => Number.parseFloat(text)}
|
||||
/>
|
||||
{selectedClipSpeed != null &&
|
||||
(selectedClipSpeed < clipSpeedRange.min || selectedClipSpeed > clipSpeedRange.max) && (
|
||||
<p className="text-[11px] text-muted-foreground" role="status">
|
||||
{selectedClipSpeed}× — {tSettings("speed.unsupported", "Not supported for preview on this device")}
|
||||
</p>
|
||||
)}
|
||||
<label className="flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-2">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{tSettings("clip.mute", "Mute clip")}
|
||||
|
||||
@@ -20,3 +20,17 @@ it("probes and caches the runtime's accepted rates", async () => {
|
||||
for (const speed of [0, -1, NaN, Infinity])
|
||||
expect(supportsPreviewPlaybackRate(speed)).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ lower: 0.25, upper: 16 },
|
||||
{ lower: 0.5, upper: 4 },
|
||||
{ lower: 1, upper: 30 },
|
||||
])("exposes only supported steps between $lower and $upper", async ({ lower, upper }) => {
|
||||
vi.stubGlobal("document", { createElement: () => ({
|
||||
set playbackRate(rate: number) {
|
||||
if (rate < lower || rate > upper) throw new DOMException("Unsupported", "NotSupportedError");
|
||||
},
|
||||
}) });
|
||||
const { getPreviewPlaybackRateRange } = await import("./playbackRate");
|
||||
expect(getPreviewPlaybackRateRange()).toEqual({ min: lower, max: upper });
|
||||
});
|
||||
|
||||
@@ -16,3 +16,12 @@ export function supportsPreviewPlaybackRate(rate: number): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Contiguous quarter-step range supported by the runtime, anchored at normal speed. */
|
||||
export function getPreviewPlaybackRateRange(): { min: number; max: number } {
|
||||
let min = 1;
|
||||
let max = 1;
|
||||
while (min > 0.25 && supportsPreviewPlaybackRate(min - 0.25)) min -= 0.25;
|
||||
while (max < 30 && supportsPreviewPlaybackRate(max + 0.25)) max += 0.25;
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"clip": {
|
||||
"title": "Clip",
|
||||
"mute": "Disattiva audio clip",
|
||||
"mute": "Disattiva l'audio del clip",
|
||||
"mutedState": "Audio disattivato",
|
||||
"unmutedState": "Audio in riproduzione",
|
||||
"separateClipFromAudio": "Separa clip dall'audio",
|
||||
|
||||
Reference in New Issue
Block a user