mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
This saves us from having our own color conversion code, which was taking up a fair amount of time in VideoDataProvider. With this change, we should be able to play high resolution videos without interruptions on machines where the CPU can keep up with decoding. In order to make this change, ImmutableBitmap is now able to be constructed with YUV data instead of an RBG bitmap. It holds onto a YUVData instance that stores the buffers of image data, since Skia itself doesn't take ownership of them. In order to support greater than 8 bits of color depth, we normalize the 10- or 12-bit color values into a 16-bit range.
198 lines
7.7 KiB
C++
198 lines
7.7 KiB
C++
/*
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibMedia/Sinks/DisplayingVideoSink.h>
|
|
#include <LibWeb/Bindings/HTMLVideoElementPrototype.h>
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
|
#include <LibWeb/CSS/ComputedProperties.h>
|
|
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
|
#include <LibWeb/DOM/Document.h>
|
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
|
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
|
|
#include <LibWeb/Fetch/Infrastructure/FetchController.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
|
#include <LibWeb/HTML/AudioTrackList.h>
|
|
#include <LibWeb/HTML/HTMLVideoElement.h>
|
|
#include <LibWeb/HTML/VideoTrack.h>
|
|
#include <LibWeb/HTML/VideoTrackList.h>
|
|
#include <LibWeb/Layout/VideoBox.h>
|
|
#include <LibWeb/Painting/Paintable.h>
|
|
#include <LibWeb/Platform/ImageCodecPlugin.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
GC_DEFINE_ALLOCATOR(HTMLVideoElement);
|
|
|
|
HTMLVideoElement::HTMLVideoElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
|
: HTMLMediaElement(document, move(qualified_name))
|
|
{
|
|
}
|
|
|
|
HTMLVideoElement::~HTMLVideoElement() = default;
|
|
|
|
void HTMLVideoElement::initialize(JS::Realm& realm)
|
|
{
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLVideoElement);
|
|
Base::initialize(realm);
|
|
}
|
|
|
|
void HTMLVideoElement::finalize()
|
|
{
|
|
Base::finalize();
|
|
}
|
|
|
|
void HTMLVideoElement::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_video_track);
|
|
visitor.visit(m_fetch_controller);
|
|
}
|
|
|
|
void HTMLVideoElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
|
|
{
|
|
Base::attribute_changed(name, old_value, value, namespace_);
|
|
|
|
if (name == HTML::AttributeNames::poster) {
|
|
determine_element_poster_frame(value).release_value_but_fixme_should_propagate_errors();
|
|
}
|
|
}
|
|
|
|
GC::Ptr<Layout::Node> HTMLVideoElement::create_layout_node(GC::Ref<CSS::ComputedProperties> style)
|
|
{
|
|
return heap().allocate<Layout::VideoBox>(document(), *this, style);
|
|
}
|
|
|
|
void HTMLVideoElement::adjust_computed_style(CSS::ComputedProperties& style)
|
|
{
|
|
// https://drafts.csswg.org/css-display-3/#unbox
|
|
if (style.display().is_contents())
|
|
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
|
|
}
|
|
|
|
Layout::VideoBox* HTMLVideoElement::layout_node()
|
|
{
|
|
return static_cast<Layout::VideoBox*>(Node::layout_node());
|
|
}
|
|
|
|
Layout::VideoBox const* HTMLVideoElement::layout_node() const
|
|
{
|
|
return static_cast<Layout::VideoBox const*>(Node::layout_node());
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
|
|
u32 HTMLVideoElement::video_width() const
|
|
{
|
|
// The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
|
|
// attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
|
|
// is HAVE_NOTHING, then the attributes must return 0.
|
|
if (ready_state() == ReadyState::HaveNothing)
|
|
return 0;
|
|
return m_video_width;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/media.html#dom-video-videoheight
|
|
u32 HTMLVideoElement::video_height() const
|
|
{
|
|
// The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
|
|
// attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
|
|
// is HAVE_NOTHING, then the attributes must return 0.
|
|
if (ready_state() == ReadyState::HaveNothing)
|
|
return 0;
|
|
return m_video_height;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/media.html#attr-video-poster
|
|
WebIDL::ExceptionOr<void> HTMLVideoElement::determine_element_poster_frame(Optional<String> const& poster)
|
|
{
|
|
auto& realm = this->realm();
|
|
auto& vm = realm.vm();
|
|
|
|
m_poster_frame = nullptr;
|
|
|
|
// 1. If there is an existing instance of this algorithm running for this video element, abort that instance of
|
|
// this algorithm without changing the poster frame.
|
|
if (m_fetch_controller)
|
|
m_fetch_controller->stop_fetch();
|
|
|
|
// 2. If the poster attribute's value is the empty string or if the attribute is absent, then there is no poster
|
|
// frame; return.
|
|
if (!poster.has_value() || poster->is_empty())
|
|
return {};
|
|
|
|
// 3. Let url be the result of encoding-parsing a URL given the poster attribute's value, relative to the element's node document.
|
|
auto url_record = document().encoding_parse_url(*poster);
|
|
|
|
// 4. If url is failure, then return.
|
|
if (!url_record.has_value())
|
|
return {};
|
|
|
|
// 5. Let request be a new request whose URL is the resulting URL record, client is the element's node document's
|
|
// relevant settings object, destination is "image", initiator type is "video", credentials mode is "include",
|
|
// and whose use-URL-credentials flag is set.
|
|
auto request = Fetch::Infrastructure::Request::create(vm);
|
|
request->set_url(url_record.release_value());
|
|
request->set_client(&document().relevant_settings_object());
|
|
request->set_destination(Fetch::Infrastructure::Request::Destination::Image);
|
|
request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Video);
|
|
request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
|
|
request->set_use_url_credentials(true);
|
|
|
|
// 6. Fetch request. This must delay the load event of the element's node document.
|
|
Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
|
|
m_load_event_delayer.emplace(document());
|
|
|
|
// 7. If an image is thus obtained, the poster frame is that image. Otherwise, there is no poster frame.
|
|
fetch_algorithms_input.process_response = [this](auto response) mutable {
|
|
ScopeGuard guard { [&] { m_load_event_delayer.clear(); } };
|
|
|
|
auto& realm = this->realm();
|
|
auto& global = document().realm().global_object();
|
|
|
|
if (response->is_network_error())
|
|
return;
|
|
|
|
if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) {
|
|
auto& filtered_response = static_cast<Fetch::Infrastructure::FilteredResponse&>(*response);
|
|
response = filtered_response.internal_response();
|
|
}
|
|
|
|
auto on_image_data_read = GC::create_function(heap(), [this](ByteBuffer image_data) mutable {
|
|
m_fetch_controller = nullptr;
|
|
|
|
// 6. If an image is thus obtained, the poster frame is that image. Otherwise, there is no poster frame.
|
|
(void)Platform::ImageCodecPlugin::the().decode_image(
|
|
image_data,
|
|
[strong_this = GC::Root(*this)](Web::Platform::DecodedImage& image) -> ErrorOr<void> {
|
|
if (!image.frames.is_empty())
|
|
strong_this->m_poster_frame = move(image.frames[0].bitmap);
|
|
return {};
|
|
},
|
|
[](auto&) {});
|
|
});
|
|
|
|
VERIFY(response->body());
|
|
auto empty_algorithm = GC::create_function(heap(), [](JS::Value) { });
|
|
|
|
response->body()->fully_read(realm, on_image_data_read, empty_algorithm, GC::Ref { global });
|
|
};
|
|
|
|
m_fetch_controller = Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)));
|
|
|
|
return {};
|
|
}
|
|
|
|
RefPtr<Gfx::ImmutableBitmap> HTMLVideoElement::bitmap() const
|
|
{
|
|
auto const& sink = selected_video_track_sink();
|
|
if (sink == nullptr)
|
|
return nullptr;
|
|
return sink->current_frame();
|
|
}
|
|
|
|
}
|