script/dom/media/
mediasession.rs1use std::rc::Rc;
6
7use constellation_traits::ScriptToConstellationMessage;
8use dom_struct::dom_struct;
9use embedder_traits::{
10 MediaMetadata as EmbedderMediaMetadata, MediaPositionState as EmbedderMediaPositionState,
11 MediaSessionActionType, MediaSessionEvent,
12};
13use rustc_hash::FxBuildHasher;
14
15use crate::conversions::Convert;
16use crate::dom::bindings::callback::ExceptionHandling;
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
19use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::{
20 MediaMetadataInit, MediaMetadataMethods,
21};
22use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::{
23 MediaPositionState, MediaSessionAction, MediaSessionActionHandler, MediaSessionMethods,
24 MediaSessionPlaybackState,
25};
26use crate::dom::bindings::error::{Error, Fallible};
27use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::bindings::trace::HashMapTracedValues;
30use crate::dom::bindings::weakref::MutableWeakRef;
31use crate::dom::html::htmlmediaelement::HTMLMediaElement;
32use crate::dom::media::mediametadata::MediaMetadata;
33use crate::dom::window::Window;
34use crate::realms::enter_auto_realm;
35use crate::script_runtime::CanGc;
36
37#[dom_struct]
38pub(crate) struct MediaSession {
39 reflector_: Reflector,
40 #[ignore_malloc_size_of = "defined in embedder_traits"]
42 #[no_trace]
43 metadata: DomRefCell<Option<EmbedderMediaMetadata>>,
44 playback_state: DomRefCell<MediaSessionPlaybackState>,
46 #[conditional_malloc_size_of]
48 action_handlers: DomRefCell<
49 HashMapTracedValues<MediaSessionActionType, Rc<MediaSessionActionHandler>, FxBuildHasher>,
50 >,
51 media_instance: MutableWeakRef<HTMLMediaElement>,
54}
55
56impl MediaSession {
57 fn new_inherited() -> MediaSession {
58 MediaSession {
59 reflector_: Reflector::new(),
60 metadata: DomRefCell::new(None),
61 playback_state: DomRefCell::new(MediaSessionPlaybackState::None),
62 action_handlers: DomRefCell::new(HashMapTracedValues::new_fx()),
63 media_instance: MutableWeakRef::new(None),
64 }
65 }
66
67 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<MediaSession> {
68 reflect_dom_object(Box::new(MediaSession::new_inherited()), window, can_gc)
69 }
70
71 pub(crate) fn register_media_instance(&self, media_instance: &HTMLMediaElement) {
72 self.media_instance.set(Some(media_instance));
73 }
74
75 pub(crate) fn handle_action(
76 &self,
77 cx: &mut js::context::JSContext,
78 action: MediaSessionActionType,
79 ) {
80 debug!("Handle media session action {:?}", action);
81
82 if let Some(handler) = self.action_handlers.borrow().get(&action) {
83 if handler
84 .Call__(ExceptionHandling::Report, CanGc::from_cx(cx))
85 .is_err()
86 {
87 warn!("Error calling MediaSessionActionHandler callback");
88 }
89 return;
90 }
91
92 if let Some(media) = self.media_instance.root() {
94 match action {
95 MediaSessionActionType::Play => {
96 let mut realm = enter_auto_realm(cx, self);
97 let mut realm = realm.current_realm();
98 media.Play(&mut realm);
99 },
100 MediaSessionActionType::Pause => {
101 media.Pause(cx);
102 },
103 MediaSessionActionType::SeekBackward => {},
104 MediaSessionActionType::SeekForward => {},
105 MediaSessionActionType::PreviousTrack => {},
106 MediaSessionActionType::NextTrack => {},
107 MediaSessionActionType::SkipAd => {},
108 MediaSessionActionType::Stop => {},
109 MediaSessionActionType::SeekTo => {},
110 }
111 }
112 }
113
114 pub(crate) fn send_event(&self, event: MediaSessionEvent) {
115 let global = self.global();
116 let window = global.as_window();
117 let pipeline_id = window.pipeline_id();
118 window.send_to_constellation(ScriptToConstellationMessage::MediaSessionEvent(
119 pipeline_id,
120 event,
121 ));
122 }
123
124 pub(crate) fn update_title(&self, title: String) {
125 let mut metadata = self.metadata.borrow_mut();
126 if let Some(ref mut metadata) = *metadata {
127 if !metadata.title.is_empty() {
130 return;
131 }
132 metadata.title = title;
133 } else {
134 *metadata = Some(EmbedderMediaMetadata::new(title));
135 }
136 self.send_event(MediaSessionEvent::SetMetadata(
137 metadata.as_ref().unwrap().clone(),
138 ));
139 }
140}
141
142impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
143 fn GetMetadata(&self, can_gc: CanGc) -> Option<DomRoot<MediaMetadata>> {
145 if let Some(ref metadata) = *self.metadata.borrow() {
146 let mut init = MediaMetadataInit::empty();
147 init.title = metadata.title.clone().into();
148 init.artist = metadata.artist.clone().into();
149 init.album = metadata.album.clone().into();
150 let global = self.global();
151 Some(MediaMetadata::new(global.as_window(), &init, can_gc))
152 } else {
153 None
154 }
155 }
156
157 fn SetMetadata(&self, metadata: Option<&MediaMetadata>) {
159 if let Some(metadata) = metadata {
160 metadata.set_session(self);
161 }
162
163 let global = self.global();
164 let window = global.as_window();
165 let _metadata = match metadata {
166 Some(m) => {
167 let title = if m.Title().is_empty() {
168 window.get_url().into_string()
169 } else {
170 m.Title().into()
171 };
172 EmbedderMediaMetadata {
173 title,
174 artist: m.Artist().into(),
175 album: m.Album().into(),
176 }
177 },
178 None => EmbedderMediaMetadata::new(window.get_url().into_string()),
179 };
180
181 *self.metadata.borrow_mut() = Some(_metadata.clone());
182
183 self.send_event(MediaSessionEvent::SetMetadata(_metadata));
184 }
185
186 fn PlaybackState(&self) -> MediaSessionPlaybackState {
188 *self.playback_state.borrow()
189 }
190
191 fn SetPlaybackState(&self, state: MediaSessionPlaybackState) {
193 *self.playback_state.borrow_mut() = state;
194 }
195
196 fn SetActionHandler(
198 &self,
199 action: MediaSessionAction,
200 handler: Option<Rc<MediaSessionActionHandler>>,
201 ) {
202 match handler {
203 Some(handler) => self
204 .action_handlers
205 .borrow_mut()
206 .insert(action.convert(), handler),
207 None => self.action_handlers.borrow_mut().remove(&action.convert()),
208 };
209 }
210
211 fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> {
213 if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() {
215 let position_state = EmbedderMediaPositionState::new(0.0, 0.0, 0.0);
216 self.send_event(MediaSessionEvent::SetPositionState(position_state));
217
218 return Ok(());
219 }
220
221 let duration = if let Some(state_duration) = state.duration {
222 if state_duration < 0.0 || state_duration.is_nan() {
224 return Err(Error::Type(c"Duration is negative or NaN".to_owned()));
225 }
226 state_duration
227 } else {
228 return Err(Error::Type(c"Duration is not present".to_owned()));
230 };
231
232 let position = if let Some(state_position) = state.position {
233 if *state_position < 0.0 || *state_position > duration {
235 return Err(Error::Type(
236 c"Position is negative or greater than duration".to_owned(),
237 ));
238 }
239 *state_position
240 } else {
241 0.0
243 };
244
245 let playback_rate = if let Some(state_playback_rate) = state.playbackRate {
246 if *state_playback_rate == 0.0 {
248 return Err(Error::Type(c"Playback rate is zero".to_owned()));
249 }
250 *state_playback_rate
251 } else {
252 1.0
254 };
255
256 let position_state = EmbedderMediaPositionState::new(duration, playback_rate, position);
258 self.send_event(MediaSessionEvent::SetPositionState(position_state));
259
260 Ok(())
261 }
262}
263
264impl Convert<MediaSessionActionType> for MediaSessionAction {
265 fn convert(self) -> MediaSessionActionType {
266 match self {
267 MediaSessionAction::Play => MediaSessionActionType::Play,
268 MediaSessionAction::Pause => MediaSessionActionType::Pause,
269 MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
270 MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
271 MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
272 MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
273 MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
274 MediaSessionAction::Stop => MediaSessionActionType::Stop,
275 MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
276 }
277 }
278}