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::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::{
62 TextTrackKind, TextTrackMethods, TextTrackMode,
63};
64use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
65use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
66use crate::dom::bindings::codegen::UnionTypes::{
67 MediaStreamOrBlob, VideoTrackOrAudioTrackOrTextTrack,
68};
69use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
70use crate::dom::bindings::inheritance::Castable;
71use crate::dom::bindings::num::Finite;
72use crate::dom::bindings::refcounted::Trusted;
73use crate::dom::bindings::reflector::DomGlobal;
74use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, UnrootedDom};
75use crate::dom::bindings::str::{DOMString, USVString};
76use crate::dom::blob::Blob;
77use crate::dom::csp::{GlobalCspReporting, Violation};
78use crate::dom::document::Document;
79use crate::dom::element::attributes::storage::AttrRef;
80use crate::dom::element::{
81 AttributeMutation, AttributeMutationReason, CustomElementCreationMode, Element, ElementCreator,
82 cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute,
83};
84use crate::dom::event::Event;
85use crate::dom::eventtarget::EventTarget;
86use crate::dom::globalscope::GlobalScope;
87use crate::dom::html::htmlelement::HTMLElement;
88use crate::dom::html::htmlsourceelement::HTMLSourceElement;
89use crate::dom::html::htmlvideoelement::HTMLVideoElement;
90use crate::dom::mediaerror::MediaError;
91use crate::dom::mediafragmentparser::MediaFragmentParser;
92use crate::dom::medialist::MediaList;
93use crate::dom::mediastream::MediaStream;
94use crate::dom::node::virtualmethods::VirtualMethods;
95use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext};
96use crate::dom::performance::performanceresourcetiming::InitiatorType;
97use crate::dom::promise::Promise;
98use crate::dom::referrer_policy_for_element;
99use crate::dom::texttrack::TextTrack;
100use crate::dom::texttracklist::TextTrackList;
101use crate::dom::timeranges::{TimeRanges, TimeRangesContainer};
102use crate::dom::trackevent::TrackEvent;
103use crate::dom::url::URL;
104use crate::dom::videotrack::VideoTrack;
105use crate::dom::videotracklist::VideoTrackList;
106use crate::fetch::{FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request};
107use crate::microtask::MicrotaskRunnable;
108use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
109use crate::realms::enter_auto_realm;
110use crate::script_thread::ScriptThread;
111use crate::task_source::SendableTaskSource;
112
113static MEDIA_CONTROL_CSS: &str = include_str!("../../resources/media-controls.css");
115
116static MEDIA_CONTROL_JS: &str = include_str!("../../resources/media-controls.js");
118
119const SEEK_POSITION_THRESHOLD: f64 = 0.5;
123
124#[derive(MallocSizeOf, PartialEq)]
125enum FrameStatus {
126 Locked,
127 Unlocked,
128}
129
130#[derive(MallocSizeOf)]
131struct FrameHolder(
132 FrameStatus,
133 #[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
134);
135
136impl FrameHolder {
137 fn new(frame: VideoFrame) -> FrameHolder {
138 FrameHolder(FrameStatus::Unlocked, frame)
139 }
140
141 fn lock(&mut self) {
142 if self.0 == FrameStatus::Unlocked {
143 self.0 = FrameStatus::Locked;
144 };
145 }
146
147 fn unlock(&mut self) {
148 if self.0 == FrameStatus::Locked {
149 self.0 = FrameStatus::Unlocked;
150 };
151 }
152
153 fn set(&mut self, new_frame: VideoFrame) {
154 if self.0 == FrameStatus::Unlocked {
155 self.1 = new_frame
156 };
157 }
158
159 fn get(&self) -> (u32, Size2D<i32>, usize) {
160 if self.0 == FrameStatus::Locked {
161 (
162 self.1.get_texture_id(),
163 Size2D::new(self.1.get_width(), self.1.get_height()),
164 0,
165 )
166 } else {
167 unreachable!();
168 }
169 }
170
171 fn get_frame(&self) -> VideoFrame {
172 self.1.clone()
173 }
174}
175
176#[derive(MallocSizeOf)]
177pub(crate) struct MediaFrameRenderer {
178 webview_id: WebViewId,
179 player_id: Option<usize>,
180 #[conditional_malloc_size_of]
181 glplayer_id: Arc<RwLock<Option<u64>>>,
182 paint_api: CrossProcessPaintApi,
183 #[ignore_malloc_size_of = "Defined in other crates"]
184 player_context: WindowGLContext,
185 current_frame: Option<MediaFrame>,
186 old_frame: Option<ImageKey>,
187 very_old_frame: Option<ImageKey>,
188 current_frame_holder: Option<FrameHolder>,
189 poster_frame: Option<MediaFrame>,
191}
192
193impl MediaFrameRenderer {
194 fn new(
195 webview_id: WebViewId,
196 paint_api: CrossProcessPaintApi,
197 player_context: WindowGLContext,
198 ) -> Self {
199 Self {
200 webview_id,
201 player_id: None,
202 glplayer_id: Arc::new(RwLock::new(None)),
203 paint_api,
204 player_context,
205 current_frame: None,
206 old_frame: None,
207 very_old_frame: None,
208 current_frame_holder: None,
209 poster_frame: None,
210 }
211 }
212
213 fn setup(
214 &mut self,
215 player_id: usize,
216 task_source: SendableTaskSource,
217 weak_video_renderer: Weak<Mutex<MediaFrameRenderer>>,
218 ) {
219 self.player_id = Some(player_id);
220 let glplayer_id = self.glplayer_id.clone();
221 let callback = GenericCallback::new(move |message| {
222 let message = message.unwrap();
223 let weak_video_renderer = weak_video_renderer.clone();
224
225 let glplayer_id = glplayer_id.clone();
226 task_source.queue(task!(handle_glplayer_message: move || {
227 trace!("GLPlayer message {:?}", message);
228
229 let Some(video_renderer) = weak_video_renderer.upgrade() else {
230 return;
231 };
232
233 match message {
234 GLPlayerMsgForward::Lock(sender) => {
235 if let Some(holder) = video_renderer
236 .lock()
237 .unwrap()
238 .current_frame_holder
239 .as_mut() {
240 holder.lock();
241 sender.send(holder.get()).unwrap();
242 };
243 },
244 GLPlayerMsgForward::Unlock() => {
245 if let Some(holder) = video_renderer
246 .lock()
247 .unwrap()
248 .current_frame_holder
249 .as_mut() { holder.unlock() }
250 },
251 GLPlayerMsgForward::PlayerId(id) => {
252 let mut glplayer_id = glplayer_id.write().unwrap();
253 if let Some(already_set_id) = *glplayer_id {
254 error!("Player id already set to {already_set_id} will be replaced with {id}");
255 }
256 *glplayer_id = Some(id);
257 },
258 }
259 }));
260 })
261 .expect("Could not create callback");
262
263 if let Some(glplayer_thread_sender) = &self.player_context.glplayer_thread_sender {
264 glplayer_thread_sender
265 .send(GLPlayerMsg::RegisterPlayer(callback))
266 .unwrap();
267 }
268 }
269
270 fn reset(&mut self) {
271 self.player_id = None;
272
273 if let Some(glplayer_id) = self.glplayer_id.write().unwrap().take() {
274 self.player_context
275 .send(GLPlayerMsg::UnregisterPlayer(glplayer_id));
276 }
277
278 self.current_frame_holder = None;
279
280 let mut updates = smallvec::smallvec![];
281
282 if let Some(current_frame) = self.current_frame.take() {
283 updates.push(ImageUpdate::DeleteImage(current_frame.image_key));
284 }
285
286 if let Some(old_image_key) = self.old_frame.take() {
287 updates.push(ImageUpdate::DeleteImage(old_image_key));
288 }
289
290 if let Some(very_old_image_key) = self.very_old_frame.take() {
291 updates.push(ImageUpdate::DeleteImage(very_old_image_key));
292 }
293
294 if !updates.is_empty() {
295 self.paint_api
296 .update_images(self.webview_id.into(), updates);
297 }
298 }
299
300 fn set_poster_frame(&mut self, image: Option<Arc<RasterImage>>) {
301 self.poster_frame = image.and_then(|image| {
302 image.id.map(|image_key| MediaFrame {
303 image_key,
304 width: image.metadata.width as i32,
305 height: image.metadata.height as i32,
306 })
307 });
308 }
309}
310
311impl Drop for MediaFrameRenderer {
312 fn drop(&mut self) {
313 self.reset();
314 }
315}
316
317impl VideoFrameRenderer for MediaFrameRenderer {
318 fn render(&mut self, frame: VideoFrame) {
319 if self.player_id.is_none() ||
320 (frame.is_gl_texture() && self.glplayer_id.read().unwrap().is_none())
321 {
322 return;
323 }
324
325 let mut updates = smallvec::smallvec![];
326
327 if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) {
328 updates.push(ImageUpdate::DeleteImage(old_image_key));
329 }
330
331 let descriptor = ImageDescriptor::new(
332 frame.get_width(),
333 frame.get_height(),
334 ImageFormat::BGRA8,
335 ImageDescriptorFlags::empty(),
336 );
337
338 match &mut self.current_frame {
339 Some(current_frame)
340 if current_frame.width == frame.get_width() &&
341 current_frame.height == frame.get_height() =>
342 {
343 if !frame.is_gl_texture() {
344 updates.push(ImageUpdate::UpdateImage(
345 current_frame.image_key,
346 descriptor,
347 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
348 frame.get_data(),
349 )),
350 None,
351 ));
352 }
353
354 self.current_frame_holder
355 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
356 .set(frame);
357
358 if let Some(old_image_key) = self.old_frame.take() {
359 updates.push(ImageUpdate::DeleteImage(old_image_key));
360 }
361 },
362 Some(current_frame) => {
363 self.old_frame = Some(current_frame.image_key);
364
365 let Some(new_image_key) =
366 self.paint_api.generate_image_key_blocking(self.webview_id)
367 else {
368 return;
369 };
370
371 current_frame.image_key = new_image_key;
373 current_frame.width = frame.get_width();
374 current_frame.height = frame.get_height();
375
376 let image_data = self
378 .glplayer_id
379 .read()
380 .unwrap()
381 .filter(|_| frame.is_gl_texture())
382 .map(|glplayer_id| {
383 let texture_target = if frame.is_external_oes() {
384 ImageBufferKind::TextureExternal
385 } else {
386 ImageBufferKind::Texture2D
387 };
388
389 SerializableImageData::External(ExternalImageData {
390 id: ExternalImageId(glplayer_id),
391 channel_index: 0,
392 image_type: ExternalImageType::TextureHandle(texture_target),
393 normalized_uvs: false,
394 })
395 })
396 .unwrap_or_else(|| {
397 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
398 frame.get_data(),
399 ))
400 });
401
402 self.current_frame_holder
403 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
404 .set(frame);
405
406 updates.push(ImageUpdate::AddImage(
407 new_image_key,
408 descriptor,
409 image_data,
410 false,
411 ));
412 },
413 None => {
414 let Some(image_key) = self.paint_api.generate_image_key_blocking(self.webview_id)
415 else {
416 return;
417 };
418
419 self.current_frame = Some(MediaFrame {
420 image_key,
421 width: frame.get_width(),
422 height: frame.get_height(),
423 });
424
425 let image_data = self
426 .glplayer_id
427 .read()
428 .unwrap()
429 .filter(|_| frame.is_gl_texture())
430 .map(|glplayer_id| {
431 let texture_target = if frame.is_external_oes() {
432 ImageBufferKind::TextureExternal
433 } else {
434 ImageBufferKind::Texture2D
435 };
436
437 SerializableImageData::External(ExternalImageData {
438 id: ExternalImageId(glplayer_id),
439 channel_index: 0,
440 image_type: ExternalImageType::TextureHandle(texture_target),
441 normalized_uvs: false,
442 })
443 })
444 .unwrap_or_else(|| {
445 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
446 frame.get_data(),
447 ))
448 });
449
450 self.current_frame_holder = Some(FrameHolder::new(frame));
451
452 updates.push(ImageUpdate::AddImage(
453 image_key, descriptor, image_data, false,
454 ));
455 },
456 }
457 self.paint_api
458 .update_images(self.webview_id.into(), updates);
459 }
460}
461
462#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
463#[derive(JSTraceable, MallocSizeOf)]
464enum SrcObject {
465 MediaStream(Dom<MediaStream>),
466 Blob(Dom<Blob>),
467}
468
469impl From<MediaStreamOrBlob> for SrcObject {
470 fn from(src_object: MediaStreamOrBlob) -> SrcObject {
471 match src_object {
472 MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)),
473 MediaStreamOrBlob::MediaStream(stream) => {
474 SrcObject::MediaStream(Dom::from_ref(&*stream))
475 },
476 }
477 }
478}
479
480#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
481enum LoadState {
482 NotLoaded,
483 LoadingFromSrcObject,
484 LoadingFromSrcAttribute,
485 LoadingFromSourceChild,
486 WaitingForSource,
487}
488
489#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
491#[derive(JSTraceable, MallocSizeOf)]
492struct SourceChildrenPointer {
493 source_before_pointer: Dom<HTMLSourceElement>,
494 inclusive: bool,
495}
496
497impl SourceChildrenPointer {
498 fn new(source_before_pointer: &HTMLSourceElement, inclusive: bool) -> Self {
499 Self {
500 source_before_pointer: Dom::from_ref(source_before_pointer),
501 inclusive,
502 }
503 }
504}
505
506#[derive(Clone, Copy, Debug, PartialEq)]
510enum LoopCondition {
511 Included,
512 Ignored,
513}
514
515#[dom_struct]
516pub(crate) struct HTMLMediaElement {
517 htmlelement: HTMLElement,
518 network_state: Cell<NetworkState>,
520 ready_state: Cell<ReadyState>,
522 src_object: DomRefCell<Option<SrcObject>>,
524 current_src: DomRefCell<String>,
526 generation_id: Cell<u32>,
528 fired_loadeddata_event: Cell<bool>,
532 error: MutNullableDom<MediaError>,
534 paused: Cell<bool>,
536 default_playback_rate: Cell<f64>,
538 playback_rate: Cell<f64>,
540 autoplaying: Cell<bool>,
542 delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>,
544 #[conditional_malloc_size_of]
546 pending_play_promises: DomRefCell<Vec<Rc<Promise>>>,
547 #[expect(clippy::type_complexity)]
549 #[conditional_malloc_size_of]
550 in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
551 #[ignore_malloc_size_of = "servo_media"]
552 #[no_trace]
553 player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
554 #[conditional_malloc_size_of]
555 #[no_trace]
556 video_renderer: Arc<Mutex<MediaFrameRenderer>>,
557 #[ignore_malloc_size_of = "servo_media"]
558 #[no_trace]
559 audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>,
560 #[conditional_malloc_size_of]
561 #[no_trace]
562 event_handler: RefCell<Option<Arc<Mutex<HTMLMediaElementEventHandler>>>>,
563 show_poster: Cell<bool>,
565 duration: Cell<f64>,
567 current_playback_position: Cell<f64>,
569 official_playback_position: Cell<f64>,
571 default_playback_start_position: Cell<f64>,
573 volume: Cell<f64>,
575 seeking: Cell<bool>,
577 current_seek_position: Cell<f64>,
581 muted: Cell<bool>,
583 load_state: Cell<LoadState>,
585 source_children_pointer: DomRefCell<Option<SourceChildrenPointer>>,
586 current_source_child: MutNullableDom<HTMLSourceElement>,
587 #[no_trace]
589 resource_url: DomRefCell<Option<ServoUrl>>,
590 #[no_trace]
593 blob_url: DomRefCell<Option<ServoUrl>>,
594 played: DomRefCell<TimeRangesContainer>,
596 audio_tracks_list: MutNullableDom<AudioTrackList>,
598 video_tracks_list: MutNullableDom<VideoTrackList>,
600 text_tracks_list: MutNullableDom<TextTrackList>,
602 #[ignore_malloc_size_of = "Defined in std::time"]
604 next_timeupdate_event: Cell<Instant>,
605 current_fetch_context: RefCell<Option<HTMLMediaElementFetchContext>>,
607 media_controls_id: DomRefCell<Option<String>>,
612 did_perform_automatic_track_selection: Cell<bool>,
614}
615
616#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
618#[repr(u8)]
619pub(crate) enum NetworkState {
620 Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
621 Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
622 Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
623 NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8,
624}
625
626#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
628#[repr(u8)]
629#[expect(clippy::enum_variant_names)] pub(crate) enum ReadyState {
631 HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8,
632 HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8,
633 HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8,
634 HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8,
635 HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8,
636}
637
638#[derive(Clone, Copy, PartialEq)]
640enum PlaybackDirection {
641 Forwards,
642 Backwards,
643}
644
645impl HTMLMediaElement {
646 pub(crate) fn new_inherited(
647 tag_name: LocalName,
648 prefix: Option<Prefix>,
649 document: &Document,
650 ) -> Self {
651 Self {
652 htmlelement: HTMLElement::new_inherited(tag_name, prefix, document),
653 network_state: Cell::new(NetworkState::Empty),
654 ready_state: Cell::new(ReadyState::HaveNothing),
655 src_object: Default::default(),
656 current_src: DomRefCell::new("".to_owned()),
657 generation_id: Cell::new(0),
658 fired_loadeddata_event: Cell::new(false),
659 error: Default::default(),
660 paused: Cell::new(true),
661 default_playback_rate: Cell::new(1.0),
662 playback_rate: Cell::new(1.0),
663 muted: Cell::new(false),
664 load_state: Cell::new(LoadState::NotLoaded),
665 source_children_pointer: DomRefCell::new(None),
666 current_source_child: Default::default(),
667 autoplaying: Cell::new(true),
669 delaying_the_load_event_flag: Default::default(),
670 pending_play_promises: Default::default(),
671 in_flight_play_promises_queue: Default::default(),
672 player: Default::default(),
673 video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new(
674 document.webview_id(),
675 document.window().paint_api().clone(),
676 document.window().get_player_context(),
677 ))),
678 audio_renderer: Default::default(),
679 event_handler: Default::default(),
680 show_poster: Cell::new(true),
681 duration: Cell::new(f64::NAN),
682 current_playback_position: Cell::new(0.),
683 official_playback_position: Cell::new(0.),
684 default_playback_start_position: Cell::new(0.),
685 volume: Cell::new(1.0),
686 seeking: Cell::new(false),
687 current_seek_position: Cell::new(f64::NAN),
688 resource_url: DomRefCell::new(None),
689 blob_url: DomRefCell::new(None),
690 played: DomRefCell::new(TimeRangesContainer::default()),
691 audio_tracks_list: Default::default(),
692 video_tracks_list: Default::default(),
693 text_tracks_list: Default::default(),
694 next_timeupdate_event: Cell::new(Instant::now() + Duration::from_millis(250)),
695 current_fetch_context: RefCell::new(None),
696 media_controls_id: DomRefCell::new(None),
697 did_perform_automatic_track_selection: Default::default(),
698 }
699 }
700
701 pub(crate) fn network_state(&self) -> NetworkState {
702 self.network_state.get()
703 }
704
705 pub(crate) fn get_ready_state(&self) -> ReadyState {
706 self.ready_state.get()
707 }
708
709 fn media_type_id(&self) -> HTMLMediaElementTypeId {
710 match self.upcast::<Node>().type_id() {
711 NodeTypeId::Element(ElementTypeId::HTMLElement(
712 HTMLElementTypeId::HTMLMediaElement(media_type_id),
713 )) => media_type_id,
714 _ => unreachable!(),
715 }
716 }
717
718 fn update_media_state(&self) {
719 let is_playing = self
720 .player
721 .borrow()
722 .as_ref()
723 .is_some_and(|player| !player.lock().unwrap().paused());
724
725 if self.is_potentially_playing() && !is_playing {
726 if let Some(ref player) = *self.player.borrow() {
727 let player = player.lock().unwrap();
728
729 if let Err(error) = player.set_playback_rate(self.playback_rate.get()) {
730 warn!("Could not set the playback rate: {error:?}");
731 }
732 if let Err(error) = player.set_volume(self.volume.get()) {
733 warn!("Could not set the volume: {error:?}");
734 }
735 if let Err(error) = player.play() {
736 error!("Could not play media: {error:?}");
737 }
738 }
739 } else if is_playing &&
740 let Some(ref player) = *self.player.borrow() &&
741 let Err(error) = player.lock().unwrap().pause()
742 {
743 error!("Could not pause player: {error:?}");
744 }
745 }
746
747 pub(crate) fn delay_load_event(&self, delay: bool, cx: &mut JSContext) {
754 let blocker = &self.delaying_the_load_event_flag;
755
756 if delay {
757 if blocker.borrow().is_none() {
758 *blocker.borrow_mut() =
759 Some(LoadBlocker::new(&self.owner_document(), LoadType::Media));
760 }
761 } else {
762 LoadBlocker::terminate(blocker, cx);
763 }
764 }
765
766 fn time_marches_on(&self) {
768 if Instant::now() > self.next_timeupdate_event.get() {
774 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
775 self.next_timeupdate_event
776 .set(Instant::now() + Duration::from_millis(250));
777 }
778 }
779
780 fn internal_play_steps(&self, cx: &mut JSContext) {
782 if self.network_state.get() == NetworkState::Empty {
785 self.invoke_resource_selection_algorithm(cx);
786 }
787
788 if self.ended_playback(LoopCondition::Ignored) &&
794 self.direction_of_playback() == PlaybackDirection::Forwards
795 {
796 self.seek(
797 self.earliest_possible_position(),
798 false,
799 );
800 }
801
802 let state = self.ready_state.get();
803
804 if self.Paused() {
806 self.paused.set(false);
808
809 if self.show_poster.get() {
812 self.show_poster.set(false);
813 self.time_marches_on();
814 }
815
816 self.queue_media_element_task_to_fire_event(atom!("play"));
819
820 match state {
826 ReadyState::HaveNothing |
827 ReadyState::HaveMetadata |
828 ReadyState::HaveCurrentData => {
829 self.queue_media_element_task_to_fire_event(atom!("waiting"));
830 },
831 ReadyState::HaveFutureData | ReadyState::HaveEnoughData => {
832 self.notify_about_playing();
833 },
834 }
835 }
836 else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
841 self.take_pending_play_promises(Ok(()));
842
843 let this = Trusted::new(self);
844 let generation_id = self.generation_id.get();
845
846 self.owner_global()
847 .task_manager()
848 .media_element_task_source()
849 .queue(task!(resolve_pending_play_promises: move |cx| {
850 let this = this.root();
851 if generation_id != this.generation_id.get() {
852 return;
853 }
854
855 this.fulfill_in_flight_play_promises(cx, |_| {});
856 }));
857 }
858
859 self.autoplaying.set(false);
861
862 self.update_media_state();
863 }
864
865 fn internal_pause_steps(&self) {
867 self.autoplaying.set(false);
869
870 if !self.Paused() {
872 self.paused.set(true);
874
875 self.take_pending_play_promises(Err(Error::Abort(None)));
877
878 let this = Trusted::new(self);
880 let generation_id = self.generation_id.get();
881
882 self.owner_global()
883 .task_manager()
884 .media_element_task_source()
885 .queue(task!(internal_pause_steps: move |cx| {
886 let this = this.root();
887 if generation_id != this.generation_id.get() {
888 return;
889 }
890
891 this.fulfill_in_flight_play_promises(cx, |cx| {
892 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
894
895 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
897
898 });
902 }));
903
904 self.official_playback_position
906 .set(self.current_playback_position.get());
907 }
908
909 self.update_media_state();
910 }
911
912 fn is_allowed_to_play(&self) -> bool {
914 true
915 }
916
917 fn notify_about_playing(&self) {
919 self.take_pending_play_promises(Ok(()));
921
922 let this = Trusted::new(self);
924 let generation_id = self.generation_id.get();
925
926 self.owner_global()
927 .task_manager()
928 .media_element_task_source()
929 .queue(task!(notify_about_playing: move |cx| {
930 let this = this.root();
931 if generation_id != this.generation_id.get() {
932 return;
933 }
934
935 this.fulfill_in_flight_play_promises(cx, |cx| {
936 this.upcast::<EventTarget>().fire_event(cx, atom!("playing"));
938
939 });
942 }));
943 }
944
945 #[expect(
947 clippy::collapsible_match,
948 reason = "This way follows the spec more closely"
949 )]
950 fn change_ready_state(&self, ready_state: ReadyState) {
951 let old_ready_state = self.ready_state.get();
952 self.ready_state.set(ready_state);
953
954 if self.network_state.get() == NetworkState::Empty {
955 return;
956 }
957
958 if old_ready_state == ready_state {
959 return;
960 }
961
962 match (old_ready_state, ready_state) {
964 (ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
967 self.queue_media_element_task_to_fire_event(atom!("loadedmetadata"));
970 return;
972 },
973 (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => {
976 if !self.fired_loadeddata_event.get() {
980 self.fired_loadeddata_event.set(true);
981
982 let this = Trusted::new(self);
983 let generation_id = self.generation_id.get();
984
985 self.owner_global()
986 .task_manager()
987 .media_element_task_source()
988 .queue(task!(media_reached_current_data: move |cx| {
989 let this = this.root();
990 if generation_id != this.generation_id.get() {
991 return;
992 }
993
994 this.upcast::<EventTarget>().fire_event(cx, atom!("loadeddata"));
995 this.delay_load_event(false, cx);
999 }));
1000 }
1001
1002 },
1006 (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => {
1007 return;
1012 },
1013
1014 _ => (),
1015 }
1016
1017 if old_ready_state <= ReadyState::HaveCurrentData &&
1020 ready_state >= ReadyState::HaveFutureData
1021 {
1022 self.queue_media_element_task_to_fire_event(atom!("canplay"));
1025
1026 if !self.Paused() {
1029 self.notify_about_playing();
1030 }
1031 }
1032
1033 if ready_state == ReadyState::HaveEnoughData {
1035 self.queue_media_element_task_to_fire_event(atom!("canplaythrough"));
1038
1039 if self.eligible_for_autoplay() {
1042 self.paused.set(false);
1044
1045 if self.show_poster.get() {
1048 self.show_poster.set(false);
1049 self.time_marches_on();
1050 }
1051
1052 self.queue_media_element_task_to_fire_event(atom!("play"));
1055
1056 self.notify_about_playing();
1058 }
1059 }
1060
1061 self.update_media_state();
1062 }
1063
1064 fn invoke_resource_selection_algorithm(&self, cx: &mut JSContext) {
1066 self.network_state.set(NetworkState::NoSource);
1068
1069 self.show_poster.set(true);
1071
1072 self.delay_load_event(true, cx);
1075
1076 let task = MediaElementMicrotask::ResourceSelection {
1083 elem: Dom::from_ref(self),
1084 generation_id: self.generation_id.get(),
1085 base_url: self.owner_document().base_url(),
1086 };
1087
1088 ScriptThread::await_stable_state(cx, Box::new(task));
1093 }
1094
1095 fn resource_selection_algorithm_sync(&self, base_url: ServoUrl, cx: &mut JSContext) {
1097 enum Mode {
1104 Object,
1105 Attribute(String),
1106 Children(DomRoot<HTMLSourceElement>),
1107 }
1108
1109 let mode = if self.src_object.borrow().is_some() {
1111 Mode::Object
1113 } else if let Some(src) = self
1114 .upcast::<Element>()
1115 .get_attribute_string_value(&local_name!("src"))
1116 {
1117 Mode::Attribute(src)
1120 } else if let Some(source) = self
1121 .upcast::<Node>()
1122 .children_unrooted(cx.no_gc())
1123 .find_map(UnrootedDom::downcast::<HTMLSourceElement>)
1124 {
1125 Mode::Children(source.as_rooted())
1129 } else {
1130 self.load_state.set(LoadState::NotLoaded);
1133
1134 self.network_state.set(NetworkState::Empty);
1136
1137 self.delay_load_event(false, cx);
1140
1141 return;
1143 };
1144
1145 self.network_state.set(NetworkState::Loading);
1147
1148 self.queue_media_element_task_to_fire_event(atom!("loadstart"));
1151
1152 match mode {
1154 Mode::Object => {
1155 self.load_from_src_object(cx);
1157 },
1158 Mode::Attribute(src) => {
1159 self.load_from_src_attribute(cx, base_url, &src);
1161 },
1162 Mode::Children(source) => {
1163 self.load_from_source_child(cx, &source);
1165 },
1166 }
1167 }
1168
1169 fn load_from_src_object(&self, cx: &JSContext) {
1171 self.load_state.set(LoadState::LoadingFromSrcObject);
1172
1173 "".clone_into(&mut self.current_src.borrow_mut());
1175
1176 self.resource_fetch_algorithm(cx, Resource::Object);
1182 }
1183
1184 fn load_from_src_attribute(&self, cx: &JSContext, base_url: ServoUrl, src: &str) {
1186 self.load_state.set(LoadState::LoadingFromSrcAttribute);
1187
1188 if src.is_empty() {
1191 self.queue_dedicated_media_source_failure_steps();
1192 return;
1193 }
1194
1195 let Ok(url_record) = base_url.join(src) else {
1199 self.queue_dedicated_media_source_failure_steps();
1200 return;
1201 };
1202
1203 *self.current_src.borrow_mut() = url_record.as_str().into();
1206
1207 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1213 }
1214
1215 fn load_from_source_child(&self, cx: &JSContext, source: &HTMLSourceElement) {
1217 self.load_state.set(LoadState::LoadingFromSourceChild);
1218
1219 *self.source_children_pointer.borrow_mut() =
1226 Some(SourceChildrenPointer::new(source, false));
1227
1228 let element = source.upcast::<Element>();
1229
1230 let Some(src) = element
1234 .get_attribute_string_value(&local_name!("src"))
1235 .filter(|value| !value.is_empty())
1236 else {
1237 self.load_from_source_child_failure_steps(cx, source);
1238 return;
1239 };
1240
1241 if let Some(media) = element.get_attribute_string_value(&local_name!("media")) &&
1245 !MediaList::matches_environment(&element.owner_document(), &media)
1246 {
1247 self.load_from_source_child_failure_steps(cx, source);
1248 return;
1249 }
1250
1251 let Ok(url_record) = source.owner_document().base_url().join(&src) else {
1255 self.load_from_source_child_failure_steps(cx, source);
1258 return;
1259 };
1260
1261 if let Some(type_) = element.get_attribute_string_value(&local_name!("type")) &&
1266 ServoMedia::get().can_play_type(&type_) == SupportsMediaType::No
1267 {
1268 self.load_from_source_child_failure_steps(cx, source);
1269 return;
1270 }
1271
1272 self.reset_media_player();
1274
1275 self.current_source_child.set(Some(source));
1276
1277 *self.current_src.borrow_mut() = url_record.as_str().into();
1280
1281 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1286 }
1287
1288 fn load_from_source_child_failure_steps(&self, cx: &JSContext, source: &HTMLSourceElement) {
1290 let trusted_this = Trusted::new(self);
1293 let trusted_source = Trusted::new(source);
1294 let generation_id = self.generation_id.get();
1295
1296 self.owner_global()
1297 .task_manager()
1298 .media_element_task_source()
1299 .queue(task!(queue_error_event: move |cx| {
1300 let this = trusted_this.root();
1301 if generation_id != this.generation_id.get() {
1302 return;
1303 }
1304
1305 let source = trusted_source.root();
1306 source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1307 }));
1308
1309 let task = MediaElementMicrotask::SelectNextSourceChild {
1311 elem: Dom::from_ref(self),
1312 generation_id: self.generation_id.get(),
1313 };
1314
1315 ScriptThread::await_stable_state(cx, Box::new(task));
1316 }
1317
1318 fn select_next_source_child(&self, cx: &mut JSContext) {
1320 self.AudioTracks(cx).clear();
1322 self.VideoTracks(cx).clear();
1323
1324 let mut source_candidate = None;
1326
1327 if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1335 if source_children_pointer.inclusive {
1339 for next_sibling in source_children_pointer
1340 .source_before_pointer
1341 .upcast::<Node>()
1342 .inclusively_following_siblings()
1343 {
1344 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1345 {
1346 source_candidate = Some(next_source);
1347 break;
1348 }
1349 }
1350 } else {
1351 for next_sibling in source_children_pointer
1352 .source_before_pointer
1353 .upcast::<Node>()
1354 .following_siblings()
1355 {
1356 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1357 {
1358 source_candidate = Some(next_source);
1359 break;
1360 }
1361 }
1362 };
1363 }
1364
1365 if let Some(source_candidate) = source_candidate {
1368 self.load_from_source_child(cx, &source_candidate);
1369 return;
1370 }
1371
1372 self.load_state.set(LoadState::WaitingForSource);
1373
1374 *self.source_children_pointer.borrow_mut() = None;
1375
1376 self.network_state.set(NetworkState::NoSource);
1379
1380 self.show_poster.set(true);
1382
1383 let this = Trusted::new(self);
1386 let generation_id = self.generation_id.get();
1387
1388 self.owner_global()
1389 .task_manager()
1390 .media_element_task_source()
1391 .queue(task!(queue_delay_load_event: move |cx| {
1392 let this = this.root();
1393 if generation_id != this.generation_id.get() {
1394 return;
1395 }
1396
1397 this.delay_load_event(false, cx);
1398 }));
1399
1400 }
1403
1404 fn resource_selection_algorithm_failure_steps(&self, cx: &JSContext) {
1406 match self.load_state.get() {
1407 LoadState::LoadingFromSrcObject => {
1408 self.queue_dedicated_media_source_failure_steps();
1413 },
1414 LoadState::LoadingFromSrcAttribute => {
1415 self.queue_dedicated_media_source_failure_steps();
1420 },
1421 LoadState::LoadingFromSourceChild => {
1422 if let Some(source) = self.current_source_child.take() {
1425 self.load_from_source_child_failure_steps(cx, &source);
1426 }
1427 },
1428 _ => {},
1429 }
1430 }
1431
1432 fn fetch_request(&self, cx: &JSContext, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1433 if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1434 error!("Missing request url");
1435 if let Some(seek_lock) = seek_lock {
1436 seek_lock.unlock(false);
1437 }
1438 self.resource_selection_algorithm_failure_steps(cx);
1439 return;
1440 }
1441
1442 let document = self.owner_document();
1443 let destination = match self.media_type_id() {
1444 HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1445 HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1446 };
1447 let mut headers = HeaderMap::new();
1448 headers.insert(
1450 header::RANGE,
1451 HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(),
1452 );
1453 let url = match self.resource_url.borrow().as_ref() {
1454 Some(url) => url.clone(),
1455 None => self.blob_url.borrow().as_ref().unwrap().clone(),
1456 };
1457
1458 let cors_setting = cors_setting_for_element(self.upcast());
1459 let global = self.global();
1460 let request = create_a_potential_cors_request(
1461 Some(document.webview_id()),
1462 url.clone(),
1463 destination,
1464 cors_setting,
1465 None,
1466 global.get_referrer(),
1467 )
1468 .with_global_scope(&global)
1469 .headers(headers)
1470 .referrer_policy(referrer_policy_for_element(self.upcast()));
1471
1472 let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1473 if let Some(ref mut current_fetch_context) = *current_fetch_context {
1474 current_fetch_context.cancel(CancelReason::Abort);
1475 }
1476
1477 *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1478 request.id,
1479 global.core_resource_thread(),
1480 ));
1481 let listener =
1482 HTMLMediaElementFetchListener::new(self, request.id, url, offset.unwrap_or(0));
1483
1484 self.owner_document().fetch_background(request, listener);
1485
1486 if let Some(seek_lock) = seek_lock {
1491 seek_lock.unlock(true);
1492 }
1493 }
1494
1495 fn eligible_for_autoplay(&self) -> bool {
1497 self.autoplaying.get() &&
1499
1500 self.Paused() &&
1502
1503 self.Autoplay() &&
1505
1506 {
1509 let document = self.owner_document();
1510
1511 !document.has_active_sandboxing_flag(
1512 SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1513 )
1514 }
1515
1516 }
1519
1520 fn resource_fetch_algorithm(&self, cx: &JSContext, resource: Resource) {
1522 if let Err(e) = self.create_media_player(&resource) {
1523 error!("Create media player error {:?}", e);
1524 self.resource_selection_algorithm_failure_steps(cx);
1525 return;
1526 }
1527
1528 match resource {
1537 Resource::Url(url) => {
1538 if self.Preload() == "none" && !self.autoplaying.get() {
1543 self.network_state.set(NetworkState::Idle);
1545
1546 self.queue_media_element_task_to_fire_event(atom!("suspend"));
1549
1550 let this = Trusted::new(self);
1554 let generation_id = self.generation_id.get();
1555
1556 self.owner_global()
1557 .task_manager()
1558 .media_element_task_source()
1559 .queue(task!(queue_delay_load_event: move |cx| {
1560 let this = this.root();
1561 if generation_id != this.generation_id.get() {
1562 return;
1563 }
1564
1565 this.delay_load_event(false, cx);
1566 }));
1567
1568 return;
1577 }
1578
1579 *self.resource_url.borrow_mut() = Some(url);
1580
1581 self.fetch_request(cx, None, None);
1583 },
1584 Resource::Object => {
1585 if let Some(ref src_object) = *self.src_object.borrow() {
1586 match src_object {
1587 SrcObject::Blob(blob) => {
1588 let blob_url = URL::CreateObjectURL(&self.global(), blob);
1589 *self.blob_url.borrow_mut() =
1590 Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1591 self.fetch_request(cx, None, None);
1592 },
1593 SrcObject::MediaStream(stream) => {
1594 let tracks = &*stream.get_tracks();
1595 for (pos, track) in tracks.iter().enumerate() {
1596 if self
1597 .player
1598 .borrow()
1599 .as_ref()
1600 .unwrap()
1601 .lock()
1602 .unwrap()
1603 .set_stream(&track.id(), pos == tracks.len() - 1)
1604 .is_err()
1605 {
1606 self.resource_selection_algorithm_failure_steps(cx);
1607 }
1608 }
1609 },
1610 }
1611 }
1612 },
1613 }
1614 }
1615
1616 fn queue_dedicated_media_source_failure_steps(&self) {
1620 let this = Trusted::new(self);
1621 let generation_id = self.generation_id.get();
1622 self.take_pending_play_promises(Err(Error::NotSupported(None)));
1623 self.owner_global()
1624 .task_manager()
1625 .media_element_task_source()
1626 .queue(task!(dedicated_media_source_failure_steps: move |cx| {
1627 let this = this.root();
1628 if generation_id != this.generation_id.get() {
1629 return;
1630 }
1631
1632 this.fulfill_in_flight_play_promises(cx, |cx| {
1633 this.error.set(Some(&*MediaError::new(
1636 cx,
1637 &this.owner_window(),
1638 MEDIA_ERR_SRC_NOT_SUPPORTED)));
1639
1640 this.AudioTracks(cx).clear();
1642 this.VideoTracks(cx).clear();
1643
1644 this.network_state.set(NetworkState::NoSource);
1647
1648 this.show_poster.set(true);
1650
1651 this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1653
1654 if let Some(ref player) = *this.player.borrow()
1655 && let Err(error) = player.lock().unwrap().stop() {
1656 error!("Could not stop player: {error:?}");
1657 }
1658
1659 });
1663
1664 this.delay_load_event(false, cx);
1667 }));
1668 }
1669
1670 fn in_error_state(&self) -> bool {
1671 self.error.get().is_some()
1672 }
1673
1674 fn is_potentially_playing(&self) -> bool {
1676 !self.paused.get() &&
1677 !self.ended_playback(LoopCondition::Included) &&
1678 self.error.get().is_none() &&
1679 !self.is_blocked_media_element()
1680 }
1681
1682 fn is_blocked_media_element(&self) -> bool {
1684 self.ready_state.get() <= ReadyState::HaveCurrentData ||
1685 self.is_paused_for_user_interaction() ||
1686 self.is_paused_for_in_band_content()
1687 }
1688
1689 fn is_paused_for_user_interaction(&self) -> bool {
1691 false
1694 }
1695
1696 fn is_paused_for_in_band_content(&self) -> bool {
1698 false
1701 }
1702
1703 fn media_element_load_algorithm(&self, cx: &mut JSContext) {
1705 self.fired_loadeddata_event.set(false);
1708
1709 self.generation_id.set(self.generation_id.get() + 1);
1714
1715 self.load_state.set(LoadState::NotLoaded);
1716 *self.source_children_pointer.borrow_mut() = None;
1717 self.current_source_child.set(None);
1718
1719 while !self.in_flight_play_promises_queue.borrow().is_empty() {
1726 self.fulfill_in_flight_play_promises(cx, |_| ());
1727 }
1728
1729 let network_state = self.network_state.get();
1734
1735 if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1739 self.queue_media_element_task_to_fire_event(atom!("abort"));
1740 }
1741
1742 self.reset_media_player();
1744
1745 if network_state != NetworkState::Empty {
1747 self.queue_media_element_task_to_fire_event(atom!("emptied"));
1750
1751 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1754 current_fetch_context.cancel(CancelReason::Abort);
1755 }
1756
1757 self.AudioTracks(cx).clear();
1762 self.VideoTracks(cx).clear();
1763
1764 if self.ready_state.get() != ReadyState::HaveNothing {
1766 self.change_ready_state(ReadyState::HaveNothing);
1767 }
1768
1769 if !self.Paused() {
1771 self.paused.set(true);
1773
1774 self.take_pending_play_promises(Err(Error::Abort(None)));
1777 self.fulfill_in_flight_play_promises(cx, |_| ());
1778 }
1779
1780 self.seeking.set(false);
1782
1783 self.current_seek_position.set(f64::NAN);
1784
1785 self.current_playback_position.set(0.);
1790 if self.official_playback_position.get() != 0. {
1791 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1792 }
1793 self.official_playback_position.set(0.);
1794
1795 self.duration.set(f64::NAN);
1799 }
1800
1801 self.playback_rate.set(self.default_playback_rate.get());
1803
1804 self.error.set(None);
1806 self.autoplaying.set(true);
1807
1808 self.invoke_resource_selection_algorithm(cx);
1810
1811 }
1813
1814 fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1817 let this = Trusted::new(self);
1818 let generation_id = self.generation_id.get();
1819
1820 self.owner_global()
1821 .task_manager()
1822 .media_element_task_source()
1823 .queue(task!(queue_event: move |cx| {
1824 let this = this.root();
1825 if generation_id != this.generation_id.get() {
1826 return;
1827 }
1828
1829 this.upcast::<EventTarget>().fire_event(cx, name);
1830 }));
1831 }
1832
1833 fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1835 self.pending_play_promises
1836 .borrow_mut()
1837 .push(promise.clone());
1838 }
1839
1840 fn take_pending_play_promises(&self, result: ErrorResult) {
1851 let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1852 self.in_flight_play_promises_queue
1853 .borrow_mut()
1854 .push_back((pending_play_promises.into(), result));
1855 }
1856
1857 fn fulfill_in_flight_play_promises<F>(&self, cx: &mut JSContext, f: F)
1866 where
1867 F: FnOnce(&mut JSContext),
1868 {
1869 let (promises, result) = self
1870 .in_flight_play_promises_queue
1871 .borrow_mut()
1872 .pop_front()
1873 .expect("there should be at least one list of in flight play promises");
1874 f(cx);
1875 for promise in &*promises {
1876 match result {
1877 Ok(ref value) => promise.resolve_native(cx, value),
1878 Err(ref error) => promise.reject_error(cx, error.clone()),
1879 }
1880 }
1881 }
1882
1883 pub(crate) fn handle_source_child_insertion(
1884 &self,
1885 source: &HTMLSourceElement,
1886 cx: &mut JSContext,
1887 ) {
1888 if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1892 return;
1893 }
1894
1895 if self.network_state.get() == NetworkState::Empty {
1896 self.invoke_resource_selection_algorithm(cx);
1897 return;
1898 }
1899
1900 if self.load_state.get() != LoadState::WaitingForSource {
1904 return;
1905 }
1906
1907 self.load_state.set(LoadState::LoadingFromSourceChild);
1908
1909 *self.source_children_pointer.borrow_mut() = Some(SourceChildrenPointer::new(source, true));
1910
1911 let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1913 elem: Dom::from_ref(self),
1914 generation_id: self.generation_id.get(),
1915 };
1916
1917 ScriptThread::await_stable_state(cx, Box::new(task));
1918 }
1919
1920 fn select_next_source_child_after_wait(&self, cx: &mut JSContext) {
1922 self.delay_load_event(true, cx);
1925
1926 self.network_state.set(NetworkState::Loading);
1928
1929 self.select_next_source_child(cx);
1931 }
1932
1933 fn media_data_processing_failure_steps(&self, cx: &JSContext) {
1938 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1940 current_fetch_context.cancel(CancelReason::Error);
1941 }
1942
1943 self.resource_selection_algorithm_failure_steps(cx);
1945 }
1946
1947 fn media_data_processing_fatal_steps(&self, error: u16, cx: &mut JSContext) {
1951 *self.source_children_pointer.borrow_mut() = None;
1952 self.current_source_child.set(None);
1953
1954 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1956 current_fetch_context.cancel(CancelReason::Error);
1957 }
1958
1959 self.error
1962 .set(Some(&*MediaError::new(cx, &self.owner_window(), error)));
1963
1964 self.network_state.set(NetworkState::Idle);
1966
1967 self.delay_load_event(false, cx);
1970
1971 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1973
1974 }
1976
1977 fn seek(&self, time: f64, _approximate_for_speed: bool) {
1979 self.show_poster.set(false);
1981
1982 if self.ready_state.get() == ReadyState::HaveNothing {
1984 return;
1985 }
1986
1987 self.current_seek_position.set(f64::NAN);
1991
1992 self.seeking.set(true);
1994
1995 let time = f64::min(time, self.Duration());
2001
2002 let time = f64::max(time, self.earliest_possible_position());
2005
2006 let seekable = self.seekable();
2012
2013 if seekable.is_empty() {
2014 self.seeking.set(false);
2015 return;
2016 }
2017
2018 let mut nearest_seekable_position = 0.0;
2019 let mut in_seekable_range = false;
2020 let mut nearest_seekable_distance = f64::MAX;
2021 for i in 0..seekable.len() {
2022 let start = seekable.start(i).unwrap().abs();
2023 let end = seekable.end(i).unwrap().abs();
2024 if time >= start && time <= end {
2025 nearest_seekable_position = time;
2026 in_seekable_range = true;
2027 break;
2028 } else if time < start {
2029 let distance = start - time;
2030 if distance < nearest_seekable_distance {
2031 nearest_seekable_distance = distance;
2032 nearest_seekable_position = start;
2033 }
2034 } else {
2035 let distance = time - end;
2036 if distance < nearest_seekable_distance {
2037 nearest_seekable_distance = distance;
2038 nearest_seekable_position = end;
2039 }
2040 }
2041 }
2042 let time = if in_seekable_range {
2043 time
2044 } else {
2045 nearest_seekable_position
2046 };
2047
2048 self.queue_media_element_task_to_fire_event(atom!("seeking"));
2059
2060 self.current_playback_position.set(time);
2062
2063 if let Some(ref player) = *self.player.borrow() &&
2064 let Err(error) = player.lock().unwrap().seek(time)
2065 {
2066 error!("Could not seek player: {error:?}");
2067 }
2068
2069 self.current_seek_position.set(time);
2070
2071 }
2077
2078 fn seek_end(&self) {
2080 self.official_playback_position
2083 .set(self.current_playback_position.get());
2084
2085 self.seeking.set(false);
2087
2088 self.current_seek_position.set(f64::NAN);
2089
2090 self.time_marches_on();
2092
2093 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2096
2097 self.queue_media_element_task_to_fire_event(atom!("seeked"));
2100 }
2101
2102 pub(crate) fn set_poster_frame(&self, image: Option<Arc<RasterImage>>) {
2104 if pref!(media_testing_enabled) && image.is_some() {
2105 self.queue_media_element_task_to_fire_event(atom!("postershown"));
2106 }
2107
2108 self.video_renderer.lock().unwrap().set_poster_frame(image);
2109
2110 self.upcast::<Node>().dirty(NodeDamage::Other);
2111 }
2112
2113 fn player_id(&self) -> Option<usize> {
2114 self.player
2115 .borrow()
2116 .as_ref()
2117 .map(|player| player.lock().unwrap().get_id())
2118 }
2119
2120 fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
2121 let stream_type = match *resource {
2122 Resource::Object => {
2123 if let Some(ref src_object) = *self.src_object.borrow() {
2124 match src_object {
2125 SrcObject::MediaStream(_) => StreamType::Stream,
2126 _ => StreamType::Seekable,
2127 }
2128 } else {
2129 return Err(());
2130 }
2131 },
2132 _ => StreamType::Seekable,
2133 };
2134
2135 let window = self.owner_window();
2136
2137 let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
2138 {
2139 HTMLMediaElementTypeId::HTMLAudioElement => None,
2140 HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
2141 };
2142
2143 let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
2144
2145 let pipeline_id = window.pipeline_id();
2146 let client_context_id =
2147 ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
2148
2149 let shared_player_id = Arc::new(OnceLock::new());
2152
2153 let event_handler = Arc::new(Mutex::new(HTMLMediaElementEventHandler::new(self)));
2154 let weak_event_handler = Arc::downgrade(&event_handler);
2155 *self.event_handler.borrow_mut() = Some(event_handler);
2156
2157 let task_source = self
2158 .owner_global()
2159 .task_manager()
2160 .media_element_task_source()
2161 .to_sendable();
2162
2163 let shared_player_id_clone = shared_player_id.clone();
2164 let action_callback = generic_channel::GenericCallback::new(move |message| {
2165 let event = message.unwrap();
2166 let weak_event_handler = weak_event_handler.clone();
2167
2168 let shared_player_id_clone = shared_player_id_clone.clone();
2169 task_source.queue(task!(handle_player_event: move |cx| {
2170 trace!("HTMLMediaElement event: {event:?}");
2171
2172 let Some(event_handler) = weak_event_handler.upgrade() else {
2173 return;
2174 };
2175
2176 if let Some(shared_player_id) = shared_player_id_clone.get() {
2177 event_handler.lock().unwrap().handle_player_event(*shared_player_id, event, cx);
2178 } else {
2179 error!("Player Action without ID being assigned yet.");
2180 }
2181 }));
2182 })
2183 .unwrap();
2184 let player = ServoMedia::get().create_player(
2185 &client_context_id,
2186 stream_type,
2187 action_callback,
2188 video_renderer,
2189 audio_renderer,
2190 Box::new(window.get_player_context()),
2191 );
2192
2193 let player_id = {
2194 let player_guard = player.lock().unwrap();
2195
2196 if let Err(error) = player_guard.set_mute(self.muted.get()) {
2197 warn!("Could not set mute state: {error:?}");
2198 }
2199
2200 let id = player_guard.get_id();
2201 if shared_player_id.set(id).is_err() {
2202 error!("Error setting player id. Already set?");
2203 }
2204 id
2205 };
2206
2207 *self.player.borrow_mut() = Some(player);
2208
2209 let task_source = self
2210 .owner_global()
2211 .task_manager()
2212 .media_element_task_source()
2213 .to_sendable();
2214 let weak_video_renderer = Arc::downgrade(&self.video_renderer);
2215
2216 self.video_renderer
2217 .lock()
2218 .unwrap()
2219 .setup(player_id, task_source, weak_video_renderer);
2220
2221 Ok(())
2222 }
2223
2224 fn reset_media_player(&self) {
2225 if self.player.borrow().is_none() {
2226 return;
2227 }
2228
2229 if let Some(ref player) = *self.player.borrow() &&
2230 let Err(error) = player.lock().unwrap().stop()
2231 {
2232 error!("Could not stop player: {error:?}");
2233 }
2234
2235 *self.player.borrow_mut() = None;
2236 self.video_renderer.lock().unwrap().reset();
2237 *self.event_handler.borrow_mut() = None;
2238
2239 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2240 video_element.set_natural_dimensions(None, None);
2241 }
2242 }
2243
2244 pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
2245 if let Some(ref player) = *self.player.borrow() &&
2246 let Err(error) = player.lock().unwrap().set_audio_track(idx as i32, enabled)
2247 {
2248 warn!("Could not set audio track {error:?}");
2249 }
2250 }
2251
2252 pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
2253 if let Some(ref player) = *self.player.borrow() &&
2254 let Err(error) = player.lock().unwrap().set_video_track(idx as i32, enabled)
2255 {
2256 warn!("Could not set video track: {error:?}");
2257 }
2258 }
2259
2260 fn direction_of_playback(&self) -> PlaybackDirection {
2262 if self.playback_rate.get() >= 0. {
2265 PlaybackDirection::Forwards
2266 } else {
2267 PlaybackDirection::Backwards
2268 }
2269 }
2270
2271 fn ended_playback(&self, loop_condition: LoopCondition) -> bool {
2273 if self.ready_state.get() < ReadyState::HaveMetadata {
2277 return false;
2278 }
2279
2280 let playback_position = self.current_playback_position.get();
2281
2282 match self.direction_of_playback() {
2283 PlaybackDirection::Forwards => {
2287 playback_position >= self.Duration() &&
2288 (loop_condition == LoopCondition::Ignored || !self.Loop())
2289 },
2290 PlaybackDirection::Backwards => playback_position <= self.earliest_possible_position(),
2293 }
2294 }
2295
2296 fn end_of_playback_in_forwards_direction(&self) {
2298 if self.Loop() {
2304 self.seek(
2305 self.earliest_possible_position(),
2306 false,
2307 );
2308 return;
2309 }
2310
2311 let this = Trusted::new(self);
2316 let generation_id = self.generation_id.get();
2317
2318 self.owner_global()
2319 .task_manager()
2320 .media_element_task_source()
2321 .queue(task!(reaches_the_end_steps: move |cx| {
2322 let this = this.root();
2323 if generation_id != this.generation_id.get() {
2324 return;
2325 }
2326
2327 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
2329
2330 if this.ended_playback(LoopCondition::Included) &&
2333 this.direction_of_playback() == PlaybackDirection::Forwards &&
2334 !this.Paused() {
2335 this.paused.set(true);
2337
2338 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
2340
2341 this.take_pending_play_promises(Err(Error::Abort(None)));
2344 this.fulfill_in_flight_play_promises(cx, |_| ());
2345 }
2346
2347 this.upcast::<EventTarget>().fire_event(cx, atom!("ended"));
2349 }));
2350
2351 self.change_ready_state(ReadyState::HaveCurrentData);
2353 }
2354
2355 fn end_of_playback_in_backwards_direction(&self) {
2357 if self.current_playback_position.get() <= self.earliest_possible_position() {
2362 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2363 }
2364 }
2365
2366 fn playback_end(&self) {
2367 if self.seeking.get() {
2369 return;
2370 }
2371
2372 match self.direction_of_playback() {
2373 PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(),
2374 PlaybackDirection::Backwards => self.end_of_playback_in_backwards_direction(),
2375 }
2376 }
2377
2378 fn playback_error(&self, error: &str, cx: &mut JSContext) {
2379 error!("Player error: {:?}", error);
2380
2381 if self.in_error_state() {
2385 return;
2386 }
2387
2388 if self.ready_state.get() == ReadyState::HaveNothing {
2390 self.media_data_processing_failure_steps(cx);
2393 } else {
2394 self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, cx);
2396 }
2397 }
2398
2399 fn playback_metadata_updated(
2400 &self,
2401 cx: &mut JSContext,
2402 metadata: &servo_media::player::metadata::Metadata,
2403 ) {
2404 if self.ready_state.get() != ReadyState::HaveNothing {
2407 return;
2408 }
2409
2410 for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2413 let audio_track_list = self.AudioTracks(cx);
2414
2415 let kind = match i {
2417 0 => DOMString::from("main"),
2418 _ => DOMString::new(),
2419 };
2420
2421 let audio_track = AudioTrack::new(
2422 cx,
2423 self.global().as_window(),
2424 DOMString::new(),
2425 kind,
2426 DOMString::new(),
2427 DOMString::new(),
2428 Some(&*audio_track_list),
2429 );
2430
2431 audio_track_list.add(&audio_track);
2434
2435 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2442 let fragment = MediaFragmentParser::from(servo_url);
2443 if let Some(id) = fragment.id() &&
2444 audio_track.id() == id
2445 {
2446 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2447 }
2448
2449 if fragment.tracks().contains(&audio_track.kind().into()) {
2450 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2451 }
2452 }
2453
2454 if audio_track_list.enabled_index().is_none() {
2459 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2460 }
2461
2462 let event = TrackEvent::new(
2465 cx,
2466 self.global().as_window(),
2467 atom!("addtrack"),
2468 false,
2469 false,
2470 &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2471 );
2472
2473 event
2474 .upcast::<Event>()
2475 .fire(cx, audio_track_list.upcast::<EventTarget>());
2476 }
2477
2478 for (i, _track) in metadata.video_tracks.iter().enumerate() {
2480 let video_track_list = self.VideoTracks(cx);
2481
2482 let kind = match i {
2484 0 => DOMString::from("main"),
2485 _ => DOMString::new(),
2486 };
2487
2488 let video_track = VideoTrack::new(
2489 cx,
2490 self.global().as_window(),
2491 DOMString::new(),
2492 kind,
2493 DOMString::new(),
2494 DOMString::new(),
2495 Some(&*video_track_list),
2496 );
2497
2498 video_track_list.add(&video_track);
2501
2502 if let Some(track) = video_track_list.item(0) &&
2509 let Some(servo_url) = self.resource_url.borrow().as_ref()
2510 {
2511 let fragment = MediaFragmentParser::from(servo_url);
2512 if let Some(id) = fragment.id() {
2513 if track.id() == id {
2514 video_track_list.set_selected(0, true);
2515 }
2516 } else if fragment.tracks().contains(&track.kind().into()) {
2517 video_track_list.set_selected(0, true);
2518 }
2519 }
2520
2521 if video_track_list.selected_index().is_none() {
2527 video_track_list.set_selected(video_track_list.len() - 1, true);
2528 }
2529
2530 let event = TrackEvent::new(
2533 cx,
2534 self.global().as_window(),
2535 atom!("addtrack"),
2536 false,
2537 false,
2538 &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2539 );
2540
2541 event
2542 .upcast::<Event>()
2543 .fire(cx, video_track_list.upcast::<EventTarget>());
2544 }
2545
2546 let earliest_possible_position = self.earliest_possible_position();
2559 self.current_playback_position
2560 .set(earliest_possible_position);
2561 self.official_playback_position
2562 .set(earliest_possible_position);
2563
2564 self.duration.set(
2570 metadata
2571 .duration
2572 .map_or(f64::INFINITY, |duration| duration.as_secs_f64()),
2573 );
2574 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2575
2576 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2580 video_element.set_natural_dimensions(Some(metadata.width), Some(metadata.height));
2581 self.queue_media_element_task_to_fire_event(atom!("resize"));
2582 }
2583
2584 self.change_ready_state(ReadyState::HaveMetadata);
2586
2587 let mut jumped = false;
2589
2590 if self.default_playback_start_position.get() > 0. {
2593 self.seek(
2594 self.default_playback_start_position.get(),
2595 false,
2596 );
2597 jumped = true;
2598 }
2599
2600 self.default_playback_start_position.set(0.);
2602
2603 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2608 let fragment = MediaFragmentParser::from(servo_url);
2609 if let Some(initial_playback_position) = fragment.start() &&
2610 initial_playback_position > 0. &&
2611 initial_playback_position < self.duration.get() &&
2612 !jumped
2613 {
2614 self.seek(
2615 initial_playback_position,
2616 false,
2617 )
2618 }
2619 }
2620
2621 let global = self.global();
2628 let window = global.as_window();
2629
2630 window.Navigator(cx).MediaSession(cx).update_title(
2632 metadata
2633 .title
2634 .clone()
2635 .unwrap_or(window.get_url().into_string()),
2636 );
2637 }
2638
2639 fn playback_duration_changed(&self, duration: Option<Duration>) {
2640 let duration = duration.map_or(f64::INFINITY, |duration| duration.as_secs_f64());
2641
2642 if self.duration.get() == duration {
2643 return;
2644 }
2645
2646 self.duration.set(duration);
2647
2648 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2654
2655 if self.current_playback_position.get() > duration {
2659 self.seek(duration, false);
2660 }
2661 }
2662
2663 fn playback_video_frame_updated(&self) {
2664 let Some(video_element) = self.downcast::<HTMLVideoElement>() else {
2665 return;
2666 };
2667
2668 if self.ready_state.get() == ReadyState::HaveNothing {
2677 return;
2678 }
2679
2680 if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2681 if video_element
2682 .set_natural_dimensions(Some(frame.width as u32), Some(frame.height as u32))
2683 {
2684 self.queue_media_element_task_to_fire_event(atom!("resize"));
2685 } else {
2686 self.upcast::<Node>().dirty(NodeDamage::Other);
2689 }
2690 }
2691 }
2692
2693 fn playback_need_data(&self) {
2694 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() &&
2698 let Some(reason) = current_fetch_context.cancel_reason()
2699 {
2700 if *reason == CancelReason::Backoff {
2706 self.seek(
2707 self.current_playback_position.get(),
2708 false,
2709 );
2710 }
2711 return;
2712 }
2713
2714 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2715 let Err(e) = {
2716 let mut data_source = current_fetch_context.data_source().borrow_mut();
2717 data_source.set_locked(false);
2718 data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2719 }
2720 {
2721 if e == PlayerError::EnoughData {
2726 current_fetch_context.cancel(CancelReason::Backoff);
2727 }
2728 }
2729 }
2730
2731 fn playback_enough_data(&self) {
2732 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2737 current_fetch_context.is_seekable()
2738 {
2739 current_fetch_context.cancel(CancelReason::Backoff);
2740 }
2741 }
2742
2743 fn playback_position_changed(&self, cx: &mut JSContext, position: f64) {
2744 if self.seeking.get() {
2746 return;
2747 }
2748
2749 let _ = self
2750 .played
2751 .borrow_mut()
2752 .add(self.current_playback_position.get(), position);
2753 self.current_playback_position.set(position);
2754 self.official_playback_position.set(position);
2755 self.time_marches_on();
2756
2757 let media_position_state =
2758 MediaPositionState::new(self.duration.get(), self.playback_rate.get(), position);
2759 debug!(
2760 "Sending media session event set position state {:?}",
2761 media_position_state
2762 );
2763 self.send_media_session_event(
2764 cx,
2765 MediaSessionEvent::SetPositionState(media_position_state),
2766 );
2767 }
2768
2769 fn playback_seek_done(&self, cx: &JSContext, position: f64) {
2770 let delta = (position - self.current_seek_position.get()).abs();
2773 if !self.seeking.get() || delta > SEEK_POSITION_THRESHOLD {
2774 return;
2775 }
2776
2777 let task = MediaElementMicrotask::Seeked {
2780 elem: Dom::from_ref(self),
2781 generation_id: self.generation_id.get(),
2782 };
2783
2784 ScriptThread::await_stable_state(cx, Box::new(task));
2785 }
2786
2787 fn playback_state_changed(&self, cx: &mut JSContext, state: &PlaybackState) {
2788 let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2789 match *state {
2790 PlaybackState::Paused => {
2791 media_session_playback_state = MediaSessionPlaybackState::Paused;
2792 if self.ready_state.get() == ReadyState::HaveMetadata {
2793 self.change_ready_state(ReadyState::HaveEnoughData);
2794 }
2795 },
2796 PlaybackState::Playing => {
2797 media_session_playback_state = MediaSessionPlaybackState::Playing;
2798 if self.ready_state.get() == ReadyState::HaveMetadata {
2799 self.change_ready_state(ReadyState::HaveEnoughData);
2800 }
2801 },
2802 PlaybackState::Buffering => {
2803 return;
2807 },
2808 _ => {},
2809 };
2810 debug!(
2811 "Sending media session event playback state changed to {:?}",
2812 media_session_playback_state
2813 );
2814 self.send_media_session_event(
2815 cx,
2816 MediaSessionEvent::PlaybackStateChange(media_session_playback_state),
2817 );
2818 }
2819
2820 fn seekable(&self) -> TimeRangesContainer {
2821 let mut seekable = TimeRangesContainer::default();
2822 if let Some(ref player) = *self.player.borrow() {
2823 let ranges = player.lock().unwrap().seekable();
2824 for range in ranges {
2825 let _ = seekable.add(range.start, range.end);
2826 }
2827 }
2828 seekable
2829 }
2830
2831 fn earliest_possible_position(&self) -> f64 {
2833 self.seekable()
2834 .start(0)
2835 .unwrap_or_else(|_| self.current_playback_position.get())
2836 }
2837
2838 fn render_controls(&self, cx: &mut JSContext) {
2839 if self.upcast::<Element>().is_shadow_host() {
2840 return;
2842 }
2843
2844 let shadow_root = self.upcast::<Element>().attach_ua_shadow_root(cx, false);
2847 let document = self.owner_document();
2848 let script = Element::create(
2849 cx,
2850 QualName::new(None, ns!(html), local_name!("script")),
2851 None,
2852 &document,
2853 ElementCreator::ScriptCreated,
2854 CustomElementCreationMode::Asynchronous,
2855 None,
2856 );
2857 let id = Uuid::new_v4().to_string();
2863 document.register_media_controls(&id, &shadow_root);
2864 let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2865 *self.media_controls_id.borrow_mut() = Some(id);
2866 script
2867 .upcast::<Node>()
2868 .set_text_content_for_element(cx, Some(DOMString::from(media_controls_script)));
2869 if let Err(e) = shadow_root
2870 .upcast::<Node>()
2871 .AppendChild(cx, script.upcast::<Node>())
2872 {
2873 warn!("Could not render media controls {:?}", e);
2874 return;
2875 }
2876
2877 let style = Element::create(
2878 cx,
2879 QualName::new(None, ns!(html), local_name!("style")),
2880 None,
2881 &document,
2882 ElementCreator::ScriptCreated,
2883 CustomElementCreationMode::Asynchronous,
2884 None,
2885 );
2886
2887 style
2888 .upcast::<Node>()
2889 .set_text_content_for_element(cx, Some(DOMString::from(MEDIA_CONTROL_CSS)));
2890
2891 if let Err(e) = shadow_root
2892 .upcast::<Node>()
2893 .AppendChild(cx, style.upcast::<Node>())
2894 {
2895 warn!("Could not render media controls {:?}", e);
2896 }
2897
2898 self.upcast::<Node>().dirty(NodeDamage::Other);
2899 }
2900
2901 fn remove_controls(&self) {
2902 if let Some(id) = self.media_controls_id.borrow_mut().take() {
2903 self.owner_document().unregister_media_controls(&id);
2904 }
2905 }
2906
2907 pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2909 self.video_renderer
2910 .lock()
2911 .unwrap()
2912 .current_frame_holder
2913 .as_ref()
2914 .map(|holder| holder.get_frame())
2915 }
2916
2917 pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2920 let (current_frame, poster_frame) = {
2921 let renderer = self.video_renderer.lock().unwrap();
2922 (renderer.current_frame, renderer.poster_frame)
2923 };
2924
2925 if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2928 return poster_frame;
2929 }
2930
2931 current_frame
2932 }
2933
2934 pub(crate) fn set_audio_renderer(
2939 &self,
2940 audio_renderer: Option<Arc<Mutex<dyn AudioRenderer>>>,
2941 cx: &mut JSContext,
2942 ) {
2943 *self.audio_renderer.borrow_mut() = audio_renderer;
2944
2945 let had_player = {
2946 if let Some(ref player) = *self.player.borrow() {
2947 if let Err(error) = player.lock().unwrap().stop() {
2948 error!("Could not stop player: {error:?}");
2949 }
2950 true
2951 } else {
2952 false
2953 }
2954 };
2955
2956 if had_player {
2957 self.media_element_load_algorithm(cx);
2958 }
2959 }
2960
2961 fn send_media_session_event(&self, cx: &mut JSContext, event: MediaSessionEvent) {
2962 let global = self.global();
2963 let media_session = global.as_window().Navigator(cx).MediaSession(cx);
2964
2965 media_session.register_media_instance(self);
2966
2967 media_session.send_event(event);
2968 }
2969
2970 pub(crate) fn origin_is_clean(&self) -> bool {
2972 if self.src_object.borrow().is_some() {
2974 return true;
2977 }
2978
2979 if self.resource_url.borrow().is_some() {
2981 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2985 return current_fetch_context.origin_is_clean();
2986 }
2987 }
2988
2989 true
2990 }
2991
2992 pub(crate) fn was_added_to_list_of_text_tracks(&self) {
2993 let this = Trusted::new(self);
2998 self.global()
2999 .task_manager()
3000 .media_element_task_source()
3001 .queue(task!(track_event_queue: move |cx| {
3002 let element = this.root();
3003 if element.did_perform_automatic_track_selection.get() {
3007 return;
3008 }
3009 element.honor_user_preferences_for_automatic_text_track_selection(cx);
3011 }));
3012 }
3013
3014 fn honor_user_preferences_for_automatic_text_track_selection(&self, cx: &mut JSContext) {
3016 self.perform_automatic_text_track_selection(
3018 cx,
3019 vec![TextTrackKind::Subtitles, TextTrackKind::Captions],
3020 );
3021 self.perform_automatic_text_track_selection(cx, vec![TextTrackKind::Descriptions]);
3023 self.did_perform_automatic_track_selection.set(true);
3030 }
3031
3032 fn perform_automatic_text_track_selection(
3034 &self,
3035 cx: &mut JSContext,
3036 text_track_kinds: Vec<TextTrackKind>,
3037 ) {
3038 let Some(text_tracks_list) = self.text_tracks_list.get() else {
3042 return;
3043 };
3044 let candidates = text_tracks_list.tracks_for_kinds(text_track_kinds);
3045 if candidates.is_empty() {
3047 return;
3048 }
3049 if candidates
3051 .iter()
3052 .any(|candidate| candidate.Mode() == TextTrackMode::Showing)
3053 {
3054 return;
3055 }
3056 if let Some(default_candidate) = candidates.iter().find(|candidate| {
3063 candidate.associated_track().is_some_and(|track| {
3064 track
3065 .upcast::<Element>()
3066 .has_attribute(&local_name!("default"))
3067 }) && candidate.Mode() == TextTrackMode::Disabled
3068 }) {
3069 default_candidate.set_text_track_mode(cx, TextTrackMode::Showing);
3070 }
3071 }
3072}
3073
3074impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
3075 fn NetworkState(&self) -> u16 {
3077 self.network_state.get() as u16
3078 }
3079
3080 fn ReadyState(&self) -> u16 {
3082 self.ready_state.get() as u16
3083 }
3084
3085 make_bool_getter!(Autoplay, "autoplay");
3087 make_bool_setter!(SetAutoplay, "autoplay");
3089
3090 make_bool_getter!(Loop, "loop");
3092 make_bool_setter!(SetLoop, "loop");
3094
3095 make_bool_getter!(DefaultMuted, "muted");
3097 make_bool_setter!(SetDefaultMuted, "muted");
3099
3100 make_bool_getter!(Controls, "controls");
3102 make_bool_setter!(SetControls, "controls");
3104
3105 make_url_getter!(Src, "src");
3107
3108 make_url_setter!(SetSrc, "src");
3110
3111 fn GetCrossOrigin(&self) -> Option<DOMString> {
3113 reflect_cross_origin_attribute(self.upcast::<Element>())
3114 }
3115 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
3117 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
3118 }
3119
3120 fn Muted(&self) -> bool {
3122 self.muted.get()
3123 }
3124
3125 fn SetMuted(&self, _cx: &mut JSContext, value: bool) {
3127 if self.muted.get() == value {
3128 return;
3129 }
3130
3131 self.muted.set(value);
3132
3133 if let Some(ref player) = *self.player.borrow() &&
3134 let Err(error) = player.lock().unwrap().set_mute(value)
3135 {
3136 warn!("Could not set mute state: {error:?}");
3137 }
3138
3139 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3142
3143 if !self.is_allowed_to_play() {
3146 self.internal_pause_steps();
3147 }
3148 }
3149
3150 fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
3152 (*self.src_object.borrow())
3153 .as_ref()
3154 .map(|src_object| match src_object {
3155 SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
3156 SrcObject::MediaStream(stream) => {
3157 MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
3158 },
3159 })
3160 }
3161
3162 fn SetSrcObject(&self, cx: &mut JSContext, value: Option<MediaStreamOrBlob>) {
3164 *self.src_object.borrow_mut() = value.map(|value| value.into());
3165 self.media_element_load_algorithm(cx);
3166 }
3167
3168 make_enumerated_getter!(
3171 Preload,
3172 "preload",
3173 "none" | "metadata" | "auto",
3174 missing => "auto",
3175 invalid => "auto"
3176 );
3177
3178 make_setter!(SetPreload, "preload");
3180
3181 fn CurrentSrc(&self) -> USVString {
3183 USVString(self.current_src.borrow().clone())
3184 }
3185
3186 fn Load(&self, cx: &mut JSContext) {
3188 self.media_element_load_algorithm(cx);
3189 }
3190
3191 fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
3193 match ServoMedia::get().can_play_type(&type_.str()) {
3194 SupportsMediaType::No => CanPlayTypeResult::_empty,
3195 SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
3196 SupportsMediaType::Probably => CanPlayTypeResult::Probably,
3197 }
3198 }
3199
3200 fn GetError(&self) -> Option<DomRoot<MediaError>> {
3202 self.error.get()
3203 }
3204
3205 fn Play(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
3207 let promise = Promise::new_in_realm(cx);
3208
3209 if self
3216 .error
3217 .get()
3218 .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
3219 {
3220 promise.reject_error(cx, Error::NotSupported(None));
3221 return promise;
3222 }
3223
3224 self.push_pending_play_promise(&promise);
3227
3228 self.internal_play_steps(cx);
3230
3231 promise
3233 }
3234
3235 fn Pause(&self, cx: &mut JSContext) {
3237 if self.network_state.get() == NetworkState::Empty {
3240 self.invoke_resource_selection_algorithm(cx);
3241 }
3242
3243 self.internal_pause_steps();
3245 }
3246
3247 fn Paused(&self) -> bool {
3249 self.paused.get()
3250 }
3251
3252 fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
3254 Ok(Finite::wrap(self.default_playback_rate.get()))
3255 }
3256
3257 fn SetDefaultPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3259 let min_allowed = -64.0;
3262 let max_allowed = 64.0;
3263 if *value < min_allowed || *value > max_allowed {
3264 return Err(Error::NotSupported(None));
3265 }
3266
3267 if self.default_playback_rate.get() == *value {
3268 return Ok(());
3269 }
3270
3271 self.default_playback_rate.set(*value);
3272
3273 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3276
3277 Ok(())
3278 }
3279
3280 fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
3282 Ok(Finite::wrap(self.playback_rate.get()))
3283 }
3284
3285 fn SetPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3287 let min_allowed = -64.0;
3292 let max_allowed = 64.0;
3293 if *value < min_allowed || *value > max_allowed {
3294 return Err(Error::NotSupported(None));
3295 }
3296
3297 if self.playback_rate.get() == *value {
3298 return Ok(());
3299 }
3300
3301 self.playback_rate.set(*value);
3304
3305 if self.is_potentially_playing() &&
3306 let Some(ref player) = *self.player.borrow() &&
3307 let Err(error) = player.lock().unwrap().set_playback_rate(*value)
3308 {
3309 warn!("Could not set the playback rate: {error:?}");
3310 }
3311
3312 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3315
3316 Ok(())
3317 }
3318
3319 fn Duration(&self) -> f64 {
3321 self.duration.get()
3322 }
3323
3324 fn CurrentTime(&self) -> Finite<f64> {
3326 Finite::wrap(if self.default_playback_start_position.get() != 0. {
3327 self.default_playback_start_position.get()
3328 } else if self.seeking.get() {
3329 self.current_seek_position.get()
3334 } else {
3335 self.official_playback_position.get()
3336 })
3337 }
3338
3339 fn SetCurrentTime(&self, _cx: &mut JSContext, time: Finite<f64>) {
3341 if self.ready_state.get() == ReadyState::HaveNothing {
3342 self.default_playback_start_position.set(*time);
3343 } else {
3344 self.official_playback_position.set(*time);
3345 self.seek(*time, false);
3346 }
3347 }
3348
3349 fn Seeking(&self) -> bool {
3351 self.seeking.get()
3352 }
3353
3354 fn Ended(&self) -> bool {
3356 self.ended_playback(LoopCondition::Included) &&
3357 self.direction_of_playback() == PlaybackDirection::Forwards
3358 }
3359
3360 fn FastSeek(&self, time: Finite<f64>) {
3362 self.seek(*time, true);
3363 }
3364
3365 fn Played(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3367 TimeRanges::new(cx, self.global().as_window(), self.played.borrow().clone())
3368 }
3369
3370 fn Seekable(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3372 TimeRanges::new(cx, self.global().as_window(), self.seekable())
3373 }
3374
3375 fn Buffered(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3377 let mut buffered = TimeRangesContainer::default();
3378 if let Some(ref player) = *self.player.borrow() {
3379 let ranges = player.lock().unwrap().buffered();
3380 for range in ranges {
3381 let _ = buffered.add(range.start, range.end);
3382 }
3383 }
3384 TimeRanges::new(cx, self.global().as_window(), buffered)
3385 }
3386
3387 fn AudioTracks(&self, cx: &mut JSContext) -> DomRoot<AudioTrackList> {
3389 let window = self.owner_window();
3390 self.audio_tracks_list
3391 .or_init(|| AudioTrackList::new(cx, &window, &[], Some(self)))
3392 }
3393
3394 fn VideoTracks(&self, cx: &mut JSContext) -> DomRoot<VideoTrackList> {
3396 let window = self.owner_window();
3397 self.video_tracks_list
3398 .or_init(|| VideoTrackList::new(cx, &window, &[], Some(self)))
3399 }
3400
3401 fn TextTracks(&self, cx: &mut JSContext) -> DomRoot<TextTrackList> {
3403 let window = self.owner_window();
3408 self.text_tracks_list
3409 .or_init(|| TextTrackList::new(cx, &window, &[]))
3410 }
3411
3412 fn AddTextTrack(
3414 &self,
3415 cx: &mut JSContext,
3416 kind: TextTrackKind,
3417 label: DOMString,
3418 language: DOMString,
3419 ) -> DomRoot<TextTrack> {
3420 let window = self.owner_window();
3421 let track = TextTrack::new(
3428 cx,
3429 &window,
3430 "".into(),
3431 kind,
3432 label,
3433 language,
3434 TextTrackMode::Hidden,
3435 None,
3436 );
3437 self.TextTracks(cx).add(self, &track);
3443 DomRoot::from_ref(&track)
3445 }
3446
3447 fn GetVolume(&self) -> Fallible<Finite<f64>> {
3449 Ok(Finite::wrap(self.volume.get()))
3450 }
3451
3452 fn SetVolume(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3454 let minimum_volume = 0.0;
3457 let maximum_volume = 1.0;
3458 if *value < minimum_volume || *value > maximum_volume {
3459 return Err(Error::IndexSize(None));
3460 }
3461
3462 if self.volume.get() == *value {
3463 return Ok(());
3464 }
3465
3466 self.volume.set(*value);
3467
3468 if let Some(ref player) = *self.player.borrow() &&
3469 let Err(error) = player.lock().unwrap().set_volume(*value)
3470 {
3471 warn!("Could not set the volume: {error:?}");
3472 }
3473
3474 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3477
3478 if !self.is_allowed_to_play() {
3481 self.internal_pause_steps();
3482 }
3483
3484 Ok(())
3485 }
3486}
3487
3488impl VirtualMethods for HTMLMediaElement {
3489 fn super_type(&self) -> Option<&dyn VirtualMethods> {
3490 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3491 }
3492
3493 fn attribute_mutated(
3494 &self,
3495 cx: &mut JSContext,
3496 attr: AttrRef<'_>,
3497 mutation: AttributeMutation,
3498 ) {
3499 self.super_type()
3500 .unwrap()
3501 .attribute_mutated(cx, attr, mutation);
3502
3503 match *attr.local_name() {
3504 local_name!("muted") => {
3505 if let AttributeMutation::Set(
3509 _,
3510 AttributeMutationReason::ByCloning | AttributeMutationReason::ByParser,
3511 ) = mutation
3512 {
3513 self.SetMuted(cx, true);
3514 }
3515 },
3516 local_name!("src") => {
3517 if !mutation.is_removal() {
3522 self.media_element_load_algorithm(cx);
3523 }
3524 },
3525 local_name!("controls") => {
3526 if mutation.new_value(attr).is_some() {
3527 self.render_controls(cx);
3528 } else {
3529 self.remove_controls();
3530 }
3531 },
3532 _ => (),
3533 };
3534 }
3535
3536 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
3538 self.super_type().unwrap().unbind_from_tree(cx, context);
3539
3540 self.remove_controls();
3541
3542 if context.tree_connected {
3546 let task = MediaElementMicrotask::PauseIfNotInDocument {
3547 elem: Dom::from_ref(self),
3548 };
3549 ScriptThread::await_stable_state(cx, Box::new(task));
3550 }
3551 }
3552
3553 fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
3554 self.super_type().unwrap().adopting_steps(cx, old_doc);
3555
3556 if let Some(id) = &*self.media_controls_id.borrow() {
3560 let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3561 error!("Missing media controls shadow root");
3562 return;
3563 };
3564
3565 old_doc.unregister_media_controls(id);
3566 self.owner_document()
3567 .register_media_controls(id, &shadow_root);
3568 }
3569 }
3570}
3571
3572#[derive(JSTraceable, MallocSizeOf)]
3573pub(crate) enum MediaElementMicrotask {
3574 ResourceSelection {
3575 elem: Dom<HTMLMediaElement>,
3576 generation_id: u32,
3577 #[no_trace]
3578 base_url: ServoUrl,
3579 },
3580 PauseIfNotInDocument {
3581 elem: Dom<HTMLMediaElement>,
3582 },
3583 Seeked {
3584 elem: Dom<HTMLMediaElement>,
3585 generation_id: u32,
3586 },
3587 SelectNextSourceChild {
3588 elem: Dom<HTMLMediaElement>,
3589 generation_id: u32,
3590 },
3591 SelectNextSourceChildAfterWait {
3592 elem: Dom<HTMLMediaElement>,
3593 generation_id: u32,
3594 },
3595}
3596
3597impl MicrotaskRunnable for MediaElementMicrotask {
3598 fn handler(&self, cx: &mut JSContext) {
3599 let mut realm = match self {
3600 &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3601 &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3602 &MediaElementMicrotask::Seeked { ref elem, .. } |
3603 &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3604 &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3605 enter_auto_realm(cx, &**elem)
3606 },
3607 };
3608 let cx = &mut realm;
3609 match self {
3610 &MediaElementMicrotask::ResourceSelection {
3611 ref elem,
3612 generation_id,
3613 ref base_url,
3614 } => {
3615 if generation_id == elem.generation_id.get() {
3616 elem.resource_selection_algorithm_sync(base_url.clone(), cx);
3617 }
3618 },
3619 MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3621 if elem.upcast::<Node>().is_connected() {
3623 return;
3624 }
3625 elem.internal_pause_steps();
3627 },
3628 &MediaElementMicrotask::Seeked {
3629 ref elem,
3630 generation_id,
3631 } => {
3632 if generation_id == elem.generation_id.get() {
3633 elem.seek_end();
3634 }
3635 },
3636 &MediaElementMicrotask::SelectNextSourceChild {
3637 ref elem,
3638 generation_id,
3639 } => {
3640 if generation_id == elem.generation_id.get() {
3641 elem.select_next_source_child(cx);
3642 }
3643 },
3644 &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3645 ref elem,
3646 generation_id,
3647 } => {
3648 if generation_id == elem.generation_id.get() {
3649 elem.select_next_source_child_after_wait(cx);
3650 }
3651 },
3652 }
3653 }
3654}
3655
3656enum Resource {
3657 Object,
3658 Url(ServoUrl),
3659}
3660
3661#[derive(Debug, MallocSizeOf, PartialEq)]
3662enum DataBuffer {
3663 Payload(Vec<u8>),
3664 EndOfStream,
3665}
3666
3667#[derive(MallocSizeOf)]
3668struct BufferedDataSource {
3669 locked: Cell<bool>,
3674 buffers: VecDeque<DataBuffer>,
3676}
3677
3678impl BufferedDataSource {
3679 fn new() -> BufferedDataSource {
3680 BufferedDataSource {
3681 locked: Cell::new(true),
3682 buffers: VecDeque::default(),
3683 }
3684 }
3685
3686 fn set_locked(&self, locked: bool) {
3687 self.locked.set(locked)
3688 }
3689
3690 fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3691 debug_assert_ne!(
3692 self.buffers.back(),
3693 Some(&DataBuffer::EndOfStream),
3694 "The media backend not expects any further data after end of stream"
3695 );
3696
3697 self.buffers.push_back(buffer);
3698 }
3699
3700 fn process_into_player_from_queue(
3701 &mut self,
3702 player: &Arc<Mutex<dyn Player>>,
3703 ) -> Result<(), PlayerError> {
3704 if self.locked.get() {
3706 return Ok(());
3707 }
3708
3709 while let Some(buffer) = self.buffers.pop_front() {
3710 match buffer {
3711 DataBuffer::Payload(payload) => {
3712 if let Err(error) = player.lock().unwrap().push_data(payload) {
3713 warn!("Could not push input data to player: {error:?}");
3714 return Err(error);
3715 }
3716 },
3717 DataBuffer::EndOfStream => {
3718 if let Err(error) = player.lock().unwrap().end_of_stream() {
3719 warn!("Could not signal EOS to player: {error:?}");
3720 return Err(error);
3721 }
3722 },
3723 }
3724 }
3725
3726 Ok(())
3727 }
3728
3729 fn reset(&mut self) {
3730 self.locked.set(true);
3731 self.buffers.clear();
3732 }
3733}
3734
3735#[derive(Debug, MallocSizeOf, PartialEq)]
3737enum CancelReason {
3738 Backoff,
3740 Error,
3742 Abort,
3744}
3745
3746#[derive(MallocSizeOf)]
3747pub(crate) struct HTMLMediaElementFetchContext {
3748 request_id: RequestId,
3750 cancel_reason: Option<CancelReason>,
3752 is_seekable: bool,
3754 origin_clean: bool,
3756 data_source: RefCell<BufferedDataSource>,
3758 fetch_canceller: FetchCanceller,
3761}
3762
3763impl HTMLMediaElementFetchContext {
3764 fn new(
3765 request_id: RequestId,
3766 core_resource_thread: CoreResourceThread,
3767 ) -> HTMLMediaElementFetchContext {
3768 HTMLMediaElementFetchContext {
3769 request_id,
3770 cancel_reason: None,
3771 is_seekable: false,
3772 origin_clean: true,
3773 data_source: RefCell::new(BufferedDataSource::new()),
3774 fetch_canceller: FetchCanceller::new(request_id, false, core_resource_thread),
3775 }
3776 }
3777
3778 fn request_id(&self) -> RequestId {
3779 self.request_id
3780 }
3781
3782 fn is_seekable(&self) -> bool {
3783 self.is_seekable
3784 }
3785
3786 fn set_seekable(&mut self, seekable: bool) {
3787 self.is_seekable = seekable;
3788 }
3789
3790 fn origin_is_clean(&self) -> bool {
3791 self.origin_clean
3792 }
3793
3794 fn set_origin_clean(&mut self, origin_clean: bool) {
3795 self.origin_clean = origin_clean;
3796 }
3797
3798 fn data_source(&self) -> &RefCell<BufferedDataSource> {
3799 &self.data_source
3800 }
3801
3802 fn cancel(&mut self, reason: CancelReason) {
3803 if self.cancel_reason.is_some() {
3804 return;
3805 }
3806 self.cancel_reason = Some(reason);
3807 self.data_source.borrow_mut().reset();
3808 self.fetch_canceller.abort();
3809 }
3810
3811 fn cancel_reason(&self) -> &Option<CancelReason> {
3812 &self.cancel_reason
3813 }
3814}
3815
3816struct HTMLMediaElementFetchListener {
3817 element: Trusted<HTMLMediaElement>,
3819 generation_id: u32,
3821 request_id: RequestId,
3823 next_progress_event: Instant,
3825 url: ServoUrl,
3827 expected_content_length: Option<u64>,
3829 fetched_content_length: u64,
3831 content_length_to_discard: u64,
3835}
3836
3837impl FetchResponseListener for HTMLMediaElementFetchListener {
3838 fn process_request_body(&mut self, _: RequestId) {}
3839
3840 fn process_response(
3841 &mut self,
3842 cx: &mut JSContext,
3843 _: RequestId,
3844 metadata: Result<FetchMetadata, NetworkError>,
3845 ) {
3846 let element = self.element.root();
3847
3848 let (metadata, origin_clean) = match metadata {
3849 Ok(fetch_metadata) => match fetch_metadata {
3850 FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3851 FetchMetadata::Filtered { filtered, unsafe_ } => (
3852 Some(unsafe_),
3853 matches!(
3854 filtered,
3855 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3856 ),
3857 ),
3858 },
3859 Err(_) => (None, true),
3860 };
3861
3862 let (status_is_success, is_seekable) =
3863 metadata.as_ref().map_or((false, false), |metadata| {
3864 let status = &metadata.status;
3865 (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3866 });
3867
3868 if !status_is_success {
3870 if element.ready_state.get() == ReadyState::HaveNothing {
3871 element.media_data_processing_failure_steps(cx);
3873 } else {
3874 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3876 }
3877 return;
3878 }
3879
3880 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3881 current_fetch_context.set_seekable(is_seekable);
3882 current_fetch_context.set_origin_clean(origin_clean);
3883 }
3884
3885 if let Some(metadata) = metadata.as_ref() &&
3886 let Some(headers) = metadata.headers.as_ref()
3887 {
3888 let content_length = if let Some(content_range) = headers.typed_get::<ContentRange>() {
3891 content_range.bytes_len()
3892 } else {
3893 headers
3894 .typed_get::<ContentLength>()
3895 .map(|content_length| content_length.0)
3896 };
3897
3898 if content_length != self.expected_content_length &&
3900 let Some(content_length) = content_length
3901 {
3902 self.expected_content_length = Some(content_length);
3903 }
3904 }
3905
3906 if let Err(e) = element
3908 .player
3909 .borrow()
3910 .as_ref()
3911 .unwrap()
3912 .lock()
3913 .unwrap()
3914 .set_seekable(is_seekable)
3915 {
3916 warn!("Could not set player seekable {:?}", e);
3917 }
3918
3919 if let Some(expected_content_length) = self.expected_content_length &&
3920 let Err(e) = element
3921 .player
3922 .borrow()
3923 .as_ref()
3924 .unwrap()
3925 .lock()
3926 .unwrap()
3927 .set_input_size(expected_content_length)
3928 {
3929 warn!("Could not set player input size {:?}", e);
3930 }
3931 }
3932
3933 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
3934 let element = self.element.root();
3935
3936 self.fetched_content_length += chunk.len() as u64;
3937
3938 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3940 if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3941 return;
3942 }
3943
3944 let payload = if !current_fetch_context.is_seekable() &&
3946 self.content_length_to_discard != 0
3947 {
3948 if chunk.len() as u64 > self.content_length_to_discard {
3949 let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
3950 self.content_length_to_discard = 0;
3951 shrink_chunk
3952 } else {
3953 self.content_length_to_discard -= chunk.len() as u64;
3955 return;
3956 }
3957 } else {
3958 chunk
3959 };
3960
3961 if let Err(e) = {
3962 let mut data_source = current_fetch_context.data_source().borrow_mut();
3963 data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
3964 data_source
3965 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3966 } {
3967 if e == PlayerError::EnoughData {
3972 current_fetch_context.cancel(CancelReason::Backoff);
3973 }
3974 return;
3975 }
3976 }
3977
3978 if Instant::now() > self.next_progress_event {
3983 element.queue_media_element_task_to_fire_event(atom!("progress"));
3984 self.next_progress_event = Instant::now() + Duration::from_millis(350);
3985 }
3986 }
3987
3988 fn process_response_eof(
3989 self,
3990 cx: &mut JSContext,
3991 _: RequestId,
3992 status: Result<(), NetworkError>,
3993 timing: ResourceFetchTiming,
3994 ) {
3995 let element = self.element.root();
3996
3997 if status.is_ok() && self.fetched_content_length != 0 {
3999 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
4004 {
4005 if self.expected_content_length.is_none() &&
4012 let Err(e) = element
4013 .player
4014 .borrow()
4015 .as_ref()
4016 .unwrap()
4017 .lock()
4018 .unwrap()
4019 .set_input_size(self.fetched_content_length)
4020 {
4021 warn!("Could not set player input size {:?}", e);
4022 }
4023
4024 let mut data_source = current_fetch_context.data_source().borrow_mut();
4025
4026 data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
4027 let _ = data_source
4028 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
4029 }
4030
4031 element
4033 .upcast::<EventTarget>()
4034 .fire_event(cx, atom!("progress"));
4035
4036 element.network_state.set(NetworkState::Idle);
4039
4040 element
4041 .upcast::<EventTarget>()
4042 .fire_event(cx, atom!("suspend"));
4043 } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
4044 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
4046 } else {
4047 element.media_data_processing_failure_steps(cx);
4050 }
4051
4052 network_listener::submit_timing(cx, &self, &status, &timing);
4053 }
4054
4055 fn process_csp_violations(
4056 &mut self,
4057 cx: &mut js::context::JSContext,
4058 _request_id: RequestId,
4059 violations: Vec<Violation>,
4060 ) {
4061 let global = &self.resource_timing_global();
4062 global.report_csp_violations(cx, violations, None, None);
4063 }
4064
4065 fn should_invoke(&self) -> bool {
4066 let element = self.element.root();
4067
4068 if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
4069 return false;
4070 }
4071
4072 let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
4073 return false;
4074 };
4075
4076 if current_fetch_context.request_id() != self.request_id {
4078 return false;
4079 }
4080
4081 if let Some(cancel_reason) = current_fetch_context.cancel_reason() &&
4084 matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort)
4085 {
4086 return false;
4087 }
4088
4089 true
4090 }
4091}
4092
4093impl ResourceTimingListener for HTMLMediaElementFetchListener {
4094 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
4095 let initiator_type = InitiatorType::LocalName(
4096 self.element
4097 .root()
4098 .upcast::<Element>()
4099 .local_name()
4100 .to_string(),
4101 );
4102 (initiator_type, self.url.clone())
4103 }
4104
4105 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
4106 self.element.root().owner_document().global()
4107 }
4108}
4109
4110impl HTMLMediaElementFetchListener {
4111 fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
4112 Self {
4113 element: Trusted::new(element),
4114 generation_id: element.generation_id.get(),
4115 request_id,
4116 next_progress_event: Instant::now() + Duration::from_millis(350),
4117 url,
4118 expected_content_length: None,
4119 fetched_content_length: 0,
4120 content_length_to_discard: offset,
4121 }
4122 }
4123}
4124
4125#[derive(JSTraceable, MallocSizeOf)]
4129struct HTMLMediaElementEventHandler {
4130 element: WeakRef<HTMLMediaElement>,
4131}
4132
4133#[expect(unsafe_code)]
4134unsafe impl Send for HTMLMediaElementEventHandler {}
4135
4136impl HTMLMediaElementEventHandler {
4137 fn new(element: &HTMLMediaElement) -> Self {
4138 Self {
4139 element: WeakRef::new(element),
4140 }
4141 }
4142
4143 fn handle_player_event(&self, player_id: usize, event: PlayerEvent, cx: &mut JSContext) {
4144 let Some(element) = self.element.root() else {
4145 return;
4146 };
4147
4148 if element.player_id().is_none_or(|id| id != player_id) {
4150 return;
4151 }
4152
4153 match event {
4154 PlayerEvent::DurationChanged(duration) => element.playback_duration_changed(duration),
4155 PlayerEvent::EndOfStream => element.playback_end(),
4156 PlayerEvent::EnoughData => element.playback_enough_data(),
4157 PlayerEvent::Error(ref error) => element.playback_error(error, cx),
4158 PlayerEvent::MetadataUpdated(ref metadata) => {
4159 element.playback_metadata_updated(cx, metadata)
4160 },
4161 PlayerEvent::NeedData => element.playback_need_data(),
4162 PlayerEvent::PositionChanged(position) => {
4163 element.playback_position_changed(cx, position)
4164 },
4165 PlayerEvent::SeekData(offset, seek_lock) => {
4166 element.fetch_request(cx, Some(offset), Some(seek_lock))
4167 },
4168 PlayerEvent::SeekDone(position) => element.playback_seek_done(cx, position),
4169 PlayerEvent::StateChanged(ref state) => element.playback_state_changed(cx, state),
4170 PlayerEvent::VideoFrameUpdated => element.playback_video_frame_updated(),
4171 }
4172 }
4173}
4174
4175impl Drop for HTMLMediaElementEventHandler {
4176 fn drop(&mut self) {
4177 assert_in_script();
4181 }
4182}