<!DOCTYPE html>
<html>
<head>
<title>
Test decodeAudioData promises
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
// The functionality of decodeAudioData() is orthogonal to the type and
// the state of AudioContext. So we use the online context here and any
// resampling of the file is okay for this test.
let context = new AudioContext();
// Test file URLs.
let validAudioFileUrl = '../resources/media/24bit-44khz.wav';
let invalidAudioFileUrl = '../resources/media/invalid-audio-file.txt';
// Global storage for array buffers from XHR.
let validArrayBuffer;
let invalidArrayBuffer;
// Decoded data from validAudioFile.
let referenceDecodedAudioBuffer;
let audit = Audit.createTaskRunner();
// Preload ArrayBuffer and the reference AudioBuffer from URLs.
audit.define('preload-arraybuffer', (task, should) => {
Promise
.all([
should(
Audit.loadFileFromUrl(validAudioFileUrl),
'Loading valid audio file')
.beResolved(),
should(
Audit.loadFileFromUrl(invalidAudioFileUrl),
'loading invalid audio file')
.beResolved()
])
.then((arrayBuffers) => {
validArrayBuffer = arrayBuffers[0];
invalidArrayBuffer = arrayBuffers[1];
})
.then(() => task.done());
});
// Decode a valid encoded file and verify that the promise succeeds
// correctly.
audit.define('decode-valid-file', (task, should) => {
// Note that the order of completion for each promise is undefined and
// we do not care about it in this test.
Promise
.all([
// Do not use the original arrayBuffers for decoding; decode a
// copy because decodeAudioData will detach the buffers.
should(
context.decodeAudioData(validArrayBuffer.slice(0)),
'Decoding a valid audio file')
.beResolved()
.then(buffer => referenceDecodedAudioBuffer = buffer),
should(
context.decodeAudioData(invalidArrayBuffer.slice(0)),
'Decoding an invalid audio file')
.beRejectedWith('EncodingError'),
should(context.decodeAudioData(null), 'Decoding null AudioBuffer')
.beRejected()
])
.then(() => task.done());
});
// Decode a valid file and verify that the promise is fulfilled and the
// successCallback is invoked and both have identical decoded audio
// buffers.
audit.define('promise-and-success-callback', (task, should) => {
let bufferByCallback;
let bufferByPromise;
// Use one callback for success and error. |callbackArg| is a parameter
// for callback functions; it is a decoded audio buffer for success case
// and an error object for failure case.
let successOrErrorCallback = (callbackArg) => {
should(
callbackArg instanceof AudioBuffer,
'Decoding valid file by callback function')
.message(
'successCallback invoked correctly',
'errorCallback incorrectly invoked with ' + callbackArg);
bufferByCallback = callbackArg;
};
// Step 1: Decode a file with callback functions.
let step1 = context.decodeAudioData(
validArrayBuffer.slice(), successOrErrorCallback,
successOrErrorCallback);
// Step 2: Then decode a file with promise pattern.
let step2 = should(step1, 'Decoding a file via promise')
.beResolved()
.then((audioBuffer) => {
bufferByPromise = audioBuffer;
});
// Step 3: compare two buffers from Step 1 and Step 2.
step2.then(() => {
should(
bufferByCallback === bufferByPromise,
'Two buffers decoded by callback function and promise')
.message('are identical', 'are different');
task.done();
});
});
// Decode an invalid file and verify that the promise is rejected and the
// errorCallback is invoked.
audit.define('promise-and-error-callback', (task, should) => {
let successOrErrorCallback = (callbackArg) => {
should(
callbackArg instanceof Error,
'Decoding invalid file with promise and callback:')
.message(
'errorCallback invoked correctly with ' + callbackArg,
'successCallback should not have invoked');
};
let decodeAudioDataPromise = context.decodeAudioData(
invalidArrayBuffer.slice(), successOrErrorCallback,
successOrErrorCallback);
should(decodeAudioDataPromise, 'decodeAudioData promise')
.beRejected('EncodingError')
.then(() => task.done());
});
// decodeAudioData() should be functional even after the associated
// context is closed.
audit.define('decoding-on-closed-context', (task, should) => {
// Use one handler for resolve and reject. |promiseArg| is a parameter
// for handlers; it is a decoded audio buffer for success case and an
// error object for failure case.
let resolveOrReject = (promiseArg) => {
let didDecode = promiseArg instanceof AudioBuffer;
if (didDecode) {
// Compare two decoded AudioBuffers.
let actual = promiseArg;
let expected = referenceDecodedAudioBuffer;
should(actual.length, 'Decoded buffer length (frames)')
.beEqualTo(expected.length);
should(actual.duration, 'Decoded buffer duration (sec)')
.beEqualTo(expected.duration);
should(actual.sampleRate, 'Decoded buffer sample rate (Hz)')
.beEqualTo(expected.sampleRate);
should(
actual.numberOfChannels, 'Number of channels in decoded buffer')
.beEqualTo(expected.numberOfChannels);
for (let c = 0; c < expected.numberOfChannels; ++c) {
let actualChannelData = actual.getChannelData(c);
let expectedChannelData = expected.getChannelData(c);
should(actualChannelData, 'Decoded buffer channel #' + c)
.beEqualToArray(
expectedChannelData, 'the expected channel #' + c);
}
should(task.state, 'The buffer')
.message(
'correctly decoded after the context has been closed',
'decoding succeeded but the data is incorrect');
}
should(
didDecode, 'Decoding ArrayBuffer after context has been closed')
.message('completed successfully', 'failed : ' + promiseArg);
};
let onlineContext = new AudioContext();
onlineContext.close()
.then(() => {
return context.decodeAudioData(validArrayBuffer);
})
.then(resolveOrReject, resolveOrReject)
.then(() => {
task.done();
});
});
audit.run();
</script>
</body>
</html>