mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Guard playback speed initialization
Signed-off-by: John Tennant <jtennant@squareup.com>
This commit is contained in:
committed by
John Tennant
parent
eec0a41312
commit
3111425ea8
@@ -1,7 +1,10 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getTtsPlaybackSpeed, setTtsPlaybackSpeed } from "@/shared/api/tauri";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { commitPlaybackSpeed } from "./playbackSpeedPersistence";
|
||||
import {
|
||||
applyLoadedPlaybackSpeed,
|
||||
commitPlaybackSpeed,
|
||||
} from "./playbackSpeedPersistence";
|
||||
import { SettingsOptionGroup, SettingsOptionRow } from "./SettingsOptionGroup";
|
||||
|
||||
const DEFAULT_SPEED = 1;
|
||||
@@ -12,22 +15,34 @@ const SPEED_STEP = 0.05;
|
||||
export function SpeechPlaybackSettings() {
|
||||
const [speed, setSpeed] = useState(DEFAULT_SPEED);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const confirmedSpeed = useRef(DEFAULT_SPEED);
|
||||
const desiredSpeed = useRef(DEFAULT_SPEED);
|
||||
const flushPromise = useRef<Promise<void> | null>(null);
|
||||
const hasLocalIntent = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
getTtsPlaybackSpeed()
|
||||
.then((savedSpeed) => {
|
||||
if (active) {
|
||||
confirmedSpeed.current = savedSpeed;
|
||||
desiredSpeed.current = savedSpeed;
|
||||
setSpeed(savedSpeed);
|
||||
applyLoadedPlaybackSpeed(
|
||||
{
|
||||
confirmedSpeed,
|
||||
desiredSpeed,
|
||||
flushPromise,
|
||||
hasLocalIntent,
|
||||
},
|
||||
{ setSpeed },
|
||||
savedSpeed,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((cause) => {
|
||||
if (active) setError(String(cause));
|
||||
})
|
||||
.finally(() => {
|
||||
if (active) setLoaded(true);
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
@@ -36,7 +51,7 @@ export function SpeechPlaybackSettings() {
|
||||
|
||||
const commitSpeed = (nextSpeed: number) => {
|
||||
commitPlaybackSpeed(
|
||||
{ confirmedSpeed, desiredSpeed, flushPromise },
|
||||
{ confirmedSpeed, desiredSpeed, flushPromise, hasLocalIntent },
|
||||
{ persist: setTtsPlaybackSpeed, setError, setSpeed },
|
||||
nextSpeed,
|
||||
);
|
||||
@@ -66,7 +81,7 @@ export function SpeechPlaybackSettings() {
|
||||
{speed.toFixed(2)}x
|
||||
</span>
|
||||
<Button
|
||||
disabled={speed === DEFAULT_SPEED}
|
||||
disabled={!loaded || speed === DEFAULT_SPEED}
|
||||
onClick={() => commitSpeed(DEFAULT_SPEED)}
|
||||
size="sm"
|
||||
type="button"
|
||||
@@ -79,6 +94,7 @@ export function SpeechPlaybackSettings() {
|
||||
<input
|
||||
aria-valuetext={`${speed.toFixed(2)} times`}
|
||||
className="mt-3 w-full accent-primary"
|
||||
disabled={!loaded}
|
||||
id="speech-playback-speed"
|
||||
max={MAX_SPEED}
|
||||
min={MIN_SPEED}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { commitPlaybackSpeed } from "./playbackSpeedPersistence.ts";
|
||||
import {
|
||||
applyLoadedPlaybackSpeed,
|
||||
commitPlaybackSpeed,
|
||||
} from "./playbackSpeedPersistence.ts";
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
@@ -22,6 +25,7 @@ function harness() {
|
||||
confirmedSpeed: { current: 1 },
|
||||
desiredSpeed: { current: 1 },
|
||||
flushPromise: { current: null },
|
||||
hasLocalIntent: { current: false },
|
||||
};
|
||||
const callbacks = {
|
||||
persist: (speed) => {
|
||||
@@ -86,3 +90,17 @@ test("the latest failure rolls back to the last confirmed speed", async () => {
|
||||
assert.deepEqual(h.speeds, [1.25, 1]);
|
||||
assert.equal(h.errors.at(-1), "Error: save failed");
|
||||
});
|
||||
|
||||
test("a delayed initial load cannot overwrite a local intent", async () => {
|
||||
const h = harness();
|
||||
commitPlaybackSpeed(h.state, h.callbacks, 1.5);
|
||||
applyLoadedPlaybackSpeed(h.state, h.callbacks, 1);
|
||||
|
||||
assert.equal(h.state.desiredSpeed.current, 1.5);
|
||||
assert.deepEqual(h.speeds, [1.5]);
|
||||
|
||||
h.saves[0].resolve();
|
||||
await settle(h.state);
|
||||
assert.equal(h.state.confirmedSpeed.current, 1.5);
|
||||
assert.equal(h.state.desiredSpeed.current, 1.5);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ export type PlaybackSpeedPersistenceState = {
|
||||
confirmedSpeed: MutableValue<number>;
|
||||
desiredSpeed: MutableValue<number>;
|
||||
flushPromise: MutableValue<Promise<void> | null>;
|
||||
hasLocalIntent: MutableValue<boolean>;
|
||||
};
|
||||
|
||||
type PlaybackSpeedPersistenceCallbacks = {
|
||||
@@ -19,12 +20,24 @@ export function commitPlaybackSpeed(
|
||||
callbacks: PlaybackSpeedPersistenceCallbacks,
|
||||
nextSpeed: number,
|
||||
) {
|
||||
state.hasLocalIntent.current = true;
|
||||
state.desiredSpeed.current = nextSpeed;
|
||||
callbacks.setSpeed(nextSpeed);
|
||||
callbacks.setError(null);
|
||||
ensurePlaybackSpeedFlush(state, callbacks);
|
||||
}
|
||||
|
||||
export function applyLoadedPlaybackSpeed(
|
||||
state: PlaybackSpeedPersistenceState,
|
||||
callbacks: Pick<PlaybackSpeedPersistenceCallbacks, "setSpeed">,
|
||||
savedSpeed: number,
|
||||
) {
|
||||
if (state.hasLocalIntent.current) return;
|
||||
state.confirmedSpeed.current = savedSpeed;
|
||||
state.desiredSpeed.current = savedSpeed;
|
||||
callbacks.setSpeed(savedSpeed);
|
||||
}
|
||||
|
||||
function ensurePlaybackSpeedFlush(
|
||||
state: PlaybackSpeedPersistenceState,
|
||||
callbacks: PlaybackSpeedPersistenceCallbacks,
|
||||
|
||||
Reference in New Issue
Block a user