Skip to main content

Overview

AudioStreamInterface defines the contract for audio stream adapters used by RealtimeTranscriber. Implement this interface to create custom audio sources for realtime transcription. Built-in Adapters:
  • AudioPcmStreamAdapter - Uses @fugood/react-native-audio-pcm-stream for microphone input
  • SimulateFileAudioStreamAdapter - Simulates streaming from a file (for testing)
  • JestAudioStreamAdapter - Mock adapter for Jest tests

Interface Definition

interface AudioStreamInterface {
  initialize(config: AudioStreamConfig): Promise<void>
  start(): Promise<void>
  stop(): Promise<void>
  isRecording(): boolean
  onData(callback: (data: AudioStreamData) => void): void
  onError(callback: (error: string) => void): void
  onStatusChange(callback: (isRecording: boolean) => void): void
  onEnd?(callback: () => void): void
  release(): Promise<void>
}

Required Methods

initialize()

Initializes the audio stream with configuration.
await audioStream.initialize(config: AudioStreamConfig): Promise<void>
config
AudioStreamConfig
required
Configuration for the audio stream
Should prepare the audio stream but not start recording yet.

start()

Starts audio recording/streaming.
await audioStream.start(): Promise<void>
After calling start(), the stream should begin emitting data through the onData callback.

stop()

Stops audio recording/streaming.
await audioStream.stop(): Promise<void>
Should stop emitting data but keep resources initialized for potential restart.

isRecording()

Returns whether the stream is currently recording.
audioStream.isRecording(): boolean
boolean
boolean
true if currently recording, false otherwise

onData()

Registers a callback to receive audio data chunks.
audioStream.onData(callback: (data: AudioStreamData) => void): void
callback
(data: AudioStreamData) => void
required
Callback function to handle audio data
This callback should be called whenever new audio data is available during recording.

onError()

Registers a callback to receive error messages.
audioStream.onError(callback: (error: string) => void): void
callback
(error: string) => void
required
Callback function to handle errors

onStatusChange()

Registers a callback to receive recording status changes.
audioStream.onStatusChange(callback: (isRecording: boolean) => void): void
callback
(isRecording: boolean) => void
required
Callback function to handle status changes
Should be called whenever recording starts or stops.

onEnd() (Optional)

Registers a callback for when the stream ends naturally (e.g., file playback complete).
audioStream.onEnd?(callback: () => void): void
callback
() => void
required
Callback function called when stream ends
This is optional and mainly useful for file-based streams or streams with a natural end point.

release()

Releases all resources associated with the audio stream.
await audioStream.release(): Promise<void>
Should stop recording if active and clean up all resources (close files, release native resources, etc.).

Example Implementation

import { AudioStreamInterface, AudioStreamConfig, AudioStreamData } from 'whisper.rn'

class MyAudioStreamAdapter implements AudioStreamInterface {
  private recording = false
  private dataCallback?: (data: AudioStreamData) => void
  private errorCallback?: (error: string) => void
  private statusCallback?: (isRecording: boolean) => void
  private endCallback?: () => void
  private config?: AudioStreamConfig

  async initialize(config: AudioStreamConfig): Promise<void> {
    this.config = config
    // Initialize audio recording system
    // e.g., set up microphone, configure format, etc.
  }

  async start(): Promise<void> {
    if (this.recording) return
    
    this.recording = true
    this.statusCallback?.(true)
    
    // Start capturing audio
    // When audio data is available:
    this.emitAudioData(audioBuffer)
  }

  async stop(): Promise<void> {
    if (!this.recording) return
    
    this.recording = false
    this.statusCallback?.(false)
    
    // Stop capturing audio
  }

  isRecording(): boolean {
    return this.recording
  }

  onData(callback: (data: AudioStreamData) => void): void {
    this.dataCallback = callback
  }

  onError(callback: (error: string) => void): void {
    this.errorCallback = callback
  }

  onStatusChange(callback: (isRecording: boolean) => void): void {
    this.statusCallback = callback
  }

  onEnd(callback: () => void): void {
    this.endCallback = callback
  }

  async release(): Promise<void> {
    await this.stop()
    // Clean up resources
    this.dataCallback = undefined
    this.errorCallback = undefined
    this.statusCallback = undefined
    this.endCallback = undefined
  }

  private emitAudioData(buffer: Uint8Array): void {
    this.dataCallback?.({
      data: buffer,
      sampleRate: this.config?.sampleRate || 16000,
      channels: this.config?.channels || 1,
      timestamp: Date.now(),
    })
  }

  private emitError(error: string): void {
    this.errorCallback?.(error)
  }
}

Usage with RealtimeTranscriber

import { RealtimeTranscriber } from 'whisper.rn'
import { MyAudioStreamAdapter } from './MyAudioStreamAdapter'

const audioStream = new MyAudioStreamAdapter()

const transcriber = new RealtimeTranscriber(
  {
    whisperContext,
    audioStream,
  },
  {
    audioStreamConfig: {
      sampleRate: 16000,
      channels: 1,
      bitsPerSample: 16,
      bufferSize: 16 * 1024,
    },
  },
  {
    onTranscribe: (event) => {
      console.log('Transcription:', event.data?.result)
    },
  }
)

await transcriber.start()

Audio Format Requirements

For compatibility with whisper.cpp:
  • Sample Rate: 16000 Hz (16 kHz)
  • Channels: 1 (mono)
  • Format: 16-bit PCM
  • Byte Order: Little-endian
  • Data Type: Signed 16-bit integers
The data field in AudioStreamData should be a Uint8Array containing raw PCM samples (2 bytes per sample).

Testing Your Implementation

You can test your audio stream adapter in isolation:
const stream = new MyAudioStreamAdapter()

stream.onData((data) => {
  console.log(`Received ${data.data.length} bytes at ${data.sampleRate} Hz`)
})

stream.onError((error) => {
  console.error('Stream error:', error)
})

stream.onStatusChange((isRecording) => {
  console.log('Recording status:', isRecording)
})

await stream.initialize({
  sampleRate: 16000,
  channels: 1,
  bitsPerSample: 16,
})

await stream.start()

// Let it record for a while...
await new Promise(resolve => setTimeout(resolve, 5000))

await stream.stop()
await stream.release()

Build docs developers (and LLMs) love