From aa243000f37d986bf89b51de66fd9949bbfd649c Mon Sep 17 00:00:00 2001 From: Psychpsyo Date: Fri, 14 Feb 2025 11:22:12 +0100 Subject: [PATCH] LibWeb: Implement TimeRanges and HTMLMediaElement.seekable() --- Libraries/LibWeb/HTML/HTMLMediaElement.cpp | 57 ++++++++++++++++++++-- Libraries/LibWeb/HTML/HTMLMediaElement.h | 1 + Libraries/LibWeb/HTML/HTMLMediaElement.idl | 2 +- Libraries/LibWeb/HTML/TimeRanges.cpp | 40 +++++++++++---- Libraries/LibWeb/HTML/TimeRanges.h | 21 +++++++- 5 files changed, 103 insertions(+), 18 deletions(-) diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index b3b47201f51..43014e384d9 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -185,6 +185,18 @@ GC::Ref HTMLMediaElement::played() const return time_ranges; } +// https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable +GC::Ref HTMLMediaElement::seekable() const +{ + auto& realm = this->realm(); + + // The seekable attribute must return a new static normalized TimeRanges object that represents the ranges of the media resource, if any, that the + // user agent is able to seek to, at the time the attribute is evaluated. + auto time_ranges = realm.create(realm); + time_ranges->add_range(0, m_duration); + return time_ranges; +} + // https://html.spec.whatwg.org/multipage/media.html#dom-navigator-canplaytype Bindings::CanPlayTypeResult HTMLMediaElement::can_play_type(StringView type) const { @@ -1566,11 +1578,46 @@ void HTMLMediaElement::seek_element(double playback_position, MediaSeekMode seek if (playback_position < 0) playback_position = 0; - // FIXME: 8. If the (possibly now changed) new playback position is not in one of the ranges given in the seekable attribute, - // then let it be the position in one of the ranges given in the seekable attribute that is the nearest to the new - // playback position. If two positions both satisfy that constraint (i.e. the new playback position is exactly in the - // middle between two ranges in the seekable attribute) then use the position that is closest to the current playback - // position. If there are no ranges given in the seekable attribute then set the seeking IDL attribute to false and return. + // 8. If the (possibly now changed) new playback position is not in one of the ranges given in the seekable attribute, + auto time_ranges = seekable(); + if (!time_ranges->in_range(playback_position)) { + // then let it be the position in one of the ranges given in the seekable attribute that is the nearest to the new + // playback position. + + // If there are no ranges given in the seekable attribute then set the seeking IDL attribute to false and return. + if (time_ranges->length() == 0) { + set_seeking(false); + return; + } + + double nearest_point; + Optional other_nearest_point = {}; + double distance = INFINITY; + for (size_t i = 0; i < time_ranges->length(); i++) { + for (double point : { MUST(time_ranges->start(i)), MUST(time_ranges->end(i)) }) { + auto point_distance = abs(playback_position - point); + if (point_distance < distance) { + nearest_point = point; + other_nearest_point = {}; + distance = point_distance; + } else if (point_distance == distance) { + other_nearest_point = point; + } + } + } + + // If two positions both satisfy that constraint (i.e. the new playback position is exactly in the middle between two ranges + // in the seekable attribute) then use the position that is closest to the current playback position. + if (other_nearest_point.has_value()) { + auto nearest_point_distance = abs(m_current_playback_position - nearest_point); + auto other_nearest_point_distance = abs(m_current_playback_position - other_nearest_point.value()); + if (nearest_point_distance < other_nearest_point_distance) { + playback_position = nearest_point; + } else { + playback_position = other_nearest_point.value(); + } + } + } // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will allow for playback to resume // promptly. If new playback position before this step is before current playback position, then the adjusted new playback position diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Libraries/LibWeb/HTML/HTMLMediaElement.h index e666d7b1c19..99fbbb170ab 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.h +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.h @@ -58,6 +58,7 @@ public: [[nodiscard]] GC::Ref buffered() const; [[nodiscard]] GC::Ref played() const; + [[nodiscard]] GC::Ref seekable() const; static inline constexpr auto supported_video_subtypes = Array { "webm"sv, diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Libraries/LibWeb/HTML/HTMLMediaElement.idl index 22bc813886d..4a09507ff1e 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.idl +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.idl @@ -63,7 +63,7 @@ interface HTMLMediaElement : HTMLElement { attribute double playbackRate; [FIXME] attribute boolean preservesPitch; readonly attribute TimeRanges played; - [FIXME] readonly attribute TimeRanges seekable; + readonly attribute TimeRanges seekable; readonly attribute boolean ended; [Reflect, CEReactions] attribute boolean autoplay; [Reflect, CEReactions] attribute boolean loop; diff --git a/Libraries/LibWeb/HTML/TimeRanges.cpp b/Libraries/LibWeb/HTML/TimeRanges.cpp index c5d6c2fdd96..56ce6d4251f 100644 --- a/Libraries/LibWeb/HTML/TimeRanges.cpp +++ b/Libraries/LibWeb/HTML/TimeRanges.cpp @@ -27,24 +27,44 @@ void TimeRanges::initialize(JS::Realm& realm) // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-length size_t TimeRanges::length() const { - // FIXME: The length IDL attribute must return the number of ranges represented by the object. - return 0; + return m_ranges.size(); } // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-start -double TimeRanges::start(u32) const +WebIDL::ExceptionOr TimeRanges::start(u32 index) const { - // FIXME: The start(index) method must return the position of the start of the indexth range represented by the object, - // in seconds measured from the start of the timeline that the object covers. - return 0.0; + // These methods must throw "IndexSizeError" DOMExceptions if called with an index argument greater than or equal to the number of ranges represented by the object. + if (index >= m_ranges.size()) + return WebIDL::IndexSizeError::create(realm(), "Index argument is greater than or equal to the number of ranges represented by this TimeRanges object"_string); + + // The start(index) method must return the position of the start of the indexth range represented by the object, + // in seconds measured from the start of the timeline that the object covers. + return m_ranges[index].start; } // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-end -double TimeRanges::end(u32) const +WebIDL::ExceptionOr TimeRanges::end(u32 index) const { - // FIXME: The end(index) method must return the position of the end of the indexth range represented by the object, - // in seconds measured from the start of the timeline that the object covers. - return 0.0; + // These methods must throw "IndexSizeError" DOMExceptions if called with an index argument greater than or equal to the number of ranges represented by the object. + if (index >= m_ranges.size()) + return WebIDL::IndexSizeError::create(realm(), "Index argument is greater than or equal to the number of ranges represented by this TimeRanges object"_string); + + // The end(index) method must return the position of the end of the indexth range represented by the object, + // in seconds measured from the start of the timeline that the object covers. + return m_ranges[index].end; +} + +void TimeRanges::add_range(double start, double end) +{ + m_ranges.append({ start, end }); +} +bool TimeRanges::in_range(double point) +{ + for (auto range : m_ranges) { + if (point >= range.start && point <= range.end) + return true; + } + return false; } } diff --git a/Libraries/LibWeb/HTML/TimeRanges.h b/Libraries/LibWeb/HTML/TimeRanges.h index e1d08b4cefa..2fea34d77d8 100644 --- a/Libraries/LibWeb/HTML/TimeRanges.h +++ b/Libraries/LibWeb/HTML/TimeRanges.h @@ -8,22 +8,39 @@ #include #include +#include namespace Web::HTML { +// https://html.spec.whatwg.org/multipage/media.html#time-ranges class TimeRanges final : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(TimeRanges, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(TimeRanges); public: + // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-length size_t length() const; - double start(u32 index) const; - double end(u32 index) const; + + // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-start + WebIDL::ExceptionOr start(u32 index) const; + + // https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-end + WebIDL::ExceptionOr end(u32 index) const; + + void add_range(double start, double end); + bool in_range(double); private: explicit TimeRanges(JS::Realm&); virtual void initialize(JS::Realm&) override; + + struct Range { + double start; + double end; + }; + + Vector m_ranges; }; }