script/dom/
mediasession.rs1use std::rc::Rc;
6
7use constellation_traits::ScriptToConstellationMessage;
8use dom_struct::dom_struct;
9use embedder_traits::{
10 MediaMetadata as EmbedderMediaMetadata, MediaSessionActionType, MediaSessionEvent,
11};
12use rustc_hash::FxBuildHasher;
13
14use super::bindings::trace::HashMapTracedValues;
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::num::Finite;
28use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
29use crate::dom::bindings::root::{DomRoot, MutNullableDom};
30use crate::dom::bindings::str::DOMString;
31use crate::dom::html::htmlmediaelement::HTMLMediaElement;
32use crate::dom::mediametadata::MediaMetadata;
33use crate::dom::window::Window;
34use crate::realms::{InRealm, enter_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 #[ignore_malloc_size_of = "Rc"]
48 action_handlers: DomRefCell<
49 HashMapTracedValues<MediaSessionActionType, Rc<MediaSessionActionHandler>, FxBuildHasher>,
50 >,
51 media_instance: MutNullableDom<HTMLMediaElement>,
54}
55
56impl MediaSession {
57 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
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: Default::default(),
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(&self, action: MediaSessionActionType, can_gc: CanGc) {
77 debug!("Handle media session action {:?}", action);
78
79 if let Some(handler) = self.action_handlers.borrow().get(&action) {
80 if handler.Call__(ExceptionHandling::Report, can_gc).is_err() {
81 warn!("Error calling MediaSessionActionHandler callback");
82 }
83 return;
84 }
85
86 if let Some(media) = self.media_instance.get() {
88 match action {
89 MediaSessionActionType::Play => {
90 let realm = enter_realm(self);
91 media.Play(InRealm::Entered(&realm), can_gc);
92 },
93 MediaSessionActionType::Pause => {
94 media.Pause(can_gc);
95 },
96 MediaSessionActionType::SeekBackward => {},
97 MediaSessionActionType::SeekForward => {},
98 MediaSessionActionType::PreviousTrack => {},
99 MediaSessionActionType::NextTrack => {},
100 MediaSessionActionType::SkipAd => {},
101 MediaSessionActionType::Stop => {},
102 MediaSessionActionType::SeekTo => {},
103 }
104 }
105 }
106
107 pub(crate) fn send_event(&self, event: MediaSessionEvent) {
108 let global = self.global();
109 let window = global.as_window();
110 let pipeline_id = window.pipeline_id();
111 window.send_to_constellation(ScriptToConstellationMessage::MediaSessionEvent(
112 pipeline_id,
113 event,
114 ));
115 }
116
117 pub(crate) fn update_title(&self, title: String) {
118 let mut metadata = self.metadata.borrow_mut();
119 if let Some(ref mut metadata) = *metadata {
120 if !metadata.title.is_empty() {
123 return;
124 }
125 metadata.title = title;
126 } else {
127 *metadata = Some(EmbedderMediaMetadata::new(title));
128 }
129 self.send_event(MediaSessionEvent::SetMetadata(
130 metadata.as_ref().unwrap().clone(),
131 ));
132 }
133}
134
135impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
136 fn GetMetadata(&self, can_gc: CanGc) -> Option<DomRoot<MediaMetadata>> {
138 if let Some(ref metadata) = *self.metadata.borrow() {
139 let mut init = MediaMetadataInit::empty();
140 init.title = DOMString::from_string(metadata.title.clone());
141 init.artist = DOMString::from_string(metadata.artist.clone());
142 init.album = DOMString::from_string(metadata.album.clone());
143 let global = self.global();
144 Some(MediaMetadata::new(global.as_window(), &init, can_gc))
145 } else {
146 None
147 }
148 }
149
150 fn SetMetadata(&self, metadata: Option<&MediaMetadata>) {
152 if let Some(metadata) = metadata {
153 metadata.set_session(self);
154 }
155
156 let global = self.global();
157 let window = global.as_window();
158 let _metadata = match metadata {
159 Some(m) => {
160 let title = if m.Title().is_empty() {
161 window.get_url().into_string()
162 } else {
163 m.Title().into()
164 };
165 EmbedderMediaMetadata {
166 title,
167 artist: m.Artist().into(),
168 album: m.Album().into(),
169 }
170 },
171 None => EmbedderMediaMetadata::new(window.get_url().into_string()),
172 };
173
174 *self.metadata.borrow_mut() = Some(_metadata.clone());
175
176 self.send_event(MediaSessionEvent::SetMetadata(_metadata));
177 }
178
179 fn PlaybackState(&self) -> MediaSessionPlaybackState {
181 *self.playback_state.borrow()
182 }
183
184 fn SetPlaybackState(&self, state: MediaSessionPlaybackState) {
186 *self.playback_state.borrow_mut() = state;
187 }
188
189 fn SetActionHandler(
191 &self,
192 action: MediaSessionAction,
193 handler: Option<Rc<MediaSessionActionHandler>>,
194 ) {
195 match handler {
196 Some(handler) => self
197 .action_handlers
198 .borrow_mut()
199 .insert(action.convert(), handler.clone()),
200 None => self.action_handlers.borrow_mut().remove(&action.convert()),
201 };
202 }
203
204 fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> {
206 if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() {
208 if let Some(media_instance) = self.media_instance.get() {
209 media_instance.reset();
210 }
211 return Ok(());
212 }
213
214 if state.duration.is_none() {
216 return Err(Error::Type(
217 "duration is not present or its value is null".to_owned(),
218 ));
219 }
220
221 if let Some(state_duration) = state.duration {
223 if *state_duration < 0.0 {
224 return Err(Error::Type("duration is negative".to_owned()));
225 }
226 }
227
228 if let Some(state_position) = state.position {
230 if *state_position < 0.0 {
231 return Err(Error::Type("position is negative".to_owned()));
232 }
233 if let Some(state_duration) = state.duration {
234 if *state_position > *state_duration {
235 return Err(Error::Type("position is greater than duration".to_owned()));
236 }
237 }
238 }
239
240 if let Some(state_playback_rate) = state.playbackRate {
242 if *state_playback_rate <= 0.0 {
243 return Err(Error::Type("playbackRate is zero".to_owned()));
244 }
245 }
246
247 if let Some(media_instance) = self.media_instance.get() {
249 media_instance.set_duration(state.duration.map(|v| *v).unwrap());
250 media_instance.SetPlaybackRate(state.playbackRate.unwrap_or(Finite::wrap(1.0)))?;
252 media_instance.SetCurrentTime(state.position.unwrap_or(Finite::wrap(0.0)));
254 }
255
256 Ok(())
257 }
258}
259
260impl Convert<MediaSessionActionType> for MediaSessionAction {
261 fn convert(self) -> MediaSessionActionType {
262 match self {
263 MediaSessionAction::Play => MediaSessionActionType::Play,
264 MediaSessionAction::Pause => MediaSessionActionType::Pause,
265 MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
266 MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
267 MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
268 MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
269 MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
270 MediaSessionAction::Stop => MediaSessionActionType::Stop,
271 MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
272 }
273 }
274}