1use std::cell::{Cell, RefCell};
6use std::collections::VecDeque;
7use std::rc::Rc;
8use std::sync::{Arc, Mutex, OnceLock, RwLock, Weak};
9use std::time::{Duration, Instant};
10use std::{f64, mem};
11
12use content_security_policy::sandboxing_directive::SandboxingFlagSet;
13use dom_struct::dom_struct;
14use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState};
15use euclid::default::Size2D;
16use headers::{ContentLength, ContentRange, HeaderMapExt};
17use html5ever::{LocalName, Prefix, QualName, local_name, ns};
18use http::StatusCode;
19use http::header::{self, HeaderMap, HeaderValue};
20use js::context::JSContext;
21use js::realm::{AutoRealm, CurrentRealm};
22use layout_api::MediaFrame;
23use media::{GLPlayerMsg, GLPlayerMsgForward, WindowGLContext};
24use net_traits::request::{Destination, RequestId};
25use net_traits::{
26 CoreResourceThread, FetchMetadata, FilteredMetadata, NetworkError, ResourceFetchTiming,
27};
28use paint_api::{CrossProcessPaintApi, ImageUpdate, SerializableImageData};
29use pixels::RasterImage;
30use script_bindings::assert::assert_in_script;
31use script_bindings::cell::DomRefCell;
32use script_bindings::codegen::InheritTypes::{
33 ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
34};
35use script_bindings::weakref::WeakRef;
36use servo_base::generic_channel::{self, GenericCallback, GenericSharedMemory};
37use servo_base::id::WebViewId;
38use servo_config::pref;
39use servo_media::player::audio::AudioRenderer;
40use servo_media::player::video::{VideoFrame, VideoFrameRenderer};
41use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType};
42use servo_media::{ClientContextId, ServoMedia, SupportsMediaType};
43use servo_url::ServoUrl;
44use stylo_atoms::Atom;
45use uuid::Uuid;
46use webrender_api::{
47 ExternalImageData, ExternalImageId, ExternalImageType, ImageBufferKind, ImageDescriptor,
48 ImageDescriptorFlags, ImageFormat, ImageKey,
49};
50
51use crate::document_loader::{LoadBlocker, LoadType};
52use crate::dom::audio::audiotrack::AudioTrack;
53use crate::dom::audio::audiotracklist::AudioTrackList;
54use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::{
55 CanPlayTypeResult, HTMLMediaElementConstants, HTMLMediaElementMethods,
56};
57use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*;
58use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
59use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
60use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
61use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode};
62use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
63use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
64use crate::dom::bindings::codegen::UnionTypes::{
65 MediaStreamOrBlob, VideoTrackOrAudioTrackOrTextTrack,
66};
67use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::num::Finite;
70use crate::dom::bindings::refcounted::Trusted;
71use crate::dom::bindings::reflector::DomGlobal;
72use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, UnrootedDom};
73use crate::dom::bindings::str::{DOMString, USVString};
74use crate::dom::blob::Blob;
75use crate::dom::csp::{GlobalCspReporting, Violation};
76use crate::dom::document::Document;
77use crate::dom::element::attributes::storage::AttrRef;
78use crate::dom::element::{
79 AttributeMutation, AttributeMutationReason, CustomElementCreationMode, Element, ElementCreator,
80 cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute,
81};
82use crate::dom::event::Event;
83use crate::dom::eventtarget::EventTarget;
84use crate::dom::globalscope::GlobalScope;
85use crate::dom::html::htmlelement::HTMLElement;
86use crate::dom::html::htmlsourceelement::HTMLSourceElement;
87use crate::dom::html::htmlvideoelement::HTMLVideoElement;
88use crate::dom::mediaerror::MediaError;
89use crate::dom::mediafragmentparser::MediaFragmentParser;
90use crate::dom::medialist::MediaList;
91use crate::dom::mediastream::MediaStream;
92use crate::dom::node::virtualmethods::VirtualMethods;
93use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext};
94use crate::dom::performance::performanceresourcetiming::InitiatorType;
95use crate::dom::promise::Promise;
96use crate::dom::texttrack::TextTrack;
97use crate::dom::texttracklist::TextTrackList;
98use crate::dom::timeranges::{TimeRanges, TimeRangesContainer};
99use crate::dom::trackevent::TrackEvent;
100use crate::dom::url::URL;
101use crate::dom::videotrack::VideoTrack;
102use crate::dom::videotracklist::VideoTrackList;
103use crate::fetch::{FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request};
104use crate::microtask::{Microtask, MicrotaskRunnable};
105use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
106use crate::realms::enter_auto_realm;
107use crate::script_runtime::CanGc;
108use crate::script_thread::ScriptThread;
109use crate::task_source::SendableTaskSource;
110
111static MEDIA_CONTROL_CSS: &str = include_str!("../../resources/media-controls.css");
113
114static MEDIA_CONTROL_JS: &str = include_str!("../../resources/media-controls.js");
116
117const SEEK_POSITION_THRESHOLD: f64 = 0.5;
121
122#[derive(MallocSizeOf, PartialEq)]
123enum FrameStatus {
124 Locked,
125 Unlocked,
126}
127
128#[derive(MallocSizeOf)]
129struct FrameHolder(
130 FrameStatus,
131 #[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
132);
133
134impl FrameHolder {
135 fn new(frame: VideoFrame) -> FrameHolder {
136 FrameHolder(FrameStatus::Unlocked, frame)
137 }
138
139 fn lock(&mut self) {
140 if self.0 == FrameStatus::Unlocked {
141 self.0 = FrameStatus::Locked;
142 };
143 }
144
145 fn unlock(&mut self) {
146 if self.0 == FrameStatus::Locked {
147 self.0 = FrameStatus::Unlocked;
148 };
149 }
150
151 fn set(&mut self, new_frame: VideoFrame) {
152 if self.0 == FrameStatus::Unlocked {
153 self.1 = new_frame
154 };
155 }
156
157 fn get(&self) -> (u32, Size2D<i32>, usize) {
158 if self.0 == FrameStatus::Locked {
159 (
160 self.1.get_texture_id(),
161 Size2D::new(self.1.get_width(), self.1.get_height()),
162 0,
163 )
164 } else {
165 unreachable!();
166 }
167 }
168
169 fn get_frame(&self) -> VideoFrame {
170 self.1.clone()
171 }
172}
173
174#[derive(MallocSizeOf)]
175pub(crate) struct MediaFrameRenderer {
176 webview_id: WebViewId,
177 player_id: Option<usize>,
178 #[conditional_malloc_size_of]
179 glplayer_id: Arc<RwLock<Option<u64>>>,
180 paint_api: CrossProcessPaintApi,
181 #[ignore_malloc_size_of = "Defined in other crates"]
182 player_context: WindowGLContext,
183 current_frame: Option<MediaFrame>,
184 old_frame: Option<ImageKey>,
185 very_old_frame: Option<ImageKey>,
186 current_frame_holder: Option<FrameHolder>,
187 poster_frame: Option<MediaFrame>,
189}
190
191impl MediaFrameRenderer {
192 fn new(
193 webview_id: WebViewId,
194 paint_api: CrossProcessPaintApi,
195 player_context: WindowGLContext,
196 ) -> Self {
197 Self {
198 webview_id,
199 player_id: None,
200 glplayer_id: Arc::new(RwLock::new(None)),
201 paint_api,
202 player_context,
203 current_frame: None,
204 old_frame: None,
205 very_old_frame: None,
206 current_frame_holder: None,
207 poster_frame: None,
208 }
209 }
210
211 fn setup(
212 &mut self,
213 player_id: usize,
214 task_source: SendableTaskSource,
215 weak_video_renderer: Weak<Mutex<MediaFrameRenderer>>,
216 ) {
217 self.player_id = Some(player_id);
218 let glplayer_id = self.glplayer_id.clone();
219 let callback = GenericCallback::new(move |message| {
220 let message = message.unwrap();
221 let weak_video_renderer = weak_video_renderer.clone();
222
223 let glplayer_id = glplayer_id.clone();
224 task_source.queue(task!(handle_glplayer_message: move || {
225 trace!("GLPlayer message {:?}", message);
226
227 let Some(video_renderer) = weak_video_renderer.upgrade() else {
228 return;
229 };
230
231 match message {
232 GLPlayerMsgForward::Lock(sender) => {
233 if let Some(holder) = video_renderer
234 .lock()
235 .unwrap()
236 .current_frame_holder
237 .as_mut() {
238 holder.lock();
239 sender.send(holder.get()).unwrap();
240 };
241 },
242 GLPlayerMsgForward::Unlock() => {
243 if let Some(holder) = video_renderer
244 .lock()
245 .unwrap()
246 .current_frame_holder
247 .as_mut() { holder.unlock() }
248 },
249 GLPlayerMsgForward::PlayerId(id) => {
250 let mut glplayer_id = glplayer_id.write().unwrap();
251 if let Some(already_set_id) = *glplayer_id {
252 error!("Player id already set to {already_set_id} will be replaced with {id}");
253 }
254 *glplayer_id = Some(id);
255 },
256 }
257 }));
258 })
259 .expect("Could not create callback");
260
261 if let Some(glplayer_thread_sender) = &self.player_context.glplayer_thread_sender {
262 glplayer_thread_sender
263 .send(GLPlayerMsg::RegisterPlayer(callback))
264 .unwrap();
265 }
266 }
267
268 fn reset(&mut self) {
269 self.player_id = None;
270
271 if let Some(glplayer_id) = self.glplayer_id.write().unwrap().take() {
272 self.player_context
273 .send(GLPlayerMsg::UnregisterPlayer(glplayer_id));
274 }
275
276 self.current_frame_holder = None;
277
278 let mut updates = smallvec::smallvec![];
279
280 if let Some(current_frame) = self.current_frame.take() {
281 updates.push(ImageUpdate::DeleteImage(current_frame.image_key));
282 }
283
284 if let Some(old_image_key) = self.old_frame.take() {
285 updates.push(ImageUpdate::DeleteImage(old_image_key));
286 }
287
288 if let Some(very_old_image_key) = self.very_old_frame.take() {
289 updates.push(ImageUpdate::DeleteImage(very_old_image_key));
290 }
291
292 if !updates.is_empty() {
293 self.paint_api
294 .update_images(self.webview_id.into(), updates);
295 }
296 }
297
298 fn set_poster_frame(&mut self, image: Option<Arc<RasterImage>>) {
299 self.poster_frame = image.and_then(|image| {
300 image.id.map(|image_key| MediaFrame {
301 image_key,
302 width: image.metadata.width as i32,
303 height: image.metadata.height as i32,
304 })
305 });
306 }
307}
308
309impl Drop for MediaFrameRenderer {
310 fn drop(&mut self) {
311 self.reset();
312 }
313}
314
315impl VideoFrameRenderer for MediaFrameRenderer {
316 fn render(&mut self, frame: VideoFrame) {
317 if self.player_id.is_none() ||
318 (frame.is_gl_texture() && self.glplayer_id.read().unwrap().is_none())
319 {
320 return;
321 }
322
323 let mut updates = smallvec::smallvec![];
324
325 if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) {
326 updates.push(ImageUpdate::DeleteImage(old_image_key));
327 }
328
329 let descriptor = ImageDescriptor::new(
330 frame.get_width(),
331 frame.get_height(),
332 ImageFormat::BGRA8,
333 ImageDescriptorFlags::empty(),
334 );
335
336 match &mut self.current_frame {
337 Some(current_frame)
338 if current_frame.width == frame.get_width() &&
339 current_frame.height == frame.get_height() =>
340 {
341 if !frame.is_gl_texture() {
342 updates.push(ImageUpdate::UpdateImage(
343 current_frame.image_key,
344 descriptor,
345 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
346 frame.get_data(),
347 )),
348 None,
349 ));
350 }
351
352 self.current_frame_holder
353 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
354 .set(frame);
355
356 if let Some(old_image_key) = self.old_frame.take() {
357 updates.push(ImageUpdate::DeleteImage(old_image_key));
358 }
359 },
360 Some(current_frame) => {
361 self.old_frame = Some(current_frame.image_key);
362
363 let Some(new_image_key) =
364 self.paint_api.generate_image_key_blocking(self.webview_id)
365 else {
366 return;
367 };
368
369 current_frame.image_key = new_image_key;
371 current_frame.width = frame.get_width();
372 current_frame.height = frame.get_height();
373
374 let image_data = self
376 .glplayer_id
377 .read()
378 .unwrap()
379 .filter(|_| frame.is_gl_texture())
380 .map(|glplayer_id| {
381 let texture_target = if frame.is_external_oes() {
382 ImageBufferKind::TextureExternal
383 } else {
384 ImageBufferKind::Texture2D
385 };
386
387 SerializableImageData::External(ExternalImageData {
388 id: ExternalImageId(glplayer_id),
389 channel_index: 0,
390 image_type: ExternalImageType::TextureHandle(texture_target),
391 normalized_uvs: false,
392 })
393 })
394 .unwrap_or_else(|| {
395 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
396 frame.get_data(),
397 ))
398 });
399
400 self.current_frame_holder
401 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
402 .set(frame);
403
404 updates.push(ImageUpdate::AddImage(
405 new_image_key,
406 descriptor,
407 image_data,
408 false,
409 ));
410 },
411 None => {
412 let Some(image_key) = self.paint_api.generate_image_key_blocking(self.webview_id)
413 else {
414 return;
415 };
416
417 self.current_frame = Some(MediaFrame {
418 image_key,
419 width: frame.get_width(),
420 height: frame.get_height(),
421 });
422
423 let image_data = self
424 .glplayer_id
425 .read()
426 .unwrap()
427 .filter(|_| frame.is_gl_texture())
428 .map(|glplayer_id| {
429 let texture_target = if frame.is_external_oes() {
430 ImageBufferKind::TextureExternal
431 } else {
432 ImageBufferKind::Texture2D
433 };
434
435 SerializableImageData::External(ExternalImageData {
436 id: ExternalImageId(glplayer_id),
437 channel_index: 0,
438 image_type: ExternalImageType::TextureHandle(texture_target),
439 normalized_uvs: false,
440 })
441 })
442 .unwrap_or_else(|| {
443 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
444 frame.get_data(),
445 ))
446 });
447
448 self.current_frame_holder = Some(FrameHolder::new(frame));
449
450 updates.push(ImageUpdate::AddImage(
451 image_key, descriptor, image_data, false,
452 ));
453 },
454 }
455 self.paint_api
456 .update_images(self.webview_id.into(), updates);
457 }
458}
459
460#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
461#[derive(JSTraceable, MallocSizeOf)]
462enum SrcObject {
463 MediaStream(Dom<MediaStream>),
464 Blob(Dom<Blob>),
465}
466
467impl From<MediaStreamOrBlob> for SrcObject {
468 fn from(src_object: MediaStreamOrBlob) -> SrcObject {
469 match src_object {
470 MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)),
471 MediaStreamOrBlob::MediaStream(stream) => {
472 SrcObject::MediaStream(Dom::from_ref(&*stream))
473 },
474 }
475 }
476}
477
478#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
479enum LoadState {
480 NotLoaded,
481 LoadingFromSrcObject,
482 LoadingFromSrcAttribute,
483 LoadingFromSourceChild,
484 WaitingForSource,
485}
486
487#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
489#[derive(JSTraceable, MallocSizeOf)]
490struct SourceChildrenPointer {
491 source_before_pointer: Dom<HTMLSourceElement>,
492 inclusive: bool,
493}
494
495impl SourceChildrenPointer {
496 fn new(source_before_pointer: DomRoot<HTMLSourceElement>, inclusive: bool) -> Self {
497 Self {
498 source_before_pointer: source_before_pointer.as_traced(),
499 inclusive,
500 }
501 }
502}
503
504#[derive(Clone, Copy, Debug, PartialEq)]
508enum LoopCondition {
509 Included,
510 Ignored,
511}
512
513#[dom_struct]
514pub(crate) struct HTMLMediaElement {
515 htmlelement: HTMLElement,
516 network_state: Cell<NetworkState>,
518 ready_state: Cell<ReadyState>,
520 src_object: DomRefCell<Option<SrcObject>>,
522 current_src: DomRefCell<String>,
524 generation_id: Cell<u32>,
526 fired_loadeddata_event: Cell<bool>,
530 error: MutNullableDom<MediaError>,
532 paused: Cell<bool>,
534 default_playback_rate: Cell<f64>,
536 playback_rate: Cell<f64>,
538 autoplaying: Cell<bool>,
540 delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>,
542 #[conditional_malloc_size_of]
544 pending_play_promises: DomRefCell<Vec<Rc<Promise>>>,
545 #[expect(clippy::type_complexity)]
547 #[conditional_malloc_size_of]
548 in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
549 #[ignore_malloc_size_of = "servo_media"]
550 #[no_trace]
551 player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
552 #[conditional_malloc_size_of]
553 #[no_trace]
554 video_renderer: Arc<Mutex<MediaFrameRenderer>>,
555 #[ignore_malloc_size_of = "servo_media"]
556 #[no_trace]
557 audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>,
558 #[conditional_malloc_size_of]
559 #[no_trace]
560 event_handler: RefCell<Option<Arc<Mutex<HTMLMediaElementEventHandler>>>>,
561 show_poster: Cell<bool>,
563 duration: Cell<f64>,
565 current_playback_position: Cell<f64>,
567 official_playback_position: Cell<f64>,
569 default_playback_start_position: Cell<f64>,
571 volume: Cell<f64>,
573 seeking: Cell<bool>,
575 current_seek_position: Cell<f64>,
579 muted: Cell<bool>,
581 load_state: Cell<LoadState>,
583 source_children_pointer: DomRefCell<Option<SourceChildrenPointer>>,
584 current_source_child: MutNullableDom<HTMLSourceElement>,
585 #[no_trace]
587 resource_url: DomRefCell<Option<ServoUrl>>,
588 #[no_trace]
591 blob_url: DomRefCell<Option<ServoUrl>>,
592 played: DomRefCell<TimeRangesContainer>,
594 audio_tracks_list: MutNullableDom<AudioTrackList>,
596 video_tracks_list: MutNullableDom<VideoTrackList>,
598 text_tracks_list: MutNullableDom<TextTrackList>,
600 #[ignore_malloc_size_of = "Defined in std::time"]
602 next_timeupdate_event: Cell<Instant>,
603 current_fetch_context: RefCell<Option<HTMLMediaElementFetchContext>>,
605 media_controls_id: DomRefCell<Option<String>>,
610}
611
612#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
614#[repr(u8)]
615pub(crate) enum NetworkState {
616 Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
617 Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
618 Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
619 NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8,
620}
621
622#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
624#[repr(u8)]
625#[expect(clippy::enum_variant_names)] pub(crate) enum ReadyState {
627 HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8,
628 HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8,
629 HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8,
630 HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8,
631 HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8,
632}
633
634#[derive(Clone, Copy, PartialEq)]
636enum PlaybackDirection {
637 Forwards,
638 Backwards,
639}
640
641impl HTMLMediaElement {
642 pub(crate) fn new_inherited(
643 tag_name: LocalName,
644 prefix: Option<Prefix>,
645 document: &Document,
646 ) -> Self {
647 Self {
648 htmlelement: HTMLElement::new_inherited(tag_name, prefix, document),
649 network_state: Cell::new(NetworkState::Empty),
650 ready_state: Cell::new(ReadyState::HaveNothing),
651 src_object: Default::default(),
652 current_src: DomRefCell::new("".to_owned()),
653 generation_id: Cell::new(0),
654 fired_loadeddata_event: Cell::new(false),
655 error: Default::default(),
656 paused: Cell::new(true),
657 default_playback_rate: Cell::new(1.0),
658 playback_rate: Cell::new(1.0),
659 muted: Cell::new(false),
660 load_state: Cell::new(LoadState::NotLoaded),
661 source_children_pointer: DomRefCell::new(None),
662 current_source_child: Default::default(),
663 autoplaying: Cell::new(true),
665 delaying_the_load_event_flag: Default::default(),
666 pending_play_promises: Default::default(),
667 in_flight_play_promises_queue: Default::default(),
668 player: Default::default(),
669 video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new(
670 document.webview_id(),
671 document.window().paint_api().clone(),
672 document.window().get_player_context(),
673 ))),
674 audio_renderer: Default::default(),
675 event_handler: Default::default(),
676 show_poster: Cell::new(true),
677 duration: Cell::new(f64::NAN),
678 current_playback_position: Cell::new(0.),
679 official_playback_position: Cell::new(0.),
680 default_playback_start_position: Cell::new(0.),
681 volume: Cell::new(1.0),
682 seeking: Cell::new(false),
683 current_seek_position: Cell::new(f64::NAN),
684 resource_url: DomRefCell::new(None),
685 blob_url: DomRefCell::new(None),
686 played: DomRefCell::new(TimeRangesContainer::default()),
687 audio_tracks_list: Default::default(),
688 video_tracks_list: Default::default(),
689 text_tracks_list: Default::default(),
690 next_timeupdate_event: Cell::new(Instant::now() + Duration::from_millis(250)),
691 current_fetch_context: RefCell::new(None),
692 media_controls_id: DomRefCell::new(None),
693 }
694 }
695
696 pub(crate) fn network_state(&self) -> NetworkState {
697 self.network_state.get()
698 }
699
700 pub(crate) fn get_ready_state(&self) -> ReadyState {
701 self.ready_state.get()
702 }
703
704 fn media_type_id(&self) -> HTMLMediaElementTypeId {
705 match self.upcast::<Node>().type_id() {
706 NodeTypeId::Element(ElementTypeId::HTMLElement(
707 HTMLElementTypeId::HTMLMediaElement(media_type_id),
708 )) => media_type_id,
709 _ => unreachable!(),
710 }
711 }
712
713 fn update_media_state(&self) {
714 let is_playing = self
715 .player
716 .borrow()
717 .as_ref()
718 .is_some_and(|player| !player.lock().unwrap().paused());
719
720 if self.is_potentially_playing() && !is_playing {
721 if let Some(ref player) = *self.player.borrow() {
722 let player = player.lock().unwrap();
723
724 if let Err(error) = player.set_playback_rate(self.playback_rate.get()) {
725 warn!("Could not set the playback rate: {error:?}");
726 }
727 if let Err(error) = player.set_volume(self.volume.get()) {
728 warn!("Could not set the volume: {error:?}");
729 }
730 if let Err(error) = player.play() {
731 error!("Could not play media: {error:?}");
732 }
733 }
734 } else if is_playing &&
735 let Some(ref player) = *self.player.borrow() &&
736 let Err(error) = player.lock().unwrap().pause()
737 {
738 error!("Could not pause player: {error:?}");
739 }
740 }
741
742 pub(crate) fn delay_load_event(&self, delay: bool, cx: &mut JSContext) {
749 let blocker = &self.delaying_the_load_event_flag;
750
751 if delay {
752 if blocker.borrow().is_none() {
753 *blocker.borrow_mut() =
754 Some(LoadBlocker::new(&self.owner_document(), LoadType::Media));
755 }
756 } else {
757 LoadBlocker::terminate(blocker, cx);
758 }
759 }
760
761 fn time_marches_on(&self) {
763 if Instant::now() > self.next_timeupdate_event.get() {
769 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
770 self.next_timeupdate_event
771 .set(Instant::now() + Duration::from_millis(250));
772 }
773 }
774
775 fn internal_play_steps(&self, cx: &mut JSContext) {
777 if self.network_state.get() == NetworkState::Empty {
780 self.invoke_resource_selection_algorithm(cx);
781 }
782
783 if self.ended_playback(LoopCondition::Ignored) &&
789 self.direction_of_playback() == PlaybackDirection::Forwards
790 {
791 self.seek(
792 self.earliest_possible_position(),
793 false,
794 );
795 }
796
797 let state = self.ready_state.get();
798
799 if self.Paused() {
801 self.paused.set(false);
803
804 if self.show_poster.get() {
807 self.show_poster.set(false);
808 self.time_marches_on();
809 }
810
811 self.queue_media_element_task_to_fire_event(atom!("play"));
814
815 match state {
821 ReadyState::HaveNothing |
822 ReadyState::HaveMetadata |
823 ReadyState::HaveCurrentData => {
824 self.queue_media_element_task_to_fire_event(atom!("waiting"));
825 },
826 ReadyState::HaveFutureData | ReadyState::HaveEnoughData => {
827 self.notify_about_playing();
828 },
829 }
830 }
831 else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
836 self.take_pending_play_promises(Ok(()));
837
838 let this = Trusted::new(self);
839 let generation_id = self.generation_id.get();
840
841 self.owner_global()
842 .task_manager()
843 .media_element_task_source()
844 .queue(task!(resolve_pending_play_promises: move |cx| {
845 let this = this.root();
846 if generation_id != this.generation_id.get() {
847 return;
848 }
849
850 this.fulfill_in_flight_play_promises(cx, |_| {});
851 }));
852 }
853
854 self.autoplaying.set(false);
856
857 self.update_media_state();
858 }
859
860 fn internal_pause_steps(&self) {
862 self.autoplaying.set(false);
864
865 if !self.Paused() {
867 self.paused.set(true);
869
870 self.take_pending_play_promises(Err(Error::Abort(None)));
872
873 let this = Trusted::new(self);
875 let generation_id = self.generation_id.get();
876
877 self.owner_global()
878 .task_manager()
879 .media_element_task_source()
880 .queue(task!(internal_pause_steps: move |cx| {
881 let this = this.root();
882 if generation_id != this.generation_id.get() {
883 return;
884 }
885
886 this.fulfill_in_flight_play_promises(cx, |cx| {
887 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
889
890 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
892
893 });
897 }));
898
899 self.official_playback_position
901 .set(self.current_playback_position.get());
902 }
903
904 self.update_media_state();
905 }
906
907 fn is_allowed_to_play(&self) -> bool {
909 true
910 }
911
912 fn notify_about_playing(&self) {
914 self.take_pending_play_promises(Ok(()));
916
917 let this = Trusted::new(self);
919 let generation_id = self.generation_id.get();
920
921 self.owner_global()
922 .task_manager()
923 .media_element_task_source()
924 .queue(task!(notify_about_playing: move |cx| {
925 let this = this.root();
926 if generation_id != this.generation_id.get() {
927 return;
928 }
929
930 this.fulfill_in_flight_play_promises(cx, |cx| {
931 this.upcast::<EventTarget>().fire_event(cx, atom!("playing"));
933
934 });
937 }));
938 }
939
940 #[expect(
942 clippy::collapsible_match,
943 reason = "This way follows the spec more closely"
944 )]
945 fn change_ready_state(&self, ready_state: ReadyState) {
946 let old_ready_state = self.ready_state.get();
947 self.ready_state.set(ready_state);
948
949 if self.network_state.get() == NetworkState::Empty {
950 return;
951 }
952
953 if old_ready_state == ready_state {
954 return;
955 }
956
957 match (old_ready_state, ready_state) {
959 (ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
962 self.queue_media_element_task_to_fire_event(atom!("loadedmetadata"));
965 return;
967 },
968 (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => {
971 if !self.fired_loadeddata_event.get() {
975 self.fired_loadeddata_event.set(true);
976
977 let this = Trusted::new(self);
978 let generation_id = self.generation_id.get();
979
980 self.owner_global()
981 .task_manager()
982 .media_element_task_source()
983 .queue(task!(media_reached_current_data: move |cx| {
984 let this = this.root();
985 if generation_id != this.generation_id.get() {
986 return;
987 }
988
989 this.upcast::<EventTarget>().fire_event(cx, atom!("loadeddata"));
990 this.delay_load_event(false, cx);
994 }));
995 }
996
997 },
1001 (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => {
1002 return;
1007 },
1008
1009 _ => (),
1010 }
1011
1012 if old_ready_state <= ReadyState::HaveCurrentData &&
1015 ready_state >= ReadyState::HaveFutureData
1016 {
1017 self.queue_media_element_task_to_fire_event(atom!("canplay"));
1020
1021 if !self.Paused() {
1024 self.notify_about_playing();
1025 }
1026 }
1027
1028 if ready_state == ReadyState::HaveEnoughData {
1030 self.queue_media_element_task_to_fire_event(atom!("canplaythrough"));
1033
1034 if self.eligible_for_autoplay() {
1037 self.paused.set(false);
1039
1040 if self.show_poster.get() {
1043 self.show_poster.set(false);
1044 self.time_marches_on();
1045 }
1046
1047 self.queue_media_element_task_to_fire_event(atom!("play"));
1050
1051 self.notify_about_playing();
1053 }
1054 }
1055
1056 self.update_media_state();
1057 }
1058
1059 fn invoke_resource_selection_algorithm(&self, cx: &mut JSContext) {
1061 self.network_state.set(NetworkState::NoSource);
1063
1064 self.show_poster.set(true);
1066
1067 self.delay_load_event(true, cx);
1070
1071 let task = MediaElementMicrotask::ResourceSelection {
1078 elem: DomRoot::from_ref(self),
1079 generation_id: self.generation_id.get(),
1080 base_url: self.owner_document().base_url(),
1081 };
1082
1083 ScriptThread::await_stable_state(cx, Microtask::MediaElement(task));
1088 }
1089
1090 fn resource_selection_algorithm_sync(&self, base_url: ServoUrl, cx: &mut JSContext) {
1092 enum Mode {
1099 Object,
1100 Attribute(String),
1101 Children(DomRoot<HTMLSourceElement>),
1102 }
1103
1104 let mode = if self.src_object.borrow().is_some() {
1106 Mode::Object
1108 } else if let Some(src) = self
1109 .upcast::<Element>()
1110 .get_attribute_string_value(&local_name!("src"))
1111 {
1112 Mode::Attribute(src)
1115 } else if let Some(source) = self
1116 .upcast::<Node>()
1117 .children_unrooted(cx.no_gc())
1118 .find_map(UnrootedDom::downcast::<HTMLSourceElement>)
1119 {
1120 Mode::Children(source.as_rooted())
1124 } else {
1125 self.load_state.set(LoadState::NotLoaded);
1128
1129 self.network_state.set(NetworkState::Empty);
1131
1132 self.delay_load_event(false, cx);
1135
1136 return;
1138 };
1139
1140 self.network_state.set(NetworkState::Loading);
1142
1143 self.queue_media_element_task_to_fire_event(atom!("loadstart"));
1146
1147 match mode {
1149 Mode::Object => {
1150 self.load_from_src_object(cx);
1152 },
1153 Mode::Attribute(src) => {
1154 self.load_from_src_attribute(cx, base_url, &src);
1156 },
1157 Mode::Children(source) => {
1158 self.load_from_source_child(cx, &source);
1160 },
1161 }
1162 }
1163
1164 fn load_from_src_object(&self, cx: &JSContext) {
1166 self.load_state.set(LoadState::LoadingFromSrcObject);
1167
1168 "".clone_into(&mut self.current_src.borrow_mut());
1170
1171 self.resource_fetch_algorithm(cx, Resource::Object);
1177 }
1178
1179 fn load_from_src_attribute(&self, cx: &JSContext, base_url: ServoUrl, src: &str) {
1181 self.load_state.set(LoadState::LoadingFromSrcAttribute);
1182
1183 if src.is_empty() {
1186 self.queue_dedicated_media_source_failure_steps();
1187 return;
1188 }
1189
1190 let Ok(url_record) = base_url.join(src) else {
1194 self.queue_dedicated_media_source_failure_steps();
1195 return;
1196 };
1197
1198 *self.current_src.borrow_mut() = url_record.as_str().into();
1201
1202 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1208 }
1209
1210 fn load_from_source_child(&self, cx: &JSContext, source: &HTMLSourceElement) {
1212 self.load_state.set(LoadState::LoadingFromSourceChild);
1213
1214 *self.source_children_pointer.borrow_mut() =
1221 Some(SourceChildrenPointer::new(DomRoot::from_ref(source), false));
1222
1223 let element = source.upcast::<Element>();
1224
1225 let Some(src) = element
1229 .get_attribute_string_value(&local_name!("src"))
1230 .filter(|value| !value.is_empty())
1231 else {
1232 self.load_from_source_child_failure_steps(cx, source);
1233 return;
1234 };
1235
1236 if let Some(media) = element.get_attribute_string_value(&local_name!("media")) &&
1240 !MediaList::matches_environment(&element.owner_document(), &media)
1241 {
1242 self.load_from_source_child_failure_steps(cx, source);
1243 return;
1244 }
1245
1246 let Ok(url_record) = source.owner_document().base_url().join(&src) else {
1250 self.load_from_source_child_failure_steps(cx, source);
1253 return;
1254 };
1255
1256 if let Some(type_) = element.get_attribute_string_value(&local_name!("type")) &&
1261 ServoMedia::get().can_play_type(&type_) == SupportsMediaType::No
1262 {
1263 self.load_from_source_child_failure_steps(cx, source);
1264 return;
1265 }
1266
1267 self.reset_media_player();
1269
1270 self.current_source_child.set(Some(source));
1271
1272 *self.current_src.borrow_mut() = url_record.as_str().into();
1275
1276 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1281 }
1282
1283 fn load_from_source_child_failure_steps(&self, cx: &JSContext, source: &HTMLSourceElement) {
1285 let trusted_this = Trusted::new(self);
1288 let trusted_source = Trusted::new(source);
1289 let generation_id = self.generation_id.get();
1290
1291 self.owner_global()
1292 .task_manager()
1293 .media_element_task_source()
1294 .queue(task!(queue_error_event: move |cx| {
1295 let this = trusted_this.root();
1296 if generation_id != this.generation_id.get() {
1297 return;
1298 }
1299
1300 let source = trusted_source.root();
1301 source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1302 }));
1303
1304 let task = MediaElementMicrotask::SelectNextSourceChild {
1306 elem: DomRoot::from_ref(self),
1307 generation_id: self.generation_id.get(),
1308 };
1309
1310 ScriptThread::await_stable_state(cx, Microtask::MediaElement(task));
1311 }
1312
1313 fn select_next_source_child(&self, cx: &mut JSContext) {
1315 self.AudioTracks(cx).clear();
1317 self.VideoTracks(cx).clear();
1318
1319 let mut source_candidate = None;
1321
1322 if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1330 if source_children_pointer.inclusive {
1334 for next_sibling in source_children_pointer
1335 .source_before_pointer
1336 .upcast::<Node>()
1337 .inclusively_following_siblings()
1338 {
1339 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1340 {
1341 source_candidate = Some(next_source);
1342 break;
1343 }
1344 }
1345 } else {
1346 for next_sibling in source_children_pointer
1347 .source_before_pointer
1348 .upcast::<Node>()
1349 .following_siblings()
1350 {
1351 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1352 {
1353 source_candidate = Some(next_source);
1354 break;
1355 }
1356 }
1357 };
1358 }
1359
1360 if let Some(source_candidate) = source_candidate {
1363 self.load_from_source_child(cx, &source_candidate);
1364 return;
1365 }
1366
1367 self.load_state.set(LoadState::WaitingForSource);
1368
1369 *self.source_children_pointer.borrow_mut() = None;
1370
1371 self.network_state.set(NetworkState::NoSource);
1374
1375 self.show_poster.set(true);
1377
1378 let this = Trusted::new(self);
1381 let generation_id = self.generation_id.get();
1382
1383 self.owner_global()
1384 .task_manager()
1385 .media_element_task_source()
1386 .queue(task!(queue_delay_load_event: move |cx| {
1387 let this = this.root();
1388 if generation_id != this.generation_id.get() {
1389 return;
1390 }
1391
1392 this.delay_load_event(false, cx);
1393 }));
1394
1395 }
1398
1399 fn resource_selection_algorithm_failure_steps(&self, cx: &JSContext) {
1401 match self.load_state.get() {
1402 LoadState::LoadingFromSrcObject => {
1403 self.queue_dedicated_media_source_failure_steps();
1408 },
1409 LoadState::LoadingFromSrcAttribute => {
1410 self.queue_dedicated_media_source_failure_steps();
1415 },
1416 LoadState::LoadingFromSourceChild => {
1417 if let Some(source) = self.current_source_child.take() {
1420 self.load_from_source_child_failure_steps(cx, &source);
1421 }
1422 },
1423 _ => {},
1424 }
1425 }
1426
1427 fn fetch_request(&self, cx: &JSContext, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1428 if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1429 error!("Missing request url");
1430 if let Some(seek_lock) = seek_lock {
1431 seek_lock.unlock(false);
1432 }
1433 self.resource_selection_algorithm_failure_steps(cx);
1434 return;
1435 }
1436
1437 let document = self.owner_document();
1438 let destination = match self.media_type_id() {
1439 HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1440 HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1441 };
1442 let mut headers = HeaderMap::new();
1443 headers.insert(
1445 header::RANGE,
1446 HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(),
1447 );
1448 let url = match self.resource_url.borrow().as_ref() {
1449 Some(url) => url.clone(),
1450 None => self.blob_url.borrow().as_ref().unwrap().clone(),
1451 };
1452
1453 let cors_setting = cors_setting_for_element(self.upcast());
1454 let global = self.global();
1455 let request = create_a_potential_cors_request(
1456 Some(document.webview_id()),
1457 url.clone(),
1458 destination,
1459 cors_setting,
1460 None,
1461 global.get_referrer(),
1462 )
1463 .with_global_scope(&global)
1464 .headers(headers)
1465 .referrer_policy(document.get_referrer_policy());
1466
1467 let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1468 if let Some(ref mut current_fetch_context) = *current_fetch_context {
1469 current_fetch_context.cancel(CancelReason::Abort);
1470 }
1471
1472 *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1473 request.id,
1474 global.core_resource_thread(),
1475 ));
1476 let listener =
1477 HTMLMediaElementFetchListener::new(self, request.id, url, offset.unwrap_or(0));
1478
1479 self.owner_document().fetch_background(request, listener);
1480
1481 if let Some(seek_lock) = seek_lock {
1486 seek_lock.unlock(true);
1487 }
1488 }
1489
1490 fn eligible_for_autoplay(&self) -> bool {
1492 self.autoplaying.get() &&
1494
1495 self.Paused() &&
1497
1498 self.Autoplay() &&
1500
1501 {
1504 let document = self.owner_document();
1505
1506 !document.has_active_sandboxing_flag(
1507 SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1508 )
1509 }
1510
1511 }
1514
1515 fn resource_fetch_algorithm(&self, cx: &JSContext, resource: Resource) {
1517 if let Err(e) = self.create_media_player(&resource) {
1518 error!("Create media player error {:?}", e);
1519 self.resource_selection_algorithm_failure_steps(cx);
1520 return;
1521 }
1522
1523 match resource {
1532 Resource::Url(url) => {
1533 if self.Preload() == "none" && !self.autoplaying.get() {
1538 self.network_state.set(NetworkState::Idle);
1540
1541 self.queue_media_element_task_to_fire_event(atom!("suspend"));
1544
1545 let this = Trusted::new(self);
1549 let generation_id = self.generation_id.get();
1550
1551 self.owner_global()
1552 .task_manager()
1553 .media_element_task_source()
1554 .queue(task!(queue_delay_load_event: move |cx| {
1555 let this = this.root();
1556 if generation_id != this.generation_id.get() {
1557 return;
1558 }
1559
1560 this.delay_load_event(false, cx);
1561 }));
1562
1563 return;
1572 }
1573
1574 *self.resource_url.borrow_mut() = Some(url);
1575
1576 self.fetch_request(cx, None, None);
1578 },
1579 Resource::Object => {
1580 if let Some(ref src_object) = *self.src_object.borrow() {
1581 match src_object {
1582 SrcObject::Blob(blob) => {
1583 let blob_url = URL::CreateObjectURL(&self.global(), blob);
1584 *self.blob_url.borrow_mut() =
1585 Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1586 self.fetch_request(cx, None, None);
1587 },
1588 SrcObject::MediaStream(stream) => {
1589 let tracks = &*stream.get_tracks();
1590 for (pos, track) in tracks.iter().enumerate() {
1591 if self
1592 .player
1593 .borrow()
1594 .as_ref()
1595 .unwrap()
1596 .lock()
1597 .unwrap()
1598 .set_stream(&track.id(), pos == tracks.len() - 1)
1599 .is_err()
1600 {
1601 self.resource_selection_algorithm_failure_steps(cx);
1602 }
1603 }
1604 },
1605 }
1606 }
1607 },
1608 }
1609 }
1610
1611 fn queue_dedicated_media_source_failure_steps(&self) {
1615 let this = Trusted::new(self);
1616 let generation_id = self.generation_id.get();
1617 self.take_pending_play_promises(Err(Error::NotSupported(None)));
1618 self.owner_global()
1619 .task_manager()
1620 .media_element_task_source()
1621 .queue(task!(dedicated_media_source_failure_steps: move |cx| {
1622 let this = this.root();
1623 if generation_id != this.generation_id.get() {
1624 return;
1625 }
1626
1627 this.fulfill_in_flight_play_promises(cx, |cx| {
1628 this.error.set(Some(&*MediaError::new(
1631 cx,
1632 &this.owner_window(),
1633 MEDIA_ERR_SRC_NOT_SUPPORTED)));
1634
1635 this.AudioTracks(cx).clear();
1637 this.VideoTracks(cx).clear();
1638
1639 this.network_state.set(NetworkState::NoSource);
1642
1643 this.show_poster.set(true);
1645
1646 this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1648
1649 if let Some(ref player) = *this.player.borrow()
1650 && let Err(error) = player.lock().unwrap().stop() {
1651 error!("Could not stop player: {error:?}");
1652 }
1653
1654 });
1658
1659 this.delay_load_event(false, cx);
1662 }));
1663 }
1664
1665 fn in_error_state(&self) -> bool {
1666 self.error.get().is_some()
1667 }
1668
1669 fn is_potentially_playing(&self) -> bool {
1671 !self.paused.get() &&
1672 !self.ended_playback(LoopCondition::Included) &&
1673 self.error.get().is_none() &&
1674 !self.is_blocked_media_element()
1675 }
1676
1677 fn is_blocked_media_element(&self) -> bool {
1679 self.ready_state.get() <= ReadyState::HaveCurrentData ||
1680 self.is_paused_for_user_interaction() ||
1681 self.is_paused_for_in_band_content()
1682 }
1683
1684 fn is_paused_for_user_interaction(&self) -> bool {
1686 false
1689 }
1690
1691 fn is_paused_for_in_band_content(&self) -> bool {
1693 false
1696 }
1697
1698 fn media_element_load_algorithm(&self, cx: &mut JSContext) {
1700 self.fired_loadeddata_event.set(false);
1703
1704 self.generation_id.set(self.generation_id.get() + 1);
1709
1710 self.load_state.set(LoadState::NotLoaded);
1711 *self.source_children_pointer.borrow_mut() = None;
1712 self.current_source_child.set(None);
1713
1714 while !self.in_flight_play_promises_queue.borrow().is_empty() {
1721 self.fulfill_in_flight_play_promises(cx, |_| ());
1722 }
1723
1724 let network_state = self.network_state.get();
1729
1730 if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1734 self.queue_media_element_task_to_fire_event(atom!("abort"));
1735 }
1736
1737 self.reset_media_player();
1739
1740 if network_state != NetworkState::Empty {
1742 self.queue_media_element_task_to_fire_event(atom!("emptied"));
1745
1746 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1749 current_fetch_context.cancel(CancelReason::Abort);
1750 }
1751
1752 self.AudioTracks(cx).clear();
1757 self.VideoTracks(cx).clear();
1758
1759 if self.ready_state.get() != ReadyState::HaveNothing {
1761 self.change_ready_state(ReadyState::HaveNothing);
1762 }
1763
1764 if !self.Paused() {
1766 self.paused.set(true);
1768
1769 self.take_pending_play_promises(Err(Error::Abort(None)));
1772 self.fulfill_in_flight_play_promises(cx, |_| ());
1773 }
1774
1775 self.seeking.set(false);
1777
1778 self.current_seek_position.set(f64::NAN);
1779
1780 self.current_playback_position.set(0.);
1785 if self.official_playback_position.get() != 0. {
1786 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1787 }
1788 self.official_playback_position.set(0.);
1789
1790 self.duration.set(f64::NAN);
1794 }
1795
1796 self.playback_rate.set(self.default_playback_rate.get());
1798
1799 self.error.set(None);
1801 self.autoplaying.set(true);
1802
1803 self.invoke_resource_selection_algorithm(cx);
1805
1806 }
1808
1809 fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1812 let this = Trusted::new(self);
1813 let generation_id = self.generation_id.get();
1814
1815 self.owner_global()
1816 .task_manager()
1817 .media_element_task_source()
1818 .queue(task!(queue_event: move |cx| {
1819 let this = this.root();
1820 if generation_id != this.generation_id.get() {
1821 return;
1822 }
1823
1824 this.upcast::<EventTarget>().fire_event(cx, name);
1825 }));
1826 }
1827
1828 fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1830 self.pending_play_promises
1831 .borrow_mut()
1832 .push(promise.clone());
1833 }
1834
1835 fn take_pending_play_promises(&self, result: ErrorResult) {
1846 let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1847 self.in_flight_play_promises_queue
1848 .borrow_mut()
1849 .push_back((pending_play_promises.into(), result));
1850 }
1851
1852 fn fulfill_in_flight_play_promises<F>(&self, cx: &mut JSContext, f: F)
1861 where
1862 F: FnOnce(&mut JSContext),
1863 {
1864 let (promises, result) = self
1865 .in_flight_play_promises_queue
1866 .borrow_mut()
1867 .pop_front()
1868 .expect("there should be at least one list of in flight play promises");
1869 f(cx);
1870 for promise in &*promises {
1871 match result {
1872 Ok(ref value) => promise.resolve_native(cx, value),
1873 Err(ref error) => promise.reject_error(cx, error.clone()),
1874 }
1875 }
1876 }
1877
1878 pub(crate) fn handle_source_child_insertion(
1879 &self,
1880 source: &HTMLSourceElement,
1881 cx: &mut JSContext,
1882 ) {
1883 if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1887 return;
1888 }
1889
1890 if self.network_state.get() == NetworkState::Empty {
1891 self.invoke_resource_selection_algorithm(cx);
1892 return;
1893 }
1894
1895 if self.load_state.get() != LoadState::WaitingForSource {
1899 return;
1900 }
1901
1902 self.load_state.set(LoadState::LoadingFromSourceChild);
1903
1904 *self.source_children_pointer.borrow_mut() =
1905 Some(SourceChildrenPointer::new(DomRoot::from_ref(source), true));
1906
1907 let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1909 elem: DomRoot::from_ref(self),
1910 generation_id: self.generation_id.get(),
1911 };
1912
1913 ScriptThread::await_stable_state(cx, Microtask::MediaElement(task));
1914 }
1915
1916 fn select_next_source_child_after_wait(&self, cx: &mut JSContext) {
1918 self.delay_load_event(true, cx);
1921
1922 self.network_state.set(NetworkState::Loading);
1924
1925 self.select_next_source_child(cx);
1927 }
1928
1929 fn media_data_processing_failure_steps(&self, cx: &JSContext) {
1934 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1936 current_fetch_context.cancel(CancelReason::Error);
1937 }
1938
1939 self.resource_selection_algorithm_failure_steps(cx);
1941 }
1942
1943 fn media_data_processing_fatal_steps(&self, error: u16, cx: &mut JSContext) {
1947 *self.source_children_pointer.borrow_mut() = None;
1948 self.current_source_child.set(None);
1949
1950 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1952 current_fetch_context.cancel(CancelReason::Error);
1953 }
1954
1955 self.error
1958 .set(Some(&*MediaError::new(cx, &self.owner_window(), error)));
1959
1960 self.network_state.set(NetworkState::Idle);
1962
1963 self.delay_load_event(false, cx);
1966
1967 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1969
1970 }
1972
1973 fn seek(&self, time: f64, _approximate_for_speed: bool) {
1975 self.show_poster.set(false);
1977
1978 if self.ready_state.get() == ReadyState::HaveNothing {
1980 return;
1981 }
1982
1983 self.current_seek_position.set(f64::NAN);
1987
1988 self.seeking.set(true);
1990
1991 let time = f64::min(time, self.Duration());
1997
1998 let time = f64::max(time, self.earliest_possible_position());
2001
2002 let seekable = self.seekable();
2008
2009 if seekable.is_empty() {
2010 self.seeking.set(false);
2011 return;
2012 }
2013
2014 let mut nearest_seekable_position = 0.0;
2015 let mut in_seekable_range = false;
2016 let mut nearest_seekable_distance = f64::MAX;
2017 for i in 0..seekable.len() {
2018 let start = seekable.start(i).unwrap().abs();
2019 let end = seekable.end(i).unwrap().abs();
2020 if time >= start && time <= end {
2021 nearest_seekable_position = time;
2022 in_seekable_range = true;
2023 break;
2024 } else if time < start {
2025 let distance = start - time;
2026 if distance < nearest_seekable_distance {
2027 nearest_seekable_distance = distance;
2028 nearest_seekable_position = start;
2029 }
2030 } else {
2031 let distance = time - end;
2032 if distance < nearest_seekable_distance {
2033 nearest_seekable_distance = distance;
2034 nearest_seekable_position = end;
2035 }
2036 }
2037 }
2038 let time = if in_seekable_range {
2039 time
2040 } else {
2041 nearest_seekable_position
2042 };
2043
2044 self.queue_media_element_task_to_fire_event(atom!("seeking"));
2055
2056 self.current_playback_position.set(time);
2058
2059 if let Some(ref player) = *self.player.borrow() &&
2060 let Err(error) = player.lock().unwrap().seek(time)
2061 {
2062 error!("Could not seek player: {error:?}");
2063 }
2064
2065 self.current_seek_position.set(time);
2066
2067 }
2073
2074 fn seek_end(&self) {
2076 self.official_playback_position
2079 .set(self.current_playback_position.get());
2080
2081 self.seeking.set(false);
2083
2084 self.current_seek_position.set(f64::NAN);
2085
2086 self.time_marches_on();
2088
2089 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2092
2093 self.queue_media_element_task_to_fire_event(atom!("seeked"));
2096 }
2097
2098 pub(crate) fn set_poster_frame(&self, image: Option<Arc<RasterImage>>) {
2100 if pref!(media_testing_enabled) && image.is_some() {
2101 self.queue_media_element_task_to_fire_event(atom!("postershown"));
2102 }
2103
2104 self.video_renderer.lock().unwrap().set_poster_frame(image);
2105
2106 self.upcast::<Node>().dirty(NodeDamage::Other);
2107 }
2108
2109 fn player_id(&self) -> Option<usize> {
2110 self.player
2111 .borrow()
2112 .as_ref()
2113 .map(|player| player.lock().unwrap().get_id())
2114 }
2115
2116 fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
2117 let stream_type = match *resource {
2118 Resource::Object => {
2119 if let Some(ref src_object) = *self.src_object.borrow() {
2120 match src_object {
2121 SrcObject::MediaStream(_) => StreamType::Stream,
2122 _ => StreamType::Seekable,
2123 }
2124 } else {
2125 return Err(());
2126 }
2127 },
2128 _ => StreamType::Seekable,
2129 };
2130
2131 let window = self.owner_window();
2132
2133 let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
2134 {
2135 HTMLMediaElementTypeId::HTMLAudioElement => None,
2136 HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
2137 };
2138
2139 let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
2140
2141 let pipeline_id = window.pipeline_id();
2142 let client_context_id =
2143 ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
2144
2145 let shared_player_id = Arc::new(OnceLock::new());
2148
2149 let event_handler = Arc::new(Mutex::new(HTMLMediaElementEventHandler::new(self)));
2150 let weak_event_handler = Arc::downgrade(&event_handler);
2151 *self.event_handler.borrow_mut() = Some(event_handler);
2152
2153 let task_source = self
2154 .owner_global()
2155 .task_manager()
2156 .media_element_task_source()
2157 .to_sendable();
2158
2159 let shared_player_id_clone = shared_player_id.clone();
2160 let action_callback = generic_channel::GenericCallback::new(move |message| {
2161 let event = message.unwrap();
2162 let weak_event_handler = weak_event_handler.clone();
2163
2164 let shared_player_id_clone = shared_player_id_clone.clone();
2165 task_source.queue(task!(handle_player_event: move |cx| {
2166 trace!("HTMLMediaElement event: {event:?}");
2167
2168 let Some(event_handler) = weak_event_handler.upgrade() else {
2169 return;
2170 };
2171
2172 if let Some(shared_player_id) = shared_player_id_clone.get() {
2173 event_handler.lock().unwrap().handle_player_event(*shared_player_id, event, cx);
2174 } else {
2175 error!("Player Action without ID being assigned yet.");
2176 }
2177 }));
2178 })
2179 .unwrap();
2180 let player = ServoMedia::get().create_player(
2181 &client_context_id,
2182 stream_type,
2183 action_callback,
2184 video_renderer,
2185 audio_renderer,
2186 Box::new(window.get_player_context()),
2187 );
2188
2189 let player_id = {
2190 let player_guard = player.lock().unwrap();
2191
2192 if let Err(error) = player_guard.set_mute(self.muted.get()) {
2193 warn!("Could not set mute state: {error:?}");
2194 }
2195
2196 let id = player_guard.get_id();
2197 if shared_player_id.set(id).is_err() {
2198 error!("Error setting player id. Already set?");
2199 }
2200 id
2201 };
2202
2203 *self.player.borrow_mut() = Some(player);
2204
2205 let task_source = self
2206 .owner_global()
2207 .task_manager()
2208 .media_element_task_source()
2209 .to_sendable();
2210 let weak_video_renderer = Arc::downgrade(&self.video_renderer);
2211
2212 self.video_renderer
2213 .lock()
2214 .unwrap()
2215 .setup(player_id, task_source, weak_video_renderer);
2216
2217 Ok(())
2218 }
2219
2220 fn reset_media_player(&self) {
2221 if self.player.borrow().is_none() {
2222 return;
2223 }
2224
2225 if let Some(ref player) = *self.player.borrow() &&
2226 let Err(error) = player.lock().unwrap().stop()
2227 {
2228 error!("Could not stop player: {error:?}");
2229 }
2230
2231 *self.player.borrow_mut() = None;
2232 self.video_renderer.lock().unwrap().reset();
2233 *self.event_handler.borrow_mut() = None;
2234
2235 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2236 video_element.set_natural_dimensions(None, None);
2237 }
2238 }
2239
2240 pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
2241 if let Some(ref player) = *self.player.borrow() &&
2242 let Err(error) = player.lock().unwrap().set_audio_track(idx as i32, enabled)
2243 {
2244 warn!("Could not set audio track {error:?}");
2245 }
2246 }
2247
2248 pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
2249 if let Some(ref player) = *self.player.borrow() &&
2250 let Err(error) = player.lock().unwrap().set_video_track(idx as i32, enabled)
2251 {
2252 warn!("Could not set video track: {error:?}");
2253 }
2254 }
2255
2256 fn direction_of_playback(&self) -> PlaybackDirection {
2258 if self.playback_rate.get() >= 0. {
2261 PlaybackDirection::Forwards
2262 } else {
2263 PlaybackDirection::Backwards
2264 }
2265 }
2266
2267 fn ended_playback(&self, loop_condition: LoopCondition) -> bool {
2269 if self.ready_state.get() < ReadyState::HaveMetadata {
2273 return false;
2274 }
2275
2276 let playback_position = self.current_playback_position.get();
2277
2278 match self.direction_of_playback() {
2279 PlaybackDirection::Forwards => {
2283 playback_position >= self.Duration() &&
2284 (loop_condition == LoopCondition::Ignored || !self.Loop())
2285 },
2286 PlaybackDirection::Backwards => playback_position <= self.earliest_possible_position(),
2289 }
2290 }
2291
2292 fn end_of_playback_in_forwards_direction(&self) {
2294 if self.Loop() {
2300 self.seek(
2301 self.earliest_possible_position(),
2302 false,
2303 );
2304 return;
2305 }
2306
2307 let this = Trusted::new(self);
2312 let generation_id = self.generation_id.get();
2313
2314 self.owner_global()
2315 .task_manager()
2316 .media_element_task_source()
2317 .queue(task!(reaches_the_end_steps: move |cx| {
2318 let this = this.root();
2319 if generation_id != this.generation_id.get() {
2320 return;
2321 }
2322
2323 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
2325
2326 if this.ended_playback(LoopCondition::Included) &&
2329 this.direction_of_playback() == PlaybackDirection::Forwards &&
2330 !this.Paused() {
2331 this.paused.set(true);
2333
2334 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
2336
2337 this.take_pending_play_promises(Err(Error::Abort(None)));
2340 this.fulfill_in_flight_play_promises(cx, |_| ());
2341 }
2342
2343 this.upcast::<EventTarget>().fire_event(cx, atom!("ended"));
2345 }));
2346
2347 self.change_ready_state(ReadyState::HaveCurrentData);
2349 }
2350
2351 fn end_of_playback_in_backwards_direction(&self) {
2353 if self.current_playback_position.get() <= self.earliest_possible_position() {
2358 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2359 }
2360 }
2361
2362 fn playback_end(&self) {
2363 if self.seeking.get() {
2365 return;
2366 }
2367
2368 match self.direction_of_playback() {
2369 PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(),
2370 PlaybackDirection::Backwards => self.end_of_playback_in_backwards_direction(),
2371 }
2372 }
2373
2374 fn playback_error(&self, error: &str, cx: &mut JSContext) {
2375 error!("Player error: {:?}", error);
2376
2377 if self.in_error_state() {
2381 return;
2382 }
2383
2384 if self.ready_state.get() == ReadyState::HaveNothing {
2386 self.media_data_processing_failure_steps(cx);
2389 } else {
2390 self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, cx);
2392 }
2393 }
2394
2395 fn playback_metadata_updated(
2396 &self,
2397 cx: &mut JSContext,
2398 metadata: &servo_media::player::metadata::Metadata,
2399 ) {
2400 if self.ready_state.get() != ReadyState::HaveNothing {
2403 return;
2404 }
2405
2406 for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2409 let audio_track_list = self.AudioTracks(cx);
2410
2411 let kind = match i {
2413 0 => DOMString::from("main"),
2414 _ => DOMString::new(),
2415 };
2416
2417 let audio_track = AudioTrack::new(
2418 cx,
2419 self.global().as_window(),
2420 DOMString::new(),
2421 kind,
2422 DOMString::new(),
2423 DOMString::new(),
2424 Some(&*audio_track_list),
2425 );
2426
2427 audio_track_list.add(&audio_track);
2430
2431 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2438 let fragment = MediaFragmentParser::from(servo_url);
2439 if let Some(id) = fragment.id() &&
2440 audio_track.id() == id
2441 {
2442 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2443 }
2444
2445 if fragment.tracks().contains(&audio_track.kind().into()) {
2446 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2447 }
2448 }
2449
2450 if audio_track_list.enabled_index().is_none() {
2455 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2456 }
2457
2458 let event = TrackEvent::new(
2461 cx,
2462 self.global().as_window(),
2463 atom!("addtrack"),
2464 false,
2465 false,
2466 &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2467 );
2468
2469 event
2470 .upcast::<Event>()
2471 .fire(cx, audio_track_list.upcast::<EventTarget>());
2472 }
2473
2474 for (i, _track) in metadata.video_tracks.iter().enumerate() {
2476 let video_track_list = self.VideoTracks(cx);
2477
2478 let kind = match i {
2480 0 => DOMString::from("main"),
2481 _ => DOMString::new(),
2482 };
2483
2484 let video_track = VideoTrack::new(
2485 cx,
2486 self.global().as_window(),
2487 DOMString::new(),
2488 kind,
2489 DOMString::new(),
2490 DOMString::new(),
2491 Some(&*video_track_list),
2492 );
2493
2494 video_track_list.add(&video_track);
2497
2498 if let Some(track) = video_track_list.item(0) &&
2505 let Some(servo_url) = self.resource_url.borrow().as_ref()
2506 {
2507 let fragment = MediaFragmentParser::from(servo_url);
2508 if let Some(id) = fragment.id() {
2509 if track.id() == id {
2510 video_track_list.set_selected(0, true);
2511 }
2512 } else if fragment.tracks().contains(&track.kind().into()) {
2513 video_track_list.set_selected(0, true);
2514 }
2515 }
2516
2517 if video_track_list.selected_index().is_none() {
2523 video_track_list.set_selected(video_track_list.len() - 1, true);
2524 }
2525
2526 let event = TrackEvent::new(
2529 cx,
2530 self.global().as_window(),
2531 atom!("addtrack"),
2532 false,
2533 false,
2534 &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2535 );
2536
2537 event
2538 .upcast::<Event>()
2539 .fire(cx, video_track_list.upcast::<EventTarget>());
2540 }
2541
2542 let earliest_possible_position = self.earliest_possible_position();
2555 self.current_playback_position
2556 .set(earliest_possible_position);
2557 self.official_playback_position
2558 .set(earliest_possible_position);
2559
2560 self.duration.set(
2566 metadata
2567 .duration
2568 .map_or(f64::INFINITY, |duration| duration.as_secs_f64()),
2569 );
2570 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2571
2572 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2576 video_element.set_natural_dimensions(Some(metadata.width), Some(metadata.height));
2577 self.queue_media_element_task_to_fire_event(atom!("resize"));
2578 }
2579
2580 self.change_ready_state(ReadyState::HaveMetadata);
2582
2583 let mut jumped = false;
2585
2586 if self.default_playback_start_position.get() > 0. {
2589 self.seek(
2590 self.default_playback_start_position.get(),
2591 false,
2592 );
2593 jumped = true;
2594 }
2595
2596 self.default_playback_start_position.set(0.);
2598
2599 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2604 let fragment = MediaFragmentParser::from(servo_url);
2605 if let Some(initial_playback_position) = fragment.start() &&
2606 initial_playback_position > 0. &&
2607 initial_playback_position < self.duration.get() &&
2608 !jumped
2609 {
2610 self.seek(
2611 initial_playback_position,
2612 false,
2613 )
2614 }
2615 }
2616
2617 let global = self.global();
2624 let window = global.as_window();
2625
2626 window.Navigator().MediaSession(cx).update_title(
2628 metadata
2629 .title
2630 .clone()
2631 .unwrap_or(window.get_url().into_string()),
2632 );
2633 }
2634
2635 fn playback_duration_changed(&self, duration: Option<Duration>) {
2636 let duration = duration.map_or(f64::INFINITY, |duration| duration.as_secs_f64());
2637
2638 if self.duration.get() == duration {
2639 return;
2640 }
2641
2642 self.duration.set(duration);
2643
2644 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2650
2651 if self.current_playback_position.get() > duration {
2655 self.seek(duration, false);
2656 }
2657 }
2658
2659 fn playback_video_frame_updated(&self) {
2660 let Some(video_element) = self.downcast::<HTMLVideoElement>() else {
2661 return;
2662 };
2663
2664 if self.ready_state.get() == ReadyState::HaveNothing {
2673 return;
2674 }
2675
2676 if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2677 if video_element
2678 .set_natural_dimensions(Some(frame.width as u32), Some(frame.height as u32))
2679 {
2680 self.queue_media_element_task_to_fire_event(atom!("resize"));
2681 } else {
2682 self.upcast::<Node>().dirty(NodeDamage::Other);
2685 }
2686 }
2687 }
2688
2689 fn playback_need_data(&self) {
2690 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() &&
2694 let Some(reason) = current_fetch_context.cancel_reason()
2695 {
2696 if *reason == CancelReason::Backoff {
2702 self.seek(
2703 self.current_playback_position.get(),
2704 false,
2705 );
2706 }
2707 return;
2708 }
2709
2710 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2711 let Err(e) = {
2712 let mut data_source = current_fetch_context.data_source().borrow_mut();
2713 data_source.set_locked(false);
2714 data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2715 }
2716 {
2717 if e == PlayerError::EnoughData {
2722 current_fetch_context.cancel(CancelReason::Backoff);
2723 }
2724 }
2725 }
2726
2727 fn playback_enough_data(&self) {
2728 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2733 current_fetch_context.is_seekable()
2734 {
2735 current_fetch_context.cancel(CancelReason::Backoff);
2736 }
2737 }
2738
2739 fn playback_position_changed(&self, cx: &mut JSContext, position: f64) {
2740 if self.seeking.get() {
2742 return;
2743 }
2744
2745 let _ = self
2746 .played
2747 .borrow_mut()
2748 .add(self.current_playback_position.get(), position);
2749 self.current_playback_position.set(position);
2750 self.official_playback_position.set(position);
2751 self.time_marches_on();
2752
2753 let media_position_state =
2754 MediaPositionState::new(self.duration.get(), self.playback_rate.get(), position);
2755 debug!(
2756 "Sending media session event set position state {:?}",
2757 media_position_state
2758 );
2759 self.send_media_session_event(
2760 cx,
2761 MediaSessionEvent::SetPositionState(media_position_state),
2762 );
2763 }
2764
2765 fn playback_seek_done(&self, cx: &JSContext, position: f64) {
2766 let delta = (position - self.current_seek_position.get()).abs();
2769 if !self.seeking.get() || delta > SEEK_POSITION_THRESHOLD {
2770 return;
2771 }
2772
2773 let task = MediaElementMicrotask::Seeked {
2776 elem: DomRoot::from_ref(self),
2777 generation_id: self.generation_id.get(),
2778 };
2779
2780 ScriptThread::await_stable_state(cx, Microtask::MediaElement(task));
2781 }
2782
2783 fn playback_state_changed(&self, cx: &mut JSContext, state: &PlaybackState) {
2784 let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2785 match *state {
2786 PlaybackState::Paused => {
2787 media_session_playback_state = MediaSessionPlaybackState::Paused;
2788 if self.ready_state.get() == ReadyState::HaveMetadata {
2789 self.change_ready_state(ReadyState::HaveEnoughData);
2790 }
2791 },
2792 PlaybackState::Playing => {
2793 media_session_playback_state = MediaSessionPlaybackState::Playing;
2794 if self.ready_state.get() == ReadyState::HaveMetadata {
2795 self.change_ready_state(ReadyState::HaveEnoughData);
2796 }
2797 },
2798 PlaybackState::Buffering => {
2799 return;
2803 },
2804 _ => {},
2805 };
2806 debug!(
2807 "Sending media session event playback state changed to {:?}",
2808 media_session_playback_state
2809 );
2810 self.send_media_session_event(
2811 cx,
2812 MediaSessionEvent::PlaybackStateChange(media_session_playback_state),
2813 );
2814 }
2815
2816 fn seekable(&self) -> TimeRangesContainer {
2817 let mut seekable = TimeRangesContainer::default();
2818 if let Some(ref player) = *self.player.borrow() {
2819 let ranges = player.lock().unwrap().seekable();
2820 for range in ranges {
2821 let _ = seekable.add(range.start, range.end);
2822 }
2823 }
2824 seekable
2825 }
2826
2827 fn earliest_possible_position(&self) -> f64 {
2829 self.seekable()
2830 .start(0)
2831 .unwrap_or_else(|_| self.current_playback_position.get())
2832 }
2833
2834 fn render_controls(&self, cx: &mut JSContext) {
2835 if self.upcast::<Element>().is_shadow_host() {
2836 return;
2838 }
2839
2840 let shadow_root = self.upcast::<Element>().attach_ua_shadow_root(cx, false);
2843 let document = self.owner_document();
2844 let script = Element::create(
2845 cx,
2846 QualName::new(None, ns!(html), local_name!("script")),
2847 None,
2848 &document,
2849 ElementCreator::ScriptCreated,
2850 CustomElementCreationMode::Asynchronous,
2851 None,
2852 );
2853 let id = Uuid::new_v4().to_string();
2859 document.register_media_controls(&id, &shadow_root);
2860 let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2861 *self.media_controls_id.borrow_mut() = Some(id);
2862 script
2863 .upcast::<Node>()
2864 .set_text_content_for_element(cx, Some(DOMString::from(media_controls_script)));
2865 if let Err(e) = shadow_root
2866 .upcast::<Node>()
2867 .AppendChild(cx, script.upcast::<Node>())
2868 {
2869 warn!("Could not render media controls {:?}", e);
2870 return;
2871 }
2872
2873 let style = Element::create(
2874 cx,
2875 QualName::new(None, ns!(html), local_name!("style")),
2876 None,
2877 &document,
2878 ElementCreator::ScriptCreated,
2879 CustomElementCreationMode::Asynchronous,
2880 None,
2881 );
2882
2883 style
2884 .upcast::<Node>()
2885 .set_text_content_for_element(cx, Some(DOMString::from(MEDIA_CONTROL_CSS)));
2886
2887 if let Err(e) = shadow_root
2888 .upcast::<Node>()
2889 .AppendChild(cx, style.upcast::<Node>())
2890 {
2891 warn!("Could not render media controls {:?}", e);
2892 }
2893
2894 self.upcast::<Node>().dirty(NodeDamage::Other);
2895 }
2896
2897 fn remove_controls(&self) {
2898 if let Some(id) = self.media_controls_id.borrow_mut().take() {
2899 self.owner_document().unregister_media_controls(&id);
2900 }
2901 }
2902
2903 pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2905 self.video_renderer
2906 .lock()
2907 .unwrap()
2908 .current_frame_holder
2909 .as_ref()
2910 .map(|holder| holder.get_frame())
2911 }
2912
2913 pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2916 let (current_frame, poster_frame) = {
2917 let renderer = self.video_renderer.lock().unwrap();
2918 (renderer.current_frame, renderer.poster_frame)
2919 };
2920
2921 if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2924 return poster_frame;
2925 }
2926
2927 current_frame
2928 }
2929
2930 pub(crate) fn set_audio_renderer(
2935 &self,
2936 audio_renderer: Option<Arc<Mutex<dyn AudioRenderer>>>,
2937 cx: &mut JSContext,
2938 ) {
2939 *self.audio_renderer.borrow_mut() = audio_renderer;
2940
2941 let had_player = {
2942 if let Some(ref player) = *self.player.borrow() {
2943 if let Err(error) = player.lock().unwrap().stop() {
2944 error!("Could not stop player: {error:?}");
2945 }
2946 true
2947 } else {
2948 false
2949 }
2950 };
2951
2952 if had_player {
2953 self.media_element_load_algorithm(cx);
2954 }
2955 }
2956
2957 fn send_media_session_event(&self, cx: &mut JSContext, event: MediaSessionEvent) {
2958 let global = self.global();
2959 let media_session = global.as_window().Navigator().MediaSession(cx);
2960
2961 media_session.register_media_instance(self);
2962
2963 media_session.send_event(event);
2964 }
2965
2966 pub(crate) fn origin_is_clean(&self) -> bool {
2968 if self.src_object.borrow().is_some() {
2970 return true;
2973 }
2974
2975 if self.resource_url.borrow().is_some() {
2977 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2981 return current_fetch_context.origin_is_clean();
2982 }
2983 }
2984
2985 true
2986 }
2987}
2988
2989impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
2990 fn NetworkState(&self) -> u16 {
2992 self.network_state.get() as u16
2993 }
2994
2995 fn ReadyState(&self) -> u16 {
2997 self.ready_state.get() as u16
2998 }
2999
3000 make_bool_getter!(Autoplay, "autoplay");
3002 make_bool_setter!(SetAutoplay, "autoplay");
3004
3005 make_bool_getter!(Loop, "loop");
3007 make_bool_setter!(SetLoop, "loop");
3009
3010 make_bool_getter!(DefaultMuted, "muted");
3012 make_bool_setter!(SetDefaultMuted, "muted");
3014
3015 make_bool_getter!(Controls, "controls");
3017 make_bool_setter!(SetControls, "controls");
3019
3020 make_url_getter!(Src, "src");
3022
3023 make_url_setter!(SetSrc, "src");
3025
3026 fn GetCrossOrigin(&self) -> Option<DOMString> {
3028 reflect_cross_origin_attribute(self.upcast::<Element>())
3029 }
3030 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
3032 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
3033 }
3034
3035 fn Muted(&self) -> bool {
3037 self.muted.get()
3038 }
3039
3040 fn SetMuted(&self, _cx: &mut JSContext, value: bool) {
3042 if self.muted.get() == value {
3043 return;
3044 }
3045
3046 self.muted.set(value);
3047
3048 if let Some(ref player) = *self.player.borrow() &&
3049 let Err(error) = player.lock().unwrap().set_mute(value)
3050 {
3051 warn!("Could not set mute state: {error:?}");
3052 }
3053
3054 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3057
3058 if !self.is_allowed_to_play() {
3061 self.internal_pause_steps();
3062 }
3063 }
3064
3065 fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
3067 (*self.src_object.borrow())
3068 .as_ref()
3069 .map(|src_object| match src_object {
3070 SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
3071 SrcObject::MediaStream(stream) => {
3072 MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
3073 },
3074 })
3075 }
3076
3077 fn SetSrcObject(&self, cx: &mut JSContext, value: Option<MediaStreamOrBlob>) {
3079 *self.src_object.borrow_mut() = value.map(|value| value.into());
3080 self.media_element_load_algorithm(cx);
3081 }
3082
3083 make_enumerated_getter!(
3086 Preload,
3087 "preload",
3088 "none" | "metadata" | "auto",
3089 missing => "auto",
3090 invalid => "auto"
3091 );
3092
3093 make_setter!(SetPreload, "preload");
3095
3096 fn CurrentSrc(&self) -> USVString {
3098 USVString(self.current_src.borrow().clone())
3099 }
3100
3101 fn Load(&self, cx: &mut JSContext) {
3103 self.media_element_load_algorithm(cx);
3104 }
3105
3106 fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
3108 match ServoMedia::get().can_play_type(&type_.str()) {
3109 SupportsMediaType::No => CanPlayTypeResult::_empty,
3110 SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
3111 SupportsMediaType::Probably => CanPlayTypeResult::Probably,
3112 }
3113 }
3114
3115 fn GetError(&self) -> Option<DomRoot<MediaError>> {
3117 self.error.get()
3118 }
3119
3120 fn Play(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
3122 let promise = Promise::new_in_realm(cx);
3123
3124 if self
3131 .error
3132 .get()
3133 .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
3134 {
3135 promise.reject_error(cx, Error::NotSupported(None));
3136 return promise;
3137 }
3138
3139 self.push_pending_play_promise(&promise);
3142
3143 self.internal_play_steps(cx);
3145
3146 promise
3148 }
3149
3150 fn Pause(&self, cx: &mut JSContext) {
3152 if self.network_state.get() == NetworkState::Empty {
3155 self.invoke_resource_selection_algorithm(cx);
3156 }
3157
3158 self.internal_pause_steps();
3160 }
3161
3162 fn Paused(&self) -> bool {
3164 self.paused.get()
3165 }
3166
3167 fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
3169 Ok(Finite::wrap(self.default_playback_rate.get()))
3170 }
3171
3172 fn SetDefaultPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3174 let min_allowed = -64.0;
3177 let max_allowed = 64.0;
3178 if *value < min_allowed || *value > max_allowed {
3179 return Err(Error::NotSupported(None));
3180 }
3181
3182 if self.default_playback_rate.get() == *value {
3183 return Ok(());
3184 }
3185
3186 self.default_playback_rate.set(*value);
3187
3188 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3191
3192 Ok(())
3193 }
3194
3195 fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
3197 Ok(Finite::wrap(self.playback_rate.get()))
3198 }
3199
3200 fn SetPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3202 let min_allowed = -64.0;
3207 let max_allowed = 64.0;
3208 if *value < min_allowed || *value > max_allowed {
3209 return Err(Error::NotSupported(None));
3210 }
3211
3212 if self.playback_rate.get() == *value {
3213 return Ok(());
3214 }
3215
3216 self.playback_rate.set(*value);
3219
3220 if self.is_potentially_playing() &&
3221 let Some(ref player) = *self.player.borrow() &&
3222 let Err(error) = player.lock().unwrap().set_playback_rate(*value)
3223 {
3224 warn!("Could not set the playback rate: {error:?}");
3225 }
3226
3227 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3230
3231 Ok(())
3232 }
3233
3234 fn Duration(&self) -> f64 {
3236 self.duration.get()
3237 }
3238
3239 fn CurrentTime(&self) -> Finite<f64> {
3241 Finite::wrap(if self.default_playback_start_position.get() != 0. {
3242 self.default_playback_start_position.get()
3243 } else if self.seeking.get() {
3244 self.current_seek_position.get()
3249 } else {
3250 self.official_playback_position.get()
3251 })
3252 }
3253
3254 fn SetCurrentTime(&self, _cx: &mut JSContext, time: Finite<f64>) {
3256 if self.ready_state.get() == ReadyState::HaveNothing {
3257 self.default_playback_start_position.set(*time);
3258 } else {
3259 self.official_playback_position.set(*time);
3260 self.seek(*time, false);
3261 }
3262 }
3263
3264 fn Seeking(&self) -> bool {
3266 self.seeking.get()
3267 }
3268
3269 fn Ended(&self) -> bool {
3271 self.ended_playback(LoopCondition::Included) &&
3272 self.direction_of_playback() == PlaybackDirection::Forwards
3273 }
3274
3275 fn FastSeek(&self, time: Finite<f64>) {
3277 self.seek(*time, true);
3278 }
3279
3280 fn Played(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3282 TimeRanges::new(cx, self.global().as_window(), self.played.borrow().clone())
3283 }
3284
3285 fn Seekable(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3287 TimeRanges::new(cx, self.global().as_window(), self.seekable())
3288 }
3289
3290 fn Buffered(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3292 let mut buffered = TimeRangesContainer::default();
3293 if let Some(ref player) = *self.player.borrow() {
3294 let ranges = player.lock().unwrap().buffered();
3295 for range in ranges {
3296 let _ = buffered.add(range.start, range.end);
3297 }
3298 }
3299 TimeRanges::new(cx, self.global().as_window(), buffered)
3300 }
3301
3302 fn AudioTracks(&self, cx: &mut JSContext) -> DomRoot<AudioTrackList> {
3304 let window = self.owner_window();
3305 self.audio_tracks_list
3306 .or_init(|| AudioTrackList::new(cx, &window, &[], Some(self)))
3307 }
3308
3309 fn VideoTracks(&self, cx: &mut JSContext) -> DomRoot<VideoTrackList> {
3311 let window = self.owner_window();
3312 self.video_tracks_list
3313 .or_init(|| VideoTrackList::new(cx, &window, &[], Some(self)))
3314 }
3315
3316 fn TextTracks(&self, cx: &mut JSContext) -> DomRoot<TextTrackList> {
3318 let window = self.owner_window();
3319 self.text_tracks_list
3320 .or_init(|| TextTrackList::new(&window, &[], CanGc::from_cx(cx)))
3321 }
3322
3323 fn AddTextTrack(
3325 &self,
3326 cx: &mut JSContext,
3327 kind: TextTrackKind,
3328 label: DOMString,
3329 language: DOMString,
3330 ) -> DomRoot<TextTrack> {
3331 let window = self.owner_window();
3332 let track = TextTrack::new(
3335 &window,
3336 "".into(),
3337 kind,
3338 label,
3339 language,
3340 TextTrackMode::Hidden,
3341 None,
3342 CanGc::from_cx(cx),
3343 );
3344 self.TextTracks(cx).add(&track);
3346 DomRoot::from_ref(&track)
3348 }
3349
3350 fn GetVolume(&self) -> Fallible<Finite<f64>> {
3352 Ok(Finite::wrap(self.volume.get()))
3353 }
3354
3355 fn SetVolume(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3357 let minimum_volume = 0.0;
3360 let maximum_volume = 1.0;
3361 if *value < minimum_volume || *value > maximum_volume {
3362 return Err(Error::IndexSize(None));
3363 }
3364
3365 if self.volume.get() == *value {
3366 return Ok(());
3367 }
3368
3369 self.volume.set(*value);
3370
3371 if let Some(ref player) = *self.player.borrow() &&
3372 let Err(error) = player.lock().unwrap().set_volume(*value)
3373 {
3374 warn!("Could not set the volume: {error:?}");
3375 }
3376
3377 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3380
3381 if !self.is_allowed_to_play() {
3384 self.internal_pause_steps();
3385 }
3386
3387 Ok(())
3388 }
3389}
3390
3391impl VirtualMethods for HTMLMediaElement {
3392 fn super_type(&self) -> Option<&dyn VirtualMethods> {
3393 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3394 }
3395
3396 fn attribute_mutated(
3397 &self,
3398 cx: &mut JSContext,
3399 attr: AttrRef<'_>,
3400 mutation: AttributeMutation,
3401 ) {
3402 self.super_type()
3403 .unwrap()
3404 .attribute_mutated(cx, attr, mutation);
3405
3406 match *attr.local_name() {
3407 local_name!("muted") => {
3408 if let AttributeMutation::Set(
3412 _,
3413 AttributeMutationReason::ByCloning | AttributeMutationReason::ByParser,
3414 ) = mutation
3415 {
3416 self.SetMuted(cx, true);
3417 }
3418 },
3419 local_name!("src") => {
3420 if !mutation.is_removal() {
3425 self.media_element_load_algorithm(cx);
3426 }
3427 },
3428 local_name!("controls") => {
3429 if mutation.new_value(attr).is_some() {
3430 self.render_controls(cx);
3431 } else {
3432 self.remove_controls();
3433 }
3434 },
3435 _ => (),
3436 };
3437 }
3438
3439 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
3441 self.super_type().unwrap().unbind_from_tree(cx, context);
3442
3443 self.remove_controls();
3444
3445 if context.tree_connected {
3449 let task = MediaElementMicrotask::PauseIfNotInDocument {
3450 elem: DomRoot::from_ref(self),
3451 };
3452 ScriptThread::await_stable_state(cx, Microtask::MediaElement(task));
3453 }
3454 }
3455
3456 fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
3457 self.super_type().unwrap().adopting_steps(cx, old_doc);
3458
3459 if let Some(id) = &*self.media_controls_id.borrow() {
3463 let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3464 error!("Missing media controls shadow root");
3465 return;
3466 };
3467
3468 old_doc.unregister_media_controls(id);
3469 self.owner_document()
3470 .register_media_controls(id, &shadow_root);
3471 }
3472 }
3473}
3474
3475#[derive(JSTraceable, MallocSizeOf)]
3476pub(crate) enum MediaElementMicrotask {
3477 ResourceSelection {
3478 elem: DomRoot<HTMLMediaElement>,
3479 generation_id: u32,
3480 #[no_trace]
3481 base_url: ServoUrl,
3482 },
3483 PauseIfNotInDocument {
3484 elem: DomRoot<HTMLMediaElement>,
3485 },
3486 Seeked {
3487 elem: DomRoot<HTMLMediaElement>,
3488 generation_id: u32,
3489 },
3490 SelectNextSourceChild {
3491 elem: DomRoot<HTMLMediaElement>,
3492 generation_id: u32,
3493 },
3494 SelectNextSourceChildAfterWait {
3495 elem: DomRoot<HTMLMediaElement>,
3496 generation_id: u32,
3497 },
3498}
3499
3500impl MicrotaskRunnable for MediaElementMicrotask {
3501 fn handler(&self, cx: &mut JSContext) {
3502 match self {
3503 &MediaElementMicrotask::ResourceSelection {
3504 ref elem,
3505 generation_id,
3506 ref base_url,
3507 } => {
3508 if generation_id == elem.generation_id.get() {
3509 elem.resource_selection_algorithm_sync(base_url.clone(), cx);
3510 }
3511 },
3512 MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3514 if elem.upcast::<Node>().is_connected() {
3516 return;
3517 }
3518 elem.internal_pause_steps();
3520 },
3521 &MediaElementMicrotask::Seeked {
3522 ref elem,
3523 generation_id,
3524 } => {
3525 if generation_id == elem.generation_id.get() {
3526 elem.seek_end();
3527 }
3528 },
3529 &MediaElementMicrotask::SelectNextSourceChild {
3530 ref elem,
3531 generation_id,
3532 } => {
3533 if generation_id == elem.generation_id.get() {
3534 elem.select_next_source_child(cx);
3535 }
3536 },
3537 &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3538 ref elem,
3539 generation_id,
3540 } => {
3541 if generation_id == elem.generation_id.get() {
3542 elem.select_next_source_child_after_wait(cx);
3543 }
3544 },
3545 }
3546 }
3547
3548 fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx> {
3549 match self {
3550 &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3551 &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3552 &MediaElementMicrotask::Seeked { ref elem, .. } |
3553 &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3554 &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3555 enter_auto_realm(cx, &**elem)
3556 },
3557 }
3558 }
3559}
3560
3561enum Resource {
3562 Object,
3563 Url(ServoUrl),
3564}
3565
3566#[derive(Debug, MallocSizeOf, PartialEq)]
3567enum DataBuffer {
3568 Payload(Vec<u8>),
3569 EndOfStream,
3570}
3571
3572#[derive(MallocSizeOf)]
3573struct BufferedDataSource {
3574 locked: Cell<bool>,
3579 buffers: VecDeque<DataBuffer>,
3581}
3582
3583impl BufferedDataSource {
3584 fn new() -> BufferedDataSource {
3585 BufferedDataSource {
3586 locked: Cell::new(true),
3587 buffers: VecDeque::default(),
3588 }
3589 }
3590
3591 fn set_locked(&self, locked: bool) {
3592 self.locked.set(locked)
3593 }
3594
3595 fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3596 debug_assert_ne!(
3597 self.buffers.back(),
3598 Some(&DataBuffer::EndOfStream),
3599 "The media backend not expects any further data after end of stream"
3600 );
3601
3602 self.buffers.push_back(buffer);
3603 }
3604
3605 fn process_into_player_from_queue(
3606 &mut self,
3607 player: &Arc<Mutex<dyn Player>>,
3608 ) -> Result<(), PlayerError> {
3609 if self.locked.get() {
3611 return Ok(());
3612 }
3613
3614 while let Some(buffer) = self.buffers.pop_front() {
3615 match buffer {
3616 DataBuffer::Payload(payload) => {
3617 if let Err(error) = player.lock().unwrap().push_data(payload) {
3618 warn!("Could not push input data to player: {error:?}");
3619 return Err(error);
3620 }
3621 },
3622 DataBuffer::EndOfStream => {
3623 if let Err(error) = player.lock().unwrap().end_of_stream() {
3624 warn!("Could not signal EOS to player: {error:?}");
3625 return Err(error);
3626 }
3627 },
3628 }
3629 }
3630
3631 Ok(())
3632 }
3633
3634 fn reset(&mut self) {
3635 self.locked.set(true);
3636 self.buffers.clear();
3637 }
3638}
3639
3640#[derive(Debug, MallocSizeOf, PartialEq)]
3642enum CancelReason {
3643 Backoff,
3645 Error,
3647 Abort,
3649}
3650
3651#[derive(MallocSizeOf)]
3652pub(crate) struct HTMLMediaElementFetchContext {
3653 request_id: RequestId,
3655 cancel_reason: Option<CancelReason>,
3657 is_seekable: bool,
3659 origin_clean: bool,
3661 data_source: RefCell<BufferedDataSource>,
3663 fetch_canceller: FetchCanceller,
3666}
3667
3668impl HTMLMediaElementFetchContext {
3669 fn new(
3670 request_id: RequestId,
3671 core_resource_thread: CoreResourceThread,
3672 ) -> HTMLMediaElementFetchContext {
3673 HTMLMediaElementFetchContext {
3674 request_id,
3675 cancel_reason: None,
3676 is_seekable: false,
3677 origin_clean: true,
3678 data_source: RefCell::new(BufferedDataSource::new()),
3679 fetch_canceller: FetchCanceller::new(request_id, false, core_resource_thread),
3680 }
3681 }
3682
3683 fn request_id(&self) -> RequestId {
3684 self.request_id
3685 }
3686
3687 fn is_seekable(&self) -> bool {
3688 self.is_seekable
3689 }
3690
3691 fn set_seekable(&mut self, seekable: bool) {
3692 self.is_seekable = seekable;
3693 }
3694
3695 fn origin_is_clean(&self) -> bool {
3696 self.origin_clean
3697 }
3698
3699 fn set_origin_clean(&mut self, origin_clean: bool) {
3700 self.origin_clean = origin_clean;
3701 }
3702
3703 fn data_source(&self) -> &RefCell<BufferedDataSource> {
3704 &self.data_source
3705 }
3706
3707 fn cancel(&mut self, reason: CancelReason) {
3708 if self.cancel_reason.is_some() {
3709 return;
3710 }
3711 self.cancel_reason = Some(reason);
3712 self.data_source.borrow_mut().reset();
3713 self.fetch_canceller.abort();
3714 }
3715
3716 fn cancel_reason(&self) -> &Option<CancelReason> {
3717 &self.cancel_reason
3718 }
3719}
3720
3721struct HTMLMediaElementFetchListener {
3722 element: Trusted<HTMLMediaElement>,
3724 generation_id: u32,
3726 request_id: RequestId,
3728 next_progress_event: Instant,
3730 url: ServoUrl,
3732 expected_content_length: Option<u64>,
3734 fetched_content_length: u64,
3736 content_length_to_discard: u64,
3740}
3741
3742impl FetchResponseListener for HTMLMediaElementFetchListener {
3743 fn process_request_body(&mut self, _: RequestId) {}
3744
3745 fn process_response(
3746 &mut self,
3747 cx: &mut JSContext,
3748 _: RequestId,
3749 metadata: Result<FetchMetadata, NetworkError>,
3750 ) {
3751 let element = self.element.root();
3752
3753 let (metadata, origin_clean) = match metadata {
3754 Ok(fetch_metadata) => match fetch_metadata {
3755 FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3756 FetchMetadata::Filtered { filtered, unsafe_ } => (
3757 Some(unsafe_),
3758 matches!(
3759 filtered,
3760 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3761 ),
3762 ),
3763 },
3764 Err(_) => (None, true),
3765 };
3766
3767 let (status_is_success, is_seekable) =
3768 metadata.as_ref().map_or((false, false), |metadata| {
3769 let status = &metadata.status;
3770 (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3771 });
3772
3773 if !status_is_success {
3775 if element.ready_state.get() == ReadyState::HaveNothing {
3776 element.media_data_processing_failure_steps(cx);
3778 } else {
3779 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3781 }
3782 return;
3783 }
3784
3785 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3786 current_fetch_context.set_seekable(is_seekable);
3787 current_fetch_context.set_origin_clean(origin_clean);
3788 }
3789
3790 if let Some(metadata) = metadata.as_ref() &&
3791 let Some(headers) = metadata.headers.as_ref()
3792 {
3793 let content_length = if let Some(content_range) = headers.typed_get::<ContentRange>() {
3796 content_range.bytes_len()
3797 } else {
3798 headers
3799 .typed_get::<ContentLength>()
3800 .map(|content_length| content_length.0)
3801 };
3802
3803 if content_length != self.expected_content_length &&
3805 let Some(content_length) = content_length
3806 {
3807 self.expected_content_length = Some(content_length);
3808 }
3809 }
3810
3811 if let Err(e) = element
3813 .player
3814 .borrow()
3815 .as_ref()
3816 .unwrap()
3817 .lock()
3818 .unwrap()
3819 .set_seekable(is_seekable)
3820 {
3821 warn!("Could not set player seekable {:?}", e);
3822 }
3823
3824 if let Some(expected_content_length) = self.expected_content_length &&
3825 let Err(e) = element
3826 .player
3827 .borrow()
3828 .as_ref()
3829 .unwrap()
3830 .lock()
3831 .unwrap()
3832 .set_input_size(expected_content_length)
3833 {
3834 warn!("Could not set player input size {:?}", e);
3835 }
3836 }
3837
3838 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
3839 let element = self.element.root();
3840
3841 self.fetched_content_length += chunk.len() as u64;
3842
3843 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3845 if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3846 return;
3847 }
3848
3849 let payload = if !current_fetch_context.is_seekable() &&
3851 self.content_length_to_discard != 0
3852 {
3853 if chunk.len() as u64 > self.content_length_to_discard {
3854 let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
3855 self.content_length_to_discard = 0;
3856 shrink_chunk
3857 } else {
3858 self.content_length_to_discard -= chunk.len() as u64;
3860 return;
3861 }
3862 } else {
3863 chunk
3864 };
3865
3866 if let Err(e) = {
3867 let mut data_source = current_fetch_context.data_source().borrow_mut();
3868 data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
3869 data_source
3870 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3871 } {
3872 if e == PlayerError::EnoughData {
3877 current_fetch_context.cancel(CancelReason::Backoff);
3878 }
3879 return;
3880 }
3881 }
3882
3883 if Instant::now() > self.next_progress_event {
3888 element.queue_media_element_task_to_fire_event(atom!("progress"));
3889 self.next_progress_event = Instant::now() + Duration::from_millis(350);
3890 }
3891 }
3892
3893 fn process_response_eof(
3894 self,
3895 cx: &mut JSContext,
3896 _: RequestId,
3897 status: Result<(), NetworkError>,
3898 timing: ResourceFetchTiming,
3899 ) {
3900 let element = self.element.root();
3901
3902 if status.is_ok() && self.fetched_content_length != 0 {
3904 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
3909 {
3910 if self.expected_content_length.is_none() &&
3917 let Err(e) = element
3918 .player
3919 .borrow()
3920 .as_ref()
3921 .unwrap()
3922 .lock()
3923 .unwrap()
3924 .set_input_size(self.fetched_content_length)
3925 {
3926 warn!("Could not set player input size {:?}", e);
3927 }
3928
3929 let mut data_source = current_fetch_context.data_source().borrow_mut();
3930
3931 data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
3932 let _ = data_source
3933 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
3934 }
3935
3936 element
3938 .upcast::<EventTarget>()
3939 .fire_event(cx, atom!("progress"));
3940
3941 element.network_state.set(NetworkState::Idle);
3944
3945 element
3946 .upcast::<EventTarget>()
3947 .fire_event(cx, atom!("suspend"));
3948 } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
3949 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3951 } else {
3952 element.media_data_processing_failure_steps(cx);
3955 }
3956
3957 network_listener::submit_timing(cx, &self, &status, &timing);
3958 }
3959
3960 fn process_csp_violations(
3961 &mut self,
3962 cx: &mut js::context::JSContext,
3963 _request_id: RequestId,
3964 violations: Vec<Violation>,
3965 ) {
3966 let global = &self.resource_timing_global();
3967 global.report_csp_violations(cx, violations, None, None);
3968 }
3969
3970 fn should_invoke(&self) -> bool {
3971 let element = self.element.root();
3972
3973 if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
3974 return false;
3975 }
3976
3977 let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
3978 return false;
3979 };
3980
3981 if current_fetch_context.request_id() != self.request_id {
3983 return false;
3984 }
3985
3986 if let Some(cancel_reason) = current_fetch_context.cancel_reason() &&
3989 matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort)
3990 {
3991 return false;
3992 }
3993
3994 true
3995 }
3996}
3997
3998impl ResourceTimingListener for HTMLMediaElementFetchListener {
3999 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
4000 let initiator_type = InitiatorType::LocalName(
4001 self.element
4002 .root()
4003 .upcast::<Element>()
4004 .local_name()
4005 .to_string(),
4006 );
4007 (initiator_type, self.url.clone())
4008 }
4009
4010 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
4011 self.element.root().owner_document().global()
4012 }
4013}
4014
4015impl HTMLMediaElementFetchListener {
4016 fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
4017 Self {
4018 element: Trusted::new(element),
4019 generation_id: element.generation_id.get(),
4020 request_id,
4021 next_progress_event: Instant::now() + Duration::from_millis(350),
4022 url,
4023 expected_content_length: None,
4024 fetched_content_length: 0,
4025 content_length_to_discard: offset,
4026 }
4027 }
4028}
4029
4030#[derive(JSTraceable, MallocSizeOf)]
4034struct HTMLMediaElementEventHandler {
4035 element: WeakRef<HTMLMediaElement>,
4036}
4037
4038#[expect(unsafe_code)]
4039unsafe impl Send for HTMLMediaElementEventHandler {}
4040
4041impl HTMLMediaElementEventHandler {
4042 fn new(element: &HTMLMediaElement) -> Self {
4043 Self {
4044 element: WeakRef::new(element),
4045 }
4046 }
4047
4048 fn handle_player_event(&self, player_id: usize, event: PlayerEvent, cx: &mut JSContext) {
4049 let Some(element) = self.element.root() else {
4050 return;
4051 };
4052
4053 if element.player_id().is_none_or(|id| id != player_id) {
4055 return;
4056 }
4057
4058 match event {
4059 PlayerEvent::DurationChanged(duration) => element.playback_duration_changed(duration),
4060 PlayerEvent::EndOfStream => element.playback_end(),
4061 PlayerEvent::EnoughData => element.playback_enough_data(),
4062 PlayerEvent::Error(ref error) => element.playback_error(error, cx),
4063 PlayerEvent::MetadataUpdated(ref metadata) => {
4064 element.playback_metadata_updated(cx, metadata)
4065 },
4066 PlayerEvent::NeedData => element.playback_need_data(),
4067 PlayerEvent::PositionChanged(position) => {
4068 element.playback_position_changed(cx, position)
4069 },
4070 PlayerEvent::SeekData(offset, seek_lock) => {
4071 element.fetch_request(cx, Some(offset), Some(seek_lock))
4072 },
4073 PlayerEvent::SeekDone(position) => element.playback_seek_done(cx, position),
4074 PlayerEvent::StateChanged(ref state) => element.playback_state_changed(cx, state),
4075 PlayerEvent::VideoFrameUpdated => element.playback_video_frame_updated(),
4076 }
4077 }
4078}
4079
4080impl Drop for HTMLMediaElementEventHandler {
4081 fn drop(&mut self) {
4082 assert_in_script();
4086 }
4087}