Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2020, the SerenityOS developers.
3
 * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#pragma once
9
10
#include <AK/ByteBuffer.h>
11
#include <AK/Optional.h>
12
#include <AK/Time.h>
13
#include <AK/Variant.h>
14
#include <LibGfx/Rect.h>
15
#include <LibJS/Heap/MarkedVector.h>
16
#include <LibJS/SafeFunction.h>
17
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
18
#include <LibWeb/HTML/CORSSettingAttribute.h>
19
#include <LibWeb/HTML/EventLoop/Task.h>
20
#include <LibWeb/HTML/HTMLElement.h>
21
#include <LibWeb/PixelUnits.h>
22
#include <LibWeb/UIEvents/KeyCode.h>
23
#include <LibWeb/WebIDL/DOMException.h>
24
#include <math.h>
25
26
namespace Web::HTML {
27
28
enum class MediaSeekMode {
29
    Accurate,
30
    ApproximateForSpeed,
31
};
32
33
class SourceElementSelector;
34
35
class HTMLMediaElement : public HTMLElement {
36
    WEB_PLATFORM_OBJECT(HTMLMediaElement, HTMLElement);
37
38
public:
39
    virtual ~HTMLMediaElement() override;
40
41
0
    virtual bool is_focusable() const override { return true; }
42
43
    // NOTE: The function is wrapped in a JS::HeapFunction immediately.
44
    void queue_a_media_element_task(Function<void()>);
45
46
0
    JS::GCPtr<MediaError> error() const { return m_error; }
47
    void set_decoder_error(String error_message);
48
49
0
    String const& current_src() const { return m_current_src; }
50
    WebIDL::ExceptionOr<void> select_resource();
51
52
    enum class NetworkState : u16 {
53
        Empty,
54
        Idle,
55
        Loading,
56
        NoSource,
57
    };
58
0
    NetworkState network_state() const { return m_network_state; }
59
60
    [[nodiscard]] JS::NonnullGCPtr<TimeRanges> buffered() const;
61
62
    Bindings::CanPlayTypeResult can_play_type(StringView type) const;
63
64
    enum class ReadyState : u16 {
65
        HaveNothing,
66
        HaveMetadata,
67
        HaveCurrentData,
68
        HaveFutureData,
69
        HaveEnoughData,
70
    };
71
0
    ReadyState ready_state() const { return m_ready_state; }
72
    bool blocked() const;
73
    bool stalled() const;
74
75
0
    bool seeking() const { return m_seeking; }
76
    void set_seeking(bool);
77
78
    WebIDL::ExceptionOr<void> load();
79
80
    double current_time() const;
81
    void set_current_time(double);
82
    void fast_seek(double);
83
84
0
    double current_playback_position() const { return m_current_playback_position; }
85
    void set_current_playback_position(double);
86
87
    double duration() const;
88
0
    bool show_poster() const { return m_show_poster; }
89
0
    bool paused() const { return m_paused; }
90
    bool ended() const;
91
    bool potentially_playing() const;
92
    WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> play();
93
    WebIDL::ExceptionOr<void> pause();
94
    WebIDL::ExceptionOr<void> toggle_playback();
95
96
0
    double volume() const { return m_volume; }
97
    WebIDL::ExceptionOr<void> set_volume(double);
98
99
0
    bool muted() const { return m_muted; }
100
    void set_muted(bool);
101
102
    void page_mute_state_changed(Badge<Page>);
103
104
    double effective_media_volume() const;
105
106
0
    JS::NonnullGCPtr<AudioTrackList> audio_tracks() const { return *m_audio_tracks; }
107
0
    JS::NonnullGCPtr<VideoTrackList> video_tracks() const { return *m_video_tracks; }
108
0
    JS::NonnullGCPtr<TextTrackList> text_tracks() const { return *m_text_tracks; }
109
110
    JS::NonnullGCPtr<TextTrack> add_text_track(Bindings::TextTrackKind kind, String const& label, String const& language);
111
112
    WebIDL::ExceptionOr<bool> handle_keydown(Badge<Web::EventHandler>, UIEvents::KeyCode, u32 modifiers);
113
114
    enum class MouseTrackingComponent {
115
        Timeline,
116
        Volume,
117
    };
118
0
    void set_layout_mouse_tracking_component(Badge<Painting::MediaPaintable>, Optional<MouseTrackingComponent> mouse_tracking_component) { m_mouse_tracking_component = move(mouse_tracking_component); }
119
0
    Optional<MouseTrackingComponent> const& layout_mouse_tracking_component(Badge<Painting::MediaPaintable>) const { return m_mouse_tracking_component; }
120
121
0
    void set_layout_mouse_position(Badge<Painting::MediaPaintable>, Optional<CSSPixelPoint> mouse_position) { m_mouse_position = move(mouse_position); }
122
0
    Optional<CSSPixelPoint> const& layout_mouse_position(Badge<Painting::MediaPaintable>) const { return m_mouse_position; }
123
124
    void set_layout_display_time(Badge<Painting::MediaPaintable>, Optional<double> display_time);
125
    double layout_display_time(Badge<Painting::MediaPaintable>) const;
126
127
    struct CachedLayoutBoxes {
128
        Optional<CSSPixelRect> control_box_rect;
129
        Optional<CSSPixelRect> playback_button_rect;
130
        Optional<CSSPixelRect> timeline_rect;
131
        Optional<CSSPixelRect> speaker_button_rect;
132
        Optional<CSSPixelRect> volume_rect;
133
        Optional<CSSPixelRect> volume_scrub_rect;
134
    };
135
0
    CachedLayoutBoxes& cached_layout_boxes(Badge<Painting::MediaPaintable>) const { return m_layout_boxes; }
136
137
protected:
138
    HTMLMediaElement(DOM::Document&, DOM::QualifiedName);
139
140
    virtual void initialize(JS::Realm&) override;
141
    virtual void finalize() override;
142
    virtual void visit_edges(Cell::Visitor&) override;
143
144
    virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) override;
145
    virtual void removed_from(DOM::Node*) override;
146
    virtual void children_changed() override;
147
148
    // Override in subclasses to handle implementation-specific behavior when the element state changes
149
    // to playing or paused, e.g. to start/stop play timers.
150
0
    virtual void on_playing() { }
151
0
    virtual void on_paused() { }
152
153
    // Override in subclasses to handle implementation-specific seeking behavior. When seeking is complete,
154
    // subclasses must invoke set_current_playback_position() to unblock the user agent.
155
0
    virtual void on_seek(double, MediaSeekMode) { m_seek_in_progress = false; }
156
157
0
    virtual void on_volume_change() { }
158
159
private:
160
    friend SourceElementSelector;
161
162
    struct EntireResource { };
163
    using ByteRange = Variant<EntireResource>; // FIXME: This will need to include "until end" and an actual byte range.
164
165
0
    Task::Source media_element_event_task_source() const { return m_media_element_event_task_source.source; }
166
167
    WebIDL::ExceptionOr<void> load_element();
168
    WebIDL::ExceptionOr<void> fetch_resource(URL::URL const&, ESCAPING Function<void(String)> failure_callback);
169
    static bool verify_response(JS::NonnullGCPtr<Fetch::Infrastructure::Response>, ByteRange const&);
170
    WebIDL::ExceptionOr<void> process_media_data(Function<void(String)> failure_callback);
171
    WebIDL::ExceptionOr<void> handle_media_source_failure(Span<JS::NonnullGCPtr<WebIDL::Promise>> promises, String error_message);
172
    void forget_media_resource_specific_tracks();
173
    void set_ready_state(ReadyState);
174
175
    WebIDL::ExceptionOr<void> play_element();
176
    WebIDL::ExceptionOr<void> pause_element();
177
    void seek_element(double playback_position, MediaSeekMode = MediaSeekMode::Accurate);
178
    void notify_about_playing();
179
    void set_show_poster(bool);
180
    void set_paused(bool);
181
    void set_duration(double);
182
183
    void volume_or_muted_attribute_changed();
184
185
    bool is_eligible_for_autoplay() const;
186
    bool has_ended_playback() const;
187
    void reached_end_of_media_playback();
188
189
    void dispatch_time_update_event();
190
191
    enum class TimeMarchesOnReason {
192
        NormalPlayback,
193
        Other,
194
    };
195
    void time_marches_on(TimeMarchesOnReason = TimeMarchesOnReason::NormalPlayback);
196
197
    JS::MarkedVector<JS::NonnullGCPtr<WebIDL::Promise>> take_pending_play_promises();
198
    void resolve_pending_play_promises(ReadonlySpan<JS::NonnullGCPtr<WebIDL::Promise>> promises);
199
    void reject_pending_play_promises(ReadonlySpan<JS::NonnullGCPtr<WebIDL::Promise>> promises, JS::NonnullGCPtr<WebIDL::DOMException> error);
200
201
    // https://html.spec.whatwg.org/multipage/media.html#reject-pending-play-promises
202
    template<typename ErrorType>
203
    void reject_pending_play_promises(ReadonlySpan<JS::NonnullGCPtr<WebIDL::Promise>> promises, String message)
204
0
    {
205
0
        auto& realm = this->realm();
206
207
0
        auto error = ErrorType::create(realm, move(message));
208
0
        reject_pending_play_promises(promises, error);
209
0
    }
Unexecuted instantiation: void Web::HTML::HTMLMediaElement::reject_pending_play_promises<Web::WebIDL::AbortError>(AK::Span<JS::NonnullGCPtr<JS::PromiseCapability> const>, AK::String)
Unexecuted instantiation: void Web::HTML::HTMLMediaElement::reject_pending_play_promises<Web::WebIDL::NotSupportedError>(AK::Span<JS::NonnullGCPtr<JS::PromiseCapability> const>, AK::String)
210
211
    // https://html.spec.whatwg.org/multipage/media.html#media-element-event-task-source
212
    UniqueTaskSource m_media_element_event_task_source {};
213
214
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-error
215
    JS::GCPtr<MediaError> m_error;
216
217
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-crossorigin
218
    CORSSettingAttribute m_crossorigin { CORSSettingAttribute::NoCORS };
219
220
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-currentsrc
221
    String m_current_src;
222
223
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-networkstate
224
    NetworkState m_network_state { NetworkState::Empty };
225
226
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-readystate
227
    ReadyState m_ready_state { ReadyState::HaveNothing };
228
    bool m_first_data_load_event_since_load_start { false };
229
230
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-seeking
231
    bool m_seeking { false };
232
233
    // https://html.spec.whatwg.org/multipage/media.html#current-playback-position
234
    double m_current_playback_position { 0 };
235
236
    // https://html.spec.whatwg.org/multipage/media.html#official-playback-position
237
    double m_official_playback_position { 0 };
238
239
    // https://html.spec.whatwg.org/multipage/media.html#default-playback-start-position
240
    double m_default_playback_start_position { 0 };
241
242
    // https://html.spec.whatwg.org/multipage/media.html#show-poster-flag
243
    bool m_show_poster { true };
244
245
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-duration
246
    double m_duration { NAN };
247
248
    // https://html.spec.whatwg.org/multipage/media.html#list-of-pending-play-promises
249
    Vector<JS::NonnullGCPtr<WebIDL::Promise>> m_pending_play_promises;
250
251
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-paused
252
    bool m_paused { true };
253
254
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-volume
255
    double m_volume { 1.0 };
256
257
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-muted
258
    bool m_muted { false };
259
260
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-audiotracks
261
    JS::GCPtr<AudioTrackList> m_audio_tracks;
262
263
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-videotracks
264
    JS::GCPtr<VideoTrackList> m_video_tracks;
265
266
    // https://html.spec.whatwg.org/multipage/media.html#dom-media-texttracks
267
    JS::GCPtr<TextTrackList> m_text_tracks;
268
269
    // https://html.spec.whatwg.org/multipage/media.html#media-data
270
    ByteBuffer m_media_data;
271
272
    // https://html.spec.whatwg.org/multipage/media.html#can-autoplay-flag
273
    bool m_can_autoplay { true };
274
275
    // https://html.spec.whatwg.org/multipage/media.html#delaying-the-load-event-flag
276
    Optional<DOM::DocumentLoadEventDelayer> m_delaying_the_load_event;
277
278
    bool m_running_time_update_event_handler { false };
279
    Optional<MonotonicTime> m_last_time_update_event_time;
280
281
    JS::GCPtr<DOM::DocumentObserver> m_document_observer;
282
283
    JS::GCPtr<SourceElementSelector> m_source_element_selector;
284
285
    JS::GCPtr<Fetch::Infrastructure::FetchController> m_fetch_controller;
286
287
    bool m_seek_in_progress = false;
288
289
    // Cached state for layout.
290
    Optional<MouseTrackingComponent> m_mouse_tracking_component;
291
    bool m_tracking_mouse_position_while_playing { false };
292
    Optional<CSSPixelPoint> m_mouse_position;
293
    Optional<double> m_display_time;
294
    mutable CachedLayoutBoxes m_layout_boxes;
295
};
296
297
}