mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:47:34 +00:00
LibWeb: Start filling out BaseAudioContext/AudioContext interfaces
- Fills out both IDLs and implements some basic attributes/methods. - No actual audio processing yet though :^)
This commit is contained in:
parent
30e67721ae
commit
49e6414c58
8 changed files with 407 additions and 8 deletions
|
@ -27,6 +27,7 @@ namespace Web::Bindings {
|
||||||
class Intrinsics;
|
class Intrinsics;
|
||||||
class OptionConstructor;
|
class OptionConstructor;
|
||||||
|
|
||||||
|
enum class AudioContextLatencyCategory;
|
||||||
enum class CanPlayTypeResult;
|
enum class CanPlayTypeResult;
|
||||||
enum class CanvasFillRule;
|
enum class CanvasFillRule;
|
||||||
enum class DOMParserSupportedType;
|
enum class DOMParserSupportedType;
|
||||||
|
@ -603,6 +604,10 @@ class Table;
|
||||||
namespace Web::WebAudio {
|
namespace Web::WebAudio {
|
||||||
class AudioContext;
|
class AudioContext;
|
||||||
class BaseAudioContext;
|
class BaseAudioContext;
|
||||||
|
|
||||||
|
enum class AudioContextState;
|
||||||
|
|
||||||
|
struct AudioContextOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::WebGL {
|
namespace Web::WebGL {
|
||||||
|
|
|
@ -84,6 +84,7 @@ namespace Web::HTML::EventNames {
|
||||||
__ENUMERATE_HTML_EVENT(select) \
|
__ENUMERATE_HTML_EVENT(select) \
|
||||||
__ENUMERATE_HTML_EVENT(slotchange) \
|
__ENUMERATE_HTML_EVENT(slotchange) \
|
||||||
__ENUMERATE_HTML_EVENT(stalled) \
|
__ENUMERATE_HTML_EVENT(stalled) \
|
||||||
|
__ENUMERATE_HTML_EVENT(statechange) \
|
||||||
__ENUMERATE_HTML_EVENT(storage) \
|
__ENUMERATE_HTML_EVENT(storage) \
|
||||||
__ENUMERATE_HTML_EVENT(submit) \
|
__ENUMERATE_HTML_EVENT(submit) \
|
||||||
__ENUMERATE_HTML_EVENT(suspend) \
|
__ENUMERATE_HTML_EVENT(suspend) \
|
||||||
|
|
|
@ -5,20 +5,76 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibWeb/Bindings/Intrinsics.h>
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
|
#include <LibWeb/DOM/Event.h>
|
||||||
|
#include <LibWeb/HTML/HTMLMediaElement.h>
|
||||||
#include <LibWeb/WebAudio/AudioContext.h>
|
#include <LibWeb/WebAudio/AudioContext.h>
|
||||||
|
#include <LibWeb/WebIDL/Promise.h>
|
||||||
|
|
||||||
namespace Web::WebAudio {
|
namespace Web::WebAudio {
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audiocontext-audiocontext
|
// https://webaudio.github.io/web-audio-api/#dom-audiocontext-audiocontext
|
||||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioContext>> AudioContext::construct_impl(JS::Realm& realm)
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioContext>> AudioContext::construct_impl(JS::Realm& realm, AudioContextOptions const& context_options)
|
||||||
{
|
{
|
||||||
dbgln("(STUBBED) new AudioContext()");
|
return MUST_OR_THROW_OOM(realm.heap().allocate<AudioContext>(realm, realm, context_options));
|
||||||
return MUST_OR_THROW_OOM(realm.heap().allocate<AudioContext>(realm, realm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioContext::AudioContext(JS::Realm& realm)
|
AudioContext::AudioContext(JS::Realm& realm, AudioContextOptions const& context_options)
|
||||||
: BaseAudioContext(realm)
|
: BaseAudioContext(realm)
|
||||||
{
|
{
|
||||||
|
// FIXME: If the current settings object’s responsible document is NOT fully active, throw an InvalidStateError and abort these steps.
|
||||||
|
|
||||||
|
// 1: Set a [[control thread state]] to suspended on the AudioContext.
|
||||||
|
BaseAudioContext::set_control_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// 2: Set a [[rendering thread state]] to suspended on the AudioContext.
|
||||||
|
BaseAudioContext::set_rendering_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// 3: Let [[pending resume promises]] be a slot on this AudioContext, that is an initially empty ordered list of promises.
|
||||||
|
|
||||||
|
// 4: If contextOptions is given, apply the options:
|
||||||
|
// 4.1: Set the internal latency of this AudioContext according to contextOptions.latencyHint, as described in latencyHint.
|
||||||
|
switch (context_options.latency_hint) {
|
||||||
|
case Bindings::AudioContextLatencyCategory::Balanced:
|
||||||
|
// FIXME: Determine optimal settings for balanced.
|
||||||
|
break;
|
||||||
|
case Bindings::AudioContextLatencyCategory::Interactive:
|
||||||
|
// FIXME: Determine optimal settings for interactive.
|
||||||
|
break;
|
||||||
|
case Bindings::AudioContextLatencyCategory::Playback:
|
||||||
|
// FIXME: Determine optimal settings for playback.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.2: If contextOptions.sampleRate is specified, set the sampleRate of this AudioContext to this value. Otherwise,
|
||||||
|
// use the sample rate of the default output device. If the selected sample rate differs from the sample rate of the output device,
|
||||||
|
// this AudioContext MUST resample the audio output to match the sample rate of the output device.
|
||||||
|
if (context_options.sample_rate.has_value()) {
|
||||||
|
BaseAudioContext::set_sample_rate(context_options.sample_rate.value());
|
||||||
|
} else {
|
||||||
|
// FIXME: This would ideally be coming from the default output device, but we can only get this on Serenity
|
||||||
|
// For now we'll just have to resample
|
||||||
|
BaseAudioContext::set_sample_rate(44100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: 5: If the context is allowed to start, send a control message to start processing.
|
||||||
|
// FIXME: Implement control message queue to run following steps on the rendering thread
|
||||||
|
if (m_allowed_to_start) {
|
||||||
|
// FIXME: 5.1: Attempt to acquire system resources. In case of failure, abort the following steps.
|
||||||
|
|
||||||
|
// 5.2: Set the [[rendering thread state]] to "running" on the AudioContext.
|
||||||
|
BaseAudioContext::set_rendering_state(Bindings::AudioContextState::Running);
|
||||||
|
|
||||||
|
// 5.3: queue a media element task to execute the following steps:
|
||||||
|
queue_a_media_element_task([&realm, this]() {
|
||||||
|
// 5.3.1: Set the state attribute of the AudioContext to "running".
|
||||||
|
BaseAudioContext::set_control_state(Bindings::AudioContextState::Running);
|
||||||
|
|
||||||
|
// 5.3.2: queue a media element task to fire an event named statechange at the AudioContext.
|
||||||
|
this->dispatch_event(DOM::Event::create(realm, HTML::EventNames::statechange).release_value_but_fixme_should_propagate_errors());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioContext::~AudioContext() = default;
|
AudioContext::~AudioContext() = default;
|
||||||
|
@ -31,4 +87,219 @@ JS::ThrowCompletionOr<void> AudioContext::initialize(JS::Realm& realm)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioContext::visit_edges(Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
Base::visit_edges(visitor);
|
||||||
|
for (auto& promise : m_pending_promises)
|
||||||
|
visitor.visit(promise);
|
||||||
|
for (auto& promise : m_pending_resume_promises)
|
||||||
|
visitor.visit(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#dom-audiocontext-getoutputtimestamp
|
||||||
|
AudioTimestamp AudioContext::get_output_timestamp()
|
||||||
|
{
|
||||||
|
dbgln("(STUBBED) getOutputTimestamp()");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#dom-audiocontext-resume
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> AudioContext::resume()
|
||||||
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
auto& vm = realm.vm();
|
||||||
|
|
||||||
|
// FIXME: 1. If this's relevant global object's associated Document is not fully active then return a promise rejected with "InvalidStateError" DOMException.
|
||||||
|
|
||||||
|
// 2. Let promise be a new Promise.
|
||||||
|
auto promise = WebIDL::create_promise(realm);
|
||||||
|
|
||||||
|
// 3. If the [[control thread state]] on the AudioContext is closed reject the promise with InvalidStateError, abort these steps, returning promise.
|
||||||
|
if (state() == Bindings::AudioContextState::Closed) {
|
||||||
|
WebIDL::reject_promise(realm, promise, WebIDL::InvalidStateError::create(realm, "Audio context is already closed."));
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Set [[suspended by user]] to true.
|
||||||
|
m_suspended_by_user = true;
|
||||||
|
|
||||||
|
// 5. If the context is not allowed to start, append promise to [[pending promises]] and [[pending resume promises]] and abort these steps, returning promise.
|
||||||
|
if (m_allowed_to_start) {
|
||||||
|
TRY_OR_THROW_OOM(vm, m_pending_promises.try_append(promise));
|
||||||
|
TRY_OR_THROW_OOM(vm, m_pending_resume_promises.try_append(promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Set the [[control thread state]] on the AudioContext to running.
|
||||||
|
set_control_state(Bindings::AudioContextState::Running);
|
||||||
|
|
||||||
|
// 7. Queue a control message to resume the AudioContext.
|
||||||
|
// FIXME: Implement control message queue to run following steps on the rendering thread
|
||||||
|
|
||||||
|
// FIXME: 7.1: Attempt to acquire system resources.
|
||||||
|
|
||||||
|
// 7.2: Set the [[rendering thread state]] on the AudioContext to running.
|
||||||
|
set_rendering_state(Bindings::AudioContextState::Running);
|
||||||
|
|
||||||
|
// 7.3: Start rendering the audio graph.
|
||||||
|
if (!start_rendering_audio_graph()) {
|
||||||
|
// 7.4: In case of failure, queue a media element task to execute the following steps:
|
||||||
|
queue_a_media_element_task([&realm, this]() {
|
||||||
|
// 7.4.1: Reject all promises from [[pending resume promises]] in order, then clear [[pending resume promises]].
|
||||||
|
for (auto const& promise : m_pending_resume_promises) {
|
||||||
|
WebIDL::reject_promise(realm, promise, JS::js_null());
|
||||||
|
}
|
||||||
|
m_pending_resume_promises.clear();
|
||||||
|
|
||||||
|
// FIXME: 7.4.2: Additionally, remove those promises from [[pending promises]].
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7.5: queue a media element task to execute the following steps:
|
||||||
|
queue_a_media_element_task([&realm, &promise, this]() {
|
||||||
|
// 7.5.1: Resolve all promises from [[pending resume promises]] in order.
|
||||||
|
for (auto const& promise : m_pending_resume_promises) {
|
||||||
|
*promise->resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7.5.2: Clear [[pending resume promises]].
|
||||||
|
m_pending_resume_promises.clear();
|
||||||
|
|
||||||
|
// FIXME: Additionally, remove those promises from [[pending promises]].
|
||||||
|
|
||||||
|
// 7.5.3: Resolve promise.
|
||||||
|
*promise->resolve();
|
||||||
|
|
||||||
|
// 7.5.4: If the state attribute of the AudioContext is not already "running":
|
||||||
|
if (state() != Bindings::AudioContextState::Running) {
|
||||||
|
// 7.5.4.1: Set the state attribute of the AudioContext to "running".
|
||||||
|
set_control_state(Bindings::AudioContextState::Running);
|
||||||
|
|
||||||
|
// 7.5.4.2: queue a media element task to fire an event named statechange at the AudioContext.
|
||||||
|
queue_a_media_element_task([&realm, this]() {
|
||||||
|
this->dispatch_event(DOM::Event::create(realm, HTML::EventNames::statechange).release_value_but_fixme_should_propagate_errors());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 8. Return promise.
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#dom-audiocontext-suspend
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> AudioContext::suspend()
|
||||||
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
auto& vm = realm.vm();
|
||||||
|
|
||||||
|
// FIXME: 1. If this's relevant global object's associated Document is not fully active then return a promise rejected with "InvalidStateError" DOMException.
|
||||||
|
|
||||||
|
// 2. Let promise be a new Promise.
|
||||||
|
auto promise = WebIDL::create_promise(realm);
|
||||||
|
|
||||||
|
// 3. If the [[control thread state]] on the AudioContext is closed reject the promise with InvalidStateError, abort these steps, returning promise.
|
||||||
|
if (state() == Bindings::AudioContextState::Closed) {
|
||||||
|
WebIDL::reject_promise(realm, promise, WebIDL::InvalidStateError::create(realm, "Audio context is already closed."));
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Append promise to [[pending promises]].
|
||||||
|
TRY_OR_THROW_OOM(vm, m_pending_promises.try_append(promise));
|
||||||
|
|
||||||
|
// 5. Set [[suspended by user]] to true.
|
||||||
|
m_suspended_by_user = true;
|
||||||
|
|
||||||
|
// 6. Set the [[control thread state]] on the AudioContext to suspended.
|
||||||
|
set_control_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// 7. Queue a control message to suspend the AudioContext.
|
||||||
|
// FIXME: Implement control message queue to run following steps on the rendering thread
|
||||||
|
|
||||||
|
// FIXME: 7.1: Attempt to release system resources.
|
||||||
|
|
||||||
|
// 7.2: Set the [[rendering thread state]] on the AudioContext to suspended.
|
||||||
|
set_rendering_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// 7.3: queue a media element task to execute the following steps:
|
||||||
|
queue_a_media_element_task([&realm, &promise, this]() {
|
||||||
|
// 7.3.1: Resolve promise.
|
||||||
|
*promise->resolve();
|
||||||
|
|
||||||
|
// 7.3.2: If the state attribute of the AudioContext is not already "suspended":
|
||||||
|
if (state() != Bindings::AudioContextState::Suspended) {
|
||||||
|
// 7.3.2.1: Set the state attribute of the AudioContext to "suspended".
|
||||||
|
set_control_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// 7.3.2.2: queue a media element task to fire an event named statechange at the AudioContext.
|
||||||
|
queue_a_media_element_task([&realm, this]() {
|
||||||
|
this->dispatch_event(DOM::Event::create(realm, HTML::EventNames::statechange).release_value_but_fixme_should_propagate_errors());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 8. Return promise.
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#dom-audiocontext-close
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> AudioContext::close()
|
||||||
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
|
// FIXME: 1. If this's relevant global object's associated Document is not fully active then return a promise rejected with "InvalidStateError" DOMException.
|
||||||
|
|
||||||
|
// 2. Let promise be a new Promise.
|
||||||
|
auto promise = WebIDL::create_promise(realm);
|
||||||
|
|
||||||
|
// 3. If the [[control thread state]] flag on the AudioContext is closed reject the promise with InvalidStateError, abort these steps, returning promise.
|
||||||
|
if (state() == Bindings::AudioContextState::Closed) {
|
||||||
|
WebIDL::reject_promise(realm, promise, WebIDL::InvalidStateError::create(realm, "Audio context is already closed."));
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Set the [[control thread state]] flag on the AudioContext to closed.
|
||||||
|
set_control_state(Bindings::AudioContextState::Closed);
|
||||||
|
|
||||||
|
// 5. Queue a control message to close the AudioContext.
|
||||||
|
// FIXME: Implement control message queue to run following steps on the rendering thread
|
||||||
|
|
||||||
|
// FIXME: 5.1: Attempt to release system resources.
|
||||||
|
|
||||||
|
// 5.2: Set the [[rendering thread state]] to "suspended".
|
||||||
|
set_rendering_state(Bindings::AudioContextState::Suspended);
|
||||||
|
|
||||||
|
// FIXME: 5.3: If this control message is being run in a reaction to the document being unloaded, abort this algorithm.
|
||||||
|
|
||||||
|
// 5.4: queue a media element task to execute the following steps:
|
||||||
|
queue_a_media_element_task([&realm, &promise, this]() {
|
||||||
|
// 5.4.1: Resolve promise.
|
||||||
|
*promise->resolve();
|
||||||
|
|
||||||
|
// 5.4.2: If the state attribute of the AudioContext is not already "closed":
|
||||||
|
if (state() != Bindings::AudioContextState::Closed) {
|
||||||
|
// 5.4.2.1: Set the state attribute of the AudioContext to "closed".
|
||||||
|
set_control_state(Bindings::AudioContextState::Closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.4.2.2: queue a media element task to fire an event named statechange at the AudioContext.
|
||||||
|
// FIXME: Attempting to queue another task in here causes an assertion fail at Vector.h:148
|
||||||
|
this->dispatch_event(DOM::Event::create(realm, HTML::EventNames::statechange).release_value_but_fixme_should_propagate_errors());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Return promise
|
||||||
|
return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioContext::queue_a_media_element_task(JS::SafeFunction<void()> steps)
|
||||||
|
{
|
||||||
|
auto task = HTML::Task::create(m_media_element_event_task_source.source, HTML::current_settings_object().responsible_document(), move(steps));
|
||||||
|
HTML::main_thread_event_loop().task_queue().add(move(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Actually implement the rendering thread
|
||||||
|
bool AudioContext::start_rendering_audio_graph()
|
||||||
|
{
|
||||||
|
bool render_result = true;
|
||||||
|
return render_result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,55 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/AudioContextPrototype.h>
|
||||||
|
#include <LibWeb/HighResolutionTime/DOMHighResTimeStamp.h>
|
||||||
#include <LibWeb/WebAudio/BaseAudioContext.h>
|
#include <LibWeb/WebAudio/BaseAudioContext.h>
|
||||||
|
|
||||||
namespace Web::WebAudio {
|
namespace Web::WebAudio {
|
||||||
|
|
||||||
|
struct AudioContextOptions {
|
||||||
|
Bindings::AudioContextLatencyCategory latency_hint = Bindings::AudioContextLatencyCategory::Interactive;
|
||||||
|
Optional<float> sample_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioTimestamp {
|
||||||
|
double context_time { 0 };
|
||||||
|
double performance_time { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#AudioContext
|
// https://webaudio.github.io/web-audio-api/#AudioContext
|
||||||
class AudioContext final : public BaseAudioContext {
|
class AudioContext final : public BaseAudioContext {
|
||||||
WEB_PLATFORM_OBJECT(AudioContext, BaseAudioContext);
|
WEB_PLATFORM_OBJECT(AudioContext, BaseAudioContext);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioContext>> construct_impl(JS::Realm&);
|
static WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioContext>> construct_impl(JS::Realm&, AudioContextOptions const& context_options = {});
|
||||||
|
|
||||||
virtual ~AudioContext() override;
|
virtual ~AudioContext() override;
|
||||||
|
|
||||||
|
double base_latency() const { return m_base_latency; };
|
||||||
|
double output_latency() const { return m_output_latency; };
|
||||||
|
AudioTimestamp get_output_timestamp();
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> resume();
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> suspend();
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> close();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit AudioContext(JS::Realm&);
|
explicit AudioContext(JS::Realm&, AudioContextOptions const& context_options);
|
||||||
|
|
||||||
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||||
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
|
||||||
|
double m_base_latency { 0 };
|
||||||
|
double m_output_latency { 0 };
|
||||||
|
|
||||||
|
bool m_allowed_to_start = true;
|
||||||
|
Vector<JS::NonnullGCPtr<WebIDL::Promise>> m_pending_promises;
|
||||||
|
Vector<JS::NonnullGCPtr<WebIDL::Promise>> m_pending_resume_promises;
|
||||||
|
bool m_suspended_by_user = false;
|
||||||
|
HTML::UniqueTaskSource m_media_element_event_task_source {};
|
||||||
|
|
||||||
|
void queue_a_media_element_task(JS::SafeFunction<void()> steps);
|
||||||
|
bool start_rendering_audio_graph();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,31 @@
|
||||||
#import <WebAudio/BaseAudioContext.idl>
|
#import <WebAudio/BaseAudioContext.idl>
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#enumdef-audiocontextlatencycategory
|
||||||
|
enum AudioContextLatencyCategory { "balanced", "interactive", "playback" };
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#AudioContext
|
// https://webaudio.github.io/web-audio-api/#AudioContext
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
interface AudioContext : BaseAudioContext {
|
interface AudioContext : BaseAudioContext {
|
||||||
// FIXME: Should be constructor (optional AudioContextOptions contextOptions = {});
|
constructor(optional AudioContextOptions contextOptions = {});
|
||||||
constructor();
|
readonly attribute double baseLatency;
|
||||||
|
readonly attribute double outputLatency;
|
||||||
|
AudioTimestamp getOutputTimestamp ();
|
||||||
|
Promise<undefined> resume ();
|
||||||
|
Promise<undefined> suspend ();
|
||||||
|
Promise<undefined> close ();
|
||||||
|
// FIXME: MediaElementAudioSourceNode createMediaElementSource (HTMLMediaElement mediaElement);
|
||||||
|
// FIXME: MediaStreamAudioSourceNode createMediaStreamSource (MediaStream mediaStream);
|
||||||
|
// FIXME: MediaStreamTrackAudioSourceNode createMediaStreamTrackSource (MediaStreamTrack mediaStreamTrack);
|
||||||
|
// FIXME: MediaStreamAudioDestinationNode createMediaStreamDestination ();
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary AudioContextOptions {
|
||||||
|
AudioContextLatencyCategory latencyHint = "interactive";
|
||||||
|
float sampleRate;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary AudioTimestamp {
|
||||||
|
double contextTime;
|
||||||
|
// FIXME: Should be DOMHighResTimeStamp, but DOMHighResTimeStamp doesn't get parsed as a double during codegen
|
||||||
|
double performanceTime;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibWeb/Bindings/Intrinsics.h>
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
|
#include <LibWeb/HTML/EventNames.h>
|
||||||
#include <LibWeb/WebAudio/BaseAudioContext.h>
|
#include <LibWeb/WebAudio/BaseAudioContext.h>
|
||||||
|
|
||||||
namespace Web::WebAudio {
|
namespace Web::WebAudio {
|
||||||
|
@ -24,4 +25,14 @@ JS::ThrowCompletionOr<void> BaseAudioContext::initialize(JS::Realm& realm)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseAudioContext::set_onstatechange(WebIDL::CallbackType* event_handler)
|
||||||
|
{
|
||||||
|
set_event_handler_attribute(HTML::EventNames::statechange, event_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebIDL::CallbackType* BaseAudioContext::onstatechange()
|
||||||
|
{
|
||||||
|
return event_handler_attribute(HTML::EventNames::statechange);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/BaseAudioContextPrototype.h>
|
||||||
#include <LibWeb/DOM/EventTarget.h>
|
#include <LibWeb/DOM/EventTarget.h>
|
||||||
|
|
||||||
namespace Web::WebAudio {
|
namespace Web::WebAudio {
|
||||||
|
@ -17,10 +18,28 @@ class BaseAudioContext : public DOM::EventTarget {
|
||||||
public:
|
public:
|
||||||
virtual ~BaseAudioContext() override;
|
virtual ~BaseAudioContext() override;
|
||||||
|
|
||||||
|
float sample_rate() const { return m_sample_rate; };
|
||||||
|
double current_time() const { return m_current_time; };
|
||||||
|
Bindings::AudioContextState state() const { return m_control_thread_state; };
|
||||||
|
|
||||||
|
void set_onstatechange(WebIDL::CallbackType*);
|
||||||
|
WebIDL::CallbackType* onstatechange();
|
||||||
|
|
||||||
|
void set_sample_rate(float sample_rate) { m_sample_rate = sample_rate; };
|
||||||
|
void set_control_state(Bindings::AudioContextState state) { m_control_thread_state = state; };
|
||||||
|
void set_rendering_state(Bindings::AudioContextState state) { m_rendering_thread_state = state; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit BaseAudioContext(JS::Realm&);
|
explicit BaseAudioContext(JS::Realm&);
|
||||||
|
|
||||||
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_sample_rate { 0 };
|
||||||
|
double m_current_time { 0 };
|
||||||
|
|
||||||
|
Bindings::AudioContextState m_control_thread_state = Bindings::AudioContextState::Suspended;
|
||||||
|
Bindings::AudioContextState m_rendering_thread_state = Bindings::AudioContextState::Suspended;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,43 @@
|
||||||
#import <DOM/EventTarget.idl>
|
#import <DOM/EventTarget.idl>
|
||||||
|
#import <DOM/EventHandler.idl>
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio/#enumdef-audiocontextstate
|
||||||
|
enum AudioContextState { "suspended", "running", "closed" };
|
||||||
|
|
||||||
|
// FIXME: callback DecodeErrorCallback = undefined (DOMException error);
|
||||||
|
|
||||||
|
// FIXME: callback DecodeSuccessCallback = undefined (AudioBuffer decodedData);
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#BaseAudioContext
|
// https://webaudio.github.io/web-audio-api/#BaseAudioContext
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
interface BaseAudioContext : EventTarget {
|
interface BaseAudioContext : EventTarget {
|
||||||
|
// FIXME: readonly attribute AudioDestinationNode destination;
|
||||||
|
readonly attribute float sampleRate;
|
||||||
|
readonly attribute double currentTime;
|
||||||
|
// FIXME: readonly attribute AudioListener listener;
|
||||||
|
readonly attribute AudioContextState state;
|
||||||
|
// FIXME: [SameObject, SecureContext]
|
||||||
|
// readonly attribute AudioWorklet audioWorklet;
|
||||||
|
attribute EventHandler onstatechange;
|
||||||
|
|
||||||
|
// FIXME: AnalyserNode createAnalyser ();
|
||||||
|
// FIXME: BiquadFilterNode createBiquadFilter ();
|
||||||
|
// FIXME: AudioBuffer createBuffer (unsigned long numberOfChannels, unsigned long length, float sampleRate);
|
||||||
|
// FIXME: AudioBufferSourceNode createBufferSource ();
|
||||||
|
// FIXME: ChannelMergerNode createChannelMerger (optional unsigned long numberOfInputs = 6);
|
||||||
|
// FIXME: ChannelSplitterNode createChannelSplitter (optional unsigned long numberOfOutputs = 6);
|
||||||
|
// FIXME: ConstantSourceNode createConstantSource ();
|
||||||
|
// FIXME: ConvolverNode createConvolver ();
|
||||||
|
// FIXME: DelayNode createDelay (optional double maxDelayTime = 1.0);
|
||||||
|
// FIXME: DynamicsCompressorNode createDynamicsCompressor ();
|
||||||
|
// FIXME: GainNode createGain ();
|
||||||
|
// FIXME: IIRFilterNode createIIRFilter (sequence<double> feedforward, sequence<double> feedback);
|
||||||
|
// FIXME: OscillatorNode createOscillator ();
|
||||||
|
// FIXME: PannerNode createPanner ();
|
||||||
|
// FIXME: PeriodicWave createPeriodicWave (sequence<float> real, sequence<float> imag, optional PeriodicWaveConstraints constraints = {});
|
||||||
|
// FIXME: ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0, optional unsigned long numberOfInputChannels = 2, optional unsigned long numberOfOutputChannels = 2);
|
||||||
|
// FIXME: StereoPannerNode createStereoPanner ();
|
||||||
|
// FIXME: WaveShaperNode createWaveShaper ();
|
||||||
|
|
||||||
|
// FIXME: Promise<AudioBuffer> decodeAudioData (ArrayBuffer audioData, optional DecodeSuccessCallback? successCallback, optional DecodeErrorCallback? errorCallback);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue