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