mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-12 01:46:46 +02:00
This commit splits out synchronization primitives from LibThreading into LibSync. This is because LibThreading depends on LibCore, while LibCore needs the synchronization primitives from LibThreading. This worked while they were header only, but when I tried to add an implementation file it ran into the circular dependency. To abstract away the pthread implementation using cpp files is necessary so the synchronization primitives were moved to a separate library.
275 lines
9.7 KiB
C++
275 lines
9.7 KiB
C++
/*
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/BinarySearch.h>
|
|
#include <LibWeb/MediaSourceExtensions/TrackBufferDemuxer.h>
|
|
|
|
namespace Web::MediaSourceExtensions {
|
|
|
|
TrackBufferDemuxer::TrackBufferDemuxer(Media::Track const& track, Media::CodecID codec_id, ByteBuffer codec_initialization_data)
|
|
: m_track(track)
|
|
, m_codec_id(codec_id)
|
|
, m_codec_initialization_data(move(codec_initialization_data))
|
|
{
|
|
}
|
|
|
|
TrackBufferDemuxer::~TrackBufferDemuxer() = default;
|
|
|
|
Media::TimeRanges TrackBufferDemuxer::track_buffer_ranges() const
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
// https://w3c.github.io/media-source/#track-buffer-ranges
|
|
// NOTE: Implementations MAY coalesce adjacent ranges separated by a gap smaller than 2 times the
|
|
// maximum frame duration buffered so far in this track buffer.
|
|
auto max_gap = maximum_time_range_gap();
|
|
if (max_gap.is_zero())
|
|
return m_track_buffer_ranges;
|
|
return m_track_buffer_ranges.coalesced(max_gap);
|
|
}
|
|
|
|
void TrackBufferDemuxer::add_coded_frame(Media::CodedFrame frame)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
auto start = frame.timestamp();
|
|
auto end = frame.timestamp() + frame.duration();
|
|
m_last_frame_duration = frame.duration();
|
|
m_track_buffer_ranges.add_range(start, end);
|
|
|
|
// Insert in sorted order by presentation timestamp using binary search.
|
|
// Overlapping frames should have been removed by remove_coded_frames_and_dependants_in_range()
|
|
// before this call.
|
|
auto timestamp = frame.timestamp();
|
|
size_t insert_index = 0;
|
|
auto* existing_coded_frame = binary_search(m_coded_frames, timestamp, &insert_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
|
|
return needle <=> frame.timestamp();
|
|
});
|
|
VERIFY(!existing_coded_frame);
|
|
if (insert_index < m_coded_frames.size() && m_coded_frames[insert_index].timestamp() < timestamp)
|
|
insert_index++;
|
|
|
|
m_coded_frames.insert(insert_index, move(frame));
|
|
|
|
if (insert_index <= m_read_position && (!m_last_returned_timestamp.has_value() || m_last_returned_timestamp.value() > timestamp))
|
|
m_read_position++;
|
|
|
|
m_data_changed.broadcast();
|
|
}
|
|
|
|
void TrackBufferDemuxer::remove_coded_frames_and_dependants_in_range(AK::Duration start, AK::Duration end)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
|
|
// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-processing
|
|
// 1.13. Remove all coded frames from track buffer that have a presentation timestamp greater than
|
|
// or equal to presentation timestamp and less than frame end timestamp.
|
|
// 1.14. Remove all possible decoding dependencies on the coded frames removed in the previous step
|
|
// by removing all coded frames from track buffer between those frames removed in the previous
|
|
// step and the next random access point after those removed frames.
|
|
|
|
// Find the first frame at or after the start of the range.
|
|
size_t remove_start = 0;
|
|
while (remove_start < m_coded_frames.size() && m_coded_frames[remove_start].timestamp() < start)
|
|
remove_start++;
|
|
|
|
// Find all overlapping frames plus subsequent frames up to the next keyframe.
|
|
size_t remove_end = remove_start;
|
|
while (remove_end < m_coded_frames.size() && m_coded_frames[remove_end].timestamp() < end)
|
|
remove_end++;
|
|
if (remove_end <= remove_start)
|
|
return;
|
|
while (remove_end < m_coded_frames.size() && !m_coded_frames[remove_end].is_keyframe())
|
|
remove_end++;
|
|
|
|
for (size_t i = remove_start; i < remove_end; i++) {
|
|
auto const& removed_frame = m_coded_frames[i];
|
|
auto removed_start = removed_frame.timestamp();
|
|
auto removed_end = (removed_frame.timestamp() + removed_frame.duration());
|
|
m_track_buffer_ranges.remove_range(removed_start, removed_end);
|
|
}
|
|
|
|
m_coded_frames.remove(remove_start, remove_end - remove_start);
|
|
|
|
// Adjust read position if it was in or past the removed range.
|
|
if (m_read_position >= remove_end) {
|
|
m_read_position -= (remove_end - remove_start);
|
|
} else if (m_read_position > remove_start) {
|
|
m_read_position = remove_start > 0 ? remove_start - 1 : 0;
|
|
|
|
while (m_read_position > 0 && !m_coded_frames[m_read_position].is_keyframe())
|
|
m_read_position--;
|
|
|
|
m_last_returned_timestamp.clear();
|
|
}
|
|
}
|
|
|
|
void TrackBufferDemuxer::set_reached_end_of_stream()
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
m_reached_end_of_stream = true;
|
|
m_data_changed.broadcast();
|
|
}
|
|
|
|
void TrackBufferDemuxer::clear_reached_end_of_stream()
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
m_reached_end_of_stream = false;
|
|
}
|
|
|
|
Media::DecoderErrorOr<void> TrackBufferDemuxer::create_context_for_track(Media::Track const&)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
Media::DecoderErrorOr<Vector<Media::Track>> TrackBufferDemuxer::get_tracks_for_type(Media::TrackType type)
|
|
{
|
|
if (m_track.type() == type)
|
|
return Vector { m_track };
|
|
return Vector<Media::Track> {};
|
|
}
|
|
|
|
Media::DecoderErrorOr<Optional<Media::Track>> TrackBufferDemuxer::get_preferred_track_for_type(Media::TrackType type)
|
|
{
|
|
if (m_track.type() == type)
|
|
return Optional<Media::Track> { m_track };
|
|
return Optional<Media::Track> {};
|
|
}
|
|
|
|
AK::Duration TrackBufferDemuxer::maximum_time_range_gap() const
|
|
{
|
|
return m_last_frame_duration + m_last_frame_duration;
|
|
}
|
|
|
|
bool TrackBufferDemuxer::next_frame_is_in_gap_while_locked() const
|
|
{
|
|
auto max_gap = maximum_time_range_gap();
|
|
if (!m_last_returned_timestamp.has_value() || max_gap.is_zero())
|
|
return false;
|
|
if (m_read_position >= m_coded_frames.size())
|
|
return false;
|
|
auto delta = m_coded_frames[m_read_position].timestamp() - m_last_returned_timestamp.value();
|
|
return delta > max_gap;
|
|
}
|
|
|
|
Media::DecoderErrorOr<Media::CodedFrame> TrackBufferDemuxer::get_next_sample_for_track(Media::Track const&)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
|
|
while (m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked()) {
|
|
if (m_aborted.load())
|
|
return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Read aborted"sv);
|
|
if (m_read_position >= m_coded_frames.size() && m_reached_end_of_stream)
|
|
return Media::DecoderError::with_description(Media::DecoderErrorCategory::EndOfStream, "End of stream"sv);
|
|
m_data_changed.wait();
|
|
}
|
|
|
|
m_last_returned_timestamp = m_coded_frames[m_read_position].timestamp();
|
|
return m_coded_frames[m_read_position++];
|
|
}
|
|
|
|
Media::DecoderErrorOr<Media::CodecID> TrackBufferDemuxer::get_codec_id_for_track(Media::Track const&)
|
|
{
|
|
return m_codec_id;
|
|
}
|
|
|
|
Media::DecoderErrorOr<ReadonlyBytes> TrackBufferDemuxer::get_codec_initialization_data_for_track(Media::Track const&)
|
|
{
|
|
return m_codec_initialization_data.bytes();
|
|
}
|
|
|
|
Media::DecoderErrorOr<Media::DemuxerSeekResult> TrackBufferDemuxer::seek_to_most_recent_keyframe(Media::Track const&, AK::Duration timestamp, Media::DemuxerSeekOptions)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
|
|
size_t best_position = 0;
|
|
AK::Duration best_timestamp;
|
|
|
|
while (true) {
|
|
if (m_aborted.load())
|
|
return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Seek aborted"sv);
|
|
|
|
best_timestamp = AK::Duration::max();
|
|
|
|
for (size_t i = 0; i < m_coded_frames.size(); i++) {
|
|
auto const& frame = m_coded_frames[i];
|
|
if (frame.timestamp() >= timestamp)
|
|
break;
|
|
if (frame.is_keyframe()) {
|
|
best_position = i;
|
|
best_timestamp = frame.timestamp();
|
|
}
|
|
}
|
|
|
|
auto max_gap = maximum_time_range_gap();
|
|
if (timestamp >= best_timestamp) {
|
|
auto has_gap = [&] {
|
|
auto prior_timestamp = m_coded_frames[best_position].timestamp();
|
|
for (size_t i = best_position + 1; i < m_coded_frames.size(); i++) {
|
|
auto const& frame = m_coded_frames[i];
|
|
auto delta = frame.timestamp() - prior_timestamp;
|
|
if (delta > max_gap)
|
|
return true;
|
|
if (frame.timestamp() >= timestamp)
|
|
break;
|
|
prior_timestamp = frame.timestamp();
|
|
}
|
|
return timestamp - prior_timestamp > max_gap;
|
|
}();
|
|
if (!has_gap)
|
|
break;
|
|
} else if (best_position < m_coded_frames.size()) {
|
|
auto& future_frame = m_coded_frames[best_position];
|
|
if (future_frame.is_keyframe() && future_frame.timestamp() - timestamp <= max_gap) {
|
|
best_timestamp = timestamp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_data_changed.wait();
|
|
}
|
|
|
|
m_read_position = best_position;
|
|
m_last_returned_timestamp = best_timestamp;
|
|
m_data_changed.broadcast();
|
|
return Media::DemuxerSeekResult::MovedPosition;
|
|
}
|
|
|
|
Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::duration_of_track(Media::Track const&)
|
|
{
|
|
return AK::Duration::zero();
|
|
}
|
|
|
|
Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::total_duration()
|
|
{
|
|
return AK::Duration::zero();
|
|
}
|
|
|
|
Media::TimeRanges TrackBufferDemuxer::buffered_time_ranges() const
|
|
{
|
|
return track_buffer_ranges();
|
|
}
|
|
|
|
void TrackBufferDemuxer::set_blocking_reads_aborted_for_track(Media::Track const&)
|
|
{
|
|
m_aborted.store(true);
|
|
Sync::MutexLocker locker { m_mutex };
|
|
m_data_changed.broadcast();
|
|
}
|
|
|
|
void TrackBufferDemuxer::reset_blocking_reads_aborted_for_track(Media::Track const&)
|
|
{
|
|
m_aborted.store(false);
|
|
}
|
|
|
|
bool TrackBufferDemuxer::is_read_blocked_for_track(Media::Track const&)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
if (m_aborted.load())
|
|
return false;
|
|
return m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked();
|
|
}
|
|
|
|
}
|