npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@superapp_men/text-to-speech

v1.0.1

Published

Text-to-speech with multi-language support for SuperApp Partner Apps

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.

npm version License: MIT

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-speech

or

yarn add @superapp_men/text-to-speech

Quick 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

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 the Language enum or any valid BCP-47 language tag (e.g. "fr-FR", "ar-MA").
  • rate: 1.0 is normal speed, 0.5 is half speed, 2.0 is double speed.
  • pitch: 1.0 is normal pitch, 0 is lowest, 2 is highest.
  • volume: 1.0 is full volume, 0 is 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 occurred

stateChange

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 rate and pitch for 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


Ready to add text-to-speech to your partner app? Install the package and start speaking!