mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +00:00
LibWeb: Initialize OfflineAudioContext
with correct defaults
This commit is contained in:
parent
2cac0dc20c
commit
27dbe49f00
Notes:
github-actions[bot]
2025-01-08 11:25:09 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/27dbe49f002 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3175 Reviewed-by: https://github.com/AtkinsSJ ✅
11 changed files with 288 additions and 22 deletions
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/WebAudio/AudioContext.h>
|
||||
#include <LibWeb/WebAudio/AudioDestinationNode.h>
|
||||
#include <LibWeb/WebIDL/Promise.h>
|
||||
|
||||
namespace Web::WebAudio {
|
||||
|
@ -20,7 +21,9 @@ GC_DEFINE_ALLOCATOR(AudioContext);
|
|||
// https://webaudio.github.io/web-audio-api/#dom-audiocontext-audiocontext
|
||||
WebIDL::ExceptionOr<GC::Ref<AudioContext>> AudioContext::construct_impl(JS::Realm& realm, AudioContextOptions const& context_options)
|
||||
{
|
||||
return realm.create<AudioContext>(realm, context_options);
|
||||
auto context = realm.create<AudioContext>(realm, context_options);
|
||||
context->m_destination = TRY(AudioDestinationNode::construct_impl(realm, context));
|
||||
return context;
|
||||
}
|
||||
|
||||
AudioContext::AudioContext(JS::Realm& realm, AudioContextOptions const& context_options)
|
||||
|
|
|
@ -17,8 +17,8 @@ namespace Web::WebAudio {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(AudioDestinationNode);
|
||||
|
||||
AudioDestinationNode::AudioDestinationNode(JS::Realm& realm, GC::Ref<BaseAudioContext> context)
|
||||
: AudioNode(realm, context)
|
||||
AudioDestinationNode::AudioDestinationNode(JS::Realm& realm, GC::Ref<BaseAudioContext> context, WebIDL::UnsignedLong channel_count)
|
||||
: AudioNode(realm, context, channel_count)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,21 @@ WebIDL::UnsignedLong AudioDestinationNode::max_channel_count()
|
|||
return 2;
|
||||
}
|
||||
|
||||
GC::Ref<AudioDestinationNode> AudioDestinationNode::construct_impl(JS::Realm& realm, GC::Ref<BaseAudioContext> context)
|
||||
WebIDL::ExceptionOr<GC::Ref<AudioDestinationNode>> AudioDestinationNode::construct_impl(JS::Realm& realm, GC::Ref<BaseAudioContext> context, WebIDL::UnsignedLong channel_count)
|
||||
{
|
||||
return realm.create<AudioDestinationNode>(realm, context);
|
||||
auto node = realm.create<AudioDestinationNode>(realm, context, channel_count);
|
||||
|
||||
// Default options for channel count and interpretation
|
||||
// https://webaudio.github.io/web-audio-api/#AudioDestinationNode
|
||||
AudioNodeDefaultOptions default_options;
|
||||
default_options.channel_count_mode = Bindings::ChannelCountMode::Explicit;
|
||||
default_options.channel_interpretation = Bindings::ChannelInterpretation::Speakers;
|
||||
default_options.channel_count = channel_count;
|
||||
// FIXME: Set tail-time to no
|
||||
|
||||
TRY(node->initialize_audio_node_options({}, default_options));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void AudioDestinationNode::initialize(JS::Realm& realm)
|
||||
|
@ -50,6 +62,9 @@ void AudioDestinationNode::visit_edges(Cell::Visitor& visitor)
|
|||
// https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount
|
||||
WebIDL::ExceptionOr<void> AudioDestinationNode::set_channel_count(WebIDL::UnsignedLong channel_count)
|
||||
{
|
||||
if (channel_count == this->channel_count())
|
||||
return {};
|
||||
|
||||
// The behavior depends on whether the destination node is the destination of an AudioContext
|
||||
// or OfflineAudioContext:
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@ public:
|
|||
WebIDL::UnsignedLong number_of_outputs() override { return 1; }
|
||||
WebIDL::ExceptionOr<void> set_channel_count(WebIDL::UnsignedLong) override;
|
||||
|
||||
static GC::Ref<AudioDestinationNode> construct_impl(JS::Realm&, GC::Ref<BaseAudioContext>);
|
||||
static WebIDL::ExceptionOr<GC::Ref<AudioDestinationNode>> construct_impl(JS::Realm& realm, GC::Ref<BaseAudioContext> context, WebIDL::UnsignedLong channel_count = 2);
|
||||
|
||||
protected:
|
||||
AudioDestinationNode(JS::Realm&, GC::Ref<BaseAudioContext>);
|
||||
AudioDestinationNode(JS::Realm&, GC::Ref<BaseAudioContext>, WebIDL::UnsignedLong channel_count);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
|
@ -12,9 +12,10 @@ namespace Web::WebAudio {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(AudioNode);
|
||||
|
||||
AudioNode::AudioNode(JS::Realm& realm, GC::Ref<BaseAudioContext> context)
|
||||
AudioNode::AudioNode(JS::Realm& realm, GC::Ref<BaseAudioContext> context, WebIDL::UnsignedLong channel_count)
|
||||
: DOM::EventTarget(realm)
|
||||
, m_context(context)
|
||||
, m_channel_count(channel_count)
|
||||
|
||||
{
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public:
|
|||
WebIDL::ExceptionOr<void> initialize_audio_node_options(AudioNodeOptions const& given_options, AudioNodeDefaultOptions const& default_options);
|
||||
|
||||
protected:
|
||||
AudioNode(JS::Realm&, GC::Ref<BaseAudioContext>);
|
||||
AudioNode(JS::Realm&, GC::Ref<BaseAudioContext>, WebIDL::UnsignedLong channel_count = 2);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
|
@ -28,7 +28,6 @@ namespace Web::WebAudio {
|
|||
|
||||
BaseAudioContext::BaseAudioContext(JS::Realm& realm, float sample_rate)
|
||||
: DOM::EventTarget(realm)
|
||||
, m_destination(AudioDestinationNode::construct_impl(realm, *this))
|
||||
, m_sample_rate(sample_rate)
|
||||
, m_listener(AudioListener::create(realm))
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
static constexpr float MIN_SAMPLE_RATE { 8000 };
|
||||
static constexpr float MAX_SAMPLE_RATE { 192000 };
|
||||
|
||||
GC::Ref<AudioDestinationNode> destination() const { return m_destination; }
|
||||
GC::Ref<AudioDestinationNode> destination() const { return *m_destination; }
|
||||
float sample_rate() const { return m_sample_rate; }
|
||||
double current_time() const { return m_current_time; }
|
||||
GC::Ref<AudioListener> listener() const { return m_listener; }
|
||||
|
@ -80,7 +80,7 @@ protected:
|
|||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
GC::Ref<AudioDestinationNode> m_destination;
|
||||
GC::Ptr<AudioDestinationNode> m_destination;
|
||||
Vector<GC::Ref<WebIDL::Promise>> m_pending_promises;
|
||||
|
||||
private:
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/WebAudio/AudioDestinationNode.h>
|
||||
#include <LibWeb/WebAudio/OfflineAudioContext.h>
|
||||
|
||||
namespace Web::WebAudio {
|
||||
|
@ -26,7 +27,9 @@ WebIDL::ExceptionOr<GC::Ref<OfflineAudioContext>> OfflineAudioContext::construct
|
|||
// A NotSupportedError exception MUST be thrown if any of the arguments is negative, zero, or outside its nominal range.
|
||||
TRY(verify_audio_options_inside_nominal_range(realm, number_of_channels, length, sample_rate));
|
||||
|
||||
return realm.create<OfflineAudioContext>(realm, number_of_channels, length, sample_rate);
|
||||
auto context = realm.create<OfflineAudioContext>(realm, length, sample_rate);
|
||||
context->m_destination = TRY(AudioDestinationNode::construct_impl(realm, context, number_of_channels));
|
||||
return context;
|
||||
}
|
||||
|
||||
OfflineAudioContext::~OfflineAudioContext() = default;
|
||||
|
@ -67,16 +70,10 @@ void OfflineAudioContext::set_oncomplete(GC::Ptr<WebIDL::CallbackType> value)
|
|||
set_event_handler_attribute(HTML::EventNames::complete, value);
|
||||
}
|
||||
|
||||
OfflineAudioContext::OfflineAudioContext(JS::Realm& realm, OfflineAudioContextOptions const&)
|
||||
: BaseAudioContext(realm)
|
||||
{
|
||||
}
|
||||
|
||||
OfflineAudioContext::OfflineAudioContext(JS::Realm& realm, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate)
|
||||
OfflineAudioContext::OfflineAudioContext(JS::Realm& realm, WebIDL::UnsignedLong length, float sample_rate)
|
||||
: BaseAudioContext(realm, sample_rate)
|
||||
, m_length(length)
|
||||
{
|
||||
(void)number_of_channels;
|
||||
}
|
||||
|
||||
void OfflineAudioContext::initialize(JS::Realm& realm)
|
||||
|
|
|
@ -42,8 +42,7 @@ public:
|
|||
void set_oncomplete(GC::Ptr<WebIDL::CallbackType>);
|
||||
|
||||
private:
|
||||
OfflineAudioContext(JS::Realm&, OfflineAudioContextOptions const&);
|
||||
OfflineAudioContext(JS::Realm&, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate);
|
||||
OfflineAudioContext(JS::Realm&, WebIDL::UnsignedLong length, float sample_rate);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 44 tests
|
||||
|
||||
44 Pass
|
||||
Pass # AUDIT TASK RUNNER STARTED.
|
||||
Pass Executing "basic"
|
||||
Pass Executing "options-1"
|
||||
Pass Executing "options-2"
|
||||
Pass Executing "options-3"
|
||||
Pass Audit report
|
||||
Pass > [basic] Old-style constructor
|
||||
Pass new OfflineAudioContext(3) threw TypeError: "Not an object of type OfflineAudioContextOptions".
|
||||
Pass new OfflineAudioContext(3, 42) threw TypeError: "Overload resolution failed".
|
||||
Pass context = new OfflineAudioContext(3, 42, 12345) did not throw an exception.
|
||||
Pass context.length is equal to 42.
|
||||
Pass context.sampleRate is equal to 12345.
|
||||
Pass context.destination.channelCount is equal to 3.
|
||||
Pass context.destination.channelCountMode is equal to explicit.
|
||||
Pass context.destination.channelInterpretation is equal to speakers.
|
||||
Pass < [basic] All assertions passed. (total 8 assertions)
|
||||
Pass > [options-1] Required options
|
||||
Pass new OfflineAudioContext() threw TypeError: "Overload resolution failed".
|
||||
Pass new OfflineAudioContext({}) threw TypeError: "Required property length is missing or undefined".
|
||||
Pass new OfflineAudioContext({"length":42}) threw TypeError: "Required property sampleRate is missing or undefined".
|
||||
Pass new OfflineAudioContext({"sampleRate":12345}) threw TypeError: "Required property length is missing or undefined".
|
||||
Pass c2 = new OfflineAudioContext({"length":42,"sampleRate":12345}) did not throw an exception.
|
||||
Pass c2.destination.channelCount is equal to 1.
|
||||
Pass c2.length is equal to 42.
|
||||
Pass c2.sampleRate is equal to 12345.
|
||||
Pass c2.destination.channelCountMode is equal to explicit.
|
||||
Pass c2.destination.channelInterpretation is equal to speakers.
|
||||
Pass < [options-1] All assertions passed. (total 10 assertions)
|
||||
Pass > [options-2] Invalid options
|
||||
Pass new OfflineAudioContext({"length":42,"sampleRate":8000,"numberOfChannels":33}) threw NotSupportedError: "Number of channels is greater than allowed range".
|
||||
Pass new OfflineAudioContext({"length":0,"sampleRate":8000}) threw NotSupportedError: "Length of buffer must be at least 1".
|
||||
Pass new OfflineAudioContext({"length":1,"sampleRate":1}) threw NotSupportedError: "Sample rate is outside of allowed range".
|
||||
Pass < [options-2] All assertions passed. (total 3 assertions)
|
||||
Pass > [options-3] Valid options
|
||||
Pass c = new OfflineAudioContext{"length":1,"sampleRate":8000}) did not throw an exception.
|
||||
Pass c.length is equal to 1.
|
||||
Pass c.sampleRate is equal to 8000.
|
||||
Pass c.destination.channelCount is equal to 1.
|
||||
Pass c.destination.channelCountMode is equal to explicit.
|
||||
Pass c.destination.channelCountMode is equal to speakers.
|
||||
Pass c = new OfflineAudioContext{"length":1,"sampleRate":8000,"numberOfChannels":7}) did not throw an exception.
|
||||
Pass c.destination.channelCount is equal to 7.
|
||||
Pass < [options-3] All assertions passed. (total 8 assertions)
|
||||
Pass # AUDIT TASK RUNNER FINISHED: 4 tasks ran successfully.
|
|
@ -0,0 +1,203 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Constructor: OfflineAudioContext</title>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../webaudio/resources/audit.js"></script>
|
||||
<script src="../../../webaudio/resources/audit-util.js"></script>
|
||||
<script src="../../../webaudio/resources/audionodeoptions.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
let audit = Audit.createTaskRunner();
|
||||
|
||||
// Just a simple test of the 3-arg constructor; This should be
|
||||
// well-covered by other layout tests that use the 3-arg constructor.
|
||||
audit.define(
|
||||
{label: 'basic', description: 'Old-style constructor'},
|
||||
(task, should) => {
|
||||
let context;
|
||||
|
||||
// First and only arg should be a dictionary.
|
||||
should(() => {
|
||||
new OfflineAudioContext(3);
|
||||
}, 'new OfflineAudioContext(3)').throw(TypeError);
|
||||
|
||||
// Constructor needs 1 or 3 args, so 2 should throw.
|
||||
should(() => {
|
||||
new OfflineAudioContext(3, 42);
|
||||
}, 'new OfflineAudioContext(3, 42)').throw(TypeError);
|
||||
|
||||
// Valid constructor
|
||||
should(() => {
|
||||
context = new OfflineAudioContext(3, 42, 12345);
|
||||
}, 'context = new OfflineAudioContext(3, 42, 12345)').notThrow();
|
||||
|
||||
// Verify that the context was constructed correctly.
|
||||
should(context.length, 'context.length').beEqualTo(42);
|
||||
should(context.sampleRate, 'context.sampleRate').beEqualTo(12345);
|
||||
should(
|
||||
context.destination.channelCount,
|
||||
'context.destination.channelCount')
|
||||
.beEqualTo(3);
|
||||
should(
|
||||
context.destination.channelCountMode,
|
||||
'context.destination.channelCountMode')
|
||||
.beEqualTo('explicit');
|
||||
should(
|
||||
context.destination.channelInterpretation,
|
||||
'context.destination.channelInterpretation')
|
||||
.beEqualTo('speakers');
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Test constructor throws an error if the required members of the
|
||||
// dictionary are not given.
|
||||
audit.define(
|
||||
{label: 'options-1', description: 'Required options'},
|
||||
(task, should) => {
|
||||
let context2;
|
||||
|
||||
// No args should throw
|
||||
should(() => {
|
||||
new OfflineAudioContext();
|
||||
}, 'new OfflineAudioContext()').throw(TypeError);
|
||||
|
||||
// Empty OfflineAudioContextOptions should throw
|
||||
should(() => {
|
||||
new OfflineAudioContext({});
|
||||
}, 'new OfflineAudioContext({})').throw(TypeError);
|
||||
|
||||
let options = {length: 42};
|
||||
// sampleRate is required.
|
||||
should(
|
||||
() => {
|
||||
new OfflineAudioContext(options);
|
||||
},
|
||||
'new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.throw(TypeError);
|
||||
|
||||
options = {sampleRate: 12345};
|
||||
// length is required.
|
||||
should(
|
||||
() => {
|
||||
new OfflineAudioContext(options);
|
||||
},
|
||||
'new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.throw(TypeError);
|
||||
|
||||
// Valid constructor. Verify that the resulting context has the
|
||||
// correct values.
|
||||
options = {length: 42, sampleRate: 12345};
|
||||
should(
|
||||
() => {
|
||||
context2 = new OfflineAudioContext(options);
|
||||
},
|
||||
'c2 = new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.notThrow();
|
||||
should(
|
||||
context2.destination.channelCount,
|
||||
'c2.destination.channelCount')
|
||||
.beEqualTo(1);
|
||||
should(context2.length, 'c2.length').beEqualTo(options.length);
|
||||
should(context2.sampleRate, 'c2.sampleRate')
|
||||
.beEqualTo(options.sampleRate);
|
||||
should(
|
||||
context2.destination.channelCountMode,
|
||||
'c2.destination.channelCountMode')
|
||||
.beEqualTo('explicit');
|
||||
should(
|
||||
context2.destination.channelInterpretation,
|
||||
'c2.destination.channelInterpretation')
|
||||
.beEqualTo('speakers');
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Constructor should throw errors for invalid values specified by
|
||||
// OfflineAudioContextOptions.
|
||||
audit.define(
|
||||
{label: 'options-2', description: 'Invalid options'},
|
||||
(task, should) => {
|
||||
let options = {length: 42, sampleRate: 8000, numberOfChannels: 33};
|
||||
|
||||
// channelCount too large.
|
||||
should(
|
||||
() => {
|
||||
new OfflineAudioContext(options);
|
||||
},
|
||||
'new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.throw(DOMException, 'NotSupportedError');
|
||||
|
||||
// length cannot be 0
|
||||
options = {length: 0, sampleRate: 8000};
|
||||
should(
|
||||
() => {
|
||||
new OfflineAudioContext(options);
|
||||
},
|
||||
'new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.throw(DOMException, 'NotSupportedError');
|
||||
|
||||
// sampleRate outside valid range
|
||||
options = {length: 1, sampleRate: 1};
|
||||
should(
|
||||
() => {
|
||||
new OfflineAudioContext(options);
|
||||
},
|
||||
'new OfflineAudioContext(' + JSON.stringify(options) + ')')
|
||||
.throw(DOMException, 'NotSupportedError');
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
audit.define(
|
||||
{label: 'options-3', description: 'Valid options'},
|
||||
(task, should) => {
|
||||
let context;
|
||||
let options = {
|
||||
length: 1,
|
||||
sampleRate: 8000,
|
||||
};
|
||||
|
||||
// Verify context with valid constructor has the correct values.
|
||||
should(
|
||||
() => {
|
||||
context = new OfflineAudioContext(options);
|
||||
},
|
||||
'c = new OfflineAudioContext' + JSON.stringify(options) + ')')
|
||||
.notThrow();
|
||||
should(context.length, 'c.length').beEqualTo(options.length);
|
||||
should(context.sampleRate, 'c.sampleRate')
|
||||
.beEqualTo(options.sampleRate);
|
||||
should(
|
||||
context.destination.channelCount, 'c.destination.channelCount')
|
||||
.beEqualTo(1);
|
||||
should(
|
||||
context.destination.channelCountMode,
|
||||
'c.destination.channelCountMode')
|
||||
.beEqualTo('explicit');
|
||||
should(
|
||||
context.destination.channelInterpretation,
|
||||
'c.destination.channelCountMode')
|
||||
.beEqualTo('speakers');
|
||||
|
||||
options.numberOfChannels = 7;
|
||||
should(
|
||||
() => {
|
||||
context = new OfflineAudioContext(options);
|
||||
},
|
||||
'c = new OfflineAudioContext' + JSON.stringify(options) + ')')
|
||||
.notThrow();
|
||||
should(
|
||||
context.destination.channelCount, 'c.destination.channelCount')
|
||||
.beEqualTo(options.numberOfChannels);
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
audit.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue