diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
index d71996360a..cca36aacac 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include
#include
#include
#include
@@ -20,15 +21,18 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
namespace Web::HTML {
HTMLMediaElement::HTMLMediaElement(DOM::Document& document, DOM::QualifiedName qualified_name)
: HTMLElement(document, move(qualified_name))
+ , m_pending_play_promises(heap())
{
}
@@ -127,6 +131,27 @@ void HTMLMediaElement::set_duration(double duration)
m_duration = duration;
}
+WebIDL::ExceptionOr> HTMLMediaElement::play()
+{
+ auto& realm = this->realm();
+ auto& vm = realm.vm();
+
+ // FIXME: 1. If the media element is not allowed to play, then return a promise rejected with a "NotAllowedError" DOMException.
+
+ // FIXME: 2. If the media element's error attribute is not null and its code is MEDIA_ERR_SRC_NOT_SUPPORTED, then return a promise
+ // rejected with a "NotSupportedError" DOMException.
+
+ // 3. Let promise be a new promise and append promise to the list of pending play promises.
+ auto promise = WebIDL::create_promise(realm);
+ TRY_OR_THROW_OOM(vm, m_pending_play_promises.try_append(promise));
+
+ // 4. Run the internal play steps for the media element.
+ TRY(play_element());
+
+ // 5. Return promise.
+ return JS::NonnullGCPtr { verify_cast(*promise->promise()) };
+}
+
// https://html.spec.whatwg.org/multipage/media.html#dom-media-pause
WebIDL::ExceptionOr HTMLMediaElement::pause()
{
@@ -192,7 +217,9 @@ WebIDL::ExceptionOr HTMLMediaElement::load_element()
// 1. Set the paused attribute to true.
set_paused(true);
- // FIXME 2. Take pending play promises and reject pending play promises with the result and an "AbortError" DOMException.
+ // 2. 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(promises, TRY_OR_THROW_OOM(vm, "Media playback was aborted"_fly_string));
}
// FIXME: 7. If seeking is true, set it to false.
@@ -289,8 +316,8 @@ WebIDL::ExceptionOr HTMLMediaElement::select_resource()
// 6. Failed with attribute: Reaching this step indicates that the media resource failed to load or that the given URL could not be parsed. Take
// pending play promises and queue a media element task given the media element to run the dedicated media source failure steps with the result.
queue_a_media_element_task([this, &ran_media_element_task]() {
- // FIXME: Find and pass pending play promises to this AO.
- handle_media_source_failure().release_value_but_fixme_should_propagate_errors();
+ auto promises = take_pending_play_promises();
+ handle_media_source_failure(promises).release_value_but_fixme_should_propagate_errors();
ran_media_element_task = true;
});
@@ -671,8 +698,10 @@ WebIDL::ExceptionOr HTMLMediaElement::process_media_data(Function
}
// https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps
-WebIDL::ExceptionOr HTMLMediaElement::handle_media_source_failure()
+WebIDL::ExceptionOr HTMLMediaElement::handle_media_source_failure(Span> promises)
{
+ auto& vm = this->vm();
+
// FIXME: 1. Set the error attribute to the result of creating a MediaError with MEDIA_ERR_SRC_NOT_SUPPORTED.
// 2. Forget the media element's media-resource-specific tracks.
@@ -686,7 +715,9 @@ WebIDL::ExceptionOr HTMLMediaElement::handle_media_source_failure()
// 5. Fire an event named error at the media element.
dispatch_event(TRY(DOM::Event::create(realm(), HTML::EventNames::error)));
- // FIXME: 6. Reject pending play promises with promises and a "NotSupportedError" DOMException.
+ // 6. Reject pending play promises with promises and a "NotSupportedError" DOMException.
+ reject_pending_play_promises(promises, TRY_OR_THROW_OOM(vm, "Media is not supported"_fly_string));
+
// FIXME: 7. Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
return {};
@@ -792,6 +823,59 @@ void HTMLMediaElement::set_ready_state(ReadyState ready_state)
}
}
+// https://html.spec.whatwg.org/multipage/media.html#internal-play-steps
+WebIDL::ExceptionOr HTMLMediaElement::play_element()
+{
+ // 1. If the media element's networkState attribute has the value NETWORK_EMPTY, invoke the media element's resource
+ // selection algorithm.
+ if (m_network_state == NetworkState::Empty)
+ TRY(select_resource());
+
+ // FIXME: 2. If the playback has ended and the direction of playback is forwards, seek to the earliest possible
+ // position of the media resource.
+
+ // 3. If the media element's paused attribute is true, then:
+ if (paused()) {
+ // 1. Change the value of paused to false.
+ set_paused(false);
+
+ // FIXME: 2. If the show poster flag is true, set the element's show poster flag to false and run the time marches on steps.
+
+ // 3. Queue a media element task given the media element to fire an event named play at the element.
+ queue_a_media_element_task([this]() {
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::play).release_value_but_fixme_should_propagate_errors());
+ });
+
+ // 4. If the media element's readyState attribute has the value HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA,
+ // queue a media element task given the media element to fire an event named waiting at the element.
+ if (m_ready_state == ReadyState::HaveNothing || m_ready_state == ReadyState::HaveMetadata || m_ready_state == ReadyState::HaveCurrentData) {
+ queue_a_media_element_task([this]() {
+ dispatch_event(DOM::Event::create(realm(), HTML::EventNames::waiting).release_value_but_fixme_should_propagate_errors());
+ });
+ }
+ // Otherwise, the media element's readyState attribute has the value HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA:
+ // notify about playing for the element.
+ else {
+ notify_about_playing();
+ }
+ }
+
+ // 4. Otherwise, if the media element's readyState attribute has the value HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take
+ // pending play promises and queue a media element task given the media element to resolve pending play promises
+ // with the result.
+ else if (m_ready_state == ReadyState::HaveFutureData || m_ready_state == ReadyState::HaveEnoughData) {
+ auto promises = take_pending_play_promises();
+
+ queue_a_media_element_task([this, promises = move(promises)]() {
+ resolve_pending_play_promises(promises);
+ });
+ }
+
+ // FIXME: 5. Set the media element's can autoplay flag to false.
+
+ return {};
+}
+
// https://html.spec.whatwg.org/multipage/media.html#internal-pause-steps
WebIDL::ExceptionOr HTMLMediaElement::pause_element()
{
@@ -802,10 +886,11 @@ WebIDL::ExceptionOr HTMLMediaElement::pause_element()
// 1. Change the value of paused to true.
set_paused(true);
- // FIXME: 2. Take pending play promises and let promises be the result.
+ // 2. Take pending play promises and let promises be the result.
+ auto promises = take_pending_play_promises();
// 3. Queue a media element task given the media element and the following steps:
- queue_a_media_element_task([this]() {
+ queue_a_media_element_task([this, promises = move(promises)]() {
auto& realm = this->realm();
// 1. Fire an event named timeupdate at the element.
@@ -814,7 +899,8 @@ WebIDL::ExceptionOr HTMLMediaElement::pause_element()
// 2. Fire an event named pause at the element.
dispatch_event(DOM::Event::create(realm, HTML::EventNames::pause).release_value_but_fixme_should_propagate_errors());
- // FIXME: 3. Reject pending play promises with promises and an "AbortError" DOMException.
+ // 3. Reject pending play promises with promises and an "AbortError" DOMException.
+ reject_pending_play_promises(promises, "Media playback was paused"_fly_string.release_value_but_fixme_should_propagate_errors());
});
// FIXME: 4. Set the official playback position to the current playback position.
@@ -826,14 +912,16 @@ WebIDL::ExceptionOr HTMLMediaElement::pause_element()
// https://html.spec.whatwg.org/multipage/media.html#notify-about-playing
void HTMLMediaElement::notify_about_playing()
{
- // FIXME: 1. Take pending play promises and let promises be the result.
+ // 1. Take pending play promises and let promises be the result.
+ auto promises = take_pending_play_promises();
// 2. Queue a media element task given the element and the following steps:
- queue_a_media_element_task([this]() {
+ queue_a_media_element_task([this, promises = move(promises)]() {
// 1. Fire an event named playing at the element.
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::playing).release_value_but_fixme_should_propagate_errors());
- // FIXME: 2. Resolve pending play promises with promises.
+ // 2. Resolve pending play promises with promises.
+ resolve_pending_play_promises(promises);
});
on_playing();
@@ -850,4 +938,52 @@ void HTMLMediaElement::set_paused(bool paused)
on_paused();
}
+// https://html.spec.whatwg.org/multipage/media.html#take-pending-play-promises
+JS::MarkedVector> HTMLMediaElement::take_pending_play_promises()
+{
+ // 1. Let promises be an empty list of promises.
+ // 2. Copy the media element's list of pending play promises to promises.
+ // 3. Clear the media element's list of pending play promises.
+ auto promises = move(m_pending_play_promises);
+
+ // 4. Return promises.
+ return promises;
+}
+
+// https://html.spec.whatwg.org/multipage/media.html#resolve-pending-play-promises
+void HTMLMediaElement::resolve_pending_play_promises(ReadonlySpan> promises)
+{
+ auto& realm = this->realm();
+
+ // FIXME: This AO runs from the media element task queue, at which point we do not have a running execution
+ // context. This pushes one to allow the promise resolving hook to run.
+ auto& environment_settings = document().relevant_settings_object();
+ environment_settings.prepare_to_run_script();
+
+ // To resolve pending play promises for a media element with a list of promises promises, the user agent
+ // must resolve each promise in promises with undefined.
+ for (auto const& promise : promises)
+ WebIDL::resolve_promise(realm, promise, JS::js_undefined());
+
+ environment_settings.clean_up_after_running_script();
+}
+
+// https://html.spec.whatwg.org/multipage/media.html#reject-pending-play-promises
+void HTMLMediaElement::reject_pending_play_promises(ReadonlySpan> promises, JS::NonnullGCPtr error)
+{
+ auto& realm = this->realm();
+
+ // FIXME: This AO runs from the media element task queue, at which point we do not have a running execution
+ // context. This pushes one to allow the promise rejection hook to run.
+ auto& environment_settings = document().relevant_settings_object();
+ environment_settings.prepare_to_run_script();
+
+ // To reject pending play promises for a media element with a list of promise promises and an exception name
+ // error, the user agent must reject each promise in promises with error.
+ for (auto const& promise : promises)
+ WebIDL::reject_promise(realm, promise, error);
+
+ environment_settings.clean_up_after_running_script();
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
index b16127f5e4..93c48fe05a 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
@@ -9,9 +9,11 @@
#include
#include
+#include
#include
#include
#include
+#include
#include
namespace Web::HTML {
@@ -46,6 +48,7 @@ public:
WebIDL::ExceptionOr load();
double duration() const;
bool paused() const { return m_paused; }
+ WebIDL::ExceptionOr> play();
WebIDL::ExceptionOr pause();
JS::NonnullGCPtr video_tracks() const { return *m_video_tracks; }
@@ -74,15 +77,30 @@ private:
WebIDL::ExceptionOr fetch_resource(AK::URL const&, Function failure_callback);
static bool verify_response(JS::NonnullGCPtr, ByteRange const&);
WebIDL::ExceptionOr process_media_data(Function failure_callback);
- WebIDL::ExceptionOr handle_media_source_failure();
+ WebIDL::ExceptionOr handle_media_source_failure(Span> promises);
void forget_media_resource_specific_tracks();
void set_ready_state(ReadyState);
+ WebIDL::ExceptionOr play_element();
WebIDL::ExceptionOr pause_element();
void notify_about_playing();
void set_paused(bool);
void set_duration(double);
+ JS::MarkedVector> take_pending_play_promises();
+ void resolve_pending_play_promises(ReadonlySpan> promises);
+ void reject_pending_play_promises(ReadonlySpan> promises, JS::NonnullGCPtr error);
+
+ // https://html.spec.whatwg.org/multipage/media.html#reject-pending-play-promises
+ template
+ void reject_pending_play_promises(ReadonlySpan> promises, FlyString const& message)
+ {
+ auto& realm = this->realm();
+
+ auto error = ErrorType::create(realm, message.to_deprecated_fly_string());
+ reject_pending_play_promises(promises, error);
+ }
+
// https://html.spec.whatwg.org/multipage/media.html#media-element-event-task-source
UniqueTaskSource m_media_element_event_task_source {};
@@ -96,6 +114,9 @@ private:
// https://html.spec.whatwg.org/multipage/media.html#dom-media-duration
double m_duration { NAN };
+ // https://html.spec.whatwg.org/multipage/media.html#list-of-pending-play-promises
+ JS::MarkedVector> m_pending_play_promises;
+
// https://html.spec.whatwg.org/multipage/media.html#dom-media-paused
bool m_paused { true };
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
index 6be3e29bd9..46e9bd5cd7 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
@@ -34,6 +34,7 @@ interface HTMLMediaElement : HTMLElement {
readonly attribute boolean paused;
[Reflect, CEReactions] attribute boolean autoplay;
[Reflect, CEReactions] attribute boolean loop;
+ Promise play();
undefined pause();
// controls