Everywhere: Remove AudioCodecPlugin and Qt Multimedia

These are no longer needed now that audio is played through
PlaybackManager.
This commit is contained in:
Zaggy1024 2025-10-07 12:07:59 -05:00 committed by Jelle Raaijmakers
parent d17e7fd921
commit 9f44fcbded
19 changed files with 16 additions and 874 deletions

View File

@ -7,4 +7,4 @@ dnf install -y git gh
# Ladybird dev dependencies
dnf install -y autoconf-archive automake ccache cmake curl google-noto-sans-mono-fonts liberation-sans-fonts \
libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib qt6-qtbase-devel \
qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static

View File

@ -24,7 +24,7 @@ install_llvm_key() {
### Install packages
apt update -y
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-multimedia-dev qt6-wayland ccache fonts-liberation2 zip unzip curl tar
apt install -y lsb-release git python3 autoconf autoconf-archive automake build-essential cmake libdrm-dev libgl1-mesa-dev libtool nasm ninja-build pkg-config qt6-base-dev qt6-tools-dev-tools qt6-wayland ccache fonts-liberation2 zip unzip curl tar
### Ensure new enough host compiler is available
VERSION="0.0.0"

View File

@ -9,9 +9,6 @@ We currently use gcc-14 and clang-20 in our CI pipeline. If these versions are n
CMake 3.25 or newer must be available in $PATH.
> [!NOTE]
> In all of the below lists of packages, the Qt6 multimedia package is not needed if your Linux system supports PulseAudio.
---
### Debian/Ubuntu:
@ -67,34 +64,34 @@ sudo apt update && sudo apt install g++-14 libstdc++-14-dev
#### Audio support:
- Recommendation: Install PulseAudio development package:
- Install PulseAudio development package:
```bash
sudo apt install libpulse-dev
```
- Alternative: Install Qt6's multimedia package:
```bash
sudo apt install qt6-multimedia-dev
```
### Arch Linux/Manjaro:
```
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-multimedia qt6-tools qt6-wayland ttf-liberation tar unzip zip
sudo pacman -S --needed autoconf-archive automake base-devel ccache cmake curl libgl nasm ninja qt6-base qt6-tools qt6-wayland ttf-liberation tar unzip zip
```
Optionally, install the PulseAudio headers for audio playback support:
```
sudo pacman -S libpulse
```
### Fedora or derivatives:
```
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qtmultimedia-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
sudo dnf install autoconf-archive automake ccache cmake curl git libdrm-devel liberation-sans-fonts libglvnd-devel libtool nasm ninja-build patchelf perl-FindBin perl-IPC-Cmd perl-lib perl-Time-Piece qt6-qtbase-devel qt6-qttools-devel qt6-qtwayland-devel tar unzip zip zlib-ng-compat-static
```
### openSUSE:
```
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
sudo zypper install autoconf-archive automake ccache cmake curl gcc14 gcc14-c++ git liberation-fonts libglvnd-devel libtool nasm ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel tar unzip zip
```
If one or more of the base repository packages are flagged as having an out-of-date version during the build process, you may need add the `devel:tools:building` repository. For example, on Leap 15.6, the `autoconf` package might be version 2.69, whereas the `gperf` package requires 2.70 to build.
@ -119,11 +116,10 @@ Nothing to do.
> sudo zypper install autoconf-2.72-80.d_t_b.1.noarch
```
It is currently recommended to install the `libpulse-devel` package to avoid runtime dynamic linking issues. If issues persist you may need to remove the `qt6-multimedia-devel` package to avoid linking issues.
It is necessary to install the `libpulse-devel` package to enable audio playback:
```
sudo zypper install libpulse-devel
sudo zypper remove qt6-multimedia-devel
```
The build process requires at least python3.7; openSUSE Leap only features Python 3.6 as default, so it is recommendable to install the package `python312` and create a virtual environment (venv) in this case.
@ -141,7 +137,7 @@ This virtual environment can be created once and reused in future shell sessions
```
sudo xbps-install -Su # (optional) ensure packages are up to date to avoid "Transaction aborted due to unresolved dependencies."
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-multimedia-devel qt6-tools-devel qt6-wayland-devel
sudo xbps-install -S git bash gcc python3 curl cmake zip unzip linux-headers make pkg-config autoconf automake autoconf-archive nasm MesaLib-devel ninja qt6-base-devel qt6-tools-devel qt6-wayland-devel
```
### NixOS or with Nix:
@ -209,7 +205,7 @@ Or, download a version of Gradle >= 8.0.0, and run the ``gradlew`` program in ``
### FreeBSD
```
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libdrm libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base qt6-multimedia unzip zip
pkg install autoconf-archive automake autoconf bash cmake curl gmake gn libdrm libtool libxcb libxkbcommon libX11 libXrender libXi nasm ninja patchelf pkgconf python3 qt6-base unzip zip
```
## Build steps

View File

@ -836,8 +836,6 @@ set(SOURCES
PerformanceTimeline/PerformanceObserverEntryList.cpp
PermissionsPolicy/AutoplayAllowlist.cpp
PixelUnits.cpp
Platform/AudioCodecPlugin.cpp
Platform/AudioCodecPluginAgnostic.cpp
Platform/EventLoopPlugin.cpp
Platform/EventLoopPluginSerenity.cpp
Platform/FontPlugin.cpp

View File

@ -988,7 +988,6 @@ class AutoplayAllowlist;
namespace Web::Platform {
class AudioCodecPlugin;
class Timer;
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Time.h>
#include <LibMedia/Audio/Loader.h>
#include <LibMedia/Audio/Sample.h>
#include <LibWeb/Platform/AudioCodecPlugin.h>
namespace Web::Platform {
static AudioCodecPlugin::AudioCodecPluginCreator s_creation_hook;
AudioCodecPlugin::AudioCodecPlugin() = default;
AudioCodecPlugin::~AudioCodecPlugin() = default;
void AudioCodecPlugin::install_creation_hook(AudioCodecPluginCreator creation_hook)
{
VERIFY(!s_creation_hook);
s_creation_hook = move(creation_hook);
}
ErrorOr<NonnullOwnPtr<AudioCodecPlugin>> AudioCodecPlugin::create(NonnullRefPtr<Audio::Loader> loader)
{
VERIFY(s_creation_hook);
return s_creation_hook(move(loader));
}
ErrorOr<FixedArray<Audio::Sample>> AudioCodecPlugin::read_samples_from_loader(Audio::Loader& loader, size_t samples_to_load)
{
auto buffer_or_error = loader.get_more_samples(samples_to_load);
if (buffer_or_error.is_error()) {
dbgln("Error while loading samples: {}", buffer_or_error.error());
return Error::from_string_literal("Error while loading samples");
}
return buffer_or_error.release_value();
}
AK::Duration AudioCodecPlugin::set_loader_position(Audio::Loader& loader, double position, AK::Duration duration)
{
if (loader.total_samples() == 0)
return current_loader_position(loader);
auto duration_value = static_cast<double>(duration.to_milliseconds()) / 1000.0;
position = position / duration_value * static_cast<double>(loader.total_samples() - 1);
loader.seek(static_cast<int>(position)).release_value_but_fixme_should_propagate_errors();
return current_loader_position(loader);
}
AK::Duration AudioCodecPlugin::current_loader_position(Audio::Loader const& loader)
{
auto samples_played = static_cast<double>(loader.loaded_samples());
auto sample_rate = static_cast<double>(loader.sample_rate());
return AK::Duration::from_milliseconds(static_cast<i64>(samples_played / sample_rate * 1000.0));
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FixedArray.h>
#include <AK/Function.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Weakable.h>
#include <LibMedia/Audio/Forward.h>
#include <LibWeb/Export.h>
namespace Web::Platform {
class WEB_API AudioCodecPlugin : public Weakable<AudioCodecPlugin> {
public:
using AudioCodecPluginCreator = Function<ErrorOr<NonnullOwnPtr<AudioCodecPlugin>>(NonnullRefPtr<Audio::Loader>)>;
static void install_creation_hook(AudioCodecPluginCreator);
static ErrorOr<NonnullOwnPtr<AudioCodecPlugin>> create(NonnullRefPtr<Audio::Loader>);
virtual ~AudioCodecPlugin();
static ErrorOr<FixedArray<Audio::Sample>> read_samples_from_loader(Audio::Loader&, size_t samples_to_load);
static AK::Duration set_loader_position(Audio::Loader&, double position, AK::Duration duration);
static AK::Duration current_loader_position(Audio::Loader const&);
virtual void resume_playback() = 0;
virtual void pause_playback() = 0;
virtual void set_volume(double) = 0;
virtual void seek(double) = 0;
virtual AK::Duration duration() = 0;
Function<void(AK::Duration)> on_playback_position_updated;
Function<void(String)> on_decoder_error;
protected:
AudioCodecPlugin();
};
}

View File

@ -1,202 +0,0 @@
/*
* Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/MemoryStream.h>
#include <AK/String.h>
#include <AK/WeakPtr.h>
#include <LibCore/EventLoop.h>
#include <LibCore/ThreadedPromise.h>
#include <LibCore/Timer.h>
#include <LibMedia/Audio/Loader.h>
#include "AudioCodecPluginAgnostic.h"
namespace Web::Platform {
constexpr int update_interval = 50;
static AK::Duration timestamp_from_samples(i64 samples, u32 sample_rate)
{
return AK::Duration::from_milliseconds(samples * 1000 / sample_rate);
}
static AK::Duration get_loader_timestamp(NonnullRefPtr<Audio::Loader> const& loader)
{
return timestamp_from_samples(loader->loaded_samples(), loader->sample_rate());
}
ErrorOr<NonnullOwnPtr<AudioCodecPluginAgnostic>> AudioCodecPluginAgnostic::create(NonnullRefPtr<Audio::Loader> const& loader)
{
auto duration = timestamp_from_samples(loader->total_samples(), loader->sample_rate());
auto update_timer = Core::Timer::create();
update_timer->set_interval(update_interval);
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) AudioCodecPluginAgnostic(loader, duration, move(update_timer))));
constexpr u32 latency_ms = 100;
// FIXME: Audio loaders are hard-coded to output stereo audio. Once that changes, the channel count provided
// below should be retrieved from the audio loader instead of being hard-coded to 2.
RefPtr<Audio::PlaybackStream> output = TRY(Audio::PlaybackStream::create(
Audio::OutputState::Suspended, loader->sample_rate(), /* channels = */ 2, latency_ms,
[&plugin = *plugin, loader](Bytes buffer, Audio::PcmSampleFormat format, size_t sample_count) -> ReadonlyBytes {
VERIFY(format == Audio::PcmSampleFormat::Float32);
auto samples_result = loader->get_more_samples(sample_count);
if (samples_result.is_error()) {
dbgln("Error while loading samples: {}", samples_result.error());
plugin.on_decoder_error(MUST(String::formatted("Decoding failure: {}", samples_result.error())));
return buffer.trim(0);
}
auto samples = samples_result.release_value();
VERIFY(samples.size() <= sample_count);
FixedMemoryStream writing_stream { buffer };
for (auto& sample : samples) {
MUST(writing_stream.write_value(sample.left));
MUST(writing_stream.write_value(sample.right));
}
// FIXME: Check if we have loaded samples past the current known duration, and if so, update it
// and notify the media element.
return buffer.trim(writing_stream.offset());
}));
output->set_underrun_callback([&plugin = *plugin, loader, output]() {
auto new_device_time = output->total_time_played();
auto new_media_time = timestamp_from_samples(loader->loaded_samples(), loader->sample_rate());
plugin.m_main_thread_event_loop.deferred_invoke([&plugin, new_device_time, new_media_time]() {
plugin.m_last_resume_in_device_time = new_device_time;
plugin.m_last_resume_in_media_time = new_media_time;
});
});
plugin->m_output = move(output);
return plugin;
}
AudioCodecPluginAgnostic::AudioCodecPluginAgnostic(NonnullRefPtr<Audio::Loader> loader, AK::Duration duration, NonnullRefPtr<Core::Timer> update_timer)
: m_loader(move(loader))
, m_duration(duration)
, m_main_thread_event_loop(Core::EventLoop::current())
, m_update_timer(move(update_timer))
{
m_update_timer->on_timeout = [self = make_weak_ptr<AudioCodecPluginAgnostic>()]() {
if (self)
self->update_timestamp();
};
}
void AudioCodecPluginAgnostic::resume_playback()
{
m_paused = false;
m_output->resume()
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>()](AK::Duration new_device_time) {
if (!self)
return;
self->m_main_thread_event_loop.deferred_invoke([self, new_device_time]() {
if (!self)
return;
self->m_last_resume_in_device_time = new_device_time;
self->m_update_timer->start();
});
})
.when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}
void AudioCodecPluginAgnostic::pause_playback()
{
m_paused = true;
m_output->drain_buffer_and_suspend()
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>()]() {
if (!self)
return;
auto new_media_time = timestamp_from_samples(self->m_loader->loaded_samples(), self->m_loader->sample_rate());
auto new_device_time = self->m_output->total_time_played();
self->m_main_thread_event_loop.deferred_invoke([self, new_media_time, new_device_time]() {
if (!self)
return;
self->m_last_resume_in_media_time = new_media_time;
self->m_last_resume_in_device_time = new_device_time;
self->m_update_timer->stop();
self->update_timestamp();
});
})
.when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}
void AudioCodecPluginAgnostic::set_volume(double volume)
{
m_output->set_volume(volume)->when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}
void AudioCodecPluginAgnostic::seek(double position)
{
m_output->discard_buffer_and_suspend()
->when_resolved([self = make_weak_ptr<AudioCodecPluginAgnostic>(), position, was_paused = m_paused]() -> ErrorOr<void> {
if (!self)
return {};
auto sample_position = static_cast<i32>(position * self->m_loader->sample_rate());
auto seek_result = self->m_loader->seek(sample_position);
if (seek_result.is_error())
return Error::from_string_literal("Seeking in audio loader failed");
auto new_media_time = get_loader_timestamp(self->m_loader);
auto new_device_time = self->m_output->total_time_played();
self->m_main_thread_event_loop.deferred_invoke([self, was_paused, new_device_time, new_media_time]() {
if (!self)
return;
self->m_last_resume_in_device_time = new_device_time;
self->m_last_resume_in_media_time = new_media_time;
if (was_paused) {
self->update_timestamp();
} else {
self->m_output->resume()->when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}
});
return {};
})
.when_rejected([](Error&&) {
// FIXME: Propagate errors.
});
}
AK::Duration AudioCodecPluginAgnostic::duration()
{
return m_duration;
}
void AudioCodecPluginAgnostic::update_timestamp()
{
auto current_device_time_delta = m_output->total_time_played() - m_last_resume_in_device_time;
auto current_media_time = m_last_resume_in_media_time + current_device_time_delta;
current_media_time = min(current_media_time, m_duration);
on_playback_position_updated(current_media_time);
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibCore/Timer.h>
#include <LibMedia/Audio/PlaybackStream.h>
#include <LibWeb/Export.h>
#include <LibWeb/Platform/AudioCodecPlugin.h>
namespace Web::Platform {
class WEB_API AudioCodecPluginAgnostic final : public AudioCodecPlugin {
public:
static ErrorOr<NonnullOwnPtr<AudioCodecPluginAgnostic>> create(NonnullRefPtr<Audio::Loader> const&);
virtual void resume_playback() override;
virtual void pause_playback() override;
virtual void set_volume(double) override;
virtual void seek(double) override;
virtual AK::Duration duration() override;
private:
explicit AudioCodecPluginAgnostic(NonnullRefPtr<Audio::Loader> loader, AK::Duration, NonnullRefPtr<Core::Timer> update_timer);
void update_timestamp();
NonnullRefPtr<Audio::Loader> m_loader;
RefPtr<Audio::PlaybackStream> m_output { nullptr };
AK::Duration m_duration { AK::Duration::zero() };
AK::Duration m_last_resume_in_media_time { AK::Duration::zero() };
AK::Duration m_last_resume_in_device_time { AK::Duration::zero() };
Core::EventLoop& m_main_thread_event_loop;
NonnullRefPtr<Core::Timer> m_update_timer;
bool m_paused { true };
};
}

View File

@ -3,25 +3,12 @@ import("//Ladybird/link_qt.gni")
import("//Ladybird/moc_qt_objects.gni")
import("//Meta/gn/build/libs/pulse/enable.gni")
enable_qt_multimedia = !enable_pulseaudio && current_os != "mac"
moc_qt_objects("generate_moc") {
sources = [ "//Ladybird/Qt/EventLoopImplementationQtEventTarget.h" ]
if (enable_qt_multimedia) {
sources += [
"//Ladybird/Qt/AudioCodecPluginQt.cpp",
"//Ladybird/Qt/AudioThread.cpp",
]
}
}
link_qt("WebContent_qt") {
qt_components = [ "Core" ]
if (enable_qt_multimedia) {
qt_components += [ "Multimedia" ]
}
}
executable("WebContent") {
@ -75,14 +62,6 @@ executable("WebContent") {
"//Ladybird/Qt/StringUtils.cpp",
]
if (enable_qt_multimedia) {
defines += [ "HAVE_QT_MULTIMEDIA" ]
sources += [
"//Ladybird/Qt/AudioCodecPluginQt.cpp",
"//Ladybird/Qt/AudioThread.cpp",
]
}
sources += get_target_outputs(":generate_moc")
deps += [ ":generate_moc" ]
}

View File

@ -2,8 +2,6 @@ source_set("Platform") {
configs += [ "//Userland/Libraries/LibWeb:configs" ]
deps = [ "//Userland/Libraries/LibWeb:all_generated" ]
sources = [
"AudioCodecPlugin.cpp",
"AudioCodecPluginAgnostic.cpp",
"EventLoopPlugin.cpp",
"EventLoopPluginSerenity.cpp",
"FontPlugin.cpp",

View File

@ -34,22 +34,7 @@ target_link_libraries(webcontentservice PUBLIC LibCore LibCrypto LibFileSystem L
target_link_libraries(webcontentservice PRIVATE OpenSSL::Crypto OpenSSL::SSL)
target_link_libraries(webcontentservice PRIVATE SDL3::SDL3)
if (ENABLE_QT AND NOT DEFINED LADYBIRD_AUDIO_BACKEND)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
qt_add_executable(WebContent main.cpp)
target_link_libraries(WebContent PRIVATE Qt::Core)
target_compile_definitions(WebContent PRIVATE HAVE_QT=1)
target_sources(WebContent PRIVATE
${LADYBIRD_SOURCE_DIR}/UI/Qt/AudioCodecPluginQt.cpp
${LADYBIRD_SOURCE_DIR}/UI/Qt/AudioThread.cpp
)
target_link_libraries(WebContent PRIVATE LibWebViewPlatform Qt::Multimedia)
target_compile_definitions(WebContent PRIVATE HAVE_QT_MULTIMEDIA=1)
else()
add_executable(WebContent main.cpp)
endif()
target_link_libraries(WebContent PRIVATE webcontentservice LibURL)

View File

@ -16,7 +16,6 @@
#include <LibIPC/ConnectionFromClient.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
#include <LibMedia/Audio/Loader.h>
#include <LibRequests/RequestClient.h>
#include <LibUnicode/TimeZone.h>
#include <LibWeb/Bindings/MainThreadVM.h>
@ -28,7 +27,6 @@
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWebView/Plugins/FontPlugin.h>
@ -39,12 +37,6 @@
#include <WebContent/PageClient.h>
#include <WebContent/WebDriverConnection.h>
#if defined(HAVE_QT_MULTIMEDIA)
# include <LibWebView/EventLoop/EventLoopImplementationQt.h>
# include <QCoreApplication>
# include <UI/Qt/AudioCodecPluginQt.h>
#endif
#if defined(AK_OS_MACOS)
# include <LibCore/Platform/ProcessStatisticsMach.h>
#endif
@ -69,25 +61,12 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
return -1;
}
#if defined(HAVE_QT_MULTIMEDIA)
QCoreApplication app(arguments.argc, arguments.argv);
Core::EventLoopManager::install(*new WebView::EventLoopManagerQt);
#endif
Core::EventLoop event_loop;
WebView::platform_init();
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
#if defined(HAVE_QT_MULTIMEDIA)
return Ladybird::AudioCodecPluginQt::create(move(loader));
#else
return Web::Platform::AudioCodecPluginAgnostic::create(move(loader));
#endif
});
StringView command_line {};
StringView executable_path {};
auto config_path = ByteString::formatted("{}/ladybird/default-config", WebView::s_ladybird_resource_root);

View File

@ -22,7 +22,6 @@
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
#include <LibWebView/HelperProcess.h>
#include <LibWebView/Plugins/FontPlugin.h>
@ -55,10 +54,6 @@ ErrorOr<int> service_main(int ipc_socket)
auto image_decoder_client = TRY(bind_image_decoder_service());
Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(image_decoder_client)));
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
return Web::Platform::AudioCodecPluginAgnostic::create(move(loader));
});
Web::Bindings::initialize_main_thread_vm(Web::Bindings::AgentType::SimilarOriginWindow);
auto request_server_client = TRY(bind_request_server_service());

View File

@ -1,67 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibMedia/Audio/Loader.h>
#include <UI/Qt/AudioCodecPluginQt.h>
#include <UI/Qt/AudioThread.h>
namespace Ladybird {
ErrorOr<NonnullOwnPtr<AudioCodecPluginQt>> AudioCodecPluginQt::create(NonnullRefPtr<Audio::Loader> loader)
{
auto audio_thread = TRY(AudioThread::create(move(loader)));
audio_thread->start();
return adopt_nonnull_own_or_enomem(new (nothrow) AudioCodecPluginQt(move(audio_thread)));
}
AudioCodecPluginQt::AudioCodecPluginQt(NonnullOwnPtr<AudioThread> audio_thread)
: m_audio_thread(move(audio_thread))
{
connect(m_audio_thread, &AudioThread::playback_position_updated, this, [this](auto position) {
if (on_playback_position_updated)
on_playback_position_updated(position);
});
}
AudioCodecPluginQt::~AudioCodecPluginQt()
{
m_audio_thread->stop().release_value_but_fixme_should_propagate_errors();
}
void AudioCodecPluginQt::resume_playback()
{
m_audio_thread->queue_task({ AudioTask::Type::Play }).release_value_but_fixme_should_propagate_errors();
}
void AudioCodecPluginQt::pause_playback()
{
m_audio_thread->queue_task({ AudioTask::Type::Pause }).release_value_but_fixme_should_propagate_errors();
}
void AudioCodecPluginQt::set_volume(double volume)
{
AudioTask task { AudioTask::Type::Volume };
task.data = volume;
m_audio_thread->queue_task(move(task)).release_value_but_fixme_should_propagate_errors();
}
void AudioCodecPluginQt::seek(double position)
{
AudioTask task { AudioTask::Type::Seek };
task.data = position;
m_audio_thread->queue_task(move(task)).release_value_but_fixme_should_propagate_errors();
}
AK::Duration AudioCodecPluginQt::duration()
{
return m_audio_thread->duration();
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/NonnullRefPtr.h>
#include <LibMedia/Audio/Forward.h>
#include <LibWeb/Platform/AudioCodecPlugin.h>
#include <QObject>
namespace Ladybird {
class AudioThread;
class AudioCodecPluginQt final
: public QObject
, public Web::Platform::AudioCodecPlugin {
Q_OBJECT
public:
static ErrorOr<NonnullOwnPtr<AudioCodecPluginQt>> create(NonnullRefPtr<Audio::Loader>);
virtual ~AudioCodecPluginQt() override;
virtual void resume_playback() override;
virtual void pause_playback() override;
virtual void set_volume(double) override;
virtual void seek(double) override;
virtual AK::Duration duration() override;
private:
explicit AudioCodecPluginQt(NonnullOwnPtr<AudioThread>);
NonnullOwnPtr<AudioThread> m_audio_thread;
};
}

View File

@ -1,212 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Platform/AudioCodecPlugin.h>
#include <UI/Qt/AudioThread.h>
namespace Ladybird {
struct AudioDevice {
static AudioDevice create(Audio::Loader const& loader)
{
auto const& device_info = QMediaDevices::defaultAudioOutput();
auto format = device_info.preferredFormat();
format.setSampleRate(static_cast<int>(loader.sample_rate()));
format.setChannelCount(2);
auto audio_output = make<QAudioSink>(device_info, format);
return AudioDevice { move(audio_output) };
}
AudioDevice(AudioDevice&&) = default;
AudioDevice& operator=(AudioDevice&& device)
{
if (audio_output) {
audio_output->stop();
io_device = nullptr;
}
swap(audio_output, device.audio_output);
swap(io_device, device.io_device);
return *this;
}
~AudioDevice()
{
if (audio_output)
audio_output->stop();
}
OwnPtr<QAudioSink> audio_output;
QIODevice* io_device { nullptr };
private:
explicit AudioDevice(NonnullOwnPtr<QAudioSink> output)
: audio_output(move(output))
{
io_device = audio_output->start();
}
};
ErrorOr<NonnullOwnPtr<AudioThread>> AudioThread::create(NonnullRefPtr<Audio::Loader> loader)
{
auto task_queue = TRY(AudioTaskQueue::create());
return adopt_nonnull_own_or_enomem(new (nothrow) AudioThread(move(loader), move(task_queue)));
}
ErrorOr<void> AudioThread::stop()
{
TRY(queue_task({ AudioTask::Type::Stop }));
wait();
return {};
}
ErrorOr<void> AudioThread::queue_task(AudioTask task)
{
return m_task_queue.blocking_enqueue(move(task), []() {
usleep(UPDATE_RATE_MS * 1000);
});
}
AudioThread::AudioThread(NonnullRefPtr<Audio::Loader> loader, AudioTaskQueue task_queue)
: m_loader(move(loader))
, m_task_queue(move(task_queue))
{
auto duration = static_cast<double>(m_loader->total_samples()) / static_cast<double>(m_loader->sample_rate());
m_duration = AK::Duration::from_milliseconds(static_cast<i64>(duration * 1000.0));
}
void AudioThread::run()
{
auto devices = make<QMediaDevices>();
auto audio_device = AudioDevice::create(m_loader);
connect(devices, &QMediaDevices::audioOutputsChanged, this, [this]() {
queue_task({ AudioTask::Type::RecreateAudioDevice }).release_value_but_fixme_should_propagate_errors();
});
auto paused = Paused::Yes;
while (true) {
auto& audio_output = audio_device.audio_output;
auto* io_device = audio_device.io_device;
if (auto result = m_task_queue.dequeue(); result.is_error()) {
VERIFY(result.error() == AudioTaskQueue::QueueStatus::Empty);
} else {
auto task = result.release_value();
switch (task.type) {
case AudioTask::Type::Stop:
return;
case AudioTask::Type::Play:
audio_output->resume();
paused = Paused::No;
break;
case AudioTask::Type::Pause:
audio_output->suspend();
paused = Paused::Yes;
break;
case AudioTask::Type::Seek:
VERIFY(task.data.has_value());
m_position = Web::Platform::AudioCodecPlugin::set_loader_position(m_loader, *task.data, m_duration);
if (paused == Paused::Yes)
Q_EMIT playback_position_updated(m_position);
break;
case AudioTask::Type::Volume:
VERIFY(task.data.has_value());
audio_output->setVolume(*task.data);
break;
case AudioTask::Type::RecreateAudioDevice:
audio_device = AudioDevice::create(m_loader);
continue;
}
}
if (paused == Paused::No) {
if (auto result = play_next_samples(*audio_output, *io_device); result.is_error()) {
// FIXME: Propagate the error to the HTMLMediaElement.
} else {
Q_EMIT playback_position_updated(m_position);
paused = result.value();
}
}
usleep(UPDATE_RATE_MS * 1000);
}
}
ErrorOr<AudioThread::Paused> AudioThread::play_next_samples(QAudioSink& audio_output, QIODevice& io_device)
{
bool all_samples_loaded = m_loader->loaded_samples() >= m_loader->total_samples();
if (all_samples_loaded) {
audio_output.suspend();
(void)m_loader->reset();
m_position = m_duration;
return Paused::Yes;
}
auto bytes_available = audio_output.bytesFree();
auto bytes_per_sample = audio_output.format().bytesPerSample();
auto channel_count = audio_output.format().channelCount();
auto samples_to_load = bytes_available / bytes_per_sample / channel_count;
auto samples = TRY(Web::Platform::AudioCodecPlugin::read_samples_from_loader(*m_loader, samples_to_load));
enqueue_samples(audio_output, io_device, move(samples));
m_position = Web::Platform::AudioCodecPlugin::current_loader_position(m_loader);
return Paused::No;
}
void AudioThread::enqueue_samples(QAudioSink const& audio_output, QIODevice& io_device, FixedArray<Audio::Sample> samples)
{
auto buffer_size = samples.size() * audio_output.format().bytesPerSample() * audio_output.format().channelCount();
if (buffer_size > static_cast<size_t>(m_sample_buffer.size()))
m_sample_buffer.resize(buffer_size);
FixedMemoryStream stream { Bytes { m_sample_buffer.data(), buffer_size } };
for (auto const& sample : samples) {
switch (audio_output.format().sampleFormat()) {
case QAudioFormat::UInt8:
write_sample<u8>(stream, sample.left);
write_sample<u8>(stream, sample.right);
break;
case QAudioFormat::Int16:
write_sample<i16>(stream, sample.left);
write_sample<i16>(stream, sample.right);
break;
case QAudioFormat::Int32:
write_sample<i32>(stream, sample.left);
write_sample<i32>(stream, sample.right);
break;
case QAudioFormat::Float:
write_sample<float>(stream, sample.left);
write_sample<float>(stream, sample.right);
break;
default:
VERIFY_NOT_REACHED();
}
}
io_device.write(m_sample_buffer.data(), buffer_size);
}
}

View File

@ -1,105 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/MemoryStream.h>
#include <AK/Optional.h>
#include <AK/Time.h>
#include <AK/Types.h>
#include <LibCore/SharedCircularQueue.h>
#include <LibMedia/Audio/Loader.h>
#include <LibMedia/Audio/Sample.h>
#include <QAudioFormat>
#include <QAudioSink>
#include <QByteArray>
#include <QMediaDevices>
#include <QThread>
namespace Ladybird {
static constexpr u32 UPDATE_RATE_MS = 10;
struct AudioTask {
enum class Type {
Stop,
Play,
Pause,
Seek,
Volume,
RecreateAudioDevice,
};
Type type;
Optional<double> data {};
};
using AudioTaskQueue = Core::SharedSingleProducerCircularQueue<AudioTask>;
class AudioThread final : public QThread { // We have to use QThread, otherwise internal Qt media QTimer objects do not work.
Q_OBJECT
public:
static ErrorOr<NonnullOwnPtr<AudioThread>> create(NonnullRefPtr<Audio::Loader> loader);
ErrorOr<void> stop();
AK::Duration duration() const { return m_duration; }
ErrorOr<void> queue_task(AudioTask task);
Q_SIGNALS:
void playback_position_updated(AK::Duration);
private:
AudioThread(NonnullRefPtr<Audio::Loader> loader, AudioTaskQueue task_queue);
enum class Paused {
Yes,
No,
};
void run() override;
ErrorOr<Paused> play_next_samples(QAudioSink& audio_output, QIODevice& io_device);
void enqueue_samples(QAudioSink const& audio_output, QIODevice& io_device, FixedArray<Audio::Sample> samples);
template<typename T>
void write_sample(FixedMemoryStream& stream, float sample)
{
// The values that need to be written to the stream vary depending on the output channel format, and isn't
// particularly well documented. The value derivations performed below were adapted from a Qt example:
// https://code.qt.io/cgit/qt/qtmultimedia.git/tree/examples/multimedia/audiooutput/audiooutput.cpp?h=6.4.2#n46
LittleEndian<T> pcm;
if constexpr (IsSame<T, u8>)
pcm = static_cast<u8>((sample + 1.0f) / 2 * NumericLimits<u8>::max());
else if constexpr (IsSame<T, i16>)
pcm = static_cast<i16>(sample * NumericLimits<i16>::max());
else if constexpr (IsSame<T, i32>)
pcm = static_cast<i32>(sample * NumericLimits<i32>::max());
else if constexpr (IsSame<T, float>)
pcm = sample;
else
static_assert(DependentFalse<T>);
MUST(stream.write_value(pcm));
}
NonnullRefPtr<Audio::Loader> m_loader;
AudioTaskQueue m_task_queue;
QByteArray m_sample_buffer;
AK::Duration m_duration;
AK::Duration m_position;
};
}

View File

@ -154,10 +154,6 @@
"zstd"
]
},
{
"name": "qtmultimedia",
"platform": "windows | freebsd"
},
{
"name": "pthread",
"platform": "windows"
@ -284,10 +280,6 @@
"name": "qtbase",
"version": "6.8.3#2"
},
{
"name": "qtmultimedia",
"version": "6.8.3#0"
},
{
"name": "sdl3",
"version": "3.2.22#0"