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 js::context::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 js::context::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 || {
851                    let this = this.root();
852                    if generation_id != this.generation_id.get() {
853                        return;
854                    }
855
856                    this.fulfill_in_flight_play_promises(|| {});
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(|| {
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(|| {
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 js::context::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(
1098        &self,
1099        base_url: ServoUrl,
1100        cx: &mut js::context::JSContext,
1101    ) {
1102        // TODO Step 5. If the media element's blocked-on-parser flag is false, then populate the
1103        // list of pending text tracks.
1104        // FIXME(ferjm): Implement blocked_on_parser logic
1105        // https://html.spec.whatwg.org/multipage/#blocked-on-parser
1106        // FIXME(nox): Maybe populate the list of pending text tracks.
1107
1108        enum Mode {
1109            Object,
1110            Attribute(String),
1111            Children(DomRoot<HTMLSourceElement>),
1112        }
1113
1114        // Step 6.
1115        let mode = if self.src_object.borrow().is_some() {
1116            // If the media element has an assigned media provider object, then let mode be object.
1117            Mode::Object
1118        } else if let Some(src) = self
1119            .upcast::<Element>()
1120            .get_attribute_string_value(&local_name!("src"))
1121        {
1122            // Otherwise, if the media element has no assigned media provider object but has a src
1123            // attribute, then let mode be attribute.
1124            Mode::Attribute(src)
1125        } else if let Some(source) = self
1126            .upcast::<Node>()
1127            .children_unrooted(cx.no_gc())
1128            .find_map(UnrootedDom::downcast::<HTMLSourceElement>)
1129        {
1130            // Otherwise, if the media element does not have an assigned media provider object and
1131            // does not have a src attribute, but does have a source element child, then let mode be
1132            // children and let candidate be the first such source element child in tree order.
1133            Mode::Children(source.as_rooted())
1134        } else {
1135            // Otherwise, the media element has no assigned media provider object and has neither a
1136            // src attribute nor a source element child:
1137            self.load_state.set(LoadState::NotLoaded);
1138
1139            // Step 6.none.1. Set the networkState to NETWORK_EMPTY.
1140            self.network_state.set(NetworkState::Empty);
1141
1142            // Step 6.none.2. Set the element's delaying-the-load-event flag to false. This stops
1143            // delaying the load event.
1144            self.delay_load_event(false, cx);
1145
1146            // Step 6.none.3. End the synchronous section and return.
1147            return;
1148        };
1149
1150        // Step 7. Set the media element's networkState to NETWORK_LOADING.
1151        self.network_state.set(NetworkState::Loading);
1152
1153        // Step 8. Queue a media element task given the media element to fire an event named
1154        // loadstart at the media element.
1155        self.queue_media_element_task_to_fire_event(atom!("loadstart"));
1156
1157        // Step 9. Run the appropriate steps from the following list:
1158        match mode {
1159            Mode::Object => {
1160                // => "If mode is object"
1161                self.load_from_src_object();
1162            },
1163            Mode::Attribute(src) => {
1164                // => "If mode is attribute"
1165                self.load_from_src_attribute(base_url, &src);
1166            },
1167            Mode::Children(source) => {
1168                // => "Otherwise (mode is children)""
1169                self.load_from_source_child(&source);
1170            },
1171        }
1172    }
1173
1174    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1175    fn load_from_src_object(&self) {
1176        self.load_state.set(LoadState::LoadingFromSrcObject);
1177
1178        // Step 9.object.1. Set the currentSrc attribute to the empty string.
1179        "".clone_into(&mut self.current_src.borrow_mut());
1180
1181        // Step 9.object.3. Run the resource fetch algorithm with the assigned media
1182        // provider object. If that algorithm returns without aborting this one, then the
1183        // load failed.
1184        // Note that the resource fetch algorithm itself takes care of the cleanup in case
1185        // of failure itself.
1186        self.resource_fetch_algorithm(Resource::Object);
1187    }
1188
1189    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1190    fn load_from_src_attribute(&self, base_url: ServoUrl, src: &str) {
1191        self.load_state.set(LoadState::LoadingFromSrcAttribute);
1192
1193        // Step 9.attribute.1. If the src attribute's value is the empty string, then end
1194        // the synchronous section, and jump down to the failed with attribute step below.
1195        if src.is_empty() {
1196            self.queue_dedicated_media_source_failure_steps();
1197            return;
1198        }
1199
1200        // Step 9.attribute.2. Let urlRecord be the result of encoding-parsing a URL given
1201        // the src attribute's value, relative to the media element's node document when the
1202        // src attribute was last changed.
1203        let Ok(url_record) = base_url.join(src) else {
1204            self.queue_dedicated_media_source_failure_steps();
1205            return;
1206        };
1207
1208        // Step 9.attribute.3. If urlRecord is not failure, then set the currentSrc
1209        // attribute to the result of applying the URL serializer to urlRecord.
1210        *self.current_src.borrow_mut() = url_record.as_str().into();
1211
1212        // Step 9.attribute.5. If urlRecord is not failure, then run the resource fetch
1213        // algorithm with urlRecord. If that algorithm returns without aborting this one,
1214        // then the load failed.
1215        // Note that the resource fetch algorithm itself takes care
1216        // of the cleanup in case of failure itself.
1217        self.resource_fetch_algorithm(Resource::Url(url_record));
1218    }
1219
1220    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1221    fn load_from_source_child(&self, source: &HTMLSourceElement) {
1222        self.load_state.set(LoadState::LoadingFromSourceChild);
1223
1224        // Step 9.children.1. Let pointer be a position defined by two adjacent nodes in the media
1225        // element's child list, treating the start of the list (before the first child in the list,
1226        // if any) and end of the list (after the last child in the list, if any) as nodes in their
1227        // own right. One node is the node before pointer, and the other node is the node after
1228        // pointer. Initially, let pointer be the position between the candidate node and the next
1229        // node, if there are any, or the end of the list, if it is the last node.
1230        *self.source_children_pointer.borrow_mut() =
1231            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), false));
1232
1233        let element = source.upcast::<Element>();
1234
1235        // Step 9.children.2. Process candidate: If candidate does not have a src attribute, or if
1236        // its src attribute's value is the empty string, then end the synchronous section, and jump
1237        // down to the failed with elements step below.
1238        let Some(src) = element
1239            .get_attribute_string_value(&local_name!("src"))
1240            .filter(|value| !value.is_empty())
1241        else {
1242            self.load_from_source_child_failure_steps(source);
1243            return;
1244        };
1245
1246        // Step 9.children.3. If candidate has a media attribute whose value does not match the
1247        // environment, then end the synchronous section, and jump down to the failed with elements
1248        // step below.
1249        if let Some(media) = element.get_attribute_string_value(&local_name!("media")) &&
1250            !MediaList::matches_environment(&element.owner_document(), &media)
1251        {
1252            self.load_from_source_child_failure_steps(source);
1253            return;
1254        }
1255
1256        // Step 9.children.4. Let urlRecord be the result of encoding-parsing a URL given
1257        // candidate's src attribute's value, relative to candidate's node document when the src
1258        // attribute was last changed.
1259        let Ok(url_record) = source.owner_document().base_url().join(&src) else {
1260            // Step 9.children.5. If urlRecord is failure, then end the synchronous section,
1261            // and jump down to the failed with elements step below.
1262            self.load_from_source_child_failure_steps(source);
1263            return;
1264        };
1265
1266        // Step 9.children.6. If candidate has a type attribute whose value, when parsed as a MIME
1267        // type (including any codecs described by the codecs parameter, for types that define that
1268        // parameter), represents a type that the user agent knows it cannot render, then end the
1269        // synchronous section, and jump down to the failed with elements step below.
1270        if let Some(type_) = element.get_attribute_string_value(&local_name!("type")) &&
1271            ServoMedia::get().can_play_type(&type_) == SupportsMediaType::No
1272        {
1273            self.load_from_source_child_failure_steps(source);
1274            return;
1275        }
1276
1277        // Reset the media player before loading the next source child.
1278        self.reset_media_player();
1279
1280        self.current_source_child.set(Some(source));
1281
1282        // Step 9.children.7. Set the currentSrc attribute to the result of applying the URL
1283        // serializer to urlRecord.
1284        *self.current_src.borrow_mut() = url_record.as_str().into();
1285
1286        // Step 9.children.9. Run the resource fetch algorithm with urlRecord. If that
1287        // algorithm returns without aborting this one, then the load failed.
1288        // Note that the resource fetch algorithm itself takes care
1289        // of the cleanup in case of failure itself.
1290        self.resource_fetch_algorithm(Resource::Url(url_record));
1291    }
1292
1293    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1294    fn load_from_source_child_failure_steps(&self, source: &HTMLSourceElement) {
1295        // Step 9.children.10. Failed with elements: Queue a media element task given the media
1296        // element to fire an event named error at candidate.
1297        let trusted_this = Trusted::new(self);
1298        let trusted_source = Trusted::new(source);
1299        let generation_id = self.generation_id.get();
1300
1301        self.owner_global()
1302            .task_manager()
1303            .media_element_task_source()
1304            .queue(task!(queue_error_event: move |cx| {
1305                let this = trusted_this.root();
1306                if generation_id != this.generation_id.get() {
1307                    return;
1308                }
1309
1310                let source = trusted_source.root();
1311                source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1312            }));
1313
1314        // Step 9.children.11. Await a stable state.
1315        let task = MediaElementMicrotask::SelectNextSourceChild {
1316            elem: DomRoot::from_ref(self),
1317            generation_id: self.generation_id.get(),
1318        };
1319
1320        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1321    }
1322
1323    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1324    fn select_next_source_child(&self, cx: &mut js::context::JSContext) {
1325        // Step 9.children.12. Forget the media element's media-resource-specific tracks.
1326        self.AudioTracks(cx).clear();
1327        self.VideoTracks(CanGc::from_cx(cx)).clear();
1328
1329        // Step 9.children.13. Find next candidate: Let candidate be null.
1330        let mut source_candidate = None;
1331
1332        // Step 9.children.14. Search loop: If the node after pointer is the end of the list, then
1333        // jump to the waiting step below.
1334        // Step 9.children.15. If the node after pointer is a source element, let candidate be that
1335        // element.
1336        // Step 9.children.16. Advance pointer so that the node before pointer is now the node that
1337        // was after pointer, and the node after pointer is the node after the node that used to be
1338        // after pointer, if any.
1339        if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1340            // Note that shared implementation between opaque types from
1341            // `inclusively_following_siblings` and `following_siblings` if not possible due to
1342            // precise capturing.
1343            if source_children_pointer.inclusive {
1344                for next_sibling in source_children_pointer
1345                    .source_before_pointer
1346                    .upcast::<Node>()
1347                    .inclusively_following_siblings()
1348                {
1349                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1350                    {
1351                        source_candidate = Some(next_source);
1352                        break;
1353                    }
1354                }
1355            } else {
1356                for next_sibling in source_children_pointer
1357                    .source_before_pointer
1358                    .upcast::<Node>()
1359                    .following_siblings()
1360                {
1361                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1362                    {
1363                        source_candidate = Some(next_source);
1364                        break;
1365                    }
1366                }
1367            };
1368        }
1369
1370        // Step 9.children.17. If candidate is null, jump back to the search loop step. Otherwise,
1371        // jump back to the process candidate step.
1372        if let Some(source_candidate) = source_candidate {
1373            self.load_from_source_child(&source_candidate);
1374            return;
1375        }
1376
1377        self.load_state.set(LoadState::WaitingForSource);
1378
1379        *self.source_children_pointer.borrow_mut() = None;
1380
1381        // Step 9.children.18. Waiting: Set the element's networkState attribute to the
1382        // NETWORK_NO_SOURCE value.
1383        self.network_state.set(NetworkState::NoSource);
1384
1385        // Step 9.children.19. Set the element's show poster flag to true.
1386        self.show_poster.set(true);
1387
1388        // Step 9.children.20. Queue a media element task given the media element to set the
1389        // element's delaying-the-load-event flag to false. This stops delaying the load event.
1390        let this = Trusted::new(self);
1391        let generation_id = self.generation_id.get();
1392
1393        self.owner_global()
1394            .task_manager()
1395            .media_element_task_source()
1396            .queue(task!(queue_delay_load_event: move |cx| {
1397                let this = this.root();
1398                if generation_id != this.generation_id.get() {
1399                    return;
1400                }
1401
1402                this.delay_load_event(false, cx);
1403            }));
1404
1405        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1406        // list. (This step might wait forever.)
1407    }
1408
1409    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1410    fn resource_selection_algorithm_failure_steps(&self) {
1411        match self.load_state.get() {
1412            LoadState::LoadingFromSrcObject => {
1413                // Step 9.object.4. Failed with media provider: Reaching this step indicates that
1414                // the media resource failed to load. Take pending play promises and queue a media
1415                // element task given the media element to run the dedicated media source failure
1416                // steps with the result.
1417                self.queue_dedicated_media_source_failure_steps();
1418            },
1419            LoadState::LoadingFromSrcAttribute => {
1420                // Step 9.attribute.6. Failed with attribute: Reaching this step indicates that the
1421                // media resource failed to load or that urlRecord is failure. Take pending play
1422                // promises and queue a media element task given the media element to run the
1423                // dedicated media source failure steps with the result.
1424                self.queue_dedicated_media_source_failure_steps();
1425            },
1426            LoadState::LoadingFromSourceChild => {
1427                // Step 9.children.10. Failed with elements: Queue a media element task given the
1428                // media element to fire an event named error at candidate.
1429                if let Some(source) = self.current_source_child.take() {
1430                    self.load_from_source_child_failure_steps(&source);
1431                }
1432            },
1433            _ => {},
1434        }
1435    }
1436
1437    fn fetch_request(&self, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1438        if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1439            error!("Missing request url");
1440            if let Some(seek_lock) = seek_lock {
1441                seek_lock.unlock(/* successful seek */ false);
1442            }
1443            self.resource_selection_algorithm_failure_steps();
1444            return;
1445        }
1446
1447        let document = self.owner_document();
1448        let destination = match self.media_type_id() {
1449            HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1450            HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1451        };
1452        let mut headers = HeaderMap::new();
1453        // FIXME(eijebong): Use typed headers once we have a constructor for the range header
1454        headers.insert(
1455            header::RANGE,
1456            HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(),
1457        );
1458        let url = match self.resource_url.borrow().as_ref() {
1459            Some(url) => url.clone(),
1460            None => self.blob_url.borrow().as_ref().unwrap().clone(),
1461        };
1462
1463        let cors_setting = cors_setting_for_element(self.upcast());
1464        let global = self.global();
1465        let request = create_a_potential_cors_request(
1466            Some(document.webview_id()),
1467            url.clone(),
1468            destination,
1469            cors_setting,
1470            None,
1471            global.get_referrer(),
1472        )
1473        .with_global_scope(&global)
1474        .headers(headers)
1475        .referrer_policy(document.get_referrer_policy());
1476
1477        let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1478        if let Some(ref mut current_fetch_context) = *current_fetch_context {
1479            current_fetch_context.cancel(CancelReason::Abort);
1480        }
1481
1482        *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1483            request.id,
1484            global.core_resource_thread(),
1485        ));
1486        let listener =
1487            HTMLMediaElementFetchListener::new(self, request.id, url, offset.unwrap_or(0));
1488
1489        self.owner_document().fetch_background(request, listener);
1490
1491        // Since we cancelled the previous fetch, from now on the media element
1492        // will only receive response data from the new fetch that's been
1493        // initiated. This means the player can resume operation, since all subsequent data
1494        // pushes will originate from the new seek offset.
1495        if let Some(seek_lock) = seek_lock {
1496            seek_lock.unlock(/* successful seek */ true);
1497        }
1498    }
1499
1500    /// <https://html.spec.whatwg.org/multipage/#eligible-for-autoplay>
1501    fn eligible_for_autoplay(&self) -> bool {
1502        // its can autoplay flag is true;
1503        self.autoplaying.get() &&
1504
1505        // its paused attribute is true;
1506        self.Paused() &&
1507
1508        // it has an autoplay attribute specified;
1509        self.Autoplay() &&
1510
1511        // its node document's active sandboxing flag set does not have the sandboxed automatic
1512        // features browsing context flag set; and
1513        {
1514            let document = self.owner_document();
1515
1516            !document.has_active_sandboxing_flag(
1517                SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1518            )
1519        }
1520
1521        // its node document is allowed to use the "autoplay" feature.
1522        // TODO: Feature policy: https://html.spec.whatwg.org/iframe-embed-object.html#allowed-to-use
1523    }
1524
1525    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
1526    fn resource_fetch_algorithm(&self, resource: Resource) {
1527        if let Err(e) = self.create_media_player(&resource) {
1528            error!("Create media player error {:?}", e);
1529            self.resource_selection_algorithm_failure_steps();
1530            return;
1531        }
1532
1533        // Steps 1-2.
1534        // Unapplicable, the `resource` variable already conveys which mode
1535        // is in use.
1536
1537        // Step 3.
1538        // FIXME(nox): Remove all media-resource-specific text tracks.
1539
1540        // Step 5. Run the appropriate steps from the following list:
1541        match resource {
1542            Resource::Url(url) => {
1543                // Step 5.remote.1. Optionally, run the following substeps. This is the expected
1544                // behavior if the user agent intends to not attempt to fetch the resource until the
1545                // user requests it explicitly (e.g. as a way to implement the preload attribute's
1546                // none keyword).
1547                if self.Preload() == "none" && !self.autoplaying.get() {
1548                    // Step 5.remote.1.1. Set the networkState to NETWORK_IDLE.
1549                    self.network_state.set(NetworkState::Idle);
1550
1551                    // Step 5.remote.1.2. Queue a media element task given the media element to fire
1552                    // an event named suspend at the element.
1553                    self.queue_media_element_task_to_fire_event(atom!("suspend"));
1554
1555                    // Step 5.remote.1.3. Queue a media element task given the media element to set
1556                    // the element's delaying-the-load-event flag to false. This stops delaying the
1557                    // load event.
1558                    let this = Trusted::new(self);
1559                    let generation_id = self.generation_id.get();
1560
1561                    self.owner_global()
1562                        .task_manager()
1563                        .media_element_task_source()
1564                        .queue(task!(queue_delay_load_event: move |cx| {
1565                            let this = this.root();
1566                            if generation_id != this.generation_id.get() {
1567                                return;
1568                            }
1569
1570                            this.delay_load_event(false, cx);
1571                        }));
1572
1573                    // TODO Steps 5.remote.1.4. Wait for the task to be run.
1574                    // FIXME(nox): Somehow we should wait for the task from previous
1575                    // step to be ran before continuing.
1576
1577                    // TODO Steps 5.remote.1.5-5.remote.1.7.
1578                    // FIXME(nox): Wait for an implementation-defined event and
1579                    // then continue with the normal set of steps instead of just
1580                    // returning.
1581                    return;
1582                }
1583
1584                *self.resource_url.borrow_mut() = Some(url);
1585
1586                // Steps 5.remote.2-5.remote.8
1587                self.fetch_request(None, None);
1588            },
1589            Resource::Object => {
1590                if let Some(ref src_object) = *self.src_object.borrow() {
1591                    match src_object {
1592                        SrcObject::Blob(blob) => {
1593                            let blob_url = URL::CreateObjectURL(&self.global(), blob);
1594                            *self.blob_url.borrow_mut() =
1595                                Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1596                            self.fetch_request(None, None);
1597                        },
1598                        SrcObject::MediaStream(stream) => {
1599                            let tracks = &*stream.get_tracks();
1600                            for (pos, track) in tracks.iter().enumerate() {
1601                                if self
1602                                    .player
1603                                    .borrow()
1604                                    .as_ref()
1605                                    .unwrap()
1606                                    .lock()
1607                                    .unwrap()
1608                                    .set_stream(&track.id(), pos == tracks.len() - 1)
1609                                    .is_err()
1610                                {
1611                                    self.resource_selection_algorithm_failure_steps();
1612                                }
1613                            }
1614                        },
1615                    }
1616                }
1617            },
1618        }
1619    }
1620
1621    /// Queues a task to run the [dedicated media source failure steps][steps].
1622    ///
1623    /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps
1624    fn queue_dedicated_media_source_failure_steps(&self) {
1625        let this = Trusted::new(self);
1626        let generation_id = self.generation_id.get();
1627        self.take_pending_play_promises(Err(Error::NotSupported(None)));
1628        self.owner_global()
1629            .task_manager()
1630            .media_element_task_source()
1631            .queue(task!(dedicated_media_source_failure_steps: move |cx| {
1632                let this = this.root();
1633                if generation_id != this.generation_id.get() {
1634                    return;
1635                }
1636
1637                this.fulfill_in_flight_play_promises(|| {
1638                    // Step 1. Set the error attribute to the result of creating a MediaError with
1639                    // MEDIA_ERR_SRC_NOT_SUPPORTED.
1640                    this.error.set(Some(&*MediaError::new(
1641                        &this.owner_window(),
1642                        MEDIA_ERR_SRC_NOT_SUPPORTED, CanGc::from_cx(cx))));
1643
1644                    // Step 2. Forget the media element's media-resource-specific tracks.
1645                    this.AudioTracks(cx).clear();
1646                    this.VideoTracks(CanGc::from_cx(cx)).clear();
1647
1648                    // Step 3. Set the element's networkState attribute to the NETWORK_NO_SOURCE
1649                    // value.
1650                    this.network_state.set(NetworkState::NoSource);
1651
1652                    // Step 4. Set the element's show poster flag to true.
1653                    this.show_poster.set(true);
1654
1655                    // Step 5. Fire an event named error at the media element.
1656                    this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1657
1658                    if let Some(ref player) = *this.player.borrow()
1659                        && let Err(error) = player.lock().unwrap().stop() {
1660                            error!("Could not stop player: {error:?}");
1661                        }
1662
1663                    // Step 6. Reject pending play promises with promises and a "NotSupportedError"
1664                    // DOMException.
1665                    // Done after running this closure in `fulfill_in_flight_play_promises`.
1666                });
1667
1668                // Step 7. Set the element's delaying-the-load-event flag to false. This stops
1669                // delaying the load event.
1670                this.delay_load_event(false, cx);
1671            }));
1672    }
1673
1674    fn in_error_state(&self) -> bool {
1675        self.error.get().is_some()
1676    }
1677
1678    /// <https://html.spec.whatwg.org/multipage/#potentially-playing>
1679    fn is_potentially_playing(&self) -> bool {
1680        !self.paused.get() &&
1681            !self.ended_playback(LoopCondition::Included) &&
1682            self.error.get().is_none() &&
1683            !self.is_blocked_media_element()
1684    }
1685
1686    /// <https://html.spec.whatwg.org/multipage/#blocked-media-element>
1687    fn is_blocked_media_element(&self) -> bool {
1688        self.ready_state.get() <= ReadyState::HaveCurrentData ||
1689            self.is_paused_for_user_interaction() ||
1690            self.is_paused_for_in_band_content()
1691    }
1692
1693    /// <https://html.spec.whatwg.org/multipage/#paused-for-user-interaction>
1694    fn is_paused_for_user_interaction(&self) -> bool {
1695        // FIXME: we will likely be able to fill this placeholder once (if) we
1696        //        implement the MediaSession API.
1697        false
1698    }
1699
1700    /// <https://html.spec.whatwg.org/multipage/#paused-for-in-band-content>
1701    fn is_paused_for_in_band_content(&self) -> bool {
1702        // FIXME: we will likely be able to fill this placeholder once (if) we
1703        //        implement https://github.com/servo/servo/issues/22314
1704        false
1705    }
1706
1707    /// <https://html.spec.whatwg.org/multipage/#media-element-load-algorithm>
1708    fn media_element_load_algorithm(&self, cx: &mut js::context::JSContext) {
1709        // Reset the flag that signals whether loadeddata was ever fired for
1710        // this invokation of the load algorithm.
1711        self.fired_loadeddata_event.set(false);
1712
1713        // TODO Step 1. Set this element's is currently stalled to false.
1714
1715        // Step 2. Abort any already-running instance of the resource selection algorithm for this
1716        // element.
1717        self.generation_id.set(self.generation_id.get() + 1);
1718
1719        self.load_state.set(LoadState::NotLoaded);
1720        *self.source_children_pointer.borrow_mut() = None;
1721        self.current_source_child.set(None);
1722
1723        // Step 3. Let pending tasks be a list of all tasks from the media element's media element
1724        // event task source in one of the task queues.
1725
1726        // Step 4. For each task in pending tasks that would resolve pending play promises or reject
1727        // pending play promises, immediately resolve or reject those promises in the order the
1728        // corresponding tasks were queued.
1729        while !self.in_flight_play_promises_queue.borrow().is_empty() {
1730            self.fulfill_in_flight_play_promises(|| ());
1731        }
1732
1733        // Step 5. Remove each task in pending tasks from its task queue.
1734        // Note that each media element's pending event and callback is scheduled with associated
1735        // generation id and will be aborted eventually (from Step 2).
1736
1737        let network_state = self.network_state.get();
1738
1739        // Step 6. If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE,
1740        // queue a media element task given the media element to fire an event named abort at the
1741        // media element.
1742        if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1743            self.queue_media_element_task_to_fire_event(atom!("abort"));
1744        }
1745
1746        // Reset the media player for any previously playing media resource (see Step 11).
1747        self.reset_media_player();
1748
1749        // Step 7. If the media element's networkState is not set to NETWORK_EMPTY, then:
1750        if network_state != NetworkState::Empty {
1751            // Step 7.1. Queue a media element task given the media element to fire an event named
1752            // emptied at the media element.
1753            self.queue_media_element_task_to_fire_event(atom!("emptied"));
1754
1755            // Step 7.2. If a fetching process is in progress for the media element, the user agent
1756            // should stop it.
1757            if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1758                current_fetch_context.cancel(CancelReason::Abort);
1759            }
1760
1761            // TODO Step 7.3. If the media element's assigned media provider object is a MediaSource
1762            // object, then detach it.
1763
1764            // Step 7.4. Forget the media element's media-resource-specific tracks.
1765            self.AudioTracks(cx).clear();
1766            self.VideoTracks(CanGc::from_cx(cx)).clear();
1767
1768            // Step 7.5. If readyState is not set to HAVE_NOTHING, then set it to that state.
1769            if self.ready_state.get() != ReadyState::HaveNothing {
1770                self.change_ready_state(ReadyState::HaveNothing);
1771            }
1772
1773            // Step 7.6. If the paused attribute is false, then:
1774            if !self.Paused() {
1775                // Step 7.6.1. Set the paused attribute to true.
1776                self.paused.set(true);
1777
1778                // Step 7.6.2. Take pending play promises and reject pending play promises with the
1779                // result and an "AbortError" DOMException.
1780                self.take_pending_play_promises(Err(Error::Abort(None)));
1781                self.fulfill_in_flight_play_promises(|| ());
1782            }
1783
1784            // Step 7.7. If seeking is true, set it to false.
1785            self.seeking.set(false);
1786
1787            self.current_seek_position.set(f64::NAN);
1788
1789            // Step 7.8. Set the current playback position to 0.
1790            // Set the official playback position to 0.
1791            // If this changed the official playback position, then queue a media element task given
1792            // the media element to fire an event named timeupdate at the media element.
1793            self.current_playback_position.set(0.);
1794            if self.official_playback_position.get() != 0. {
1795                self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1796            }
1797            self.official_playback_position.set(0.);
1798
1799            // TODO Step 7.9. Set the timeline offset to Not-a-Number (NaN).
1800
1801            // Step 7.10. Update the duration attribute to Not-a-Number (NaN).
1802            self.duration.set(f64::NAN);
1803        }
1804
1805        // Step 8. Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1806        self.playback_rate.set(self.default_playback_rate.get());
1807
1808        // Step 9. Set the error attribute to null and the can autoplay flag to true.
1809        self.error.set(None);
1810        self.autoplaying.set(true);
1811
1812        // Step 10. Invoke the media element's resource selection algorithm.
1813        self.invoke_resource_selection_algorithm(cx);
1814
1815        // Step 11. Note: Playback of any previously playing media resource for this element stops.
1816    }
1817
1818    /// Queue a media element task given the media element to fire an event at the media element.
1819    /// <https://html.spec.whatwg.org/multipage/#queue-a-media-element-task>
1820    fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1821        let this = Trusted::new(self);
1822        let generation_id = self.generation_id.get();
1823
1824        self.owner_global()
1825            .task_manager()
1826            .media_element_task_source()
1827            .queue(task!(queue_event: move |cx| {
1828                let this = this.root();
1829                if generation_id != this.generation_id.get() {
1830                    return;
1831                }
1832
1833                this.upcast::<EventTarget>().fire_event(cx, name);
1834            }));
1835    }
1836
1837    /// Appends a promise to the list of pending play promises.
1838    fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1839        self.pending_play_promises
1840            .borrow_mut()
1841            .push(promise.clone());
1842    }
1843
1844    /// Takes the pending play promises.
1845    ///
1846    /// The result with which these promises will be fulfilled is passed here
1847    /// and this method returns nothing because we actually just move the
1848    /// current list of pending play promises to the
1849    /// `in_flight_play_promises_queue` field.
1850    ///
1851    /// Each call to this method must be followed by a call to
1852    /// `fulfill_in_flight_play_promises`, to actually fulfill the promises
1853    /// which were taken and moved to the in-flight queue.
1854    fn take_pending_play_promises(&self, result: ErrorResult) {
1855        let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1856        self.in_flight_play_promises_queue
1857            .borrow_mut()
1858            .push_back((pending_play_promises.into(), result));
1859    }
1860
1861    /// Fulfills the next in-flight play promises queue after running a closure.
1862    ///
1863    /// See the comment on `take_pending_play_promises` for why this method
1864    /// does not take a list of promises to fulfill. Callers cannot just pop
1865    /// the front list off of `in_flight_play_promises_queue` and later fulfill
1866    /// the promises because that would mean putting
1867    /// `#[cfg_attr(crown, expect(crown::unrooted_must_root))]` on even more functions, potentially
1868    /// hiding actual safety bugs.
1869    fn fulfill_in_flight_play_promises<F>(&self, f: F)
1870    where
1871        F: FnOnce(),
1872    {
1873        let (promises, result) = self
1874            .in_flight_play_promises_queue
1875            .borrow_mut()
1876            .pop_front()
1877            .expect("there should be at least one list of in flight play promises");
1878        f();
1879        for promise in &*promises {
1880            match result {
1881                Ok(ref value) => promise.resolve_native(value, CanGc::deprecated_note()),
1882                Err(ref error) => promise.reject_error(error.clone(), CanGc::deprecated_note()),
1883            }
1884        }
1885    }
1886
1887    pub(crate) fn handle_source_child_insertion(
1888        &self,
1889        source: &HTMLSourceElement,
1890        cx: &mut js::context::JSContext,
1891    ) {
1892        // <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-insertion-steps>
1893        // Step 2. If parent is a media element that has no src attribute and whose networkState has
1894        // the value NETWORK_EMPTY, then invoke that media element's resource selection algorithm.
1895        if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1896            return;
1897        }
1898
1899        if self.network_state.get() == NetworkState::Empty {
1900            self.invoke_resource_selection_algorithm(cx);
1901            return;
1902        }
1903
1904        // <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1905        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1906        // list. (This step might wait forever.)
1907        if self.load_state.get() != LoadState::WaitingForSource {
1908            return;
1909        }
1910
1911        self.load_state.set(LoadState::LoadingFromSourceChild);
1912
1913        *self.source_children_pointer.borrow_mut() =
1914            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), true));
1915
1916        // Step 9.children.23. Await a stable state.
1917        let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1918            elem: DomRoot::from_ref(self),
1919            generation_id: self.generation_id.get(),
1920        };
1921
1922        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1923    }
1924
1925    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1926    fn select_next_source_child_after_wait(&self, cx: &mut js::context::JSContext) {
1927        // Step 9.children.24. Set the element's delaying-the-load-event flag back to true (this
1928        // delays the load event again, in case it hasn't been fired yet).
1929        self.delay_load_event(true, cx);
1930
1931        // Step 9.children.25. Set the networkState back to NETWORK_LOADING.
1932        self.network_state.set(NetworkState::Loading);
1933
1934        // Step 9.children.26. Jump back to the find next candidate step above.
1935        self.select_next_source_child(cx);
1936    }
1937
1938    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1939    /// => "If the media data cannot be fetched at all, due to network errors..."
1940    /// => "If the media data can be fetched but is found by inspection to be in an unsupported
1941    /// format, or can otherwise not be rendered at all"
1942    fn media_data_processing_failure_steps(&self) {
1943        // Step 1. The user agent should cancel the fetching process.
1944        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1945            current_fetch_context.cancel(CancelReason::Error);
1946        }
1947
1948        // Step 2. Abort this subalgorithm, returning to the resource selection algorithm.
1949        self.resource_selection_algorithm_failure_steps();
1950    }
1951
1952    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1953    /// => "If the connection is interrupted after some media data has been received..."
1954    /// => "If the media data is corrupted"
1955    fn media_data_processing_fatal_steps(&self, error: u16, cx: &mut js::context::JSContext) {
1956        *self.source_children_pointer.borrow_mut() = None;
1957        self.current_source_child.set(None);
1958
1959        // Step 1. The user agent should cancel the fetching process.
1960        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1961            current_fetch_context.cancel(CancelReason::Error);
1962        }
1963
1964        // Step 2. Set the error attribute to the result of creating a MediaError with
1965        // MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1966        self.error.set(Some(&*MediaError::new(
1967            &self.owner_window(),
1968            error,
1969            CanGc::from_cx(cx),
1970        )));
1971
1972        // Step 3. Set the element's networkState attribute to the NETWORK_IDLE value.
1973        self.network_state.set(NetworkState::Idle);
1974
1975        // Step 4. Set the element's delaying-the-load-event flag to false. This stops delaying
1976        // the load event.
1977        self.delay_load_event(false, cx);
1978
1979        // Step 5. Fire an event named error at the media element.
1980        self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1981
1982        // Step 6. Abort the overall resource selection algorithm.
1983    }
1984
1985    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
1986    fn seek(&self, time: f64, _approximate_for_speed: bool) {
1987        // Step 1. Set the media element's show poster flag to false.
1988        self.show_poster.set(false);
1989
1990        // Step 2. If the media element's readyState is HAVE_NOTHING, return.
1991        if self.ready_state.get() == ReadyState::HaveNothing {
1992            return;
1993        }
1994
1995        // Step 3. If the element's seeking IDL attribute is true, then another instance of this
1996        // algorithm is already running. Abort that other instance of the algorithm without waiting
1997        // for the step that it is running to complete.
1998        self.current_seek_position.set(f64::NAN);
1999
2000        // Step 4. Set the seeking IDL attribute to true.
2001        self.seeking.set(true);
2002
2003        // Step 5. If the seek was in response to a DOM method call or setting of an IDL attribute,
2004        // then continue the script. The remainder of these steps must be run in parallel.
2005
2006        // Step 6. If the new playback position is later than the end of the media resource, then
2007        // let it be the end of the media resource instead.
2008        let time = f64::min(time, self.Duration());
2009
2010        // Step 7. If the new playback position is less than the earliest possible position, let it
2011        // be that position instead.
2012        let time = f64::max(time, self.earliest_possible_position());
2013
2014        // Step 8. If the (possibly now changed) new playback position is not in one of the ranges
2015        // given in the seekable attribute, then let it be the position in one of the ranges given
2016        // in the seekable attribute that is the nearest to the new playback position. If there are
2017        // no ranges given in the seekable attribute, then set the seeking IDL attribute to false
2018        // and return.
2019        let seekable = self.seekable();
2020
2021        if seekable.is_empty() {
2022            self.seeking.set(false);
2023            return;
2024        }
2025
2026        let mut nearest_seekable_position = 0.0;
2027        let mut in_seekable_range = false;
2028        let mut nearest_seekable_distance = f64::MAX;
2029        for i in 0..seekable.len() {
2030            let start = seekable.start(i).unwrap().abs();
2031            let end = seekable.end(i).unwrap().abs();
2032            if time >= start && time <= end {
2033                nearest_seekable_position = time;
2034                in_seekable_range = true;
2035                break;
2036            } else if time < start {
2037                let distance = start - time;
2038                if distance < nearest_seekable_distance {
2039                    nearest_seekable_distance = distance;
2040                    nearest_seekable_position = start;
2041                }
2042            } else {
2043                let distance = time - end;
2044                if distance < nearest_seekable_distance {
2045                    nearest_seekable_distance = distance;
2046                    nearest_seekable_position = end;
2047                }
2048            }
2049        }
2050        let time = if in_seekable_range {
2051            time
2052        } else {
2053            nearest_seekable_position
2054        };
2055
2056        // Step 9. If the approximate-for-speed flag is set, adjust the new playback position to a
2057        // value that will allow for playback to resume promptly. If new playback position before
2058        // this step is before current playback position, then the adjusted new playback position
2059        // must also be before the current playback position. Similarly, if the new playback
2060        // position before this step is after current playback position, then the adjusted new
2061        // playback position must also be after the current playback position.
2062        // TODO: Note that servo-media with gstreamer does not support inaccurate seeking for now.
2063
2064        // Step 10. Queue a media element task given the media element to fire an event named
2065        // seeking at the element.
2066        self.queue_media_element_task_to_fire_event(atom!("seeking"));
2067
2068        // Step 11. Set the current playback position to the new playback position.
2069        self.current_playback_position.set(time);
2070
2071        if let Some(ref player) = *self.player.borrow() &&
2072            let Err(error) = player.lock().unwrap().seek(time)
2073        {
2074            error!("Could not seek player: {error:?}");
2075        }
2076
2077        self.current_seek_position.set(time);
2078
2079        // Step 12. Wait until the user agent has established whether or not the media data for the
2080        // new playback position is available, and, if it is, until it has decoded enough data to
2081        // play back that position.
2082        // The rest of the steps are handled when the media engine signals a ready state change or
2083        // otherwise satisfies seek completion and signals a position change.
2084    }
2085
2086    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
2087    fn seek_end(&self) {
2088        // Any time the user agent provides a stable state, the official playback position must be
2089        // set to the current playback position.
2090        self.official_playback_position
2091            .set(self.current_playback_position.get());
2092
2093        // Step 14. Set the seeking IDL attribute to false.
2094        self.seeking.set(false);
2095
2096        self.current_seek_position.set(f64::NAN);
2097
2098        // Step 15. Run the time marches on steps.
2099        self.time_marches_on();
2100
2101        // Step 16. Queue a media element task given the media element to fire an event named
2102        // timeupdate at the element.
2103        self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2104
2105        // Step 17. Queue a media element task given the media element to fire an event named seeked
2106        // at the element.
2107        self.queue_media_element_task_to_fire_event(atom!("seeked"));
2108    }
2109
2110    /// <https://html.spec.whatwg.org/multipage/#poster-frame>
2111    pub(crate) fn set_poster_frame(&self, image: Option<Arc<RasterImage>>) {
2112        if pref!(media_testing_enabled) && image.is_some() {
2113            self.queue_media_element_task_to_fire_event(atom!("postershown"));
2114        }
2115
2116        self.video_renderer.lock().unwrap().set_poster_frame(image);
2117
2118        self.upcast::<Node>().dirty(NodeDamage::Other);
2119    }
2120
2121    fn player_id(&self) -> Option<usize> {
2122        self.player
2123            .borrow()
2124            .as_ref()
2125            .map(|player| player.lock().unwrap().get_id())
2126    }
2127
2128    fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
2129        let stream_type = match *resource {
2130            Resource::Object => {
2131                if let Some(ref src_object) = *self.src_object.borrow() {
2132                    match src_object {
2133                        SrcObject::MediaStream(_) => StreamType::Stream,
2134                        _ => StreamType::Seekable,
2135                    }
2136                } else {
2137                    return Err(());
2138                }
2139            },
2140            _ => StreamType::Seekable,
2141        };
2142
2143        let window = self.owner_window();
2144        let (action_sender, action_receiver) = ipc::channel::<PlayerEvent>().unwrap();
2145        let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
2146        {
2147            HTMLMediaElementTypeId::HTMLAudioElement => None,
2148            HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
2149        };
2150
2151        let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
2152
2153        let pipeline_id = window.pipeline_id();
2154        let client_context_id =
2155            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
2156        let player = ServoMedia::get().create_player(
2157            &client_context_id,
2158            stream_type,
2159            action_sender,
2160            video_renderer,
2161            audio_renderer,
2162            Box::new(window.get_player_context()),
2163        );
2164        let player_id = {
2165            let player_guard = player.lock().unwrap();
2166
2167            if let Err(error) = player_guard.set_mute(self.muted.get()) {
2168                warn!("Could not set mute state: {error:?}");
2169            }
2170
2171            player_guard.get_id()
2172        };
2173
2174        *self.player.borrow_mut() = Some(player);
2175
2176        let event_handler = Arc::new(Mutex::new(HTMLMediaElementEventHandler::new(self)));
2177        let weak_event_handler = Arc::downgrade(&event_handler);
2178        *self.event_handler.borrow_mut() = Some(event_handler);
2179
2180        let task_source = self
2181            .owner_global()
2182            .task_manager()
2183            .media_element_task_source()
2184            .to_sendable();
2185        ROUTER.add_typed_route(
2186            action_receiver,
2187            Box::new(move |message| {
2188                let event = message.unwrap();
2189                let weak_event_handler = weak_event_handler.clone();
2190
2191                task_source.queue(task!(handle_player_event: move |cx| {
2192                    trace!("HTMLMediaElement event: {event:?}");
2193
2194                    let Some(event_handler) = weak_event_handler.upgrade() else {
2195                        return;
2196                    };
2197
2198                    event_handler.lock().unwrap().handle_player_event(player_id, event, cx);
2199                }));
2200            }),
2201        );
2202
2203        let task_source = self
2204            .owner_global()
2205            .task_manager()
2206            .media_element_task_source()
2207            .to_sendable();
2208        let weak_video_renderer = Arc::downgrade(&self.video_renderer);
2209
2210        self.video_renderer
2211            .lock()
2212            .unwrap()
2213            .setup(player_id, task_source, weak_video_renderer);
2214
2215        Ok(())
2216    }
2217
2218    fn reset_media_player(&self) {
2219        if self.player.borrow().is_none() {
2220            return;
2221        }
2222
2223        if let Some(ref player) = *self.player.borrow() &&
2224            let Err(error) = player.lock().unwrap().stop()
2225        {
2226            error!("Could not stop player: {error:?}");
2227        }
2228
2229        *self.player.borrow_mut() = None;
2230        self.video_renderer.lock().unwrap().reset();
2231        *self.event_handler.borrow_mut() = None;
2232
2233        if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2234            video_element.set_natural_dimensions(None, None);
2235        }
2236    }
2237
2238    pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
2239        if let Some(ref player) = *self.player.borrow() &&
2240            let Err(error) = player.lock().unwrap().set_audio_track(idx as i32, enabled)
2241        {
2242            warn!("Could not set audio track {error:?}");
2243        }
2244    }
2245
2246    pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
2247        if let Some(ref player) = *self.player.borrow() &&
2248            let Err(error) = player.lock().unwrap().set_video_track(idx as i32, enabled)
2249        {
2250            warn!("Could not set video track: {error:?}");
2251        }
2252    }
2253
2254    /// <https://html.spec.whatwg.org/multipage/#direction-of-playback>
2255    fn direction_of_playback(&self) -> PlaybackDirection {
2256        // If the element's playbackRate is positive or zero, then the direction of playback is
2257        // forwards. Otherwise, it is backwards.
2258        if self.playback_rate.get() >= 0. {
2259            PlaybackDirection::Forwards
2260        } else {
2261            PlaybackDirection::Backwards
2262        }
2263    }
2264
2265    /// <https://html.spec.whatwg.org/multipage/#ended-playback>
2266    fn ended_playback(&self, loop_condition: LoopCondition) -> bool {
2267        // A media element is said to have ended playback when:
2268
2269        // The element's readyState attribute is HAVE_METADATA or greater, and
2270        if self.ready_state.get() < ReadyState::HaveMetadata {
2271            return false;
2272        }
2273
2274        let playback_position = self.current_playback_position.get();
2275
2276        match self.direction_of_playback() {
2277            // Either: The current playback position is the end of the media resource, and the
2278            // direction of playback is forwards, and the media element does not have a loop
2279            // attribute specified.
2280            PlaybackDirection::Forwards => {
2281                playback_position >= self.Duration() &&
2282                    (loop_condition == LoopCondition::Ignored || !self.Loop())
2283            },
2284            // Or: The current playback position is the earliest possible position, and the
2285            // direction of playback is backwards.
2286            PlaybackDirection::Backwards => playback_position <= self.earliest_possible_position(),
2287        }
2288    }
2289
2290    /// <https://html.spec.whatwg.org/multipage/#reaches-the-end>
2291    fn end_of_playback_in_forwards_direction(&self) {
2292        // When the current playback position reaches the end of the media resource when the
2293        // direction of playback is forwards, then the user agent must follow these steps:
2294
2295        // Step 1. If the media element has a loop attribute specified, then seek to the earliest
2296        // posible position of the media resource and return.
2297        if self.Loop() {
2298            self.seek(
2299                self.earliest_possible_position(),
2300                /* approximate_for_speed */ false,
2301            );
2302            return;
2303        }
2304
2305        // Step 2. As defined above, the ended IDL attribute starts returning true once the event
2306        // loop returns to step 1.
2307
2308        // Step 3. Queue a media element task given the media element and the following steps:
2309        let this = Trusted::new(self);
2310        let generation_id = self.generation_id.get();
2311
2312        self.owner_global()
2313            .task_manager()
2314            .media_element_task_source()
2315            .queue(task!(reaches_the_end_steps: move |cx| {
2316                let this = this.root();
2317                if generation_id != this.generation_id.get() {
2318                    return;
2319                }
2320
2321                // Step 3.1. Fire an event named timeupdate at the media element.
2322                this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
2323
2324                // Step 3.2. If the media element has ended playback, the direction of playback is
2325                // forwards, and paused is false, then:
2326                if this.ended_playback(LoopCondition::Included) &&
2327                    this.direction_of_playback() == PlaybackDirection::Forwards &&
2328                    !this.Paused() {
2329                    // Step 3.2.1. Set the paused attribute to true.
2330                    this.paused.set(true);
2331
2332                    // Step 3.2.2. Fire an event named pause at the media element.
2333                    this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
2334
2335                    // Step 3.2.3. Take pending play promises and reject pending play promises with
2336                    // the result and an "AbortError" DOMException.
2337                    this.take_pending_play_promises(Err(Error::Abort(None)));
2338                    this.fulfill_in_flight_play_promises(|| ());
2339                }
2340
2341                // Step 3.3. Fire an event named ended at the media element.
2342                this.upcast::<EventTarget>().fire_event(cx, atom!("ended"));
2343            }));
2344
2345        // <https://html.spec.whatwg.org/multipage/#dom-media-have_current_data>
2346        self.change_ready_state(ReadyState::HaveCurrentData);
2347    }
2348
2349    /// <https://html.spec.whatwg.org/multipage/#reaches-the-end>
2350    fn end_of_playback_in_backwards_direction(&self) {
2351        // When the current playback position reaches the earliest possible position of the media
2352        // resource when the direction of playback is backwards, then the user agent must only queue
2353        // a media element task given the media element to fire an event named timeupdate at the
2354        // element.
2355        if self.current_playback_position.get() <= self.earliest_possible_position() {
2356            self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2357        }
2358    }
2359
2360    fn playback_end(&self) {
2361        // Abort the following steps of the end of playback if seeking is in progress.
2362        if self.seeking.get() {
2363            return;
2364        }
2365
2366        match self.direction_of_playback() {
2367            PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(),
2368            PlaybackDirection::Backwards => self.end_of_playback_in_backwards_direction(),
2369        }
2370    }
2371
2372    fn playback_error(&self, error: &str, cx: &mut js::context::JSContext) {
2373        error!("Player error: {:?}", error);
2374
2375        // If we have already flagged an error condition while processing
2376        // the network response, we should silently skip any observable
2377        // errors originating while decoding the erroneous response.
2378        if self.in_error_state() {
2379            return;
2380        }
2381
2382        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
2383        if self.ready_state.get() == ReadyState::HaveNothing {
2384            // => "If the media data can be fetched but is found by inspection to be in an
2385            // unsupported format, or can otherwise not be rendered at all"
2386            self.media_data_processing_failure_steps();
2387        } else {
2388            // => "If the media data is corrupted"
2389            self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, cx);
2390        }
2391    }
2392
2393    fn playback_metadata_updated(
2394        &self,
2395        cx: &mut JSContext,
2396        metadata: &servo_media::player::metadata::Metadata,
2397    ) {
2398        // The following steps should be run once on the initial `metadata` signal from the media
2399        // engine.
2400        if self.ready_state.get() != ReadyState::HaveNothing {
2401            return;
2402        }
2403
2404        // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list
2405        // => "If the media resource is found to have an audio track"
2406        for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2407            let audio_track_list = self.AudioTracks(cx);
2408
2409            // Step 1. Create an AudioTrack object to represent the audio track.
2410            let kind = match i {
2411                0 => DOMString::from("main"),
2412                _ => DOMString::new(),
2413            };
2414
2415            let audio_track = AudioTrack::new(
2416                cx,
2417                self.global().as_window(),
2418                DOMString::new(),
2419                kind,
2420                DOMString::new(),
2421                DOMString::new(),
2422                Some(&*audio_track_list),
2423            );
2424
2425            // Steps 2. Update the media element's audioTracks attribute's AudioTrackList object
2426            // with the new AudioTrack object.
2427            audio_track_list.add(&audio_track);
2428
2429            // Step 3. Let enable be unknown.
2430            // Step 4. If either the media resource or the URL of the current media resource
2431            // indicate a particular set of audio tracks to enable, or if the user agent has
2432            // information that would facilitate the selection of specific audio tracks to
2433            // improve the user's experience, then: if this audio track is one of the ones to
2434            // enable, then set enable to true, otherwise, set enable to false.
2435            if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2436                let fragment = MediaFragmentParser::from(servo_url);
2437                if let Some(id) = fragment.id() &&
2438                    audio_track.id() == id
2439                {
2440                    audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2441                }
2442
2443                if fragment.tracks().contains(&audio_track.kind().into()) {
2444                    audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2445                }
2446            }
2447
2448            // Step 5. If enable is still unknown, then, if the media element does not yet have an
2449            // enabled audio track, then set enable to true, otherwise, set enable to false.
2450            // Step 6. If enable is true, then enable this audio track, otherwise, do not enable
2451            // this audio track.
2452            if audio_track_list.enabled_index().is_none() {
2453                audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2454            }
2455
2456            // Step 7. Fire an event named addtrack at this AudioTrackList object, using TrackEvent,
2457            // with the track attribute initialized to the new AudioTrack object.
2458            let event = TrackEvent::new(
2459                self.global().as_window(),
2460                atom!("addtrack"),
2461                false,
2462                false,
2463                &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2464                CanGc::from_cx(cx),
2465            );
2466
2467            event
2468                .upcast::<Event>()
2469                .fire(cx, audio_track_list.upcast::<EventTarget>());
2470        }
2471
2472        // => "If the media resource is found to have a video track"
2473        for (i, _track) in metadata.video_tracks.iter().enumerate() {
2474            let video_track_list = self.VideoTracks(CanGc::from_cx(cx));
2475
2476            // Step 1. Create a VideoTrack object to represent the video track.
2477            let kind = match i {
2478                0 => DOMString::from("main"),
2479                _ => DOMString::new(),
2480            };
2481
2482            let video_track = VideoTrack::new(
2483                self.global().as_window(),
2484                DOMString::new(),
2485                kind,
2486                DOMString::new(),
2487                DOMString::new(),
2488                Some(&*video_track_list),
2489                CanGc::from_cx(cx),
2490            );
2491
2492            // Steps 2. Update the media element's videoTracks attribute's VideoTrackList object
2493            // with the new VideoTrack object.
2494            video_track_list.add(&video_track);
2495
2496            // Step 3. Let enable be unknown.
2497            // Step 4. If either the media resource or the URL of the current media resource
2498            // indicate a particular set of video tracks to enable, or if the user agent has
2499            // information that would facilitate the selection of specific video tracks to
2500            // improve the user's experience, then: if this video track is the first such video
2501            // track, then set enable to true, otherwise, set enable to false.
2502            if let Some(track) = video_track_list.item(0) &&
2503                let Some(servo_url) = self.resource_url.borrow().as_ref()
2504            {
2505                let fragment = MediaFragmentParser::from(servo_url);
2506                if let Some(id) = fragment.id() {
2507                    if track.id() == id {
2508                        video_track_list.set_selected(0, true);
2509                    }
2510                } else if fragment.tracks().contains(&track.kind().into()) {
2511                    video_track_list.set_selected(0, true);
2512                }
2513            }
2514
2515            // Step 5. If enable is still unknown, then, if the media element does not yet have a
2516            // selected video track, then set enable to true, otherwise, set enable to false.
2517            // Step 6. If enable is true, then select this track and unselect any previously
2518            // selected video tracks, otherwise, do not select this video track. If other tracks are
2519            // unselected, then a change event will be fired.
2520            if video_track_list.selected_index().is_none() {
2521                video_track_list.set_selected(video_track_list.len() - 1, true);
2522            }
2523
2524            // Step 7. Fire an event named addtrack at this VideoTrackList object, using TrackEvent,
2525            // with the track attribute initialized to the new VideoTrack object.
2526            let event = TrackEvent::new(
2527                self.global().as_window(),
2528                atom!("addtrack"),
2529                false,
2530                false,
2531                &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2532                CanGc::from_cx(cx),
2533            );
2534
2535            event
2536                .upcast::<Event>()
2537                .fire(cx, video_track_list.upcast::<EventTarget>());
2538        }
2539
2540        // => "Once enough of the media data has been fetched to determine the duration..."
2541
2542        // TODO Step 1. Establish the media timeline for the purposes of the current playback
2543        // position and the earliest possible position, based on the media data.
2544
2545        // TODO Step 2. Update the timeline offset to the date and time that corresponds to the zero
2546        // time in the media timeline established in the previous step, if any. If no explicit time
2547        // and date is given by the media resource, the timeline offset must be set to Not-a-Number
2548        // (NaN).
2549
2550        // Step 3. Set the current playback position and the official playback position to the
2551        // earliest possible position.
2552        let earliest_possible_position = self.earliest_possible_position();
2553        self.current_playback_position
2554            .set(earliest_possible_position);
2555        self.official_playback_position
2556            .set(earliest_possible_position);
2557
2558        // Step 4. Update the duration attribute with the time of the last frame of the resource, if
2559        // known, on the media timeline established above. If it is not known (e.g. a stream that is
2560        // in principle infinite), update the duration attribute to the value positive Infinity.
2561        // Note: The user agent will queue a media element task given the media element to fire an
2562        // event named durationchange at the element at this point.
2563        self.duration.set(
2564            metadata
2565                .duration
2566                .map_or(f64::INFINITY, |duration| duration.as_secs_f64()),
2567        );
2568        self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2569
2570        // Step 5. For video elements, set the videoWidth and videoHeight attributes, and queue a
2571        // media element task given the media element to fire an event named resize at the media
2572        // element.
2573        if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2574            video_element.set_natural_dimensions(Some(metadata.width), Some(metadata.height));
2575            self.queue_media_element_task_to_fire_event(atom!("resize"));
2576        }
2577
2578        // Step 6. Set the readyState attribute to HAVE_METADATA.
2579        self.change_ready_state(ReadyState::HaveMetadata);
2580
2581        // Step 7. Let jumped be false.
2582        let mut jumped = false;
2583
2584        // Step 8. If the media element's default playback start position is greater than zero, then
2585        // seek to that time, and let jumped be true.
2586        if self.default_playback_start_position.get() > 0. {
2587            self.seek(
2588                self.default_playback_start_position.get(),
2589                /* approximate_for_speed */ false,
2590            );
2591            jumped = true;
2592        }
2593
2594        // Step 9. Set the media element's default playback start position to zero.
2595        self.default_playback_start_position.set(0.);
2596
2597        // Step 10. Let the initial playback position be 0.
2598        // Step 11. If either the media resource or the URL of the current media resource indicate a
2599        // particular start time, then set the initial playback position to that time and, if jumped
2600        // is still false, seek to that time.
2601        if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2602            let fragment = MediaFragmentParser::from(servo_url);
2603            if let Some(initial_playback_position) = fragment.start() &&
2604                initial_playback_position > 0. &&
2605                initial_playback_position < self.duration.get() &&
2606                !jumped
2607            {
2608                self.seek(
2609                    initial_playback_position,
2610                    /* approximate_for_speed */ false,
2611                )
2612            }
2613        }
2614
2615        // Step 12. If there is no enabled audio track, then enable an audio track. This will cause
2616        // a change event to be fired.
2617        // Step 13. If there is no selected video track, then select a video track. This will cause
2618        // a change event to be fired.
2619        // Note that these steps are already handled by the earlier media track processing.
2620
2621        let global = self.global();
2622        let window = global.as_window();
2623
2624        // Update the media session metadata title with the obtained metadata.
2625        window.Navigator().MediaSession().update_title(
2626            metadata
2627                .title
2628                .clone()
2629                .unwrap_or(window.get_url().into_string()),
2630        );
2631    }
2632
2633    fn playback_duration_changed(&self, duration: Option<Duration>) {
2634        let duration = duration.map_or(f64::INFINITY, |duration| duration.as_secs_f64());
2635
2636        if self.duration.get() == duration {
2637            return;
2638        }
2639
2640        self.duration.set(duration);
2641
2642        // When the length of the media resource changes to a known value (e.g. from being unknown
2643        // to known, or from a previously established length to a new length), the user agent must
2644        // queue a media element task given the media element to fire an event named durationchange
2645        // at the media element.
2646        // <https://html.spec.whatwg.org/multipage/#offsets-into-the-media-resource:media-resource-22>
2647        self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2648
2649        // If the duration is changed such that the current playback position ends up being greater
2650        // than the time of the end of the media resource, then the user agent must also seek to the
2651        // time of the end of the media resource.
2652        if self.current_playback_position.get() > duration {
2653            self.seek(duration, /* approximate_for_speed */ false);
2654        }
2655    }
2656
2657    fn playback_video_frame_updated(&self) {
2658        let Some(video_element) = self.downcast::<HTMLVideoElement>() else {
2659            return;
2660        };
2661
2662        // Whenever the natural width or natural height of the video changes (including, for
2663        // example, because the selected video track was changed), if the element's readyState
2664        // attribute is not HAVE_NOTHING, the user agent must queue a media element task given
2665        // the media element to fire an event named resize at the media element.
2666        // <https://html.spec.whatwg.org/multipage/#concept-video-intrinsic-width>
2667
2668        // The event for the prerolled frame from media engine could reached us before the media
2669        // element HAVE_METADATA ready state so subsequent steps will be cancelled.
2670        if self.ready_state.get() == ReadyState::HaveNothing {
2671            return;
2672        }
2673
2674        if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2675            if video_element
2676                .set_natural_dimensions(Some(frame.width as u32), Some(frame.height as u32))
2677            {
2678                self.queue_media_element_task_to_fire_event(atom!("resize"));
2679            } else {
2680                // If the natural dimensions have not been changed, the node should be marked as
2681                // damaged to force a repaint with the new frame contents.
2682                self.upcast::<Node>().dirty(NodeDamage::Other);
2683            }
2684        }
2685    }
2686
2687    fn playback_need_data(&self) {
2688        // The media engine signals that the source needs more data. If we already have a valid
2689        // fetch request, we do nothing. Otherwise, if we have no request and the previous request
2690        // was cancelled because we got an EnoughData event, we restart fetching where we left.
2691        if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() &&
2692            let Some(reason) = current_fetch_context.cancel_reason()
2693        {
2694            // XXX(ferjm) Ideally we should just create a fetch request from
2695            // where we left. But keeping track of the exact next byte that the
2696            // media backend expects is not the easiest task, so I'm simply
2697            // seeking to the current playback position for now which will create
2698            // a new fetch request for the last rendered frame.
2699            if *reason == CancelReason::Backoff {
2700                self.seek(
2701                    self.current_playback_position.get(),
2702                    /* approximate_for_speed */ false,
2703                );
2704            }
2705            return;
2706        }
2707
2708        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2709            let Err(e) = {
2710                let mut data_source = current_fetch_context.data_source().borrow_mut();
2711                data_source.set_locked(false);
2712                data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2713            }
2714        {
2715            // If we are pushing too much data and we know that we can
2716            // restart the download later from where we left, we cancel
2717            // the current request. Otherwise, we continue the request
2718            // assuming that we may drop some frames.
2719            if e == PlayerError::EnoughData {
2720                current_fetch_context.cancel(CancelReason::Backoff);
2721            }
2722        }
2723    }
2724
2725    fn playback_enough_data(&self) {
2726        // The media engine signals that the source has enough data and asks us to stop pushing bytes
2727        // to avoid excessive buffer queueing, so we cancel the ongoing fetch request if we are able
2728        // to restart it from where we left. Otherwise, we continue the current fetch request,
2729        // assuming that some frames will be dropped.
2730        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2731            current_fetch_context.is_seekable()
2732        {
2733            current_fetch_context.cancel(CancelReason::Backoff);
2734        }
2735    }
2736
2737    fn playback_position_changed(&self, position: f64) {
2738        // Abort the following steps of the current time update if seeking is in progress.
2739        if self.seeking.get() {
2740            return;
2741        }
2742
2743        let _ = self
2744            .played
2745            .borrow_mut()
2746            .add(self.current_playback_position.get(), position);
2747        self.current_playback_position.set(position);
2748        self.official_playback_position.set(position);
2749        self.time_marches_on();
2750
2751        let media_position_state =
2752            MediaPositionState::new(self.duration.get(), self.playback_rate.get(), position);
2753        debug!(
2754            "Sending media session event set position state {:?}",
2755            media_position_state
2756        );
2757        self.send_media_session_event(MediaSessionEvent::SetPositionState(media_position_state));
2758    }
2759
2760    fn playback_seek_done(&self, position: f64) {
2761        // If the seek was initiated by script or by the user agent itself continue with the
2762        // following steps, otherwise abort.
2763        let delta = (position - self.current_seek_position.get()).abs();
2764        if !self.seeking.get() || delta > SEEK_POSITION_THRESHOLD {
2765            return;
2766        }
2767
2768        // <https://html.spec.whatwg.org/multipage/#dom-media-seek>
2769        // Step 13. Await a stable state.
2770        let task = MediaElementMicrotask::Seeked {
2771            elem: DomRoot::from_ref(self),
2772            generation_id: self.generation_id.get(),
2773        };
2774
2775        ScriptThread::await_stable_state(Microtask::MediaElement(task));
2776    }
2777
2778    fn playback_state_changed(&self, state: &PlaybackState) {
2779        let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2780        match *state {
2781            PlaybackState::Paused => {
2782                media_session_playback_state = MediaSessionPlaybackState::Paused;
2783                if self.ready_state.get() == ReadyState::HaveMetadata {
2784                    self.change_ready_state(ReadyState::HaveEnoughData);
2785                }
2786            },
2787            PlaybackState::Playing => {
2788                media_session_playback_state = MediaSessionPlaybackState::Playing;
2789                if self.ready_state.get() == ReadyState::HaveMetadata {
2790                    self.change_ready_state(ReadyState::HaveEnoughData);
2791                }
2792            },
2793            PlaybackState::Buffering => {
2794                // Do not send the media session playback state change event
2795                // in this case as a None_ state is expected to clean up the
2796                // session.
2797                return;
2798            },
2799            _ => {},
2800        };
2801        debug!(
2802            "Sending media session event playback state changed to {:?}",
2803            media_session_playback_state
2804        );
2805        self.send_media_session_event(MediaSessionEvent::PlaybackStateChange(
2806            media_session_playback_state,
2807        ));
2808    }
2809
2810    fn seekable(&self) -> TimeRangesContainer {
2811        let mut seekable = TimeRangesContainer::default();
2812        if let Some(ref player) = *self.player.borrow() {
2813            let ranges = player.lock().unwrap().seekable();
2814            for range in ranges {
2815                let _ = seekable.add(range.start, range.end);
2816            }
2817        }
2818        seekable
2819    }
2820
2821    /// <https://html.spec.whatwg.org/multipage/#earliest-possible-position>
2822    fn earliest_possible_position(&self) -> f64 {
2823        self.seekable()
2824            .start(0)
2825            .unwrap_or_else(|_| self.current_playback_position.get())
2826    }
2827
2828    fn render_controls(&self, cx: &mut JSContext) {
2829        if self.upcast::<Element>().is_shadow_host() {
2830            // Bail out if we are already showing the controls.
2831            return;
2832        }
2833
2834        // FIXME(stevennovaryo): Recheck styling of media element to avoid
2835        //                       reparsing styles.
2836        let shadow_root = self.upcast::<Element>().attach_ua_shadow_root(cx, false);
2837        let document = self.owner_document();
2838        let script = Element::create(
2839            cx,
2840            QualName::new(None, ns!(html), local_name!("script")),
2841            None,
2842            &document,
2843            ElementCreator::ScriptCreated,
2844            CustomElementCreationMode::Asynchronous,
2845            None,
2846        );
2847        // This is our hacky way to temporarily workaround the lack of a privileged
2848        // JS context.
2849        // The media controls UI accesses the document.servoGetMediaControls(id) API
2850        // to get an instance to the media controls ShadowRoot.
2851        // `id` needs to match the internally generated UUID assigned to a media element.
2852        let id = Uuid::new_v4().to_string();
2853        document.register_media_controls(&id, &shadow_root);
2854        let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2855        *self.media_controls_id.borrow_mut() = Some(id);
2856        script
2857            .upcast::<Node>()
2858            .set_text_content_for_element(cx, Some(DOMString::from(media_controls_script)));
2859        if let Err(e) = shadow_root
2860            .upcast::<Node>()
2861            .AppendChild(cx, script.upcast::<Node>())
2862        {
2863            warn!("Could not render media controls {:?}", e);
2864            return;
2865        }
2866
2867        let style = Element::create(
2868            cx,
2869            QualName::new(None, ns!(html), local_name!("style")),
2870            None,
2871            &document,
2872            ElementCreator::ScriptCreated,
2873            CustomElementCreationMode::Asynchronous,
2874            None,
2875        );
2876
2877        style
2878            .upcast::<Node>()
2879            .set_text_content_for_element(cx, Some(DOMString::from(MEDIA_CONTROL_CSS)));
2880
2881        if let Err(e) = shadow_root
2882            .upcast::<Node>()
2883            .AppendChild(cx, style.upcast::<Node>())
2884        {
2885            warn!("Could not render media controls {:?}", e);
2886        }
2887
2888        self.upcast::<Node>().dirty(NodeDamage::Other);
2889    }
2890
2891    fn remove_controls(&self) {
2892        if let Some(id) = self.media_controls_id.borrow_mut().take() {
2893            self.owner_document().unregister_media_controls(&id);
2894        }
2895    }
2896
2897    /// Gets the video frame at the current playback position.
2898    pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2899        self.video_renderer
2900            .lock()
2901            .unwrap()
2902            .current_frame_holder
2903            .as_ref()
2904            .map(|holder| holder.get_frame())
2905    }
2906
2907    /// Gets the current frame of the video element to present, if any.
2908    /// <https://html.spec.whatwg.org/multipage/#the-video-element:the-video-element-7>
2909    pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2910        let (current_frame, poster_frame) = {
2911            let renderer = self.video_renderer.lock().unwrap();
2912            (renderer.current_frame, renderer.poster_frame)
2913        };
2914
2915        // If the show poster flag is set (or there is no current video frame to
2916        // present) AND there is a poster frame, present that.
2917        if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2918            return poster_frame;
2919        }
2920
2921        current_frame
2922    }
2923
2924    /// By default the audio is rendered through the audio sink automatically
2925    /// selected by the servo-media Player instance. However, in some cases, like
2926    /// the WebAudio MediaElementAudioSourceNode, we need to set a custom audio
2927    /// renderer.
2928    pub(crate) fn set_audio_renderer(
2929        &self,
2930        audio_renderer: Option<Arc<Mutex<dyn AudioRenderer>>>,
2931        cx: &mut js::context::JSContext,
2932    ) {
2933        *self.audio_renderer.borrow_mut() = audio_renderer;
2934
2935        let had_player = {
2936            if let Some(ref player) = *self.player.borrow() {
2937                if let Err(error) = player.lock().unwrap().stop() {
2938                    error!("Could not stop player: {error:?}");
2939                }
2940                true
2941            } else {
2942                false
2943            }
2944        };
2945
2946        if had_player {
2947            self.media_element_load_algorithm(cx);
2948        }
2949    }
2950
2951    fn send_media_session_event(&self, event: MediaSessionEvent) {
2952        let global = self.global();
2953        let media_session = global.as_window().Navigator().MediaSession();
2954
2955        media_session.register_media_instance(self);
2956
2957        media_session.send_event(event);
2958    }
2959
2960    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
2961    pub(crate) fn origin_is_clean(&self) -> bool {
2962        // Step 5.local (media provider object).
2963        if self.src_object.borrow().is_some() {
2964            // The resource described by the current media resource, if any,
2965            // contains the media data. It is CORS-same-origin.
2966            return true;
2967        }
2968
2969        // Step 5.remote (URL record).
2970        if self.resource_url.borrow().is_some() {
2971            // Update the media data with the contents
2972            // of response's unsafe response obtained in this fashion.
2973            // Response can be CORS-same-origin or CORS-cross-origin;
2974            if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2975                return current_fetch_context.origin_is_clean();
2976            }
2977        }
2978
2979        true
2980    }
2981}
2982
2983impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
2984    /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
2985    fn NetworkState(&self) -> u16 {
2986        self.network_state.get() as u16
2987    }
2988
2989    /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
2990    fn ReadyState(&self) -> u16 {
2991        self.ready_state.get() as u16
2992    }
2993
2994    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2995    make_bool_getter!(Autoplay, "autoplay");
2996    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2997    make_bool_setter!(SetAutoplay, "autoplay");
2998
2999    // https://html.spec.whatwg.org/multipage/#attr-media-loop
3000    make_bool_getter!(Loop, "loop");
3001    // https://html.spec.whatwg.org/multipage/#attr-media-loop
3002    make_bool_setter!(SetLoop, "loop");
3003
3004    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
3005    make_bool_getter!(DefaultMuted, "muted");
3006    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
3007    make_bool_setter!(SetDefaultMuted, "muted");
3008
3009    // https://html.spec.whatwg.org/multipage/#dom-media-controls
3010    make_bool_getter!(Controls, "controls");
3011    // https://html.spec.whatwg.org/multipage/#dom-media-controls
3012    make_bool_setter!(SetControls, "controls");
3013
3014    // https://html.spec.whatwg.org/multipage/#dom-media-src
3015    make_url_getter!(Src, "src");
3016
3017    // https://html.spec.whatwg.org/multipage/#dom-media-src
3018    make_url_setter!(SetSrc, "src");
3019
3020    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
3021    fn GetCrossOrigin(&self) -> Option<DOMString> {
3022        reflect_cross_origin_attribute(self.upcast::<Element>())
3023    }
3024    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
3025    fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
3026        set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
3027    }
3028
3029    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3030    fn Muted(&self) -> bool {
3031        self.muted.get()
3032    }
3033
3034    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3035    fn SetMuted(&self, _cx: &mut JSContext, value: bool) {
3036        if self.muted.get() == value {
3037            return;
3038        }
3039
3040        self.muted.set(value);
3041
3042        if let Some(ref player) = *self.player.borrow() &&
3043            let Err(error) = player.lock().unwrap().set_mute(value)
3044        {
3045            warn!("Could not set mute state: {error:?}");
3046        }
3047
3048        // The user agent must queue a media element task given the media element to fire an event
3049        // named volumechange at the media element.
3050        self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3051
3052        // Then, if the media element is not allowed to play, the user agent must run the internal
3053        // pause steps for the media element.
3054        if !self.is_allowed_to_play() {
3055            self.internal_pause_steps();
3056        }
3057    }
3058
3059    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
3060    fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
3061        (*self.src_object.borrow())
3062            .as_ref()
3063            .map(|src_object| match src_object {
3064                SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
3065                SrcObject::MediaStream(stream) => {
3066                    MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
3067                },
3068            })
3069    }
3070
3071    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
3072    fn SetSrcObject(&self, cx: &mut js::context::JSContext, value: Option<MediaStreamOrBlob>) {
3073        *self.src_object.borrow_mut() = value.map(|value| value.into());
3074        self.media_element_load_algorithm(cx);
3075    }
3076
3077    // https://html.spec.whatwg.org/multipage/#attr-media-preload
3078    // Missing/Invalid values are user-agent defined.
3079    make_enumerated_getter!(
3080        Preload,
3081        "preload",
3082        "none" | "metadata" | "auto",
3083        missing => "auto",
3084        invalid => "auto"
3085    );
3086
3087    // https://html.spec.whatwg.org/multipage/#attr-media-preload
3088    make_setter!(SetPreload, "preload");
3089
3090    /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc>
3091    fn CurrentSrc(&self) -> USVString {
3092        USVString(self.current_src.borrow().clone())
3093    }
3094
3095    /// <https://html.spec.whatwg.org/multipage/#dom-media-load>
3096    fn Load(&self, cx: &mut js::context::JSContext) {
3097        self.media_element_load_algorithm(cx);
3098    }
3099
3100    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype>
3101    fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
3102        match ServoMedia::get().can_play_type(&type_.str()) {
3103            SupportsMediaType::No => CanPlayTypeResult::_empty,
3104            SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
3105            SupportsMediaType::Probably => CanPlayTypeResult::Probably,
3106        }
3107    }
3108
3109    /// <https://html.spec.whatwg.org/multipage/#dom-media-error>
3110    fn GetError(&self) -> Option<DomRoot<MediaError>> {
3111        self.error.get()
3112    }
3113
3114    /// <https://html.spec.whatwg.org/multipage/#dom-media-play>
3115    fn Play(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
3116        let promise = Promise::new_in_realm(cx);
3117
3118        // TODO Step 1. If the media element is not allowed to play, then return a promise rejected
3119        // with a "NotAllowedError" DOMException.
3120
3121        // Step 2. If the media element's error attribute is not null and its code is
3122        // MEDIA_ERR_SRC_NOT_SUPPORTED, then return a promise rejected with a "NotSupportedError"
3123        // DOMException.
3124        if self
3125            .error
3126            .get()
3127            .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
3128        {
3129            promise.reject_error_with_cx(cx, Error::NotSupported(None));
3130            return promise;
3131        }
3132
3133        // Step 3. Let promise be a new promise and append promise to the list of pending play
3134        // promises.
3135        self.push_pending_play_promise(&promise);
3136
3137        // Step 4. Run the internal play steps for the media element.
3138        self.internal_play_steps(cx);
3139
3140        // Step 5. Return promise.
3141        promise
3142    }
3143
3144    /// <https://html.spec.whatwg.org/multipage/#dom-media-pause>
3145    fn Pause(&self, cx: &mut js::context::JSContext) {
3146        // Step 1. If the media element's networkState attribute has the value NETWORK_EMPTY, invoke
3147        // the media element's resource selection algorithm.
3148        if self.network_state.get() == NetworkState::Empty {
3149            self.invoke_resource_selection_algorithm(cx);
3150        }
3151
3152        // Step 2. Run the internal pause steps for the media element.
3153        self.internal_pause_steps();
3154    }
3155
3156    /// <https://html.spec.whatwg.org/multipage/#dom-media-paused>
3157    fn Paused(&self) -> bool {
3158        self.paused.get()
3159    }
3160
3161    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
3162    fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
3163        Ok(Finite::wrap(self.default_playback_rate.get()))
3164    }
3165
3166    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
3167    fn SetDefaultPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3168        // If the given value is not supported by the user agent, then throw a "NotSupportedError"
3169        // DOMException.
3170        let min_allowed = -64.0;
3171        let max_allowed = 64.0;
3172        if *value < min_allowed || *value > max_allowed {
3173            return Err(Error::NotSupported(None));
3174        }
3175
3176        if self.default_playback_rate.get() == *value {
3177            return Ok(());
3178        }
3179
3180        self.default_playback_rate.set(*value);
3181
3182        // The user agent must queue a media element task given the media element to fire an event
3183        // named ratechange at the media element.
3184        self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3185
3186        Ok(())
3187    }
3188
3189    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
3190    fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
3191        Ok(Finite::wrap(self.playback_rate.get()))
3192    }
3193
3194    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
3195    fn SetPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3196        // The attribute is mutable: on setting, the user agent must follow these steps:
3197
3198        // Step 1. If the given value is not supported by the user agent, then throw a
3199        // "NotSupportedError" DOMException.
3200        let min_allowed = -64.0;
3201        let max_allowed = 64.0;
3202        if *value < min_allowed || *value > max_allowed {
3203            return Err(Error::NotSupported(None));
3204        }
3205
3206        if self.playback_rate.get() == *value {
3207            return Ok(());
3208        }
3209
3210        // Step 2. Set playbackRate to the new value, and if the element is potentially playing,
3211        // change the playback speed.
3212        self.playback_rate.set(*value);
3213
3214        if self.is_potentially_playing() &&
3215            let Some(ref player) = *self.player.borrow() &&
3216            let Err(error) = player.lock().unwrap().set_playback_rate(*value)
3217        {
3218            warn!("Could not set the playback rate: {error:?}");
3219        }
3220
3221        // The user agent must queue a media element task given the media element to fire an event
3222        // named ratechange at the media element.
3223        self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3224
3225        Ok(())
3226    }
3227
3228    /// <https://html.spec.whatwg.org/multipage/#dom-media-duration>
3229    fn Duration(&self) -> f64 {
3230        self.duration.get()
3231    }
3232
3233    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
3234    fn CurrentTime(&self) -> Finite<f64> {
3235        Finite::wrap(if self.default_playback_start_position.get() != 0. {
3236            self.default_playback_start_position.get()
3237        } else if self.seeking.get() {
3238            // Note that the other browsers do the similar (by checking `seeking` value or clamp the
3239            // `official` position to the earliest possible position, the duration, and the seekable
3240            // ranges.
3241            // <https://github.com/whatwg/html/issues/11773>
3242            self.current_seek_position.get()
3243        } else {
3244            self.official_playback_position.get()
3245        })
3246    }
3247
3248    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
3249    fn SetCurrentTime(&self, _cx: &mut JSContext, time: Finite<f64>) {
3250        if self.ready_state.get() == ReadyState::HaveNothing {
3251            self.default_playback_start_position.set(*time);
3252        } else {
3253            self.official_playback_position.set(*time);
3254            self.seek(*time, /* approximate_for_speed */ false);
3255        }
3256    }
3257
3258    /// <https://html.spec.whatwg.org/multipage/#dom-media-seeking>
3259    fn Seeking(&self) -> bool {
3260        self.seeking.get()
3261    }
3262
3263    /// <https://html.spec.whatwg.org/multipage/#dom-media-ended>
3264    fn Ended(&self) -> bool {
3265        self.ended_playback(LoopCondition::Included) &&
3266            self.direction_of_playback() == PlaybackDirection::Forwards
3267    }
3268
3269    /// <https://html.spec.whatwg.org/multipage/#dom-media-fastseek>
3270    fn FastSeek(&self, time: Finite<f64>) {
3271        self.seek(*time, /* approximate_for_speed */ true);
3272    }
3273
3274    /// <https://html.spec.whatwg.org/multipage/#dom-media-played>
3275    fn Played(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3276        TimeRanges::new(
3277            self.global().as_window(),
3278            self.played.borrow().clone(),
3279            can_gc,
3280        )
3281    }
3282
3283    /// <https://html.spec.whatwg.org/multipage/#dom-media-seekable>
3284    fn Seekable(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3285        TimeRanges::new(self.global().as_window(), self.seekable(), can_gc)
3286    }
3287
3288    /// <https://html.spec.whatwg.org/multipage/#dom-media-buffered>
3289    fn Buffered(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
3290        let mut buffered = TimeRangesContainer::default();
3291        if let Some(ref player) = *self.player.borrow() {
3292            let ranges = player.lock().unwrap().buffered();
3293            for range in ranges {
3294                let _ = buffered.add(range.start, range.end);
3295            }
3296        }
3297        TimeRanges::new(self.global().as_window(), buffered, can_gc)
3298    }
3299
3300    /// <https://html.spec.whatwg.org/multipage/#dom-media-audiotracks>
3301    fn AudioTracks(&self, cx: &mut js::context::JSContext) -> DomRoot<AudioTrackList> {
3302        let window = self.owner_window();
3303        self.audio_tracks_list
3304            .or_init(|| AudioTrackList::new(cx, &window, &[], Some(self)))
3305    }
3306
3307    /// <https://html.spec.whatwg.org/multipage/#dom-media-videotracks>
3308    fn VideoTracks(&self, can_gc: CanGc) -> DomRoot<VideoTrackList> {
3309        let window = self.owner_window();
3310        self.video_tracks_list
3311            .or_init(|| VideoTrackList::new(&window, &[], Some(self), can_gc))
3312    }
3313
3314    /// <https://html.spec.whatwg.org/multipage/#dom-media-texttracks>
3315    fn TextTracks(&self, can_gc: CanGc) -> DomRoot<TextTrackList> {
3316        let window = self.owner_window();
3317        self.text_tracks_list
3318            .or_init(|| TextTrackList::new(&window, &[], can_gc))
3319    }
3320
3321    /// <https://html.spec.whatwg.org/multipage/#dom-media-addtexttrack>
3322    fn AddTextTrack(
3323        &self,
3324        kind: TextTrackKind,
3325        label: DOMString,
3326        language: DOMString,
3327        can_gc: CanGc,
3328    ) -> DomRoot<TextTrack> {
3329        let window = self.owner_window();
3330        // Step 1 & 2
3331        // FIXME(#22314, dlrobertson) set the ready state to Loaded
3332        let track = TextTrack::new(
3333            &window,
3334            "".into(),
3335            kind,
3336            label,
3337            language,
3338            TextTrackMode::Hidden,
3339            None,
3340            can_gc,
3341        );
3342        // Step 3 & 4
3343        self.TextTracks(can_gc).add(&track);
3344        // Step 5
3345        DomRoot::from_ref(&track)
3346    }
3347
3348    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3349    fn GetVolume(&self) -> Fallible<Finite<f64>> {
3350        Ok(Finite::wrap(self.volume.get()))
3351    }
3352
3353    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3354    fn SetVolume(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3355        // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
3356        // "IndexSizeError" DOMException must be thrown instead.
3357        let minimum_volume = 0.0;
3358        let maximum_volume = 1.0;
3359        if *value < minimum_volume || *value > maximum_volume {
3360            return Err(Error::IndexSize(None));
3361        }
3362
3363        if self.volume.get() == *value {
3364            return Ok(());
3365        }
3366
3367        self.volume.set(*value);
3368
3369        if let Some(ref player) = *self.player.borrow() &&
3370            let Err(error) = player.lock().unwrap().set_volume(*value)
3371        {
3372            warn!("Could not set the volume: {error:?}");
3373        }
3374
3375        // The user agent must queue a media element task given the media element to fire an event
3376        // named volumechange at the media element.
3377        self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3378
3379        // Then, if the media element is not allowed to play, the user agent must run the internal
3380        // pause steps for the media element.
3381        if !self.is_allowed_to_play() {
3382            self.internal_pause_steps();
3383        }
3384
3385        Ok(())
3386    }
3387}
3388
3389impl VirtualMethods for HTMLMediaElement {
3390    fn super_type(&self) -> Option<&dyn VirtualMethods> {
3391        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3392    }
3393
3394    fn attribute_mutated(
3395        &self,
3396        cx: &mut js::context::JSContext,
3397        attr: AttrRef<'_>,
3398        mutation: AttributeMutation,
3399    ) {
3400        self.super_type()
3401            .unwrap()
3402            .attribute_mutated(cx, attr, mutation);
3403
3404        match *attr.local_name() {
3405            local_name!("muted") => {
3406                // <https://html.spec.whatwg.org/multipage/#dom-media-muted>
3407                // When a media element is created, if the element has a muted content attribute
3408                // specified, then the muted IDL attribute should be set to true.
3409                if let AttributeMutation::Set(
3410                    _,
3411                    AttributeMutationReason::ByCloning | AttributeMutationReason::ByParser,
3412                ) = mutation
3413                {
3414                    self.SetMuted(cx, true);
3415                }
3416            },
3417            local_name!("src") => {
3418                // <https://html.spec.whatwg.org/multipage/#location-of-the-media-resource>
3419                // If a src attribute of a media element is set or changed, the user agent must invoke
3420                // the media element's media element load algorithm (Removing the src attribute does
3421                // not do this, even if there are source elements present).
3422                if !mutation.is_removal() {
3423                    self.media_element_load_algorithm(cx);
3424                }
3425            },
3426            local_name!("controls") => {
3427                if mutation.new_value(attr).is_some() {
3428                    self.render_controls(cx);
3429                } else {
3430                    self.remove_controls();
3431                }
3432            },
3433            _ => (),
3434        };
3435    }
3436
3437    /// <https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document>
3438    fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
3439        self.super_type().unwrap().unbind_from_tree(cx, context);
3440
3441        self.remove_controls();
3442
3443        // Step 1. Await a stable state, allowing the task that removed the media element from the Document to continue.
3444        // The synchronous section consists of all the remaining steps of this algorithm.
3445        // (Steps in the synchronous section are marked with ⌛.)
3446        if context.tree_connected {
3447            let task = MediaElementMicrotask::PauseIfNotInDocument {
3448                elem: DomRoot::from_ref(self),
3449            };
3450            ScriptThread::await_stable_state(Microtask::MediaElement(task));
3451        }
3452    }
3453
3454    fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
3455        self.super_type().unwrap().adopting_steps(cx, old_doc);
3456
3457        // Note that media control id should be adopting between documents so "privileged"
3458        // document.servoGetMediaControls(id) API is keeping access to the whitelist of media
3459        // controls identifiers.
3460        if let Some(id) = &*self.media_controls_id.borrow() {
3461            let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3462                error!("Missing media controls shadow root");
3463                return;
3464            };
3465
3466            old_doc.unregister_media_controls(id);
3467            self.owner_document()
3468                .register_media_controls(id, &shadow_root);
3469        }
3470    }
3471}
3472
3473#[derive(JSTraceable, MallocSizeOf)]
3474pub(crate) enum MediaElementMicrotask {
3475    ResourceSelection {
3476        elem: DomRoot<HTMLMediaElement>,
3477        generation_id: u32,
3478        #[no_trace]
3479        base_url: ServoUrl,
3480    },
3481    PauseIfNotInDocument {
3482        elem: DomRoot<HTMLMediaElement>,
3483    },
3484    Seeked {
3485        elem: DomRoot<HTMLMediaElement>,
3486        generation_id: u32,
3487    },
3488    SelectNextSourceChild {
3489        elem: DomRoot<HTMLMediaElement>,
3490        generation_id: u32,
3491    },
3492    SelectNextSourceChildAfterWait {
3493        elem: DomRoot<HTMLMediaElement>,
3494        generation_id: u32,
3495    },
3496}
3497
3498impl MicrotaskRunnable for MediaElementMicrotask {
3499    fn handler(&self, cx: &mut js::context::JSContext) {
3500        match self {
3501            &MediaElementMicrotask::ResourceSelection {
3502                ref elem,
3503                generation_id,
3504                ref base_url,
3505            } => {
3506                if generation_id == elem.generation_id.get() {
3507                    elem.resource_selection_algorithm_sync(base_url.clone(), cx);
3508                }
3509            },
3510            // https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document
3511            MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3512                // Step 2. ⌛ If the media element is in a document, return.
3513                if elem.upcast::<Node>().is_connected() {
3514                    return;
3515                }
3516                // Step 3. ⌛ Run the internal pause steps for the media element.
3517                elem.internal_pause_steps();
3518            },
3519            &MediaElementMicrotask::Seeked {
3520                ref elem,
3521                generation_id,
3522            } => {
3523                if generation_id == elem.generation_id.get() {
3524                    elem.seek_end();
3525                }
3526            },
3527            &MediaElementMicrotask::SelectNextSourceChild {
3528                ref elem,
3529                generation_id,
3530            } => {
3531                if generation_id == elem.generation_id.get() {
3532                    elem.select_next_source_child(cx);
3533                }
3534            },
3535            &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3536                ref elem,
3537                generation_id,
3538            } => {
3539                if generation_id == elem.generation_id.get() {
3540                    elem.select_next_source_child_after_wait(cx);
3541                }
3542            },
3543        }
3544    }
3545
3546    fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
3547        match self {
3548            &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3549            &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3550            &MediaElementMicrotask::Seeked { ref elem, .. } |
3551            &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3552            &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3553                enter_auto_realm(cx, &**elem)
3554            },
3555        }
3556    }
3557}
3558
3559enum Resource {
3560    Object,
3561    Url(ServoUrl),
3562}
3563
3564#[derive(Debug, MallocSizeOf, PartialEq)]
3565enum DataBuffer {
3566    Payload(Vec<u8>),
3567    EndOfStream,
3568}
3569
3570#[derive(MallocSizeOf)]
3571struct BufferedDataSource {
3572    /// During initial setup and seeking (including clearing the buffer queue
3573    /// and resetting the end-of-stream state), the data source should be locked and
3574    /// any request for processing should be ignored until the media player informs us
3575    /// via the NeedData event that it is ready to accept incoming data.
3576    locked: Cell<bool>,
3577    /// Temporary storage for incoming data.
3578    buffers: VecDeque<DataBuffer>,
3579}
3580
3581impl BufferedDataSource {
3582    fn new() -> BufferedDataSource {
3583        BufferedDataSource {
3584            locked: Cell::new(true),
3585            buffers: VecDeque::default(),
3586        }
3587    }
3588
3589    fn set_locked(&self, locked: bool) {
3590        self.locked.set(locked)
3591    }
3592
3593    fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3594        debug_assert_ne!(
3595            self.buffers.back(),
3596            Some(&DataBuffer::EndOfStream),
3597            "The media backend not expects any further data after end of stream"
3598        );
3599
3600        self.buffers.push_back(buffer);
3601    }
3602
3603    fn process_into_player_from_queue(
3604        &mut self,
3605        player: &Arc<Mutex<dyn Player>>,
3606    ) -> Result<(), PlayerError> {
3607        // Early out if any request for processing should be ignored.
3608        if self.locked.get() {
3609            return Ok(());
3610        }
3611
3612        while let Some(buffer) = self.buffers.pop_front() {
3613            match buffer {
3614                DataBuffer::Payload(payload) => {
3615                    if let Err(error) = player.lock().unwrap().push_data(payload) {
3616                        warn!("Could not push input data to player: {error:?}");
3617                        return Err(error);
3618                    }
3619                },
3620                DataBuffer::EndOfStream => {
3621                    if let Err(error) = player.lock().unwrap().end_of_stream() {
3622                        warn!("Could not signal EOS to player: {error:?}");
3623                        return Err(error);
3624                    }
3625                },
3626            }
3627        }
3628
3629        Ok(())
3630    }
3631
3632    fn reset(&mut self) {
3633        self.locked.set(true);
3634        self.buffers.clear();
3635    }
3636}
3637
3638/// Indicates the reason why a fetch request was cancelled.
3639#[derive(Debug, MallocSizeOf, PartialEq)]
3640enum CancelReason {
3641    /// We were asked to stop pushing data to the player.
3642    Backoff,
3643    /// An error ocurred while fetching the media data.
3644    Error,
3645    /// The fetching process is aborted by the user.
3646    Abort,
3647}
3648
3649#[derive(MallocSizeOf)]
3650pub(crate) struct HTMLMediaElementFetchContext {
3651    /// The fetch request id.
3652    request_id: RequestId,
3653    /// Some if the request has been cancelled.
3654    cancel_reason: Option<CancelReason>,
3655    /// Indicates whether the fetched stream is seekable.
3656    is_seekable: bool,
3657    /// Indicates whether the fetched stream is origin clean.
3658    origin_clean: bool,
3659    /// The buffered data source which to be processed by media backend.
3660    data_source: RefCell<BufferedDataSource>,
3661    /// Fetch canceller. Allows cancelling the current fetch request by
3662    /// manually calling its .cancel() method or automatically on Drop.
3663    fetch_canceller: FetchCanceller,
3664}
3665
3666impl HTMLMediaElementFetchContext {
3667    fn new(
3668        request_id: RequestId,
3669        core_resource_thread: CoreResourceThread,
3670    ) -> HTMLMediaElementFetchContext {
3671        HTMLMediaElementFetchContext {
3672            request_id,
3673            cancel_reason: None,
3674            is_seekable: false,
3675            origin_clean: true,
3676            data_source: RefCell::new(BufferedDataSource::new()),
3677            fetch_canceller: FetchCanceller::new(request_id, false, core_resource_thread),
3678        }
3679    }
3680
3681    fn request_id(&self) -> RequestId {
3682        self.request_id
3683    }
3684
3685    fn is_seekable(&self) -> bool {
3686        self.is_seekable
3687    }
3688
3689    fn set_seekable(&mut self, seekable: bool) {
3690        self.is_seekable = seekable;
3691    }
3692
3693    fn origin_is_clean(&self) -> bool {
3694        self.origin_clean
3695    }
3696
3697    fn set_origin_clean(&mut self, origin_clean: bool) {
3698        self.origin_clean = origin_clean;
3699    }
3700
3701    fn data_source(&self) -> &RefCell<BufferedDataSource> {
3702        &self.data_source
3703    }
3704
3705    fn cancel(&mut self, reason: CancelReason) {
3706        if self.cancel_reason.is_some() {
3707            return;
3708        }
3709        self.cancel_reason = Some(reason);
3710        self.data_source.borrow_mut().reset();
3711        self.fetch_canceller.abort();
3712    }
3713
3714    fn cancel_reason(&self) -> &Option<CancelReason> {
3715        &self.cancel_reason
3716    }
3717}
3718
3719struct HTMLMediaElementFetchListener {
3720    /// The element that initiated the request.
3721    element: Trusted<HTMLMediaElement>,
3722    /// The generation of the media element when this fetch started.
3723    generation_id: u32,
3724    /// The fetch request id.
3725    request_id: RequestId,
3726    /// Time of last progress notification.
3727    next_progress_event: Instant,
3728    /// Url for the resource.
3729    url: ServoUrl,
3730    /// Expected content length of the media asset being fetched or played.
3731    expected_content_length: Option<u64>,
3732    /// Actual content length of the media asset was fetched.
3733    fetched_content_length: u64,
3734    /// Discarded content length from the network for the ongoing
3735    /// request if range requests are not supported. Seek requests set it
3736    /// to the required position (in bytes).
3737    content_length_to_discard: u64,
3738}
3739
3740impl FetchResponseListener for HTMLMediaElementFetchListener {
3741    fn process_request_body(&mut self, _: RequestId) {}
3742
3743    fn process_response(
3744        &mut self,
3745        cx: &mut js::context::JSContext,
3746        _: RequestId,
3747        metadata: Result<FetchMetadata, NetworkError>,
3748    ) {
3749        let element = self.element.root();
3750
3751        let (metadata, origin_clean) = match metadata {
3752            Ok(fetch_metadata) => match fetch_metadata {
3753                FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3754                FetchMetadata::Filtered { filtered, unsafe_ } => (
3755                    Some(unsafe_),
3756                    matches!(
3757                        filtered,
3758                        FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3759                    ),
3760                ),
3761            },
3762            Err(_) => (None, true),
3763        };
3764
3765        let (status_is_success, is_seekable) =
3766            metadata.as_ref().map_or((false, false), |metadata| {
3767                let status = &metadata.status;
3768                (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3769            });
3770
3771        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3772        if !status_is_success {
3773            if element.ready_state.get() == ReadyState::HaveNothing {
3774                // => "If the media data cannot be fetched at all, due to network errors..."
3775                element.media_data_processing_failure_steps();
3776            } else {
3777                // => "If the connection is interrupted after some media data has been received..."
3778                element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3779            }
3780            return;
3781        }
3782
3783        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3784            current_fetch_context.set_seekable(is_seekable);
3785            current_fetch_context.set_origin_clean(origin_clean);
3786        }
3787
3788        if let Some(metadata) = metadata.as_ref() &&
3789            let Some(headers) = metadata.headers.as_ref()
3790        {
3791            // For range requests we get the size of the media asset from the Content-Range
3792            // header. Otherwise, we get it from the Content-Length header.
3793            let content_length = if let Some(content_range) = headers.typed_get::<ContentRange>() {
3794                content_range.bytes_len()
3795            } else {
3796                headers
3797                    .typed_get::<ContentLength>()
3798                    .map(|content_length| content_length.0)
3799            };
3800
3801            // We only set the expected input size if it changes.
3802            if content_length != self.expected_content_length &&
3803                let Some(content_length) = content_length
3804            {
3805                self.expected_content_length = Some(content_length);
3806            }
3807        }
3808
3809        // Explicit media player initialization with live/seekable source.
3810        if let Err(e) = element
3811            .player
3812            .borrow()
3813            .as_ref()
3814            .unwrap()
3815            .lock()
3816            .unwrap()
3817            .set_seekable(is_seekable)
3818        {
3819            warn!("Could not set player seekable {:?}", e);
3820        }
3821
3822        if let Some(expected_content_length) = self.expected_content_length &&
3823            let Err(e) = element
3824                .player
3825                .borrow()
3826                .as_ref()
3827                .unwrap()
3828                .lock()
3829                .unwrap()
3830                .set_input_size(expected_content_length)
3831        {
3832            warn!("Could not set player input size {:?}", e);
3833        }
3834    }
3835
3836    fn process_response_chunk(
3837        &mut self,
3838        _: &mut js::context::JSContext,
3839        _: RequestId,
3840        chunk: Vec<u8>,
3841    ) {
3842        let element = self.element.root();
3843
3844        self.fetched_content_length += chunk.len() as u64;
3845
3846        // If an error was received previously, we skip processing the payload.
3847        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3848            if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3849                return;
3850            }
3851
3852            // Discard chunk of the response body if fetch context doesn't support range requests.
3853            let payload = if !current_fetch_context.is_seekable() &&
3854                self.content_length_to_discard != 0
3855            {
3856                if chunk.len() as u64 > self.content_length_to_discard {
3857                    let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
3858                    self.content_length_to_discard = 0;
3859                    shrink_chunk
3860                } else {
3861                    // Completely discard this response chunk.
3862                    self.content_length_to_discard -= chunk.len() as u64;
3863                    return;
3864                }
3865            } else {
3866                chunk
3867            };
3868
3869            if let Err(e) = {
3870                let mut data_source = current_fetch_context.data_source().borrow_mut();
3871                data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
3872                data_source
3873                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3874            } {
3875                // If we are pushing too much data and we know that we can
3876                // restart the download later from where we left, we cancel
3877                // the current request. Otherwise, we continue the request
3878                // assuming that we may drop some frames.
3879                if e == PlayerError::EnoughData {
3880                    current_fetch_context.cancel(CancelReason::Backoff);
3881                }
3882                return;
3883            }
3884        }
3885
3886        // <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
3887        // While the load is not suspended (see below), every 350ms (±200ms) or for every byte
3888        // received, whichever is least frequent, queue a media element task given the media element
3889        // to fire an event named progress at the element.
3890        if Instant::now() > self.next_progress_event {
3891            element.queue_media_element_task_to_fire_event(atom!("progress"));
3892            self.next_progress_event = Instant::now() + Duration::from_millis(350);
3893        }
3894    }
3895
3896    fn process_response_eof(
3897        self,
3898        cx: &mut js::context::JSContext,
3899        _: RequestId,
3900        status: Result<(), NetworkError>,
3901        timing: ResourceFetchTiming,
3902    ) {
3903        let element = self.element.root();
3904
3905        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3906        if status.is_ok() && self.fetched_content_length != 0 {
3907            // => "Once the entire media resource has been fetched..."
3908
3909            // There are no more chunks of the response body forthcoming, so we can
3910            // go ahead and notify the media backend not to expect any further data.
3911            if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
3912            {
3913                // On initial state change READY -> PAUSED the media player perform
3914                // seek to initial position by event with seek segment (TIME format)
3915                // while media stack operates in BYTES format and configuring segment
3916                // start and stop positions without the total size of the stream is not
3917                // possible. As fallback the media player perform seek with BYTES format
3918                // and initiate seek request via "seek-data" callback with required offset.
3919                if self.expected_content_length.is_none() &&
3920                    let Err(e) = element
3921                        .player
3922                        .borrow()
3923                        .as_ref()
3924                        .unwrap()
3925                        .lock()
3926                        .unwrap()
3927                        .set_input_size(self.fetched_content_length)
3928                {
3929                    warn!("Could not set player input size {:?}", e);
3930                }
3931
3932                let mut data_source = current_fetch_context.data_source().borrow_mut();
3933
3934                data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
3935                let _ = data_source
3936                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
3937            }
3938
3939            // Step 1. Fire an event named progress at the media element.
3940            element
3941                .upcast::<EventTarget>()
3942                .fire_event(cx, atom!("progress"));
3943
3944            // Step 2. Set the networkState to NETWORK_IDLE and fire an event named suspend at the
3945            // media element.
3946            element.network_state.set(NetworkState::Idle);
3947
3948            element
3949                .upcast::<EventTarget>()
3950                .fire_event(cx, atom!("suspend"));
3951        } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
3952            // => "If the connection is interrupted after some media data has been received..."
3953            element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3954        } else {
3955            // => "If the media data can be fetched but is found by inspection to be in an
3956            // unsupported format, or can otherwise not be rendered at all"
3957            element.media_data_processing_failure_steps();
3958        }
3959
3960        network_listener::submit_timing(cx, &self, &status, &timing);
3961    }
3962
3963    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
3964        let global = &self.resource_timing_global();
3965        global.report_csp_violations(violations, None, None);
3966    }
3967
3968    fn should_invoke(&self) -> bool {
3969        let element = self.element.root();
3970
3971        if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
3972            return false;
3973        }
3974
3975        let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
3976            return false;
3977        };
3978
3979        // Whether the new fetch request was triggered.
3980        if current_fetch_context.request_id() != self.request_id {
3981            return false;
3982        }
3983
3984        // Whether the current fetch request was cancelled due to a network or decoding error, or
3985        // was aborted by the user.
3986        if let Some(cancel_reason) = current_fetch_context.cancel_reason() &&
3987            matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort)
3988        {
3989            return false;
3990        }
3991
3992        true
3993    }
3994}
3995
3996impl ResourceTimingListener for HTMLMediaElementFetchListener {
3997    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
3998        let initiator_type = InitiatorType::LocalName(
3999            self.element
4000                .root()
4001                .upcast::<Element>()
4002                .local_name()
4003                .to_string(),
4004        );
4005        (initiator_type, self.url.clone())
4006    }
4007
4008    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
4009        self.element.root().owner_document().global()
4010    }
4011}
4012
4013impl HTMLMediaElementFetchListener {
4014    fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
4015        Self {
4016            element: Trusted::new(element),
4017            generation_id: element.generation_id.get(),
4018            request_id,
4019            next_progress_event: Instant::now() + Duration::from_millis(350),
4020            url,
4021            expected_content_length: None,
4022            fetched_content_length: 0,
4023            content_length_to_discard: offset,
4024        }
4025    }
4026}
4027
4028/// The [`HTMLMediaElementEventHandler`] is a structure responsible for handling media events for
4029/// the [`HTMLMediaElement`] and exists to decouple ownership of the [`HTMLMediaElement`] from IPC
4030/// router callback.
4031#[derive(JSTraceable, MallocSizeOf)]
4032struct HTMLMediaElementEventHandler {
4033    element: WeakRef<HTMLMediaElement>,
4034}
4035
4036#[expect(unsafe_code)]
4037unsafe impl Send for HTMLMediaElementEventHandler {}
4038
4039impl HTMLMediaElementEventHandler {
4040    fn new(element: &HTMLMediaElement) -> Self {
4041        Self {
4042            element: WeakRef::new(element),
4043        }
4044    }
4045
4046    fn handle_player_event(
4047        &self,
4048        player_id: usize,
4049        event: PlayerEvent,
4050        cx: &mut js::context::JSContext,
4051    ) {
4052        let Some(element) = self.element.root() else {
4053            return;
4054        };
4055
4056        // Abort event processing if the associated media player is outdated.
4057        if element.player_id().is_none_or(|id| id != player_id) {
4058            return;
4059        }
4060
4061        match event {
4062            PlayerEvent::DurationChanged(duration) => element.playback_duration_changed(duration),
4063            PlayerEvent::EndOfStream => element.playback_end(),
4064            PlayerEvent::EnoughData => element.playback_enough_data(),
4065            PlayerEvent::Error(ref error) => element.playback_error(error, cx),
4066            PlayerEvent::MetadataUpdated(ref metadata) => {
4067                element.playback_metadata_updated(cx, metadata)
4068            },
4069            PlayerEvent::NeedData => element.playback_need_data(),
4070            PlayerEvent::PositionChanged(position) => element.playback_position_changed(position),
4071            PlayerEvent::SeekData(offset, seek_lock) => {
4072                element.fetch_request(Some(offset), Some(seek_lock))
4073            },
4074            PlayerEvent::SeekDone(position) => element.playback_seek_done(position),
4075            PlayerEvent::StateChanged(ref state) => element.playback_state_changed(state),
4076            PlayerEvent::VideoFrameUpdated => element.playback_video_frame_updated(),
4077        }
4078    }
4079}
4080
4081impl Drop for HTMLMediaElementEventHandler {
4082    fn drop(&mut self) {
4083        // The weak reference to the media element is not thread-safe and MUST be deleted on the
4084        // script thread, which is guaranteed by ownership of the `event handler` in the IPC router
4085        // callback (queued task to the media element task source) and the media element itself.
4086        assert_in_script();
4087    }
4088}