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 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(&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.root() {
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 let position_state = EmbedderMediaPositionState::new(0.0, 0.0, 0.0);
209 self.send_event(MediaSessionEvent::SetPositionState(position_state));
210
211 return Ok(());
212 }
213
214 let duration = if let Some(state_duration) = state.duration {
215 if state_duration < 0.0 || state_duration.is_nan() {
217 return Err(Error::Type("Duration is negative or NaN".to_owned()));
218 }
219 state_duration
220 } else {
221 return Err(Error::Type("Duration is not present".to_owned()));
223 };
224
225 let position = if let Some(state_position) = state.position {
226 if *state_position < 0.0 || *state_position > duration {
228 return Err(Error::Type(
229 "Position is negative or greater than duration".to_owned(),
230 ));
231 }
232 *state_position
233 } else {
234 0.0
236 };
237
238 let playback_rate = if let Some(state_playback_rate) = state.playbackRate {
239 if *state_playback_rate == 0.0 {
241 return Err(Error::Type("Playback rate is zero".to_owned()));
242 }
243 *state_playback_rate
244 } else {
245 1.0
247 };
248
249 let position_state = EmbedderMediaPositionState::new(duration, playback_rate, position);
251 self.send_event(MediaSessionEvent::SetPositionState(position_state));
252
253 Ok(())
254 }
255}
256
257impl Convert<MediaSessionActionType> for MediaSessionAction {
258 fn convert(self) -> MediaSessionActionType {
259 match self {
260 MediaSessionAction::Play => MediaSessionActionType::Play,
261 MediaSessionAction::Pause => MediaSessionActionType::Pause,
262 MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
263 MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
264 MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
265 MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
266 MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
267 MediaSessionAction::Stop => MediaSessionActionType::Stop,
268 MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
269 }
270 }
271}