initialize method

  1. @override
Future<int> initialize({
  1. required int outputSampleRate,
})
override

Implementation

@override
Future<int> initialize({required int outputSampleRate}) async {
  if (_isInitialized) {
    return _inputSampleRate!;
  }

  _outputSampleRate = outputSampleRate;

  try {
    _audioContext = AudioContext();
    _inputSampleRate = _audioContext!.sampleRate.toInt();

    // Route audio through an <audio> element to force loudspeaker output.
    // On Android Chrome, getUserMedia with echoCancellation puts the device
    // into "communication mode" which routes audio to the earpiece.
    // Using setSinkId('') on an <audio> element forces loudspeaker output.
    _destinationNode = _audioContext!.createMediaStreamDestination();

    _outputAudioElement = HTMLAudioElement();
    _outputAudioElement!.srcObject = _destinationNode!.stream;
    _outputAudioElement!.autoplay = true;
    try {
      final promise = (_outputAudioElement as JSObject)
          .callMethod('setSinkId'.toJS, ''.toJS) as JSPromise;
      await promise.toDart;
    } catch (_) {}
    await _outputAudioElement!.play().toDart;

    _voiceGainNode = _audioContext!.createGain();
    _voiceGainNode!.gain.value = 1.0;
    _voiceGainNode!.connect(_destinationNode!);

    _backgroundGainNode = _audioContext!.createGain();
    _backgroundGainNode!.gain.value = 0.3;
    _backgroundGainNode!.connect(_destinationNode!);

    _soundGainNode = _audioContext!.createGain();
    _soundGainNode!.gain.value = 0.5;
    _soundGainNode!.connect(_destinationNode!);

    final mediaDevices = window.navigator.mediaDevices;
    final audioConstraints =
        {
              'echoCancellation': true,
              'noiseSuppression': true,
              'autoGainControl': true,
            }.jsify()
            as JSObject;

    final constraints = MediaStreamConstraints(audio: audioConstraints);

    _mediaStream = await mediaDevices.getUserMedia(constraints).toDart;

    final audioTracks = _mediaStream!.getAudioTracks().toDart;
    if (audioTracks.isEmpty) {
      throw Exception('No audio track available');
    }

    final track = audioTracks.first;
    final settings = track.getSettings();
    final settingsObj = settings as JSObject;

    final echoCancellation = settingsObj.getProperty('echoCancellation'.toJS);
    final hasEchoCancellation =
        echoCancellation != null && (echoCancellation as JSBoolean).toDart;

    final sinkId = settingsObj.getProperty('sinkId'.toJS);
    final hasSinkId =
        sinkId != null && (sinkId as JSString).toDart.isNotEmpty;
    final hasHeadphones = hasSinkId;

    if (!hasEchoCancellation && !hasHeadphones) {
      await _cleanup();
      throw Exception(
        'Echo cancellation not available and no headphones connected',
      );
    }

    await _setupMediaRecorder();

    _isInitialized = true;

    return _inputSampleRate!;
  } catch (e) {
    await _cleanup();
    if (e.toString().contains('NotAllowedError') ||
        e.toString().contains('Permission')) {
      throw Exception('PERMISSION_DENIED');
    }
    rethrow;
  }
}