diff --git a/apps/readest-app/src/app/reader/components/BooksGrid.tsx b/apps/readest-app/src/app/reader/components/BooksGrid.tsx index 67eac266..6791e6ae 100644 --- a/apps/readest-app/src/app/reader/components/BooksGrid.tsx +++ b/apps/readest-app/src/app/reader/components/BooksGrid.tsx @@ -123,6 +123,7 @@ const BooksGrid: React.FC = ({ bookKeys, onCloseBook }) => { bookKey={bookKey} bookDoc={bookDoc} config={config} + gridInsets={gridInsets} contentInsets={contentInsets} /> {viewSettings.vertical && viewSettings.scrolled && ( diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 6a87d903..9c489328 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -58,16 +58,18 @@ const FoliateViewer: React.FC<{ bookKey: string; bookDoc: BookDoc; config: BookConfig; + gridInsets: Insets; contentInsets: Insets; -}> = ({ bookKey, bookDoc, config, contentInsets: insets }) => { +}> = ({ bookKey, bookDoc, config, gridInsets, contentInsets: insets }) => { const { appService, envConfig } = useEnv(); const { themeCode, isDarkMode } = useThemeStore(); const { settings } = useSettingsStore(); const { loadCustomFonts, getLoadedFonts } = useCustomFontStore(); const { getView, setView: setFoliateView, setProgress } = useReaderStore(); - const { getViewSettings, setViewSettings } = useReaderStore(); + const { getViewState, getViewSettings, setViewSettings } = useReaderStore(); const { getParallels } = useParallelViewStore(); const { getBookData } = useBookDataStore(); + const viewState = getViewState(bookKey); const viewSettings = getViewSettings(bookKey); const viewRef = useRef(null); @@ -308,6 +310,7 @@ const FoliateViewer: React.FC<{ const applyMarginAndGap = () => { const viewSettings = getViewSettings(bookKey)!; + const viewState = getViewState(bookKey); const viewInsets = getViewInsets(viewSettings); const showDoubleBorder = viewSettings.vertical && viewSettings.doubleBorder; const showDoubleBorderHeader = showDoubleBorder && viewSettings.showHeader; @@ -315,7 +318,11 @@ const FoliateViewer: React.FC<{ const showTopHeader = viewSettings.showHeader && !viewSettings.vertical; const showBottomFooter = viewSettings.showFooter && !viewSettings.vertical; const moreTopInset = showTopHeader ? Math.max(0, 44 - insets.top) : 0; - const moreBottomInset = showBottomFooter ? Math.max(0, 44 - insets.bottom) : 0; + const ttsBarHeight = + viewState?.ttsEnabled && viewSettings.showTTSBar ? 52 + gridInsets.bottom * 0.33 : 0; + const moreBottomInset = showBottomFooter + ? Math.max(0, Math.max(ttsBarHeight, 44) - insets.bottom) + : Math.max(0, ttsBarHeight); const moreRightInset = showDoubleBorderHeader ? 32 : 0; const moreLeftInset = showDoubleBorderFooter ? 32 : 0; const topMargin = (showTopHeader ? insets.top : viewInsets.top) + moreTopInset; @@ -379,6 +386,8 @@ const FoliateViewer: React.FC<{ viewSettings?.doubleBorder, viewSettings?.showHeader, viewSettings?.showFooter, + viewSettings?.showTTSBar, + viewState?.ttsEnabled, ]); return ( diff --git a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx index ac496255..8b57c9e1 100644 --- a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx +++ b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx @@ -68,11 +68,6 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { unblockerAudioRef.current.addEventListener('play', () => { if ('mediaSession' in navigator) { navigator.mediaSession.metadata = null; - navigator.mediaSession.setActionHandler('play', null); - navigator.mediaSession.setActionHandler('pause', null); - navigator.mediaSession.setActionHandler('stop', null); - navigator.mediaSession.setActionHandler('seekbackward', null); - navigator.mediaSession.setActionHandler('seekforward', null); } }); unblockerAudioRef.current.preload = 'auto'; @@ -127,7 +122,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { const mark = (e as CustomEvent).detail; if (appService?.isMobileApp && 'mediaSession' in navigator) { navigator.mediaSession.metadata = new MediaMetadata({ - title: mark.text, + title: mark?.text || '', artist: sectionLabel || title, album: author, artwork: [{ src: coverImageUrl || '/icon.png', sizes: '512x512', type: 'image/png' }], @@ -244,7 +239,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { } }; - const handleTogglePlay = async () => { + const handleTogglePlay = useCallback(async () => { const ttsController = ttsControllerRef.current; if (!ttsController) return; @@ -263,41 +258,54 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { await ttsController.start(); } } - }; + }, [isPlaying, isPaused]); - const handleBackward = async () => { + const handleBackward = useCallback(async () => { const ttsController = ttsControllerRef.current; if (ttsController) { await ttsController.backward(); } - }; + }, []); - const handleForward = async () => { + const handleForward = useCallback(async () => { const ttsController = ttsControllerRef.current; if (ttsController) { await ttsController.forward(); } - }; + }, []); - const handleStop = async (bookKey: string) => { + const handlePause = useCallback(async () => { const ttsController = ttsControllerRef.current; if (ttsController) { - await ttsController.shutdown(); - ttsControllerRef.current = null; - setTtsController(null); - getView(bookKey)?.deselect(); setIsPlaying(false); - setShowPanel(false); - setShowIndicator(false); + setIsPaused(true); + await ttsController.pause(); } - if (appService?.isIOSApp) { - await invokeUseBackgroundAudio({ enabled: false }); - } - if (appService?.isMobile) { - releaseUnblockAudio(); - } - setTTSEnabled(bookKey, false); - }; + }, []); + + const handleStop = useCallback( + async (bookKey: string) => { + const ttsController = ttsControllerRef.current; + if (ttsController) { + await ttsController.shutdown(); + ttsControllerRef.current = null; + setTtsController(null); + getView(bookKey)?.deselect(); + setIsPlaying(false); + setShowPanel(false); + setShowIndicator(false); + } + if (appService?.isIOSApp) { + await invokeUseBackgroundAudio({ enabled: false }); + } + if (appService?.isMobile) { + releaseUnblockAudio(); + } + setTTSEnabled(bookKey, false); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [appService], + ); // rate range: 0.5 - 3, 1.0 is normal speed // eslint-disable-next-line react-hooks/exhaustive-deps @@ -415,6 +423,30 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => { setShowPanel(false); }; + useEffect(() => { + if ('mediaSession' in navigator) { + navigator.mediaSession.setActionHandler('play', () => { + handleTogglePlay(); + }); + + navigator.mediaSession.setActionHandler('pause', () => { + handleTogglePlay(); + }); + + navigator.mediaSession.setActionHandler('stop', () => { + handlePause(); + }); + + navigator.mediaSession.setActionHandler('seekforward', () => { + handleForward(); + }); + + navigator.mediaSession.setActionHandler('seekbackward', () => { + handleBackward(); + }); + } + }, [handleTogglePlay, handlePause, handleForward, handleBackward]); + useEffect(() => { if (!iconRef.current || !showPanel) return; const parentElement = iconRef.current.parentElement; diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts index a911c8ab..370d81bd 100644 --- a/apps/readest-app/src/services/tts/TTSController.ts +++ b/apps/readest-app/src/services/tts/TTSController.ts @@ -219,6 +219,7 @@ export class TTSController extends EventTarget { await this.initViewTTS(); this.#speak(ssml).catch((e) => this.error(e)); this.preloadNextSSML(); + this.dispatchSpeakMark(); } play() { @@ -357,8 +358,8 @@ export class TTSController extends EventTarget { return this.ttsClient.getSpeakingLang(); } - dispatchSpeakMark(mark: TTSMark) { - this.dispatchEvent(new CustomEvent('tts-speak-mark', { detail: mark })); + dispatchSpeakMark(mark?: TTSMark) { + this.dispatchEvent(new CustomEvent('tts-speak-mark', { detail: mark || { text: '' } })); } error(e: unknown) { diff --git a/apps/readest-app/wrangler.toml b/apps/readest-app/wrangler.toml index 4886a927..cc654a61 100644 --- a/apps/readest-app/wrangler.toml +++ b/apps/readest-app/wrangler.toml @@ -5,7 +5,7 @@ compatibility_flags = ["nodejs_compat"] [observability] enabled = true -head_sampling_rate = 1 +head_sampling_rate = 0.01 [assets] directory = ".open-next/assets"