@superapp_men/text-to-speech
v1.0.1
Published
Text-to-speech with multi-language support for SuperApp Partner Apps
Maintainers
Readme
@superapp_men/text-to-speech
Multi-language text-to-speech for SuperApp Partner Apps. Convert text to speech with language support, playback control, and real-time events.
Features
- Multi-language Support - English, French, Spanish, Arabic and more
- Playback Control - Speak, stop, pause, and resume
- Event-driven Architecture - React to speech events in real-time
- Configurable - Control rate, pitch, and volume
- Voice Discovery - Query available voices per language
- TypeScript - Full type safety and IntelliSense
- Cross-platform - Works on Web, iOS, and Android
- Zero Dependencies - Lightweight and efficient
Installation
npm install @superapp_men/text-to-speechor
yarn add @superapp_men/text-to-speechQuick Start
import { TextToSpeech, Language } from "@superapp_men/text-to-speech";
// Initialize
const tts = new TextToSpeech({
timeout: 5000,
debug: true,
});
// Check availability
const available = await tts.isAvailable();
if (!available) {
console.log("Text-to-speech not available");
return;
}
// Listen for events
tts.on("speakFinished", ({ duration }) => {
console.log("Finished speaking, duration:", duration, "ms");
});
tts.on("error", ({ message }) => {
console.error("Error:", message);
});
// Speak text in any supported language
await tts.speak({ text: "Hello world", language: Language.EN_US });
await tts.speak({ text: "Bonjour le monde", language: Language.FR_FR });
await tts.speak({ text: "Hola mundo", language: Language.ES_ES });
await tts.speak({ text: "مرحبا بالعالم", language: Language.AR_SA });Table of Contents
- Basic Usage
- Configuration
- API Reference
- Events
- React Integration
- Vue Integration
- Supported Languages
- Error Handling
- Best Practices
Basic Usage
Simple Text-to-Speech
import { TextToSpeech, Language } from "@superapp_men/text-to-speech";
async function speakText() {
const tts = new TextToSpeech({ debug: true });
// Check if available
const available = await tts.isAvailable();
if (!available) {
console.log("Text-to-speech not available");
return;
}
// Listen for completion
tts.on("speakFinished", ({ duration }) => {
console.log("Done speaking! Duration:", duration, "ms");
});
// Speak with language
await tts.speak({
text: "Bonjour, comment allez-vous ?",
language: Language.FR_FR,
});
}With Playback Control
import { TextToSpeech } from "@superapp_men/text-to-speech";
const tts = new TextToSpeech();
// Start speaking
await tts.speak({
text: "This is a long text that the user might want to pause.",
language: "en-US",
rate: 0.9, // Slightly slower
pitch: 1.0,
volume: 1.0,
});
// Pause speech
await tts.pause();
// Resume speech
await tts.resume();
// Stop speech entirely
await tts.stop();With Event Listeners (React Example)
import { useState, useEffect } from "react";
import { TextToSpeech, SpeechState, Language } from "@superapp_men/text-to-speech";
function TextToSpeechComponent() {
const [tts] = useState(() => new TextToSpeech({ debug: true }));
const [state, setState] = useState<SpeechState>(SpeechState.IDLE);
const [isSpeaking, setIsSpeaking] = useState(false);
const [error, setError] = useState<string | null>(null);
const [text, setText] = useState("");
const [language, setLanguage] = useState<string>(Language.EN_US);
useEffect(() => {
const unsubState = tts.on("stateChange", ({ state }) => {
setState(state);
setIsSpeaking(
state === SpeechState.SPEAKING || state === SpeechState.PAUSED
);
});
const unsubFinished = tts.on("speakFinished", ({ duration }) => {
console.log("Finished speaking, duration:", duration, "ms");
});
const unsubError = tts.on("error", ({ message }) => {
setError(message);
});
return () => {
unsubState();
unsubFinished();
unsubError();
tts.destroy();
};
}, [tts]);
const handleSpeak = async () => {
try {
setError(null);
await tts.speak({ text, language });
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to speak");
}
};
const handleStop = async () => {
await tts.stop();
};
return (
<div>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter text to speak..."
/>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="en-US">English (US)</option>
<option value="fr-FR">French</option>
<option value="es-ES">Spanish</option>
<option value="ar-SA">Arabic (Standard)</option>
<option value="ar-MA">Arabic (Morocco)</option>
</select>
<button onClick={handleSpeak} disabled={isSpeaking || !text}>
{isSpeaking ? "Speaking..." : "Speak"}
</button>
<button onClick={handleStop} disabled={!isSpeaking}>
Stop
</button>
{error && <p style={{ color: "red" }}>{error}</p>}
</div>
);
}Configuration
TextToSpeech Configuration
interface TextToSpeechConfig {
timeout?: number; // Request timeout in ms (default: 5000)
debug?: boolean; // Enable debug logging (default: false)
}
const tts = new TextToSpeech({
timeout: 10000,
debug: true,
});Speak Configuration
interface SpeakConfig {
text: string; // The text to speak (required)
language?: string; // Language/locale code (default: 'en-US')
rate?: number; // Speech rate 0.1 - 10 (default: 1.0)
pitch?: number; // Speech pitch 0 - 2 (default: 1.0)
volume?: number; // Speech volume 0 - 1 (default: 1.0)
}
await tts.speak({
text: "Hello world",
language: Language.EN_US,
rate: 1.0,
pitch: 1.0,
volume: 1.0,
});Notes:
language: Determines the voice and pronunciation. Use theLanguageenum or any valid BCP-47 language tag (e.g."fr-FR","ar-MA").rate:1.0is normal speed,0.5is half speed,2.0is double speed.pitch:1.0is normal pitch,0is lowest,2is highest.volume:1.0is full volume,0is silent.
API Reference
TextToSpeech Class
Constructor
new TextToSpeech(config?: TextToSpeechConfig)Methods
isAvailable(): Promise<boolean>
Check if text-to-speech is available on the device.
const available = await tts.isAvailable();
if (!available) {
console.log("Text-to-speech not supported");
}getSupportedLanguages(): Promise<string[]>
Get list of supported language codes.
const languages = await tts.getSupportedLanguages();
console.log("Supported:", languages);
// ['en-US', 'es-ES', 'fr-FR', 'ar-SA', ...]getVoices(): Promise<VoiceInfo[]>
Get available voices with their language and name.
const voices = await tts.getVoices();
voices.forEach((voice) => {
console.log(`${voice.name} (${voice.language})`);
});Returns:
interface VoiceInfo {
voiceId: string; // Voice identifier
name: string; // Human-readable name
language: string; // Language code (e.g. "en-US")
}speak(config: SpeakConfig): Promise<void>
Speak the given text. If already speaking, the current speech is stopped before starting the new one.
await tts.speak({
text: "Bonjour le monde",
language: Language.FR_FR,
rate: 1.0,
});stop(): Promise<void>
Stop current speech.
await tts.stop();pause(): Promise<void>
Pause current speech.
await tts.pause();resume(): Promise<void>
Resume paused speech.
await tts.resume();getStatus(): Promise<StatusResponsePayload>
Get current speech status from the SuperApp.
const status = await tts.getStatus();
console.log("Is speaking:", status.isSpeaking);
console.log("State:", status.state);getState(): SpeechState
Get current speech state.
Returns: 'idle' | 'speaking' | 'paused' | 'error'
const state = tts.getState();isSpeaking(): boolean
Check if currently speaking or paused.
if (tts.isSpeaking()) {
await tts.stop();
}on<T>(event: TTSEventType, callback: EventListener<T>): () => void
Add event listener. Returns unsubscribe function.
const unsubscribe = tts.on("speakFinished", ({ duration }) => {
console.log("Duration:", duration);
});
// Later, unsubscribe
unsubscribe();off<T>(event: TTSEventType, callback: EventListener<T>): void
Remove event listener.
tts.off("speakFinished", myCallback);removeAllListeners(event?: TTSEventType): void
Remove all listeners for an event, or all events if none specified.
tts.removeAllListeners("speakFinished");
tts.removeAllListeners();destroy(): void
Cleanup and destroy the instance. Stops any current speech.
tts.destroy();Events
Event Types
type TTSEventType =
| "stateChange" // Speech state changed
| "speakStarted" // Started speaking
| "speakFinished" // Finished speaking
| "error"; // Error occurredstateChange
Fired when speech state changes.
tts.on("stateChange", ({ state, previousState }) => {
console.log(`${previousState} -> ${state}`);
switch (state) {
case "speaking":
button.textContent = "Stop";
break;
case "paused":
button.textContent = "Resume";
break;
case "idle":
button.textContent = "Speak";
break;
}
});speakStarted
Fired when speech starts.
tts.on("speakStarted", ({ sessionId, config }) => {
console.log("Session started:", sessionId);
console.log("Language:", config.language);
console.log("Text:", config.text);
});speakFinished
Fired when speech finishes (naturally or via stop).
tts.on("speakFinished", ({ sessionId, duration }) => {
console.log("Session ended:", sessionId);
console.log("Duration:", duration, "ms");
});error
Fired when an error occurs.
tts.on("error", ({ code, message, details }) => {
console.error(`Error [${code}]: ${message}`);
switch (code) {
case "NOT_SUPPORTED":
alert("Text-to-speech is not supported on this device");
break;
case "INVALID_TEXT":
alert("Please enter some text");
break;
case "SPEAK_FAILED":
alert("Speech synthesis failed");
break;
}
});React Integration
Custom Hook
import { useState, useEffect, useCallback } from "react";
import { TextToSpeech, SpeechState } from "@superapp_men/text-to-speech";
function useTextToSpeech() {
const [tts] = useState(() => new TextToSpeech());
const [isSpeaking, setIsSpeaking] = useState(false);
const [state, setState] = useState<SpeechState>(SpeechState.IDLE);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const unsubscribers = [
tts.on("stateChange", ({ state }) => {
setState(state);
setIsSpeaking(
state === SpeechState.SPEAKING || state === SpeechState.PAUSED
);
}),
tts.on("error", ({ message }) => {
setError(message);
}),
];
return () => {
unsubscribers.forEach((unsub) => unsub());
tts.destroy();
};
}, [tts]);
const speak = useCallback(
async (text: string, language: string = "en-US") => {
setError(null);
try {
await tts.speak({ text, language });
} catch (err: any) {
setError(err.message);
}
},
[tts]
);
const stop = useCallback(async () => {
await tts.stop();
}, [tts]);
const pause = useCallback(async () => {
await tts.pause();
}, [tts]);
const resume = useCallback(async () => {
await tts.resume();
}, [tts]);
return { speak, stop, pause, resume, isSpeaking, state, error, tts };
}Usage in Component
function MyComponent() {
const { speak, stop, isSpeaking, error } = useTextToSpeech();
return (
<div>
<button onClick={() => speak("Bonjour!", "fr-FR")} disabled={isSpeaking}>
Speak French
</button>
<button onClick={() => speak("Hello!", "en-US")} disabled={isSpeaking}>
Speak English
</button>
<button onClick={() => speak("مرحبا!", "ar-SA")} disabled={isSpeaking}>
Speak Arabic
</button>
<button onClick={stop} disabled={!isSpeaking}>
Stop
</button>
{error && <p style={{ color: "red" }}>{error}</p>}
</div>
);
}Vue Integration
Composable
import { ref, onUnmounted } from "vue";
import { TextToSpeech, SpeechState } from "@superapp_men/text-to-speech";
export function useTextToSpeech() {
const tts = new TextToSpeech();
const isSpeaking = ref(false);
const state = ref<SpeechState>(SpeechState.IDLE);
const error = ref<string | null>(null);
tts.on("stateChange", ({ state: newState }) => {
state.value = newState;
isSpeaking.value =
newState === SpeechState.SPEAKING || newState === SpeechState.PAUSED;
});
tts.on("error", ({ message }) => {
error.value = message;
});
onUnmounted(() => {
tts.destroy();
});
const speak = async (text: string, language: string = "en-US") => {
error.value = null;
try {
await tts.speak({ text, language });
} catch (err: any) {
error.value = err.message;
}
};
const stop = async () => {
await tts.stop();
};
const pause = async () => {
await tts.pause();
};
const resume = async () => {
await tts.resume();
};
return { speak, stop, pause, resume, isSpeaking, state, error, tts };
}Component
<template>
<div>
<input v-model="text" placeholder="Enter text to speak..." />
<select v-model="language">
<option value="en-US">English (US)</option>
<option value="fr-FR">French</option>
<option value="es-ES">Spanish</option>
<option value="ar-SA">Arabic (Standard)</option>
<option value="ar-MA">Arabic (Morocco)</option>
</select>
<button @click="speak(text, language)" :disabled="isSpeaking || !text">
{{ isSpeaking ? "Speaking..." : "Speak" }}
</button>
<button @click="stop()" :disabled="!isSpeaking">Stop</button>
<p v-if="error" class="error">{{ error }}</p>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useTextToSpeech } from "./useTextToSpeech";
const text = ref("");
const language = ref("en-US");
const { speak, stop, isSpeaking, error } = useTextToSpeech();
</script>Supported Languages
Language Enum
enum Language {
EN_US = "en-US", // English (US)
ES_ES = "es-ES", // Spanish (Spain)
FR_FR = "fr-FR", // French
AR_SA = "ar-SA", // Arabic (Standard)
AR_MA = "ar-MA", // Arabic (Morocco)
}Using Custom Language Codes
You can pass any valid BCP-47 language tag:
await tts.speak({ text: "Guten Tag", language: "de-DE" });
await tts.speak({ text: "Ciao mondo", language: "it-IT" });
await tts.speak({ text: "Olá mundo", language: "pt-BR" });Note: Actual language/voice availability depends on the device. Use getSupportedLanguages() and getVoices() to check what's available at runtime.
Error Handling
Error Codes
enum TextToSpeechError {
NOT_SUPPORTED = "NOT_SUPPORTED",
SPEAK_FAILED = "SPEAK_FAILED",
NOT_SPEAKING = "NOT_SPEAKING",
ALREADY_SPEAKING = "ALREADY_SPEAKING",
LANGUAGE_NOT_SUPPORTED = "LANGUAGE_NOT_SUPPORTED",
INVALID_TEXT = "INVALID_TEXT",
CANCELLED = "CANCELLED",
SUPERAPP_NOT_AVAILABLE = "SUPERAPP_NOT_AVAILABLE",
TIMEOUT = "TIMEOUT",
}Handling Errors
import { TextToSpeech, TextToSpeechError } from "@superapp_men/text-to-speech";
const tts = new TextToSpeech();
// Try-catch for methods
try {
await tts.speak({ text: "Hello", language: "en-US" });
} catch (error: any) {
switch (error.code) {
case TextToSpeechError.NOT_SUPPORTED:
alert("Text-to-speech not supported on this device");
break;
case TextToSpeechError.INVALID_TEXT:
alert("Please provide text to speak");
break;
default:
console.error("Error:", error.message);
}
}
// Event listener for errors
tts.on("error", ({ code, message }) => {
console.error(`Error [${code}]: ${message}`);
});Best Practices
1. Always Check Availability
const available = await tts.isAvailable();
if (!available) {
// Show alternative (e.g. no audio feedback)
return;
}2. Stop Before Speaking New Text
The speak() method automatically stops any current speech before starting new speech, so you don't need to call stop() manually:
// This is safe - previous speech is auto-stopped
await tts.speak({ text: "First sentence", language: "en-US" });
await tts.speak({ text: "Second sentence", language: "fr-FR" });3. Query Available Languages at Runtime
const languages = await tts.getSupportedLanguages();
const voices = await tts.getVoices();
// Filter voices for a specific language
const frenchVoices = voices.filter((v) => v.language.startsWith("fr"));4. Use Appropriate Speech Rate for Education
// Slower rate for language learning
await tts.speak({
text: "Bonjour, comment allez-vous ?",
language: "fr-FR",
rate: 0.8, // Slightly slower for clarity
});
// Normal rate for feedback
await tts.speak({
text: "Correct! Well done!",
language: "en-US",
rate: 1.0,
});5. Clean Up Resources
// In React
useEffect(() => {
const tts = new TextToSpeech();
// ... use tts
return () => {
tts.destroy();
};
}, []);
// In Vue
onUnmounted(() => {
tts.destroy();
});
// Vanilla JS
window.addEventListener("beforeunload", () => {
tts.destroy();
});6. Handle Background/Foreground
document.addEventListener("visibilitychange", async () => {
if (document.hidden && tts.isSpeaking()) {
await tts.stop();
}
});Browser Support
- Chrome/Edge 33+
- Firefox 49+
- Safari 7+
- iOS Safari 7+ (via Capacitor)
- Android WebView (via Capacitor)
Note: Voice availability varies by platform and device. Mobile devices via Capacitor typically provide the widest range of voices and languages.
Troubleshooting
"Text-to-speech not available"
Cause: Device or browser doesn't support the SpeechSynthesis API
Solution: Check with isAvailable() and provide a fallback
"No voice found for language"
Cause: The requested language voice is not installed on the device
Solution: Use getSupportedLanguages() to check available languages, or let the system use a fallback voice
Speech sounds robotic
Solution:
- Try different voices with
getVoices() - Adjust
rateandpitchfor more natural speech - Some platforms have higher quality voices than others
Speech cuts off on mobile
Cause: Some mobile browsers pause speech synthesis when the app goes to background
Solution: Handle the visibilitychange event and inform the user
License
MIT
Support
- Email: [email protected]
Ready to add text-to-speech to your partner app? Install the package and start speaking!
