> ## Documentation Index
> Fetch the complete documentation index at: https://typecast.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Dart/Flutter

The official Dart and Flutter SDK for the [Typecast API](https://studio.typecast.ai/developers/api). Convert text to lifelike speech, stream audio, generate timestamps, discover voices, and create custom voices from Dart or Flutter applications.

<CardGroup cols={2}>
<Card title="pub.dev" icon="cube" href="https://pub.dev/packages/typecast_dart">
Typecast Dart SDK
</Card>

<Card title="Source Code" icon="github" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-dart">
Typecast Dart SDK Source Code
</Card>
</CardGroup>

## Installation

Install from pub.dev:

```bash
dart pub add typecast_dart
```

<Note>Latest registered version: **0.1.13** on pub.dev.</Note>

For Flutter projects:

```bash
flutter pub add typecast_dart
flutter pub add audioplayers
```

<Warning>
Use **typecast_dart 0.1.13 or higher**. For production Flutter apps, avoid embedding a long-lived API key in a distributed client. Route requests through your backend when the API key must remain private.
</Warning>

## Quick Start

```dart
import 'package:audioplayers/audioplayers.dart';
import 'package:typecast_dart/typecast_dart.dart';

final client = TypecastClient(apiKey: 'YOUR_API_KEY');
final player = AudioPlayer();

Future<void> speakAndPlay() async {
  final response = await client.textToSpeech(
    const TtsRequest(
      voiceId: 'tc_672c5f5ce59fac2a48faeaee', // Find voice IDs at https://studio.typecast.ai/developers/api/voices
      text: "Hello there! I'm your friendly text-to-speech agent.",
      model: TtsModel.ssfmV30,
      language: LanguageCode.eng,
      output: Output(audioFormat: AudioFormat.wav),
    ),
  );

  await player.play(BytesSource(response.audioData));
  print('Duration: ${response.duration}s, Format: ${response.format.value}');
}
```

## Playback in Flutter

The Dart SDK returns generated audio as bytes. In Flutter, pass those bytes to an audio playback package such as `audioplayers`.

Use one shared `AudioPlayer` instance and play each response directly from memory:

```dart
import 'package:audioplayers/audioplayers.dart';
import 'package:typecast_dart/typecast_dart.dart';

final client = TypecastClient(apiKey: 'YOUR_API_KEY');
final player = AudioPlayer();

Future<void> playTts(String text) async {
  final response = await client.textToSpeech(
    TtsRequest(
      voiceId: 'tc_672c5f5ce59fac2a48faeaee',
      text: text,
      model: TtsModel.ssfmV30,
      language: LanguageCode.eng,
      output: const Output(audioFormat: AudioFormat.wav),
    ),
  );

  await player.play(BytesSource(response.audioData));
}
```

<Note>
For production Flutter apps, keep long-lived API keys on your backend. The Flutter app can request generated audio from your backend and still play the returned bytes with `BytesSource`.
</Note>

## Features

- **Multiple Voice Models**: Support for `ssfm-v30` and `ssfm-v21` AI voice models
- **Multi-language Support**: 35+ languages including English, Korean, Japanese, Chinese, Spanish, and more
- **Emotion Control**: Preset emotions or smart context-aware inference
- **Audio Customization**: Control loudness, pitch, tempo, and output format
- **Voice Discovery**: V2 Voices API with filtering by model, gender, age, and use cases
- **Streaming**: Access the streaming TTS endpoint as a Dart `Stream<List<int>>`
- **Timestamp TTS**: Word- and character-level alignment data with SRT/VTT helpers
- **Instant Voice Cloning**: Upload a WAV sample and create a custom voice ID
- **Dart and Flutter**: Use the same package in Dart CLI, server, and Flutter projects

## Voice Recommendations

Use `recommendVoices` when you know the desired style but not the exact `voice_id`.

```dart
final voices = await client.recommendVoices(
  'warm female voice for a product tutorial',
  count: 3,
);

for (final voice in voices) {
  print('${voice.voiceId} ${voice.voiceName} ${voice.score}');
}
```

Recommendation results contain only `voiceId`, `voiceName`, and `score`. Use `getVoiceV2` or `getVoicesV2` when you need detailed metadata such as supported models, emotions, gender, age, or use cases.

## Configuration

Set your API key via environment variable or constructor:

<CodeGroup>

```bash Environment Variable
export TYPECAST_API_KEY="your-api-key-here"
```

```dart From Environment
import 'dart:io';
import 'package:typecast_dart/typecast_dart.dart';

final client = TypecastClient(
  apiKey: Platform.environment['TYPECAST_API_KEY'],
);
```

```dart Direct Configuration
import 'package:typecast_dart/typecast_dart.dart';

final client = TypecastClient(
  apiKey: 'your-api-key-here',
);
```

</CodeGroup>

<Info>
When requests go through your own proxy, set `baseUrl` to the proxy endpoint and omit `apiKey`. The SDK will not send the `X-API-KEY` header for empty or missing keys. Requests to the default Typecast host still require an API key.
</Info>

```dart Proxy without API key
final client = TypecastClient(
  baseUrl: 'https://your-proxy.example.com',
);
```

## Advanced Usage

### Emotion Control (ssfm-v30)

ssfm-v30 offers two emotion control modes: **Preset** and **Smart**.

<Tabs>
  <Tab title="Smart Mode">
    Let the AI infer emotion from context:

    ```dart
    final response = await client.textToSpeech(
      const TtsRequest(
        voiceId: 'tc_672c5f5ce59fac2a48faeaee',
        text: 'Everything is going to be okay.',
        model: TtsModel.ssfmV30,
        prompt: SmartPrompt(
          previousText: 'I just got the best news!',
          nextText: "I can't wait to celebrate!",
        ),
      ),
    );

    await player.play(BytesSource(response.audioData));
    ```
  </Tab>

  <Tab title="Preset Mode">
    Explicitly set emotion with preset values:

    ```dart
    final response = await client.textToSpeech(
      const TtsRequest(
        voiceId: 'tc_672c5f5ce59fac2a48faeaee',
        text: 'I am so excited to show you these features!',
        model: TtsModel.ssfmV30,
        prompt: PresetPrompt(
          emotionPreset: EmotionPreset.happy,
          emotionIntensity: 1.5,
        ),
      ),
    );

    await player.play(BytesSource(response.audioData));
    ```
  </Tab>
</Tabs>

### Audio Customization

Control loudness, pitch, tempo, and output format:

```dart
final response = await client.textToSpeech(
  const TtsRequest(
    voiceId: 'tc_672c5f5ce59fac2a48faeaee',
    text: 'Customized audio output!',
    model: TtsModel.ssfmV30,
    output: Output(
      targetLufs: -14.0,
      audioPitch: 2,
      audioTempo: 1.2,
      audioFormat: AudioFormat.mp3,
    ),
  ),
);

await player.play(BytesSource(response.audioData));
```

### Generate audio to a file

Use `generateToFile` when you want the SDK to synthesize speech and write the audio bytes directly to a local file. The model defaults to `ssfm-v30`, and `.mp3` / `.wav` extensions infer the output format when no output format is set. Browse available voice IDs on the [Voices](https://studio.typecast.ai/developers/api/voices) page.

```dart
await client.generateToFile(
  'output.mp3',
  GenerateToFileRequest(
    text: 'Hello from Typecast.',
    voiceId: 'tc_672c5f5ce59fac2a48faeaee', // Find voice IDs at https://studio.typecast.ai/developers/api/voices
  ),
);
```

### Text pauses

Use text pause markup when you only need silent gaps inside one composed text segment. Put `<|5s|>`, `<|1s|>`, `<|0.3s|>`, or `<|0.34413s|>` directly in the text. The value is interpreted as seconds and must end with `s`. This keeps the pause expression visible in plain text without adding separate pause calls.

```dart
final audio = await client
    .composeSpeech()
    .defaults(ComposerSettings(voiceId: 'tc_672c5f5ce59fac2a48faeaee', model: TTSModel.ssfmV30))
    .say('Hello<|5s|>Nice to meet you<|1s|>Today<|2s|>how does the weather feel?')
    .generate();
```

### Multi-speaker composition

Use the composer chaining API when one output file needs different voices or per-segment options such as pitch, tempo, or prompt. The composer sends segments to `POST /v1/text-to-speech/compose`, which returns WAV or MP3 directly. Set silence removal explicitly on TTS segments; explicit pauses are preserved.

```dart
final audio = await client
    .composeSpeech()
    .defaults(const ComposerSettings(voiceId: 'tc_672c5f5ce59fac2a48faeaee', model: TtsModel.ssfmV30))
    .say('Hello there')
    .pause(5)
    .say(
      'Nice to meet you',
      overrides: const ComposerSettings(
        voiceId: 'tc_60e5426de8b95f1d3000d7b5',
        output: Output(audioPitch: 2),
      ),
    )
    .say('Today')
  .pause(2)
  .say('How does the weather feel?')
    .generate();

await File('conversation.wav').writeAsBytes(audio.audioData);
```

### Voice Discovery (V2 API)

List and filter available voices with enhanced metadata:

```dart
final voices = await client.getVoicesV2();

final filtered = await client.getVoicesV2(
  const VoicesV2Filter(
    model: TtsModel.ssfmV30,
    gender: 'female',
    age: 'young_adult',
  ),
);

for (final voice in voices) {
  print('ID: ${voice.voiceId}, Name: ${voice.voiceName}');
  print('Gender: ${voice.gender}, Age: ${voice.age}');
  print('Models: ${voice.models.map((model) => model.version).join(', ')}');
}

final voice = await client.getVoiceV2('tc_672c5f5ce59fac2a48faeaee');
print(voice.voiceName);
```

### Streaming

Consume streaming audio as a Dart stream and play it without writing a file:

```dart
import 'dart:typed_data';

final stream = await client.textToSpeechStream(
  const TtsRequestStream(
    voiceId: 'tc_672c5f5ce59fac2a48faeaee',
    text: 'Stream this text as audio in real time.',
    model: TtsModel.ssfmV30,
    output: OutputStream(audioFormat: AudioFormat.wav),
  ),
);

final audioBytes = <int>[];
await for (final chunk in stream) {
  audioBytes.addAll(chunk);
}

await player.play(BytesSource(Uint8List.fromList(audioBytes)));
```

<Note>
**WAV streaming format:** 32000 Hz, 16-bit, mono PCM. The first chunk includes a 44-byte WAV header (size = `0xFFFFFFFF`); subsequent chunks are raw PCM only. The example above avoids file storage and plays the complete stream from memory. For true low-latency chunk-by-chunk playback, feed the PCM chunks into a streaming audio engine instead of `audioplayers`.
</Note>

## Timestamp TTS

`textToSpeechWithTimestamps()` wraps `POST /v1/text-to-speech/with-timestamps` and returns audio together with word- or character-level alignment data.

```dart
final result = await client.textToSpeechWithTimestamps(
  const TtsRequest(
    voiceId: 'tc_60e5426de8b95f1d3000d7b5',
    text: 'Hello. How are you?',
    model: TtsModel.ssfmV30,
  ),
);

await player.play(BytesSource(result.audioBytes()));
print('Duration: ${result.audioDuration}s');

for (final word in result.words) {
  print('[${word.startTime}s - ${word.endTime}s] ${word.word}');
}
```

### Granularity

Pass `granularity: 'word'` (default) or `granularity: 'char'` to control the alignment unit.

```dart
final result = await client.textToSpeechWithTimestamps(
  const TtsRequest(
    voiceId: 'tc_60e5426de8b95f1d3000d7b5',
    text: 'Hello. How are you?',
    model: TtsModel.ssfmV30,
  ),
  granularity: 'char',
);
```

### Subtitle Export

```dart
await File('output.srt').writeAsString(result.toSrt());
await File('output.vtt').writeAsString(result.toVtt());
```

## Instant Voice Cloning

Upload a short WAV sample to create a custom voice:

```dart
final voice = await client.cloneVoice(
  audio: await File('sample.wav').readAsBytes(),
  filename: 'sample.wav',
  name: 'My Voice',
  model: TtsModel.ssfmV30,
);

print('Custom voice ID: ${voice.voiceId}');
```

<Warning>
Voice cloning audio must be **25 MB or smaller**, and the custom voice name must be **1-30 characters**.
</Warning>

## Control silence duration

Requires **0.1.13 or later**.

`remove_silence_ms` specifies the **silence duration to retain**, not the amount to remove. Use an integer from `0` to `1000` ms. `0` removes detected silence; omission or `null` disables duration-based silence removal.

Standard, streaming, and timestamp TTS use `output.remove_silence_ms`; Compose uses `segments[].output.remove_silence_ms` on each `tts` segment. Returned timestamps align with the processed audio, and explicit `pause` segments are preserved.

Streaming's default leading-silence trimming is separate. Small values such as `0` can leave gaps between playable chunks; allow sufficient playback buffering and test with your content.

Pass these output settings to the corresponding request's `output`. For streaming, use the streaming output type.

```dart
final output = Output(removeSilenceMs: 300);
final streamOutput = OutputStream(removeSilenceMs: 300);
```
