Files
readest/apps/readest-app/src/components/AtmosphereOverlay.tsx
T
Huang Xin 7b60b1bb0c fix(ios): reduce GPU memory pressure to prevent WebKit GPU process crash (#3842)
On iOS, navigating to a book group in the library caused the WebKit GPU
process to exceed its 300 MB jetsam limit (peaking at ~328 MB), resulting
in a blank screen flash and broken scroll state.

Three changes reduce peak GPU memory usage:

- Add overscan={200} to VirtuosoGrid/Virtuoso so only items within 200px
  of the viewport are rendered, limiting simultaneous image decoding
- Add loading="lazy" to both Image components in BookCover so the browser
  defers decoding offscreen cover images
- Conditionally mount the <video> and <audio> elements in AtmosphereOverlay
  only when atmosphere mode is active, eliminating idle H.264 decoder
  memory overhead

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:06:16 +02:00

62 lines
1.7 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
import { useAtmosphereStore } from '@/store/atmosphereStore';
import { useThemeStore } from '@/store/themeStore';
const AtmosphereOverlay = () => {
const active = useAtmosphereStore((s) => s.active);
const isDarkMode = useThemeStore((s) => s.isDarkMode);
const videoRef = useRef<HTMLVideoElement>(null);
const audioRef = useRef<HTMLAudioElement>(null);
const isInitialMount = useRef(true);
const audioSrc = isDarkMode ? '/assets/forest-crickets.mp3' : '/assets/forest-birds.mp3';
useEffect(() => {
const video = videoRef.current;
const audio = audioRef.current;
if (active) {
document.body.classList.add('atmosphere');
video?.play()?.catch(() => {});
if (!isInitialMount.current) {
audio?.play()?.catch(() => {});
}
} else {
document.body.classList.remove('atmosphere');
}
isInitialMount.current = false;
}, [active]);
useEffect(() => {
const audio = audioRef.current;
if (!audio || !active) return;
audio.src = audioSrc;
audio.play()?.catch(() => {});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [audioSrc]);
return (
<>
{active && (
<video
ref={videoRef}
id='atmosphere-overlay'
src='/assets/komorebi.mp4'
loop
muted
playsInline
preload='none'
/>
)}
{active && (
// biome-ignore lint/a11y/useMediaCaption: ambient background audio, no spoken content
<audio ref={audioRef} id='forest-audio' src={audioSrc} loop preload='none' />
)}
</>
);
};
export default AtmosphereOverlay;