1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-19 18:35:06 +00:00
serenity/Userland/Libraries/LibAudio/Loader.cpp
Zaggy1024 ac6c06e235 LibAudio: Don't count buffered samples in Loader::loaded_samples()
The `Loader` uses a `Vector<Sample>` to store any samples that the
actual audio decoder returned that the loader client did not request
yet. However, it did not take that buffer into account when those
clients asked for the number of samples loaded. The buffer is an
internal implementation detail, and should not be reflected on the
external API.

This allows LibWeb to keep better track of audio position without any
desync caused by the buffer. When using the Serenity `PlaybackStream`
implementation as the back end for an `HTMLAudioElement`, this allows
us to perfectly stop on the exact last sample of the audio file, so it
will not stop before the media element can see that it has finished
playback.

Note that `Loader::get_more_samples()` also calls `loaded_samples()` to
determine how many samples are remaining to load into the output
buffer. However, this change appears to be correct even there, given
that the samples copied from the internal sample buffer are included in
that count.
2023-08-29 11:31:10 +02:00

120 lines
4.1 KiB
C++

/*
* Copyright (c) 2018-2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypedTransfer.h>
#include <LibAudio/FlacLoader.h>
#include <LibAudio/Loader.h>
#include <LibAudio/MP3Loader.h>
#include <LibAudio/QOALoader.h>
#include <LibAudio/WavLoader.h>
#include <LibCore/File.h>
namespace Audio {
LoaderPlugin::LoaderPlugin(NonnullOwnPtr<SeekableStream> stream)
: m_stream(move(stream))
{
}
Loader::Loader(NonnullOwnPtr<LoaderPlugin> plugin)
: m_plugin(move(plugin))
{
}
struct LoaderPluginInitializer {
bool (*sniff)(SeekableStream&);
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> (*create)(NonnullOwnPtr<SeekableStream>);
};
#define ENUMERATE_LOADER_PLUGINS \
__ENUMERATE_LOADER_PLUGIN(Wav) \
__ENUMERATE_LOADER_PLUGIN(Flac) \
__ENUMERATE_LOADER_PLUGIN(QOA) \
__ENUMERATE_LOADER_PLUGIN(MP3)
static constexpr LoaderPluginInitializer s_initializers[] = {
#define __ENUMERATE_LOADER_PLUGIN(Type) \
{ Type##LoaderPlugin::sniff, Type##LoaderPlugin::create },
ENUMERATE_LOADER_PLUGINS
#undef __ENUMERATE_LOADER_PLUGIN
};
ErrorOr<NonnullRefPtr<Loader>, LoaderError> Loader::create(StringView path)
{
auto stream = TRY(Core::InputBufferedFile::create(TRY(Core::File::open(path, Core::File::OpenMode::Read))));
return adopt_ref(*new (nothrow) Loader(TRY(Loader::create_plugin(move(stream)))));
}
ErrorOr<NonnullRefPtr<Loader>, LoaderError> Loader::create(ReadonlyBytes buffer)
{
auto stream = TRY(try_make<FixedMemoryStream>(buffer));
return adopt_ref(*new (nothrow) Loader(TRY(Loader::create_plugin(move(stream)))));
}
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> Loader::create_plugin(NonnullOwnPtr<SeekableStream> stream)
{
for (auto const& loader : s_initializers) {
if (loader.sniff(*stream)) {
TRY(stream->seek(0, SeekMode::SetPosition));
return loader.create(move(stream));
}
TRY(stream->seek(0, SeekMode::SetPosition));
}
return LoaderError { "No loader plugin available" };
}
LoaderSamples Loader::get_more_samples(size_t samples_to_read_from_input)
{
if (m_plugin_at_end_of_stream && m_buffer.is_empty())
return FixedArray<Sample> {};
size_t remaining_samples = total_samples() - loaded_samples();
size_t samples_to_read = min(remaining_samples, samples_to_read_from_input);
auto samples = TRY(FixedArray<Sample>::create(samples_to_read));
size_t sample_index = 0;
if (m_buffer.size() > 0) {
size_t to_transfer = min(m_buffer.size(), samples_to_read);
AK::TypedTransfer<Sample>::move(samples.data(), m_buffer.data(), to_transfer);
if (to_transfer < m_buffer.size())
m_buffer.remove(0, to_transfer);
else
m_buffer.clear_with_capacity();
sample_index += to_transfer;
}
while (sample_index < samples_to_read) {
auto chunk_data = TRY(m_plugin->load_chunks(samples_to_read - sample_index));
chunk_data.remove_all_matching([](auto& chunk) { return chunk.is_empty(); });
if (chunk_data.is_empty()) {
m_plugin_at_end_of_stream = true;
break;
}
for (auto& chunk : chunk_data) {
if (sample_index < samples_to_read) {
auto count = min(samples_to_read - sample_index, chunk.size());
AK::TypedTransfer<Sample>::move(samples.span().offset(sample_index), chunk.data(), count);
// We didn't read all of the chunk; transfer the rest into the buffer.
if (count < chunk.size()) {
auto remaining_samples_count = chunk.size() - count;
// We will always have an empty buffer at this point!
TRY(m_buffer.try_append(chunk.span().offset(count), remaining_samples_count));
}
} else {
// We're now past what the user requested. Transfer the entirety of the data into the buffer.
TRY(m_buffer.try_append(chunk.data(), chunk.size()));
}
sample_index += chunk.size();
}
}
return samples;
}
}