diff --git a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp index 5ddd954efe22..f1e5caaaa137 100644 --- a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp +++ b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -17,6 +19,8 @@ #include #include #include +#include +#include namespace Web::WebAudio { @@ -120,4 +124,122 @@ void BaseAudioContext::queue_a_media_element_task(JS::NonnullGCPtr BaseAudioContext::decode_audio_data(JS::Handle audio_data, JS::GCPtr success_callback, JS::GCPtr error_callback) +{ + auto& realm = this->realm(); + + // FIXME: When decodeAudioData is called, the following steps MUST be performed on the control thread: + + // 1. If this's relevant global object's associated Document is not fully active then return a + // promise rejected with "InvalidStateError" DOMException. + auto const& associated_document = verify_cast(HTML::relevant_global_object(*this)).associated_document(); + if (!associated_document.is_fully_active()) { + auto error = WebIDL::InvalidStateError::create(realm, "The document is not fully active."_string); + return WebIDL::create_rejected_promise_from_exception(realm, error); + } + + // 2. Let promise be a new Promise. + auto promise = WebIDL::create_promise(realm); + + // FIXME: 3. If audioData is detached, execute the following steps: + if (true) { + // FIXME: 3.1. Append promise to [[pending promises]]. + + // FIXME: 3.2. Detach the audioData ArrayBuffer. If this operations throws, jump to the step 3. + + // 3.3. Queue a decoding operation to be performed on another thread. + queue_a_decoding_operation(promise, move(audio_data), success_callback, error_callback); + } + + // 4. Else, execute the following error steps: + else { + // 4.1. Let error be a DataCloneError. + auto error = WebIDL::DataCloneError::create(realm, "Audio data is not detached."_string); + + // 4.2. Reject promise with error, and remove it from [[pending promises]]. + WebIDL::reject_promise(realm, promise, error); + + // 4.3. Queue a media element task to invoke errorCallback with error. + if (error_callback) { + queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, error_callback, error] { + auto completion = WebIDL::invoke_callback(*error_callback, {}, error); + if (completion.is_abrupt()) + HTML::report_exception(completion, realm); + })); + } + } + + // 5. Return promise. + return verify_cast(*promise->promise()); +} + +// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata +void BaseAudioContext::queue_a_decoding_operation(JS::NonnullGCPtr promise, [[maybe_unused]] JS::Handle audio_data, JS::GCPtr success_callback, JS::GCPtr error_callback) +{ + auto& realm = this->realm(); + + // FIXME: When queuing a decoding operation to be performed on another thread, the following steps + // MUST happen on a thread that is not the control thread nor the rendering thread, called + // the decoding thread. + + // 1. Let can decode be a boolean flag, initially set to true. + auto can_decode { true }; + + // FIXME: 2. Attempt to determine the MIME type of audioData, using MIME Sniffing § 6.2 Matching an + // audio or video type pattern. If the audio or video type pattern matching algorithm returns + // undefined, set can decode to false. + + // 3. If can decode is true, + if (can_decode) { + // FIXME: attempt to decode the encoded audioData into linear PCM. In case of + // failure, set can decode to false. + + // FIXME: If the media byte-stream contains multiple audio tracks, only decode the first track to linear pcm. + } + + // 4. If can decode is false, + if (!can_decode) { + // queue a media element task to execute the following steps: + queue_a_media_element_task(JS::create_heap_function(heap(), [&realm, promise, error_callback] { + // 4.1. Let error be a DOMException whose name is EncodingError. + auto error = WebIDL::EncodingError::create(realm, "Unable to decode."_string); + + // 4.1.2. Reject promise with error, + WebIDL::reject_promise(realm, promise, error); + // FIXME: and remove it from [[pending promises]]. + + // 4.2. If errorCallback is not missing, invoke errorCallback with error. + if (error_callback) { + auto completion = WebIDL::invoke_callback(*error_callback, {}, error); + if (completion.is_abrupt()) + HTML::report_exception(completion, realm); + } + })); + } + + // 5. Otherwise: + else { + // FIXME: 5.1. Take the result, representing the decoded linear PCM audio data, and resample it to the + // sample-rate of the BaseAudioContext if it is different from the sample-rate of + // audioData. + + // FIXME: 5.2. queue a media element task to execute the following steps: + + // FIXME: 5.2.1. Let buffer be an AudioBuffer containing the final result (after possibly performing + // sample-rate conversion). + auto buffer = MUST(create_buffer(2, 1, 44100)); + + // 5.2.2. Resolve promise with buffer. + WebIDL::resolve_promise(realm, promise, buffer); + + // 5.2.3. If successCallback is not missing, invoke successCallback with buffer. + if (success_callback) { + auto completion = WebIDL::invoke_callback(*success_callback, {}, buffer); + if (completion.is_abrupt()) + HTML::report_exception(completion, realm); + } + } +} + } diff --git a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.h b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.h index 0588ac8d2c8d..7b087151068d 100644 --- a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.h +++ b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.h @@ -59,6 +59,8 @@ class BaseAudioContext : public DOM::EventTarget { WebIDL::ExceptionOr> create_dynamics_compressor(); JS::NonnullGCPtr create_gain(); + JS::NonnullGCPtr decode_audio_data(JS::Handle, JS::GCPtr, JS::GCPtr); + protected: explicit BaseAudioContext(JS::Realm&, float m_sample_rate = 0); @@ -70,6 +72,8 @@ class BaseAudioContext : public DOM::EventTarget { JS::NonnullGCPtr m_destination; private: + void queue_a_decoding_operation(JS::NonnullGCPtr, JS::Handle, JS::GCPtr, JS::GCPtr); + float m_sample_rate { 0 }; double m_current_time { 0 }; diff --git a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.idl b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.idl index f768efe82f47..e5acc435e55c 100644 --- a/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.idl +++ b/Userland/Libraries/LibWeb/WebAudio/BaseAudioContext.idl @@ -6,13 +6,14 @@ #import #import #import +#import // https://www.w3.org/TR/webaudio/#enumdef-audiocontextstate enum AudioContextState { "suspended", "running", "closed" }; -// FIXME: callback DecodeErrorCallback = undefined (DOMException error); +callback DecodeErrorCallback = undefined (DOMException error); -// FIXME: callback DecodeSuccessCallback = undefined (AudioBuffer decodedData); +callback DecodeSuccessCallback = undefined (AudioBuffer decodedData); // https://webaudio.github.io/web-audio-api/#BaseAudioContext [Exposed=Window] @@ -45,5 +46,5 @@ interface BaseAudioContext : EventTarget { [FIXME] StereoPannerNode createStereoPanner (); [FIXME] WaveShaperNode createWaveShaper (); - [FIXME] Promise decodeAudioData (ArrayBuffer audioData, optional DecodeSuccessCallback? successCallback, optional DecodeErrorCallback? errorCallback); + Promise decodeAudioData (ArrayBuffer audioData, optional DecodeSuccessCallback? successCallback, optional DecodeErrorCallback? errorCallback); };