Files
ladybird/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp
Shannon Booth fd44da6829 LibWeb/Bindings: Emit one bindings header and cpp per IDL
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.

This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.

Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
2026-04-21 07:36:13 +02:00

766 lines
36 KiB
C++

/*
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
* Copyright (c) 2026, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibMedia/PlaybackManager.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/SourceBuffer.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/AudioTrackList.h>
#include <LibWeb/HTML/HTMLMediaElement.h>
#include <LibWeb/HTML/TextTrackList.h>
#include <LibWeb/HTML/TimeRanges.h>
#include <LibWeb/HTML/VideoTrackList.h>
#include <LibWeb/MediaSourceExtensions/EventNames.h>
#include <LibWeb/MediaSourceExtensions/MediaSource.h>
#include <LibWeb/MediaSourceExtensions/SourceBuffer.h>
#include <LibWeb/MediaSourceExtensions/SourceBufferList.h>
#include <LibWeb/MediaSourceExtensions/SourceBufferProcessor.h>
#include <LibWeb/MediaSourceExtensions/TrackBuffer.h>
#include <LibWeb/MediaSourceExtensions/TrackBufferDemuxer.h>
#include <LibWeb/MediaSourceExtensions/WebMByteStreamParser.h>
#include <LibWeb/MimeSniff/MimeType.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/QuotaExceededError.h>
namespace Web::MediaSourceExtensions {
GC_DEFINE_ALLOCATOR(SourceBuffer);
SourceBuffer::SourceBuffer(JS::Realm& realm, MediaSource& media_source)
: DOM::EventTarget(realm)
, m_media_source(media_source)
, m_processor(adopt_ref(*new SourceBufferProcessor()))
, m_audio_tracks(realm.create<HTML::AudioTrackList>(realm))
, m_video_tracks(realm.create<HTML::VideoTrackList>(realm))
, m_text_tracks(realm.create<HTML::TextTrackList>(realm))
{
m_processor->set_duration_change_callback([self = GC::Weak(*this)](double new_duration) {
if (!self)
return;
// https://w3c.github.io/media-source/#sourcebuffer-init-segment-received
// 1. Update the duration attribute if it currently equals NaN:
if (isnan(self->m_media_source->duration()))
self->m_media_source->run_duration_change_algorithm(new_duration);
});
m_processor->set_first_initialization_segment_callback([self = GC::Weak(*this)](InitializationSegmentData&& init_data) {
if (!self)
return;
self->on_first_initialization_segment_processed(init_data);
});
m_processor->set_append_error_callback([self = GC::Weak(*this)]() {
if (!self)
return;
self->run_append_error_algorithm();
});
m_processor->set_coded_frame_processing_done_callback([self = GC::Weak(*this)]() {
if (!self)
return;
self->update_ready_state_and_duration_after_coded_frame_processing();
});
m_processor->set_append_done_callback([self = GC::Weak(*this)]() {
if (!self)
return;
self->finish_buffer_append();
});
}
SourceBuffer::~SourceBuffer() = default;
static Bindings::AppendMode processor_mode_to_bindings(AppendMode mode)
{
switch (mode) {
case AppendMode::Segments:
return Bindings::AppendMode::Segments;
case AppendMode::Sequence:
return Bindings::AppendMode::Sequence;
}
VERIFY_NOT_REACHED();
}
static AppendMode bindings_mode_to_processor(Bindings::AppendMode mode)
{
switch (mode) {
case Bindings::AppendMode::Segments:
return AppendMode::Segments;
case Bindings::AppendMode::Sequence:
return AppendMode::Sequence;
}
VERIFY_NOT_REACHED();
}
static Bindings::TextTrackKind media_track_kind_to_text_track_kind(Media::Track::Kind kind)
{
switch (kind) {
case Media::Track::Kind::Captions:
return Bindings::TextTrackKind::Captions;
case Media::Track::Kind::Descriptions:
return Bindings::TextTrackKind::Descriptions;
case Media::Track::Kind::Metadata:
return Bindings::TextTrackKind::Metadata;
case Media::Track::Kind::Subtitles:
return Bindings::TextTrackKind::Subtitles;
default:
return Bindings::TextTrackKind::Metadata;
}
}
void SourceBuffer::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(SourceBuffer);
Base::initialize(realm);
}
void SourceBuffer::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_media_source);
visitor.visit(m_audio_tracks);
visitor.visit(m_video_tracks);
visitor.visit(m_text_tracks);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdatestart
void SourceBuffer::set_onupdatestart(GC::Ptr<WebIDL::CallbackType> event_handler)
{
set_event_handler_attribute(EventNames::updatestart, event_handler);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdatestart
GC::Ptr<WebIDL::CallbackType> SourceBuffer::onupdatestart()
{
return event_handler_attribute(EventNames::updatestart);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdate
void SourceBuffer::set_onupdate(GC::Ptr<WebIDL::CallbackType> event_handler)
{
set_event_handler_attribute(EventNames::update, event_handler);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdate
GC::Ptr<WebIDL::CallbackType> SourceBuffer::onupdate()
{
return event_handler_attribute(EventNames::update);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdateend
void SourceBuffer::set_onupdateend(GC::Ptr<WebIDL::CallbackType> event_handler)
{
set_event_handler_attribute(EventNames::updateend, event_handler);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onupdateend
GC::Ptr<WebIDL::CallbackType> SourceBuffer::onupdateend()
{
return event_handler_attribute(EventNames::updateend);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onerror
void SourceBuffer::set_onerror(GC::Ptr<WebIDL::CallbackType> event_handler)
{
set_event_handler_attribute(EventNames::error, event_handler);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onerror
GC::Ptr<WebIDL::CallbackType> SourceBuffer::onerror()
{
return event_handler_attribute(EventNames::error);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onabort
void SourceBuffer::set_onabort(GC::Ptr<WebIDL::CallbackType> event_handler)
{
set_event_handler_attribute(EventNames::abort, event_handler);
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-onabort
GC::Ptr<WebIDL::CallbackType> SourceBuffer::onabort()
{
return event_handler_attribute(EventNames::abort);
}
void SourceBuffer::set_content_type(String const& type)
{
auto mime_type = MimeSniff::MimeType::parse(type);
VERIFY(mime_type.has_value());
NonnullOwnPtr<ByteStreamParser> parser = [&]() -> NonnullOwnPtr<ByteStreamParser> {
if (mime_type->subtype() == "webm")
return make<WebMByteStreamParser>();
VERIFY_NOT_REACHED();
}();
m_processor->set_parser(move(parser));
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-mode
Bindings::AppendMode SourceBuffer::mode() const
{
return processor_mode_to_bindings(m_processor->mode());
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-updating
bool SourceBuffer::updating() const
{
return m_processor->updating();
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-buffered
GC::Ref<HTML::TimeRanges> SourceBuffer::buffered()
{
auto time_ranges = realm().create<HTML::TimeRanges>(realm());
// FIXME: 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw
// an InvalidStateError exception and abort these steps.
// NB: Further steps to intersect the buffered ranges of the track buffers are implemented within
// SourceBufferProcessor::buffered_ranges() below, since it has access to the track buffers.
auto ranges = m_processor->buffered_ranges();
for (auto const& range : ranges)
time_ranges->add_range(range.start.to_seconds_f64(), range.end.to_seconds_f64());
return time_ranges;
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-mode
WebIDL::ExceptionOr<void> SourceBuffer::set_mode(Bindings::AppendMode mode)
{
// 1. If this object has been removed from the sourceBuffers attribute of the parent media source
// then throw an InvalidStateError exception and abort these steps.
if (!m_media_source->source_buffers()->contains(*this))
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer has been removed"_utf16);
// 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
if (updating())
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer is updating"_utf16);
// 3. If the [[generate timestamps flag]] equals true and the new value equals "segments",
// then throw a TypeError exception and abort these steps.
if (m_processor->generate_timestamps_flag() && mode == Bindings::AppendMode::Segments)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot set mode to 'segments' when generate timestamps flag is true"sv };
// 4. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
if (m_media_source->ready_state() == Bindings::ReadyState::Ended) {
// 1. Set the readyState attribute of the parent media source to "open"
// 2. Queue a task to fire an event named sourceopen at the parent media source.
m_media_source->set_ready_state_to_open_and_fire_sourceopen_event();
}
// 5. If the [[append state]] equals PARSING_MEDIA_SEGMENT, then throw an InvalidStateError exception
// and abort these steps.
if (m_processor->is_parsing_media_segment())
return WebIDL::InvalidStateError::create(realm(), "Cannot change mode while parsing a media segment"_utf16);
// 6. If the new value equals "sequence", then set the [[group start timestamp]] to the [[group end timestamp]].
if (mode == Bindings::AppendMode::Sequence)
m_processor->set_group_start_timestamp(m_processor->group_end_timestamp());
// 7. Update the attribute to the new value.
m_processor->set_mode(bindings_mode_to_processor(mode));
return {};
}
// https://w3c.github.io/media-source/#sourcebuffer-prepare-append
WebIDL::ExceptionOr<void> SourceBuffer::prepare_append()
{
// FIXME: Support MediaSourceExtensions in workers.
if (!m_media_source->media_element_assigned_to())
return WebIDL::InvalidStateError::create(realm(), "Unsupported in workers"_utf16);
// 1. If the SourceBuffer has been removed from the sourceBuffers attribute of the parent media source then throw an
// InvalidStateError exception and abort these steps.
if (!m_media_source->source_buffers()->contains(*this))
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer has been removed"_utf16);
// 2. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
if (updating())
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer is already updating"_utf16);
// 3. Let recent element error be determined as follows:
auto recent_element_error = [&] {
// If the MediaSource was constructed in a Window
if (m_media_source->media_element_assigned_to()) {
// Let recent element error be true if the HTMLMediaElement's error attribute is not null.
// If that attribute is null, then let recent element error be false.
return m_media_source->media_element_assigned_to()->error() != nullptr;
}
// Otherwise
// FIXME: Let recent element error be the value resulting from the steps for the Window case,
// but run on the Window HTMLMediaElement on any change to its error attribute and
// communicated by using [[port to worker]] implicit messages.
// If such a message has not yet been received, then let recent element error be false.
VERIFY_NOT_REACHED();
}();
// 4. If recent element error is true, then throw an InvalidStateError exception and abort these steps.
if (recent_element_error)
return WebIDL::InvalidStateError::create(realm(), "Element has a recent error"_utf16);
// 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
if (m_media_source->ready_state() == Bindings::ReadyState::Ended) {
// 1. Set the readyState attribute of the parent media source to "open"
// 2. Queue a task to fire an event named sourceopen at the parent media source.
m_media_source->set_ready_state_to_open_and_fire_sourceopen_event();
}
// 6. Run the coded frame eviction algorithm.
m_processor->run_coded_frame_eviction();
// 7. If the [[buffer full flag]] equals true, then throw a QuotaExceededError exception and abort these steps.
if (m_processor->is_buffer_full())
return WebIDL::QuotaExceededError::create(realm(), "Buffer is full"_utf16);
return {};
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-appendbuffer
WebIDL::ExceptionOr<void> SourceBuffer::append_buffer(GC::Root<WebIDL::BufferSource> const& data)
{
// 1. Run the prepare append algorithm.
TRY(prepare_append());
// 2. Add data to the end of the [[input buffer]].
if (auto array_buffer = data->viewed_array_buffer(); array_buffer && !array_buffer->is_detached()) {
auto bytes = array_buffer->buffer().bytes().slice(data->byte_offset(), data->byte_length());
m_processor->append_to_input_buffer(bytes);
}
// 3. Set the updating attribute to true.
m_processor->set_updating(true);
// 4. Queue a task to fire an event named updatestart at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::updatestart));
}));
// 5. Asynchronously run the buffer append algorithm.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
run_buffer_append_algorithm();
}));
return {};
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-abort
WebIDL::ExceptionOr<void> SourceBuffer::abort()
{
// 1. If this object has been removed from the sourceBuffers attribute of the parent media source
// then throw an InvalidStateError exception and abort these steps.
if (!m_media_source->source_buffers()->contains(*this))
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer has been removed"_utf16);
// 2. If the readyState attribute of the parent media source is not in the "open" state
// then throw an InvalidStateError exception and abort these steps.
if (m_media_source->ready_state() != Bindings::ReadyState::Open)
return WebIDL::InvalidStateError::create(realm(), "MediaSource is not open"_utf16);
// FIXME: 3. If the range removal algorithm is running, then throw an InvalidStateError exception and abort these steps.
// 4. If the updating attribute equals true, then run the following steps:
if (updating()) {
// 4.1. Abort the buffer append algorithm if it is running.
// FIXME: The buffer append algorithm cannot be running in parallel with this code. However, when
// it can, this will need additional work.
// 4.2. Set the updating attribute to false.
m_processor->set_updating(false);
// 4.3. Queue a task to fire an event named abort at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::abort));
}));
// 4.4. Queue a task to fire an event named updateend at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::updateend));
}));
}
// 5. Run the reset parser state algorithm.
m_processor->reset_parser_state();
// FIXME: 6. Set appendWindowStart to the presentation start time.
// 7. Set appendWindowEnd to positive Infinity.
return {};
}
// https://w3c.github.io/media-source/#dom-sourcebuffer-changetype
WebIDL::ExceptionOr<void> SourceBuffer::change_type(String const& type)
{
// 1. If type is an empty string then throw a TypeError exception and abort these steps.
if (type.is_empty())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Type cannot be empty"sv };
// 2. If this object has been removed from the sourceBuffers attribute of the parent media source,
// then throw an InvalidStateError exception and abort these steps.
if (!m_media_source->source_buffers()->contains(*this))
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer has been removed"_utf16);
// 3. If the updating attribute equals true, then throw an InvalidStateError exception and abort these steps.
if (updating())
return WebIDL::InvalidStateError::create(realm(), "SourceBuffer is updating"_utf16);
// 4. If type contains a MIME type that is not supported or contains a MIME type that is not supported
// with the types specified (currently or previously) of SourceBuffer objects in the sourceBuffers
// attribute of the parent media source, then throw a NotSupportedError exception and abort these steps.
if (!MediaSource::is_type_supported(type))
return WebIDL::NotSupportedError::create(realm(), "Type is not supported"_utf16);
// 5. If the readyState attribute of the parent media source is in the "ended" state then run the following steps:
if (m_media_source->ready_state() == Bindings::ReadyState::Ended) {
// 5.1. Set the readyState attribute of the parent media source to "open"
// 5.2. Queue a task to fire an event named sourceopen at the parent media source.
m_media_source->set_ready_state_to_open_and_fire_sourceopen_event();
}
// 6. Run the reset parser state algorithm.
m_processor->reset_parser_state();
// AD-HOC: Recreate the byte stream parser for the new type.
set_content_type(type);
// 7. Update the [[generate timestamps flag]] on this SourceBuffer object to the value in the
// "Generate Timestamps Flag" column of the byte stream format registry entry that is associated with type.
// FIXME: Look up the generate timestamps flag from the registry
// For now, assume false for most formats
m_processor->set_generate_timestamps_flag(false);
// 8. If the [[generate timestamps flag]] equals true:
// Set the mode attribute on this SourceBuffer object to "sequence", including running the
// associated steps for that attribute being set.
// Otherwise:
// Keep the previous value of the mode attribute on this SourceBuffer object, without running
// any associated steps for that attribute being set.
if (m_processor->generate_timestamps_flag())
TRY(set_mode(Bindings::AppendMode::Sequence));
// 9. Set the [[pending initialization segment for changeType flag]] on this SourceBuffer object to true.
m_processor->set_pending_initialization_segment_for_change_type_flag(true);
return {};
}
void SourceBuffer::set_reached_end_of_stream(Badge<MediaSource>)
{
m_processor->set_reached_end_of_stream();
}
void SourceBuffer::clear_reached_end_of_stream(Badge<MediaSource>)
{
m_processor->clear_reached_end_of_stream();
}
// https://w3c.github.io/media-source/#sourcebuffer-buffer-append
void SourceBuffer::run_buffer_append_algorithm()
{
// 1. Run the segment parser loop algorithm.
// NB: SourceBufferProcessor's append done callback invokes finish_buffer_append for the rest of this algorithm.
m_processor->run_segment_parser_loop();
}
// https://w3c.github.io/media-source/#sourcebuffer-append-error
void SourceBuffer::run_append_error_algorithm()
{
// 1. Run the reset parser state algorithm.
m_processor->reset_parser_state();
// 2. Set the updating attribute to false.
m_processor->set_updating(false);
// 3. Queue a task to fire an event named error at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::error));
}));
// 4. Queue a task to fire an event named updateend at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::updateend));
}));
// 5. Run the end of stream algorithm with the error parameter set to "decode".
m_media_source->run_end_of_stream_algorithm({}, Bindings::EndOfStreamError::Decode);
}
// https://w3c.github.io/media-source/#sourcebuffer-init-segment-received
void SourceBuffer::on_first_initialization_segment_processed(InitializationSegmentData const& init_data)
{
auto& realm = this->realm();
// 4. Let active track flag equal false.
bool active_track_flag = false;
// 5. If the [[first initialization segment received flag]] is false, then run the following steps:
{
// FIXME: 1. If the initialization segment contains tracks with codecs the user agent does not support,
// then run the append error algorithm and abort these steps.
// 2. For each audio track in the initialization segment, run following steps:
for (auto const& audio_track_info : init_data.audio_tracks) {
auto const& audio_track = audio_track_info.track;
// 1. Let audio byte stream track ID be the Track ID for the current track being processed.
// NB: Used by the processor when creating the track buffer.
// 2. Let audio language be a BCP 47 language tag for the language specified in the initialization
// segment for this track or an empty string if no language info is present.
// 3. If audio language equals the 'und' BCP 47 value, then assign an empty string to audio language.
// 4. Let audio label be a label specified in the initialization segment for this track or an empty
// string if no label info is present.
// NB: All of the above is handled by the MediaTrackBase constructor.
// 5. Let audio kinds be a sequence of kind strings specified in the initialization segment for this
// track or a sequence with a single empty string element in it if no kind information is provided.
Array audio_kinds = { audio_track.kind() };
// 6. For each value in audio kinds, run the following steps:
for (auto const& current_audio_kind : audio_kinds) {
// 1. Let current audio kind equal the value from audio kinds for this iteration of the loop.
// 2. Let new audio track be a new AudioTrack object.
auto new_audio_track = realm.create<HTML::AudioTrack>(realm, *m_media_source->media_element_assigned_to(), audio_track);
// 3. Generate a unique ID and assign it to the id property on new audio track.
auto unique_id = m_media_source->next_track_id();
new_audio_track->set_id(unique_id);
// 4. Assign audio language to the language property on new audio track.
// 5. Assign audio label to the label property on new audio track.
// 6. Assign current audio kind to the kind property on new audio track.
new_audio_track->set_kind(current_audio_kind);
// 7. If this SourceBuffer object's audioTracks's length equals 0, then run the following steps:
if (m_audio_tracks->length() == 0) {
// 1. Set the enabled property on new audio track to true.
new_audio_track->set_enabled(true);
// 2. Set active track flag to true.
active_track_flag = true;
}
// 8. Add new audio track to the audioTracks attribute on this SourceBuffer object.
m_audio_tracks->add_track(new_audio_track);
// 9. If the parent media source was constructed in a DedicatedWorkerGlobalScope:
if (!m_media_source->media_element_assigned_to()) {
// FIXME: Post an internal `create track mirror` message...
VERIFY_NOT_REACHED();
}
// Otherwise:
// Add new audio track to the audioTracks attribute on the HTMLMediaElement.
m_media_source->media_element_assigned_to()->audio_tracks()->add_track(new_audio_track);
}
// 7. Create a new track buffer to store coded frames for this track.
// 8. Add the track description for this track to the track buffer.
// NB: Track buffers and their demuxers are created by the processor. Here we pass the
// demuxer to the PlaybackManager so the decoder thread can read coded frames from it.
m_media_source->media_element_assigned_to()->playback_manager().add_media_source(audio_track_info.demuxer);
}
// 3. For each video track in the initialization segment, run following steps:
for (auto const& video_track_info : init_data.video_tracks) {
auto const& video_track = video_track_info.track;
// 1. Let video byte stream track ID be the Track ID for the current track being processed.
// NB: Used by the processor when creating the track buffer.
// 2. Let video language be a BCP 47 language tag for the language specified in the initialization
// segment for this track or an empty string if no language info is present.
// 3. If video language equals the 'und' BCP 47 value, then assign an empty string to video language.
// 4. Let video label be a label specified in the initialization segment for this track or an empty
// string if no label info is present.
// NB: All of the above is handled by the MediaTrackBase constructor.
// 5. Let video kinds be a sequence of kind strings specified in the initialization segment for this
// track or a sequence with a single empty string element in it if no kind information is provided.
Array video_kinds = { video_track.kind() };
// 6. For each value in video kinds, run the following steps:
for (auto const& current_video_kind : video_kinds) {
// 1. Let current video kind equal the value from video kinds for this iteration of the loop.
// 2. Let new video track be a new VideoTrack object.
auto new_video_track = realm.create<HTML::VideoTrack>(realm, *m_media_source->media_element_assigned_to(), video_track);
// 3. Generate a unique ID and assign it to the id property on new video track.
auto unique_id = m_media_source->next_track_id();
new_video_track->set_id(unique_id);
// 4. Assign video language to the language property on new video track.
// 5. Assign video label to the label property on new video track.
// 6. Assign current video kind to the kind property on new video track.
new_video_track->set_kind(current_video_kind);
// 7. If this SourceBuffer object's videoTracks's length equals 0, then run the following steps:
if (m_video_tracks->length() == 0) {
// 1. Set the selected property on new video track to true.
new_video_track->set_selected(true);
// 2. Set active track flag to true.
active_track_flag = true;
}
// 8. Add new video track to the videoTracks attribute on this SourceBuffer object.
m_video_tracks->add_track(new_video_track);
// 9. If the parent media source was constructed in a DedicatedWorkerGlobalScope:
if (!m_media_source->media_element_assigned_to()) {
// FIXME: Post an internal `create track mirror` message...
VERIFY_NOT_REACHED();
}
// Otherwise:
// Add new video track to the videoTracks attribute on the HTMLMediaElement.
m_media_source->media_element_assigned_to()->video_tracks()->add_track(new_video_track);
}
// 7. Create a new track buffer to store coded frames for this track.
// 8. Add the track description for this track to the track buffer.
// NB: Track buffers and their demuxers are created by the processor. Here we pass the
// demuxer to the PlaybackManager so the decoder thread can read coded frames from it.
m_media_source->media_element_assigned_to()->playback_manager().add_media_source(video_track_info.demuxer);
}
// 4. For each text track in the initialization segment, run following steps:
for (auto const& text_track_info : init_data.text_tracks) {
auto const& text_track = text_track_info.track;
// 1. Let text byte stream track ID be the Track ID for the current track being processed.
// NB: Used by the processor when creating the track buffer.
// 2. Let text language be a BCP 47 language tag for the language specified in the initialization
// segment for this track or an empty string if no language info is present.
auto text_language = text_track.language();
// 3. If text language equals the 'und' BCP 47 value, then assign an empty string to text language.
if (text_language == u"und"sv)
text_language = ""_utf16;
// 4. Let text label be a label specified in the initialization segment for this track or an empty
// string if no label info is present.
auto const& text_label = text_track.label();
// 5. Let text kinds be a sequence of kind strings specified in the initialization segment for this
// track or a sequence with a single empty string element in it if no kind information is provided.
Array text_kinds = { text_track.kind() };
// 6. For each value in text kinds, run the following steps:
for (auto const& current_text_kind : text_kinds) {
// 1. Let current text kind equal the value from text kinds for this iteration of the loop.
// 2. Let new text track be a new TextTrack object.
auto new_text_track = realm.create<HTML::TextTrack>(realm);
// 3. Generate a unique ID and assign it to the id property on new text track.
auto unique_id = m_media_source->next_track_id();
new_text_track->set_id(unique_id.to_utf8());
// 4. Assign text language to the language property on new text track.
new_text_track->set_language(text_language.to_utf8());
// 5. Assign text label to the label property on new text track.
new_text_track->set_label(text_label.to_utf8());
// 6. Assign current text kind to the kind property on new text track.
new_text_track->set_kind(media_track_kind_to_text_track_kind(current_text_kind));
// FIXME: 7. Populate the remaining properties on new text track with the appropriate information from
// the initialization segment.
// FIXME: 8. If the mode property on new text track equals "showing" or "hidden", then set active track
// flag to true.
// 9. Add new text track to the textTracks attribute on this SourceBuffer object.
m_text_tracks->add_track(new_text_track);
// 10. If the parent media source was constructed in a DedicatedWorkerGlobalScope:
if (!m_media_source->media_element_assigned_to()) {
// FIXME: Post an internal `create track mirror` message...
VERIFY_NOT_REACHED();
}
// Otherwise:
// Add new text track to the textTracks attribute on the HTMLMediaElement.
m_media_source->media_element_assigned_to()->text_tracks()->add_track(new_text_track);
}
// 7. Create a new track buffer to store coded frames for this track.
// 8. Add the track description for this track to the track buffer.
// NB: Track buffers and their demuxers are created by the processor.
// Text track demuxers are not added to the PlaybackManager.
}
// 5. If active track flag equals true, then run the following steps:
if (active_track_flag) {
// 1. Add this SourceBuffer to activeSourceBuffers.
m_media_source->active_source_buffers()->append(*this);
// 2. Queue a task to fire an event named addsourcebuffer at activeSourceBuffers.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [realm = GC::Ref(realm), source_buffers = m_media_source->active_source_buffers()] {
source_buffers->dispatch_event(DOM::Event::create(realm, EventNames::addsourcebuffer));
}));
}
// 6. Set [[first initialization segment received flag]] to true.
// AD-HOC: This is handled within SourceBufferProcessor, since it's observable immediately in the processing
// loop.
}
// 6. Set [[pending initialization segment for changeType flag]] to false.
// AD-HOC: This is handled within SourceBufferProcessor, same as the above inner step 6.
// 7. If the active track flag equals true, then run the following steps:
if (!active_track_flag)
return;
// FIXME: Mirror the following steps to the Window when workers are supported.
auto& media_element = *m_media_source->media_element_assigned_to();
// 8. Use the parent media source's mirror if necessary algorithm to run the following step in Window:
// If the HTMLMediaElement's readyState attribute is greater than HAVE_CURRENT_DATA, then set
// the HTMLMediaElement's readyState attribute to HAVE_METADATA.
// 9. If each object in sourceBuffers of the parent media source has [[first initialization segment received
// flag]] equal to true, then use the parent media source's mirror if necessary algorithm to run the
// following step in Window:
// If the HTMLMediaElement's readyState attribute is HAVE_NOTHING, then set the HTMLMediaElement's
// readyState attribute to HAVE_METADATA.
// NB: These steps are handled by the unified readyState update method on HTMLMediaElement, based on conditions
// that Media Source Extensions requires.
media_element.update_ready_state();
}
// https://w3c.github.io/media-source/#sourcebuffer-buffer-append
void SourceBuffer::finish_buffer_append()
{
// 3. Set the updating attribute to false.
m_processor->set_updating(false);
// 4. Queue a task to fire an event named update at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::update));
}));
// 5. Queue a task to fire an event named updateend at this SourceBuffer object.
m_media_source->queue_a_media_source_task(GC::create_function(heap(), [this] {
dispatch_event(DOM::Event::create(realm(), EventNames::updateend));
}));
}
// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-processing
void SourceBuffer::update_ready_state_and_duration_after_coded_frame_processing()
{
auto media_element = m_media_source->media_element_assigned_to();
VERIFY(media_element);
// AD-HOC: Steps 2-4 (readyState transitions based on new coded frames) are covered by the unified
// readyState update method on HTMLMediaElement.
media_element->update_ready_state();
// FIXME: 5. If the media segment contains data beyond the current duration, then run the duration change
// algorithm with new duration set to the maximum of the current duration and the group end timestamp.
}
}