keep awake

This commit is contained in:
Sunny 2026-02-14 15:40:37 +00:00
parent 4ce1874d78
commit d074bb2aef
2 changed files with 67 additions and 0 deletions

View file

@ -36,6 +36,7 @@ import { useMediaSession } from '@/hooks/audio/useMediaSession';
import { useAudioContext } from '@/hooks/audio/useAudioContext'; import { useAudioContext } from '@/hooks/audio/useAudioContext';
import { getLastDocumentLocation, setLastDocumentLocation } from '@/lib/dexie'; import { getLastDocumentLocation, setLastDocumentLocation } from '@/lib/dexie';
import { useBackgroundState } from '@/hooks/audio/useBackgroundState'; import { useBackgroundState } from '@/hooks/audio/useBackgroundState';
import { useWakeLock } from '@/hooks/audio/useWakeLock';
import { withRetry, generateTTS, alignAudio } from '@/lib/client'; import { withRetry, generateTTS, alignAudio } from '@/lib/client';
import { preprocessSentenceForAudio, splitTextToTtsBlocks, splitTextToTtsBlocksEPUB } from '@/lib/nlp'; import { preprocessSentenceForAudio, splitTextToTtsBlocks, splitTextToTtsBlocksEPUB } from '@/lib/nlp';
import { isKokoroModel } from '@/utils/voice'; import { isKokoroModel } from '@/utils/voice';
@ -1144,6 +1145,9 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
playAudio, playAudio,
}); });
// Prevent screen from sleeping during audio playback (mobile Safari)
useWakeLock(isPlaying);
// Track the current word index during playback using Howler's seek position // Track the current word index during playback using Howler's seek position
useEffect(() => { useEffect(() => {
if (!activeHowl || !isPlaying || !currentSentenceAlignment || !currentSentenceAlignment.words.length) { if (!activeHowl || !isPlaying || !currentSentenceAlignment || !currentSentenceAlignment.words.length) {

View file

@ -0,0 +1,63 @@
import { useEffect, useRef, useCallback } from 'react';
/**
* Requests a screen wake lock to prevent the display from sleeping
* during audio playback. Automatically re-acquires the lock when the
* page regains visibility (Safari releases it on visibility change).
*/
export function useWakeLock(isPlaying: boolean) {
const wakeLockRef = useRef<WakeLockSentinel | null>(null);
const requestWakeLock = useCallback(async () => {
if (!('wakeLock' in navigator)) return;
try {
wakeLockRef.current = await navigator.wakeLock.request('screen');
wakeLockRef.current.addEventListener('release', () => {
wakeLockRef.current = null;
});
} catch (err) {
// Wake lock request can fail (e.g. low battery mode)
console.warn('Wake lock request failed:', err);
}
}, []);
const releaseWakeLock = useCallback(async () => {
if (wakeLockRef.current) {
try {
await wakeLockRef.current.release();
} catch {
// Ignore release errors
}
wakeLockRef.current = null;
}
}, []);
// Acquire/release based on playback state
useEffect(() => {
if (isPlaying) {
requestWakeLock();
} else {
releaseWakeLock();
}
return () => {
releaseWakeLock();
};
}, [isPlaying, requestWakeLock, releaseWakeLock]);
// Re-acquire wake lock when page becomes visible again
// (Safari releases wake locks when the page is hidden)
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible' && isPlaying) {
requestWakeLock();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [isPlaying, requestWakeLock]);
}