Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/app-expo/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ module.exports = {
permissions: [
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO",
"android.permission.FOREGROUND_SERVICE",
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
"android.permission.WAKE_LOCK",
"android.permission.MODIFY_AUDIO_SETTINGS",
],
},
Expand Down
6 changes: 4 additions & 2 deletions packages/app-expo/src/lib/platform/expo-speech-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ITTSPlayer, TTSConfig } from "@readany/core/tts";
* the system kills it.
*/
import * as Speech from "expo-speech";
import { AppState, type AppStateStatus, type NativeEventSubscription } from "react-native";
import { AppState, type AppStateStatus, type NativeEventSubscription, Platform } from "react-native";

export class ExpoSpeechTTSPlayer implements ITTSPlayer {
onStateChange?: (state: "playing" | "paused" | "stopped") => void;
Expand All @@ -20,7 +20,9 @@ export class ExpoSpeechTTSPlayer implements ITTSPlayer {
private _appStateSubscription: NativeEventSubscription | null = null;

private _handleAppStateChange = (nextAppState: AppStateStatus): void => {
if (nextAppState === "background") {
// Only stop on iOS — Apple's TextToSpeech.framework crashes if speech is
// active when backgrounded. Android handles background speech fine.
if (Platform.OS === "ios" && nextAppState === "background") {
if (!this._stopped) {
this.stop();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,9 @@ export class TrackPlayerDashScopeTTSPlayer implements ITTSPlayer {
},
);

// On iOS, keep the audio session alive by inserting a silent track.
if (Platform.OS === "ios") {
void this._insertSilenceKeepAlive();
}
// Keep the audio session alive by inserting a silent track.
// Prevents OS from killing playback during network stalls on both platforms.
void this._insertSilenceKeepAlive();
}

private async _insertSilenceKeepAlive(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,10 @@ export class TrackPlayerEdgeTTSPlayer implements ITTSPlayer {
total: this._chunks.length,
});

// On iOS, keep the audio session alive by inserting a silent track.
// This prevents the OS from suspending JS when audio stops between real tracks.
if (Platform.OS === "ios") {
void this._insertSilenceKeepAlive();
}
// Keep the audio session alive by inserting a silent track.
// iOS: Prevents OS from suspending JS when audio stops between real tracks.
// Android: Maintains audio focus and prevents system from killing playback during network stalls.
void this._insertSilenceKeepAlive();
}

private async _insertSilenceKeepAlive(): Promise<void> {
Expand Down