ladybird/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp
Zaggy1024 e627376368 LibWeb: Implement some of SourceBuffer.appendBuffer()
Currently, it just fires the error event immediately.
2026-04-01 02:54:22 -05:00

228 lines
8.6 KiB
C++

/*
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/SourceBufferPrototype.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/HTMLMediaElement.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/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_processor->set_append_error_callback([self = GC::Weak(*this)]() {
if (!self)
return;
self->run_append_error_algorithm();
});
}
SourceBuffer::~SourceBuffer() = default;
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);
}
// 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);
}
bool SourceBuffer::updating() const
{
return m_processor->updating();
}
// 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/#sourcebuffer-buffer-append
void SourceBuffer::run_buffer_append_algorithm()
{
// 1. Run the segment parser loop algorithm.
// NB: Steps 2-5 (error handling, updating flag, update/updateend events) are handled by the segment parser loop
// done callback set up in the constructor.
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);
}
}