decodeAudioData method Null safety

Future<AudioBuffer> decodeAudioData(
  1. ByteBuffer audioData,
  2. [DecodeSuccessCallback? successCallback,
  3. DecodeErrorCallback? errorCallback]
)
override

Implementation

Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
    [DecodeSuccessCallback? successCallback,
    DecodeErrorCallback? errorCallback]) {
  // Both callbacks need to be provided if they're being used.
  assert((successCallback == null) == (errorCallback == null));
  // `decodeAudioData` can exist either in the older callback syntax or the
  // newer `Promise`-based syntax that also accepts callbacks. In the former,
  // we synthesize a `Future` to be consistent.
  // For more details:
  // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
  // https://www.w3.org/TR/webaudio/#dom-baseaudiocontext-decodeaudiodata
  final completer = Completer<Object>();
  var errorInCallbackIsNull = false;

  void success(AudioBuffer decodedData) {
    completer.complete(decodedData);
    successCallback!.call(decodedData);
  }

  final nullErrorString =
      '[AudioContext.decodeAudioData] completed with a null error.';

  void error(DomException? error) {
    // Safari has a bug where it may return null for the error callback. In
    // the case where the Safari version still returns a `Promise` and the
    // error is not null after the `Promise` is finished, the error callback
    // is called instead in the `Promise`'s `catch` block. Otherwise, and in
    // the case where a `Promise` is not returned by the API at all, the
    // callback never gets called (for backwards compatibility, it can not
    // accept null). Instead, the `Future` completes with a custom string,
    // indicating that null was given.
    // https://github.com/mdn/webaudio-examples/issues/5
    if (error != null) {
      // Note that we `complete` and not `completeError`. This is to make sure
      // that errors in the `Completer` are not thrown if the call gets back
      // a `Promise`.
      completer.complete(error);
      errorCallback!.call(error);
    } else {
      completer.complete(nullErrorString);
      errorInCallbackIsNull = true;
    }
  }

  var decodeResult;
  if (successCallback == null) {
    decodeResult =
        JS("creates:AudioBuffer;", "#.decodeAudioData(#)", this, audioData);
  } else {
    decodeResult = JS(
        "creates:AudioBuffer;",
        "#.decodeAudioData(#, #, #)",
        this,
        audioData,
        convertDartClosureToJS(success, 1),
        convertDartClosureToJS(error, 1));
  }

  if (decodeResult != null) {
    // Promise-based syntax.
    return promiseToFuture<AudioBuffer>(decodeResult).catchError((error) {
      // If the error was null in the callback, but no longer is now that the
      // `Promise` is finished, call the error callback. If it's still null,
      // throw the error string. This is to handle the aforementioned bug in
      // Safari.
      if (errorInCallbackIsNull) {
        if (error != null) {
          errorCallback?.call(error);
        } else {
          throw nullErrorString;
        }
      }
      throw error;
    });
  }

  // Callback-based syntax. We use the above completer to synthesize a
  // `Future` from the callback values. Since we don't use `completeError`
  // above, `then` is used to simulate an error.
  return completer.future.then((value) {
    if (value is AudioBuffer) return value;
    throw value;
  });
}