diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 8e179f98bd..c091673d25 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -70,6 +70,7 @@ } from '$lib/utils'; import { AudioQueue } from '$lib/utils/audio'; import { RealtimeCall, getBridgeTurnState, type BridgeSubmission } from '$lib/utils/realtime'; + import { acquireWakeLock } from '$lib/utils/wakeLock'; import { routeAvatarAnimation } from '$lib/utils/voice-avatar'; import { createTemporaryChatId, isTemporaryChatId } from '$lib/utils/chatId'; import { @@ -2210,6 +2211,16 @@ let callMode = 'current'; let bridge: RealtimeCall; + let releaseCallWakeLock = () => {}; + const updateCallWakeLock = (active: boolean) => { + releaseCallWakeLock(); + releaseCallWakeLock = active ? acquireWakeLock() : () => {}; + }; + $: callInProgress = + ($showCallOverlay && callMode === 'current') || !!(bridge?.connected || bridge?.connecting); + $: updateCallWakeLock(callInProgress); + onDestroy(() => releaseCallWakeLock()); + const bridgeCancellations = new Map void>(); const visibleMessageText = (message: any) => getOutputText(message?.output) || removeAllDetails(message?.content ?? ''); diff --git a/src/lib/components/chat/MessageInput/CallOverlay.svelte b/src/lib/components/chat/MessageInput/CallOverlay.svelte index 2ba3147109..2004964f6a 100644 --- a/src/lib/components/chat/MessageInput/CallOverlay.svelte +++ b/src/lib/components/chat/MessageInput/CallOverlay.svelte @@ -29,8 +29,6 @@ export let modelId; export let bridge: RealtimeCall | undefined = undefined; - let wakeLock = null; - let model = null; let loading = false; @@ -830,34 +828,6 @@ document.addEventListener('keydown', handleKeydown); return; } - const setWakeLock = async () => { - try { - wakeLock = await navigator.wakeLock.request('screen'); - } catch (err) { - // The Wake Lock request has failed - usually system related, such as battery. - console.log(err); - } - - if (wakeLock) { - // Add a listener to release the wake lock when the page is unloaded - wakeLock.addEventListener('release', () => { - // the wake lock has been released - console.log('Wake Lock released'); - }); - } - }; - - if ('wakeLock' in navigator) { - await setWakeLock(); - - document.addEventListener('visibilitychange', async () => { - // Re-request the wake lock if the document becomes visible - if (wakeLock !== null && document.visibilityState === 'visible') { - await setWakeLock(); - } - }); - } - model = $models.find((m) => m.id === modelId); if ($settings.audio?.tts?.engine === 'browser-kokoro' && !$TTSWorker) { diff --git a/src/lib/utils/wakeLock.ts b/src/lib/utils/wakeLock.ts new file mode 100644 index 0000000000..0163c60705 --- /dev/null +++ b/src/lib/utils/wakeLock.ts @@ -0,0 +1,32 @@ +export const acquireWakeLock = () => { + if (typeof navigator === 'undefined' || !('wakeLock' in navigator)) return () => {}; + + let lock: WakeLockSentinel | null = null; + let stopped = false; + let pending = false; + + const request = async () => { + if (stopped || pending || (lock && !lock.released) || document.visibilityState !== 'visible') + return; + pending = true; + try { + const acquired = await navigator.wakeLock.request('screen'); + // Cleanup can run while the browser is still granting the lock. + if (stopped) await acquired.release(); + else lock = acquired; + } catch (error) { + console.log('Wake Lock request failed:', error); + } finally { + pending = false; + } + }; + + document.addEventListener('visibilitychange', request); + void request(); + return () => { + stopped = true; + document.removeEventListener('visibilitychange', request); + void lock?.release().catch((error) => console.log('Wake Lock release failed:', error)); + lock = null; + }; +};