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