mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-28 12:10:28 +00:00
Set a limit on SourceBuffer size to 150MiB for video and 12MiB for audio, matching Chromium's limits.
369 lines
13 KiB
C++
369 lines
13 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_total_bytes += frame.data().size();
|
|
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_total_bytes -= removed_frame.data().size();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
size_t TrackBufferDemuxer::total_bytes() const
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
return m_total_bytes;
|
|
}
|
|
|
|
bool TrackBufferDemuxer::is_frame_evictable_while_locked(Media::CodedFrame const& frame, AK::Duration current_time) const
|
|
{
|
|
auto time_range_start = current_time;
|
|
auto time_range_end = current_time;
|
|
if (m_last_returned_timestamp.has_value()) {
|
|
time_range_start = min(time_range_start, m_last_returned_timestamp.value());
|
|
time_range_end = max(time_range_end, m_last_returned_timestamp.value());
|
|
}
|
|
return frame.timestamp() < time_range_start || frame.timestamp() > time_range_end;
|
|
}
|
|
|
|
Optional<AK::Duration> TrackBufferDemuxer::earliest_evictable_frame_timestamp(AK::Duration current_time) const
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
if (m_coded_frames.is_empty())
|
|
return {};
|
|
auto const& frame = m_coded_frames[0];
|
|
if (!is_frame_evictable_while_locked(frame, current_time))
|
|
return {};
|
|
return frame.timestamp();
|
|
}
|
|
|
|
size_t TrackBufferDemuxer::take_earliest_frame()
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
auto frame = m_coded_frames.take_first();
|
|
m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
|
|
auto bytes = frame.data().size();
|
|
m_total_bytes -= bytes;
|
|
if (m_read_position > 0)
|
|
m_read_position--;
|
|
return bytes;
|
|
}
|
|
|
|
Optional<AK::Duration> TrackBufferDemuxer::latest_evictable_frame_timestamp(AK::Duration current_time) const
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
if (m_coded_frames.is_empty())
|
|
return {};
|
|
auto const& frame = m_coded_frames.last();
|
|
if (!is_frame_evictable_while_locked(frame, current_time))
|
|
return {};
|
|
return frame.timestamp();
|
|
}
|
|
|
|
size_t TrackBufferDemuxer::take_latest_frame()
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
auto frame = m_coded_frames.take_last();
|
|
m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
|
|
auto bytes = frame.data().size();
|
|
m_total_bytes -= bytes;
|
|
return bytes;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
AK::Duration TrackBufferDemuxer::select_fast_seek_target_for_track(Media::Track const&, AK::Duration target, Media::SeekMode mode)
|
|
{
|
|
Sync::MutexLocker locker { m_mutex };
|
|
if (m_coded_frames.is_empty())
|
|
return target;
|
|
|
|
size_t nearby_index = 0;
|
|
binary_search(m_coded_frames, target, &nearby_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
|
|
return needle <=> frame.timestamp();
|
|
});
|
|
|
|
if (mode == Media::SeekMode::FastBefore) {
|
|
if (m_coded_frames[nearby_index].timestamp() > target)
|
|
return target;
|
|
for (auto i = nearby_index; i-- > 0;) {
|
|
if (m_coded_frames[i].is_keyframe())
|
|
return m_coded_frames[i].timestamp();
|
|
}
|
|
return target;
|
|
}
|
|
|
|
VERIFY(mode == Media::SeekMode::FastAfter);
|
|
auto start = nearby_index;
|
|
if (m_coded_frames[start].timestamp() < target)
|
|
start++;
|
|
for (auto i = start; i < m_coded_frames.size(); i++) {
|
|
if (m_coded_frames[i].is_keyframe())
|
|
return m_coded_frames[i].timestamp();
|
|
}
|
|
return target;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
}
|