script/dom/media/
mediasession.rs1use dom_struct::dom_struct;
6use embedder_traits::{
7 MediaMetadata as EmbedderMediaMetadata, MediaPositionState as EmbedderMediaPositionState,
8 MediaSessionActionType, MediaSessionEvent,
9};
10use js::context::JSContext;
11use rustc_hash::FxBuildHasher;
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::{Reflector, reflect_dom_object};
14use servo_constellation_traits::ScriptToConstellationMessage;
15
16use crate::conversions::Convert;
17use crate::dom::bindings::callback::{ExceptionHandling, RootedCallback, TracedCallback};
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;
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::bindings::trace::HashMapTracedValues;
30use crate::dom::html::htmlmediaelement::{HTMLMediaElement, MediaElementWeakRef};
31use crate::dom::media::mediametadata::MediaMetadata;
32use crate::dom::window::Window;
33use crate::realms::enter_auto_realm;
34
35#[dom_struct]
36pub(crate) struct MediaSession {
37 reflector_: Reflector,
38 #[no_trace]
40 metadata: DomRefCell<Option<EmbedderMediaMetadata>>,
41 playback_state: DomRefCell<MediaSessionPlaybackState>,
43 action_handlers: DomRefCell<
45 HashMapTracedValues<
46 MediaSessionActionType,
47 TracedCallback<MediaSessionActionHandler>,
48 FxBuildHasher,
49 >,
50 >,
51 media_instance: DomRefCell<Option<MediaElementWeakRef>>,
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: DomRefCell::new(None),
64 }
65 }
66
67 pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<MediaSession> {
68 reflect_dom_object(cx, Box::new(MediaSession::new_inherited()), window)
69 }
70
71 pub(crate) fn register_media_instance(&self, media_instance: &HTMLMediaElement) {
72 *self.media_instance.borrow_mut() = Some(MediaElementWeakRef::new(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.Call__(cx, ExceptionHandling::Report).is_err() {
84 warn!("Error calling MediaSessionActionHandler callback");
85 }
86 return;
87 }
88
89 let media_instance = {
92 let media_instance = self.media_instance.borrow();
93 media_instance.as_ref().and_then(|media| media.root())
94 };
95 if let Some(media) = media_instance {
96 match action {
97 MediaSessionActionType::Play => {
98 let mut realm = enter_auto_realm(cx, self);
99 let mut realm = realm.current_realm();
100 media.Play(&mut realm);
101 },
102 MediaSessionActionType::Pause => {
103 media.Pause(cx);
104 },
105 MediaSessionActionType::SeekBackward => {},
106 MediaSessionActionType::SeekForward => {},
107 MediaSessionActionType::PreviousTrack => {},
108 MediaSessionActionType::NextTrack => {},
109 MediaSessionActionType::SkipAd => {},
110 MediaSessionActionType::Stop => {},
111 MediaSessionActionType::SeekTo => {},
112 }
113 }
114 }
115
116 pub(crate) fn send_event(&self, event: MediaSessionEvent) {
117 let global = self.global();
118 let window = global.as_window();
119 let pipeline_id = window.pipeline_id();
120 window.send_to_constellation(ScriptToConstellationMessage::MediaSessionEvent(
121 pipeline_id,
122 event,
123 ));
124 }
125
126 pub(crate) fn update_title(&self, title: String) {
127 let mut metadata = self.metadata.borrow_mut();
128 if let Some(ref mut metadata) = *metadata {
129 if !metadata.title.is_empty() {
132 return;
133 }
134 metadata.title = title;
135 } else {
136 *metadata = Some(EmbedderMediaMetadata::new(title));
137 }
138 self.send_event(MediaSessionEvent::SetMetadata(
139 metadata.as_ref().unwrap().clone(),
140 ));
141 }
142}
143
144impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
145 fn GetMetadata(&self, cx: &mut JSContext) -> Option<DomRoot<MediaMetadata>> {
147 if let Some(ref metadata) = *self.metadata.borrow() {
148 let mut init = MediaMetadataInit::empty();
149 init.title = metadata.title.clone().into();
150 init.artist = metadata.artist.clone().into();
151 init.album = metadata.album.clone().into();
152 let global = self.global();
153 Some(MediaMetadata::new(cx, global.as_window(), &init))
154 } else {
155 None
156 }
157 }
158
159 fn SetMetadata(&self, metadata: Option<&MediaMetadata>) {
161 if let Some(metadata) = metadata {
162 metadata.set_session(self);
163 }
164
165 let global = self.global();
166 let window = global.as_window();
167 let _metadata = match metadata {
168 Some(m) => {
169 let title = if m.Title().is_empty() {
170 window.get_url().into_string()
171 } else {
172 m.Title().into()
173 };
174 EmbedderMediaMetadata {
175 title,
176 artist: m.Artist().into(),
177 album: m.Album().into(),
178 }
179 },
180 None => EmbedderMediaMetadata::new(window.get_url().into_string()),
181 };
182
183 *self.metadata.borrow_mut() = Some(_metadata.clone());
184
185 self.send_event(MediaSessionEvent::SetMetadata(_metadata));
186 }
187
188 fn PlaybackState(&self) -> MediaSessionPlaybackState {
190 *self.playback_state.borrow()
191 }
192
193 fn SetPlaybackState(&self, state: MediaSessionPlaybackState) {
195 *self.playback_state.borrow_mut() = state;
196 }
197
198 fn SetActionHandler(
200 &self,
201 action: MediaSessionAction,
202 handler: Option<RootedCallback<MediaSessionActionHandler>>,
203 ) {
204 match handler {
205 Some(handler) => self
206 .action_handlers
207 .borrow_mut()
208 .insert(action.convert(), handler.to_traced()),
209 None => self.action_handlers.borrow_mut().remove(&action.convert()),
210 };
211 }
212
213 fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> {
215 if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() {
217 let position_state = EmbedderMediaPositionState::new(0.0, 0.0, 0.0);
218 self.send_event(MediaSessionEvent::SetPositionState(position_state));
219
220 return Ok(());
221 }
222
223 let duration = if let Some(state_duration) = state.duration {
224 if state_duration < 0.0 || state_duration.is_nan() {
226 return Err(Error::Type(c"Duration is negative or NaN".to_owned()));
227 }
228 state_duration
229 } else {
230 return Err(Error::Type(c"Duration is not present".to_owned()));
232 };
233
234 let position = if let Some(state_position) = state.position {
235 if *state_position < 0.0 || *state_position > duration {
237 return Err(Error::Type(
238 c"Position is negative or greater than duration".to_owned(),
239 ));
240 }
241 *state_position
242 } else {
243 0.0
245 };
246
247 let playback_rate = if let Some(state_playback_rate) = state.playbackRate {
248 if *state_playback_rate == 0.0 {
250 return Err(Error::Type(c"Playback rate is zero".to_owned()));
251 }
252 *state_playback_rate
253 } else {
254 1.0
256 };
257
258 let position_state = EmbedderMediaPositionState::new(duration, playback_rate, position);
260 self.send_event(MediaSessionEvent::SetPositionState(position_state));
261
262 Ok(())
263 }
264}
265
266impl Convert<MediaSessionActionType> for MediaSessionAction {
267 fn convert(self) -> MediaSessionActionType {
268 match self {
269 MediaSessionAction::Play => MediaSessionActionType::Play,
270 MediaSessionAction::Pause => MediaSessionActionType::Pause,
271 MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
272 MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
273 MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
274 MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
275 MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
276 MediaSessionAction::Stop => MediaSessionActionType::Stop,
277 MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
278 }
279 }
280}