HTMLVideoElement.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibGfx/Bitmap.h>
  8. #include <LibWeb/Bindings/HTMLVideoElementPrototype.h>
  9. #include <LibWeb/Bindings/Intrinsics.h>
  10. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  11. #include <LibWeb/DOM/Document.h>
  12. #include <LibWeb/Fetch/Fetching/Fetching.h>
  13. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  14. #include <LibWeb/Fetch/Infrastructure/FetchController.h>
  15. #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
  16. #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
  17. #include <LibWeb/HTML/HTMLVideoElement.h>
  18. #include <LibWeb/HTML/VideoTrack.h>
  19. #include <LibWeb/HTML/VideoTrackList.h>
  20. #include <LibWeb/Layout/VideoBox.h>
  21. #include <LibWeb/Painting/Paintable.h>
  22. #include <LibWeb/Platform/ImageCodecPlugin.h>
  23. namespace Web::HTML {
  24. JS_DEFINE_ALLOCATOR(HTMLVideoElement);
  25. HTMLVideoElement::HTMLVideoElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  26. : HTMLMediaElement(document, move(qualified_name))
  27. {
  28. }
  29. HTMLVideoElement::~HTMLVideoElement() = default;
  30. void HTMLVideoElement::initialize(JS::Realm& realm)
  31. {
  32. Base::initialize(realm);
  33. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLVideoElement);
  34. }
  35. void HTMLVideoElement::finalize()
  36. {
  37. Base::finalize();
  38. for (auto video_track : video_tracks()->video_tracks())
  39. video_track->stop_video({});
  40. }
  41. void HTMLVideoElement::visit_edges(Cell::Visitor& visitor)
  42. {
  43. Base::visit_edges(visitor);
  44. visitor.visit(m_video_track);
  45. visitor.visit(m_fetch_controller);
  46. }
  47. void HTMLVideoElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value)
  48. {
  49. Base::attribute_changed(name, old_value, value);
  50. if (name == HTML::AttributeNames::poster) {
  51. determine_element_poster_frame(value).release_value_but_fixme_should_propagate_errors();
  52. }
  53. }
  54. JS::GCPtr<Layout::Node> HTMLVideoElement::create_layout_node(CSS::StyleProperties style)
  55. {
  56. return heap().allocate<Layout::VideoBox>(document(), *this, move(style));
  57. }
  58. void HTMLVideoElement::adjust_computed_style(CSS::StyleProperties& style)
  59. {
  60. // https://drafts.csswg.org/css-display-3/#unbox
  61. if (style.display().is_contents())
  62. style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
  63. }
  64. Layout::VideoBox* HTMLVideoElement::layout_node()
  65. {
  66. return static_cast<Layout::VideoBox*>(Node::layout_node());
  67. }
  68. Layout::VideoBox const* HTMLVideoElement::layout_node() const
  69. {
  70. return static_cast<Layout::VideoBox const*>(Node::layout_node());
  71. }
  72. // https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
  73. u32 HTMLVideoElement::video_width() const
  74. {
  75. // The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
  76. // attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
  77. // is HAVE_NOTHING, then the attributes must return 0.
  78. if (ready_state() == ReadyState::HaveNothing)
  79. return 0;
  80. return m_video_width;
  81. }
  82. // https://html.spec.whatwg.org/multipage/media.html#dom-video-videoheight
  83. u32 HTMLVideoElement::video_height() const
  84. {
  85. // The videoWidth IDL attribute must return the intrinsic width of the video in CSS pixels. The videoHeight IDL
  86. // attribute must return the intrinsic height of the video in CSS pixels. If the element's readyState attribute
  87. // is HAVE_NOTHING, then the attributes must return 0.
  88. if (ready_state() == ReadyState::HaveNothing)
  89. return 0;
  90. return m_video_height;
  91. }
  92. void HTMLVideoElement::set_video_track(JS::GCPtr<HTML::VideoTrack> video_track)
  93. {
  94. set_needs_style_update(true);
  95. document().set_needs_layout();
  96. if (m_video_track)
  97. m_video_track->pause_video({});
  98. m_video_track = video_track;
  99. }
  100. void HTMLVideoElement::set_current_frame(Badge<VideoTrack>, RefPtr<Gfx::Bitmap> frame, double position)
  101. {
  102. m_current_frame = { move(frame), position };
  103. if (paintable())
  104. paintable()->set_needs_display();
  105. }
  106. void HTMLVideoElement::on_playing()
  107. {
  108. if (m_video_track)
  109. m_video_track->play_video({});
  110. }
  111. void HTMLVideoElement::on_paused()
  112. {
  113. if (m_video_track)
  114. m_video_track->pause_video({});
  115. }
  116. void HTMLVideoElement::on_seek(double position, MediaSeekMode seek_mode)
  117. {
  118. if (m_video_track)
  119. m_video_track->seek(AK::Duration::from_milliseconds(position * 1000.0), seek_mode);
  120. }
  121. // https://html.spec.whatwg.org/multipage/media.html#attr-video-poster
  122. WebIDL::ExceptionOr<void> HTMLVideoElement::determine_element_poster_frame(Optional<String> const& poster)
  123. {
  124. auto& realm = this->realm();
  125. auto& vm = realm.vm();
  126. m_poster_frame = nullptr;
  127. // 1. If there is an existing instance of this algorithm running for this video element, abort that instance of
  128. // this algorithm without changing the poster frame.
  129. if (m_fetch_controller)
  130. m_fetch_controller->stop_fetch();
  131. // 2. If the poster attribute's value is the empty string or if the attribute is absent, then there is no poster
  132. // frame; return.
  133. if (!poster.has_value() || poster->is_empty())
  134. return {};
  135. // 3. Parse the poster attribute's value relative to the element's node document. If this fails, then there is no
  136. // poster frame; return.
  137. auto url_record = document().parse_url(*poster);
  138. if (!url_record.is_valid())
  139. return {};
  140. // 4. Let request be a new request whose URL is the resulting URL record, client is the element's node document's
  141. // relevant settings object, destination is "image", initiator type is "video", credentials mode is "include",
  142. // and whose use-URL-credentials flag is set.
  143. auto request = Fetch::Infrastructure::Request::create(vm);
  144. request->set_url(move(url_record));
  145. request->set_client(&document().relevant_settings_object());
  146. request->set_destination(Fetch::Infrastructure::Request::Destination::Image);
  147. request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Video);
  148. request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
  149. request->set_use_url_credentials(true);
  150. // 5. Fetch request. This must delay the load event of the element's node document.
  151. Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  152. m_load_event_delayer.emplace(document());
  153. fetch_algorithms_input.process_response = [this](auto response) mutable {
  154. ScopeGuard guard { [&] { m_load_event_delayer.clear(); } };
  155. auto& realm = this->realm();
  156. auto& global = document().realm().global_object();
  157. if (response->is_network_error())
  158. return;
  159. if (response->type() == Fetch::Infrastructure::Response::Type::Opaque || response->type() == Fetch::Infrastructure::Response::Type::OpaqueRedirect) {
  160. auto& filtered_response = static_cast<Fetch::Infrastructure::FilteredResponse&>(*response);
  161. response = filtered_response.internal_response();
  162. }
  163. auto on_image_data_read = JS::create_heap_function(heap(), [this](ByteBuffer image_data) mutable {
  164. m_fetch_controller = nullptr;
  165. // 6. If an image is thus obtained, the poster frame is that image. Otherwise, there is no poster frame.
  166. (void)Platform::ImageCodecPlugin::the().decode_image(
  167. image_data,
  168. [strong_this = JS::Handle(*this)](Web::Platform::DecodedImage& image) -> ErrorOr<void> {
  169. if (!image.frames.is_empty())
  170. strong_this->m_poster_frame = move(image.frames[0].bitmap);
  171. return {};
  172. },
  173. [](auto&) {});
  174. });
  175. VERIFY(response->body());
  176. auto empty_algorithm = JS::create_heap_function(heap(), [](JS::Value) {});
  177. response->body()->fully_read(realm, on_image_data_read, empty_algorithm, JS::NonnullGCPtr { global });
  178. };
  179. m_fetch_controller = TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
  180. return {};
  181. }
  182. }