/* * Copyright (c) 2024, Jelle Raaijmakers * Copyright (c) 2026, Gregory Bertilson * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(realm)) , m_video_tracks(realm.create(realm)) , m_text_tracks(realm.create(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 event_handler) { set_event_handler_attribute(EventNames::updatestart, event_handler); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onupdatestart GC::Ptr SourceBuffer::onupdatestart() { return event_handler_attribute(EventNames::updatestart); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onupdate void SourceBuffer::set_onupdate(GC::Ptr event_handler) { set_event_handler_attribute(EventNames::update, event_handler); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onupdate GC::Ptr SourceBuffer::onupdate() { return event_handler_attribute(EventNames::update); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onupdateend void SourceBuffer::set_onupdateend(GC::Ptr event_handler) { set_event_handler_attribute(EventNames::updateend, event_handler); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onupdateend GC::Ptr SourceBuffer::onupdateend() { return event_handler_attribute(EventNames::updateend); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onerror void SourceBuffer::set_onerror(GC::Ptr event_handler) { set_event_handler_attribute(EventNames::error, event_handler); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onerror GC::Ptr SourceBuffer::onerror() { return event_handler_attribute(EventNames::error); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onabort void SourceBuffer::set_onabort(GC::Ptr event_handler) { set_event_handler_attribute(EventNames::abort, event_handler); } // https://w3c.github.io/media-source/#dom-sourcebuffer-onabort GC::Ptr 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 parser = [&]() -> NonnullOwnPtr { if (mime_type->subtype() == "webm") return make(); 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 SourceBuffer::buffered() { auto time_ranges = realm().create(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 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 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 SourceBuffer::append_buffer(GC::Root 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 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 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) { m_processor->set_reached_end_of_stream(); } void SourceBuffer::clear_reached_end_of_stream(Badge) { 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(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(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(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. } }