Prevent screen dimming during playback

This commit is contained in:
2026-02-08 21:05:26 +01:00
parent 5c9e260ce1
commit d2e5108d3f
3 changed files with 79 additions and 0 deletions
+1
View File
@@ -5,6 +5,7 @@
- Add `Shift+↑/↓` shortcuts for adjusting WPM by 100 and document them in the shortcuts modal
- Improve mobile support for long words by shifting the focal point left
- Fix major performance issues on long texts by reducing frequent localStorage writes during playback and keeping global keyboard listeners stable
- Keep the screen awake while playing when the browser supports the Screen Wake Lock API
### 2026-02-07: 1.1.0
+32
View File
@@ -31,6 +31,7 @@ import {
saveTextPayload,
setPositionForTextInPlace,
} from "./lib/storage";
import { createWakeLockHandle } from "./lib/wakelock";
import { styles } from "./styles";
import type { BookMetadata, PositionsMap } from "./types";
@@ -119,16 +120,22 @@ export default function App() {
const lastIndexRef = useRef<number>(currentIndex);
const currentIndexRef = useRef<number>(currentIndex);
const wordsLengthRef = useRef<number>(words.length);
const isPlayingRef = useRef<boolean>(isPlaying);
const speedScaleRef = useRef(1);
const observedWpmRef = useRef<number | null>(null);
const observedWpmEmaRef = useRef<number | null>(null);
const lastControlTickRef = useRef<number | null>(null);
const wasPlayingRef = useRef(false);
const wakeLockRef = useRef(createWakeLockHandle());
useEffect(() => {
currentIndexRef.current = currentIndex;
}, [currentIndex]);
useEffect(() => {
isPlayingRef.current = isPlaying;
}, [isPlaying]);
useEffect(() => {
wordsLengthRef.current = words.length;
}, [words.length]);
@@ -604,6 +611,31 @@ export default function App() {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
}, []);
// Keep the screen awake while playing (best effort; may be unsupported).
useEffect(() => {
const wl = wakeLockRef.current;
if (!isPlaying) {
void wl.release();
return;
}
void wl.request();
const onVisibilityChange = () => {
if (document.visibilityState === "visible" && isPlayingRef.current) {
void wl.request();
} else {
void wl.release();
}
};
document.addEventListener("visibilitychange", onVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", onVisibilityChange);
void wl.release();
};
}, [isPlaying]);
const handleProgressClick = useCallback(
(e: ReactMouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
+46
View File
@@ -0,0 +1,46 @@
// Best-effort screen wake lock helper.
// Supported in Chromium-based browsers and some others; not supported in iOS Safari.
export type WakeLockHandle = {
request: () => Promise<void>;
release: () => Promise<void>;
};
type WakeLockSentinelLike = {
released: boolean;
release: () => Promise<void>;
};
export function createWakeLockHandle(): WakeLockHandle {
let sentinel: WakeLockSentinelLike | null = null;
const request = async () => {
// Re-request is allowed; only keep one sentinel.
if (sentinel && !sentinel.released) return;
const wl = (navigator as unknown as { wakeLock?: { request: (type: "screen") => Promise<WakeLockSentinelLike> } })
.wakeLock;
if (!wl) return;
try {
sentinel = await wl.request("screen");
} catch {
// Not allowed (e.g. user gesture required, tab not visible) or unsupported.
sentinel = null;
}
};
const release = async () => {
const s = sentinel;
sentinel = null;
if (!s || s.released) return;
try {
await s.release();
} catch {
// Ignore.
}
};
return { request, release };
}