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;
};
}