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 bytes::Bytes;
13use content_security_policy::sandboxing_directive::SandboxingFlagSet;
14use dom_struct::dom_struct;
15use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState};
16use euclid::default::Size2D;
17use headers::{ContentLength, ContentRange, HeaderMapExt, Range as RangeHeader};
18use html5ever::{LocalName, Prefix, QualName, local_name, ns};
19use http::StatusCode;
20use http::header::HeaderMap;
21use js::context::{JSContext, NoGC};
22use js::realm::CurrentRealm;
23use layout_api::MediaFrame;
24use media::{GLPlayerMsg, GLPlayerMsgForward, WindowGLContext};
25use net_traits::request::{Destination, RequestId};
26use net_traits::{
27 CoreResourceThread, FetchMetadata, FilteredMetadata, NetworkError, ResourceFetchTiming,
28};
29use paint_api::{CrossProcessPaintApi, ImageUpdate, SerializableImageData};
30use pixels::RasterImage;
31use script_bindings::assert::assert_in_script;
32use script_bindings::cell::DomRefCell;
33use script_bindings::codegen::InheritTypes::{
34 ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
35};
36use script_bindings::weakref::WeakRef;
37use servo_base::generic_channel::{self, GenericCallback, GenericSharedMemory};
38use servo_base::id::WebViewId;
39use servo_config::pref;
40use servo_media::player::audio::AudioRenderer;
41use servo_media::player::video::{VideoFrame, VideoFrameRenderer};
42use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType};
43use servo_media::{ClientContextId, ServoMedia, SupportsMediaType};
44use servo_url::ServoUrl;
45use stylo_atoms::Atom;
46use uuid::Uuid;
47use webrender_api::{
48 ExternalImageData, ExternalImageId, ExternalImageType, ImageBufferKind, ImageDescriptor,
49 ImageDescriptorFlags, ImageFormat, ImageKey,
50};
51
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::event_loop::document_loader::{LoadBlocker, LoadType};
107use crate::event_loop::script_thread::ScriptThread;
108use crate::fetch::fetch::{
109 FetchCanceller, RequestWithGlobalScope, create_a_potential_cors_request,
110};
111use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
112use crate::realms::enter_auto_realm;
113use crate::runtime::microtask::MicrotaskRunnable;
114use crate::tasks::task_source::SendableTaskSource;
115
116static MEDIA_CONTROL_CSS: &str = include_str!("../../../resources/media-controls.css");
118
119static MEDIA_CONTROL_JS: &str = include_str!("../../../resources/media-controls.js");
121
122const SEEK_POSITION_THRESHOLD: f64 = 0.5;
126
127#[derive(MallocSizeOf, PartialEq)]
128enum FrameStatus {
129 Locked,
130 Unlocked,
131}
132
133#[derive(MallocSizeOf)]
134struct FrameHolder(
135 FrameStatus,
136 #[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
137);
138
139impl FrameHolder {
140 fn new(frame: VideoFrame) -> FrameHolder {
141 FrameHolder(FrameStatus::Unlocked, frame)
142 }
143
144 fn lock(&mut self) {
145 if self.0 == FrameStatus::Unlocked {
146 self.0 = FrameStatus::Locked;
147 };
148 }
149
150 fn unlock(&mut self) {
151 if self.0 == FrameStatus::Locked {
152 self.0 = FrameStatus::Unlocked;
153 };
154 }
155
156 fn set(&mut self, new_frame: VideoFrame) {
157 if self.0 == FrameStatus::Unlocked {
158 self.1 = new_frame
159 };
160 }
161
162 fn get(&self) -> (u32, Size2D<i32>, usize) {
163 if self.0 == FrameStatus::Locked {
164 (
165 self.1.get_texture_id(),
166 Size2D::new(self.1.get_width(), self.1.get_height()),
167 0,
168 )
169 } else {
170 unreachable!();
171 }
172 }
173
174 fn get_frame(&self) -> VideoFrame {
175 self.1.clone()
176 }
177}
178
179#[derive(MallocSizeOf)]
180pub(crate) struct MediaFrameRenderer {
181 webview_id: WebViewId,
182 player_id: Option<usize>,
183 #[conditional_malloc_size_of]
184 glplayer_id: Arc<RwLock<Option<u64>>>,
185 paint_api: CrossProcessPaintApi,
186 #[ignore_malloc_size_of = "Defined in other crates"]
187 player_context: WindowGLContext,
188 current_frame: Option<MediaFrame>,
189 old_frame: Option<ImageKey>,
190 very_old_frame: Option<ImageKey>,
191 current_frame_holder: Option<FrameHolder>,
192 poster_frame: Option<MediaFrame>,
194}
195
196impl MediaFrameRenderer {
197 fn new(
198 webview_id: WebViewId,
199 paint_api: CrossProcessPaintApi,
200 player_context: WindowGLContext,
201 ) -> Self {
202 Self {
203 webview_id,
204 player_id: None,
205 glplayer_id: Arc::new(RwLock::new(None)),
206 paint_api,
207 player_context,
208 current_frame: None,
209 old_frame: None,
210 very_old_frame: None,
211 current_frame_holder: None,
212 poster_frame: None,
213 }
214 }
215
216 fn setup(
217 &mut self,
218 player_id: usize,
219 task_source: SendableTaskSource,
220 weak_video_renderer: Weak<Mutex<MediaFrameRenderer>>,
221 ) {
222 self.player_id = Some(player_id);
223 let glplayer_id = self.glplayer_id.clone();
224 let callback = GenericCallback::new(move |message| {
225 let message = message.unwrap();
226 let weak_video_renderer = weak_video_renderer.clone();
227
228 let glplayer_id = glplayer_id.clone();
229 task_source.queue(task!(handle_glplayer_message: move || {
230 trace!("GLPlayer message {:?}", message);
231
232 let Some(video_renderer) = weak_video_renderer.upgrade() else {
233 return;
234 };
235
236 match message {
237 GLPlayerMsgForward::Lock(sender) => {
238 if let Some(holder) = video_renderer
239 .lock()
240 .unwrap()
241 .current_frame_holder
242 .as_mut() {
243 holder.lock();
244 sender.send(holder.get()).unwrap();
245 };
246 },
247 GLPlayerMsgForward::Unlock() => {
248 if let Some(holder) = video_renderer
249 .lock()
250 .unwrap()
251 .current_frame_holder
252 .as_mut() { holder.unlock() }
253 },
254 GLPlayerMsgForward::PlayerId(id) => {
255 let mut glplayer_id = glplayer_id.write().unwrap();
256 if let Some(already_set_id) = *glplayer_id {
257 error!("Player id already set to {already_set_id} will be replaced with {id}");
258 }
259 *glplayer_id = Some(id);
260 },
261 }
262 }));
263 })
264 .expect("Could not create callback");
265
266 if let Some(glplayer_thread_sender) = &self.player_context.glplayer_thread_sender {
267 glplayer_thread_sender
268 .send(GLPlayerMsg::RegisterPlayer(callback))
269 .unwrap();
270 }
271 }
272
273 fn reset(&mut self) {
274 self.player_id = None;
275
276 if let Some(glplayer_id) = self.glplayer_id.write().unwrap().take() {
277 self.player_context
278 .send(GLPlayerMsg::UnregisterPlayer(glplayer_id));
279 }
280
281 self.current_frame_holder = None;
282
283 let mut updates = smallvec::smallvec![];
284
285 if let Some(current_frame) = self.current_frame.take() {
286 updates.push(ImageUpdate::DeleteImage(current_frame.image_key));
287 }
288
289 if let Some(old_image_key) = self.old_frame.take() {
290 updates.push(ImageUpdate::DeleteImage(old_image_key));
291 }
292
293 if let Some(very_old_image_key) = self.very_old_frame.take() {
294 updates.push(ImageUpdate::DeleteImage(very_old_image_key));
295 }
296
297 if !updates.is_empty() {
298 self.paint_api
299 .update_images(self.webview_id.into(), updates);
300 }
301 }
302
303 fn set_poster_frame(&mut self, image: Option<Arc<RasterImage>>) {
304 self.poster_frame = image.and_then(|image| {
305 image.id.map(|image_key| MediaFrame {
306 image_key,
307 width: image.metadata.width as i32,
308 height: image.metadata.height as i32,
309 })
310 });
311 }
312}
313
314impl Drop for MediaFrameRenderer {
315 fn drop(&mut self) {
316 self.reset();
317 }
318}
319
320impl VideoFrameRenderer for MediaFrameRenderer {
321 fn render(&mut self, frame: VideoFrame) {
322 if self.player_id.is_none() ||
323 (frame.is_gl_texture() && self.glplayer_id.read().unwrap().is_none())
324 {
325 return;
326 }
327
328 let mut updates = smallvec::smallvec![];
329
330 if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) {
331 updates.push(ImageUpdate::DeleteImage(old_image_key));
332 }
333
334 let descriptor = ImageDescriptor::new(
335 frame.get_width(),
336 frame.get_height(),
337 ImageFormat::BGRA8,
338 ImageDescriptorFlags::empty(),
339 );
340
341 match &mut self.current_frame {
342 Some(current_frame)
343 if current_frame.width == frame.get_width() &&
344 current_frame.height == frame.get_height() =>
345 {
346 if !frame.is_gl_texture() {
347 updates.push(ImageUpdate::UpdateImage(
348 current_frame.image_key,
349 descriptor,
350 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
351 frame.get_data(),
352 )),
353 None,
354 ));
355 }
356
357 self.current_frame_holder
358 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
359 .set(frame);
360
361 if let Some(old_image_key) = self.old_frame.take() {
362 updates.push(ImageUpdate::DeleteImage(old_image_key));
363 }
364 },
365 Some(current_frame) => {
366 self.old_frame = Some(current_frame.image_key);
367
368 let Some(new_image_key) =
369 self.paint_api.generate_image_key_blocking(self.webview_id)
370 else {
371 return;
372 };
373
374 current_frame.image_key = new_image_key;
376 current_frame.width = frame.get_width();
377 current_frame.height = frame.get_height();
378
379 let image_data = self
381 .glplayer_id
382 .read()
383 .unwrap()
384 .filter(|_| frame.is_gl_texture())
385 .map(|glplayer_id| {
386 let texture_target = if frame.is_external_oes() {
387 ImageBufferKind::TextureExternal
388 } else {
389 ImageBufferKind::Texture2D
390 };
391
392 SerializableImageData::External(ExternalImageData {
393 id: ExternalImageId(glplayer_id),
394 channel_index: 0,
395 image_type: ExternalImageType::TextureHandle(texture_target),
396 normalized_uvs: false,
397 })
398 })
399 .unwrap_or_else(|| {
400 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
401 frame.get_data(),
402 ))
403 });
404
405 self.current_frame_holder
406 .get_or_insert_with(|| FrameHolder::new(frame.clone()))
407 .set(frame);
408
409 updates.push(ImageUpdate::AddImage(
410 new_image_key,
411 descriptor,
412 image_data,
413 false,
414 ));
415 },
416 None => {
417 let Some(image_key) = self.paint_api.generate_image_key_blocking(self.webview_id)
418 else {
419 return;
420 };
421
422 self.current_frame = Some(MediaFrame {
423 image_key,
424 width: frame.get_width(),
425 height: frame.get_height(),
426 });
427
428 let image_data = self
429 .glplayer_id
430 .read()
431 .unwrap()
432 .filter(|_| frame.is_gl_texture())
433 .map(|glplayer_id| {
434 let texture_target = if frame.is_external_oes() {
435 ImageBufferKind::TextureExternal
436 } else {
437 ImageBufferKind::Texture2D
438 };
439
440 SerializableImageData::External(ExternalImageData {
441 id: ExternalImageId(glplayer_id),
442 channel_index: 0,
443 image_type: ExternalImageType::TextureHandle(texture_target),
444 normalized_uvs: false,
445 })
446 })
447 .unwrap_or_else(|| {
448 SerializableImageData::Raw(GenericSharedMemory::from_arc_vec(
449 frame.get_data(),
450 ))
451 });
452
453 self.current_frame_holder = Some(FrameHolder::new(frame));
454
455 updates.push(ImageUpdate::AddImage(
456 image_key, descriptor, image_data, false,
457 ));
458 },
459 }
460 self.paint_api
461 .update_images(self.webview_id.into(), updates);
462 }
463}
464
465#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
466#[derive(JSTraceable, MallocSizeOf)]
467enum SrcObject {
468 MediaStream(Dom<MediaStream>),
469 Blob(Dom<Blob>),
470}
471
472impl From<MediaStreamOrBlob> for SrcObject {
473 fn from(src_object: MediaStreamOrBlob) -> SrcObject {
474 match src_object {
475 MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)),
476 MediaStreamOrBlob::MediaStream(stream) => {
477 SrcObject::MediaStream(Dom::from_ref(&*stream))
478 },
479 }
480 }
481}
482
483#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
484enum LoadState {
485 NotLoaded,
486 LoadingFromSrcObject,
487 LoadingFromSrcAttribute,
488 LoadingFromSourceChild,
489 WaitingForSource,
490}
491
492#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
494#[derive(JSTraceable, MallocSizeOf)]
495struct SourceChildrenPointer {
496 source_before_pointer: Dom<HTMLSourceElement>,
497 inclusive: bool,
498}
499
500impl SourceChildrenPointer {
501 fn new(source_before_pointer: &HTMLSourceElement, inclusive: bool) -> Self {
502 Self {
503 source_before_pointer: Dom::from_ref(source_before_pointer),
504 inclusive,
505 }
506 }
507}
508
509#[derive(Clone, Copy, Debug, PartialEq)]
513enum LoopCondition {
514 Included,
515 Ignored,
516}
517
518#[dom_struct]
519pub(crate) struct HTMLMediaElement {
520 htmlelement: HTMLElement,
521 network_state: Cell<NetworkState>,
523 ready_state: Cell<ReadyState>,
525 src_object: DomRefCell<Option<SrcObject>>,
527 current_src: DomRefCell<String>,
529 generation_id: Cell<u32>,
531 fired_loadeddata_event: Cell<bool>,
535 error: MutNullableDom<MediaError>,
537 paused: Cell<bool>,
539 default_playback_rate: Cell<f64>,
541 playback_rate: Cell<f64>,
543 autoplaying: Cell<bool>,
545 delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>,
547 #[conditional_malloc_size_of]
549 pending_play_promises: DomRefCell<Vec<Rc<Promise>>>,
550 #[expect(clippy::type_complexity)]
552 #[conditional_malloc_size_of]
553 in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
554 #[ignore_malloc_size_of = "servo_media"]
555 #[no_trace]
556 player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
557 #[conditional_malloc_size_of]
558 #[no_trace]
559 video_renderer: Arc<Mutex<MediaFrameRenderer>>,
560 #[ignore_malloc_size_of = "servo_media"]
561 #[no_trace]
562 audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>,
563 #[conditional_malloc_size_of]
564 #[no_trace]
565 event_handler: RefCell<Option<Arc<Mutex<HTMLMediaElementEventHandler>>>>,
566 show_poster: Cell<bool>,
568 duration: Cell<f64>,
570 current_playback_position: Cell<f64>,
572 official_playback_position: Cell<f64>,
574 default_playback_start_position: Cell<f64>,
576 volume: Cell<f64>,
578 seeking: Cell<bool>,
580 current_seek_position: Cell<f64>,
584 muted: Cell<bool>,
586 load_state: Cell<LoadState>,
588 source_children_pointer: DomRefCell<Option<SourceChildrenPointer>>,
589 current_source_child: MutNullableDom<HTMLSourceElement>,
590 #[no_trace]
592 resource_url: DomRefCell<Option<ServoUrl>>,
593 #[no_trace]
596 blob_url: DomRefCell<Option<ServoUrl>>,
597 played: DomRefCell<TimeRangesContainer>,
599 audio_tracks_list: MutNullableDom<AudioTrackList>,
601 video_tracks_list: MutNullableDom<VideoTrackList>,
603 text_tracks_list: MutNullableDom<TextTrackList>,
605 #[ignore_malloc_size_of = "Defined in std::time"]
607 next_timeupdate_event: Cell<Instant>,
608 current_fetch_context: RefCell<Option<HTMLMediaElementFetchContext>>,
610 media_controls_id: DomRefCell<Option<String>>,
615 did_perform_automatic_track_selection: Cell<bool>,
617}
618
619#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
621#[repr(u8)]
622pub(crate) enum NetworkState {
623 Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
624 Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
625 Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
626 NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8,
627}
628
629#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
631#[repr(u8)]
632#[expect(clippy::enum_variant_names)] pub(crate) enum ReadyState {
634 HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8,
635 HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8,
636 HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8,
637 HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8,
638 HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8,
639}
640
641#[derive(Clone, Copy, PartialEq)]
643enum PlaybackDirection {
644 Forwards,
645 Backwards,
646}
647
648impl HTMLMediaElement {
649 pub(crate) fn new_inherited(
650 tag_name: LocalName,
651 prefix: Option<Prefix>,
652 document: &Document,
653 ) -> Self {
654 Self {
655 htmlelement: HTMLElement::new_inherited(tag_name, prefix, document),
656 network_state: Cell::new(NetworkState::Empty),
657 ready_state: Cell::new(ReadyState::HaveNothing),
658 src_object: Default::default(),
659 current_src: DomRefCell::new("".to_owned()),
660 generation_id: Cell::new(0),
661 fired_loadeddata_event: Cell::new(false),
662 error: Default::default(),
663 paused: Cell::new(true),
664 default_playback_rate: Cell::new(1.0),
665 playback_rate: Cell::new(1.0),
666 muted: Cell::new(false),
667 load_state: Cell::new(LoadState::NotLoaded),
668 source_children_pointer: DomRefCell::new(None),
669 current_source_child: Default::default(),
670 autoplaying: Cell::new(true),
672 delaying_the_load_event_flag: Default::default(),
673 pending_play_promises: Default::default(),
674 in_flight_play_promises_queue: Default::default(),
675 player: Default::default(),
676 video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new(
677 document.webview_id(),
678 document.window().paint_api().clone(),
679 document.window().get_player_context(),
680 ))),
681 audio_renderer: Default::default(),
682 event_handler: Default::default(),
683 show_poster: Cell::new(true),
684 duration: Cell::new(f64::NAN),
685 current_playback_position: Cell::new(0.),
686 official_playback_position: Cell::new(0.),
687 default_playback_start_position: Cell::new(0.),
688 volume: Cell::new(1.0),
689 seeking: Cell::new(false),
690 current_seek_position: Cell::new(f64::NAN),
691 resource_url: DomRefCell::new(None),
692 blob_url: DomRefCell::new(None),
693 played: DomRefCell::new(TimeRangesContainer::default()),
694 audio_tracks_list: Default::default(),
695 video_tracks_list: Default::default(),
696 text_tracks_list: Default::default(),
697 next_timeupdate_event: Cell::new(Instant::now() + Duration::from_millis(250)),
698 current_fetch_context: RefCell::new(None),
699 media_controls_id: DomRefCell::new(None),
700 did_perform_automatic_track_selection: Default::default(),
701 }
702 }
703
704 pub(crate) fn network_state(&self) -> NetworkState {
705 self.network_state.get()
706 }
707
708 pub(crate) fn get_ready_state(&self) -> ReadyState {
709 self.ready_state.get()
710 }
711
712 fn media_type_id(&self) -> HTMLMediaElementTypeId {
713 match self.upcast::<Node>().type_id() {
714 NodeTypeId::Element(ElementTypeId::HTMLElement(
715 HTMLElementTypeId::HTMLMediaElement(media_type_id),
716 )) => media_type_id,
717 _ => unreachable!(),
718 }
719 }
720
721 fn update_media_state(&self) {
722 let is_playing = self
723 .player
724 .borrow()
725 .as_ref()
726 .is_some_and(|player| !player.lock().unwrap().paused());
727
728 if self.is_potentially_playing() && !is_playing {
729 if let Some(ref player) = *self.player.borrow() {
730 let player = player.lock().unwrap();
731
732 if let Err(error) = player.set_playback_rate(self.playback_rate.get()) {
733 warn!("Could not set the playback rate: {error:?}");
734 }
735 if let Err(error) = player.set_volume(self.volume.get()) {
736 warn!("Could not set the volume: {error:?}");
737 }
738 if let Err(error) = player.play() {
739 error!("Could not play media: {error:?}");
740 }
741 }
742 } else if is_playing &&
743 let Some(ref player) = *self.player.borrow() &&
744 let Err(error) = player.lock().unwrap().pause()
745 {
746 error!("Could not pause player: {error:?}");
747 }
748 }
749
750 pub(crate) fn delay_load_event(&self, delay: bool, cx: &mut JSContext) {
757 let blocker = &self.delaying_the_load_event_flag;
758
759 if delay {
760 if blocker.borrow().is_none() {
761 *blocker.borrow_mut() =
762 Some(LoadBlocker::new(&self.owner_document(), LoadType::Media));
763 }
764 } else {
765 LoadBlocker::terminate(blocker, cx);
766 }
767 }
768
769 fn time_marches_on(&self) {
771 if Instant::now() > self.next_timeupdate_event.get() {
777 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
778 self.next_timeupdate_event
779 .set(Instant::now() + Duration::from_millis(250));
780 }
781 }
782
783 fn internal_play_steps(&self, cx: &mut JSContext) {
785 if self.network_state.get() == NetworkState::Empty {
788 self.invoke_resource_selection_algorithm(cx);
789 }
790
791 if self.ended_playback(LoopCondition::Ignored) &&
797 self.direction_of_playback() == PlaybackDirection::Forwards
798 {
799 self.seek(
800 self.earliest_possible_position(),
801 false,
802 );
803 }
804
805 let state = self.ready_state.get();
806
807 if self.Paused() {
809 self.paused.set(false);
811
812 if self.show_poster.get() {
815 self.show_poster.set(false);
816 self.time_marches_on();
817 }
818
819 self.queue_media_element_task_to_fire_event(atom!("play"));
822
823 match state {
829 ReadyState::HaveNothing |
830 ReadyState::HaveMetadata |
831 ReadyState::HaveCurrentData => {
832 self.queue_media_element_task_to_fire_event(atom!("waiting"));
833 },
834 ReadyState::HaveFutureData | ReadyState::HaveEnoughData => {
835 self.notify_about_playing();
836 },
837 }
838 }
839 else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
844 self.take_pending_play_promises(Ok(()));
845
846 let this = Trusted::new(self);
847 let generation_id = self.generation_id.get();
848
849 self.owner_global()
850 .task_manager()
851 .media_element_task_source()
852 .queue(task!(resolve_pending_play_promises: move |cx| {
853 let this = this.root();
854 if generation_id != this.generation_id.get() {
855 return;
856 }
857
858 this.fulfill_in_flight_play_promises(cx, |_| {});
859 }));
860 }
861
862 self.autoplaying.set(false);
864
865 self.update_media_state();
866 }
867
868 fn internal_pause_steps(&self) {
870 self.autoplaying.set(false);
872
873 if !self.Paused() {
875 self.paused.set(true);
877
878 self.take_pending_play_promises(Err(Error::Abort(Some(
880 "Media element was paused".into(),
881 ))));
882
883 let this = Trusted::new(self);
885 let generation_id = self.generation_id.get();
886
887 self.owner_global()
888 .task_manager()
889 .media_element_task_source()
890 .queue(task!(internal_pause_steps: move |cx| {
891 let this = this.root();
892 if generation_id != this.generation_id.get() {
893 return;
894 }
895
896 this.fulfill_in_flight_play_promises(cx, |cx| {
897 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
899
900 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
902
903 });
907 }));
908
909 self.official_playback_position
911 .set(self.current_playback_position.get());
912 }
913
914 self.update_media_state();
915 }
916
917 fn is_allowed_to_play(&self) -> bool {
919 true
920 }
921
922 fn notify_about_playing(&self) {
924 self.take_pending_play_promises(Ok(()));
926
927 let this = Trusted::new(self);
929 let generation_id = self.generation_id.get();
930
931 self.owner_global()
932 .task_manager()
933 .media_element_task_source()
934 .queue(task!(notify_about_playing: move |cx| {
935 let this = this.root();
936 if generation_id != this.generation_id.get() {
937 return;
938 }
939
940 this.fulfill_in_flight_play_promises(cx, |cx| {
941 this.upcast::<EventTarget>().fire_event(cx, atom!("playing"));
943
944 });
947 }));
948 }
949
950 fn change_ready_state(&self, ready_state: ReadyState) {
952 let old_ready_state = self.ready_state.get();
953 self.ready_state.set(ready_state);
954
955 if self.network_state.get() == NetworkState::Empty {
956 return;
957 }
958
959 if old_ready_state == ready_state {
960 return;
961 }
962
963 match (old_ready_state, ready_state) {
965 (ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
968 self.queue_media_element_task_to_fire_event(atom!("loadedmetadata"));
971 return;
973 },
974 (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => {
977 if !self.fired_loadeddata_event.get() {
981 self.fired_loadeddata_event.set(true);
982
983 let this = Trusted::new(self);
984 let generation_id = self.generation_id.get();
985
986 self.owner_global()
987 .task_manager()
988 .media_element_task_source()
989 .queue(task!(media_reached_current_data: move |cx| {
990 let this = this.root();
991 if generation_id != this.generation_id.get() {
992 return;
993 }
994
995 this.upcast::<EventTarget>().fire_event(cx, atom!("loadeddata"));
996 this.delay_load_event(false, cx);
1000 }));
1001 }
1002
1003 },
1007 (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => {
1008 return;
1013 },
1014
1015 _ => (),
1016 }
1017
1018 if old_ready_state <= ReadyState::HaveCurrentData &&
1021 ready_state >= ReadyState::HaveFutureData
1022 {
1023 self.queue_media_element_task_to_fire_event(atom!("canplay"));
1026
1027 if !self.Paused() {
1030 self.notify_about_playing();
1031 }
1032 }
1033
1034 if ready_state == ReadyState::HaveEnoughData {
1036 self.queue_media_element_task_to_fire_event(atom!("canplaythrough"));
1039
1040 if self.eligible_for_autoplay() {
1043 self.paused.set(false);
1045
1046 if self.show_poster.get() {
1049 self.show_poster.set(false);
1050 self.time_marches_on();
1051 }
1052
1053 self.queue_media_element_task_to_fire_event(atom!("play"));
1056
1057 self.notify_about_playing();
1059 }
1060 }
1061
1062 self.update_media_state();
1063 }
1064
1065 fn invoke_resource_selection_algorithm(&self, cx: &mut JSContext) {
1067 self.network_state.set(NetworkState::NoSource);
1069
1070 self.show_poster.set(true);
1072
1073 self.delay_load_event(true, cx);
1076
1077 let task = MediaElementMicrotask::ResourceSelection {
1084 elem: Dom::from_ref(self),
1085 generation_id: self.generation_id.get(),
1086 base_url: self.owner_document().base_url(),
1087 };
1088
1089 ScriptThread::await_stable_state(cx, Box::new(task));
1094 }
1095
1096 fn resource_selection_algorithm_sync(&self, base_url: ServoUrl, cx: &mut JSContext) {
1098 enum Mode {
1105 Object,
1106 Attribute(String),
1107 Children(DomRoot<HTMLSourceElement>),
1108 }
1109
1110 let mode = if self.src_object.borrow().is_some() {
1112 Mode::Object
1114 } else if let Some(src) = self
1115 .upcast::<Element>()
1116 .get_attribute_string_value(&local_name!("src"))
1117 {
1118 Mode::Attribute(src)
1121 } else if let Some(source) = self
1122 .upcast::<Node>()
1123 .children_unrooted(cx.no_gc())
1124 .find_map(UnrootedDom::downcast::<HTMLSourceElement>)
1125 {
1126 Mode::Children(source.as_rooted())
1130 } else {
1131 self.load_state.set(LoadState::NotLoaded);
1134
1135 self.network_state.set(NetworkState::Empty);
1137
1138 self.delay_load_event(false, cx);
1141
1142 return;
1144 };
1145
1146 self.network_state.set(NetworkState::Loading);
1148
1149 self.queue_media_element_task_to_fire_event(atom!("loadstart"));
1152
1153 match mode {
1155 Mode::Object => {
1156 self.load_from_src_object(cx);
1158 },
1159 Mode::Attribute(src) => {
1160 self.load_from_src_attribute(cx, base_url, &src);
1162 },
1163 Mode::Children(source) => {
1164 self.load_from_source_child(cx, &source);
1166 },
1167 }
1168 }
1169
1170 fn load_from_src_object(&self, cx: &JSContext) {
1172 self.load_state.set(LoadState::LoadingFromSrcObject);
1173
1174 "".clone_into(&mut self.current_src.borrow_mut());
1176
1177 self.resource_fetch_algorithm(cx, Resource::Object);
1183 }
1184
1185 fn load_from_src_attribute(&self, cx: &JSContext, base_url: ServoUrl, src: &str) {
1187 self.load_state.set(LoadState::LoadingFromSrcAttribute);
1188
1189 if src.is_empty() {
1192 self.queue_dedicated_media_source_failure_steps();
1193 return;
1194 }
1195
1196 let Ok(url_record) = base_url.join(src) else {
1200 self.queue_dedicated_media_source_failure_steps();
1201 return;
1202 };
1203
1204 *self.current_src.borrow_mut() = url_record.as_str().into();
1207
1208 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1214 }
1215
1216 fn load_from_source_child(&self, cx: &JSContext, source: &HTMLSourceElement) {
1218 self.load_state.set(LoadState::LoadingFromSourceChild);
1219
1220 *self.source_children_pointer.borrow_mut() =
1227 Some(SourceChildrenPointer::new(source, false));
1228
1229 let element = source.upcast::<Element>();
1230
1231 let Some(src) = element
1235 .get_attribute_string_value(&local_name!("src"))
1236 .filter(|value| !value.is_empty())
1237 else {
1238 self.load_from_source_child_failure_steps(cx, source);
1239 return;
1240 };
1241
1242 if let Some(media) = element.get_attribute_string_value(&local_name!("media")) &&
1246 !MediaList::matches_environment(&element.owner_document(), &media)
1247 {
1248 self.load_from_source_child_failure_steps(cx, source);
1249 return;
1250 }
1251
1252 let Ok(url_record) = source.owner_document().base_url().join(&src) else {
1256 self.load_from_source_child_failure_steps(cx, source);
1259 return;
1260 };
1261
1262 if let Some(type_) = element.get_attribute_string_value(&local_name!("type")) &&
1267 ServoMedia::get().can_play_type(&type_) == SupportsMediaType::No
1268 {
1269 self.load_from_source_child_failure_steps(cx, source);
1270 return;
1271 }
1272
1273 self.reset_media_player(cx.no_gc());
1275
1276 self.current_source_child.set(Some(source));
1277
1278 *self.current_src.borrow_mut() = url_record.as_str().into();
1281
1282 self.resource_fetch_algorithm(cx, Resource::Url(url_record));
1287 }
1288
1289 fn load_from_source_child_failure_steps(&self, cx: &JSContext, source: &HTMLSourceElement) {
1291 let trusted_this = Trusted::new(self);
1294 let trusted_source = Trusted::new(source);
1295 let generation_id = self.generation_id.get();
1296
1297 self.owner_global()
1298 .task_manager()
1299 .media_element_task_source()
1300 .queue(task!(queue_error_event: move |cx| {
1301 let this = trusted_this.root();
1302 if generation_id != this.generation_id.get() {
1303 return;
1304 }
1305
1306 let source = trusted_source.root();
1307 source.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1308 }));
1309
1310 let task = MediaElementMicrotask::SelectNextSourceChild {
1312 elem: Dom::from_ref(self),
1313 generation_id: self.generation_id.get(),
1314 };
1315
1316 ScriptThread::await_stable_state(cx, Box::new(task));
1317 }
1318
1319 fn select_next_source_child(&self, cx: &mut JSContext) {
1321 self.AudioTracks(cx).clear();
1323 self.VideoTracks(cx).clear();
1324
1325 let mut source_candidate = None;
1327
1328 if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1336 if source_children_pointer.inclusive {
1340 for next_sibling in source_children_pointer
1341 .source_before_pointer
1342 .upcast::<Node>()
1343 .inclusively_following_siblings()
1344 {
1345 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1346 {
1347 source_candidate = Some(next_source);
1348 break;
1349 }
1350 }
1351 } else {
1352 for next_sibling in source_children_pointer
1353 .source_before_pointer
1354 .upcast::<Node>()
1355 .following_siblings()
1356 {
1357 if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1358 {
1359 source_candidate = Some(next_source);
1360 break;
1361 }
1362 }
1363 };
1364 }
1365
1366 if let Some(source_candidate) = source_candidate {
1369 self.load_from_source_child(cx, &source_candidate);
1370 return;
1371 }
1372
1373 self.load_state.set(LoadState::WaitingForSource);
1374
1375 *self.source_children_pointer.borrow_mut() = None;
1376
1377 self.network_state.set(NetworkState::NoSource);
1380
1381 self.show_poster.set(true);
1383
1384 let this = Trusted::new(self);
1387 let generation_id = self.generation_id.get();
1388
1389 self.owner_global()
1390 .task_manager()
1391 .media_element_task_source()
1392 .queue(task!(queue_delay_load_event: move |cx| {
1393 let this = this.root();
1394 if generation_id != this.generation_id.get() {
1395 return;
1396 }
1397
1398 this.delay_load_event(false, cx);
1399 }));
1400
1401 }
1404
1405 fn resource_selection_algorithm_failure_steps(&self, cx: &JSContext) {
1407 match self.load_state.get() {
1408 LoadState::LoadingFromSrcObject => {
1409 self.queue_dedicated_media_source_failure_steps();
1414 },
1415 LoadState::LoadingFromSrcAttribute => {
1416 self.queue_dedicated_media_source_failure_steps();
1421 },
1422 LoadState::LoadingFromSourceChild => {
1423 if let Some(source) = self.current_source_child.take() {
1426 self.load_from_source_child_failure_steps(cx, &source);
1427 }
1428 },
1429 _ => {},
1430 }
1431 }
1432
1433 fn fetch_request(&self, cx: &JSContext, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1434 if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1435 error!("Missing request url");
1436 if let Some(seek_lock) = seek_lock {
1437 seek_lock.unlock(false);
1438 }
1439 self.resource_selection_algorithm_failure_steps(cx);
1440 return;
1441 }
1442
1443 let document = self.owner_document();
1444 let destination = match self.media_type_id() {
1445 HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1446 HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1447 };
1448 let mut headers = HeaderMap::new();
1449 if let Ok(range_header_value) = RangeHeader::bytes(offset.unwrap_or(0)..) {
1450 headers.typed_insert(range_header_value);
1451 }
1452 let url = match self.resource_url.borrow().as_ref() {
1453 Some(url) => url.clone(),
1454 None => self.blob_url.borrow().as_ref().unwrap().clone(),
1455 };
1456
1457 let cors_setting = cors_setting_for_element(self.upcast());
1458 let global = self.global();
1459 let request = create_a_potential_cors_request(
1460 Some(document.webview_id()),
1461 url.clone(),
1462 destination,
1463 cors_setting,
1464 None,
1465 global.get_referrer(),
1466 )
1467 .with_global_scope(&global)
1468 .headers(headers)
1469 .referrer_policy(referrer_policy_for_element(self.upcast()));
1470
1471 let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1472 if let Some(ref mut current_fetch_context) = *current_fetch_context {
1473 current_fetch_context.cancel(CancelReason::Abort);
1474 }
1475
1476 *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1477 request.id,
1478 global.core_resource_thread(),
1479 ));
1480 let listener =
1481 HTMLMediaElementFetchListener::new(self, request.id, url, offset.unwrap_or(0));
1482
1483 self.owner_document().fetch_background(request, listener);
1484
1485 if let Some(seek_lock) = seek_lock {
1490 seek_lock.unlock(true);
1491 }
1492 }
1493
1494 fn eligible_for_autoplay(&self) -> bool {
1496 self.autoplaying.get() &&
1498
1499 self.Paused() &&
1501
1502 self.Autoplay() &&
1504
1505 {
1508 let document = self.owner_document();
1509
1510 !document.has_active_sandboxing_flag(
1511 SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1512 )
1513 }
1514
1515 }
1518
1519 fn resource_fetch_algorithm(&self, cx: &JSContext, resource: Resource) {
1521 if let Err(e) = self.create_media_player(&resource) {
1522 error!("Create media player error {:?}", e);
1523 self.resource_selection_algorithm_failure_steps(cx);
1524 return;
1525 }
1526
1527 match resource {
1536 Resource::Url(url) => {
1537 if self.Preload() == "none" && !self.autoplaying.get() {
1542 self.network_state.set(NetworkState::Idle);
1544
1545 self.queue_media_element_task_to_fire_event(atom!("suspend"));
1548
1549 let this = Trusted::new(self);
1553 let generation_id = self.generation_id.get();
1554
1555 self.owner_global()
1556 .task_manager()
1557 .media_element_task_source()
1558 .queue(task!(queue_delay_load_event: move |cx| {
1559 let this = this.root();
1560 if generation_id != this.generation_id.get() {
1561 return;
1562 }
1563
1564 this.delay_load_event(false, cx);
1565 }));
1566
1567 return;
1576 }
1577
1578 *self.resource_url.borrow_mut() = Some(url);
1579
1580 self.fetch_request(cx, None, None);
1582 },
1583 Resource::Object => {
1584 if let Some(ref src_object) = *self.src_object.borrow() {
1585 match src_object {
1586 SrcObject::Blob(blob) => {
1587 let blob_url = URL::CreateObjectURL(&self.global(), blob);
1588 *self.blob_url.borrow_mut() =
1589 Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1590 self.fetch_request(cx, None, None);
1591 },
1592 SrcObject::MediaStream(stream) => {
1593 let tracks = &*stream.get_tracks();
1594 for (pos, track) in tracks.iter().enumerate() {
1595 if self
1596 .player
1597 .borrow()
1598 .as_ref()
1599 .unwrap()
1600 .lock()
1601 .unwrap()
1602 .set_stream(&track.id(), pos == tracks.len() - 1)
1603 .is_err()
1604 {
1605 self.resource_selection_algorithm_failure_steps(cx);
1606 }
1607 }
1608 },
1609 }
1610 }
1611 },
1612 }
1613 }
1614
1615 fn queue_dedicated_media_source_failure_steps(&self) {
1619 let this = Trusted::new(self);
1620 let generation_id = self.generation_id.get();
1621 self.take_pending_play_promises(Err(Error::NotSupported(Some(
1622 "Media source is not supported".into(),
1623 ))));
1624 self.owner_global()
1625 .task_manager()
1626 .media_element_task_source()
1627 .queue(task!(dedicated_media_source_failure_steps: move |cx| {
1628 let this = this.root();
1629 if generation_id != this.generation_id.get() {
1630 return;
1631 }
1632
1633 this.fulfill_in_flight_play_promises(cx, |cx| {
1634 this.error.set(Some(&*MediaError::new(
1637 cx,
1638 &this.owner_window(),
1639 MEDIA_ERR_SRC_NOT_SUPPORTED)));
1640
1641 this.AudioTracks(cx).clear();
1643 this.VideoTracks(cx).clear();
1644
1645 this.network_state.set(NetworkState::NoSource);
1648
1649 this.show_poster.set(true);
1651
1652 this.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1654
1655 if let Some(ref player) = *this.player.borrow()
1656 && let Err(error) = player.lock().unwrap().stop() {
1657 error!("Could not stop player: {error:?}");
1658 }
1659
1660 });
1664
1665 this.delay_load_event(false, cx);
1668 }));
1669 }
1670
1671 fn in_error_state(&self) -> bool {
1672 self.error.get().is_some()
1673 }
1674
1675 fn is_potentially_playing(&self) -> bool {
1677 !self.paused.get() &&
1678 !self.ended_playback(LoopCondition::Included) &&
1679 self.error.get().is_none() &&
1680 !self.is_blocked_media_element()
1681 }
1682
1683 fn is_blocked_media_element(&self) -> bool {
1685 self.ready_state.get() <= ReadyState::HaveCurrentData ||
1686 self.is_paused_for_user_interaction() ||
1687 self.is_paused_for_in_band_content()
1688 }
1689
1690 fn is_paused_for_user_interaction(&self) -> bool {
1692 false
1695 }
1696
1697 fn is_paused_for_in_band_content(&self) -> bool {
1699 false
1702 }
1703
1704 fn media_element_load_algorithm(&self, cx: &mut JSContext) {
1706 self.fired_loadeddata_event.set(false);
1709
1710 self.generation_id.set(self.generation_id.get() + 1);
1715
1716 self.load_state.set(LoadState::NotLoaded);
1717 *self.source_children_pointer.borrow_mut() = None;
1718 self.current_source_child.set(None);
1719
1720 while !self.in_flight_play_promises_queue.borrow().is_empty() {
1727 self.fulfill_in_flight_play_promises(cx, |_| ());
1728 }
1729
1730 let network_state = self.network_state.get();
1735
1736 if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1740 self.queue_media_element_task_to_fire_event(atom!("abort"));
1741 }
1742
1743 self.reset_media_player(cx.no_gc());
1745
1746 if network_state != NetworkState::Empty {
1748 self.queue_media_element_task_to_fire_event(atom!("emptied"));
1751
1752 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1755 current_fetch_context.cancel(CancelReason::Abort);
1756 }
1757
1758 self.AudioTracks(cx).clear();
1763 self.VideoTracks(cx).clear();
1764
1765 if self.ready_state.get() != ReadyState::HaveNothing {
1767 self.change_ready_state(ReadyState::HaveNothing);
1768 }
1769
1770 if !self.Paused() {
1772 self.paused.set(true);
1774
1775 self.take_pending_play_promises(Err(Error::Abort(Some(
1778 "Playback interrupted by new resource load".into(),
1779 ))));
1780 self.fulfill_in_flight_play_promises(cx, |_| ());
1781 }
1782
1783 self.seeking.set(false);
1785
1786 self.current_seek_position.set(f64::NAN);
1787
1788 self.current_playback_position.set(0.);
1793 if self.official_playback_position.get() != 0. {
1794 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1795 }
1796 self.official_playback_position.set(0.);
1797
1798 self.duration.set(f64::NAN);
1802 }
1803
1804 self.playback_rate.set(self.default_playback_rate.get());
1806
1807 self.error.set(None);
1809 self.autoplaying.set(true);
1810
1811 self.invoke_resource_selection_algorithm(cx);
1813
1814 }
1816
1817 fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1820 let this = Trusted::new(self);
1821 let generation_id = self.generation_id.get();
1822
1823 self.owner_global()
1824 .task_manager()
1825 .media_element_task_source()
1826 .queue(task!(queue_event: move |cx| {
1827 let this = this.root();
1828 if generation_id != this.generation_id.get() {
1829 return;
1830 }
1831
1832 this.upcast::<EventTarget>().fire_event(cx, name);
1833 }));
1834 }
1835
1836 fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1838 self.pending_play_promises
1839 .borrow_mut()
1840 .push(promise.clone());
1841 }
1842
1843 fn take_pending_play_promises(&self, result: ErrorResult) {
1854 let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1855 self.in_flight_play_promises_queue
1856 .borrow_mut()
1857 .push_back((pending_play_promises.into(), result));
1858 }
1859
1860 fn fulfill_in_flight_play_promises<F>(&self, cx: &mut JSContext, f: F)
1869 where
1870 F: FnOnce(&mut JSContext),
1871 {
1872 let (promises, result) = self
1873 .in_flight_play_promises_queue
1874 .borrow_mut()
1875 .pop_front()
1876 .expect("there should be at least one list of in flight play promises");
1877 f(cx);
1878 for promise in &*promises {
1879 match result {
1880 Ok(ref value) => promise.resolve_native(cx, value),
1881 Err(ref error) => promise.reject_error(cx, error.clone()),
1882 }
1883 }
1884 }
1885
1886 pub(crate) fn handle_source_child_insertion(
1887 &self,
1888 source: &HTMLSourceElement,
1889 cx: &mut JSContext,
1890 ) {
1891 if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1895 return;
1896 }
1897
1898 if self.network_state.get() == NetworkState::Empty {
1899 self.invoke_resource_selection_algorithm(cx);
1900 return;
1901 }
1902
1903 if self.load_state.get() != LoadState::WaitingForSource {
1907 return;
1908 }
1909
1910 self.load_state.set(LoadState::LoadingFromSourceChild);
1911
1912 *self.source_children_pointer.borrow_mut() = Some(SourceChildrenPointer::new(source, true));
1913
1914 let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1916 elem: Dom::from_ref(self),
1917 generation_id: self.generation_id.get(),
1918 };
1919
1920 ScriptThread::await_stable_state(cx, Box::new(task));
1921 }
1922
1923 fn select_next_source_child_after_wait(&self, cx: &mut JSContext) {
1925 self.delay_load_event(true, cx);
1928
1929 self.network_state.set(NetworkState::Loading);
1931
1932 self.select_next_source_child(cx);
1934 }
1935
1936 fn media_data_processing_failure_steps(&self, cx: &JSContext) {
1941 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1943 current_fetch_context.cancel(CancelReason::Error);
1944 }
1945
1946 self.resource_selection_algorithm_failure_steps(cx);
1948 }
1949
1950 fn media_data_processing_fatal_steps(&self, error: u16, cx: &mut JSContext) {
1954 *self.source_children_pointer.borrow_mut() = None;
1955 self.current_source_child.set(None);
1956
1957 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1959 current_fetch_context.cancel(CancelReason::Error);
1960 }
1961
1962 self.error
1965 .set(Some(&*MediaError::new(cx, &self.owner_window(), error)));
1966
1967 self.network_state.set(NetworkState::Idle);
1969
1970 self.delay_load_event(false, cx);
1973
1974 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1976
1977 }
1979
1980 fn seek(&self, time: f64, _approximate_for_speed: bool) {
1982 self.show_poster.set(false);
1984
1985 if self.ready_state.get() == ReadyState::HaveNothing {
1987 return;
1988 }
1989
1990 self.current_seek_position.set(f64::NAN);
1994
1995 self.seeking.set(true);
1997
1998 let time = f64::min(time, self.Duration());
2004
2005 let time = f64::max(time, self.earliest_possible_position());
2008
2009 let seekable = self.seekable();
2015
2016 if seekable.is_empty() {
2017 self.seeking.set(false);
2018 return;
2019 }
2020
2021 let mut nearest_seekable_position = 0.0;
2022 let mut in_seekable_range = false;
2023 let mut nearest_seekable_distance = f64::MAX;
2024 for i in 0..seekable.len() {
2025 let start = seekable.start(i).unwrap().abs();
2026 let end = seekable.end(i).unwrap().abs();
2027 if time >= start && time <= end {
2028 nearest_seekable_position = time;
2029 in_seekable_range = true;
2030 break;
2031 } else if time < start {
2032 let distance = start - time;
2033 if distance < nearest_seekable_distance {
2034 nearest_seekable_distance = distance;
2035 nearest_seekable_position = start;
2036 }
2037 } else {
2038 let distance = time - end;
2039 if distance < nearest_seekable_distance {
2040 nearest_seekable_distance = distance;
2041 nearest_seekable_position = end;
2042 }
2043 }
2044 }
2045 let time = if in_seekable_range {
2046 time
2047 } else {
2048 nearest_seekable_position
2049 };
2050
2051 self.queue_media_element_task_to_fire_event(atom!("seeking"));
2062
2063 self.current_playback_position.set(time);
2065
2066 if let Some(ref player) = *self.player.borrow() &&
2067 let Err(error) = player.lock().unwrap().seek(time)
2068 {
2069 error!("Could not seek player: {error:?}");
2070 }
2071
2072 self.current_seek_position.set(time);
2073
2074 }
2080
2081 fn seek_end(&self) {
2083 self.official_playback_position
2086 .set(self.current_playback_position.get());
2087
2088 self.seeking.set(false);
2090
2091 self.current_seek_position.set(f64::NAN);
2092
2093 self.time_marches_on();
2095
2096 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2099
2100 self.queue_media_element_task_to_fire_event(atom!("seeked"));
2103 }
2104
2105 pub(crate) fn set_poster_frame(&self, no_gc: &NoGC, image: Option<Arc<RasterImage>>) {
2107 if pref!(media_testing_enabled) && image.is_some() {
2108 self.queue_media_element_task_to_fire_event(atom!("postershown"));
2109 }
2110
2111 self.video_renderer.lock().unwrap().set_poster_frame(image);
2112
2113 self.upcast::<Node>().dirty(no_gc, NodeDamage::Other);
2114 }
2115
2116 fn player_id(&self) -> Option<usize> {
2117 self.player
2118 .borrow()
2119 .as_ref()
2120 .map(|player| player.lock().unwrap().get_id())
2121 }
2122
2123 fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
2124 let stream_type = match *resource {
2125 Resource::Object => {
2126 if let Some(ref src_object) = *self.src_object.borrow() {
2127 match src_object {
2128 SrcObject::MediaStream(_) => StreamType::Stream,
2129 _ => StreamType::Seekable,
2130 }
2131 } else {
2132 return Err(());
2133 }
2134 },
2135 _ => StreamType::Seekable,
2136 };
2137
2138 let window = self.owner_window();
2139
2140 let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
2141 {
2142 HTMLMediaElementTypeId::HTMLAudioElement => None,
2143 HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
2144 };
2145
2146 let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
2147
2148 let pipeline_id = window.pipeline_id();
2149 let client_context_id =
2150 ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
2151
2152 let shared_player_id = Arc::new(OnceLock::new());
2155
2156 let event_handler = Arc::new(Mutex::new(HTMLMediaElementEventHandler::new(self)));
2157 let weak_event_handler = Arc::downgrade(&event_handler);
2158 *self.event_handler.borrow_mut() = Some(event_handler);
2159
2160 let task_source = self
2161 .owner_global()
2162 .task_manager()
2163 .media_element_task_source()
2164 .to_sendable();
2165
2166 let shared_player_id_clone = shared_player_id.clone();
2167 let action_callback = generic_channel::GenericCallback::new(move |message| {
2168 let event = message.unwrap();
2169 let weak_event_handler = weak_event_handler.clone();
2170
2171 let shared_player_id_clone = shared_player_id_clone.clone();
2172 task_source.queue(task!(handle_player_event: move |cx| {
2173 trace!("HTMLMediaElement event: {event:?}");
2174
2175 let Some(event_handler) = weak_event_handler.upgrade() else {
2176 return;
2177 };
2178
2179 if let Some(shared_player_id) = shared_player_id_clone.get() {
2180 event_handler.lock().unwrap().handle_player_event(*shared_player_id, event, cx);
2181 } else {
2182 error!("Player Action without ID being assigned yet.");
2183 }
2184 }));
2185 })
2186 .unwrap();
2187 let player = ServoMedia::get().create_player(
2188 &client_context_id,
2189 stream_type,
2190 action_callback,
2191 video_renderer,
2192 audio_renderer,
2193 Box::new(window.get_player_context()),
2194 );
2195
2196 let player_id = {
2197 let player_guard = player.lock().unwrap();
2198
2199 if let Err(error) = player_guard.set_mute(self.muted.get()) {
2200 warn!("Could not set mute state: {error:?}");
2201 }
2202
2203 let id = player_guard.get_id();
2204 if shared_player_id.set(id).is_err() {
2205 error!("Error setting player id. Already set?");
2206 }
2207 id
2208 };
2209
2210 *self.player.borrow_mut() = Some(player);
2211
2212 let task_source = self
2213 .owner_global()
2214 .task_manager()
2215 .media_element_task_source()
2216 .to_sendable();
2217 let weak_video_renderer = Arc::downgrade(&self.video_renderer);
2218
2219 self.video_renderer
2220 .lock()
2221 .unwrap()
2222 .setup(player_id, task_source, weak_video_renderer);
2223
2224 Ok(())
2225 }
2226
2227 fn reset_media_player(&self, no_gc: &NoGC) {
2228 if self.player.borrow().is_none() {
2229 return;
2230 }
2231
2232 if let Some(ref player) = *self.player.borrow() &&
2233 let Err(error) = player.lock().unwrap().stop()
2234 {
2235 error!("Could not stop player: {error:?}");
2236 }
2237
2238 *self.player.borrow_mut() = None;
2239 self.video_renderer.lock().unwrap().reset();
2240 *self.event_handler.borrow_mut() = None;
2241
2242 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2243 video_element.set_natural_dimensions(no_gc, None, None);
2244 }
2245 }
2246
2247 pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
2248 if let Some(ref player) = *self.player.borrow() &&
2249 let Err(error) = player.lock().unwrap().set_audio_track(idx as i32, enabled)
2250 {
2251 warn!("Could not set audio track {error:?}");
2252 }
2253 }
2254
2255 pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
2256 if let Some(ref player) = *self.player.borrow() &&
2257 let Err(error) = player.lock().unwrap().set_video_track(idx as i32, enabled)
2258 {
2259 warn!("Could not set video track: {error:?}");
2260 }
2261 }
2262
2263 fn direction_of_playback(&self) -> PlaybackDirection {
2265 if self.playback_rate.get() >= 0. {
2268 PlaybackDirection::Forwards
2269 } else {
2270 PlaybackDirection::Backwards
2271 }
2272 }
2273
2274 fn ended_playback(&self, loop_condition: LoopCondition) -> bool {
2276 if self.ready_state.get() < ReadyState::HaveMetadata {
2280 return false;
2281 }
2282
2283 let playback_position = self.current_playback_position.get();
2284
2285 match self.direction_of_playback() {
2286 PlaybackDirection::Forwards => {
2290 playback_position >= self.Duration() &&
2291 (loop_condition == LoopCondition::Ignored || !self.Loop())
2292 },
2293 PlaybackDirection::Backwards => playback_position <= self.earliest_possible_position(),
2296 }
2297 }
2298
2299 fn end_of_playback_in_forwards_direction(&self) {
2301 if self.Loop() {
2307 self.seek(
2308 self.earliest_possible_position(),
2309 false,
2310 );
2311 return;
2312 }
2313
2314 let this = Trusted::new(self);
2319 let generation_id = self.generation_id.get();
2320
2321 self.owner_global()
2322 .task_manager()
2323 .media_element_task_source()
2324 .queue(task!(reaches_the_end_steps: move |cx| {
2325 let this = this.root();
2326 if generation_id != this.generation_id.get() {
2327 return;
2328 }
2329
2330 this.upcast::<EventTarget>().fire_event(cx, atom!("timeupdate"));
2332
2333 if this.ended_playback(LoopCondition::Included) &&
2336 this.direction_of_playback() == PlaybackDirection::Forwards &&
2337 !this.Paused() {
2338 this.paused.set(true);
2340
2341 this.upcast::<EventTarget>().fire_event(cx, atom!("pause"));
2343
2344 this.take_pending_play_promises(Err(Error::Abort(Some("Media playback finished".into()))));
2347 this.fulfill_in_flight_play_promises(cx, |_| ());
2348 }
2349
2350 this.upcast::<EventTarget>().fire_event(cx, atom!("ended"));
2352 }));
2353
2354 self.change_ready_state(ReadyState::HaveCurrentData);
2356 }
2357
2358 fn end_of_playback_in_backwards_direction(&self) {
2360 if self.current_playback_position.get() <= self.earliest_possible_position() {
2365 self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
2366 }
2367 }
2368
2369 fn playback_end(&self) {
2370 if self.seeking.get() {
2372 return;
2373 }
2374
2375 match self.direction_of_playback() {
2376 PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(),
2377 PlaybackDirection::Backwards => self.end_of_playback_in_backwards_direction(),
2378 }
2379 }
2380
2381 fn playback_error(&self, error: &str, cx: &mut JSContext) {
2382 error!("Player error: {:?}", error);
2383
2384 if self.in_error_state() {
2388 return;
2389 }
2390
2391 if self.ready_state.get() == ReadyState::HaveNothing {
2393 self.media_data_processing_failure_steps(cx);
2396 } else {
2397 self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, cx);
2399 }
2400 }
2401
2402 fn playback_metadata_updated(
2403 &self,
2404 cx: &mut JSContext,
2405 metadata: &servo_media::player::metadata::Metadata,
2406 ) {
2407 if self.ready_state.get() != ReadyState::HaveNothing {
2410 return;
2411 }
2412
2413 for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2416 let audio_track_list = self.AudioTracks(cx);
2417
2418 let kind = match i {
2420 0 => DOMString::from_static("main"),
2421 _ => DOMString::new(),
2422 };
2423
2424 let audio_track = AudioTrack::new(
2425 cx,
2426 self.global().as_window(),
2427 DOMString::new(),
2428 kind,
2429 DOMString::new(),
2430 DOMString::new(),
2431 Some(&*audio_track_list),
2432 );
2433
2434 audio_track_list.add(&audio_track);
2437
2438 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2445 let fragment = MediaFragmentParser::from(servo_url);
2446 if let Some(id) = fragment.id() &&
2447 audio_track.id() == id
2448 {
2449 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2450 }
2451
2452 if fragment.tracks().contains(&audio_track.kind().into()) {
2453 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2454 }
2455 }
2456
2457 if audio_track_list.enabled_index().is_none() {
2462 audio_track_list.set_enabled(audio_track_list.len() - 1, true);
2463 }
2464
2465 let event = TrackEvent::new(
2468 cx,
2469 self.global().as_window(),
2470 atom!("addtrack"),
2471 false,
2472 false,
2473 &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2474 );
2475
2476 event
2477 .upcast::<Event>()
2478 .fire(cx, audio_track_list.upcast::<EventTarget>());
2479 }
2480
2481 for (i, _track) in metadata.video_tracks.iter().enumerate() {
2483 let video_track_list = self.VideoTracks(cx);
2484
2485 let kind = match i {
2487 0 => DOMString::from_static("main"),
2488 _ => DOMString::new(),
2489 };
2490
2491 let video_track = VideoTrack::new(
2492 cx,
2493 self.global().as_window(),
2494 DOMString::new(),
2495 kind,
2496 DOMString::new(),
2497 DOMString::new(),
2498 Some(&*video_track_list),
2499 );
2500
2501 video_track_list.add(&video_track);
2504
2505 if let Some(track) = video_track_list.item(0) &&
2512 let Some(servo_url) = self.resource_url.borrow().as_ref()
2513 {
2514 let fragment = MediaFragmentParser::from(servo_url);
2515 if let Some(id) = fragment.id() {
2516 if track.id() == id {
2517 video_track_list.set_selected(0, true);
2518 }
2519 } else if fragment.tracks().contains(&track.kind().into()) {
2520 video_track_list.set_selected(0, true);
2521 }
2522 }
2523
2524 if video_track_list.selected_index().is_none() {
2530 video_track_list.set_selected(video_track_list.len() - 1, true);
2531 }
2532
2533 let event = TrackEvent::new(
2536 cx,
2537 self.global().as_window(),
2538 atom!("addtrack"),
2539 false,
2540 false,
2541 &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2542 );
2543
2544 event
2545 .upcast::<Event>()
2546 .fire(cx, video_track_list.upcast::<EventTarget>());
2547 }
2548
2549 let earliest_possible_position = self.earliest_possible_position();
2562 self.current_playback_position
2563 .set(earliest_possible_position);
2564 self.official_playback_position
2565 .set(earliest_possible_position);
2566
2567 self.duration.set(
2573 metadata
2574 .duration
2575 .map_or(f64::INFINITY, |duration| duration.as_secs_f64()),
2576 );
2577 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2578
2579 if let Some(video_element) = self.downcast::<HTMLVideoElement>() {
2583 video_element.set_natural_dimensions(
2584 cx.no_gc(),
2585 Some(metadata.width),
2586 Some(metadata.height),
2587 );
2588 self.queue_media_element_task_to_fire_event(atom!("resize"));
2589 }
2590
2591 self.change_ready_state(ReadyState::HaveMetadata);
2593
2594 let mut jumped = false;
2596
2597 if self.default_playback_start_position.get() > 0. {
2600 self.seek(
2601 self.default_playback_start_position.get(),
2602 false,
2603 );
2604 jumped = true;
2605 }
2606
2607 self.default_playback_start_position.set(0.);
2609
2610 if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2615 let fragment = MediaFragmentParser::from(servo_url);
2616 if let Some(initial_playback_position) = fragment.start() &&
2617 initial_playback_position > 0. &&
2618 initial_playback_position < self.duration.get() &&
2619 !jumped
2620 {
2621 self.seek(
2622 initial_playback_position,
2623 false,
2624 )
2625 }
2626 }
2627
2628 let global = self.global();
2635 let window = global.as_window();
2636
2637 window.Navigator(cx).MediaSession(cx).update_title(
2639 metadata
2640 .title
2641 .clone()
2642 .unwrap_or(window.get_url().into_string()),
2643 );
2644 }
2645
2646 fn playback_duration_changed(&self, duration: Option<Duration>) {
2647 let duration = duration.map_or(f64::INFINITY, |duration| duration.as_secs_f64());
2648
2649 if self.duration.get() == duration {
2650 return;
2651 }
2652
2653 self.duration.set(duration);
2654
2655 self.queue_media_element_task_to_fire_event(atom!("durationchange"));
2661
2662 if self.current_playback_position.get() > duration {
2666 self.seek(duration, false);
2667 }
2668 }
2669
2670 fn playback_video_frame_updated(&self, no_gc: &NoGC) {
2671 let Some(video_element) = self.downcast::<HTMLVideoElement>() else {
2672 return;
2673 };
2674
2675 if self.ready_state.get() == ReadyState::HaveNothing {
2684 return;
2685 }
2686
2687 if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2688 if video_element.set_natural_dimensions(
2689 no_gc,
2690 Some(frame.width as u32),
2691 Some(frame.height as u32),
2692 ) {
2693 self.queue_media_element_task_to_fire_event(atom!("resize"));
2694 } else {
2695 self.upcast::<Node>().dirty(no_gc, NodeDamage::Other);
2698 }
2699 }
2700 }
2701
2702 fn playback_need_data(&self) {
2703 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() &&
2707 let Some(reason) = current_fetch_context.cancel_reason()
2708 {
2709 if *reason == CancelReason::Backoff {
2715 self.seek(
2716 self.current_playback_position.get(),
2717 false,
2718 );
2719 }
2720 return;
2721 }
2722
2723 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2724 let Err(e) = {
2725 let mut data_source = current_fetch_context.data_source().borrow_mut();
2726 data_source.set_locked(false);
2727 data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2728 }
2729 {
2730 if e == PlayerError::EnoughData {
2735 current_fetch_context.cancel(CancelReason::Backoff);
2736 }
2737 }
2738 }
2739
2740 fn playback_enough_data(&self) {
2741 if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() &&
2746 current_fetch_context.is_seekable()
2747 {
2748 current_fetch_context.cancel(CancelReason::Backoff);
2749 }
2750 }
2751
2752 fn playback_position_changed(&self, cx: &mut JSContext, position: f64) {
2753 if self.seeking.get() {
2755 return;
2756 }
2757
2758 let _ = self
2759 .played
2760 .borrow_mut()
2761 .add(self.current_playback_position.get(), position);
2762 self.current_playback_position.set(position);
2763 self.official_playback_position.set(position);
2764 self.time_marches_on();
2765
2766 let media_position_state =
2767 MediaPositionState::new(self.duration.get(), self.playback_rate.get(), position);
2768 debug!(
2769 "Sending media session event set position state {:?}",
2770 media_position_state
2771 );
2772 self.send_media_session_event(
2773 cx,
2774 MediaSessionEvent::SetPositionState(media_position_state),
2775 );
2776 }
2777
2778 fn playback_seek_done(&self, cx: &JSContext, position: f64) {
2779 let delta = (position - self.current_seek_position.get()).abs();
2782 if !self.seeking.get() || delta > SEEK_POSITION_THRESHOLD {
2783 return;
2784 }
2785
2786 let task = MediaElementMicrotask::Seeked {
2789 elem: Dom::from_ref(self),
2790 generation_id: self.generation_id.get(),
2791 };
2792
2793 ScriptThread::await_stable_state(cx, Box::new(task));
2794 }
2795
2796 fn playback_state_changed(&self, cx: &mut JSContext, state: &PlaybackState) {
2797 let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2798 match *state {
2799 PlaybackState::Paused => {
2800 media_session_playback_state = MediaSessionPlaybackState::Paused;
2801 if self.ready_state.get() == ReadyState::HaveMetadata {
2802 self.change_ready_state(ReadyState::HaveEnoughData);
2803 }
2804 },
2805 PlaybackState::Playing => {
2806 media_session_playback_state = MediaSessionPlaybackState::Playing;
2807 if self.ready_state.get() == ReadyState::HaveMetadata {
2808 self.change_ready_state(ReadyState::HaveEnoughData);
2809 }
2810 },
2811 PlaybackState::Buffering => {
2812 return;
2816 },
2817 _ => {},
2818 };
2819 debug!(
2820 "Sending media session event playback state changed to {:?}",
2821 media_session_playback_state
2822 );
2823 self.send_media_session_event(
2824 cx,
2825 MediaSessionEvent::PlaybackStateChange(media_session_playback_state),
2826 );
2827 }
2828
2829 fn seekable(&self) -> TimeRangesContainer {
2830 let mut seekable = TimeRangesContainer::default();
2831 if let Some(ref player) = *self.player.borrow() {
2832 let ranges = player.lock().unwrap().seekable();
2833 for range in ranges {
2834 let _ = seekable.add(range.start, range.end);
2835 }
2836 }
2837 seekable
2838 }
2839
2840 fn earliest_possible_position(&self) -> f64 {
2842 self.seekable()
2843 .start(0)
2844 .unwrap_or_else(|_| self.current_playback_position.get())
2845 }
2846
2847 fn render_controls(&self, cx: &mut JSContext) {
2848 if self.upcast::<Element>().is_shadow_host() {
2849 return;
2851 }
2852
2853 let shadow_root = self.upcast::<Element>().attach_ua_shadow_root(cx, false);
2856 let document = self.owner_document();
2857 let script = Element::create(
2858 cx,
2859 QualName::new(None, ns!(html), local_name!("script")),
2860 None,
2861 &document,
2862 ElementCreator::ScriptCreated,
2863 CustomElementCreationMode::Asynchronous,
2864 None,
2865 );
2866 let id = Uuid::new_v4().to_string();
2872 document.register_media_controls(&id, &shadow_root);
2873 let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2874 *self.media_controls_id.borrow_mut() = Some(id);
2875 script
2876 .upcast::<Node>()
2877 .set_text_content_for_element(cx, Some(DOMString::from(media_controls_script)));
2878 if let Err(e) = shadow_root
2879 .upcast::<Node>()
2880 .AppendChild(cx, script.upcast::<Node>())
2881 {
2882 warn!("Could not render media controls {:?}", e);
2883 return;
2884 }
2885
2886 let style = Element::create(
2887 cx,
2888 QualName::new(None, ns!(html), local_name!("style")),
2889 None,
2890 &document,
2891 ElementCreator::ScriptCreated,
2892 CustomElementCreationMode::Asynchronous,
2893 None,
2894 );
2895
2896 style
2897 .upcast::<Node>()
2898 .set_text_content_for_element(cx, Some(DOMString::from(MEDIA_CONTROL_CSS)));
2899
2900 if let Err(e) = shadow_root
2901 .upcast::<Node>()
2902 .AppendChild(cx, style.upcast::<Node>())
2903 {
2904 warn!("Could not render media controls {:?}", e);
2905 }
2906
2907 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
2908 }
2909
2910 fn remove_controls(&self) {
2911 if let Some(id) = self.media_controls_id.borrow_mut().take() {
2912 self.owner_document().unregister_media_controls(&id);
2913 }
2914 }
2915
2916 pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2918 self.video_renderer
2919 .lock()
2920 .unwrap()
2921 .current_frame_holder
2922 .as_ref()
2923 .map(|holder| holder.get_frame())
2924 }
2925
2926 pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2929 let (current_frame, poster_frame) = {
2930 let renderer = self.video_renderer.lock().unwrap();
2931 (renderer.current_frame, renderer.poster_frame)
2932 };
2933
2934 if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2937 return poster_frame;
2938 }
2939
2940 current_frame
2941 }
2942
2943 pub(crate) fn set_audio_renderer(
2948 &self,
2949 audio_renderer: Option<Arc<Mutex<dyn AudioRenderer>>>,
2950 cx: &mut JSContext,
2951 ) {
2952 *self.audio_renderer.borrow_mut() = audio_renderer;
2953
2954 let had_player = {
2955 if let Some(ref player) = *self.player.borrow() {
2956 if let Err(error) = player.lock().unwrap().stop() {
2957 error!("Could not stop player: {error:?}");
2958 }
2959 true
2960 } else {
2961 false
2962 }
2963 };
2964
2965 if had_player {
2966 self.media_element_load_algorithm(cx);
2967 }
2968 }
2969
2970 fn send_media_session_event(&self, cx: &mut JSContext, event: MediaSessionEvent) {
2971 let global = self.global();
2972 let media_session = global.as_window().Navigator(cx).MediaSession(cx);
2973
2974 media_session.register_media_instance(self);
2975
2976 media_session.send_event(event);
2977 }
2978
2979 pub(crate) fn origin_is_clean(&self) -> bool {
2981 if self.src_object.borrow().is_some() {
2983 return true;
2986 }
2987
2988 if self.resource_url.borrow().is_some() {
2990 if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2994 return current_fetch_context.origin_is_clean();
2995 }
2996 }
2997
2998 true
2999 }
3000
3001 pub(crate) fn was_added_to_list_of_text_tracks(&self) {
3002 let this = Trusted::new(self);
3007 self.global()
3008 .task_manager()
3009 .media_element_task_source()
3010 .queue(task!(track_event_queue: move |cx| {
3011 let element = this.root();
3012 if element.did_perform_automatic_track_selection.get() {
3016 return;
3017 }
3018 element.honor_user_preferences_for_automatic_text_track_selection(cx);
3020 }));
3021 }
3022
3023 fn honor_user_preferences_for_automatic_text_track_selection(&self, cx: &mut JSContext) {
3025 self.perform_automatic_text_track_selection(
3027 cx,
3028 vec![TextTrackKind::Subtitles, TextTrackKind::Captions],
3029 );
3030 self.perform_automatic_text_track_selection(cx, vec![TextTrackKind::Descriptions]);
3032 self.did_perform_automatic_track_selection.set(true);
3039 }
3040
3041 fn perform_automatic_text_track_selection(
3043 &self,
3044 cx: &mut JSContext,
3045 text_track_kinds: Vec<TextTrackKind>,
3046 ) {
3047 let Some(text_tracks_list) = self.text_tracks_list.get() else {
3051 return;
3052 };
3053 let candidates = text_tracks_list.tracks_for_kinds(text_track_kinds);
3054 if candidates.is_empty() {
3056 return;
3057 }
3058 if candidates
3060 .iter()
3061 .any(|candidate| candidate.Mode() == TextTrackMode::Showing)
3062 {
3063 return;
3064 }
3065 if let Some(default_candidate) = candidates.iter().find(|candidate| {
3072 candidate.associated_track().is_some_and(|track| {
3073 track
3074 .upcast::<Element>()
3075 .has_attribute(&local_name!("default"))
3076 }) && candidate.Mode() == TextTrackMode::Disabled
3077 }) {
3078 default_candidate.set_text_track_mode(cx, TextTrackMode::Showing);
3079 }
3080 }
3081}
3082
3083impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
3084 fn NetworkState(&self) -> u16 {
3086 self.network_state.get() as u16
3087 }
3088
3089 fn ReadyState(&self) -> u16 {
3091 self.ready_state.get() as u16
3092 }
3093
3094 make_bool_getter!(Autoplay, "autoplay");
3096 make_bool_setter!(SetAutoplay, "autoplay");
3098
3099 make_bool_getter!(Loop, "loop");
3101 make_bool_setter!(SetLoop, "loop");
3103
3104 make_bool_getter!(DefaultMuted, "muted");
3106 make_bool_setter!(SetDefaultMuted, "muted");
3108
3109 make_bool_getter!(Controls, "controls");
3111 make_bool_setter!(SetControls, "controls");
3113
3114 make_url_getter!(Src, "src");
3116
3117 make_url_setter!(SetSrc, "src");
3119
3120 fn GetCrossOrigin(&self) -> Option<DOMString> {
3122 reflect_cross_origin_attribute(self.upcast::<Element>())
3123 }
3124 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
3126 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
3127 }
3128
3129 fn Muted(&self) -> bool {
3131 self.muted.get()
3132 }
3133
3134 fn SetMuted(&self, _cx: &mut JSContext, value: bool) {
3136 if self.muted.get() == value {
3137 return;
3138 }
3139
3140 self.muted.set(value);
3141
3142 if let Some(ref player) = *self.player.borrow() &&
3143 let Err(error) = player.lock().unwrap().set_mute(value)
3144 {
3145 warn!("Could not set mute state: {error:?}");
3146 }
3147
3148 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3151
3152 if !self.is_allowed_to_play() {
3155 self.internal_pause_steps();
3156 }
3157 }
3158
3159 fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
3161 (*self.src_object.borrow())
3162 .as_ref()
3163 .map(|src_object| match src_object {
3164 SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
3165 SrcObject::MediaStream(stream) => {
3166 MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
3167 },
3168 })
3169 }
3170
3171 fn SetSrcObject(&self, cx: &mut JSContext, value: Option<MediaStreamOrBlob>) {
3173 *self.src_object.borrow_mut() = value.map(|value| value.into());
3174 self.media_element_load_algorithm(cx);
3175 }
3176
3177 make_enumerated_getter!(
3180 Preload,
3181 "preload",
3182 "none" | "metadata" | "auto",
3183 missing => "auto",
3184 invalid => "auto"
3185 );
3186
3187 make_setter!(SetPreload, "preload");
3189
3190 fn CurrentSrc(&self) -> USVString {
3192 USVString(self.current_src.borrow().clone())
3193 }
3194
3195 fn Load(&self, cx: &mut JSContext) {
3197 self.media_element_load_algorithm(cx);
3198 }
3199
3200 fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
3202 match ServoMedia::get().can_play_type(&type_.str()) {
3203 SupportsMediaType::No => CanPlayTypeResult::_empty,
3204 SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
3205 SupportsMediaType::Probably => CanPlayTypeResult::Probably,
3206 }
3207 }
3208
3209 fn GetError(&self) -> Option<DomRoot<MediaError>> {
3211 self.error.get()
3212 }
3213
3214 fn Play(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
3216 let promise = Promise::new_in_realm(cx);
3217
3218 if self
3225 .error
3226 .get()
3227 .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
3228 {
3229 promise.reject_error(
3230 cx,
3231 Error::NotSupported(Some("Media element source not supported".into())),
3232 );
3233 return promise;
3234 }
3235
3236 self.push_pending_play_promise(&promise);
3239
3240 self.internal_play_steps(cx);
3242
3243 promise
3245 }
3246
3247 fn Pause(&self, cx: &mut JSContext) {
3249 if self.network_state.get() == NetworkState::Empty {
3252 self.invoke_resource_selection_algorithm(cx);
3253 }
3254
3255 self.internal_pause_steps();
3257 }
3258
3259 fn Paused(&self) -> bool {
3261 self.paused.get()
3262 }
3263
3264 fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
3266 Ok(Finite::wrap(self.default_playback_rate.get()))
3267 }
3268
3269 fn SetDefaultPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3271 let min_allowed = -64.0;
3274 let max_allowed = 64.0;
3275 if *value < min_allowed || *value > max_allowed {
3276 return Err(Error::NotSupported(Some(
3277 "Playback rate value must be between -64.0 and 64.0".into(),
3278 )));
3279 }
3280
3281 if self.default_playback_rate.get() == *value {
3282 return Ok(());
3283 }
3284
3285 self.default_playback_rate.set(*value);
3286
3287 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3290
3291 Ok(())
3292 }
3293
3294 fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
3296 Ok(Finite::wrap(self.playback_rate.get()))
3297 }
3298
3299 fn SetPlaybackRate(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3301 let min_allowed = -64.0;
3306 let max_allowed = 64.0;
3307 if *value < min_allowed || *value > max_allowed {
3308 return Err(Error::NotSupported(Some(
3309 "Playback rate value must be between -64.0 and 64.0".into(),
3310 )));
3311 }
3312
3313 if self.playback_rate.get() == *value {
3314 return Ok(());
3315 }
3316
3317 self.playback_rate.set(*value);
3320
3321 if self.is_potentially_playing() &&
3322 let Some(ref player) = *self.player.borrow() &&
3323 let Err(error) = player.lock().unwrap().set_playback_rate(*value)
3324 {
3325 warn!("Could not set the playback rate: {error:?}");
3326 }
3327
3328 self.queue_media_element_task_to_fire_event(atom!("ratechange"));
3331
3332 Ok(())
3333 }
3334
3335 fn Duration(&self) -> f64 {
3337 self.duration.get()
3338 }
3339
3340 fn CurrentTime(&self) -> Finite<f64> {
3342 Finite::wrap(if self.default_playback_start_position.get() != 0. {
3343 self.default_playback_start_position.get()
3344 } else if self.seeking.get() {
3345 self.current_seek_position.get()
3350 } else {
3351 self.official_playback_position.get()
3352 })
3353 }
3354
3355 fn SetCurrentTime(&self, _cx: &mut JSContext, time: Finite<f64>) {
3357 if self.ready_state.get() == ReadyState::HaveNothing {
3358 self.default_playback_start_position.set(*time);
3359 } else {
3360 self.official_playback_position.set(*time);
3361 self.seek(*time, false);
3362 }
3363 }
3364
3365 fn Seeking(&self) -> bool {
3367 self.seeking.get()
3368 }
3369
3370 fn Ended(&self) -> bool {
3372 self.ended_playback(LoopCondition::Included) &&
3373 self.direction_of_playback() == PlaybackDirection::Forwards
3374 }
3375
3376 fn FastSeek(&self, time: Finite<f64>) {
3378 self.seek(*time, true);
3379 }
3380
3381 fn Played(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3383 TimeRanges::new(cx, self.global().as_window(), self.played.borrow().clone())
3384 }
3385
3386 fn Seekable(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3388 TimeRanges::new(cx, self.global().as_window(), self.seekable())
3389 }
3390
3391 fn Buffered(&self, cx: &mut JSContext) -> DomRoot<TimeRanges> {
3393 let mut buffered = TimeRangesContainer::default();
3394 if let Some(ref player) = *self.player.borrow() {
3395 let ranges = player.lock().unwrap().buffered();
3396 for range in ranges {
3397 let _ = buffered.add(range.start, range.end);
3398 }
3399 }
3400 TimeRanges::new(cx, self.global().as_window(), buffered)
3401 }
3402
3403 fn AudioTracks(&self, cx: &mut JSContext) -> DomRoot<AudioTrackList> {
3405 let window = self.owner_window();
3406 self.audio_tracks_list
3407 .or_init(|| AudioTrackList::new(cx, &window, &[], Some(self)))
3408 }
3409
3410 fn VideoTracks(&self, cx: &mut JSContext) -> DomRoot<VideoTrackList> {
3412 let window = self.owner_window();
3413 self.video_tracks_list
3414 .or_init(|| VideoTrackList::new(cx, &window, &[], Some(self)))
3415 }
3416
3417 fn TextTracks(&self, cx: &mut JSContext) -> DomRoot<TextTrackList> {
3419 let window = self.owner_window();
3424 self.text_tracks_list
3425 .or_init(|| TextTrackList::new(cx, &window, &[]))
3426 }
3427
3428 fn AddTextTrack(
3430 &self,
3431 cx: &mut JSContext,
3432 kind: TextTrackKind,
3433 label: DOMString,
3434 language: DOMString,
3435 ) -> DomRoot<TextTrack> {
3436 let window = self.owner_window();
3437 let track = TextTrack::new(
3444 cx,
3445 &window,
3446 "".into(),
3447 kind,
3448 label,
3449 language,
3450 TextTrackMode::Hidden,
3451 None,
3452 );
3453 self.TextTracks(cx).add(self, &track);
3459 DomRoot::from_ref(&track)
3461 }
3462
3463 fn GetVolume(&self) -> Fallible<Finite<f64>> {
3465 Ok(Finite::wrap(self.volume.get()))
3466 }
3467
3468 fn SetVolume(&self, _cx: &mut JSContext, value: Finite<f64>) -> ErrorResult {
3470 let minimum_volume = 0.0;
3473 let maximum_volume = 1.0;
3474 if *value < minimum_volume || *value > maximum_volume {
3475 return Err(Error::IndexSize(Some(
3476 "Volume value must be between 0.0 and 1.0".into(),
3477 )));
3478 }
3479
3480 if self.volume.get() == *value {
3481 return Ok(());
3482 }
3483
3484 self.volume.set(*value);
3485
3486 if let Some(ref player) = *self.player.borrow() &&
3487 let Err(error) = player.lock().unwrap().set_volume(*value)
3488 {
3489 warn!("Could not set the volume: {error:?}");
3490 }
3491
3492 self.queue_media_element_task_to_fire_event(atom!("volumechange"));
3495
3496 if !self.is_allowed_to_play() {
3499 self.internal_pause_steps();
3500 }
3501
3502 Ok(())
3503 }
3504}
3505
3506impl VirtualMethods for HTMLMediaElement {
3507 fn super_type(&self) -> Option<&dyn VirtualMethods> {
3508 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3509 }
3510
3511 fn attribute_mutated(
3512 &self,
3513 cx: &mut JSContext,
3514 attr: AttrRef<'_>,
3515 mutation: AttributeMutation,
3516 ) {
3517 self.super_type()
3518 .unwrap()
3519 .attribute_mutated(cx, attr, mutation);
3520
3521 match *attr.local_name() {
3522 local_name!("muted") => {
3523 if let AttributeMutation::Set(
3527 _,
3528 AttributeMutationReason::ByCloning | AttributeMutationReason::ByParser,
3529 ) = mutation
3530 {
3531 self.SetMuted(cx, true);
3532 }
3533 },
3534 local_name!("src") => {
3535 if !mutation.is_removal() {
3540 self.media_element_load_algorithm(cx);
3541 }
3542 },
3543 local_name!("controls") => {
3544 if mutation.new_value(attr).is_some() {
3545 self.render_controls(cx);
3546 } else {
3547 self.remove_controls();
3548 }
3549 },
3550 _ => (),
3551 };
3552 }
3553
3554 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
3556 self.super_type().unwrap().unbind_from_tree(cx, context);
3557
3558 self.remove_controls();
3559
3560 if context.tree_connected {
3564 let task = MediaElementMicrotask::PauseIfNotInDocument {
3565 elem: Dom::from_ref(self),
3566 };
3567 ScriptThread::await_stable_state(cx, Box::new(task));
3568 }
3569 }
3570
3571 fn adopting_steps(&self, cx: &mut JSContext, old_doc: &Document) {
3572 self.super_type().unwrap().adopting_steps(cx, old_doc);
3573
3574 if let Some(id) = &*self.media_controls_id.borrow() {
3578 let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3579 error!("Missing media controls shadow root");
3580 return;
3581 };
3582
3583 old_doc.unregister_media_controls(id);
3584 self.owner_document()
3585 .register_media_controls(id, &shadow_root);
3586 }
3587 }
3588}
3589
3590#[derive(JSTraceable, MallocSizeOf)]
3591pub(crate) enum MediaElementMicrotask {
3592 ResourceSelection {
3593 elem: Dom<HTMLMediaElement>,
3594 generation_id: u32,
3595 #[no_trace]
3596 base_url: ServoUrl,
3597 },
3598 PauseIfNotInDocument {
3599 elem: Dom<HTMLMediaElement>,
3600 },
3601 Seeked {
3602 elem: Dom<HTMLMediaElement>,
3603 generation_id: u32,
3604 },
3605 SelectNextSourceChild {
3606 elem: Dom<HTMLMediaElement>,
3607 generation_id: u32,
3608 },
3609 SelectNextSourceChildAfterWait {
3610 elem: Dom<HTMLMediaElement>,
3611 generation_id: u32,
3612 },
3613}
3614
3615impl MicrotaskRunnable for MediaElementMicrotask {
3616 fn handler(&self, cx: &mut JSContext) {
3617 let mut realm = match self {
3618 &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3619 &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3620 &MediaElementMicrotask::Seeked { ref elem, .. } |
3621 &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3622 &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3623 enter_auto_realm(cx, &**elem)
3624 },
3625 };
3626 let cx = &mut realm;
3627 match self {
3628 &MediaElementMicrotask::ResourceSelection {
3629 ref elem,
3630 generation_id,
3631 ref base_url,
3632 } => {
3633 if generation_id == elem.generation_id.get() {
3634 elem.resource_selection_algorithm_sync(base_url.clone(), cx);
3635 }
3636 },
3637 MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3639 if elem.upcast::<Node>().is_connected() {
3641 return;
3642 }
3643 elem.internal_pause_steps();
3645 },
3646 &MediaElementMicrotask::Seeked {
3647 ref elem,
3648 generation_id,
3649 } => {
3650 if generation_id == elem.generation_id.get() {
3651 elem.seek_end();
3652 }
3653 },
3654 &MediaElementMicrotask::SelectNextSourceChild {
3655 ref elem,
3656 generation_id,
3657 } => {
3658 if generation_id == elem.generation_id.get() {
3659 elem.select_next_source_child(cx);
3660 }
3661 },
3662 &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3663 ref elem,
3664 generation_id,
3665 } => {
3666 if generation_id == elem.generation_id.get() {
3667 elem.select_next_source_child_after_wait(cx);
3668 }
3669 },
3670 }
3671 }
3672}
3673
3674enum Resource {
3675 Object,
3676 Url(ServoUrl),
3677}
3678
3679#[derive(Debug, MallocSizeOf, PartialEq)]
3680enum DataBuffer {
3681 Payload(Vec<u8>),
3682 EndOfStream,
3683}
3684
3685#[derive(MallocSizeOf)]
3686struct BufferedDataSource {
3687 locked: Cell<bool>,
3692 buffers: VecDeque<DataBuffer>,
3694}
3695
3696impl BufferedDataSource {
3697 fn new() -> BufferedDataSource {
3698 BufferedDataSource {
3699 locked: Cell::new(true),
3700 buffers: VecDeque::default(),
3701 }
3702 }
3703
3704 fn set_locked(&self, locked: bool) {
3705 self.locked.set(locked)
3706 }
3707
3708 fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3709 debug_assert_ne!(
3710 self.buffers.back(),
3711 Some(&DataBuffer::EndOfStream),
3712 "The media backend not expects any further data after end of stream"
3713 );
3714
3715 self.buffers.push_back(buffer);
3716 }
3717
3718 fn process_into_player_from_queue(
3719 &mut self,
3720 player: &Arc<Mutex<dyn Player>>,
3721 ) -> Result<(), PlayerError> {
3722 if self.locked.get() {
3724 return Ok(());
3725 }
3726
3727 while let Some(buffer) = self.buffers.pop_front() {
3728 match buffer {
3729 DataBuffer::Payload(payload) => {
3730 if let Err(error) = player.lock().unwrap().push_data(payload) {
3731 warn!("Could not push input data to player: {error:?}");
3732 return Err(error);
3733 }
3734 },
3735 DataBuffer::EndOfStream => {
3736 if let Err(error) = player.lock().unwrap().end_of_stream() {
3737 warn!("Could not signal EOS to player: {error:?}");
3738 return Err(error);
3739 }
3740 },
3741 }
3742 }
3743
3744 Ok(())
3745 }
3746
3747 fn reset(&mut self) {
3748 self.locked.set(true);
3749 self.buffers.clear();
3750 }
3751}
3752
3753#[derive(Debug, MallocSizeOf, PartialEq)]
3755enum CancelReason {
3756 Backoff,
3758 Error,
3760 Abort,
3762}
3763
3764#[derive(MallocSizeOf)]
3765pub(crate) struct HTMLMediaElementFetchContext {
3766 request_id: RequestId,
3768 cancel_reason: Option<CancelReason>,
3770 is_seekable: bool,
3772 origin_clean: bool,
3774 data_source: RefCell<BufferedDataSource>,
3776 fetch_canceller: FetchCanceller,
3779}
3780
3781impl HTMLMediaElementFetchContext {
3782 fn new(
3783 request_id: RequestId,
3784 core_resource_thread: CoreResourceThread,
3785 ) -> HTMLMediaElementFetchContext {
3786 HTMLMediaElementFetchContext {
3787 request_id,
3788 cancel_reason: None,
3789 is_seekable: false,
3790 origin_clean: true,
3791 data_source: RefCell::new(BufferedDataSource::new()),
3792 fetch_canceller: FetchCanceller::new(request_id, false, core_resource_thread),
3793 }
3794 }
3795
3796 fn request_id(&self) -> RequestId {
3797 self.request_id
3798 }
3799
3800 fn is_seekable(&self) -> bool {
3801 self.is_seekable
3802 }
3803
3804 fn set_seekable(&mut self, seekable: bool) {
3805 self.is_seekable = seekable;
3806 }
3807
3808 fn origin_is_clean(&self) -> bool {
3809 self.origin_clean
3810 }
3811
3812 fn set_origin_clean(&mut self, origin_clean: bool) {
3813 self.origin_clean = origin_clean;
3814 }
3815
3816 fn data_source(&self) -> &RefCell<BufferedDataSource> {
3817 &self.data_source
3818 }
3819
3820 fn cancel(&mut self, reason: CancelReason) {
3821 if self.cancel_reason.is_some() {
3822 return;
3823 }
3824 self.cancel_reason = Some(reason);
3825 self.data_source.borrow_mut().reset();
3826 self.fetch_canceller.abort();
3827 }
3828
3829 fn cancel_reason(&self) -> &Option<CancelReason> {
3830 &self.cancel_reason
3831 }
3832}
3833
3834struct HTMLMediaElementFetchListener {
3835 element: Trusted<HTMLMediaElement>,
3837 generation_id: u32,
3839 request_id: RequestId,
3841 next_progress_event: Instant,
3843 url: ServoUrl,
3845 expected_content_length: Option<u64>,
3847 fetched_content_length: u64,
3849 content_length_to_discard: u64,
3853}
3854
3855impl FetchResponseListener for HTMLMediaElementFetchListener {
3856 fn process_request_body(&mut self, _: RequestId) {}
3857
3858 fn process_response(
3859 &mut self,
3860 cx: &mut JSContext,
3861 _: RequestId,
3862 metadata: Result<FetchMetadata, NetworkError>,
3863 ) {
3864 let element = self.element.root();
3865
3866 let (metadata, origin_clean) = match metadata {
3867 Ok(fetch_metadata) => match fetch_metadata {
3868 FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3869 FetchMetadata::Filtered { filtered, unsafe_ } => (
3870 Some(unsafe_),
3871 matches!(
3872 filtered,
3873 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3874 ),
3875 ),
3876 },
3877 Err(_) => (None, true),
3878 };
3879
3880 let (status_is_success, is_seekable) =
3881 metadata.as_ref().map_or((false, false), |metadata| {
3882 let status = &metadata.status;
3883 (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3884 });
3885
3886 if !status_is_success {
3888 if element.ready_state.get() == ReadyState::HaveNothing {
3889 element.media_data_processing_failure_steps(cx);
3891 } else {
3892 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
3894 }
3895 return;
3896 }
3897
3898 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3899 current_fetch_context.set_seekable(is_seekable);
3900 current_fetch_context.set_origin_clean(origin_clean);
3901 }
3902
3903 if let Some(metadata) = metadata.as_ref() &&
3904 let Some(headers) = metadata.headers.as_ref()
3905 {
3906 let content_length = if let Some(content_range) = headers.typed_get::<ContentRange>() {
3909 content_range.bytes_len()
3910 } else {
3911 headers
3912 .typed_get::<ContentLength>()
3913 .map(|content_length| content_length.0)
3914 };
3915
3916 if content_length != self.expected_content_length &&
3918 let Some(content_length) = content_length
3919 {
3920 self.expected_content_length = Some(content_length);
3921 }
3922 }
3923
3924 if let Err(e) = element
3926 .player
3927 .borrow()
3928 .as_ref()
3929 .unwrap()
3930 .lock()
3931 .unwrap()
3932 .set_seekable(is_seekable)
3933 {
3934 warn!("Could not set player seekable {:?}", e);
3935 }
3936
3937 if let Some(expected_content_length) = self.expected_content_length &&
3938 let Err(e) = element
3939 .player
3940 .borrow()
3941 .as_ref()
3942 .unwrap()
3943 .lock()
3944 .unwrap()
3945 .set_input_size(expected_content_length)
3946 {
3947 warn!("Could not set player input size {:?}", e);
3948 }
3949 }
3950
3951 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
3952 let element = self.element.root();
3953
3954 self.fetched_content_length += chunk.len() as u64;
3955
3956 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3958 if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3959 return;
3960 }
3961
3962 let payload =
3964 if !current_fetch_context.is_seekable() && self.content_length_to_discard != 0 {
3965 if chunk.len() as u64 > self.content_length_to_discard {
3966 let shrink_chunk = chunk.slice(self.content_length_to_discard as usize..);
3967 self.content_length_to_discard = 0;
3968 shrink_chunk
3969 } else {
3970 self.content_length_to_discard -= chunk.len() as u64;
3972 return;
3973 }
3974 } else {
3975 chunk
3976 };
3977
3978 if let Err(e) = {
3979 let mut data_source = current_fetch_context.data_source().borrow_mut();
3980 data_source.add_buffer_to_queue(DataBuffer::Payload(payload.to_vec()));
3981 data_source
3982 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3983 } {
3984 if e == PlayerError::EnoughData {
3989 current_fetch_context.cancel(CancelReason::Backoff);
3990 }
3991 return;
3992 }
3993 }
3994
3995 if Instant::now() > self.next_progress_event {
4000 element.queue_media_element_task_to_fire_event(atom!("progress"));
4001 self.next_progress_event = Instant::now() + Duration::from_millis(350);
4002 }
4003 }
4004
4005 fn process_response_eof(
4006 self,
4007 cx: &mut JSContext,
4008 _: RequestId,
4009 status: Result<(), NetworkError>,
4010 timing: ResourceFetchTiming,
4011 ) {
4012 let element = self.element.root();
4013
4014 if status.is_ok() && self.fetched_content_length != 0 {
4016 if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
4021 {
4022 if self.expected_content_length.is_none() &&
4029 let Err(e) = element
4030 .player
4031 .borrow()
4032 .as_ref()
4033 .unwrap()
4034 .lock()
4035 .unwrap()
4036 .set_input_size(self.fetched_content_length)
4037 {
4038 warn!("Could not set player input size {:?}", e);
4039 }
4040
4041 let mut data_source = current_fetch_context.data_source().borrow_mut();
4042
4043 data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
4044 let _ = data_source
4045 .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
4046 }
4047
4048 element
4050 .upcast::<EventTarget>()
4051 .fire_event(cx, atom!("progress"));
4052
4053 element.network_state.set(NetworkState::Idle);
4056
4057 element
4058 .upcast::<EventTarget>()
4059 .fire_event(cx, atom!("suspend"));
4060 } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
4061 element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, cx);
4063 } else {
4064 element.media_data_processing_failure_steps(cx);
4067 }
4068
4069 network_listener::submit_timing(cx, &self, &status, &timing);
4070 }
4071
4072 fn process_csp_violations(
4073 &mut self,
4074 cx: &mut js::context::JSContext,
4075 _request_id: RequestId,
4076 violations: Vec<Violation>,
4077 ) {
4078 let global = &self.resource_timing_global();
4079 global.report_csp_violations(cx, violations, None, None);
4080 }
4081
4082 fn should_invoke(&self) -> bool {
4083 let element = self.element.root();
4084
4085 if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
4086 return false;
4087 }
4088
4089 let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
4090 return false;
4091 };
4092
4093 if current_fetch_context.request_id() != self.request_id {
4095 return false;
4096 }
4097
4098 if let Some(cancel_reason) = current_fetch_context.cancel_reason() &&
4101 matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort)
4102 {
4103 return false;
4104 }
4105
4106 true
4107 }
4108}
4109
4110impl ResourceTimingListener for HTMLMediaElementFetchListener {
4111 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
4112 let initiator_type = InitiatorType::LocalName(
4113 self.element
4114 .root()
4115 .upcast::<Element>()
4116 .local_name()
4117 .to_string(),
4118 );
4119 (initiator_type, self.url.clone())
4120 }
4121
4122 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
4123 self.element.root().owner_document().global()
4124 }
4125}
4126
4127impl HTMLMediaElementFetchListener {
4128 fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
4129 Self {
4130 element: Trusted::new(element),
4131 generation_id: element.generation_id.get(),
4132 request_id,
4133 next_progress_event: Instant::now() + Duration::from_millis(350),
4134 url,
4135 expected_content_length: None,
4136 fetched_content_length: 0,
4137 content_length_to_discard: offset,
4138 }
4139 }
4140}
4141
4142#[derive(JSTraceable, MallocSizeOf)]
4146struct HTMLMediaElementEventHandler {
4147 element: WeakRef<HTMLMediaElement>,
4148}
4149
4150#[expect(unsafe_code)]
4151unsafe impl Send for HTMLMediaElementEventHandler {}
4152
4153impl HTMLMediaElementEventHandler {
4154 fn new(element: &HTMLMediaElement) -> Self {
4155 Self {
4156 element: WeakRef::new(element),
4157 }
4158 }
4159
4160 fn handle_player_event(&self, player_id: usize, event: PlayerEvent, cx: &mut JSContext) {
4161 let Some(element) = self.element.root() else {
4162 return;
4163 };
4164
4165 if element.player_id().is_none_or(|id| id != player_id) {
4167 return;
4168 }
4169
4170 match event {
4171 PlayerEvent::DurationChanged(duration) => element.playback_duration_changed(duration),
4172 PlayerEvent::EndOfStream => element.playback_end(),
4173 PlayerEvent::EnoughData => element.playback_enough_data(),
4174 PlayerEvent::Error(ref error) => element.playback_error(error, cx),
4175 PlayerEvent::MetadataUpdated(ref metadata) => {
4176 element.playback_metadata_updated(cx, metadata)
4177 },
4178 PlayerEvent::NeedData => element.playback_need_data(),
4179 PlayerEvent::PositionChanged(position) => {
4180 element.playback_position_changed(cx, position)
4181 },
4182 PlayerEvent::SeekData(offset, seek_lock) => {
4183 element.fetch_request(cx, Some(offset), Some(seek_lock))
4184 },
4185 PlayerEvent::SeekDone(position) => element.playback_seek_done(cx, position),
4186 PlayerEvent::StateChanged(ref state) => element.playback_state_changed(cx, state),
4187 PlayerEvent::VideoFrameUpdated => element.playback_video_frame_updated(cx.no_gc()),
4188 }
4189 }
4190}
4191
4192impl Drop for HTMLMediaElementEventHandler {
4193 fn drop(&mut self) {
4194 assert_in_script();
4198 }
4199}