summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2023-04-10 20:04:46 -0400
committerLinus Groh <mail@linusgroh.de>2023-04-11 19:27:55 +0200
commit4797f078832a2d60ef85d0b1911d150bdf77badc (patch)
tree7bcdc4d2726d4e2fd3218d901853cb5d8394778e
parent33047b38ec0d9ce55bea2f74c00972711e106a30 (diff)
downloadserenity-4797f078832a2d60ef85d0b1911d150bdf77badc.zip
LibWeb: Begin detecting the end of an HTMLMediaElement media resource
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp80
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h4
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl1
-rw-r--r--Userland/Libraries/LibWeb/HTML/VideoTrack.cpp5
4 files changed, 90 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
index 5112dfb941..4389026619 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
@@ -171,6 +171,11 @@ void HTMLMediaElement::set_current_playback_position(double playback_position)
m_official_playback_position = m_current_playback_position;
time_marches_on();
+
+ // NOTE: Invoking the following steps is not listed in the spec. Rather, the spec just describes the scenario in
+ // which these steps should be invoked, which is when we've reached the end of the media playback.
+ if (m_current_playback_position == m_duration)
+ reached_end_of_media_playback().release_value_but_fixme_should_propagate_errors();
}
// https://html.spec.whatwg.org/multipage/media.html#dom-media-duration
@@ -184,6 +189,16 @@ double HTMLMediaElement::duration() const
return m_duration;
}
+// https://html.spec.whatwg.org/multipage/media.html#dom-media-ended
+bool HTMLMediaElement::ended() const
+{
+ // The ended attribute must return true if, the last time the event loop reached step 1, the media element had ended
+ // playback and the direction of playback was forwards, and false otherwise.
+ // FIXME: Add a hook into EventLoop::process() to be notified when step 1 is reached.
+ // FIXME: Detect playback direction.
+ return has_ended_playback();
+}
+
// https://html.spec.whatwg.org/multipage/media.html#durationChange
void HTMLMediaElement::set_duration(double duration)
{
@@ -1047,6 +1062,71 @@ void HTMLMediaElement::set_paused(bool paused)
on_paused();
}
+// https://html.spec.whatwg.org/multipage/media.html#ended-playback
+bool HTMLMediaElement::has_ended_playback() const
+{
+ // A media element is said to have ended playback when:
+
+ // The element's readyState attribute is HAVE_METADATA or greater, and
+ if (m_ready_state < ReadyState::HaveMetadata)
+ return false;
+
+ // Either:
+ if (
+ // The current playback position is the end of the media resource, and
+ m_current_playback_position == m_duration &&
+
+ // FIXME: The direction of playback is forwards, and
+
+ // The media element does not have a loop attribute specified.
+ !has_attribute(HTML::AttributeNames::loop)) {
+ return true;
+ }
+
+ // FIXME: Or:
+ // The current playback position is the earliest possible position, and
+ // The direction of playback is backwards.
+
+ return false;
+}
+
+// https://html.spec.whatwg.org/multipage/media.html#reaches-the-end
+WebIDL::ExceptionOr<void> HTMLMediaElement::reached_end_of_media_playback()
+{
+ // 1. If the media element has a loop attribute specified, then seek to the earliest possible position of the media resource and return.
+ if (has_attribute(HTML::AttributeNames::loop)) {
+ // FIXME: Seek to the beginning of the media resource.
+ return {};
+ }
+
+ // 2. As defined above, the ended IDL attribute starts returning true once the event loop returns to step 1.
+
+ // 3. Queue a media element task given the media element and the following steps:
+ queue_a_media_element_task([this]() mutable {
+ // 1. Fire an event named timeupdate at the media element.
+ dispatch_time_update_event().release_value_but_fixme_should_propagate_errors();
+
+ // 2. If the media element has ended playback, the direction of playback is forwards, and paused is false, then:
+ // FIXME: Detect playback direction.
+ if (has_ended_playback() && !paused()) {
+ // 1. Set the paused attribute to true.
+ set_paused(true);
+
+ // 2. Fire an event named pause at the media element.
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::pause).release_value_but_fixme_should_propagate_errors());
+
+ // 3. Take pending play promises and reject pending play promises with the result and an "AbortError" DOMException.
+ auto promises = take_pending_play_promises();
+ reject_pending_play_promises<WebIDL::AbortError>(promises, "Media playback has ended"_fly_string.release_value_but_fixme_should_propagate_errors());
+ }
+ });
+
+ // 4. Fire an event named ended at the media element.
+ dispatch_event(TRY(DOM::Event::create(realm(), HTML::EventNames::ended)));
+
+ return {};
+}
+
WebIDL::ExceptionOr<void> HTMLMediaElement::dispatch_time_update_event()
{
ScopeGuard guard { [this] { m_running_time_update_event_handler = false; } };
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
index 1219b3e104..a744e5bca7 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
@@ -54,6 +54,7 @@ public:
double duration() const;
bool paused() const { return m_paused; }
+ bool ended() const;
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> play();
WebIDL::ExceptionOr<void> pause();
@@ -93,6 +94,9 @@ private:
void set_paused(bool);
void set_duration(double);
+ bool has_ended_playback() const;
+ WebIDL::ExceptionOr<void> reached_end_of_media_playback();
+
WebIDL::ExceptionOr<void> dispatch_time_update_event();
enum class TimeMarchesOnReason {
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
index d655e10052..9ddc5aebca 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
@@ -33,6 +33,7 @@ interface HTMLMediaElement : HTMLElement {
attribute double currentTime;
readonly attribute unrestricted double duration;
readonly attribute boolean paused;
+ readonly attribute boolean ended;
[Reflect, CEReactions] attribute boolean autoplay;
[Reflect, CEReactions] attribute boolean loop;
Promise<undefined> play();
diff --git a/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp b/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp
index 78d6767c73..a80d2ab892 100644
--- a/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp
+++ b/Userland/Libraries/LibWeb/HTML/VideoTrack.cpp
@@ -37,6 +37,11 @@ VideoTrack::VideoTrack(JS::Realm& realm, JS::NonnullGCPtr<HTMLMediaElement> medi
m_media_element->set_current_playback_position(playback_position_ms / 1000.0);
};
+ m_playback_manager->on_end_of_stream = [this]() {
+ auto playback_position_ms = static_cast<double>(duration().to_milliseconds());
+ m_media_element->set_current_playback_position(playback_position_ms / 1000.0);
+ };
+
m_playback_manager->on_decoder_error = [](auto) {
// FIXME: Propagate this error to HTMLMediaElement's error attribute.
};