30195a437b
Closes https://github.com/dart-lang/sdk/issues/47520 decodeAudioData has an older callback-based syntax and newer Promise-based syntax. In order to be consistent with the method signature as well as be able to use both syntaxes, this CL provides an API that can handle both. Change-Id: I875defcfec9e429496a1ac9866f1b53d204eff69 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221744 Reviewed-by: Riley Porter <rileyporter@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com>
129 lines
4.9 KiB
Plaintext
129 lines
4.9 KiB
Plaintext
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of web_audio;
|
|
|
|
$(ANNOTATIONS)$(NATIVESPEC)$(CLASS_MODIFIERS)class $CLASSNAME$EXTENDS$IMPLEMENTS {
|
|
$!MEMBERS
|
|
factory AudioContext() => JS('AudioContext',
|
|
'new (window.AudioContext || window.webkitAudioContext)()');
|
|
|
|
GainNode createGain() {
|
|
if (JS('bool', '#.createGain !== undefined', this)) {
|
|
return JS('GainNode', '#.createGain()', this);
|
|
} else {
|
|
return JS('GainNode', '#.createGainNode()', this);
|
|
}
|
|
}
|
|
|
|
ScriptProcessorNode createScriptProcessor([int$NULLABLE bufferSize,
|
|
int$NULLABLE numberOfInputChannels,
|
|
int$NULLABLE numberOfOutputChannels]) {
|
|
var function = JS(
|
|
'=Object',
|
|
'#.createScriptProcessor || '
|
|
'#.createJavaScriptNode',
|
|
this,
|
|
this);
|
|
if (numberOfOutputChannels != null) {
|
|
return JS('ScriptProcessorNode', '#.call(#, #, #, #)', function, this,
|
|
bufferSize, numberOfInputChannels, numberOfOutputChannels);
|
|
} else if (numberOfInputChannels != null) {
|
|
return JS('ScriptProcessorNode', '#.call(#, #, #)', function, this,
|
|
bufferSize, numberOfInputChannels);
|
|
} else if (bufferSize != null) {
|
|
return JS(
|
|
'ScriptProcessorNode', '#.call(#, #)', function, this, bufferSize);
|
|
} else {
|
|
return JS(
|
|
'ScriptProcessorNode', '#.call(#)', function, this);
|
|
}
|
|
}
|
|
|
|
Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
|
|
[DecodeSuccessCallback$NULLABLE successCallback,
|
|
DecodeErrorCallback$NULLABLE 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$NULLASSERT.call(decodedData);
|
|
}
|
|
|
|
final nullErrorString =
|
|
'[AudioContext.decodeAudioData] completed with a null error.';
|
|
|
|
void error(DomException$NULLABLE 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$NULLASSERT.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;
|
|
});
|
|
}
|
|
}
|