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::str::DOMString;
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::{InRealm, enter_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 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
59 fn new_inherited() -> MediaSession {
60 MediaSession {
61 reflector_: Reflector::new(),
62 metadata: DomRefCell::new(None),
63 playback_state: DomRefCell::new(MediaSessionPlaybackState::None),
64 action_handlers: DomRefCell::new(HashMapTracedValues::new_fx()),
65 media_instance: MutableWeakRef::new(None),
66 }
67 }
68
69 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<MediaSession> {
70 reflect_dom_object(Box::new(MediaSession::new_inherited()), window, can_gc)
71 }
72
73 pub(crate) fn register_media_instance(&self, media_instance: &HTMLMediaElement) {
74 self.media_instance.set(Some(media_instance));
75 }
76
77 pub(crate) fn handle_action(&self, action: MediaSessionActionType, can_gc: CanGc) {
78 debug!("Handle media session action {:?}", action);
79
80 if let Some(handler) = self.action_handlers.borrow().get(&action) {
81 if handler.Call__(ExceptionHandling::Report, can_gc).is_err() {
82 warn!("Error calling MediaSessionActionHandler callback");
83 }
84 return;
85 }
86
87 if let Some(media) = self.media_instance.root() {
89 match action {
90 MediaSessionActionType::Play => {
91 let realm = enter_realm(self);
92 media.Play(InRealm::Entered(&realm), can_gc);
93 },
94 MediaSessionActionType::Pause => {
95 media.Pause(can_gc);
96 },
97 MediaSessionActionType::SeekBackward => {},
98 MediaSessionActionType::SeekForward => {},
99 MediaSessionActionType::PreviousTrack => {},
100 MediaSessionActionType::NextTrack => {},
101 MediaSessionActionType::SkipAd => {},
102 MediaSessionActionType::Stop => {},
103 MediaSessionActionType::SeekTo => {},
104 }
105 }
106 }
107
108 pub(crate) fn send_event(&self, event: MediaSessionEvent) {
109 let global = self.global();
110 let window = global.as_window();
111 let pipeline_id = window.pipeline_id();
112 window.send_to_constellation(ScriptToConstellationMessage::MediaSessionEvent(
113 pipeline_id,
114 event,
115 ));
116 }
117
118 pub(crate) fn update_title(&self, title: String) {
119 let mut metadata = self.metadata.borrow_mut();
120 if let Some(ref mut metadata) = *metadata {
121 if !metadata.title.is_empty() {
124 return;
125 }
126 metadata.title = title;
127 } else {
128 *metadata = Some(EmbedderMediaMetadata::new(title));
129 }
130 self.send_event(MediaSessionEvent::SetMetadata(
131 metadata.as_ref().unwrap().clone(),
132 ));
133 }
134}
135
136impl MediaSessionMethods<crate::DomTypeHolder> for MediaSession {
137 fn GetMetadata(&self, can_gc: CanGc) -> Option<DomRoot<MediaMetadata>> {
139 if let Some(ref metadata) = *self.metadata.borrow() {
140 let mut init = MediaMetadataInit::empty();
141 init.title = DOMString::from_string(metadata.title.clone());
142 init.artist = DOMString::from_string(metadata.artist.clone());
143 init.album = DOMString::from_string(metadata.album.clone());
144 let global = self.global();
145 Some(MediaMetadata::new(global.as_window(), &init, can_gc))
146 } else {
147 None
148 }
149 }
150
151 fn SetMetadata(&self, metadata: Option<&MediaMetadata>) {
153 if let Some(metadata) = metadata {
154 metadata.set_session(self);
155 }
156
157 let global = self.global();
158 let window = global.as_window();
159 let _metadata = match metadata {
160 Some(m) => {
161 let title = if m.Title().is_empty() {
162 window.get_url().into_string()
163 } else {
164 m.Title().into()
165 };
166 EmbedderMediaMetadata {
167 title,
168 artist: m.Artist().into(),
169 album: m.Album().into(),
170 }
171 },
172 None => EmbedderMediaMetadata::new(window.get_url().into_string()),
173 };
174
175 *self.metadata.borrow_mut() = Some(_metadata.clone());
176
177 self.send_event(MediaSessionEvent::SetMetadata(_metadata));
178 }
179
180 fn PlaybackState(&self) -> MediaSessionPlaybackState {
182 *self.playback_state.borrow()
183 }
184
185 fn SetPlaybackState(&self, state: MediaSessionPlaybackState) {
187 *self.playback_state.borrow_mut() = state;
188 }
189
190 fn SetActionHandler(
192 &self,
193 action: MediaSessionAction,
194 handler: Option<Rc<MediaSessionActionHandler>>,
195 ) {
196 match handler {
197 Some(handler) => self
198 .action_handlers
199 .borrow_mut()
200 .insert(action.convert(), handler.clone()),
201 None => self.action_handlers.borrow_mut().remove(&action.convert()),
202 };
203 }
204
205 fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> {
207 if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() {
209 let position_state = EmbedderMediaPositionState::new(0.0, 0.0, 0.0);
210 self.send_event(MediaSessionEvent::SetPositionState(position_state));
211
212 return Ok(());
213 }
214
215 let duration = if let Some(state_duration) = state.duration {
216 if state_duration < 0.0 || state_duration.is_nan() {
218 return Err(Error::Type("Duration is negative or NaN".to_owned()));
219 }
220 state_duration
221 } else {
222 return Err(Error::Type("Duration is not present".to_owned()));
224 };
225
226 let position = if let Some(state_position) = state.position {
227 if *state_position < 0.0 || *state_position > duration {
229 return Err(Error::Type(
230 "Position is negative or greater than duration".to_owned(),
231 ));
232 }
233 *state_position
234 } else {
235 0.0
237 };
238
239 let playback_rate = if let Some(state_playback_rate) = state.playbackRate {
240 if *state_playback_rate == 0.0 {
242 return Err(Error::Type("Playback rate is zero".to_owned()));
243 }
244 *state_playback_rate
245 } else {
246 1.0
248 };
249
250 let position_state = EmbedderMediaPositionState::new(duration, playback_rate, position);
252 self.send_event(MediaSessionEvent::SetPositionState(position_state));
253
254 Ok(())
255 }
256}
257
258impl Convert<MediaSessionActionType> for MediaSessionAction {
259 fn convert(self) -> MediaSessionActionType {
260 match self {
261 MediaSessionAction::Play => MediaSessionActionType::Play,
262 MediaSessionAction::Pause => MediaSessionActionType::Pause,
263 MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
264 MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
265 MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
266 MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
267 MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
268 MediaSessionAction::Stop => MediaSessionActionType::Stop,
269 MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
270 }
271 }
272}