Skip to main content

script/dom/html/
htmlmediaelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::collections::VecDeque;
7use std::rc::Rc;
8use std::sync::{Arc, Mutex, Weak};
9use std::time::{Duration, Instant};
10use std::{f64, mem};
11
12use content_security_policy::sandboxing_directive::SandboxingFlagSet;
13use dom_struct::dom_struct;
14use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState};
15use euclid::default::Size2D;
16use headers::{ContentLength, ContentRange, HeaderMapExt};
17use html5ever::{LocalName, Prefix, QualName, local_name, ns};
18use http::StatusCode;
19use http::header::{self, HeaderMap, HeaderValue};
20use ipc_channel::ipc::{self};
21use ipc_channel::router::ROUTER;
22use js::context::JSContext;
23use js::realm::{AutoRealm, CurrentRealm};
24use layout_api::MediaFrame;
25use media::{GLPlayerMsg, GLPlayerMsgForward, WindowGLContext};
26use net_traits::request::{Destination, RequestId};
27use net_traits::{
28    CoreResourceThread, FetchMetadata, FilteredMetadata, NetworkError, ResourceFetchTiming,
29};
30use paint_api::{CrossProcessPaintApi, ImageUpdate, SerializableImageData};
31use pixels::RasterImage;
32use script_bindings::assert::assert_in_script;
33use script_bindings::cell::DomRefCell;
34use script_bindings::codegen::InheritTypes::{
35    ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
36};
37use script_bindings::weakref::WeakRef;
38use servo_base::generic_channel::GenericSharedMemory;
39use servo_base::id::WebViewId;
40use servo_config::pref;
41use servo_media::player::audio::AudioRenderer;
42use servo_media::player::video::{VideoFrame, VideoFrameRenderer};
43use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType};
44use servo_media::{ClientContextId, ServoMedia, SupportsMediaType};
45use servo_url::ServoUrl;
46use stylo_atoms::Atom;
47use uuid::Uuid;
48use webrender_api::{
49    ExternalImageData, ExternalImageId, ExternalImageType, ImageBufferKind, ImageDescriptor,
50    ImageDescriptorFlags, ImageFormat, ImageKey,
51};
52
53use crate::document_loader::{LoadBlocker, LoadType};
54use crate::dom::audio::audiotrack::AudioTrack;
55use crate::dom::audio::audiotracklist::AudioTrackList;
56use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::{
57    CanPlayTypeResult, HTMLMediaElementConstants, HTMLMediaElementMethods,
58};
59use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*;
60use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
61use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
62use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
63use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode};
64use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
65use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
66use crate::dom::bindings::codegen::UnionTypes::{
67    MediaStreamOrBlob, VideoTrackOrAudioTrackOrTextTrack,
68};
69use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
70use crate::dom::bindings::inheritance::Castable;
71use crate::dom::bindings::num::Finite;
72use crate::dom::bindings::refcounted::Trusted;
73use crate::dom::bindings::reflector::DomGlobal;
74use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, UnrootedDom};
75use crate::dom::bindings::str::{DOMString, USVString};
76use crate::dom::blob::Blob;
77use crate::dom::csp::{GlobalCspReporting, Violation};
78use crate::dom::document::Document;
79use crate::dom::element::attributes::storage::AttrRef;
80use crate::dom::element::{
81    AttributeMutation, AttributeMutationReason, CustomElementCreationMode, Element, ElementCreator,
82    cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute,
83};
84use crate::dom::event::Event;
85use crate::dom::eventtarget::EventTarget;
86use crate::dom::globalscope::GlobalScope;
87use crate::dom::html::htmlelement::HTMLElement;
88use crate::dom::html::htmlsourceelement::HTMLSourceElement;
89use crate::dom::html::htmlvideoelement::HTMLVideoElement;
90use crate::dom::mediaerror::MediaError;
91use crate::dom::mediafragmentparser::MediaFragmentParser;
92use crate::dom::medialist::MediaList;
93use crate::dom::mediastream::MediaStream;
94use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext};
95use crate::dom::performance::performanceresourcetiming::InitiatorType;
96use crate::dom::promise::Promise;
97use crate::dom::texttrack::TextTrack;
98use crate::dom::texttracklist::TextTrackList;
99use crate::dom::timeranges::{TimeRanges, TimeRangesContainer};
100use crate::dom::trackevent::TrackEvent;
101use crate::dom::url::URL;
102use crate::dom::videotrack::VideoTrack;
103use crate::dom::videotracklist::VideoTrackList;
104use crate::dom::virtualmethods::VirtualMethods;
105use crate::fetch::{FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request};
106use crate::microtask::{Microtask, MicrotaskRunnable};
107use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
108use crate::realms::enter_auto_realm;
109use crate::script_runtime::CanGc;
110use crate::script_thread::ScriptThread;
111use crate::task_source::SendableTaskSource;
112
113/// A CSS file to style the media controls.
114static MEDIA_CONTROL_CSS: &str = include_str!("../../resources/media-controls.css");
115
116/// A JS file to control the media controls.
117static MEDIA_CONTROL_JS: &str = include_str!("../../resources/media-controls.js");
118
119/// The media engine may report a seek-done position that differs slightly from the
120/// requested position (e.g. snapping to the nearest keyframe), so we use a threshold
121/// instead of strict equality. (Unit is second)
122const SEEK_POSITION_THRESHOLD: f64 = 0.5;
123
124#[derive(MallocSizeOf, PartialEq)]
125enum FrameStatus {
126    Locked,
127    Unlocked,
128}
129
130#[derive(MallocSizeOf)]
131struct FrameHolder(
132    FrameStatus,
133    #[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
134);
135
136impl FrameHolder {
137    fn new(frame: VideoFrame) -> FrameHolder {
138        FrameHolder(FrameStatus::Unlocked, frame)
139    }
140
141    fn lock(&mut self) {
142        if self.0 == FrameStatus::Unlocked {
143            self.0 = FrameStatus::Locked;
144        };
145    }
146
147    fn unlock(&mut self) {
148        if self.0 == FrameStatus::Locked {
149            self.0 = FrameStatus::Unlocked;
150        };
151    }
152
153    fn set(&mut self, new_frame: VideoFrame) {
154        if self.0 == FrameStatus::Unlocked {
155            self.1 = new_frame
156        };
157    }
158
159    fn get(&self) -> (u32, Size2D<i32>, usize) {
160        if self.0 == FrameStatus::Locked {
161            (
162                self.1.get_texture_id(),
163                Size2D::new(self.1.get_width(), self.1.get_height()),
164                0,
165            )
166        } else {
167            unreachable!();
168        }
169    }
170
171    fn get_frame(&self) -> VideoFrame {
172        self.1.clone()
173    }
174}
175
176#[derive(MallocSizeOf)]
177pub(crate) struct MediaFrameRenderer {
178    webview_id: WebViewId,
179    player_id: Option<usize>,
180    glplayer_id: Option<u64>,
181    paint_api: CrossProcessPaintApi,
182    #[ignore_malloc_size_of = "Defined in other crates"]
183    player_context: WindowGLContext,
184    current_frame: Option<MediaFrame>,
185    old_frame: Option<ImageKey>,
186    very_old_frame: Option<ImageKey>,
187    current_frame_holder: Option<FrameHolder>,
188    /// <https://html.spec.whatwg.org/multipage/#poster-frame>
189    poster_frame: Option<MediaFrame>,
190}
191
192impl MediaFrameRenderer {
193    fn new(
194        webview_id: WebViewId,
195        paint_api: CrossProcessPaintApi,
196        player_context: WindowGLContext,
197    ) -> Self {
198        Self {
199            webview_id,
200            player_id: None,
201            glplayer_id: None,
202            paint_api,
203            player_context,
204            current_frame: None,
205            old_frame: None,
206            very_old_frame: None,
207            current_frame_holder: None,
208            poster_frame: None,
209        }
210    }
211
212    fn setup(
213        &mut self,
214        player_id: usize,
215        task_source: SendableTaskSource,
216        weak_video_renderer: Weak<Mutex<MediaFrameRenderer>>,
217    ) {
218        self.player_id = Some(player_id);
219
220        let (glplayer_id, image_receiver) = self
221            .player_context
222            .glplayer_thread_sender
223            .as_ref()
224            .map(|sender| {
225                let (image_sender, image_receiver) = ipc::channel::<GLPlayerMsgForward>().unwrap();
226                sender
227                    .send(GLPlayerMsg::RegisterPlayer(image_sender))
228                    .unwrap();
229                match image_receiver.recv().unwrap() {
230                    GLPlayerMsgForward::PlayerId(id) => (Some(id), Some(image_receiver)),
231                    _ => unreachable!(),
232                }
233            })
234            .unwrap_or((None, None));
235
236        self.glplayer_id = glplayer_id;
237
238        let Some(image_receiver) = image_receiver else {
239            return;
240        };
241
242        ROUTER.add_typed_route(
243            image_receiver,
244            Box::new(move |message| {
245                let message = message.unwrap();
246                let weak_video_renderer = weak_video_renderer.clone();
247
248                task_source.queue(task!(handle_glplayer_message: move || {
249                    trace!("GLPlayer message {:?}", message);
250
251                    let Some(video_renderer) = weak_video_renderer.upgrade() else {
252                        return;
253                    };
254
255                    match message {
256                        GLPlayerMsgForward::Lock(sender) => {
257                            if let Some(holder) = video_renderer
258                                .lock()
259                                .unwrap()
260                                .current_frame_holder
261                                .as_mut() {
262                                    holder.lock();
263                                    sender.send(holder.get()).unwrap();
264                                };
265                        },
266                        GLPlayerMsgForward::Unlock() => {
267                            if let Some(holder) = video_renderer
268                                .lock()
269                                .unwrap()
270                                .current_frame_holder
271                                .as_mut() { holder.unlock() }
272                        },
273                        _ => (),
274                    }
275                }));
276            }),
277        );
278    }
279
280    fn reset(&mut self) {
281        self.player_id = None;
282
283        if let Some(glplayer_id) = self.glplayer_id.take() {
284            self.player_context
285                .send(GLPlayerMsg::UnregisterPlayer(glplayer_id));
286        }
287
288        self.current_frame_holder = None;
289
290        let mut updates = smallvec::smallvec![];
291
292        if let Some(current_frame) = self.current_frame.take() {
293            updates.push(ImageUpdate::DeleteImage(current_frame.image_key));
294        }
295
296        if let Some(old_image_key) = self.old_frame.take() {
297            updates.push(ImageUpdate::DeleteImage(old_image_key));
298        }
299
300        if let Some(very_old_image_key) = self.very_old_frame.take() {
301            updates.push(ImageUpdate::DeleteImage(very_old_image_key));
302        }
303
304        if !updates.is_empty() {
305            self.paint_api
306                .update_images(self.webview_id.into(), updates);
307        }
308    }
309
310    fn set_poster_frame(&mut self, image: Option<Arc<RasterImage>>) {
311        self.poster_frame = image.and_then(|image| {
312            image.id.map(|image_key| MediaFrame {
313                image_key,
314                width: image.metadata.width as i32,
315                height: image.metadata.height as i32,
316            })
317        });
318    }
319}
320
321impl Drop for MediaFrameRenderer {
322    fn drop(&mut self) {
323        self.reset();
324    }
325}
326
327impl VideoFrameRenderer for MediaFrameRenderer {
328    fn render(&mut self, frame: VideoFrame) {
329        if self.player_id.is_none() || (frame.is_gl_texture() && self.glplayer_id.is_none()) {
330            return;
331        }
332
333        let mut updates = smallvec::smallvec![];
334
335        if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) {
336            updates.push(ImageUpdate::DeleteImage(old_image_key));
337        }
338
339        let descriptor = ImageDescriptor::new(
340            frame.get_width(),
341            frame.get_height(),
342            ImageFormat::BGRA8,
343            ImageDescriptorFlags::empty(),
344        );
345
346        match &mut self.current_frame {
347            Some(current_frame)
348                if current_frame.width == frame.get_width() &&
349                    current_frame.height == frame.get_height() =>
350            {
351                if !frame.is_gl_texture() {
352                    updates.push(ImageUpdate::UpdateImage(
353                        current_frame.image_key,
354                        descriptor,
355                        SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
356                            frame.get_data(),
357                        )),
358                        None,
359                    ));
360                }
361
362                self.current_frame_holder
363                    .get_or_insert_with(|| FrameHolder::new(frame.clone()))
364                    .set(frame);
365
366                if let Some(old_image_key) = self.old_frame.take() {
367                    updates.push(ImageUpdate::DeleteImage(old_image_key));
368                }
369            },
370            Some(current_frame) => {
371                self.old_frame = Some(current_frame.image_key);
372
373                let Some(new_image_key) =
374                    self.paint_api.generate_image_key_blocking(self.webview_id)
375                else {
376                    return;
377                };
378
379                /* update current_frame */
380                current_frame.image_key = new_image_key;
381                current_frame.width = frame.get_width();
382                current_frame.height = frame.get_height();
383
384                // FIXME: This code is duplicated below this branch
385                let image_data = self
386                    .glplayer_id
387                    .filter(|_| frame.is_gl_texture())
388                    .map(|glplayer_id| {
389                        let texture_target = if frame.is_external_oes() {
390                            ImageBufferKind::TextureExternal
391                        } else {
392                            ImageBufferKind::Texture2D
393                        };
394
395                        SerializableImageData::External(ExternalImageData {
396                            id: ExternalImageId(glplayer_id),
397                            channel_index: 0,
398                            image_type: ExternalImageType::TextureHandle(texture_target),
399                            normalized_uvs: false,
400                        })
401                    })
402                    .unwrap_or_else(|| {
403                        SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
404                            frame.get_data(),
405                        ))
406                    });
407
408                self.current_frame_holder
409                    .get_or_insert_with(|| FrameHolder::new(frame.clone()))
410                    .set(frame);
411
412                updates.push(ImageUpdate::AddImage(
413                    new_image_key,
414                    descriptor,
415                    image_data,
416                    false,
417                ));
418            },
419            None => {
420                let Some(image_key) = self.paint_api.generate_image_key_blocking(self.webview_id)
421                else {
422                    return;
423                };
424
425                self.current_frame = Some(MediaFrame {
426                    image_key,
427                    width: frame.get_width(),
428                    height: frame.get_height(),
429                });
430
431                let image_data = self
432                    .glplayer_id
433                    .filter(|_| frame.is_gl_texture())
434                    .map(|glplayer_id| {
435                        let texture_target = if frame.is_external_oes() {
436                            ImageBufferKind::TextureExternal
437                        } else {
438                            ImageBufferKind::Texture2D
439                        };
440
441                        SerializableImageData::External(ExternalImageData {
442                            id: ExternalImageId(glplayer_id),
443                            channel_index: 0,
444                            image_type: ExternalImageType::TextureHandle(texture_target),
445                            normalized_uvs: false,
446                        })
447                    })
448                    .unwrap_or_else(|| {
449                        SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
450                            frame.get_data(),
451                        ))
452                    });
453
454                self.current_frame_holder = Some(FrameHolder::new(frame));
455
456                updates.push(ImageUpdate::AddImage(
457                    image_key, descriptor, image_data, false,
458                ));
459            },
460        }
461        self.paint_api
462            .update_images(self.webview_id.into(), updates);
463    }
464}
465
466#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
467#[derive(JSTraceable, MallocSizeOf)]
468enum SrcObject {
469    MediaStream(Dom<MediaStream>),
470    Blob(Dom<Blob>),
471}
472
473impl From<MediaStreamOrBlob> for SrcObject {
474    fn from(src_object: MediaStreamOrBlob) -> SrcObject {
475        match src_object {
476            MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)),
477            MediaStreamOrBlob::MediaStream(stream) => {
478                SrcObject::MediaStream(Dom::from_ref(&*stream))
479            },
480        }
481    }
482}
483
484#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
485enum LoadState {
486    NotLoaded,
487    LoadingFromSrcObject,
488    LoadingFromSrcAttribute,
489    LoadingFromSourceChild,
490    WaitingForSource,
491}
492
493/// <https://html.spec.whatwg.org/multipage/#loading-the-media-resource:media-element-29>
494#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
495#[derive(JSTraceable, MallocSizeOf)]
496struct SourceChildrenPointer {
497    source_before_pointer: Dom<HTMLSourceElement>,
498    inclusive: bool,
499}
500
501impl SourceChildrenPointer {
502    fn new(source_before_pointer: DomRoot<HTMLSourceElement>, inclusive: bool) -> Self {
503        Self {
504            source_before_pointer: source_before_pointer.as_traced(),
505            inclusive,
506        }
507    }
508}
509
510/// Generally the presence of the loop attribute should be considered to mean playback has not
511/// "ended", as "ended" and "looping" are mutually exclusive.
512/// <https://html.spec.whatwg.org/multipage/#ended-playback>
513#[derive(Clone, Copy, Debug, PartialEq)]
514enum LoopCondition {
515    Included,
516    Ignored,
517}
518
519#[dom_struct]
520pub(crate) struct HTMLMediaElement {
521    htmlelement: HTMLElement,
522    /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
523    network_state: Cell<NetworkState>,
524    /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
525    ready_state: Cell<ReadyState>,
526    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
527    src_object: DomRefCell<Option<SrcObject>>,
528    /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc>
529    current_src: DomRefCell<String>,
530    /// Incremented whenever tasks associated with this element are cancelled.
531    generation_id: Cell<u32>,
532    /// <https://html.spec.whatwg.org/multipage/#fire-loadeddata>
533    ///
534    /// Reset to false every time the load algorithm is invoked.
535    fired_loadeddata_event: Cell<bool>,
536    /// <https://html.spec.whatwg.org/multipage/#dom-media-error>
537    error: MutNullableDom<MediaError>,
538    /// <https://html.spec.whatwg.org/multipage/#dom-media-paused>
539    paused: Cell<bool>,
540    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
541    default_playback_rate: Cell<f64>,
542    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
543    playback_rate: Cell<f64>,
544    /// <https://html.spec.whatwg.org/multipage/#attr-media-autoplay>
545    autoplaying: Cell<bool>,
546    /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag>
547    delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>,
548    /// <https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises>
549    #[conditional_malloc_size_of]
550    pending_play_promises: DomRefCell<Vec<Rc<Promise>>>,
551    /// Play promises which are soon to be fulfilled by a queued task.
552    #[expect(clippy::type_complexity)]
553    #[conditional_malloc_size_of]
554    in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
555    #[ignore_malloc_size_of = "servo_media"]
556    #[no_trace]
557    player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
558    #[conditional_malloc_size_of]
559    #[no_trace]
560    video_renderer: Arc<Mutex<MediaFrameRenderer>>,
561    #[ignore_malloc_size_of = "servo_media"]
562    #[no_trace]
563    audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>,
564    #[conditional_malloc_size_of]
565    #[no_trace]
566    event_handler: RefCell<Option<Arc<Mutex<HTMLMediaElementEventHandler>>>>,
567    /// <https://html.spec.whatwg.org/multipage/#show-poster-flag>
568    show_poster: Cell<bool>,
569    /// <https://html.spec.whatwg.org/multipage/#dom-media-duration>
570    duration: Cell<f64>,
571    /// <https://html.spec.whatwg.org/multipage/#current-playback-position>
572    current_playback_position: Cell<f64>,
573    /// <https://html.spec.whatwg.org/multipage/#official-playback-position>
574    official_playback_position: Cell<f64>,
575    /// <https://html.spec.whatwg.org/multipage/#default-playback-start-position>
576    default_playback_start_position: Cell<f64>,
577    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
578    volume: Cell<f64>,
579    /// <https://html.spec.whatwg.org/multipage/#dom-media-seeking>
580    seeking: Cell<bool>,
581    /// The latest seek position (in seconds) is used to distinguish whether the seek request was
582    /// initiated by a script or by the user agent itself, rather than by the media engine and to
583    /// abort other running instance of the `seek` algorithm.
584    current_seek_position: Cell<f64>,
585    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
586    muted: Cell<bool>,
587    /// Loading state from source, if any.
588    load_state: Cell<LoadState>,
589    source_children_pointer: DomRefCell<Option<SourceChildrenPointer>>,
590    current_source_child: MutNullableDom<HTMLSourceElement>,
591    /// URL of the media resource, if any.
592    #[no_trace]
593    resource_url: DomRefCell<Option<ServoUrl>>,
594    /// URL of the media resource, if the resource is set through the src_object attribute and it
595    /// is a blob.
596    #[no_trace]
597    blob_url: DomRefCell<Option<ServoUrl>>,
598    /// <https://html.spec.whatwg.org/multipage/#dom-media-played>
599    played: DomRefCell<TimeRangesContainer>,
600    // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks
601    audio_tracks_list: MutNullableDom<AudioTrackList>,
602    // https://html.spec.whatwg.org/multipage/#dom-media-videotracks
603    video_tracks_list: MutNullableDom<VideoTrackList>,
604    /// <https://html.spec.whatwg.org/multipage/#dom-media-texttracks>
605    text_tracks_list: MutNullableDom<TextTrackList>,
606    /// Time of last timeupdate notification.
607    #[ignore_malloc_size_of = "Defined in std::time"]
608    next_timeupdate_event: Cell<Instant>,
609    /// Latest fetch request context.
610    current_fetch_context: RefCell<Option<HTMLMediaElementFetchContext>>,
611    /// Media controls id.
612    /// In order to workaround the lack of privileged JS context, we secure the
613    /// the access to the "privileged" document.servoGetMediaControls(id) API by
614    /// keeping a whitelist of media controls identifiers.
615    media_controls_id: DomRefCell<Option<String>>,
616}
617
618/// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
619#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
620#[repr(u8)]
621pub(crate) enum NetworkState {
622    Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
623    Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
624    Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
625    NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8,
626}
627
628/// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
629#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
630#[repr(u8)]
631#[expect(clippy::enum_variant_names)] // Clippy warning silenced here because these names are from the specification.
632pub(crate) enum ReadyState {
633    HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8,
634    HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8,
635    HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8,
636    HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8,
637    HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8,
638}
639
640/// <https://html.spec.whatwg.org/multipage/#direction-of-playback>
641#[derive(Clone, Copy, PartialEq)]
642enum PlaybackDirection {
643    Forwards,
644    Backwards,
645}
646
647impl HTMLMediaElement {
648    pub(crate) fn new_inherited(
649        tag_name: LocalName,
650        prefix: Option<Prefix>,
651        document: &Document,
652    ) -> Self {
653        Self {
654            htmlelement: HTMLElement::new_inherited(tag_name, prefix, document),
655            network_state: Cell::new(NetworkState::Empty),
656            ready_state: Cell::new(ReadyState::HaveNothing),
657            src_object: Default::default(),
658            current_src: DomRefCell::new("".to_owned()),
659            generation_id: Cell::new(0),
660            fired_loadeddata_event: Cell::new(false),
661            error: Default::default(),
662            paused: Cell::new(true),
663            default_playback_rate: Cell::new(1.0),
664            playback_rate: Cell::new(1.0),
665            muted: Cell::new(false),
666            load_state: Cell::new(LoadState::NotLoaded),
667            source_children_pointer: DomRefCell::new(None),
668            current_source_child: Default::default(),
669            // FIXME(nox): Why is this initialised to true?
670            autoplaying: Cell::new(true),
671            delaying_the_load_event_flag: Default::default(),
672            pending_play_promises: Default::default(),
673            in_flight_play_promises_queue: Default::default(),
674            player: Default::default(),
675            video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new(
676                document.webview_id(),
677                document.window().paint_api().clone(),
678                document.window().get_player_context(),
679            ))),
680            audio_renderer: Default::default(),
681            event_handler: Default::default(),
682            show_poster: Cell::new(true),
683            duration: Cell::new(f64::NAN),
684            current_playback_position: Cell::new(0.),
685            official_playback_position: Cell::new(0.),
686            default_playback_start_position: Cell::new(0.),
687            volume: Cell::new(1.0),
688            seeking: Cell::new(false),
689            current_seek_position: Cell::new(f64::NAN),
690            resource_url: DomRefCell::new(None),
691            blob_url: DomRefCell::new(None),
692            played: DomRefCell::new(TimeRangesContainer::default()),
693            audio_tracks_list: Default::default(),
694            video_tracks_list: Default::default(),
695            text_tracks_list: Default::default(),
696            next_timeupdate_event: Cell::new(Instant::now() + Duration::from_millis(250)),
697            current_fetch_context: RefCell::new(None),
698            media_controls_id: DomRefCell::new(None),
699        }
700    }
701
702    pub(crate) fn network_state(&self) -> NetworkState {
703        self.network_state.get()
704    }
705
706    pub(crate) fn get_ready_state(&self) -> ReadyState {
707        self.ready_state.get()
708    }
709
710    fn media_type_id(&self) -> HTMLMediaElementTypeId {
711        match self.upcast::<Node>().type_id() {
712            NodeTypeId::Element(ElementTypeId::HTMLElement(
713                HTMLElementTypeId::HTMLMediaElement(media_type_id),
714            )) => media_type_id,
715            _ => unreachable!(),
716        }
717    }
718
719    fn update_media_state(&self) {
720        let is_playing = self
721            .player
722            .borrow()
723            .as_ref()
724            .is_some_and(|player| !player.lock().unwrap().paused());
725
726        if self.is_potentially_playing() && !is_playing {
727            if let Some(ref player) = *self.player.borrow() {
728                let player = player.lock().unwrap();
729
730                if let Err(error) = player.set_playback_rate(self.playback_rate.get()) {
731                    warn!("Could not set the playback rate: {error:?}");
732                }
733                if let Err(error) = player.set_volume(self.volume.get()) {
734                    warn!("Could not set the volume: {error:?}");
735                }
736                if let Err(error) = player.play() {
737                    error!("Could not play media: {error:?}");
738                }
739            }
740        } else if is_playing &&
741            let Some(ref player) = *self.player.borrow() &&
742            let Err(error) = player.lock().unwrap().pause()
743        {
744            error!("Could not pause player: {error:?}");
745        }
746    }
747
748    /// Marks that element as delaying the load event or not.
749    ///
750    /// Nothing happens if the element was already delaying the load event and
751    /// we pass true to that method again.
752    ///
753    /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag>
754    pub(crate) fn delay_load_event(&self, delay: bool, cx: &mut JSContext) {
755        let blocker = &self.delaying_the_load_event_flag;
756
757        if delay {
758            if blocker.borrow().is_none() {
759                *blocker.borrow_mut() =
760                    Some(LoadBlocker::new(&self.owner_document(), LoadType::Media));
761            }
762        } else {
763            LoadBlocker::terminate(blocker, cx);
764        }
765    }
766
767    /// <https://html.spec.whatwg.org/multipage/#time-marches-on>
768    fn time_marches_on(&self) {
769        // Step 6. If the time was reached through the usual monotonic increase of the current
770        // playback position during normal playback, and if the user agent has not fired a
771        // timeupdate event at the element in the past 15 to 250ms and is not still running event
772        // handlers for such an event, then the user agent must queue a media element task given the
773        // media element to fire an event named timeupdate at the element.
774        if Instant::now() > self.next_timeupdate_event.get() {
775            self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
776            self.next_timeupdate_event
777                .set(Instant::now() + Duration::from_millis(250));
778        }
779    }
780
781    /// <https://html.spec.whatwg.org/multipage/#internal-play-steps>
782    fn internal_play_steps(&self, cx: &mut JSContext) {
783        // Step 1. If the media element's networkState attribute has the value NETWORK_EMPTY, invoke
784        // the media element's resource selection algorithm.
785        if self.network_state.get() == NetworkState::Empty {
786            self.invoke_resource_selection_algorithm(cx);
787        }
788
789        // Step 2. If the playback has ended and the direction of playback is forwards, seek to the
790        // earliest possible position of the media resource.
791        // Generally "ended" and "looping" are exclusive. Here, the loop attribute is ignored to
792        // seek back to start in case loop was set after playback ended.
793        // <https://github.com/whatwg/html/issues/4487>
794        if self.ended_playback(LoopCondition::Ignored) &&
795            self.direction_of_playback() == PlaybackDirection::Forwards
796        {
797            self.seek(
798                self.earliest_possible_position(),
799                /* approximate_for_speed */ false,
800            );
801        }
802
803        let state = self.ready_state.get();
804
805        // Step 3. If the media element's paused attribute is true, then:
806        if self.Paused() {
807            // Step 3.1. Change the value of paused to false.
808            self.paused.set(false);
809
810            // Step 3.2. If the show poster flag is true, set the element's show poster flag to
811            // false and run the time marches on steps.
812            if self.show_poster.get() {
813                self.show_poster.set(false);
814                self.time_marches_on();
815            }
816
817            // Step 3.3. Queue a media element task given the media element to fire an event named
818            // play at the element.
819            self.queue_media_element_task_to_fire_event(atom!("play"));
820
821            // Step 3.4. If the media element's readyState attribute has the value HAVE_NOTHING,
822            // HAVE_METADATA, or HAVE_CURRENT_DATA, queue a media element task given the media
823            // element to fire an event named waiting at the element. Otherwise, the media element's
824            // readyState attribute has the value HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about
825            // playing for the element.
826            match state {
827                ReadyState::HaveNothing |
828                ReadyState::HaveMetadata |
829                ReadyState::HaveCurrentData => {
830                    self.queue_media_element_task_to_fire_event(atom!("waiting"));
831                },
832                ReadyState::HaveFutureData | ReadyState::HaveEnoughData => {
833                    self.notify_about_playing();
834                },
835            }
836        }
837        // Step 4. Otherwise, if the media element's readyState attribute has the value
838        // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and queue a media
839        // element task given the media element to resolve pending play promises with the
840        // result.
841        else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
842            self.take_pending_play_promises(Ok(()));
843
844            let this = Trusted::new(self);
845            let generation_id = self.generation_id.get();
846
847            self.owner_global()
848                .task_manager()
849                .media_element_task_source()
850                .queue(task!(resolve_pending_play_promises: move |cx| {
851                    let this = this.root();
852                    if generation_id != this.generation_id.get() {
853                        return;
854                    }
855
856                    this.fulfill_in_flight_play_promises(cx, |_| {});
857                }));
858        }
859
860        // Step 5. Set the media element's can autoplay flag to false.
861        self.autoplaying.set(false);
862
863        self.update_media_state();
864    }
865
866    /// <https://html.spec.whatwg.org/multipage/#internal-pause-steps>
867    fn internal_pause_steps(&self) {
868        // Step 1. Set the media element's can autoplay flag to false.
869        self.autoplaying.set(false);
870
871        // Step 2. If the media element's paused attribute is false, run the following steps:
872        if !self.Paused() {
873            // Step 2.1. Change the value of paused to true.
874            self.paused.set(true);
875
876            // Step 2.2. Take pending play promises and let promises be the result.
877            self.take_pending_play_promises(Err(Error::Abort(None)));
878
879            // Step 2.3. Queue a media element task given the media element and the following steps:
880            let this = Trusted::new(self);
881            let generation_id = self.generation_id.get();
882
883            self.owner_global()
884                .task_manager()
885                .media_element_task_source()
886                .queue(task!(internal_pause_steps: move |cx| {
887                    let this = this.root();
888                    if generation_id != this.generation_id.get() {
889                        return;
890                    }
891
892                    this.fulfill_in_flight_play_promises(cx, |cx| {
893                        // Step 2.3.1. Fire an event named timeupdate at the element.
894                        this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
895
896                        // Step 2.3.2. Fire an event named pause at the element.
897                        this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
898
899                        // Step 2.3.3. Reject pending play promises with promises and an
900                        // "AbortError" DOMException.
901                        // Done after running this closure in `fulfill_in_flight_play_promises`.
902                    });
903                }));
904
905            // Step 2.4. Set the official playback position to the current playback position.
906            self.official_playback_position
907                .set(self.current_playback_position.get());
908        }
909
910        self.update_media_state();
911    }
912
913    /// <https://html.spec.whatwg.org/multipage/#allowed-to-play>
914    fn is_allowed_to_play(&self) -> bool {
915        true
916    }
917
918    /// <https://html.spec.whatwg.org/multipage/#notify-about-playing>
919    fn notify_about_playing(&self) {
920        // Step 1. Take pending play promises and let promises be the result.
921        self.take_pending_play_promises(Ok(()));
922
923        // Step 2. Queue a media element task given the element and the following steps:
924        let this = Trusted::new(self);
925        let generation_id = self.generation_id.get();
926
927        self.owner_global()
928            .task_manager()
929            .media_element_task_source()
930            .queue(task!(notify_about_playing: move |cx| {
931                let this = this.root();
932                if generation_id != this.generation_id.get() {
933                    return;
934                }
935
936                this.fulfill_in_flight_play_promises(cx, |cx| {
937                    // Step 2.1. Fire an event named playing at the element.
938                    this.upcast::<EventTarget>().fire_event(cx, atom!("playing"));
939
940                    // Step 2.2. Resolve pending play promises with promises.
941                    // Done after running this closure in `fulfill_in_flight_play_promises`.
942                });
943            }));
944    }
945
946    /// <https://html.spec.whatwg.org/multipage/#ready-states>
947    #[expect(
948        clippy::collapsible_match,
949        reason = "This way follows the spec more closely"
950    )]
951    fn change_ready_state(&self, ready_state: ReadyState) {
952        let old_ready_state = self.ready_state.get();
953        self.ready_state.set(ready_state);
954
955        if self.network_state.get() == NetworkState::Empty {
956            return;
957        }
958
959        if old_ready_state == ready_state {
960            return;
961        }
962
963        // Step 1. Apply the first applicable set of substeps from the following list:
964        match (old_ready_state, ready_state) {
965            // => "If the previous ready state was HAVE_NOTHING, and the new ready state is
966            // HAVE_METADATA"
967            (ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
968                // Queue a media element task given the media element to fire an event named
969                // loadedmetadata at the element.
970                self.queue_media_element_task_to_fire_event(atom!("loadedmetadata"));
971                // No other steps are applicable in this case.
972                return;
973            },
974            // => "If the previous ready state was HAVE_METADATA and the new ready state is
975            // HAVE_CURRENT_DATA or greater"
976            (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => {
977                // If this is the first time this occurs for this media element since the load()
978                // algorithm was last invoked, the user agent must queue a media element task given
979                // the media element to fire an event named loadeddata at the element.
980                if !self.fired_loadeddata_event.get() {
981                    self.fired_loadeddata_event.set(true);
982
983                    let this = Trusted::new(self);
984                    let generation_id = self.generation_id.get();
985
986                    self.owner_global()
987                        .task_manager()
988                        .media_element_task_source()
989                        .queue(task!(media_reached_current_data: move |cx| {
990                            let this = this.root();
991                            if generation_id != this.generation_id.get() {
992                                return;
993                            }
994
995                            this.upcast::<EventTarget>().fire_event(cx, atom!("loadeddata"));
996                            // Once the readyState attribute reaches HAVE_CURRENT_DATA, after the
997                            // loadeddata event has been fired, set the element's
998                            // delaying-the-load-event flag to false.
999                            this.delay_load_event(false, cx);
1000                        }));
1001                }
1002
1003                // Steps for the transition from HaveMetadata to HaveCurrentData
1004                // or HaveFutureData also apply here, as per the next match
1005                // expression.
1006            },
1007            (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => {
1008                // FIXME(nox): Queue a task to fire timeupdate and waiting
1009                // events if the conditions call from the spec are met.
1010
1011                // No other steps are applicable in this case.
1012                return;
1013            },
1014
1015            _ => (),
1016        }
1017
1018        // => "If the previous ready state was HAVE_CURRENT_DATA or less, and the new ready state is
1019        // HAVE_FUTURE_DATA or more"
1020        if old_ready_state <= ReadyState::HaveCurrentData &&
1021            ready_state >= ReadyState::HaveFutureData
1022        {
1023            // The user agent must queue a media element task given the media element to fire an
1024            // event named canplay at the element.
1025            self.queue_media_element_task_to_fire_event(atom!("canplay"));
1026
1027            // If the element's paused attribute is false, the user agent must notify about playing
1028            // for the element.
1029            if !self.Paused() {
1030                self.notify_about_playing();
1031            }
1032        }
1033
1034        // => "If the new ready state is HAVE_ENOUGH_DATA"
1035        if ready_state == ReadyState::HaveEnoughData {
1036            // The user agent must queue a media element task given the media element to fire an
1037            // event named canplaythrough at the element.
1038            self.queue_media_element_task_to_fire_event(atom!("canplaythrough"));
1039
1040            // If the element is eligible for autoplay, then the user agent may run the following
1041            // substeps:
1042            if self.eligible_for_autoplay() {
1043                // Step 1. Set the paused attribute to false.
1044                self.paused.set(false);
1045
1046                // Step 2. If the element's show poster flag is true, set it to false and run the
1047                // time marches on steps.
1048                if self.show_poster.get() {
1049                    self.show_poster.set(false);
1050                    self.time_marches_on();
1051                }
1052
1053                // Step 3. Queue a media element task given the element to fire an event named play
1054                // at the element.
1055                self.queue_media_element_task_to_fire_event(atom!("play"));
1056
1057                // Step 4. Notify about playing for the element.
1058                self.notify_about_playing();
1059            }
1060        }
1061
1062        self.update_media_state();
1063    }
1064
1065    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1066    fn invoke_resource_selection_algorithm(&self, cx: &mut JSContext) {
1067        // Step 1. Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1068        self.network_state.set(NetworkState::NoSource);
1069
1070        // Step 2. Set the element's show poster flag to true.
1071        self.show_poster.set(true);
1072
1073        // Step 3. Set the media element's delaying-the-load-event flag to true (this delays the
1074        // load event).
1075        self.delay_load_event(true, cx);
1076
1077        // Step 4. Await a stable state, allowing the task that invoked this algorithm to continue.
1078        // If the resource selection mode in the synchronous section is
1079        // "attribute", the URL of the resource to fetch is relative to the
1080        // media element's node document when the src attribute was last
1081        // changed, which is why we need to pass the base URL in the task
1082        // right here.
1083        let task = MediaElementMicrotask::ResourceSelection {
1084            elem: DomRoot::from_ref(self),
1085            generation_id: self.generation_id.get(),
1086            base_url: self.owner_document().base_url(),
1087        };
1088
1089        // FIXME(nox): This will later call the resource_selection_algorithm_sync
1090        // method from below, if microtasks were trait objects, we would be able
1091        // to put the code directly in this method, without the boilerplate
1092        // indirections.
1093        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1094    }
1095
1096    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1097    fn resource_selection_algorithm_sync(&self, base_url: ServoUrl, cx: &mut JSContext) {
1098        // TODO Step 5. If the media element's blocked-on-parser flag is false, then populate the
1099        // list of pending text tracks.
1100        // FIXME(ferjm): Implement blocked_on_parser logic
1101        // https://html.spec.whatwg.org/multipage/#blocked-on-parser
1102        // FIXME(nox): Maybe populate the list of pending text tracks.
1103
1104        enum Mode {
1105            Object,
1106            Attribute(String),
1107            Children(DomRoot<HTMLSourceElement>),
1108        }
1109
1110        // Step 6.
1111        let mode = if self.src_object.borrow().is_some() {
1112            // If the media element has an assigned media provider object, then let mode be object.
1113            Mode::Object
1114        } else if let Some(src) = self
1115            .upcast::<Element>()
1116            .get_attribute_string_value(&local_name!("src"))
1117        {
1118            // Otherwise, if the media element has no assigned media provider object but has a src
1119            // attribute, then let mode be attribute.
1120            Mode::Attribute(src)
1121        } else if let Some(source) = self
1122            .upcast::<Node>()
1123            .children_unrooted(cx.no_gc())
1124            .find_map(UnrootedDom::downcast::<HTMLSourceElement>)
1125        {
1126            // Otherwise, if the media element does not have an assigned media provider object and
1127            // does not have a src attribute, but does have a source element child, then let mode be
1128            // children and let candidate be the first such source element child in tree order.
1129            Mode::Children(source.as_rooted())
1130        } else {
1131            // Otherwise, the media element has no assigned media provider object and has neither a
1132            // src attribute nor a source element child:
1133            self.load_state.set(LoadState::NotLoaded);
1134
1135            // Step 6.none.1. Set the networkState to NETWORK_EMPTY.
1136            self.network_state.set(NetworkState::Empty);
1137
1138            // Step 6.none.2. Set the element's delaying-the-load-event flag to false. This stops
1139            // delaying the load event.
1140            self.delay_load_event(false, cx);
1141
1142            // Step 6.none.3. End the synchronous section and return.
1143            return;
1144        };
1145
1146        // Step 7. Set the media element's networkState to NETWORK_LOADING.
1147        self.network_state.set(NetworkState::Loading);
1148
1149        // Step 8. Queue a media element task given the media element to fire an event named
1150        // loadstart at the media element.
1151        self.queue_media_element_task_to_fire_event(atom!("loadstart"));
1152
1153        // Step 9. Run the appropriate steps from the following list:
1154        match mode {
1155            Mode::Object => {
1156                // => "If mode is object"
1157                self.load_from_src_object();
1158            },
1159            Mode::Attribute(src) => {
1160                // => "If mode is attribute"
1161                self.load_from_src_attribute(base_url, &src);
1162            },
1163            Mode::Children(source) => {
1164                // => "Otherwise (mode is children)""
1165                self.load_from_source_child(&source);
1166            },
1167        }
1168    }
1169
1170    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1171    fn load_from_src_object(&self) {
1172        self.load_state.set(LoadState::LoadingFromSrcObject);
1173
1174        // Step 9.object.1. Set the currentSrc attribute to the empty string.
1175        "".clone_into(&mut self.current_src.borrow_mut());
1176
1177        // Step 9.object.3. Run the resource fetch algorithm with the assigned media
1178        // provider object. If that algorithm returns without aborting this one, then the
1179        // load failed.
1180        // Note that the resource fetch algorithm itself takes care of the cleanup in case
1181        // of failure itself.
1182        self.resource_fetch_algorithm(Resource::Object);
1183    }
1184
1185    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1186    fn load_from_src_attribute(&self, base_url: ServoUrl, src: &str) {
1187        self.load_state.set(LoadState::LoadingFromSrcAttribute);
1188
1189        // Step 9.attribute.1. If the src attribute's value is the empty string, then end
1190        // the synchronous section, and jump down to the failed with attribute step below.
1191        if src.is_empty() {
1192            self.queue_dedicated_media_source_failure_steps();
1193            return;
1194        }
1195
1196        // Step 9.attribute.2. Let urlRecord be the result of encoding-parsing a URL given
1197        // the src attribute's value, relative to the media element's node document when the
1198        // src attribute was last changed.
1199        let Ok(url_record) = base_url.join(src) else {
1200            self.queue_dedicated_media_source_failure_steps();
1201            return;
1202        };
1203
1204        // Step 9.attribute.3. If urlRecord is not failure, then set the currentSrc
1205        // attribute to the result of applying the URL serializer to urlRecord.
1206        *self.current_src.borrow_mut() = url_record.as_str().into();
1207
1208        // Step 9.attribute.5. If urlRecord is not failure, then run the resource fetch
1209        // algorithm with urlRecord. If that algorithm returns without aborting this one,
1210        // then the load failed.
1211        // Note that the resource fetch algorithm itself takes care
1212        // of the cleanup in case of failure itself.
1213        self.resource_fetch_algorithm(Resource::Url(url_record));
1214    }
1215
1216    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1217    fn load_from_source_child(&self, source: &HTMLSourceElement) {
1218        self.load_state.set(LoadState::LoadingFromSourceChild);
1219
1220        // Step 9.children.1. Let pointer be a position defined by two adjacent nodes in the media
1221        // element's child list, treating the start of the list (before the first child in the list,
1222        // if any) and end of the list (after the last child in the list, if any) as nodes in their
1223        // own right. One node is the node before pointer, and the other node is the node after
1224        // pointer. Initially, let pointer be the position between the candidate node and the next
1225        // node, if there are any, or the end of the list, if it is the last node.
1226        *self.source_children_pointer.borrow_mut() =
1227            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), false));
1228
1229        let element = source.upcast::<Element>();
1230
1231        // Step 9.children.2. Process candidate: If candidate does not have a src attribute, or if
1232        // its src attribute's value is the empty string, then end the synchronous section, and jump
1233        // down to the failed with elements step below.
1234        let Some(src) = element
1235            .get_attribute_string_value(&local_name!("src"))
1236            .filter(|value| !value.is_empty())
1237        else {
1238            self.load_from_source_child_failure_steps(source);
1239            return;
1240        };
1241
1242        // Step 9.children.3. If candidate has a media attribute whose value does not match the
1243        // environment, then end the synchronous section, and jump down to the failed with elements
1244        // step below.
1245        if let Some(media) = element.get_attribute_string_value(&local_name!("media")) &&
1246            !MediaList::matches_environment(&element.owner_document(), &media)
1247        {
1248            self.load_from_source_child_failure_steps(source);
1249            return;
1250        }
1251
1252        // Step 9.children.4. Let urlRecord be the result of encoding-parsing a URL given
1253        // candidate's src attribute's value, relative to candidate's node document when the src
1254        // attribute was last changed.
1255        let Ok(url_record) = source.owner_document().base_url().join(&src) else {
1256            // Step 9.children.5. If urlRecord is failure, then end the synchronous section,
1257            // and jump down to the failed with elements step below.
1258            self.load_from_source_child_failure_steps(source);
1259            return;
1260        };
1261
1262        // Step 9.children.6. If candidate has a type attribute whose value, when parsed as a MIME
1263        // type (including any codecs described by the codecs parameter, for types that define that
1264        // parameter), represents a type that the user agent knows it cannot render, then end the
1265        // synchronous section, and jump down to the failed with elements step below.
1266        if let Some(type_) = element.get_attribute_string_value(&local_name!("type")) &&
1267            ServoMedia::get().can_play_type(&type_) == SupportsMediaType::No
1268        {
1269            self.load_from_source_child_failure_steps(source);
1270            return;
1271        }
1272
1273        // Reset the media player before loading the next source child.
1274        self.reset_media_player();
1275
1276        self.current_source_child.set(Some(source));
1277
1278        // Step 9.children.7. Set the currentSrc attribute to the result of applying the URL
1279        // serializer to urlRecord.
1280        *self.current_src.borrow_mut() = url_record.as_str().into();
1281
1282        // Step 9.children.9. Run the resource fetch algorithm with urlRecord. If that
1283        // algorithm returns without aborting this one, then the load failed.
1284        // Note that the resource fetch algorithm itself takes care
1285        // of the cleanup in case of failure itself.
1286        self.resource_fetch_algorithm(Resource::Url(url_record));
1287    }
1288
1289    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1290    fn load_from_source_child_failure_steps(&self, source: &HTMLSourceElement) {
1291        // Step 9.children.10. Failed with elements: Queue a media element task given the media
1292        // element to fire an event named error at candidate.
1293        let trusted_this = Trusted::new(self);
1294        let trusted_source = Trusted::new(source);
1295        let generation_id = self.generation_id.get();
1296
1297        self.owner_global()
1298            .task_manager()
1299            .media_element_task_source()
1300            .queue(task!(queue_error_event: move |cx| {
1301                let this = trusted_this.root();
1302                if generation_id != this.generation_id.get() {
1303                    return;
1304                }
1305
1306                let source = trusted_source.root();
1307                source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1308            }));
1309
1310        // Step 9.children.11. Await a stable state.
1311        let task = MediaElementMicrotask::SelectNextSourceChild {
1312            elem: DomRoot::from_ref(self),
1313            generation_id: self.generation_id.get(),
1314        };
1315
1316        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1317    }
1318
1319    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1320    fn select_next_source_child(&self, cx: &mut JSContext) {
1321        // Step 9.children.12. Forget the media element's media-resource-specific tracks.
1322        self.AudioTracks(cx).clear();
1323        self.VideoTracks(CanGc::from_cx(cx)).clear();
1324
1325        // Step 9.children.13. Find next candidate: Let candidate be null.
1326        let mut source_candidate = None;
1327
1328        // Step 9.children.14. Search loop: If the node after pointer is the end of the list, then
1329        // jump to the waiting step below.
1330        // Step 9.children.15. If the node after pointer is a source element, let candidate be that
1331        // element.
1332        // Step 9.children.16. Advance pointer so that the node before pointer is now the node that
1333        // was after pointer, and the node after pointer is the node after the node that used to be
1334        // after pointer, if any.
1335        if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1336            // Note that shared implementation between opaque types from
1337            // `inclusively_following_siblings` and `following_siblings` if not possible due to
1338            // precise capturing.
1339            if source_children_pointer.inclusive {
1340                for next_sibling in source_children_pointer
1341                    .source_before_pointer
1342                    .upcast::<Node>()
1343                    .inclusively_following_siblings()
1344                {
1345                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1346                    {
1347                        source_candidate = Some(next_source);
1348                        break;
1349                    }
1350                }
1351            } else {
1352                for next_sibling in source_children_pointer
1353                    .source_before_pointer
1354                    .upcast::<Node>()
1355                    .following_siblings()
1356                {
1357                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1358                    {
1359                        source_candidate = Some(next_source);
1360                        break;
1361                    }
1362                }
1363            };
1364        }
1365
1366        // Step 9.children.17. If candidate is null, jump back to the search loop step. Otherwise,
1367        // jump back to the process candidate step.
1368        if let Some(source_candidate) = source_candidate {
1369            self.load_from_source_child(&source_candidate);
1370            return;
1371        }
1372
1373        self.load_state.set(LoadState::WaitingForSource);
1374
1375        *self.source_children_pointer.borrow_mut() = None;
1376
1377        // Step 9.children.18. Waiting: Set the element's networkState attribute to the
1378        // NETWORK_NO_SOURCE value.
1379        self.network_state.set(NetworkState::NoSource);
1380
1381        // Step 9.children.19. Set the element's show poster flag to true.
1382        self.show_poster.set(true);
1383
1384        // Step 9.children.20. Queue a media element task given the media element to set the
1385        // element's delaying-the-load-event flag to false. This stops delaying the load event.
1386        let this = Trusted::new(self);
1387        let generation_id = self.generation_id.get();
1388
1389        self.owner_global()
1390            .task_manager()
1391            .media_element_task_source()
1392            .queue(task!(queue_delay_load_event: move |cx| {
1393                let this = this.root();
1394                if generation_id != this.generation_id.get() {
1395                    return;
1396                }
1397
1398                this.delay_load_event(false, cx);
1399            }));
1400
1401        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1402        // list. (This step might wait forever.)
1403    }
1404
1405    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1406    fn resource_selection_algorithm_failure_steps(&self) {
1407        match self.load_state.get() {
1408            LoadState::LoadingFromSrcObject => {
1409                // Step 9.object.4. Failed with media provider: Reaching this step indicates that
1410                // the media resource failed to load. Take pending play promises and queue a media
1411                // element task given the media element to run the dedicated media source failure
1412                // steps with the result.
1413                self.queue_dedicated_media_source_failure_steps();
1414            },
1415            LoadState::LoadingFromSrcAttribute => {
1416                // Step 9.attribute.6. Failed with attribute: Reaching this step indicates that the
1417                // media resource failed to load or that urlRecord is failure. Take pending play
1418                // promises and queue a media element task given the media element to run the
1419                // dedicated media source failure steps with the result.
1420                self.queue_dedicated_media_source_failure_steps();
1421            },
1422            LoadState::LoadingFromSourceChild => {
1423                // Step 9.children.10. Failed with elements: Queue a media element task given the
1424                // media element to fire an event named error at candidate.
1425                if let Some(source) = self.current_source_child.take() {
1426                    self.load_from_source_child_failure_steps(&source);
1427                }
1428            },
1429            _ => {},
1430        }
1431    }
1432
1433    fn fetch_request(&self, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1434        if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1435            error!("Missing request url");
1436            if let Some(seek_lock) = seek_lock {
1437                seek_lock.unlock(/* successful seek */ false);
1438            }
1439            self.resource_selection_algorithm_failure_steps();
1440            return;
1441        }
1442
1443        let document = self.owner_document();
1444        let destination = match self.media_type_id() {
1445            HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1446            HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1447        };
1448        let mut headers = HeaderMap::new();
1449        // FIXME(eijebong): Use typed headers once we have a constructor for the range header
1450        headers.insert(
1451            header::RANGE,
1452            HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(),
1453        );
1454        let url = match self.resource_url.borrow().as_ref() {
1455            Some(url) => url.clone(),
1456            None => self.blob_url.borrow().as_ref().unwrap().clone(),
1457        };
1458
1459        let cors_setting = cors_setting_for_element(self.upcast());
1460        let global = self.global();
1461        let request = create_a_potential_cors_request(
1462            Some(document.webview_id()),
1463            url.clone(),
1464            destination,
1465            cors_setting,
1466            None,
1467            global.get_referrer(),
1468        )
1469        .with_global_scope(&global)
1470        .headers(headers)
1471        .referrer_policy(document.get_referrer_policy());
1472
1473        let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1474        if let Some(ref mut current_fetch_context) = *current_fetch_context {
1475            current_fetch_context.cancel(CancelReason::Abort);
1476        }
1477
1478        *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1479            request.id,
1480            global.core_resource_thread(),
1481        ));
1482        let listener =
1483            HTMLMediaElementFetchListener::new(self, request.id, url, offset.unwrap_or(0));
1484
1485        self.owner_document().fetch_background(request, listener);
1486
1487        // Since we cancelled the previous fetch, from now on the media element
1488        // will only receive response data from the new fetch that's been
1489        // initiated. This means the player can resume operation, since all subsequent data
1490        // pushes will originate from the new seek offset.
1491        if let Some(seek_lock) = seek_lock {
1492            seek_lock.unlock(/* successful seek */ true);
1493        }
1494    }
1495
1496    /// <https://html.spec.whatwg.org/multipage/#eligible-for-autoplay>
1497    fn eligible_for_autoplay(&self) -> bool {
1498        // its can autoplay flag is true;
1499        self.autoplaying.get() &&
1500
1501        // its paused attribute is true;
1502        self.Paused() &&
1503
1504        // it has an autoplay attribute specified;
1505        self.Autoplay() &&
1506
1507        // its node document's active sandboxing flag set does not have the sandboxed automatic
1508        // features browsing context flag set; and
1509        {
1510            let document = self.owner_document();
1511
1512            !document.has_active_sandboxing_flag(
1513                SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1514            )
1515        }
1516
1517        // its node document is allowed to use the "autoplay" feature.
1518        // TODO: Feature policy: https://html.spec.whatwg.org/iframe-embed-object.html#allowed-to-use
1519    }
1520
1521    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
1522    fn resource_fetch_algorithm(&self, resource: Resource) {
1523        if let Err(e) = self.create_media_player(&resource) {
1524            error!("Create media player error {:?}", e);
1525            self.resource_selection_algorithm_failure_steps();
1526            return;
1527        }
1528
1529        // Steps 1-2.
1530        // Unapplicable, the `resource` variable already conveys which mode
1531        // is in use.
1532
1533        // Step 3.
1534        // FIXME(nox): Remove all media-resource-specific text tracks.
1535
1536        // Step 5. Run the appropriate steps from the following list:
1537        match resource {
1538            Resource::Url(url) => {
1539                // Step 5.remote.1. Optionally, run the following substeps. This is the expected
1540                // behavior if the user agent intends to not attempt to fetch the resource until the
1541                // user requests it explicitly (e.g. as a way to implement the preload attribute's
1542                // none keyword).
1543                if self.Preload() == "none" && !self.autoplaying.get() {
1544                    // Step 5.remote.1.1. Set the networkState to NETWORK_IDLE.
1545                    self.network_state.set(NetworkState::Idle);
1546
1547                    // Step 5.remote.1.2. Queue a media element task given the media element to fire
1548                    // an event named suspend at the element.
1549                    self.queue_media_element_task_to_fire_event(atom!("suspend"));
1550
1551                    // Step 5.remote.1.3. Queue a media element task given the media element to set
1552                    // the element's delaying-the-load-event flag to false. This stops delaying the
1553                    // load event.
1554                    let this = Trusted::new(self);
1555                    let generation_id = self.generation_id.get();
1556
1557                    self.owner_global()
1558                        .task_manager()
1559                        .media_element_task_source()
1560                        .queue(task!(queue_delay_load_event: move |cx| {
1561                            let this = this.root();
1562                            if generation_id != this.generation_id.get() {
1563                                return;
1564                            }
1565
1566                            this.delay_load_event(false, cx);
1567                        }));
1568
1569                    // TODO Steps 5.remote.1.4. Wait for the task to be run.
1570                    // FIXME(nox): Somehow we should wait for the task from previous
1571                    // step to be ran before continuing.
1572
1573                    // TODO Steps 5.remote.1.5-5.remote.1.7.
1574                    // FIXME(nox): Wait for an implementation-defined event and
1575                    // then continue with the normal set of steps instead of just
1576                    // returning.
1577                    return;
1578                }
1579
1580                *self.resource_url.borrow_mut() = Some(url);
1581
1582                // Steps 5.remote.2-5.remote.8
1583                self.fetch_request(None, None);
1584            },
1585            Resource::Object => {
1586                if let Some(ref src_object) = *self.src_object.borrow() {
1587                    match src_object {
1588                        SrcObject::Blob(blob) => {
1589                            let blob_url = URL::CreateObjectURL(&self.global(), blob);
1590                            *self.blob_url.borrow_mut() =
1591                                Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1592                            self.fetch_request(None, None);
1593                        },
1594                        SrcObject::MediaStream(stream) => {
1595                            let tracks = &*stream.get_tracks();
1596                            for (pos, track) in tracks.iter().enumerate() {
1597                                if self
1598                                    .player
1599                                    .borrow()
1600                                    .as_ref()
1601                                    .unwrap()
1602                                    .lock()
1603                                    .unwrap()
1604                                    .set_stream(&track.id(), pos == tracks.len() - 1)
1605                                    .is_err()
1606                                {
1607                                    self.resource_selection_algorithm_failure_steps();
1608                                }
1609                            }
1610                        },
1611                    }
1612                }
1613            },
1614        }
1615    }
1616
1617    /// Queues a task to run the [dedicated media source failure steps][steps].
1618    ///
1619    /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps
1620    fn queue_dedicated_media_source_failure_steps(&self) {
1621        let this = Trusted::new(self);
1622        let generation_id = self.generation_id.get();
1623        self.take_pending_play_promises(Err(Error::NotSupported(None)));
1624        self.owner_global()
1625            .task_manager()
1626            .media_element_task_source()
1627            .queue(task!(dedicated_media_source_failure_steps: move |cx| {
1628                let this = this.root();
1629                if generation_id != this.generation_id.get() {
1630                    return;
1631                }
1632
1633                this.fulfill_in_flight_play_promises(cx, |cx| {
1634                    // Step 1. Set the error attribute to the result of creating a MediaError with
1635                    // MEDIA_ERR_SRC_NOT_SUPPORTED.
1636                    this.error.set(Some(&*MediaError::new(
1637                        &this.owner_window(),
1638                        MEDIA_ERR_SRC_NOT_SUPPORTED, CanGc::from_cx(cx))));
1639
1640                    // Step 2. Forget the media element's media-resource-specific tracks.
1641                    this.AudioTracks(cx).clear();
1642                    this.VideoTracks(CanGc::from_cx(cx)).clear();
1643
1644                    // Step 3. Set the element's networkState attribute to the NETWORK_NO_SOURCE
1645                    // value.
1646                    this.network_state.set(NetworkState::NoSource);
1647
1648                    // Step 4. Set the element's show poster flag to true.
1649                    this.show_poster.set(true);
1650
1651                    // Step 5. Fire an event named error at the media element.
1652                    this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1653
1654                    if let Some(ref player) = *this.player.borrow()
1655                        && let Err(error) = player.lock().unwrap().stop() {
1656                            error!("Could not stop player: {error:?}");
1657                        }
1658
1659                    // Step 6. Reject pending play promises with promises and a "NotSupportedError"
1660                    // DOMException.
1661                    // Done after running this closure in `fulfill_in_flight_play_promises`.
1662                });
1663
1664                // Step 7. Set the element's delaying-the-load-event flag to false. This stops
1665                // delaying the load event.
1666                this.delay_load_event(false, cx);
1667            }));
1668    }
1669
1670    fn in_error_state(&self) -> bool {
1671        self.error.get().is_some()
1672    }
1673
1674    /// <https://html.spec.whatwg.org/multipage/#potentially-playing>
1675    fn is_potentially_playing(&self) -> bool {
1676        !self.paused.get() &&
1677            !self.ended_playback(LoopCondition::Included) &&
1678            self.error.get().is_none() &&
1679            !self.is_blocked_media_element()
1680    }
1681
1682    /// <https://html.spec.whatwg.org/multipage/#blocked-media-element>
1683    fn is_blocked_media_element(&self) -> bool {
1684        self.ready_state.get() <= ReadyState::HaveCurrentData ||
1685            self.is_paused_for_user_interaction() ||
1686            self.is_paused_for_in_band_content()
1687    }
1688
1689    /// <https://html.spec.whatwg.org/multipage/#paused-for-user-interaction>
1690    fn is_paused_for_user_interaction(&self) -> bool {
1691        // FIXME: we will likely be able to fill this placeholder once (if) we
1692        //        implement the MediaSession API.
1693        false
1694    }
1695
1696    /// <https://html.spec.whatwg.org/multipage/#paused-for-in-band-content>
1697    fn is_paused_for_in_band_content(&self) -> bool {
1698        // FIXME: we will likely be able to fill this placeholder once (if) we
1699        //        implement https://github.com/servo/servo/issues/22314
1700        false
1701    }
1702
1703    /// <https://html.spec.whatwg.org/multipage/#media-element-load-algorithm>
1704    fn media_element_load_algorithm(&self, cx: &mut JSContext) {
1705        // Reset the flag that signals whether loadeddata was ever fired for
1706        // this invokation of the load algorithm.
1707        self.fired_loadeddata_event.set(false);
1708
1709        // TODO Step 1. Set this element's is currently stalled to false.
1710
1711        // Step 2. Abort any already-running instance of the resource selection algorithm for this
1712        // element.
1713        self.generation_id.set(self.generation_id.get() + 1);
1714
1715        self.load_state.set(LoadState::NotLoaded);
1716        *self.source_children_pointer.borrow_mut() = None;
1717        self.current_source_child.set(None);
1718
1719        // Step 3. Let pending tasks be a list of all tasks from the media element's media element
1720        // event task source in one of the task queues.
1721
1722        // Step 4. For each task in pending tasks that would resolve pending play promises or reject
1723        // pending play promises, immediately resolve or reject those promises in the order the
1724        // corresponding tasks were queued.
1725        while !self.in_flight_play_promises_queue.borrow().is_empty() {
1726            self.fulfill_in_flight_play_promises(cx, |_| ());
1727        }
1728
1729        // Step 5. Remove each task in pending tasks from its task queue.
1730        // Note that each media element's pending event and callback is scheduled with associated
1731        // generation id and will be aborted eventually (from Step 2).
1732
1733        let network_state = self.network_state.get();
1734
1735        // Step 6. If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE,
1736        // queue a media element task given the media element to fire an event named abort at the
1737        // media element.
1738        if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1739            self.queue_media_element_task_to_fire_event(atom!("abort"));
1740        }
1741
1742        // Reset the media player for any previously playing media resource (see Step 11).
1743        self.reset_media_player();
1744
1745        // Step 7. If the media element's networkState is not set to NETWORK_EMPTY, then:
1746        if network_state != NetworkState::Empty {
1747            // Step 7.1. Queue a media element task given the media element to fire an event named
1748            // emptied at the media element.
1749            self.queue_media_element_task_to_fire_event(atom!("emptied"));
1750
1751            // Step 7.2. If a fetching process is in progress for the media element, the user agent
1752            // should stop it.
1753            if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1754                current_fetch_context.cancel(CancelReason::Abort);
1755            }
1756
1757            // TODO Step 7.3. If the media element's assigned media provider object is a MediaSource
1758            // object, then detach it.
1759
1760            // Step 7.4. Forget the media element's media-resource-specific tracks.
1761            self.AudioTracks(cx).clear();
1762            self.VideoTracks(CanGc::from_cx(cx)).clear();
1763
1764            // Step 7.5. If readyState is not set to HAVE_NOTHING, then set it to that state.
1765            if self.ready_state.get() != ReadyState::HaveNothing {
1766                self.change_ready_state(ReadyState::HaveNothing);
1767            }
1768
1769            // Step 7.6. If the paused attribute is false, then:
1770            if !self.Paused() {
1771                // Step 7.6.1. Set the paused attribute to true.
1772                self.paused.set(true);
1773
1774                // Step 7.6.2. Take pending play promises and reject pending play promises with the
1775                // result and an "AbortError" DOMException.
1776                self.take_pending_play_promises(Err(Error::Abort(None)));
1777                self.fulfill_in_flight_play_promises(cx, |_| ());
1778            }
1779
1780            // Step 7.7. If seeking is true, set it to false.
1781            self.seeking.set(false);
1782
1783            self.current_seek_position.set(f64::NAN);
1784
1785            // Step 7.8. Set the current playback position to 0.
1786            // Set the official playback position to 0.
1787            // If this changed the official playback position, then queue a media element task given
1788            // the media element to fire an event named timeupdate at the media element.
1789            self.current_playback_position.set(0.);
1790            if self.official_playback_position.get() != 0. {
1791                self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1792            }
1793            self.official_playback_position.set(0.);
1794
1795            // TODO Step 7.9. Set the timeline offset to Not-a-Number (NaN).
1796
1797            // Step 7.10. Update the duration attribute to Not-a-Number (NaN).
1798            self.duration.set(f64::NAN);
1799        }
1800
1801        // Step 8. Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1802        self.playback_rate.set(self.default_playback_rate.get());
1803
1804        // Step 9. Set the error attribute to null and the can autoplay flag to true.
1805        self.error.set(None);
1806        self.autoplaying.set(true);
1807
1808        // Step 10. Invoke the media element's resource selection algorithm.
1809        self.invoke_resource_selection_algorithm(cx);
1810
1811        // Step 11. Note: Playback of any previously playing media resource for this element stops.
1812    }
1813
1814    /// Queue a media element task given the media element to fire an event at the media element.
1815    /// <https://html.spec.whatwg.org/multipage/#queue-a-media-element-task>
1816    fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1817        let this = Trusted::new(self);
1818        let generation_id = self.generation_id.get();
1819
1820        self.owner_global()
1821            .task_manager()
1822            .media_element_task_source()
1823            .queue(task!(queue_event: move |cx| {
1824                let this = this.root();
1825                if generation_id != this.generation_id.get() {
1826                    return;
1827                }
1828
1829                this.upcast::<EventTarget>().fire_event(cx, name);
1830            }));
1831    }
1832
1833    /// Appends a promise to the list of pending play promises.
1834    fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1835        self.pending_play_promises
1836            .borrow_mut()
1837            .push(promise.clone());
1838    }
1839
1840    /// Takes the pending play promises.
1841    ///
1842    /// The result with which these promises will be fulfilled is passed here
1843    /// and this method returns nothing because we actually just move the
1844    /// current list of pending play promises to the
1845    /// `in_flight_play_promises_queue` field.
1846    ///
1847    /// Each call to this method must be followed by a call to
1848    /// `fulfill_in_flight_play_promises`, to actually fulfill the promises
1849    /// which were taken and moved to the in-flight queue.
1850    fn take_pending_play_promises(&self, result: ErrorResult) {
1851        let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1852        self.in_flight_play_promises_queue
1853            .borrow_mut()
1854            .push_back((pending_play_promises.into(), result));
1855    }
1856
1857    /// Fulfills the next in-flight play promises queue after running a closure.
1858    ///
1859    /// See the comment on `take_pending_play_promises` for why this method
1860    /// does not take a list of promises to fulfill. Callers cannot just pop
1861    /// the front list off of `in_flight_play_promises_queue` and later fulfill
1862    /// the promises because that would mean putting
1863    /// `#[cfg_attr(crown, expect(crown::unrooted_must_root))]` on even more functions, potentially
1864    /// hiding actual safety bugs.
1865    fn fulfill_in_flight_play_promises<F>(&self, cx: &mut JSContext, f: F)
1866    where
1867        F: FnOnce(&mut JSContext),
1868    {
1869        let (promises, result) = self
1870            .in_flight_play_promises_queue
1871            .borrow_mut()
1872            .pop_front()
1873            .expect("there should be at least one list of in flight play promises");
1874        f(cx);
1875        for promise in &*promises {
1876            match result {
1877                Ok(ref value) => promise.resolve_native_with_cx(cx, value),
1878                Err(ref error) => promise.reject_error_with_cx(cx, error.clone()),
1879            }
1880        }
1881    }
1882
1883    pub(crate) fn handle_source_child_insertion(
1884        &self,
1885        source: &HTMLSourceElement,
1886        cx: &mut JSContext,
1887    ) {
1888        // <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-insertion-steps>
1889        // Step 2. If parent is a media element that has no src attribute and whose networkState has
1890        // the value NETWORK_EMPTY, then invoke that media element's resource selection algorithm.
1891        if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1892            return;
1893        }
1894
1895        if self.network_state.get() == NetworkState::Empty {
1896            self.invoke_resource_selection_algorithm(cx);
1897            return;
1898        }
1899
1900        // <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1901        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1902        // list. (This step might wait forever.)
1903        if self.load_state.get() != LoadState::WaitingForSource {
1904            return;
1905        }
1906
1907        self.load_state.set(LoadState::LoadingFromSourceChild);
1908
1909        *self.source_children_pointer.borrow_mut() =
1910            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), true));
1911
1912        // Step 9.children.23. Await a stable state.
1913        let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1914            elem: DomRoot::from_ref(self),
1915            generation_id: self.generation_id.get(),
1916        };
1917
1918        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1919    }
1920
1921    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1922    fn select_next_source_child_after_wait(&self, cx: &mut JSContext) {
1923        // Step 9.children.24. Set the element's delaying-the-load-event flag back to true (this
1924        // delays the load event again, in case it hasn't been fired yet).
1925        self.delay_load_event(true, cx);
1926
1927        // Step 9.children.25. Set the networkState back to NETWORK_LOADING.
1928        self.network_state.set(NetworkState::Loading);
1929
1930        // Step 9.children.26. Jump back to the find next candidate step above.
1931        self.select_next_source_child(cx);
1932    }
1933
1934    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1935    /// => "If the media data cannot be fetched at all, due to network errors..."
1936    /// => "If the media data can be fetched but is found by inspection to be in an unsupported
1937    /// format, or can otherwise not be rendered at all"
1938    fn media_data_processing_failure_steps(&self) {
1939        // Step 1. The user agent should cancel the fetching process.
1940        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1941            current_fetch_context.cancel(CancelReason::Error);
1942        }
1943
1944        // Step 2. Abort this subalgorithm, returning to the resource selection algorithm.
1945        self.resource_selection_algorithm_failure_steps();
1946    }
1947
1948    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1949    /// => "If the connection is interrupted after some media data has been received..."
1950    /// => "If the media data is corrupted"
1951    fn media_data_processing_fatal_steps(&self, error: u16, cx: &mut JSContext) {
1952        *self.source_children_pointer.borrow_mut() = None;
1953        self.current_source_child.set(None);
1954
1955        // Step 1. The user agent should cancel the fetching process.
1956        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1957            current_fetch_context.cancel(CancelReason::Error);
1958        }
1959
1960        // Step 2. Set the error attribute to the result of creating a MediaError with
1961        // MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1962        self.error.set(Some(&*MediaError::new(
1963            &self.owner_window(),
1964            error,
1965            CanGc::from_cx(cx),
1966        )));
1967
1968        // Step 3. Set the element's networkState attribute to the NETWORK_IDLE value.
1969        self.network_state.set(NetworkState::Idle);
1970
1971        // Step 4. Set the element's delaying-the-load-event flag to false. This stops delaying
1972        // the load event.
1973        self.delay_load_event(false, cx);
1974
1975        // Step 5. Fire an event named error at the media element.
1976        self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1977
1978        // Step 6. Abort the overall resource selection algorithm.
1979    }
1980
1981    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
1982    fn seek(&self, time: f64, _approximate_for_speed: bool) {
1983        // Step 1. Set the media element's show poster flag to false.
1984        self.show_poster.set(false);
1985
1986        // Step 2. If the media element's readyState is HAVE_NOTHING, return.
1987        if self.ready_state.get() == ReadyState::HaveNothing {
1988            return;
1989        }
1990
1991        // Step 3. If the element's seeking IDL attribute is true, then another instance of this
1992        // algorithm is already running. Abort that other instance of the algorithm without waiting
1993        // for the step that it is running to complete.
1994        self.current_seek_position.set(f64::NAN);
1995
1996        // Step 4. Set the seeking IDL attribute to true.
1997        self.seeking.set(true);
1998
1999        // Step 5. If the seek was in response to a DOM method call or setting of an IDL attribute,
2000        // then continue the script. The remainder of these steps must be run in parallel.
2001
2002        // Step 6. If the new playback position is later than the end of the media resource, then
2003        // let it be the end of the media resource instead.
2004        let time = f64::min(time, self.Duration());
2005
2006        // Step 7. If the new playback position is less than the earliest possible position, let it
2007        // be that position instead.
2008        let time = f64::max(time, self.earliest_possible_position());
2009
2010        // Step 8. If the (possibly now changed) new playback position is not in one of the ranges
2011        // given in the seekable attribute, then let it be the position in one of the ranges given
2012        // in the seekable attribute that is the nearest to the new playback position. If there are
2013        // no ranges given in the seekable attribute, then set the seeking IDL attribute to false
2014        // and return.
2015        let seekable = self.seekable();
2016
2017        if seekable.is_empty() {
2018            self.seeking.set(false);
2019            return;
2020        }
2021
2022        let mut nearest_seekable_position = 0.0;
2023        let mut in_seekable_range = false;
2024        let mut nearest_seekable_distance = f64::MAX;
2025        for i in 0..seekable.len() {
2026            let start = seekable.start(i).unwrap().abs();
2027            let end = seekable.end(i).unwrap().abs();
2028            if time >= start && time <= end {
2029                nearest_seekable_position = time;
2030                in_seekable_range = true;
2031                break;
2032            } else if time < start {
2033                let distance = start - time;
2034                if distance < nearest_seekable_distance {
2035                    nearest_seekable_distance = distance;
2036                    nearest_seekable_position = start;
2037                }
2038            } else {
2039                let distance = time - end;
2040                if distance < nearest_seekable_distance {
2041                    nearest_seekable_distance = distance;
2042                    nearest_seekable_position = end;
2043                }
2044            }
2045        }
2046        let time = if in_seekable_range {
2047            time
2048        } else {
2049            nearest_seekable_position
2050        };
2051
2052        // Step 9. If the approximate-for-speed flag is set, adjust the new playback position to a
2053        // value that will allow for playback to resume promptly. If new playback position before
2054        // this step is before current playback position, then the adjusted new playback position
2055        // must also be before the current playback position. Similarly, if the new playback
2056        // position before this step is after current playback position, then the adjusted new
2057        // playback position must also be after the current playback position.
2058        // TODO: Note that servo-media with gstreamer does not support inaccurate seeking for now.
2059
2060        // Step 10. Queue a media element task given the media element to fire an event named
2061        // seeking at the element.
2062        self.queue_media_element_task_to_fire_event(atom!("seeking"));
2063
2064        // Step 11. Set the current playback position to the new playback position.
2065        self.current_playback_position.set(time);
2066
2067        if let Some(ref player) = *self.player.borrow() &&
2068            let Err(error) = player.lock().unwrap().seek(time)
2069        {
2070            error!("Could not seek player: {error:?}");
2071        }
2072
2073        self.current_seek_position.set(time);
2074
2075        // Step 12. Wait until the user agent has established whether or not the media data for the
2076        // new playback position is available, and, if it is, until it has decoded enough data to
2077        // play back that position.
2078        // The rest of the steps are handled when the media engine signals a ready state change or
2079        // otherwise satisfies seek completion and signals a position change.
2080    }
2081
2082    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
2083    fn seek_end(&self) {
2084        // Any time the user agent provides a stable state, the official playback position must be
2085        // set to the current playback position.
2086        self.official_playback_position
2087            .set(self.current_playback_position.get());
2088
2089        // Step 14. Set the seeking IDL attribute to false.
2090        self.seeking.set(false);
2091
2092        self.current_seek_position.set(f64::NAN);
2093
2094        // Step 15. Run the time marches on steps.
2095        self.time_marches_on();
2096
2097        // Step 16. Queue a media element task given the media element to fire an event named
2098        // timeupdate at the element.
2099        self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2100
2101        // Step 17. Queue a media element task given the media element to fire an event named seeked
2102        // at the element.
2103        self.queue_media_element_task_to_fire_event(atom!("seeked"));
2104    }
2105
2106    /// <https://html.spec.whatwg.org/multipage/#poster-frame>
2107    pub(crate) fn set_poster_frame(&self, image: Option<Arc<RasterImage>>) {
2108        if pref!(media_testing_enabled) && image.is_some() {
2109            self.queue_media_element_task_to_fire_event(atom!("postershown"));
2110        }
2111
2112        self.video_renderer.lock().unwrap().set_poster_frame(image);
2113
2114        self.upcast::<Node>().dirty(NodeDamage::Other);
2115    }
2116
2117    fn player_id(&self) -> Option<usize> {
2118        self.player
2119            .borrow()
2120            .as_ref()
2121            .map(|player| player.lock().unwrap().get_id())
2122    }
2123
2124    fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
2125        let stream_type = match *resource {
2126            Resource::Object => {
2127                if let Some(ref src_object) = *self.src_object.borrow() {
2128                    match src_object {
2129                        SrcObject::MediaStream(_) => StreamType::Stream,
2130                        _ => StreamType::Seekable,
2131                    }
2132                } else {
2133                    return Err(());
2134                }
2135            },
2136            _ => StreamType::Seekable,
2137        };
2138
2139        let window = self.owner_window();
2140        let (action_sender, action_receiver) = ipc::channel::<PlayerEvent>().unwrap();
2141        let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
2142        {
2143            HTMLMediaElementTypeId::HTMLAudioElement => None,
2144            HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
2145        };
2146
2147        let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
2148
2149        let pipeline_id = window.pipeline_id();
2150        let client_context_id =
2151            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
2152        let player = ServoMedia::get().create_player(
2153            &client_context_id,
2154            stream_type,
2155            action_sender,
2156            video_renderer,
2157            audio_renderer,
2158            Box::new(window.get_player_context()),
2159        );
2160        let player_id = {
2161            let player_guard = player.lock().unwrap();
2162
2163            if let Err(error) = player_guard.set_mute(self.muted.get()) {
2164                warn!("Could not set mute state: {error:?}");
2165            }
2166
2167            player_guard.get_id()
2168        };
2169
2170        *self.player.borrow_mut() = Some(player);
2171
2172        let event_handler = Arc::new(Mutex::new(HTMLMediaElementEventHandler::new(self)));
2173        let weak_event_handler = Arc::downgrade(&event_handler);
2174        *self.event_handler.borrow_mut() = Some(event_handler);
2175
2176        let task_source = self
2177            .owner_global()
2178            .task_manager()
2179            .media_element_task_source()
2180            .to_sendable();
2181        ROUTER.add_typed_route(
2182            action_receiver,
2183            Box::new(move |message| {
2184                let event = message.unwrap();
2185                let weak_event_handler = weak_event_handler.clone();
2186
2187                task_source.queue(task!(handle_player_event: move |cx| {
2188                    trace!("HTMLMediaElement event: {event:?}");
2189
2190                    let Some(event_handler) = weak_event_handler.upgrade() else {
2191                        return;
2192                    };
2193
2194                    event_handler.lock().unwrap().handle_player_event(player_id, event, cx);
2195                }));
2196            }),
2197        );
2198
2199        let task_source = self
2200            .owner_global()
2201            .task_manager()
2202            .media_element_task_source()
2203            .to_sendable();
2204        let weak_video_renderer = Arc::downgrade(&self.video_renderer);
2205
2206        self.video_renderer
2207            .lock()
2208            .unwrap()
2209            .setup(player_id, task_source, weak_video_renderer);
2210
2211        Ok(())
2212    }
2213
2214    fn reset_media_player(&self) {
2215        if self.player.borrow().is_none() {
2216            return;
2217        }
2218
2219        if let Some(ref player) = *self.player.borrow() &&
2220            let Err(error) = player.lock().unwrap().stop()
2221        {
2222            error!("Could not stop player: {error:?}");
2223        }
2224
2225        *self.player.borrow_mut() = None;
2226        self.video_renderer.lock().unwrap().reset();
2227        *self.event_handler.borrow_mut() = None;
2228
2229        if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2230            video_element.set_natural_dimensions(None, None);
2231        }
2232    }
2233
2234    pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
2235        if let Some(ref player) = *self.player.borrow() &&
2236            let Err(error) = player.lock().unwrap().set_audio_track(idx as i32, enabled)
2237        {
2238            warn!("Could not set audio track {error:?}");
2239        }
2240    }
2241
2242    pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
2243        if let Some(ref player) = *self.player.borrow() &&
2244            let Err(error) = player.lock().unwrap().set_video_track(idx as i32, enabled)
2245        {
2246            warn!("Could not set video track: {error:?}");
2247        }
2248    }
2249
2250    /// <https://html.spec.whatwg.org/multipage/#direction-of-playback>
2251    fn direction_of_playback(&self) -> PlaybackDirection {
2252        // If the element's playbackRate is positive or zero, then the direction of playback is
2253        // forwards. Otherwise, it is backwards.
2254        if self.playback_rate.get() >= 0. {
2255            PlaybackDirection::Forwards
2256        } else {
2257            PlaybackDirection::Backwards
2258        }
2259    }
2260
2261    /// <https://html.spec.whatwg.org/multipage/#ended-playback>
2262    fn ended_playback(&self, loop_condition: LoopCondition) -> bool {
2263        // A media element is said to have ended playback when:
2264
2265        // The element's readyState attribute is HAVE_METADATA or greater, and
2266        if self.ready_state.get() < ReadyState::HaveMetadata {
2267            return false;
2268        }
2269
2270        let playback_position = self.current_playback_position.get();
2271
2272        match self.direction_of_playback() {
2273            // Either: The current playback position is the end of the media resource, and the
2274            // direction of playback is forwards, and the media element does not have a loop
2275            // attribute specified.
2276            PlaybackDirection::Forwards => {
2277                playback_position >= self.Duration() &&
2278                    (loop_condition == LoopCondition::Ignored || !self.Loop())
2279            },
2280            // Or: The current playback position is the earliest possible position, and the
2281            // direction of playback is backwards.
2282            PlaybackDirection::Backwards => playback_position <= self.earliest_possible_position(),
2283        }
2284    }
2285
2286    /// <https://html.spec.whatwg.org/multipage/#reaches-the-end>
2287    fn end_of_playback_in_forwards_direction(&self) {
2288        // When the current playback position reaches the end of the media resource when the
2289        // direction of playback is forwards, then the user agent must follow these steps:
2290
2291        // Step 1. If the media element has a loop attribute specified, then seek to the earliest
2292        // posible position of the media resource and return.
2293        if self.Loop() {
2294            self.seek(
2295                self.earliest_possible_position(),
2296                /* approximate_for_speed */ false,
2297            );
2298            return;
2299        }
2300
2301        // Step 2. As defined above, the ended IDL attribute starts returning true once the event
2302        // loop returns to step 1.
2303
2304        // Step 3. Queue a media element task given the media element and the following steps:
2305        let this = Trusted::new(self);
2306        let generation_id = self.generation_id.get();
2307
2308        self.owner_global()
2309            .task_manager()
2310            .media_element_task_source()
2311            .queue(task!(reaches_the_end_steps: move |cx| {
2312                let this = this.root();
2313                if generation_id != this.generation_id.get() {
2314                    return;
2315                }
2316
2317                // Step 3.1. Fire an event named timeupdate at the media element.
2318                this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
2319
2320                // Step 3.2. If the media element has ended playback, the direction of playback is
2321                // forwards, and paused is false, then:
2322                if this.ended_playback(LoopCondition::Included) &&
2323                    this.direction_of_playback() == PlaybackDirection::Forwards &&
2324                    !this.Paused() {
2325                    // Step 3.2.1. Set the paused attribute to true.
2326                    this.paused.set(true);
2327
2328                    // Step 3.2.2. Fire an event named pause at the media element.
2329                    this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
2330
2331                    // Step 3.2.3. Take pending play promises and reject pending play promises with
2332                    // the result and an "AbortError" DOMException.
2333                    this.take_pending_play_promises(Err(Error::Abort(None)));
2334                    this.fulfill_in_flight_play_promises(cx, |_| ());
2335                }
2336
2337                // Step 3.3. Fire an event named ended at the media element.
2338                this.upcast::<EventTarget>().fire_event(cx, atom!("ended"));
2339            }));
2340
2341        // <https://html.spec.whatwg.org/multipage/#dom-media-have_current_data>
2342        self.change_ready_state(ReadyState::HaveCurrentData);
2343    }
2344
2345    /// <https://html.spec.whatwg.org/multipage/#reaches-the-end>
2346    fn end_of_playback_in_backwards_direction(&self) {
2347        // When the current playback position reaches the earliest possible position of the media
2348        // resource when the direction of playback is backwards, then the user agent must only queue
2349        // a media element task given the media element to fire an event named timeupdate at the
2350        // element.
2351        if self.current_playback_position.get() <= self.earliest_possible_position() {
2352            self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2353        }
2354    }
2355
2356    fn playback_end(&self) {
2357        // Abort the following steps of the end of playback if seeking is in progress.
2358        if self.seeking.get() {
2359            return;
2360        }
2361
2362        match self.direction_of_playback() {
2363            PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(),
2364            PlaybackDirection::Backwards => self.end_of_playback_in_backwards_direction(),
2365        }
2366    }
2367
2368    fn playback_error(&self, error: &str, cx: &mut JSContext) {
2369        error!("Player error: {:?}", error);
2370
2371        // If we have already flagged an error condition while processing
2372        // the network response, we should silently skip any observable
2373        // errors originating while decoding the erroneous response.
2374        if self.in_error_state() {
2375            return;
2376        }
2377
2378        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
2379        if self.ready_state.get() == ReadyState::HaveNothing {
2380            // => "If the media data can be fetched but is found by inspection to be in an
2381            // unsupported format, or can otherwise not be rendered at all"
2382            self.media_data_processing_failure_steps();
2383        } else {
2384            // => "If the media data is corrupted"
2385            self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, cx);
2386        }
2387    }
2388
2389    fn playback_metadata_updated(
2390        &self,
2391        cx: &mut JSContext,
2392        metadata: &servo_media::player::metadata::Metadata,
2393    ) {
2394        // The following steps should be run once on the initial `metadata` signal from the media
2395        // engine.
2396        if self.ready_state.get() != ReadyState::HaveNothing {
2397            return;
2398        }
2399
2400        // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list
2401        // => "If the media resource is found to have an audio track"
2402        for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2403            let audio_track_list = self.AudioTracks(cx);
2404
2405            // Step 1. Create an AudioTrack object to represent the audio track.
2406            let kind = match i {
2407                0 => DOMString::from("main"),
2408                _ => DOMString::new(),
2409            };
2410
2411            let audio_track = AudioTrack::new(
2412                cx,
2413                self.global().as_window(),
2414                DOMString::new(),
2415                kind,
2416                DOMString::new(),
2417                DOMString::new(),
2418                Some(&*audio_track_list),
2419            );
2420
2421            // Steps 2. Update the media element's audioTracks attribute's AudioTrackList object
2422            // with the new AudioTrack object.
2423            audio_track_list.add(&audio_track);
2424
2425            // Step 3. Let enable be unknown.
2426            // Step 4. If either the media resource or the URL of the current media resource
2427            // indicate a particular set of audio tracks to enable, or if the user agent has
2428            // information that would facilitate the selection of specific audio tracks to
2429            // improve the user's experience, then: if this audio track is one of the ones to
2430            // enable, then set enable to true, otherwise, set enable to false.
2431            if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2432                let fragment = MediaFragmentParser::from(servo_url);
2433                if let Some(id) = fragment.id() &&
2434                    audio_track.id() == id
2435                {
2436                    audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2437                }
2438
2439                if fragment.tracks().contains(&audio_track.kind().into()) {
2440                    audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2441                }
2442            }
2443
2444            // Step 5. If enable is still unknown, then, if the media element does not yet have an
2445            // enabled audio track, then set enable to true, otherwise, set enable to false.
2446            // Step 6. If enable is true, then enable this audio track, otherwise, do not enable
2447            // this audio track.
2448            if audio_track_list.enabled_index().is_none() {
2449                audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2450            }
2451
2452            // Step 7. Fire an event named addtrack at this AudioTrackList object, using TrackEvent,
2453            // with the track attribute initialized to the new AudioTrack object.
2454            let event = TrackEvent::new(
2455                self.global().as_window(),
2456                atom!("addtrack"),
2457                false,
2458                false,
2459                &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2460                CanGc::from_cx(cx),
2461            );
2462
2463            event
2464                .upcast::<Event>()
2465                .fire(cx, audio_track_list.upcast::<EventTarget>());
2466        }
2467
2468        // => "If the media resource is found to have a video track"
2469        for (i, _track) in metadata.video_tracks.iter().enumerate() {
2470            let video_track_list = self.VideoTracks(CanGc::from_cx(cx));
2471
2472            // Step 1. Create a VideoTrack object to represent the video track.
2473            let kind = match i {
2474                0 => DOMString::from("main"),
2475                _ => DOMString::new(),
2476            };
2477
2478            let video_track = VideoTrack::new(
2479                self.global().as_window(),
2480                DOMString::new(),
2481                kind,
2482                DOMString::new(),
2483                DOMString::new(),
2484                Some(&*video_track_list),
2485                CanGc::from_cx(cx),
2486            );
2487
2488            // Steps 2. Update the media element's videoTracks attribute's VideoTrackList object
2489            // with the new VideoTrack object.
2490            video_track_list.add(&video_track);
2491
2492            // Step 3. Let enable be unknown.
2493            // Step 4. If either the media resource or the URL of the current media resource
2494            // indicate a particular set of video tracks to enable, or if the user agent has
2495            // information that would facilitate the selection of specific video tracks to
2496            // improve the user's experience, then: if this video track is the first such video
2497            // track, then set enable to true, otherwise, set enable to false.
2498            if let Some(track) = video_track_list.item(0) &&
2499                let Some(servo_url) = self.resource_url.borrow().as_ref()
2500            {
2501                let fragment = MediaFragmentParser::from(servo_url);
2502                if let Some(id) = fragment.id() {
2503                    if track.id() == id {
2504                        video_track_list.set_selected(0, true);
2505                    }
2506                } else if fragment.tracks().contains(&track.kind().into()) {
2507                    video_track_list.set_selected(0, true);
2508                }
2509            }
2510
2511            // Step 5. If enable is still unknown, then, if the media element does not yet have a
2512            // selected video track, then set enable to true, otherwise, set enable to false.
2513            // Step 6. If enable is true, then select this track and unselect any previously
2514            // selected video tracks, otherwise, do not select this video track. If other tracks are
2515            // unselected, then a change event will be fired.
2516            if video_track_list.selected_index().is_none() {
2517                video_track_list.set_selected(video_track_list.len() - 1, true);
2518            }
2519
2520            // Step 7. Fire an event named addtrack at this VideoTrackList object, using TrackEvent,
2521            // with the track attribute initialized to the new VideoTrack object.
2522            let event = TrackEvent::new(
2523                self.global().as_window(),
2524                atom!("addtrack"),
2525                false,
2526                false,
2527                &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2528                CanGc::from_cx(cx),
2529            );
2530
2531            event
2532                .upcast::<Event>()
2533                .fire(cx, video_track_list.upcast::<EventTarget>());
2534        }
2535
2536        // => "Once enough of the media data has been fetched to determine the duration..."
2537
2538        // TODO Step 1. Establish the media timeline for the purposes of the current playback
2539        // position and the earliest possible position, based on the media data.
2540
2541        // TODO Step 2. Update the timeline offset to the date and time that corresponds to the zero
2542        // time in the media timeline established in the previous step, if any. If no explicit time
2543        // and date is given by the media resource, the timeline offset must be set to Not-a-Number
2544        // (NaN).
2545
2546        // Step 3. Set the current playback position and the official playback position to the
2547        // earliest possible position.
2548        let earliest_possible_position = self.earliest_possible_position();
2549        self.current_playback_position
2550            .set(earliest_possible_position);
2551        self.official_playback_position
2552            .set(earliest_possible_position);
2553
2554        // Step 4. Update the duration attribute with the time of the last frame of the resource, if
2555        // known, on the media timeline established above. If it is not known (e.g. a stream that is
2556        // in principle infinite), update the duration attribute to the value positive Infinity.
2557        // Note: The user agent will queue a media element task given the media element to fire an
2558        // event named durationchange at the element at this point.
2559        self.duration.set(
2560            metadata
2561                .duration
2562                .map_or(f64::INFINITY, |duration| duration.as_secs_f64()),
2563        );
2564        self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2565
2566        // Step 5. For video elements, set the videoWidth and videoHeight attributes, and queue a
2567        // media element task given the media element to fire an event named resize at the media
2568        // element.
2569        if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2570            video_element.set_natural_dimensions(Some(metadata.width), Some(metadata.height));
2571            self.queue_media_element_task_to_fire_event(atom!("resize"));
2572        }
2573
2574        // Step 6. Set the readyState attribute to HAVE_METADATA.
2575        self.change_ready_state(ReadyState::HaveMetadata);
2576
2577        // Step 7. Let jumped be false.
2578        let mut jumped = false;
2579
2580        // Step 8. If the media element's default playback start position is greater than zero, then
2581        // seek to that time, and let jumped be true.
2582        if self.default_playback_start_position.get() > 0. {
2583            self.seek(
2584                self.default_playback_start_position.get(),
2585                /* approximate_for_speed */ false,
2586            );
2587            jumped = true;
2588        }
2589
2590        // Step 9. Set the media element's default playback start position to zero.
2591        self.default_playback_start_position.set(0.);
2592
2593        // Step 10. Let the initial playback position be 0.
2594        // Step 11. If either the media resource or the URL of the current media resource indicate a
2595        // particular start time, then set the initial playback position to that time and, if jumped
2596        // is still false, seek to that time.
2597        if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2598            let fragment = MediaFragmentParser::from(servo_url);
2599            if let Some(initial_playback_position) = fragment.start() &&
2600                initial_playback_position > 0. &&
2601                initial_playback_position < self.duration.get() &&
2602                !jumped
2603            {
2604                self.seek(
2605                    initial_playback_position,
2606                    /* approximate_for_speed */ false,
2607                )
2608            }
2609        }
2610
2611        // Step 12. If there is no enabled audio track, then enable an audio track. This will cause
2612        // a change event to be fired.
2613        // Step 13. If there is no selected video track, then select a video track. This will cause
2614        // a change event to be fired.
2615        // Note that these steps are already handled by the earlier media track processing.
2616
2617        let global = self.global();
2618        let window = global.as_window();
2619
2620        // Update the media session metadata title with the obtained metadata.
2621        window.Navigator().MediaSession().update_title(
2622            metadata
2623                .title
2624                .clone()
2625                .unwrap_or(window.get_url().into_string()),
2626        );
2627    }
2628
2629    fn playback_duration_changed(&self, duration: Option<Duration>) {
2630        let duration = duration.map_or(f64::INFINITY, |duration| duration.as_secs_f64());
2631
2632        if self.duration.get() == duration {
2633            return;
2634        }
2635
2636        self.duration.set(duration);
2637
2638        // When the length of the media resource changes to a known value (e.g. from being unknown
2639        // to known, or from a previously established length to a new length), the user agent must
2640        // queue a media element task given the media element to fire an event named durationchange
2641        // at the media element.
2642        // <https://html.spec.whatwg.org/multipage/#offsets-into-the-media-resource:media-resource-22>
2643        self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2644
2645        // If the duration is changed such that the current playback position ends up being greater
2646        // than the time of the end of the media resource, then the user agent must also seek to the
2647        // time of the end of the media resource.
2648        if self.current_playback_position.get() > duration {
2649            self.seek(duration, /* approximate_for_speed */ false);
2650        }
2651    }
2652
2653    fn playback_video_frame_updated(&self) {
2654        let Some(video_element) = self.downcast::<HTMLVideoElement>() else {
2655            return;
2656        };
2657
2658        // Whenever the natural width or natural height of the video changes (including, for
2659        // example, because the selected video track was changed), if the element's readyState
2660        // attribute is not HAVE_NOTHING, the user agent must queue a media element task given
2661        // the media element to fire an event named resize at the media element.
2662        // <https://html.spec.whatwg.org/multipage/#concept-video-intrinsic-width>
2663
2664        // The event for the prerolled frame from media engine could reached us before the media
2665        // element HAVE_METADATA ready state so subsequent steps will be cancelled.
2666        if self.ready_state.get() == ReadyState::HaveNothing {
2667            return;
2668        }
2669
2670        if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2671            if video_element
2672                .set_natural_dimensions(Some(frame.width as u32), Some(frame.height as u32))
2673            {
2674                self.queue_media_element_task_to_fire_event(atom!("resize"));
2675            } else {
2676                // If the natural dimensions have not been changed, the node should be marked as
2677                // damaged to force a repaint with the new frame contents.
2678                self.upcast::<Node>().dirty(NodeDamage::Other);
2679            }
2680        }
2681    }
2682
2683    fn playback_need_data(&self) {
2684        // The media engine signals that the source needs more data. If we already have a valid
2685        // fetch request, we do nothing. Otherwise, if we have no request and the previous request
2686        // was cancelled because we got an EnoughData event, we restart fetching where we left.
2687        if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() &&
2688            let Some(reason) = current_fetch_context.cancel_reason()
2689        {
2690            // XXX(ferjm) Ideally we should just create a fetch request from
2691            // where we left. But keeping track of the exact next byte that the
2692            // media backend expects is not the easiest task, so I'm simply
2693            // seeking to the current playback position for now which will create
2694            // a new fetch request for the last rendered frame.
2695            if *reason == CancelReason::Backoff {
2696                self.seek(
2697                    self.current_playback_position.get(),
2698                    /* approximate_for_speed */ false,
2699                );
2700            }
2701            return;
2702        }
2703
2704        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2705            let Err(e) = {
2706                let mut data_source = current_fetch_context.data_source().borrow_mut();
2707                data_source.set_locked(false);
2708                data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2709            }
2710        {
2711            // If we are pushing too much data and we know that we can
2712            // restart the download later from where we left, we cancel
2713            // the current request. Otherwise, we continue the request
2714            // assuming that we may drop some frames.
2715            if e == PlayerError::EnoughData {
2716                current_fetch_context.cancel(CancelReason::Backoff);
2717            }
2718        }
2719    }
2720
2721    fn playback_enough_data(&self) {
2722        // The media engine signals that the source has enough data and asks us to stop pushing bytes
2723        // to avoid excessive buffer queueing, so we cancel the ongoing fetch request if we are able
2724        // to restart it from where we left. Otherwise, we continue the current fetch request,
2725        // assuming that some frames will be dropped.
2726        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2727            current_fetch_context.is_seekable()
2728        {
2729            current_fetch_context.cancel(CancelReason::Backoff);
2730        }
2731    }
2732
2733    fn playback_position_changed(&self, position: f64) {
2734        // Abort the following steps of the current time update if seeking is in progress.
2735        if self.seeking.get() {
2736            return;
2737        }
2738
2739        let _ = self
2740            .played
2741            .borrow_mut()
2742            .add(self.current_playback_position.get(), position);
2743        self.current_playback_position.set(position);
2744        self.official_playback_position.set(position);
2745        self.time_marches_on();
2746
2747        let media_position_state =
2748            MediaPositionState::new(self.duration.get(), self.playback_rate.get(), position);
2749        debug!(
2750            "Sending media session event set position state {:?}",
2751            media_position_state
2752        );
2753        self.send_media_session_event(MediaSessionEvent::SetPositionState(media_position_state));
2754    }
2755
2756    fn playback_seek_done(&self, position: f64) {
2757        // If the seek was initiated by script or by the user agent itself continue with the
2758        // following steps, otherwise abort.
2759        let delta = (position - self.current_seek_position.get()).abs();
2760        if !self.seeking.get() || delta > SEEK_POSITION_THRESHOLD {
2761            return;
2762        }
2763
2764        // <https://html.spec.whatwg.org/multipage/#dom-media-seek>
2765        // Step 13. Await a stable state.
2766        let task = MediaElementMicrotask::Seeked {
2767            elem: DomRoot::from_ref(self),
2768            generation_id: self.generation_id.get(),
2769        };
2770
2771        ScriptThread::await_stable_state(Microtask::MediaElement(task));
2772    }
2773
2774    fn playback_state_changed(&self, state: &PlaybackState) {
2775        let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2776        match *state {
2777            PlaybackState::Paused => {
2778                media_session_playback_state = MediaSessionPlaybackState::Paused;
2779                if self.ready_state.get() == ReadyState::HaveMetadata {
2780                    self.change_ready_state(ReadyState::HaveEnoughData);
2781                }
2782            },
2783            PlaybackState::Playing => {
2784                media_session_playback_state = MediaSessionPlaybackState::Playing;
2785                if self.ready_state.get() == ReadyState::HaveMetadata {
2786                    self.change_ready_state(ReadyState::HaveEnoughData);
2787                }
2788            },
2789            PlaybackState::Buffering => {
2790                // Do not send the media session playback state change event
2791                // in this case as a None_ state is expected to clean up the
2792                // session.
2793                return;
2794            },
2795            _ => {},
2796        };
2797        debug!(
2798            "Sending media session event playback state changed to {:?}",
2799            media_session_playback_state
2800        );
2801        self.send_media_session_event(MediaSessionEvent::PlaybackStateChange(
2802            media_session_playback_state,
2803        ));
2804    }
2805
2806    fn seekable(&self) -> TimeRangesContainer {
2807        let mut seekable = TimeRangesContainer::default();
2808        if let Some(ref player) = *self.player.borrow() {
2809            let ranges = player.lock().unwrap().seekable();
2810            for range in ranges {
2811                let _ = seekable.add(range.start, range.end);
2812            }
2813        }
2814        seekable
2815    }
2816
2817    /// <https://html.spec.whatwg.org/multipage/#earliest-possible-position>
2818    fn earliest_possible_position(&self) -> f64 {
2819        self.seekable()
2820            .start(0)
2821            .unwrap_or_else(|_| self.current_playback_position.get())
2822    }
2823
2824    fn render_controls(&self, cx: &mut JSContext) {
2825        if self.upcast::<Element>().is_shadow_host() {
2826            // Bail out if we are already showing the controls.
2827            return;
2828        }
2829
2830        // FIXME(stevennovaryo): Recheck styling of media element to avoid
2831        //                       reparsing styles.
2832        let shadow_root = self.upcast::<Element>().attach_ua_shadow_root(cx, false);
2833        let document = self.owner_document();
2834        let script = Element::create(
2835            cx,
2836            QualName::new(None, ns!(html), local_name!("script")),
2837            None,
2838            &document,
2839            ElementCreator::ScriptCreated,
2840            CustomElementCreationMode::Asynchronous,
2841            None,
2842        );
2843        // This is our hacky way to temporarily workaround the lack of a privileged
2844        // JS context.
2845        // The media controls UI accesses the document.servoGetMediaControls(id) API
2846        // to get an instance to the media controls ShadowRoot.
2847        // `id` needs to match the internally generated UUID assigned to a media element.
2848        let id = Uuid::new_v4().to_string();
2849        document.register_media_controls(&id, &shadow_root);
2850        let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2851        *self.media_controls_id.borrow_mut() = Some(id);
2852        script
2853            .upcast::<Node>()
2854            .set_text_content_for_element(cx, Some(DOMString::from(media_controls_script)));
2855        if let Err(e) = shadow_root
2856            .upcast::<Node>()
2857            .AppendChild(cx, script.upcast::<Node>())
2858        {
2859            warn!("Could not render media controls {:?}", e);
2860            return;
2861        }
2862
2863        let style = Element::create(
2864            cx,
2865            QualName::new(None, ns!(html), local_name!("style")),
2866            None,
2867            &document,
2868            ElementCreator::ScriptCreated,
2869            CustomElementCreationMode::Asynchronous,
2870            None,
2871        );
2872
2873        style
2874            .upcast::<Node>()
2875            .set_text_content_for_element(cx, Some(DOMString::from(MEDIA_CONTROL_CSS)));
2876
2877        if let Err(e) = shadow_root
2878            .upcast::<Node>()
2879            .AppendChild(cx, style.upcast::<Node>())
2880        {
2881            warn!("Could not render media controls {:?}", e);
2882        }
2883
2884        self.upcast::<Node>().dirty(NodeDamage::Other);
2885    }
2886
2887    fn remove_controls(&self) {
2888        if let Some(id) = self.media_controls_id.borrow_mut().take() {
2889            self.owner_document().unregister_media_controls(&id);
2890        }
2891    }
2892
2893    /// Gets the video frame at the current playback position.
2894    pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2895        self.video_renderer
2896            .lock()
2897            .unwrap()
2898            .current_frame_holder
2899            .as_ref()
2900            .map(|holder| holder.get_frame())
2901    }
2902
2903    /// Gets the current frame of the video element to present, if any.
2904    /// <https://html.spec.whatwg.org/multipage/#the-video-element:the-video-element-7>
2905    pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2906        let (current_frame, poster_frame) = {
2907            let renderer = self.video_renderer.lock().unwrap();
2908            (renderer.current_frame, renderer.poster_frame)
2909        };
2910
2911        // If the show poster flag is set (or there is no current video frame to
2912        // present) AND there is a poster frame, present that.
2913        if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2914            return poster_frame;
2915        }
2916
2917        current_frame
2918    }
2919
2920    /// By default the audio is rendered through the audio sink automatically
2921    /// selected by the servo-media Player instance. However, in some cases, like
2922    /// the WebAudio MediaElementAudioSourceNode, we need to set a custom audio
2923    /// renderer.
2924    pub(crate) fn set_audio_renderer(
2925        &self,
2926        audio_renderer: Option<Arc<Mutex<dyn AudioRenderer>>>,
2927        cx: &mut JSContext,
2928    ) {
2929        *self.audio_renderer.borrow_mut() = audio_renderer;
2930
2931        let had_player = {
2932            if let Some(ref player) = *self.player.borrow() {
2933                if let Err(error) = player.lock().unwrap().stop() {
2934                    error!("Could not stop player: {error:?}");
2935                }
2936                true
2937            } else {
2938                false
2939            }
2940        };
2941
2942        if had_player {
2943            self.media_element_load_algorithm(cx);
2944        }
2945    }
2946
2947    fn send_media_session_event(&self, event: MediaSessionEvent) {
2948        let global = self.global();
2949        let media_session = global.as_window().Navigator().MediaSession();
2950
2951        media_session.register_media_instance(self);
2952
2953        media_session.send_event(event);
2954    }
2955
2956    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
2957    pub(crate) fn origin_is_clean(&self) -> bool {
2958        // Step 5.local (media provider object).
2959        if self.src_object.borrow().is_some() {
2960            // The resource described by the current media resource, if any,
2961            // contains the media data. It is CORS-same-origin.
2962            return true;
2963        }
2964
2965        // Step 5.remote (URL record).
2966        if self.resource_url.borrow().is_some() {
2967            // Update the media data with the contents
2968            // of response's unsafe response obtained in this fashion.
2969            // Response can be CORS-same-origin or CORS-cross-origin;
2970            if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2971                return current_fetch_context.origin_is_clean();
2972            }
2973        }
2974
2975        true
2976    }
2977}
2978
2979impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
2980    /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
2981    fn NetworkState(&self) -> u16 {
2982        self.network_state.get() as u16
2983    }
2984
2985    /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
2986    fn ReadyState(&self) -> u16 {
2987        self.ready_state.get() as u16
2988    }
2989
2990    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2991    make_bool_getter!(Autoplay, "autoplay");
2992    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2993    make_bool_setter!(SetAutoplay, "autoplay");
2994
2995    // https://html.spec.whatwg.org/multipage/#attr-media-loop
2996    make_bool_getter!(Loop, "loop");
2997    // https://html.spec.whatwg.org/multipage/#attr-media-loop
2998    make_bool_setter!(SetLoop, "loop");
2999
3000    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
3001    make_bool_getter!(DefaultMuted, "muted");
3002    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
3003    make_bool_setter!(SetDefaultMuted, "muted");
3004
3005    // https://html.spec.whatwg.org/multipage/#dom-media-controls
3006    make_bool_getter!(Controls, "controls");
3007    // https://html.spec.whatwg.org/multipage/#dom-media-controls
3008    make_bool_setter!(SetControls, "controls");
3009
3010    // https://html.spec.whatwg.org/multipage/#dom-media-src
3011    make_url_getter!(Src, "src");
3012
3013    // https://html.spec.whatwg.org/multipage/#dom-media-src
3014    make_url_setter!(SetSrc, "src");
3015
3016    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
3017    fn GetCrossOrigin(&self) -> Option<DOMString> {
3018        reflect_cross_origin_attribute(self.upcast::<Element>())
3019    }
3020    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
3021    fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
3022        set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
3023    }
3024
3025    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3026    fn Muted(&self) -> bool {
3027        self.muted.get()
3028    }
3029
3030    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3031    fn SetMuted(&self, _cx: &mut JSContext, value: bool) {
3032        if self.muted.get() == value {
3033            return;
3034        }
3035
3036        self.muted.set(value);
3037
3038        if let Some(ref player) = *self.player.borrow() &&
3039            let Err(error) = player.lock().unwrap().set_mute(value)
3040        {
3041            warn!("Could not set mute state: {error:?}");
3042        }
3043
3044        // The user agent must queue a media element task given the media element to fire an event
3045        // named volumechange at the media element.
3046        self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3047
3048        // Then, if the media element is not allowed to play, the user agent must run the internal
3049        // pause steps for the media element.
3050        if !self.is_allowed_to_play() {
3051            self.internal_pause_steps();
3052        }
3053    }
3054
3055    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
3056    fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
3057        (*self.src_object.borrow())
3058            .as_ref()
3059            .map(|src_object| match src_object {
3060                SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
3061                SrcObject::MediaStream(stream) => {
3062                    MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
3063                },
3064            })
3065    }
3066
3067    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
3068    fn SetSrcObject(&self, cx: &mut JSContext, value: Option<MediaStreamOrBlob>) {
3069        *self.src_object.borrow_mut() = value.map(|value| value.into());
3070        self.media_element_load_algorithm(cx);
3071    }
3072
3073    // https://html.spec.whatwg.org/multipage/#attr-media-preload
3074    // Missing/Invalid values are user-agent defined.
3075    make_enumerated_getter!(
3076        Preload,
3077        "preload",
3078        "none" | "metadata" | "auto",
3079        missing => "auto",
3080        invalid => "auto"
3081    );
3082
3083    // https://html.spec.whatwg.org/multipage/#attr-media-preload
3084    make_setter!(SetPreload, "preload");
3085
3086    /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc>
3087    fn CurrentSrc(&self) -> USVString {
3088        USVString(self.current_src.borrow().clone())
3089    }
3090
3091    /// <https://html.spec.whatwg.org/multipage/#dom-media-load>
3092    fn Load(&self, cx: &mut JSContext) {
3093        self.media_element_load_algorithm(cx);
3094    }
3095
3096    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype>
3097    fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
3098        match ServoMedia::get().can_play_type(&type_.str()) {
3099            SupportsMediaType::No => CanPlayTypeResult::_empty,
3100            SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
3101            SupportsMediaType::Probably => CanPlayTypeResult::Probably,
3102        }
3103    }
3104
3105    /// <https://html.spec.whatwg.org/multipage/#dom-media-error>
3106    fn GetError(&self) -> Option<DomRoot<MediaError>> {
3107        self.error.get()
3108    }
3109
3110    /// <https://html.spec.whatwg.org/multipage/#dom-media-play>
3111    fn Play(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
3112        let promise = Promise::new_in_realm(cx);
3113
3114        // TODO Step 1. If the media element is not allowed to play, then return a promise rejected
3115        // with a "NotAllowedError" DOMException.
3116
3117        // Step 2. If the media element's error attribute is not null and its code is
3118        // MEDIA_ERR_SRC_NOT_SUPPORTED, then return a promise rejected with a "NotSupportedError"
3119        // DOMException.
3120        if self
3121            .error
3122            .get()
3123            .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
3124        {
3125            promise.reject_error_with_cx(cx, Error::NotSupported(None));
3126            return promise;
3127        }
3128
3129        // Step 3. Let promise be a new promise and append promise to the list of pending play
3130        // promises.
3131        self.push_pending_play_promise(&promise);
3132
3133        // Step 4. Run the internal play steps for the media element.
3134        self.internal_play_steps(cx);
3135
3136        // Step 5. Return promise.
3137        promise
3138    }
3139
3140    /// <https://html.spec.whatwg.org/multipage/#dom-media-pause>
3141    fn Pause(&self, cx: &mut JSContext) {
3142        // Step 1. If the media element's networkState attribute has the value NETWORK_EMPTY, invoke
3143        // the media element's resource selection algorithm.
3144        if self.network_state.get() == NetworkState::Empty {
3145            self.invoke_resource_selection_algorithm(cx);
3146        }
3147
3148        // Step 2. Run the internal pause steps for the media element.
3149        self.internal_pause_steps();
3150    }
3151
3152    /// <https://html.spec.whatwg.org/multipage/#dom-media-paused>
3153    fn Paused(&self) -> bool {
3154        self.paused.get()
3155    }
3156
3157    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
3158    fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
3159        Ok(Finite::wrap(self.default_playback_rate.get()))
3160    }
3161
3162    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
3163    fn SetDefaultPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3164        // If the given value is not supported by the user agent, then throw a "NotSupportedError"
3165        // DOMException.
3166        let min_allowed = -64.0;
3167        let max_allowed = 64.0;
3168        if *value < min_allowed || *value > max_allowed {
3169            return Err(Error::NotSupported(None));
3170        }
3171
3172        if self.default_playback_rate.get() == *value {
3173            return Ok(());
3174        }
3175
3176        self.default_playback_rate.set(*value);
3177
3178        // The user agent must queue a media element task given the media element to fire an event
3179        // named ratechange at the media element.
3180        self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3181
3182        Ok(())
3183    }
3184
3185    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
3186    fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
3187        Ok(Finite::wrap(self.playback_rate.get()))
3188    }
3189
3190    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
3191    fn SetPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3192        // The attribute is mutable: on setting, the user agent must follow these steps:
3193
3194        // Step 1. If the given value is not supported by the user agent, then throw a
3195        // "NotSupportedError" DOMException.
3196        let min_allowed = -64.0;
3197        let max_allowed = 64.0;
3198        if *value < min_allowed || *value > max_allowed {
3199            return Err(Error::NotSupported(None));
3200        }
3201
3202        if self.playback_rate.get() == *value {
3203            return Ok(());
3204        }
3205
3206        // Step 2. Set playbackRate to the new value, and if the element is potentially playing,
3207        // change the playback speed.
3208        self.playback_rate.set(*value);
3209
3210        if self.is_potentially_playing() &&
3211            let Some(ref player) = *self.player.borrow() &&
3212            let Err(error) = player.lock().unwrap().set_playback_rate(*value)
3213        {
3214            warn!("Could not set the playback rate: {error:?}");
3215        }
3216
3217        // The user agent must queue a media element task given the media element to fire an event
3218        // named ratechange at the media element.
3219        self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3220
3221        Ok(())
3222    }
3223
3224    /// <https://html.spec.whatwg.org/multipage/#dom-media-duration>
3225    fn Duration(&self) -> f64 {
3226        self.duration.get()
3227    }
3228
3229    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
3230    fn CurrentTime(&self) -> Finite<f64> {
3231        Finite::wrap(if self.default_playback_start_position.get() != 0. {
3232            self.default_playback_start_position.get()
3233        } else if self.seeking.get() {
3234            // Note that the other browsers do the similar (by checking `seeking` value or clamp the
3235            // `official` position to the earliest possible position, the duration, and the seekable
3236            // ranges.
3237            // <https://github.com/whatwg/html/issues/11773>
3238            self.current_seek_position.get()
3239        } else {
3240            self.official_playback_position.get()
3241        })
3242    }
3243
3244    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
3245    fn SetCurrentTime(&self, _cx: &mut JSContext, time: Finite<f64>) {
3246        if self.ready_state.get() == ReadyState::HaveNothing {
3247            self.default_playback_start_position.set(*time);
3248        } else {
3249            self.official_playback_position.set(*time);
3250            self.seek(*time, /* approximate_for_speed */ false);
3251        }
3252    }
3253
3254    /// <https://html.spec.whatwg.org/multipage/#dom-media-seeking>
3255    fn Seeking(&self) -> bool {
3256        self.seeking.get()
3257    }
3258
3259    /// <https://html.spec.whatwg.org/multipage/#dom-media-ended>
3260    fn Ended(&self) -> bool {
3261        self.ended_playback(LoopCondition::Included) &&
3262            self.direction_of_playback() == PlaybackDirection::Forwards
3263    }
3264
3265    /// <https://html.spec.whatwg.org/multipage/#dom-media-fastseek>
3266    fn FastSeek(&self, time: Finite<f64>) {
3267        self.seek(*time, /* approximate_for_speed */ true);
3268    }
3269
3270    /// <https://html.spec.whatwg.org/multipage/#dom-media-played>
3271    fn Played(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3272        TimeRanges::new(
3273            self.global().as_window(),
3274            self.played.borrow().clone(),
3275            can_gc,
3276        )
3277    }
3278
3279    /// <https://html.spec.whatwg.org/multipage/#dom-media-seekable>
3280    fn Seekable(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3281        TimeRanges::new(self.global().as_window(), self.seekable(), can_gc)
3282    }
3283
3284    /// <https://html.spec.whatwg.org/multipage/#dom-media-buffered>
3285    fn Buffered(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3286        let mut buffered = TimeRangesContainer::default();
3287        if let Some(ref player) = *self.player.borrow() {
3288            let ranges = player.lock().unwrap().buffered();
3289            for range in ranges {
3290                let _ = buffered.add(range.start, range.end);
3291            }
3292        }
3293        TimeRanges::new(self.global().as_window(), buffered, can_gc)
3294    }
3295
3296    /// <https://html.spec.whatwg.org/multipage/#dom-media-audiotracks>
3297    fn AudioTracks(&self, cx: &mut JSContext) -> DomRoot<AudioTrackList> {
3298        let window = self.owner_window();
3299        self.audio_tracks_list
3300            .or_init(|| AudioTrackList::new(cx, &window, &[], Some(self)))
3301    }
3302
3303    /// <https://html.spec.whatwg.org/multipage/#dom-media-videotracks>
3304    fn VideoTracks(&self, can_gc: CanGc) -> DomRoot<VideoTrackList> {
3305        let window = self.owner_window();
3306        self.video_tracks_list
3307            .or_init(|| VideoTrackList::new(&window, &[], Some(self), can_gc))
3308    }
3309
3310    /// <https://html.spec.whatwg.org/multipage/#dom-media-texttracks>
3311    fn TextTracks(&self, can_gc: CanGc) -> DomRoot<TextTrackList> {
3312        let window = self.owner_window();
3313        self.text_tracks_list
3314            .or_init(|| TextTrackList::new(&window, &[], can_gc))
3315    }
3316
3317    /// <https://html.spec.whatwg.org/multipage/#dom-media-addtexttrack>
3318    fn AddTextTrack(
3319        &self,
3320        kind: TextTrackKind,
3321        label: DOMString,
3322        language: DOMString,
3323        can_gc: CanGc,
3324    ) -> DomRoot<TextTrack> {
3325        let window = self.owner_window();
3326        // Step 1 & 2
3327        // FIXME(#22314, dlrobertson) set the ready state to Loaded
3328        let track = TextTrack::new(
3329            &window,
3330            "".into(),
3331            kind,
3332            label,
3333            language,
3334            TextTrackMode::Hidden,
3335            None,
3336            can_gc,
3337        );
3338        // Step 3 & 4
3339        self.TextTracks(can_gc).add(&track);
3340        // Step 5
3341        DomRoot::from_ref(&track)
3342    }
3343
3344    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3345    fn GetVolume(&self) -> Fallible<Finite<f64>> {
3346        Ok(Finite::wrap(self.volume.get()))
3347    }
3348
3349    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3350    fn SetVolume(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3351        // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
3352        // "IndexSizeError" DOMException must be thrown instead.
3353        let minimum_volume = 0.0;
3354        let maximum_volume = 1.0;
3355        if *value < minimum_volume || *value > maximum_volume {
3356            return Err(Error::IndexSize(None));
3357        }
3358
3359        if self.volume.get() == *value {
3360            return Ok(());
3361        }
3362
3363        self.volume.set(*value);
3364
3365        if let Some(ref player) = *self.player.borrow() &&
3366            let Err(error) = player.lock().unwrap().set_volume(*value)
3367        {
3368            warn!("Could not set the volume: {error:?}");
3369        }
3370
3371        // The user agent must queue a media element task given the media element to fire an event
3372        // named volumechange at the media element.
3373        self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3374
3375        // Then, if the media element is not allowed to play, the user agent must run the internal
3376        // pause steps for the media element.
3377        if !self.is_allowed_to_play() {
3378            self.internal_pause_steps();
3379        }
3380
3381        Ok(())
3382    }
3383}
3384
3385impl VirtualMethods for HTMLMediaElement {
3386    fn super_type(&self) -> Option<&dyn VirtualMethods> {
3387        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3388    }
3389
3390    fn attribute_mutated(
3391        &self,
3392        cx: &mut JSContext,
3393        attr: AttrRef<'_>,
3394        mutation: AttributeMutation,
3395    ) {
3396        self.super_type()
3397            .unwrap()
3398            .attribute_mutated(cx, attr, mutation);
3399
3400        match *attr.local_name() {
3401            local_name!("muted") => {
3402                // <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3403                // When a media element is created, if the element has a muted content attribute
3404                // specified, then the muted IDL attribute should be set to true.
3405                if let AttributeMutation::Set(
3406                    _,
3407                    AttributeMutationReason::ByCloning | AttributeMutationReason::ByParser,
3408                ) = mutation
3409                {
3410                    self.SetMuted(cx, true);
3411                }
3412            },
3413            local_name!("src") => {
3414                // <https://html.spec.whatwg.org/multipage/#location-of-the-media-resource>
3415                // If a src attribute of a media element is set or changed, the user agent must invoke
3416                // the media element's media element load algorithm (Removing the src attribute does
3417                // not do this, even if there are source elements present).
3418                if !mutation.is_removal() {
3419                    self.media_element_load_algorithm(cx);
3420                }
3421            },
3422            local_name!("controls") => {
3423                if mutation.new_value(attr).is_some() {
3424                    self.render_controls(cx);
3425                } else {
3426                    self.remove_controls();
3427                }
3428            },
3429            _ => (),
3430        };
3431    }
3432
3433    /// <https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document>
3434    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
3435        self.super_type().unwrap().unbind_from_tree(cx, context);
3436
3437        self.remove_controls();
3438
3439        // Step 1. Await a stable state, allowing the task that removed the media element from the Document to continue.
3440        // The synchronous section consists of all the remaining steps of this algorithm.
3441        // (Steps in the synchronous section are marked with ⌛.)
3442        if context.tree_connected {
3443            let task = MediaElementMicrotask::PauseIfNotInDocument {
3444                elem: DomRoot::from_ref(self),
3445            };
3446            ScriptThread::await_stable_state(Microtask::MediaElement(task));
3447        }
3448    }
3449
3450    fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
3451        self.super_type().unwrap().adopting_steps(cx, old_doc);
3452
3453        // Note that media control id should be adopting between documents so "privileged"
3454        // document.servoGetMediaControls(id) API is keeping access to the whitelist of media
3455        // controls identifiers.
3456        if let Some(id) = &*self.media_controls_id.borrow() {
3457            let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3458                error!("Missing media controls shadow root");
3459                return;
3460            };
3461
3462            old_doc.unregister_media_controls(id);
3463            self.owner_document()
3464                .register_media_controls(id, &shadow_root);
3465        }
3466    }
3467}
3468
3469#[derive(JSTraceable, MallocSizeOf)]
3470pub(crate) enum MediaElementMicrotask {
3471    ResourceSelection {
3472        elem: DomRoot<HTMLMediaElement>,
3473        generation_id: u32,
3474        #[no_trace]
3475        base_url: ServoUrl,
3476    },
3477    PauseIfNotInDocument {
3478        elem: DomRoot<HTMLMediaElement>,
3479    },
3480    Seeked {
3481        elem: DomRoot<HTMLMediaElement>,
3482        generation_id: u32,
3483    },
3484    SelectNextSourceChild {
3485        elem: DomRoot<HTMLMediaElement>,
3486        generation_id: u32,
3487    },
3488    SelectNextSourceChildAfterWait {
3489        elem: DomRoot<HTMLMediaElement>,
3490        generation_id: u32,
3491    },
3492}
3493
3494impl MicrotaskRunnable for MediaElementMicrotask {
3495    fn handler(&self, cx: &mut JSContext) {
3496        match self {
3497            &MediaElementMicrotask::ResourceSelection {
3498                ref elem,
3499                generation_id,
3500                ref base_url,
3501            } => {
3502                if generation_id == elem.generation_id.get() {
3503                    elem.resource_selection_algorithm_sync(base_url.clone(), cx);
3504                }
3505            },
3506            // https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document
3507            MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3508                // Step 2. ⌛ If the media element is in a document, return.
3509                if elem.upcast::<Node>().is_connected() {
3510                    return;
3511                }
3512                // Step 3. ⌛ Run the internal pause steps for the media element.
3513                elem.internal_pause_steps();
3514            },
3515            &MediaElementMicrotask::Seeked {
3516                ref elem,
3517                generation_id,
3518            } => {
3519                if generation_id == elem.generation_id.get() {
3520                    elem.seek_end();
3521                }
3522            },
3523            &MediaElementMicrotask::SelectNextSourceChild {
3524                ref elem,
3525                generation_id,
3526            } => {
3527                if generation_id == elem.generation_id.get() {
3528                    elem.select_next_source_child(cx);
3529                }
3530            },
3531            &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3532                ref elem,
3533                generation_id,
3534            } => {
3535                if generation_id == elem.generation_id.get() {
3536                    elem.select_next_source_child_after_wait(cx);
3537                }
3538            },
3539        }
3540    }
3541
3542    fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
3543        match self {
3544            &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3545            &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3546            &MediaElementMicrotask::Seeked { ref elem, .. } |
3547            &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3548            &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3549                enter_auto_realm(cx, &**elem)
3550            },
3551        }
3552    }
3553}
3554
3555enum Resource {
3556    Object,
3557    Url(ServoUrl),
3558}
3559
3560#[derive(Debug, MallocSizeOf, PartialEq)]
3561enum DataBuffer {
3562    Payload(Vec<u8>),
3563    EndOfStream,
3564}
3565
3566#[derive(MallocSizeOf)]
3567struct BufferedDataSource {
3568    /// During initial setup and seeking (including clearing the buffer queue
3569    /// and resetting the end-of-stream state), the data source should be locked and
3570    /// any request for processing should be ignored until the media player informs us
3571    /// via the NeedData event that it is ready to accept incoming data.
3572    locked: Cell<bool>,
3573    /// Temporary storage for incoming data.
3574    buffers: VecDeque<DataBuffer>,
3575}
3576
3577impl BufferedDataSource {
3578    fn new() -> BufferedDataSource {
3579        BufferedDataSource {
3580            locked: Cell::new(true),
3581            buffers: VecDeque::default(),
3582        }
3583    }
3584
3585    fn set_locked(&self, locked: bool) {
3586        self.locked.set(locked)
3587    }
3588
3589    fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3590        debug_assert_ne!(
3591            self.buffers.back(),
3592            Some(&DataBuffer::EndOfStream),
3593            "The media backend not expects any further data after end of stream"
3594        );
3595
3596        self.buffers.push_back(buffer);
3597    }
3598
3599    fn process_into_player_from_queue(
3600        &mut self,
3601        player: &Arc<Mutex<dyn Player>>,
3602    ) -> Result<(), PlayerError> {
3603        // Early out if any request for processing should be ignored.
3604        if self.locked.get() {
3605            return Ok(());
3606        }
3607
3608        while let Some(buffer) = self.buffers.pop_front() {
3609            match buffer {
3610                DataBuffer::Payload(payload) => {
3611                    if let Err(error) = player.lock().unwrap().push_data(payload) {
3612                        warn!("Could not push input data to player: {error:?}");
3613                        return Err(error);
3614                    }
3615                },
3616                DataBuffer::EndOfStream => {
3617                    if let Err(error) = player.lock().unwrap().end_of_stream() {
3618                        warn!("Could not signal EOS to player: {error:?}");
3619                        return Err(error);
3620                    }
3621                },
3622            }
3623        }
3624
3625        Ok(())
3626    }
3627
3628    fn reset(&mut self) {
3629        self.locked.set(true);
3630        self.buffers.clear();
3631    }
3632}
3633
3634/// Indicates the reason why a fetch request was cancelled.
3635#[derive(Debug, MallocSizeOf, PartialEq)]
3636enum CancelReason {
3637    /// We were asked to stop pushing data to the player.
3638    Backoff,
3639    /// An error ocurred while fetching the media data.
3640    Error,
3641    /// The fetching process is aborted by the user.
3642    Abort,
3643}
3644
3645#[derive(MallocSizeOf)]
3646pub(crate) struct HTMLMediaElementFetchContext {
3647    /// The fetch request id.
3648    request_id: RequestId,
3649    /// Some if the request has been cancelled.
3650    cancel_reason: Option<CancelReason>,
3651    /// Indicates whether the fetched stream is seekable.
3652    is_seekable: bool,
3653    /// Indicates whether the fetched stream is origin clean.
3654    origin_clean: bool,
3655    /// The buffered data source which to be processed by media backend.
3656    data_source: RefCell<BufferedDataSource>,
3657    /// Fetch canceller. Allows cancelling the current fetch request by
3658    /// manually calling its .cancel() method or automatically on Drop.
3659    fetch_canceller: FetchCanceller,
3660}
3661
3662impl HTMLMediaElementFetchContext {
3663    fn new(
3664        request_id: RequestId,
3665        core_resource_thread: CoreResourceThread,
3666    ) -> HTMLMediaElementFetchContext {
3667        HTMLMediaElementFetchContext {
3668            request_id,
3669            cancel_reason: None,
3670            is_seekable: false,
3671            origin_clean: true,
3672            data_source: RefCell::new(BufferedDataSource::new()),
3673            fetch_canceller: FetchCanceller::new(request_id, false, core_resource_thread),
3674        }
3675    }
3676
3677    fn request_id(&self) -> RequestId {
3678        self.request_id
3679    }
3680
3681    fn is_seekable(&self) -> bool {
3682        self.is_seekable
3683    }
3684
3685    fn set_seekable(&mut self, seekable: bool) {
3686        self.is_seekable = seekable;
3687    }
3688
3689    fn origin_is_clean(&self) -> bool {
3690        self.origin_clean
3691    }
3692
3693    fn set_origin_clean(&mut self, origin_clean: bool) {
3694        self.origin_clean = origin_clean;
3695    }
3696
3697    fn data_source(&self) -> &RefCell<BufferedDataSource> {
3698        &self.data_source
3699    }
3700
3701    fn cancel(&mut self, reason: CancelReason) {
3702        if self.cancel_reason.is_some() {
3703            return;
3704        }
3705        self.cancel_reason = Some(reason);
3706        self.data_source.borrow_mut().reset();
3707        self.fetch_canceller.abort();
3708    }
3709
3710    fn cancel_reason(&self) -> &Option<CancelReason> {
3711        &self.cancel_reason
3712    }
3713}
3714
3715struct HTMLMediaElementFetchListener {
3716    /// The element that initiated the request.
3717    element: Trusted<HTMLMediaElement>,
3718    /// The generation of the media element when this fetch started.
3719    generation_id: u32,
3720    /// The fetch request id.
3721    request_id: RequestId,
3722    /// Time of last progress notification.
3723    next_progress_event: Instant,
3724    /// Url for the resource.
3725    url: ServoUrl,
3726    /// Expected content length of the media asset being fetched or played.
3727    expected_content_length: Option<u64>,
3728    /// Actual content length of the media asset was fetched.
3729    fetched_content_length: u64,
3730    /// Discarded content length from the network for the ongoing
3731    /// request if range requests are not supported. Seek requests set it
3732    /// to the required position (in bytes).
3733    content_length_to_discard: u64,
3734}
3735
3736impl FetchResponseListener for HTMLMediaElementFetchListener {
3737    fn process_request_body(&mut self, _: RequestId) {}
3738
3739    fn process_response(
3740        &mut self,
3741        cx: &mut JSContext,
3742        _: RequestId,
3743        metadata: Result<FetchMetadata, NetworkError>,
3744    ) {
3745        let element = self.element.root();
3746
3747        let (metadata, origin_clean) = match metadata {
3748            Ok(fetch_metadata) => match fetch_metadata {
3749                FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3750                FetchMetadata::Filtered { filtered, unsafe_ } => (
3751                    Some(unsafe_),
3752                    matches!(
3753                        filtered,
3754                        FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3755                    ),
3756                ),
3757            },
3758            Err(_) => (None, true),
3759        };
3760
3761        let (status_is_success, is_seekable) =
3762            metadata.as_ref().map_or((false, false), |metadata| {
3763                let status = &metadata.status;
3764                (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3765            });
3766
3767        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3768        if !status_is_success {
3769            if element.ready_state.get() == ReadyState::HaveNothing {
3770                // => "If the media data cannot be fetched at all, due to network errors..."
3771                element.media_data_processing_failure_steps();
3772            } else {
3773                // => "If the connection is interrupted after some media data has been received..."
3774                element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3775            }
3776            return;
3777        }
3778
3779        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3780            current_fetch_context.set_seekable(is_seekable);
3781            current_fetch_context.set_origin_clean(origin_clean);
3782        }
3783
3784        if let Some(metadata) = metadata.as_ref() &&
3785            let Some(headers) = metadata.headers.as_ref()
3786        {
3787            // For range requests we get the size of the media asset from the Content-Range
3788            // header. Otherwise, we get it from the Content-Length header.
3789            let content_length = if let Some(content_range) = headers.typed_get::<ContentRange>() {
3790                content_range.bytes_len()
3791            } else {
3792                headers
3793                    .typed_get::<ContentLength>()
3794                    .map(|content_length| content_length.0)
3795            };
3796
3797            // We only set the expected input size if it changes.
3798            if content_length != self.expected_content_length &&
3799                let Some(content_length) = content_length
3800            {
3801                self.expected_content_length = Some(content_length);
3802            }
3803        }
3804
3805        // Explicit media player initialization with live/seekable source.
3806        if let Err(e) = element
3807            .player
3808            .borrow()
3809            .as_ref()
3810            .unwrap()
3811            .lock()
3812            .unwrap()
3813            .set_seekable(is_seekable)
3814        {
3815            warn!("Could not set player seekable {:?}", e);
3816        }
3817
3818        if let Some(expected_content_length) = self.expected_content_length &&
3819            let Err(e) = element
3820                .player
3821                .borrow()
3822                .as_ref()
3823                .unwrap()
3824                .lock()
3825                .unwrap()
3826                .set_input_size(expected_content_length)
3827        {
3828            warn!("Could not set player input size {:?}", e);
3829        }
3830    }
3831
3832    fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
3833        let element = self.element.root();
3834
3835        self.fetched_content_length += chunk.len() as u64;
3836
3837        // If an error was received previously, we skip processing the payload.
3838        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3839            if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3840                return;
3841            }
3842
3843            // Discard chunk of the response body if fetch context doesn't support range requests.
3844            let payload = if !current_fetch_context.is_seekable() &&
3845                self.content_length_to_discard != 0
3846            {
3847                if chunk.len() as u64 > self.content_length_to_discard {
3848                    let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
3849                    self.content_length_to_discard = 0;
3850                    shrink_chunk
3851                } else {
3852                    // Completely discard this response chunk.
3853                    self.content_length_to_discard -= chunk.len() as u64;
3854                    return;
3855                }
3856            } else {
3857                chunk
3858            };
3859
3860            if let Err(e) = {
3861                let mut data_source = current_fetch_context.data_source().borrow_mut();
3862                data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
3863                data_source
3864                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3865            } {
3866                // If we are pushing too much data and we know that we can
3867                // restart the download later from where we left, we cancel
3868                // the current request. Otherwise, we continue the request
3869                // assuming that we may drop some frames.
3870                if e == PlayerError::EnoughData {
3871                    current_fetch_context.cancel(CancelReason::Backoff);
3872                }
3873                return;
3874            }
3875        }
3876
3877        // <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
3878        // While the load is not suspended (see below), every 350ms (±200ms) or for every byte
3879        // received, whichever is least frequent, queue a media element task given the media element
3880        // to fire an event named progress at the element.
3881        if Instant::now() > self.next_progress_event {
3882            element.queue_media_element_task_to_fire_event(atom!("progress"));
3883            self.next_progress_event = Instant::now() + Duration::from_millis(350);
3884        }
3885    }
3886
3887    fn process_response_eof(
3888        self,
3889        cx: &mut JSContext,
3890        _: RequestId,
3891        status: Result<(), NetworkError>,
3892        timing: ResourceFetchTiming,
3893    ) {
3894        let element = self.element.root();
3895
3896        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3897        if status.is_ok() && self.fetched_content_length != 0 {
3898            // => "Once the entire media resource has been fetched..."
3899
3900            // There are no more chunks of the response body forthcoming, so we can
3901            // go ahead and notify the media backend not to expect any further data.
3902            if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
3903            {
3904                // On initial state change READY -> PAUSED the media player perform
3905                // seek to initial position by event with seek segment (TIME format)
3906                // while media stack operates in BYTES format and configuring segment
3907                // start and stop positions without the total size of the stream is not
3908                // possible. As fallback the media player perform seek with BYTES format
3909                // and initiate seek request via "seek-data" callback with required offset.
3910                if self.expected_content_length.is_none() &&
3911                    let Err(e) = element
3912                        .player
3913                        .borrow()
3914                        .as_ref()
3915                        .unwrap()
3916                        .lock()
3917                        .unwrap()
3918                        .set_input_size(self.fetched_content_length)
3919                {
3920                    warn!("Could not set player input size {:?}", e);
3921                }
3922
3923                let mut data_source = current_fetch_context.data_source().borrow_mut();
3924
3925                data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
3926                let _ = data_source
3927                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
3928            }
3929
3930            // Step 1. Fire an event named progress at the media element.
3931            element
3932                .upcast::<EventTarget>()
3933                .fire_event(cx, atom!("progress"));
3934
3935            // Step 2. Set the networkState to NETWORK_IDLE and fire an event named suspend at the
3936            // media element.
3937            element.network_state.set(NetworkState::Idle);
3938
3939            element
3940                .upcast::<EventTarget>()
3941                .fire_event(cx, atom!("suspend"));
3942        } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
3943            // => "If the connection is interrupted after some media data has been received..."
3944            element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3945        } else {
3946            // => "If the media data can be fetched but is found by inspection to be in an
3947            // unsupported format, or can otherwise not be rendered at all"
3948            element.media_data_processing_failure_steps();
3949        }
3950
3951        network_listener::submit_timing(cx, &self, &status, &timing);
3952    }
3953
3954    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
3955        let global = &self.resource_timing_global();
3956        global.report_csp_violations(violations, None, None);
3957    }
3958
3959    fn should_invoke(&self) -> bool {
3960        let element = self.element.root();
3961
3962        if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
3963            return false;
3964        }
3965
3966        let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
3967            return false;
3968        };
3969
3970        // Whether the new fetch request was triggered.
3971        if current_fetch_context.request_id() != self.request_id {
3972            return false;
3973        }
3974
3975        // Whether the current fetch request was cancelled due to a network or decoding error, or
3976        // was aborted by the user.
3977        if let Some(cancel_reason) = current_fetch_context.cancel_reason() &&
3978            matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort)
3979        {
3980            return false;
3981        }
3982
3983        true
3984    }
3985}
3986
3987impl ResourceTimingListener for HTMLMediaElementFetchListener {
3988    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
3989        let initiator_type = InitiatorType::LocalName(
3990            self.element
3991                .root()
3992                .upcast::<Element>()
3993                .local_name()
3994                .to_string(),
3995        );
3996        (initiator_type, self.url.clone())
3997    }
3998
3999    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
4000        self.element.root().owner_document().global()
4001    }
4002}
4003
4004impl HTMLMediaElementFetchListener {
4005    fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
4006        Self {
4007            element: Trusted::new(element),
4008            generation_id: element.generation_id.get(),
4009            request_id,
4010            next_progress_event: Instant::now() + Duration::from_millis(350),
4011            url,
4012            expected_content_length: None,
4013            fetched_content_length: 0,
4014            content_length_to_discard: offset,
4015        }
4016    }
4017}
4018
4019/// The [`HTMLMediaElementEventHandler`] is a structure responsible for handling media events for
4020/// the [`HTMLMediaElement`] and exists to decouple ownership of the [`HTMLMediaElement`] from IPC
4021/// router callback.
4022#[derive(JSTraceable, MallocSizeOf)]
4023struct HTMLMediaElementEventHandler {
4024    element: WeakRef<HTMLMediaElement>,
4025}
4026
4027#[expect(unsafe_code)]
4028unsafe impl Send for HTMLMediaElementEventHandler {}
4029
4030impl HTMLMediaElementEventHandler {
4031    fn new(element: &HTMLMediaElement) -> Self {
4032        Self {
4033            element: WeakRef::new(element),
4034        }
4035    }
4036
4037    fn handle_player_event(&self, player_id: usize, event: PlayerEvent, cx: &mut JSContext) {
4038        let Some(element) = self.element.root() else {
4039            return;
4040        };
4041
4042        // Abort event processing if the associated media player is outdated.
4043        if element.player_id().is_none_or(|id| id != player_id) {
4044            return;
4045        }
4046
4047        match event {
4048            PlayerEvent::DurationChanged(duration) => element.playback_duration_changed(duration),
4049            PlayerEvent::EndOfStream => element.playback_end(),
4050            PlayerEvent::EnoughData => element.playback_enough_data(),
4051            PlayerEvent::Error(ref error) => element.playback_error(error, cx),
4052            PlayerEvent::MetadataUpdated(ref metadata) => {
4053                element.playback_metadata_updated(cx, metadata)
4054            },
4055            PlayerEvent::NeedData => element.playback_need_data(),
4056            PlayerEvent::PositionChanged(position) => element.playback_position_changed(position),
4057            PlayerEvent::SeekData(offset, seek_lock) => {
4058                element.fetch_request(Some(offset), Some(seek_lock))
4059            },
4060            PlayerEvent::SeekDone(position) => element.playback_seek_done(position),
4061            PlayerEvent::StateChanged(ref state) => element.playback_state_changed(state),
4062            PlayerEvent::VideoFrameUpdated => element.playback_video_frame_updated(),
4063        }
4064    }
4065}
4066
4067impl Drop for HTMLMediaElementEventHandler {
4068    fn drop(&mut self) {
4069        // The weak reference to the media element is not thread-safe and MUST be deleted on the
4070        // script thread, which is guaranteed by ownership of the `event handler` in the IPC router
4071        // callback (queued task to the media element task source) and the media element itself.
4072        assert_in_script();
4073    }
4074}