Prevent screen dimming during playback
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
- Add `Shift+↑/↓` shortcuts for adjusting WPM by 100 and document them in the shortcuts modal
|
- 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
|
- 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
|
- 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
|
### 2026-02-07: 1.1.0
|
||||||
|
|
||||||
|
|||||||
+32
@@ -31,6 +31,7 @@ import {
|
|||||||
saveTextPayload,
|
saveTextPayload,
|
||||||
setPositionForTextInPlace,
|
setPositionForTextInPlace,
|
||||||
} from "./lib/storage";
|
} from "./lib/storage";
|
||||||
|
import { createWakeLockHandle } from "./lib/wakelock";
|
||||||
import { styles } from "./styles";
|
import { styles } from "./styles";
|
||||||
import type { BookMetadata, PositionsMap } from "./types";
|
import type { BookMetadata, PositionsMap } from "./types";
|
||||||
|
|
||||||
@@ -119,16 +120,22 @@ export default function App() {
|
|||||||
const lastIndexRef = useRef<number>(currentIndex);
|
const lastIndexRef = useRef<number>(currentIndex);
|
||||||
const currentIndexRef = useRef<number>(currentIndex);
|
const currentIndexRef = useRef<number>(currentIndex);
|
||||||
const wordsLengthRef = useRef<number>(words.length);
|
const wordsLengthRef = useRef<number>(words.length);
|
||||||
|
const isPlayingRef = useRef<boolean>(isPlaying);
|
||||||
const speedScaleRef = useRef(1);
|
const speedScaleRef = useRef(1);
|
||||||
const observedWpmRef = useRef<number | null>(null);
|
const observedWpmRef = useRef<number | null>(null);
|
||||||
const observedWpmEmaRef = useRef<number | null>(null);
|
const observedWpmEmaRef = useRef<number | null>(null);
|
||||||
const lastControlTickRef = useRef<number | null>(null);
|
const lastControlTickRef = useRef<number | null>(null);
|
||||||
const wasPlayingRef = useRef(false);
|
const wasPlayingRef = useRef(false);
|
||||||
|
const wakeLockRef = useRef(createWakeLockHandle());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
currentIndexRef.current = currentIndex;
|
currentIndexRef.current = currentIndex;
|
||||||
}, [currentIndex]);
|
}, [currentIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isPlayingRef.current = isPlaying;
|
||||||
|
}, [isPlaying]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
wordsLengthRef.current = words.length;
|
wordsLengthRef.current = words.length;
|
||||||
}, [words.length]);
|
}, [words.length]);
|
||||||
@@ -604,6 +611,31 @@ export default function App() {
|
|||||||
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
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(
|
const handleProgressClick = useCallback(
|
||||||
(e: ReactMouseEvent<HTMLDivElement>) => {
|
(e: ReactMouseEvent<HTMLDivElement>) => {
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user