ladybird/Userland/Libraries/LibWeb/HTML/AudioTrack.cpp
Timothy Flynn 1c4dd0caad Ladybird+LibWeb+WebConent: Drive audio in Ladybird off the main thread
The main thread in the WebContent process is often busy with layout and
running JavaScript. This can cause audio to sound jittery and crack. To
avoid this behavior, we now drive audio on a secondary thread.

Note: Browser on Serenity uses AudioServer, the connection for which is
already handled on a secondary thread within LibAudio. So this only
applies to Lagom.

Rather than using LibThreading, our hands are tied to QThread for now.
Internally, the Qt media objects use a QTimer, which is forbidden from
running on a thread that is not a QThread (the debug console is spammed
with messages pointing this out). Ideally, in the future AudioServer
will be able to run for non-Serenity platforms, and most of this can be
aligned with the Serenity implementation.
2023-06-21 06:14:15 +02:00

115 lines
3.5 KiB
C++

/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/IDAllocator.h>
#include <LibAudio/Loader.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/AudioTrackPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/AudioTrack.h>
#include <LibWeb/HTML/AudioTrackList.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/HTMLMediaElement.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Platform/AudioCodecPlugin.h>
namespace Web::HTML {
static IDAllocator s_audio_track_id_allocator;
AudioTrack::AudioTrack(JS::Realm& realm, JS::NonnullGCPtr<HTMLMediaElement> media_element, NonnullRefPtr<Audio::Loader> loader)
: PlatformObject(realm)
, m_media_element(media_element)
, m_audio_plugin(Platform::AudioCodecPlugin::create(move(loader)).release_value_but_fixme_should_propagate_errors())
{
m_audio_plugin->on_playback_position_updated = [this](auto position) {
if (auto* layout_node = m_media_element->layout_node())
layout_node->set_needs_display();
auto playback_position = static_cast<double>(position.to_milliseconds()) / 1000.0;
m_media_element->set_current_playback_position(playback_position);
};
}
AudioTrack::~AudioTrack()
{
auto id = m_id.to_number<int>();
VERIFY(id.has_value());
s_audio_track_id_allocator.deallocate(id.value());
}
JS::ThrowCompletionOr<void> AudioTrack::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::AudioTrackPrototype>(realm, "AudioTrack"));
auto id = s_audio_track_id_allocator.allocate();
m_id = TRY_OR_THROW_OOM(realm.vm(), String::number(id));
return {};
}
void AudioTrack::play(Badge<HTMLAudioElement>)
{
m_audio_plugin->resume_playback();
}
void AudioTrack::pause(Badge<HTMLAudioElement>)
{
m_audio_plugin->pause_playback();
}
Duration AudioTrack::duration()
{
return m_audio_plugin->duration();
}
void AudioTrack::seek(double position, MediaSeekMode seek_mode)
{
// FIXME: Implement seeking mode.
(void)seek_mode;
m_audio_plugin->seek(position);
}
void AudioTrack::update_volume()
{
m_audio_plugin->set_volume(m_media_element->effective_media_volume());
}
void AudioTrack::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_media_element);
visitor.visit(m_audio_track_list);
}
// https://html.spec.whatwg.org/multipage/media.html#dom-audiotrack-enabled
void AudioTrack::set_enabled(bool enabled)
{
// On setting, it must enable the track if the new value is true, and disable it otherwise. (If the track is no
// longer in an AudioTrackList object, then the track being enabled or disabled has no effect beyond changing the
// value of the attribute on the AudioTrack object.)
if (m_enabled == enabled)
return;
if (m_audio_track_list) {
// Whenever an audio track in an AudioTrackList that was disabled is enabled, and whenever one that was enabled
// is disabled, the user agent must queue a media element task given the media element to fire an event named
// change at the AudioTrackList object.
m_media_element->queue_a_media_element_task([this]() {
m_audio_track_list->dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change).release_value_but_fixme_should_propagate_errors());
});
}
m_enabled = enabled;
}
}