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