LibWeb: Implement TimeRanges and HTMLMediaElement.seekable()

This commit is contained in:
Psychpsyo 2025-02-14 11:22:12 +01:00 committed by Andrew Kaster
parent 3952ff4786
commit aa243000f3
Notes: github-actions[bot] 2025-02-18 17:46:30 +00:00
5 changed files with 103 additions and 18 deletions

View file

@ -185,6 +185,18 @@ GC::Ref<TimeRanges> HTMLMediaElement::played() const
return time_ranges;
}
// https://html.spec.whatwg.org/multipage/media.html#dom-media-seekable
GC::Ref<TimeRanges> 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<TimeRanges>(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,
// 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 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.
// 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<double> 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

View file

@ -58,6 +58,7 @@ public:
[[nodiscard]] GC::Ref<TimeRanges> buffered() const;
[[nodiscard]] GC::Ref<TimeRanges> played() const;
[[nodiscard]] GC::Ref<TimeRanges> seekable() const;
static inline constexpr auto supported_video_subtypes = Array {
"webm"sv,

View file

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

View file

@ -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<double> 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,
// 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 0.0;
return m_ranges[index].start;
}
// https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-end
double TimeRanges::end(u32) const
WebIDL::ExceptionOr<double> 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,
// 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 0.0;
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;
}
}

View file

@ -8,22 +8,39 @@
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
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<double> start(u32 index) const;
// https://html.spec.whatwg.org/multipage/media.html#dom-timeranges-end
WebIDL::ExceptionOr<double> 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<Range> m_ranges;
};
}