Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
eda079a
LibWeb: Move media controls' timeline fill to an inner div
Zaggy1024 Mar 28, 2026
63b7926
LibWeb: Simplify formatting of media timeline progress width style
Zaggy1024 Mar 28, 2026
af11078
LibWeb: Make buffered ranges visible on the media controls' timeline
Zaggy1024 Mar 28, 2026
785c268
LibWeb: Remove two unused static vectors in MediaControls.cpp
Zaggy1024 May 28, 2026
8cd1ff4
LibMedia: Avoid crashing in tests appending data to incremental streams
Zaggy1024 Mar 29, 2026
470c1ff
LibMedia: Allow MediaStreamCursor to never block on missing data
Zaggy1024 Mar 29, 2026
635d50a
LibMedia: Prevent a crash when Matroska reads a block before a cluster
Zaggy1024 Mar 29, 2026
d00e9e9
LibMedia: Split out Matroska::SampleIterator and Streamer
Zaggy1024 Mar 29, 2026
8aac098
LibMedia: Add a method to get the available bytes from a MediaStream
Zaggy1024 Mar 29, 2026
db004b0
AK: Add a method to remove an iterated node from a RedBlackTree
Zaggy1024 May 16, 2026
dd609fd
LibMedia: Ensure that overlapped incremental stream chunks are merged
Zaggy1024 Mar 29, 2026
55acf89
Tests: Add tests for available incremental stream byte ranges
Zaggy1024 Mar 29, 2026
6c82b99
LibMedia+Tests: Add a method to remove data from incremental streams
Zaggy1024 May 15, 2026
558b2ad
LibMedia: Return an error if a Matroska octet couldn't be read
Zaggy1024 Mar 29, 2026
2c82b6c
LibMedia: Allow Matroska::SampleIterator to iterate all tracks
Zaggy1024 Mar 29, 2026
26b5c86
LibMedia: Make some Matroska::Reader functions const
Zaggy1024 Mar 29, 2026
329413b
LibMedia: Implement buffered time range scanning for Matroska
Zaggy1024 Mar 29, 2026
4f20f59
LibMedia: Actually adjust incremental stream's request positions
Zaggy1024 Mar 30, 2026
a342ec4
LibWeb: Fire the progress event when new media data is received
Zaggy1024 Mar 30, 2026
f40f567
LibMedia: Move the bitwise operators for FrameFlags to Media::
Zaggy1024 Apr 23, 2026
5c79b0c
LibMedia: Implement buffered ranges for FFmpegDemuxer
Zaggy1024 Mar 30, 2026
cb02c5b
LibWeb: Don't use toggle_playback() in MediaControls' play/pause button
Zaggy1024 Mar 30, 2026
21921d8
LibWeb: Remove now-unused HTMLMediaElement::toggle_playback()
Zaggy1024 Mar 30, 2026
da6f7be
LibMedia: Add some reading helpers to MediaStreamCursor
Zaggy1024 May 15, 2026
fea9edb
LibMedia: Calculate accurate buffered ranges for FLAC
Zaggy1024 May 15, 2026
ed1b6fd
LibMedia: Always clear EOF/error flags in AVIOContext when seeking
Zaggy1024 May 13, 2026
a519d70
LibMedia: Implement CBR seeking through ContainerNavigator
Zaggy1024 Apr 22, 2026
6fdd7fa
LibMedia: Implement buffered ranges for MP3
Zaggy1024 May 12, 2026
c5708ee
LibMedia: Factor out Opus frame duration parsing from Matroska::Reader
Zaggy1024 May 15, 2026
554b7f0
LibMedia: Implement buffered range scanning for Ogg
Zaggy1024 May 15, 2026
b59df5f
LibMedia: Reduce the amount of WAV data loaded before playback starts
Zaggy1024 May 15, 2026
eefc0c2
Tests: Add a test set for file buffered ranges
Zaggy1024 May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions AK/RedBlackTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,25 @@ class RedBlackTree final : public BaseRedBlackTree<K> {
return temp;
}

V remove_and_advance(Iterator& iterator)
{
VERIFY(!iterator.is_end());

auto* node = iterator.m_node;
auto* successor = static_cast<Node*>(BaseTree::successor(node));
iterator.m_node = successor;

BaseTree::remove(node);

V temp = move(node->value);

node->right_child = nullptr;
node->left_child = nullptr;
delete node;

return temp;
}

bool remove(K key)
{
auto* node = BaseTree::find(this->m_root, key);
Expand Down
10 changes: 10 additions & 0 deletions Libraries/LibMedia/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ include(audio)

set(SOURCES
Audio/AudioDevices.cpp
Codecs/FLAC.cpp
Codecs/Opus.cpp
Codecs/Vorbis.cpp
Containers/ConstantBitrateContainerNavigator.cpp
Containers/FLACNavigator.cpp
Containers/IndexedContainerNavigator.cpp
Containers/MP3Navigator.cpp
Containers/OggNavigator.cpp
Containers/Matroska/MatroskaDemuxer.cpp
Containers/Matroska/Reader.cpp
Containers/Matroska/SampleIterator.cpp
Containers/Matroska/Streamer.cpp
GenericTimeProvider.cpp
IncrementallyPopulatedStream.cpp
PlaybackManager.cpp
Expand Down
164 changes: 164 additions & 0 deletions Libraries/LibMedia/Codecs/FLAC.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/BuiltinWrappers.h>
#include <LibMedia/Codecs/FLAC.h>
#include <LibMedia/MediaStream.h>

namespace Media::Codecs {

template<Integral T>
static Optional<T> read_big_endian_value(MediaStreamCursor& cursor)
{
auto value = cursor.read_value<T>(AK::Endianness::Big);
if (value.is_error())
return {};
return value.release_value();
}

static Optional<u64> read_coded_number(MediaStreamCursor& cursor)
{
auto first_byte = read_big_endian_value<u8>(cursor);
if (!first_byte.has_value())
return {};
u64 value = first_byte.value();

auto length = count_leading_zeroes_safe(static_cast<u8>(~value));
if (length == 0)
return value;
if (length == 1)
return {};
if (length > 7)
return {};

value &= (1 << (8 - (length + 1))) - 1;
while (--length > 0) {
auto continuation_byte = read_big_endian_value<u8>(cursor);
if (!continuation_byte.has_value())
return {};
if (continuation_byte.value() >> 6 != 0b10)
return {};
value <<= 6;
value |= continuation_byte.value() & 0b111111;
}

return value;
}

static bool verify_header_crc(MediaStreamCursor& cursor, size_t header_start)
{
constexpr auto lookup_table = [] {
Array<u8, 256> result;
for (size_t i = 0; i < result.size(); i++) {
u8 value = i;
for (auto j = 0; j < 8; j++)
value = (value << 1) ^ (value & 0x80 ? 0x07 : 0);
result[i] = value;
}
return result;
}();

auto header_end = cursor.position();
VERIFY(header_end > header_start);
auto header_size = header_end - header_start;
Array<u8, 16> buffer;
if (header_size > buffer.size())
return false;

auto crc = read_big_endian_value<u8>(cursor);
if (!crc.has_value())
return false;

if (cursor.seek_to_position(header_start).is_error())
return false;
auto header_data = buffer.span().trim(header_size);
if (cursor.read_until_filled(header_data).is_error())
return false;
if (cursor.skip(1).is_error())
return false;

u8 actual_crc = 0;
for (auto const& byte : header_data)
actual_crc = lookup_table[actual_crc ^ byte];
return crc.value() == actual_crc;
}

bool FLAC::is_sync_code(u16 value)
{
return (value >> 1) == 0b111111111111100;
}

Optional<FLAC::FrameInfo> FLAC::parse_frame_header(MediaStreamCursor& cursor, u16 sync_code, u16 fixed_block_size)
{
auto header_start = cursor.position();

Array<u8, 4> header;
if (cursor.read_until_filled(header).is_error())
return {};

u16 maybe_sync_code = (static_cast<u16>(header[0]) << 8) | header[1];
if (maybe_sync_code != sync_code)
return {};

bool variable_block_size = (sync_code & 1) != 0;
u8 block_size_bits = (header[2] >> 4) & 0x0F;
u8 sample_rate_bits = header[2] & 0x0F;
u8 channels_bits = (header[3] >> 4) & 0x0F;
u8 bit_depth_bits = (header[3] >> 1) & 0x07;
u8 reserved_bit = header[3] & 0x01;

if (reserved_bit != 0)
return {};

auto coded_number = read_coded_number(cursor);
if (!coded_number.has_value())
return {};

u16 block_size = 0;
if (block_size_bits == 0b0000)
return {};
if (block_size_bits == 0b0001) {
block_size = 192;
} else if (block_size_bits <= 0b0101) {
block_size = 144 * (1 << block_size_bits);
} else if (block_size_bits == 0b0110) {
auto uncommon_block_size_minus_1 = read_big_endian_value<u8>(cursor);
if (!uncommon_block_size_minus_1.has_value())
return {};
block_size = uncommon_block_size_minus_1.value() + 1;
} else if (block_size_bits == 0b0111) {
auto uncommon_block_size_minus_1 = read_big_endian_value<u16>(cursor);
if (!uncommon_block_size_minus_1.has_value())
return {};
block_size = uncommon_block_size_minus_1.value() + 1;
} else if (block_size_bits >= 0b1000) {
block_size = 1 << block_size_bits;
}

if (sample_rate_bits == 0b1100 && cursor.skip(1).is_error())
return {};
if ((sample_rate_bits == 0b1101 || sample_rate_bits == 0b1110) && cursor.skip(2).is_error())
return {};
if (sample_rate_bits == 0b1111)
return {};

if (channels_bits >= 0b1011)
return {};

if (bit_depth_bits == 0b011)
return {};

if (!verify_header_crc(cursor, header_start))
return {};

u64 sample_number = coded_number.value();
if (!variable_block_size)
sample_number *= fixed_block_size;

return FrameInfo { sample_number, block_size };
}

}
25 changes: 25 additions & 0 deletions Libraries/LibMedia/Codecs/FLAC.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Optional.h>
#include <LibMedia/Forward.h>

namespace Media::Codecs {

class FLAC {
public:
struct FrameInfo {
u64 sample_number;
u16 block_size;
};

static bool is_sync_code(u16);
static Optional<FrameInfo> parse_frame_header(MediaStreamCursor&, u16 sync_code, u16 fixed_block_size);
};

}
64 changes: 64 additions & 0 deletions Libraries/LibMedia/Codecs/Opus.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibMedia/Codecs/Opus.h>
#include <LibMedia/MediaStream.h>

namespace Media::Codecs {

DecoderErrorOr<u32> Opus::parse_frame_duration_in_samples(MediaStreamCursor& cursor, size_t frame_size)
{
if (frame_size == 0)
return DecoderError::corrupted("Opus frame is too small"sv);

// https://datatracker.ietf.org/doc/html/rfc6716#section-3.1
auto toc_byte = TRY(cursor.read_value<u8>());
auto configuration_number = (toc_byte >> 3) & 0b1'1111;
auto packet_code = toc_byte & 0b11;

// clang-format off
constexpr Array frame_durations = {
10000, 20000, 40000, 60000, // SILK-only NB
10000, 20000, 40000, 60000, // SILK-only MB
10000, 20000, 40000, 60000, // SILK-only WB
10000, 20000, // Hybrid SWB
10000, 20000, // Hybrid FB
2500, 5000, 10000, 20000, // CELT-only NB
2500, 5000, 10000, 20000, // CELT-only WB
2500, 5000, 10000, 20000, // CELT-only SWB
2500, 5000, 10000, 20000, // CELT-only FB
};
// clang-format on

auto frame_duration = frame_durations[configuration_number];
auto packet_duration = TRY([&] -> DecoderErrorOr<int> {
switch (packet_code) {
case 0:
return frame_duration;
case 1:
case 2:
return frame_duration * 2;
case 3: {
if (frame_size == 1)
return DecoderError::corrupted("Opus frame is too small"sv);
auto frame_count_byte = TRY(cursor.read_value<u8>());
auto frame_count = frame_count_byte & 0b11'1111;
return frame_duration * frame_count;
}
default:
VERIFY_NOT_REACHED();
}
}());

return packet_duration * 48 / 1000;
}

DecoderErrorOr<AK::Duration> Opus::parse_frame_duration(MediaStreamCursor& cursor, size_t frame_size)
{
return AK::Duration::from_time_units(TRY(parse_frame_duration_in_samples(cursor, frame_size)), 1, 48000);
}

}
21 changes: 21 additions & 0 deletions Libraries/LibMedia/Codecs/Opus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Time.h>
#include <LibMedia/DecoderError.h>
#include <LibMedia/Forward.h>

namespace Media::Codecs {

class Opus {
public:
static DecoderErrorOr<AK::Duration> parse_frame_duration(MediaStreamCursor&, size_t frame_size);
static DecoderErrorOr<u32> parse_frame_duration_in_samples(MediaStreamCursor&, size_t frame_size);
};

}
Loading
Loading