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;