mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
This is used by YouTube occasionally when seeking, and if it doesn't do its job, the player gets stuck.
765 lines
36 KiB
C++
765 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/SourceBufferPrototype.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.
|
|
}
|
|
|
|
}
|