intron_voice 0.0.1 copy "intron_voice: ^0.0.1" to clipboard
intron_voice: ^0.0.1 copied to clipboard

A typed Dart SDK contract for the Intron Voice API.

Intron Voice Dart SDK #

intron_voice is a standalone Dart SDK for the Intron Voice API. It provides typed request, response, streaming, cancellation, retry, and error primitives for Flutter and server-side Dart applications. The package does not own microphone capture or audio playback.

Official API documentation is available at https://docs.voice.intron.io/.

What This Package Provides #

This package is the core SDK. It gives you typed Dart APIs for Intron Voice, but it deliberately does not bundle a concrete HTTP client, WebSocket client, microphone recorder, audio player, Flutter widgets, or platform permissions.

You provide these pieces from your app:

  • IntronHttpTransport for REST calls such as file upload, STT status, and TTS.
  • IntronWebSocketTransport for streaming STT and streaming TTS.
  • IntronFileUploadSource for audio bytes when uploading files.
  • Microphone capture and audio playback, if your app needs them.

This keeps the SDK usable from Flutter, server-side Dart, tests, and command line tools. A Flutter app can use this package directly, but Flutter-specific recording, playback, permissions, and lifecycle handling stay in the host app.

Installation #

Add the package after publication, or depend on this repository during local development.

dependencies:
  intron_voice: ^0.0.1

For local development before publication, use a path dependency:

dependencies:
  intron_voice:
    path: ../intron_voice

or a Git dependency:

dependencies:
  intron_voice:
    git:
      url: https://github.com/intron-health/intron_voice_dart.git

Quickstart #

The shortest useful flow is: create a client, provide an HTTP transport, provide an upload source, submit audio, and poll for the transcript.

import 'dart:io';
import 'dart:typed_data';

import 'package:intron_voice/intron_voice.dart';

Future<void> main() async {
  final transport = MyHttpTransport();
  final client = IntronClient(
    config: IntronClientConfig(
      tokenProvider: MyTokenProvider(),
    ),
    httpTransport: transport,
  );

  try {
    final job = await client.uploadAudioFile(
      audioFile: SttAudioFile(
        LocalFileUploadSource(File('audio/visit.wav')),
      ),
      options: SttUploadOptions.forLanguage(IntronLanguage.english),
    );

    final result = await client.waitForTranscription(
      fileId: job.fileId,
      pollingInterval: const Duration(seconds: 2),
      timeout: const Duration(minutes: 30),
    );

    print(result.transcript);
  } on IntronRateLimitException catch (error) {
    print('Rate limited. Retry after: ${error.retryAfter}');
  } on IntronApiException catch (error) {
    print('Intron request failed: ${error.message}');
  } finally {
    await client.dispose();
  }
}

final class MyTokenProvider implements IntronTokenProvider {
  @override
  Future<String> resolveToken() async {
    return fetchShortLivedTokenFromYourBackend();
  }
}

final class LocalFileUploadSource implements IntronFileUploadSource {
  LocalFileUploadSource(this.file);

  final File file;

  @override
  String get fileName => file.uri.pathSegments.last;

  @override
  int? get length => file.lengthSync();

  @override
  Stream<Uint8List> openRead() {
    return file.openRead().map(Uint8List.fromList);
  }
}

MyHttpTransport is your adapter from IntronHttpRequest to whichever HTTP client your app already uses. The SDK owns request modeling, authentication, retry metadata, typed parsing, and errors; your transport only sends bytes and returns IntronHttpResponse.

Authentication #

Create an IntronClient with either a static API key or an IntronTokenProvider. The SDK formats credentials as:

Authorization: Bearer <token>

Static keys are useful for local development and trusted server processes. Do not embed long-lived API keys in mobile or web apps. Production apps should call your backend token broker, then provide short-lived credentials through an IntronTokenProvider.

final client = IntronClient(
  config: IntronClientConfig(tokenProvider: MyTokenProvider()),
  httpTransport: myHttpTransport,
  webSocketTransport: myWebSocketTransport,
);
final class MyTokenProvider implements IntronTokenProvider {
  @override
  Future<String> resolveToken() async {
    final token = await fetchShortLivedTokenFromYourBackend();
    return token;
  }
}

Configuration strings, exceptions, and logging helpers redact credentials before exposing diagnostics.

Providing Transports #

The SDK depends on small transport interfaces instead of a bundled networking stack. That lets you use dart:io, package:http, Dio, browser networking, a Flutter plugin, or fakes in tests.

An HTTP adapter must:

  • send the method, URL, headers, body, multipart fields, and multipart files from IntronHttpRequest;
  • return the status code, headers, and response bytes as IntronHttpResponse;
  • avoid logging Authorization headers or request bodies containing sensitive content;
  • implement close() if it owns resources.

A WebSocket adapter must:

  • connect to the URL and headers in IntronWebSocketRequest;
  • expose incoming server messages through messages;
  • send JSON text through addText;
  • close the socket from close().

For file uploads, implement IntronFileUploadSource. On native/server Dart, the local-file implementation from the quickstart is enough. In Flutter mobile, you can wrap a file selected by your picker or recorder. In Flutter web, wrap browser bytes and return a Stream<Uint8List>.

Languages And Voices #

The SDK includes catalog helpers for documented language codes and voice values. The authoritative lists remain the public documentation and may change before an SDK release:

Use the catalog constants where they fit, or pass raw documented strings when the service adds a value before the SDK catalog is updated.

final sttOptions = SttUploadOptions.forLanguage(IntronLanguage.swahili);

final voice = TtsVoiceConfig.fromCatalog(
  language: IntronLanguage.english,
  accent: TtsVoiceAccent.swahili,
  gender: TtsVoiceGender.female,
  outputFormat: TtsOutputAudioFormat.opus,
);

STT Examples #

Synchronous File Transcription #

Use transcribeAudioFileSync for audio of 120 seconds or less when a single request/response flow is appropriate.

final result = await client.transcribeAudioFileSync(
  audioFile: SttAudioFile(
    audioSource,
    duration: const Duration(seconds: 90),
  ),
  options: SttUploadOptions.forLanguage(
    IntronLanguage.english,
    enableDiarization: true,
  ),
);

print(result.transcript);

If the service returns HTTP 503 after queueing work, the SDK preserves the continuation file ID on SttResult.continuationFileId so you can continue with status polling.

Asynchronous File Transcription #

Use uploadAudioFile, getFileStatus, and waitForTranscription for longer audio or when queueing is preferred.

final job = await client.uploadAudioFile(
  audioFile: SttAudioFile(audioSource),
  options: SttUploadOptions.forLanguage(IntronLanguage.swahili),
);

final result = await client.waitForTranscription(
  fileId: job.fileId,
  pollingInterval: const Duration(seconds: 2),
  timeout: const Duration(minutes: 30),
);

print(result.transcript);

Uploads validate supported extensions and send the documented multipart fields audio_file_name and audio_file_blob.

Streaming STT #

Streaming STT accepts externally supplied PCM16 little-endian audio chunks. The SDK does not capture microphone audio; your app owns capture and chunking.

final session = await client.startStreamingTranscription(
  audioStream: pcmAudioChunks,
  options: SttStreamingOptions.forLanguage(
    IntronLanguage.english,
    sampleRate: 16000,
    bitDepth: 16,
    channels: 1,
  ),
);

await for (final transcript in session.transcriptEvents) {
  if (transcript.isFinal) {
    print(transcript.text);
  }
}

The SDK waits for SESSION_CREATED, sends INPUT_AUDIO_CHUNK with sequential ack_id values, and sends COMMIT when the audio stream ends.

TTS Examples #

Synchronous TTS #

Use generateSpeech for text up to the documented 4096 character limit when a single request/response flow is appropriate.

final result = await client.generateSpeech(
  request: TtsRequest(
    text: 'Hello from Intron Voice.',
    voice: TtsVoiceConfig.fromCatalog(
      language: IntronLanguage.english,
      accent: TtsVoiceAccent.swahili,
      gender: TtsVoiceGender.female,
    ),
  ),
);

print(result.audioPath);

Queued TTS #

Use enqueueSpeech and waitForSpeech when queueing is preferred.

final job = await client.enqueueSpeech(
  request: TtsRequest(
    text: 'Queue this text for synthesis.',
    voice: TtsVoiceConfig.fromCatalog(
      language: IntronLanguage.english,
      accent: TtsVoiceAccent.swahili,
      gender: TtsVoiceGender.female,
      outputFormat: TtsOutputAudioFormat.opus,
    ),
  ),
);

final status = await client.waitForSpeech(textId: job.textId);
print(status.audioPath);

Streaming TTS #

Streaming TTS accepts text chunks between 10 and 100 characters. The caller asks for generated audio chunks with fetchAudioChunk and can listen for decoded audio bytes.

final session = await client.startStreamingSpeech(
  textStream: Stream<String>.fromIterable(<String>[
    'This is the first text chunk.',
    'This is the second text chunk.',
  ]),
  options: TtsStreamingOptions(
    voice: TtsVoiceConfig.fromCatalog(
      language: IntronLanguage.english,
      accent: TtsVoiceAccent.swahili,
      gender: TtsVoiceGender.female,
    ),
  ),
);

session.fetchAudioChunk(1);

await for (final audio in session.audioChunkEvents) {
  playOrStore(audio.audioBytes);
}

The SDK sends INPUT_TEXT_CHUNK, FETCH_AUDIO_CHUNK, and COMMIT messages using the documented WebSocket protocol.

Streaming Lifecycle And Reconnection #

STT and TTS streaming sessions expose:

  • events for low-level typed protocol events.
  • stateEvents for lifecycle transitions.
  • done for deterministic shutdown.
  • dispose() to close local resources and the WebSocket.

The service documents a 300 second maximum WebSocket session lifetime and a 60 second idle gap. The SDK rolls streaming sessions over before the 300 second limit by default, commits the old session, opens a new WebSocket, resets ack_id values, and preserves pending input that has not yet been accepted by the server.

Supported Formats And Limits #

STT file uploads currently validate these extensions: .wav, .mp3, .mp4, .m4a, .ogg, .webm, and .flac. Synchronous STT validates the documented 120 second maximum when the caller supplies duration metadata.

Streaming STT expects PCM16 little-endian audio chunks between 1 KB and 32 KB. TTS output formats are wav and opus. TTS text generation accepts up to 4096 characters for REST calls, while streaming TTS chunks must be 10 to 100 characters.

Rate Limits #

The public TTS generate documentation currently lists 30 requests per minute and includes Retry-After guidance. SDK REST responses preserve safe rate-limit metadata such as retryAfter, request IDs, server error codes, and retryability on typed exceptions or response metadata. Check the official docs for current endpoint-specific limits before changing production traffic patterns.

Cancellation And Disposal #

Use IntronCancellationTokenSource to cancel long-running REST polling, uploads, retry waits, and streaming sessions.

final cancellation = IntronCancellationTokenSource();

final future = client.waitForTranscription(
  fileId: fileId,
  cancellationToken: cancellation.token,
);

cancellation.cancel();
try {
  await future;
} on IntronRequestCancelledException {
  // The operation was cancelled by the caller.
}

Always call dispose() on streaming sessions that you stop early, and call client.dispose() when your application no longer needs the client.

Error Handling #

Catch IntronApiException for safe SDK errors, or catch narrower types such as IntronAuthenticationException, IntronRateLimitException, IntronRequestCancelledException, IntronProtocolException, and IntronTransportException.

try {
  final result = await client.generateSpeech(request: request);
  print(result.audioPath);
} on IntronRateLimitException catch (error) {
  scheduleRetryAfter(error.retryAfter);
} on IntronApiException catch (error) {
  logSafeSdkError(error.message, requestId: error.requestId);
}

Privacy Considerations #

Do not log bearer tokens, patient identifiers, raw transcripts, audio bytes, or clinical notes unless your application has explicit consent and compliant storage. Prefer short-lived tokens, least-privilege backend services, explicit retention policies, and synthetic or public test audio in development.

Testing Without The Live Service #

All network surfaces are injectable. Unit tests can use fake transports that record requests and return deterministic responses, without calling the live service.

final transport = FakeHttpTransport(
  IntronHttpResponse(
    statusCode: 200,
    headers: <String, String>{'content-type': 'application/json'},
    body: utf8.encode(jsonEncode(<String, Object?>{
      'file_id': 'file-1',
      'processing_status': 'FILE_QUEUED',
    })),
  ),
);

final client = IntronClient(
  config: IntronClientConfig(apiKey: 'test-key'),
  httpTransport: transport,
);
final memoryUploadSource = MemoryUploadSource(
  fileName: 'sample.wav',
  bytes: Uint8List.fromList(<int>[1, 2, 3]),
);

final job = await client.uploadAudioFile(
  audioFile: SttAudioFile(memoryUploadSource),
);

expect(job.fileId, 'file-1');
expect(transport.requests.single.headers['Authorization'], 'Bearer test-key');

final class FakeHttpTransport implements IntronHttpTransport {
  FakeHttpTransport(this.response);

  final IntronHttpResponse response;
  final List<IntronHttpRequest> requests = <IntronHttpRequest>[];

  @override
  Future<IntronHttpResponse> send(IntronHttpRequest request) async {
    requests.add(request);
    return response;
  }

  @override
  Future<void> close() async {}
}

final class MemoryUploadSource implements IntronFileUploadSource {
  MemoryUploadSource({required this.fileName, required Uint8List bytes})
      : _bytes = bytes;

  final Uint8List _bytes;

  @override
  final String fileName;

  @override
  int? get length => _bytes.length;

  @override
  Stream<Uint8List> openRead() => Stream<Uint8List>.value(_bytes);
}

For WebSocket tests, fake IntronWebSocketTransport.connect, capture outgoing text messages, and push documented JSON messages into the connection stream.

The repository test suite contains fuller fake HTTP and WebSocket transports if you want examples of retry, streaming, and cancellation tests.

Development #

Run the local validation suite before committing changes:

dart format --set-exit-if-changed .
dart analyze
dart test

License #

This SDK is open source under the MIT License.

0
likes
150
points
244
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A typed Dart SDK contract for the Intron Voice API.

License

MIT (license)

More

Packages that depend on intron_voice