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