diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
index 74039a1aeb..5102b2b7e4 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
@@ -135,6 +135,8 @@ WebIDL::ExceptionOr HTMLMediaElement::load_element()
{
auto& vm = this->vm();
+ m_first_data_load_event_since_load_start = true;
+
// FIXME: 1. Abort any already-running instance of the resource selection algorithm for this element.
// 2. Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues.
@@ -169,7 +171,11 @@ WebIDL::ExceptionOr HTMLMediaElement::load_element()
// FIXME: 3. If the media element's assigned media provider object is a MediaSource object, then detach it.
// FIXME: 4. Forget the media element's media-resource-specific tracks.
- // FIXME: 5. If readyState is not set to HAVE_NOTHING, then set it to that state.
+
+ // 5. If readyState is not set to HAVE_NOTHING, then set it to that state.
+ if (m_ready_state != ReadyState::HaveNothing)
+ set_ready_state(ReadyState::HaveNothing);
+
// FIXME: 6. If the paused attribute is false, then:
// 1. Set the paused attribute to true.
// 2. Take pending play promises and reject pending play promises with the result and an "AbortError" DOMException.
@@ -603,7 +609,9 @@ WebIDL::ExceptionOr HTMLMediaElement::process_media_data(Function
});
}
- // FIXME: 6. Set the readyState attribute to HAVE_METADATA.
+ // 6. Set the readyState attribute to HAVE_METADATA.
+ set_ready_state(ReadyState::HaveMetadata);
+
// FIXME: 7. Let jumped be false.
// FIXME: 8. If the media element's default playback start position is greater than zero, then seek to that time, and let jumped be true.
// FIXME: 9. Let the media element's default playback start position be zero.
@@ -675,4 +683,89 @@ void HTMLMediaElement::forget_media_resource_specific_tracks()
m_video_tracks->remove_all_tracks({});
}
+// https://html.spec.whatwg.org/multipage/media.html#ready-states:media-element-3
+void HTMLMediaElement::set_ready_state(ReadyState ready_state)
+{
+ ScopeGuard guard { [&] { m_ready_state = ready_state; } };
+
+ // -> If the previous ready state was HAVE_NOTHING, and the new ready state is HAVE_METADATA
+ if (m_ready_state == ReadyState::HaveNothing && ready_state == ReadyState::HaveMetadata) {
+ // Queue a media element task given the media element to fire an event named loadedmetadata at the element.
+ queue_a_media_element_task([this] {
+ dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::loadedmetadata.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors());
+ });
+
+ return;
+ }
+
+ // -> If the previous ready state was HAVE_METADATA and the new ready state is HAVE_CURRENT_DATA or greater
+ if (m_ready_state == ReadyState::HaveMetadata && ready_state >= ReadyState::HaveCurrentData) {
+ // If this is the first time this occurs for this media element since the load() algorithm was last invoked, the user agent must queue a media
+ // element task given the media element to fire an event named loadeddata at the element.
+ if (m_first_data_load_event_since_load_start) {
+ m_first_data_load_event_since_load_start = false;
+
+ queue_a_media_element_task([this] {
+ dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::loadeddata.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors());
+ });
+ }
+
+ // If the new ready state is HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, then the relevant steps below must then be run also.
+ if (ready_state != ReadyState::HaveFutureData && ready_state != ReadyState::HaveEnoughData)
+ return;
+ }
+
+ // -> If the previous ready state was HAVE_FUTURE_DATA or more, and the new ready state is HAVE_CURRENT_DATA or less
+ if (m_ready_state >= ReadyState::HaveFutureData && ready_state <= ReadyState::HaveCurrentData) {
+ // FIXME: If the media element was potentially playing before its readyState attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
+ // has not ended playback, and playback has not stopped due to errors, paused for user interaction, or paused for in-band content, the user agent
+ // must queue a media element task given the media element to fire an event named timeupdate at the element, and queue a media element task given
+ // the media element to fire an event named waiting at the element.
+ return;
+ }
+
+ // -> If the previous ready state was HAVE_CURRENT_DATA or less, and the new ready state is HAVE_FUTURE_DATA
+ if (m_ready_state <= ReadyState::HaveCurrentData && ready_state == ReadyState::HaveFutureData) {
+ // The user agent must queue a media element task given the media element to fire an event named canplay at the element.
+ queue_a_media_element_task([this] {
+ dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::canplay.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors());
+ });
+
+ // FIXME: If the element's paused attribute is false, the user agent must notify about playing for the element.
+ return;
+ }
+
+ // -> If the new ready state is HAVE_ENOUGH_DATA
+ if (ready_state == ReadyState::HaveEnoughData) {
+ // If the previous ready state was HAVE_CURRENT_DATA or less, the user agent must queue a media element task given the media element to fire an event
+ // named canplay at the element, and, if the element's paused attribute is false, notify about playing for the element.
+ if (m_ready_state <= ReadyState::HaveCurrentData) {
+ // FIXME: Handle the paused attribute.
+ queue_a_media_element_task([this] {
+ dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::canplay.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors());
+ });
+ }
+
+ // The user agent must queue a media element task given the media element to fire an event named canplaythrough at the element.
+ queue_a_media_element_task([this] {
+ dispatch_event(DOM::Event::create(this->realm(), HTML::EventNames::canplaythrough.to_deprecated_fly_string()).release_value_but_fixme_should_propagate_errors());
+ });
+
+ // FIXME: If the element is not eligible for autoplay, then the user agent must abort these substeps.
+
+ // FIXME: The user agent may run the following substeps:
+ // Set the paused attribute to false.
+ // If the element's show poster flag is true, set it to false and run the time marches on steps.
+ // Queue a media element task given the element to fire an event named play at the element.
+ // Notify about playing for the element.
+
+ // FIXME: Alternatively, if the element is a video element, the user agent may start observing whether the element intersects the viewport. When the
+ // element starts intersecting the viewport, if the element is still eligible for autoplay, run the substeps above. Optionally, when the element
+ // stops intersecting the viewport, if the can autoplay flag is still true and the autoplay attribute is still specified, run the following substeps:
+ // Run the internal pause steps and set the can autoplay flag to true.
+ // Queue a media element task given the element to fire an event named pause at the element.
+ return;
+ }
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
index 58324f5e2c..ab95962ace 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
@@ -33,6 +33,15 @@ public:
WebIDL::ExceptionOr can_play_type(DeprecatedString const& type) const;
+ enum class ReadyState : u16 {
+ HaveNothing,
+ HaveMetadata,
+ HaveCurrentData,
+ HaveFutureData,
+ HaveEnoughData,
+ };
+ ReadyState ready_state() const { return m_ready_state; }
+
void load() const;
double duration() const;
void pause() const;
@@ -60,6 +69,7 @@ private:
WebIDL::ExceptionOr process_media_data(Function failure_callback);
WebIDL::ExceptionOr handle_media_source_failure();
void forget_media_resource_specific_tracks();
+ void set_ready_state(ReadyState);
void set_duration(double);
// https://html.spec.whatwg.org/multipage/media.html#media-element-event-task-source
@@ -68,6 +78,10 @@ private:
// https://html.spec.whatwg.org/multipage/media.html#dom-media-networkstate
NetworkState m_network_state { NetworkState::Empty };
+ // https://html.spec.whatwg.org/multipage/media.html#dom-media-readystate
+ ReadyState m_ready_state { ReadyState::HaveNothing };
+ bool m_first_data_load_event_since_load_start { false };
+
// https://html.spec.whatwg.org/multipage/media.html#dom-media-duration
double m_duration { NAN };
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
index bd7e648f69..48cf818406 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl
@@ -21,6 +21,14 @@ interface HTMLMediaElement : HTMLElement {
undefined load();
CanPlayTypeResult canPlayType(DOMString type);
+ // ready state
+ const unsigned short HAVE_NOTHING = 0;
+ const unsigned short HAVE_METADATA = 1;
+ const unsigned short HAVE_CURRENT_DATA = 2;
+ const unsigned short HAVE_FUTURE_DATA = 3;
+ const unsigned short HAVE_ENOUGH_DATA = 4;
+ readonly attribute unsigned short readyState;
+
// playback state
readonly attribute unrestricted double duration;
[Reflect, CEReactions] attribute boolean autoplay;