startRecording method

  1. @override
Stream<List<int>> startRecording()
override

Start audio recording and get a stream of recorded audio data

Uses Web Audio API with ScriptProcessorNode to capture raw PCM audio data. Returns 16-bit PCM audio samples (mono, typically 44100 or 48000 Hz).

Implementation

@override
Stream<List<int>> startRecording() async* {
  try {
    // Request microphone access
    final stream = await window.navigator.mediaDevices.getUserMedia(
      MediaStreamConstraints(audio: true.toJS),
    ).toDart;

    _mediaStream = stream;

    // Create audio context if needed
    _audioContext ??= _createAudioContext();
    final context = _audioContext!;

    // Create media stream source
    _sourceNode = context.createMediaStreamSource(stream);

    // Create script processor for capturing raw audio
    // Buffer size: 4096 samples
    // Input channels: 1 (mono)
    // Output channels: 1 (mono)
    const bufferSize = 4096;
    _scriptProcessor = context.createScriptProcessor(
      bufferSize,
      1,
      1,
    );

    // Connect nodes: source -> processor -> destination
    _sourceNode!.connect(_scriptProcessor!);
    _scriptProcessor!.connect(context.destination);

    // Create audioprocess handler
    void audioProcessHandler(JSAny event) {
      final audioProcessingEvent = event as AudioProcessingEvent;
      final inputBuffer = audioProcessingEvent.inputBuffer;
      final channelData = inputBuffer.getChannelData(0);

      // Convert Float32Array to 16-bit PCM
      final pcmData = <int>[];
      final length = channelData.length;

      for (var i = 0; i < length; i++) {
        // Get float sample (-1.0 to 1.0)
        final sample = channelData[i];

        // Convert to 16-bit PCM (-32768 to 32767)
        final pcmSample = (sample * 32767.0).clamp(-32768.0, 32767.0).toInt();

        // Add as little-endian bytes
        pcmData.add(pcmSample & 0xFF);
        pcmData.add((pcmSample >> 8) & 0xFF);
      }

      if (!_recordingController.isClosed) {
        _recordingController.add(pcmData);
      }
    }

    // Listen to audioprocess event
    _scriptProcessor!.onaudioprocess = (audioProcessHandler.toJS);

    yield* _recordingController.stream;
  } catch (e) {
    _cleanupRecording();
    throw Exception('Error starting recording: $e');
  }
}