From b9e4dc2cb745aee2323ad8c659b0be9a80d2787b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 14 Jun 2023 13:07:09 -0400 Subject: [PATCH] LibWeb: Implement the HTMLMediaElement volume and muted IDL attributes --- Userland/Libraries/LibWeb/HTML/AudioTrack.cpp | 5 ++ Userland/Libraries/LibWeb/HTML/AudioTrack.h | 2 + .../LibWeb/HTML/HTMLAudioElement.cpp | 7 ++ .../Libraries/LibWeb/HTML/HTMLAudioElement.h | 1 + .../LibWeb/HTML/HTMLMediaElement.cpp | 65 +++++++++++++++++++ .../Libraries/LibWeb/HTML/HTMLMediaElement.h | 18 +++++ .../LibWeb/HTML/HTMLMediaElement.idl | 3 + 7 files changed, 101 insertions(+) diff --git a/Userland/Libraries/LibWeb/HTML/AudioTrack.cpp b/Userland/Libraries/LibWeb/HTML/AudioTrack.cpp index 9f6db1f22f..59aa4fb9f0 100644 --- a/Userland/Libraries/LibWeb/HTML/AudioTrack.cpp +++ b/Userland/Libraries/LibWeb/HTML/AudioTrack.cpp @@ -100,6 +100,11 @@ void AudioTrack::seek(double position, MediaSeekMode seek_mode) m_loader->seek(position).release_value_but_fixme_should_propagate_errors(); } +void AudioTrack::update_volume() +{ + m_audio_plugin->set_volume(m_media_element->effective_media_volume()); +} + void AudioTrack::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); diff --git a/Userland/Libraries/LibWeb/HTML/AudioTrack.h b/Userland/Libraries/LibWeb/HTML/AudioTrack.h index 844b579ad1..424e52cbe9 100644 --- a/Userland/Libraries/LibWeb/HTML/AudioTrack.h +++ b/Userland/Libraries/LibWeb/HTML/AudioTrack.h @@ -28,6 +28,8 @@ public: Duration duration() const; void seek(double, MediaSeekMode); + void update_volume(); + String const& id() const { return m_id; } String const& kind() const { return m_kind; } String const& label() const { return m_label; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp index b2bb0ba0a2..f006b07f27 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.cpp @@ -63,4 +63,11 @@ void HTMLAudioElement::on_seek(double position, MediaSeekMode seek_mode) }); } +void HTMLAudioElement::on_volume_change() +{ + audio_tracks()->for_each_enabled_track([&](auto& audio_track) { + audio_track.update_volume(); + }); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h index 08d0c92153..e08554afd4 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLAudioElement.h @@ -29,6 +29,7 @@ private: virtual void on_playing() override; virtual void on_paused() override; virtual void on_seek(double, MediaSeekMode) override; + virtual void on_volume_change() override; }; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 2e9e5b3534..b8168dc04f 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -92,6 +92,8 @@ void HTMLMediaElement::parse_attribute(DeprecatedFlyString const& name, Deprecat load_element().release_value_but_fixme_should_propagate_errors(); else if (name == HTML::AttributeNames::crossorigin) m_crossorigin = cors_setting_attribute_from_keyword(String::from_deprecated_string(value).release_value_but_fixme_should_propagate_errors()); + else if (name == HTML::AttributeNames::muted) + set_muted(true); } void HTMLMediaElement::did_remove_attribute(DeprecatedFlyString const& name) @@ -344,6 +346,69 @@ WebIDL::ExceptionOr HTMLMediaElement::pause() return {}; } +// https://html.spec.whatwg.org/multipage/media.html#dom-media-volume +WebIDL::ExceptionOr HTMLMediaElement::set_volume(double volume) +{ + if (m_volume == volume) + return {}; + + // On setting, if the new value is in the range 0.0 to 1.0 inclusive, the media element's playback volume must be + // set to the new value. If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an + // "IndexSizeError" DOMException must be thrown instead. + if (volume < 0.0 || volume > 1.0) + return WebIDL::IndexSizeError::create(realm(), "Volume must be in the range 0.0 to 1.0, inclusive"sv); + + m_volume = volume; + volume_or_muted_attribute_changed(); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/media.html#dom-media-muted +void HTMLMediaElement::set_muted(bool muted) +{ + if (m_muted == muted) + return; + + m_muted = muted; + volume_or_muted_attribute_changed(); +} + +// https://html.spec.whatwg.org/multipage/media.html#user-interface:dom-media-volume-3 +void HTMLMediaElement::volume_or_muted_attribute_changed() +{ + // Whenever either of the values that would be returned by the volume and muted IDL attributes change, the user + // agent must queue a media element task given the media element to fire an event named volumechange at the media + // element. + queue_a_media_element_task([this] { + dispatch_event(DOM::Event::create(realm(), HTML::EventNames::volumechange).release_value_but_fixme_should_propagate_errors()); + }); + + // FIXME: Then, if the media element is not allowed to play, the user agent must run the internal pause steps for the media element. + + on_volume_change(); +} + +// https://html.spec.whatwg.org/multipage/media.html#effective-media-volume +double HTMLMediaElement::effective_media_volume() const +{ + // FIXME 1. If the user has indicated that the user agent is to override the volume of the element, then return the + // volume desired by the user. + + // 2. If the element's audio output is muted, then return zero. + if (m_muted) + return 0.0; + + // 3. Let volume be the playback volume of the audio portions of the media element, in range 0.0 (silent) to + // 1.0 (loudest). + auto volume = clamp(m_volume, 0.0, 1.0); + + // 4. Return volume, interpreted relative to the range 0.0 to 1.0, with 0.0 being silent, and 1.0 being the loudest + // setting, values in between increasing in loudness. The range need not be linear. The loudest setting may be + // lower than the system's loudest possible setting; for example the user could have set a maximum volume. + return volume; +} + // https://html.spec.whatwg.org/multipage/media.html#media-element-load-algorithm WebIDL::ExceptionOr HTMLMediaElement::load_element() { diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h index 6ecf072bd6..74ec7c65fb 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h @@ -85,6 +85,14 @@ public: WebIDL::ExceptionOr> play(); WebIDL::ExceptionOr pause(); + double volume() const { return m_volume; } + WebIDL::ExceptionOr set_volume(double); + + bool muted() const { return m_muted; } + void set_muted(bool); + + double effective_media_volume() const; + JS::NonnullGCPtr audio_tracks() const { return *m_audio_tracks; } JS::NonnullGCPtr video_tracks() const { return *m_video_tracks; } @@ -118,6 +126,8 @@ protected: // subclasses must invoke set_current_playback_position() to unblock the user agent. virtual void on_seek(double, MediaSeekMode) { m_seek_in_progress = false; } + virtual void on_volume_change() { } + private: friend SourceElementSelector; @@ -142,6 +152,8 @@ private: void set_paused(bool); void set_duration(double); + void volume_or_muted_attribute_changed(); + bool blocked() const; bool is_eligible_for_autoplay() const; bool has_ended_playback() const; @@ -212,6 +224,12 @@ private: // https://html.spec.whatwg.org/multipage/media.html#dom-media-paused bool m_paused { true }; + // https://html.spec.whatwg.org/multipage/media.html#dom-media-volume + double m_volume { 1.0 }; + + // https://html.spec.whatwg.org/multipage/media.html#dom-media-muted + bool m_muted { false }; + // https://html.spec.whatwg.org/multipage/media.html#dom-media-audiotracks JS::GCPtr m_audio_tracks; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl index 6f6806ab67..8f3a869c11 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.idl @@ -52,6 +52,9 @@ interface HTMLMediaElement : HTMLElement { // controls [Reflect, CEReactions] attribute boolean controls; + attribute double volume; + attribute boolean muted; + [Reflect=muted, CEReactions] attribute boolean defaultMuted; // tracks [SameObject] readonly attribute AudioTrackList audioTracks;