diff --git a/src/contexts/ConfigContext.tsx b/src/contexts/ConfigContext.tsx
index f8443a2..8a9c2b7 100644
--- a/src/contexts/ConfigContext.tsx
+++ b/src/contexts/ConfigContext.tsx
@@ -18,6 +18,7 @@ interface ConfigContextType {
audioPlayerSpeed: number;
voice: string;
skipBlank: boolean;
+ keepPlayingInBackground: boolean;
epubTheme: boolean;
smartSentenceSplitting: boolean;
headerMargin: number;
@@ -162,6 +163,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
audioPlayerSpeed,
voice,
skipBlank,
+ keepPlayingInBackground,
epubTheme,
headerMargin,
footerMargin,
@@ -258,6 +260,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
audioPlayerSpeed,
voice,
skipBlank,
+ keepPlayingInBackground,
epubTheme,
smartSentenceSplitting,
headerMargin,
diff --git a/src/contexts/TTSContext.tsx b/src/contexts/TTSContext.tsx
index 1644c77..adab129 100644
--- a/src/contexts/TTSContext.tsx
+++ b/src/contexts/TTSContext.tsx
@@ -285,6 +285,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
ttsProvider: configTTSProvider,
ttsModel: configTTSModel,
ttsInstructions: configTTSInstructions,
+ keepPlayingInBackground,
updateConfigKey,
skipBlank,
smartSentenceSplitting,
@@ -1141,7 +1142,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
const isBackgrounded = useBackgroundState({
activeHowl,
isPlaying,
- playAudio,
+ keepPlayingInBackground,
});
// Track the current word index during playback using Howler's seek position
@@ -1221,7 +1222,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
if (isProcessing) return; // Don't proceed if processing audio
if (!sentences[currentIndex]) return; // Don't proceed if no sentence to play
if (activeHowl) return; // Don't proceed if audio is already playing
- if (isBackgrounded) return; // Don't proceed if backgrounded
+ if (isBackgrounded && !keepPlayingInBackground) return; // Don't proceed if background playback is disabled
// Start playing current sentence
playAudio();
@@ -1242,6 +1243,7 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
sentences,
activeHowl,
isBackgrounded,
+ keepPlayingInBackground,
playAudio,
preloadNextAudio,
abortAudio
diff --git a/src/hooks/audio/useBackgroundState.ts b/src/hooks/audio/useBackgroundState.ts
index 12c608b..5831dc4 100644
--- a/src/hooks/audio/useBackgroundState.ts
+++ b/src/hooks/audio/useBackgroundState.ts
@@ -4,23 +4,25 @@ import { Howl } from 'howler';
interface UseBackgroundStateProps {
activeHowl: Howl | null;
isPlaying: boolean;
- playAudio: () => void;
+ keepPlayingInBackground: boolean;
}
-export function useBackgroundState({ activeHowl, isPlaying, playAudio }: UseBackgroundStateProps) {
+export function useBackgroundState({ activeHowl, isPlaying, keepPlayingInBackground }: UseBackgroundStateProps) {
const [isBackgrounded, setIsBackgrounded] = useState(false);
useEffect(() => {
+ setIsBackgrounded(document.hidden);
+
const handleVisibilityChange = () => {
setIsBackgrounded(document.hidden);
- if (document.hidden) {
+ if (document.hidden && !keepPlayingInBackground) {
// When backgrounded, pause audio but maintain isPlaying state
- if (activeHowl) {
+ if (activeHowl?.playing()) {
activeHowl.pause();
}
} else if (isPlaying) {
// When returning to foreground, resume from current position
- if (activeHowl) {
+ if (activeHowl && !activeHowl.playing()) {
activeHowl.play();
}
}
@@ -30,7 +32,22 @@ export function useBackgroundState({ activeHowl, isPlaying, playAudio }: UseBack
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
- }, [isPlaying, activeHowl, playAudio]);
+ }, [isPlaying, activeHowl, keepPlayingInBackground]);
+
+ useEffect(() => {
+ if (!document.hidden || !activeHowl || !isPlaying) return;
+
+ if (keepPlayingInBackground) {
+ if (!activeHowl.playing()) {
+ activeHowl.play();
+ }
+ return;
+ }
+
+ if (activeHowl.playing()) {
+ activeHowl.pause();
+ }
+ }, [activeHowl, isPlaying, keepPlayingInBackground]);
return isBackgrounded;
-}
\ No newline at end of file
+}
diff --git a/src/lib/dexie.ts b/src/lib/dexie.ts
index e43d39e..5648a15 100644
--- a/src/lib/dexie.ts
+++ b/src/lib/dexie.ts
@@ -156,6 +156,8 @@ function buildAppConfigFromRaw(raw: RawConfigMap): AppConfigRow {
audioPlayerSpeed: raw.audioPlayerSpeed ? parseFloat(raw.audioPlayerSpeed) : APP_CONFIG_DEFAULTS.audioPlayerSpeed,
voice: '',
skipBlank: raw.skipBlank === 'false' ? false : APP_CONFIG_DEFAULTS.skipBlank,
+ keepPlayingInBackground:
+ raw.keepPlayingInBackground === 'false' ? false : APP_CONFIG_DEFAULTS.keepPlayingInBackground,
epubTheme: raw.epubTheme === 'true',
smartSentenceSplitting:
raw.smartSentenceSplitting === 'false' ? false : APP_CONFIG_DEFAULTS.smartSentenceSplitting,
@@ -1008,4 +1010,3 @@ export async function importDocumentsFromLibrary(
}
}
-
diff --git a/src/types/config.ts b/src/types/config.ts
index 92b4af3..d2d1c79 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -14,6 +14,7 @@ export interface AppConfigValues {
audioPlayerSpeed: number;
voice: string;
skipBlank: boolean;
+ keepPlayingInBackground: boolean;
epubTheme: boolean;
headerMargin: number;
footerMargin: number;
@@ -40,6 +41,7 @@ export const APP_CONFIG_DEFAULTS: AppConfigValues = {
audioPlayerSpeed: 1,
voice: '',
skipBlank: true,
+ keepPlayingInBackground: true,
epubTheme: false,
headerMargin: 0,
footerMargin: 0,