aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2019-11-20 12:31:29 -0500
committerGitHub <noreply@github.com>2019-11-20 12:31:29 -0500
commitf6348b8b54a1586b291bd4858df94050e05706c0 (patch)
tree4a1dd8e99f310609028b179b66f0eb74915b599e /components/script/dom
parent7da8d75a7e2a1f07bd09b8fb03b404ce4392a2af (diff)
parent9f77ea11651f2d987d84e01e222f2382d525b868 (diff)
downloadservo-f6348b8b54a1586b291bd4858df94050e05706c0.tar.gz
servo-f6348b8b54a1586b291bd4858df94050e05706c0.zip
Auto merge of #24499 - ferjm:media.session.api, r=Manishearth
Media Session API - [X] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #24172 - [x] There are tests for these changes This PR introduces all the pieces required to prove an end to end media session flow with Android as a test platform.
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/bindings/trace.rs8
-rw-r--r--components/script/dom/htmlmediaelement.rs58
-rw-r--r--components/script/dom/mediametadata.rs97
-rw-r--r--components/script/dom/mediasession.rs213
-rw-r--r--components/script/dom/mod.rs2
-rw-r--r--components/script/dom/navigator.rs19
-rw-r--r--components/script/dom/webidls/MediaMetadata.webidl30
-rw-r--r--components/script/dom/webidls/MediaSession.webidl57
8 files changed, 473 insertions, 11 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index 74e6996b67a..2051a1a19d8 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -57,7 +57,7 @@ use content_security_policy::CspList;
use crossbeam_channel::{Receiver, Sender};
use cssparser::RGBA;
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
-use embedder_traits::EventLoopWaker;
+use embedder_traits::{EventLoopWaker, MediaMetadata};
use encoding_rs::{Decoder, Encoding};
use euclid::default::{Point2D, Rect, Rotation3D, Transform2D, Transform3D};
use euclid::Length as EuclidLength;
@@ -94,8 +94,8 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan;
use script_layout_interface::rpc::LayoutRPC;
use script_layout_interface::OpaqueStyleAndLayoutData;
use script_traits::transferable::MessagePortImpl;
-use script_traits::DrawAPaintImageResult;
-use script_traits::{DocumentActivity, ScriptToConstellationChan, TimerEventId, TimerSource};
+use script_traits::{DocumentActivity, DrawAPaintImageResult};
+use script_traits::{MediaSessionActionType, ScriptToConstellationChan, TimerEventId, TimerSource};
use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use selectors::matching::ElementSelectorFlags;
use serde::{Deserialize, Serialize};
@@ -536,6 +536,8 @@ unsafe_no_jsmanaged_fields!(WindowGLContext);
unsafe_no_jsmanaged_fields!(VideoFrame);
unsafe_no_jsmanaged_fields!(WebGLContextId);
unsafe_no_jsmanaged_fields!(Arc<Mutex<dyn AudioRenderer>>);
+unsafe_no_jsmanaged_fields!(MediaSessionActionType);
+unsafe_no_jsmanaged_fields!(MediaMetadata);
unsafe impl<'a> JSTraceable for &'a str {
#[inline]
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
index d10801c5779..f8f707a0dd9 100644
--- a/components/script/dom/htmlmediaelement.rs
+++ b/components/script/dom/htmlmediaelement.rs
@@ -15,8 +15,10 @@ use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaE
use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods;
use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*;
use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
+use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode};
+use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId};
use crate::dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId};
use crate::dom::bindings::codegen::UnionTypes::{
@@ -65,6 +67,7 @@ use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
use dom_struct::dom_struct;
use embedder_traits::resources::{self, Resource as EmbedderResource};
+use embedder_traits::{MediaSessionEvent, MediaSessionPlaybackState};
use euclid::default::Size2D;
use headers::{ContentLength, ContentRange, HeaderMapExt};
use html5ever::{LocalName, Prefix};
@@ -592,7 +595,6 @@ impl HTMLMediaElement {
match (old_ready_state, ready_state) {
(ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
task_source.queue_simple_event(self.upcast(), atom!("loadedmetadata"), &window);
-
// No other steps are applicable in this case.
return;
},
@@ -1725,6 +1727,17 @@ impl HTMLMediaElement {
if self.Controls() {
self.render_controls();
}
+
+ let global = self.global();
+ let window = global.as_window();
+
+ // Update the media session metadata title with the obtained metadata.
+ window.Navigator().MediaSession().update_title(
+ metadata
+ .title
+ .clone()
+ .unwrap_or(window.get_url().into_string()),
+ );
},
PlayerEvent::NeedData => {
// The player needs more data.
@@ -1782,13 +1795,33 @@ impl HTMLMediaElement {
};
ScriptThread::await_stable_state(Microtask::MediaElement(task));
},
- PlayerEvent::StateChanged(ref state) => match *state {
- PlaybackState::Paused => {
- if self.ready_state.get() == ReadyState::HaveMetadata {
- self.change_ready_state(ReadyState::HaveEnoughData);
- }
- },
- _ => {},
+ PlayerEvent::StateChanged(ref state) => {
+ let mut media_session_playback_state = MediaSessionPlaybackState::None_;
+ match *state {
+ PlaybackState::Paused => {
+ media_session_playback_state = MediaSessionPlaybackState::Paused;
+ if self.ready_state.get() == ReadyState::HaveMetadata {
+ self.change_ready_state(ReadyState::HaveEnoughData);
+ }
+ },
+ PlaybackState::Playing => {
+ media_session_playback_state = MediaSessionPlaybackState::Playing;
+ },
+ PlaybackState::Buffering => {
+ // Do not send the media session playback state change event
+ // in this case as a None_ state is expected to clean up the
+ // session.
+ return;
+ },
+ _ => {},
+ };
+ debug!(
+ "Sending media session event playback state changed to {:?}",
+ media_session_playback_state
+ );
+ self.send_media_session_event(MediaSessionEvent::PlaybackStateChange(
+ media_session_playback_state,
+ ));
},
}
}
@@ -1883,6 +1916,15 @@ impl HTMLMediaElement {
self.media_element_load_algorithm();
}
}
+
+ fn send_media_session_event(&self, event: MediaSessionEvent) {
+ let global = self.global();
+ let media_session = global.as_window().Navigator().MediaSession();
+
+ media_session.register_media_instance(&self);
+
+ media_session.send_event(event);
+ }
}
// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
diff --git a/components/script/dom/mediametadata.rs b/components/script/dom/mediametadata.rs
new file mode 100644
index 00000000000..f2e94abfaa1
--- /dev/null
+++ b/components/script/dom/mediametadata.rs
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding;
+use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit;
+use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods;
+use crate::dom::bindings::error::Fallible;
+use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
+use crate::dom::bindings::root::{DomRoot, MutNullableDom};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::mediasession::MediaSession;
+use crate::dom::window::Window;
+use dom_struct::dom_struct;
+
+#[dom_struct]
+pub struct MediaMetadata {
+ reflector_: Reflector,
+ session: MutNullableDom<MediaSession>,
+ title: DomRefCell<DOMString>,
+ artist: DomRefCell<DOMString>,
+ album: DomRefCell<DOMString>,
+}
+
+impl MediaMetadata {
+ fn new_inherited(init: &MediaMetadataInit) -> MediaMetadata {
+ MediaMetadata {
+ reflector_: Reflector::new(),
+ session: Default::default(),
+ title: DomRefCell::new(init.title.clone()),
+ artist: DomRefCell::new(init.artist.clone()),
+ album: DomRefCell::new(init.album.clone()),
+ }
+ }
+
+ pub fn new(global: &Window, init: &MediaMetadataInit) -> DomRoot<MediaMetadata> {
+ reflect_dom_object(
+ Box::new(MediaMetadata::new_inherited(init)),
+ global,
+ MediaMetadataBinding::Wrap,
+ )
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-mediametadata
+ pub fn Constructor(
+ window: &Window,
+ init: &MediaMetadataInit,
+ ) -> Fallible<DomRoot<MediaMetadata>> {
+ Ok(MediaMetadata::new(window, init))
+ }
+
+ fn queue_update_metadata_algorithm(&self) {
+ if self.session.get().is_none() {
+ return;
+ }
+ }
+
+ pub fn set_session(&self, session: &MediaSession) {
+ self.session.set(Some(&session));
+ }
+}
+
+impl MediaMetadataMethods for MediaMetadata {
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-title
+ fn Title(&self) -> DOMString {
+ self.title.borrow().clone()
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-title
+ fn SetTitle(&self, value: DOMString) {
+ *self.title.borrow_mut() = value;
+ self.queue_update_metadata_algorithm();
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-artist
+ fn Artist(&self) -> DOMString {
+ self.artist.borrow().clone()
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-artist
+ fn SetArtist(&self, value: DOMString) {
+ *self.artist.borrow_mut() = value;
+ self.queue_update_metadata_algorithm();
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-album
+ fn Album(&self) -> DOMString {
+ self.album.borrow().clone()
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediametadata-album
+ fn SetAlbum(&self, value: DOMString) {
+ *self.album.borrow_mut() = value;
+ self.queue_update_metadata_algorithm();
+ }
+}
diff --git a/components/script/dom/mediasession.rs b/components/script/dom/mediasession.rs
new file mode 100644
index 00000000000..1523e9a0ae6
--- /dev/null
+++ b/components/script/dom/mediasession.rs
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::compartments::{AlreadyInCompartment, InCompartment};
+use crate::dom::bindings::callback::ExceptionHandling;
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods;
+use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit;
+use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods;
+use crate::dom::bindings::codegen::Bindings::MediaSessionBinding;
+use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionAction;
+use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionActionHandler;
+use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionMethods;
+use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionPlaybackState;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
+use crate::dom::bindings::root::{DomRoot, MutNullableDom};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::htmlmediaelement::HTMLMediaElement;
+use crate::dom::mediametadata::MediaMetadata;
+use crate::dom::window::Window;
+use dom_struct::dom_struct;
+use embedder_traits::MediaMetadata as EmbedderMediaMetadata;
+use embedder_traits::MediaSessionEvent;
+use script_traits::MediaSessionActionType;
+use script_traits::ScriptMsg;
+use std::collections::HashMap;
+use std::rc::Rc;
+
+#[dom_struct]
+pub struct MediaSession {
+ reflector_: Reflector,
+ /// https://w3c.github.io/mediasession/#dom-mediasession-metadata
+ #[ignore_malloc_size_of = "defined in embedder_traits"]
+ metadata: DomRefCell<Option<EmbedderMediaMetadata>>,
+ /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate
+ playback_state: DomRefCell<MediaSessionPlaybackState>,
+ /// https://w3c.github.io/mediasession/#supported-media-session-actions
+ #[ignore_malloc_size_of = "Rc"]
+ action_handlers: DomRefCell<HashMap<MediaSessionActionType, Rc<MediaSessionActionHandler>>>,
+ /// The media instance controlled by this media session.
+ /// For now only HTMLMediaElements are controlled by media sessions.
+ media_instance: MutNullableDom<HTMLMediaElement>,
+}
+
+impl MediaSession {
+ #[allow(unrooted_must_root)]
+ fn new_inherited() -> MediaSession {
+ let media_session = MediaSession {
+ reflector_: Reflector::new(),
+ metadata: DomRefCell::new(None),
+ playback_state: DomRefCell::new(MediaSessionPlaybackState::None),
+ action_handlers: DomRefCell::new(HashMap::new()),
+ media_instance: Default::default(),
+ };
+ media_session
+ }
+
+ pub fn new(window: &Window) -> DomRoot<MediaSession> {
+ reflect_dom_object(
+ Box::new(MediaSession::new_inherited()),
+ window,
+ MediaSessionBinding::Wrap,
+ )
+ }
+
+ pub fn register_media_instance(&self, media_instance: &HTMLMediaElement) {
+ self.media_instance.set(Some(media_instance));
+ }
+
+ pub fn handle_action(&self, action: MediaSessionActionType) {
+ debug!("Handle media session action {:?}", action);
+
+ if let Some(handler) = self.action_handlers.borrow().get(&action) {
+ if handler.Call__(ExceptionHandling::Report).is_err() {
+ warn!("Error calling MediaSessionActionHandler callback");
+ }
+ return;
+ }
+
+ // Default action.
+ if let Some(media) = self.media_instance.get() {
+ match action {
+ MediaSessionActionType::Play => {
+ let in_compartment_proof = AlreadyInCompartment::assert(&self.global());
+ media.Play(InCompartment::Already(&in_compartment_proof));
+ },
+ MediaSessionActionType::Pause => {
+ media.Pause();
+ },
+ MediaSessionActionType::SeekBackward => {},
+ MediaSessionActionType::SeekForward => {},
+ MediaSessionActionType::PreviousTrack => {},
+ MediaSessionActionType::NextTrack => {},
+ MediaSessionActionType::SkipAd => {},
+ MediaSessionActionType::Stop => {},
+ MediaSessionActionType::SeekTo => {},
+ }
+ }
+ }
+
+ pub fn send_event(&self, event: MediaSessionEvent) {
+ let global = self.global();
+ let window = global.as_window();
+ let pipeline_id = window
+ .pipeline_id()
+ .expect("Cannot send media session event outside of a pipeline");
+ window.send_to_constellation(ScriptMsg::MediaSessionEvent(pipeline_id, event));
+ }
+
+ pub fn update_title(&self, title: String) {
+ let mut metadata = self.metadata.borrow_mut();
+ if let Some(ref mut metadata) = *metadata {
+ // We only update the title with the data provided by the media
+ // player and iff the user did not provide a title.
+ if !metadata.title.is_empty() {
+ return;
+ }
+ metadata.title = title;
+ } else {
+ *metadata = Some(EmbedderMediaMetadata::new(title));
+ }
+ self.send_event(MediaSessionEvent::SetMetadata(
+ metadata.as_ref().unwrap().clone(),
+ ));
+ }
+}
+
+impl MediaSessionMethods for MediaSession {
+ /// https://w3c.github.io/mediasession/#dom-mediasession-metadata
+ fn GetMetadata(&self) -> Option<DomRoot<MediaMetadata>> {
+ if let Some(ref metadata) = *self.metadata.borrow() {
+ let mut init = MediaMetadataInit::empty();
+ init.title = DOMString::from_string(metadata.title.clone());
+ init.artist = DOMString::from_string(metadata.artist.clone());
+ init.album = DOMString::from_string(metadata.album.clone());
+ let global = self.global();
+ Some(MediaMetadata::new(&global.as_window(), &init))
+ } else {
+ None
+ }
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediasession-metadata
+ fn SetMetadata(&self, metadata: Option<&MediaMetadata>) {
+ if let Some(ref metadata) = metadata {
+ metadata.set_session(self);
+ }
+
+ let global = self.global();
+ let window = global.as_window();
+ let _metadata = match metadata {
+ Some(m) => {
+ let title = if m.Title().is_empty() {
+ window.get_url().into_string()
+ } else {
+ m.Title().into()
+ };
+ EmbedderMediaMetadata {
+ title,
+ artist: m.Artist().into(),
+ album: m.Album().into(),
+ }
+ },
+ None => EmbedderMediaMetadata::new(window.get_url().into_string()),
+ };
+
+ *self.metadata.borrow_mut() = Some(_metadata.clone());
+
+ self.send_event(MediaSessionEvent::SetMetadata(_metadata));
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate
+ fn PlaybackState(&self) -> MediaSessionPlaybackState {
+ *self.playback_state.borrow()
+ }
+
+ /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate
+ fn SetPlaybackState(&self, state: MediaSessionPlaybackState) {
+ *self.playback_state.borrow_mut() = state;
+ }
+
+ /// https://w3c.github.io/mediasession/#update-action-handler-algorithm
+ fn SetActionHandler(
+ &self,
+ action: MediaSessionAction,
+ handler: Option<Rc<MediaSessionActionHandler>>,
+ ) {
+ match handler {
+ Some(handler) => self
+ .action_handlers
+ .borrow_mut()
+ .insert(action.into(), handler.clone()),
+ None => self.action_handlers.borrow_mut().remove(&action.into()),
+ };
+ }
+}
+
+impl From<MediaSessionAction> for MediaSessionActionType {
+ fn from(action: MediaSessionAction) -> MediaSessionActionType {
+ match action {
+ MediaSessionAction::Play => MediaSessionActionType::Play,
+ MediaSessionAction::Pause => MediaSessionActionType::Pause,
+ MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward,
+ MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward,
+ MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack,
+ MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack,
+ MediaSessionAction::Skipad => MediaSessionActionType::SkipAd,
+ MediaSessionAction::Stop => MediaSessionActionType::Stop,
+ MediaSessionAction::Seekto => MediaSessionActionType::SeekTo,
+ }
+ }
+}
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index fbc4d457482..ef562931bee 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -398,8 +398,10 @@ pub mod mediaelementaudiosourcenode;
pub mod mediaerror;
pub mod mediafragmentparser;
pub mod medialist;
+pub mod mediametadata;
pub mod mediaquerylist;
pub mod mediaquerylistevent;
+pub mod mediasession;
pub mod mediastream;
pub mod mediastreamtrack;
pub mod messagechannel;
diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs
index 8a0f6a21d98..127883dd956 100644
--- a/components/script/dom/navigator.rs
+++ b/components/script/dom/navigator.rs
@@ -12,6 +12,7 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::bluetooth::Bluetooth;
use crate::dom::gamepadlist::GamepadList;
use crate::dom::mediadevices::MediaDevices;
+use crate::dom::mediasession::MediaSession;
use crate::dom::mimetypearray::MimeTypeArray;
use crate::dom::navigatorinfo;
use crate::dom::permissions::Permissions;
@@ -34,6 +35,7 @@ pub struct Navigator {
mediadevices: MutNullableDom<MediaDevices>,
gamepads: MutNullableDom<GamepadList>,
permissions: MutNullableDom<Permissions>,
+ mediasession: MutNullableDom<MediaSession>,
}
impl Navigator {
@@ -48,6 +50,7 @@ impl Navigator {
mediadevices: Default::default(),
gamepads: Default::default(),
permissions: Default::default(),
+ mediasession: Default::default(),
}
}
@@ -186,4 +189,20 @@ impl NavigatorMethods for Navigator {
self.mediadevices
.or_init(|| MediaDevices::new(&self.global()))
}
+
+ /// https://w3c.github.io/mediasession/#dom-navigator-mediasession
+ fn MediaSession(&self) -> DomRoot<MediaSession> {
+ self.mediasession.or_init(|| {
+ // There is a single MediaSession instance per Pipeline
+ // and only one active MediaSession globally.
+ //
+ // MediaSession creation can happen in two cases:
+ //
+ // - If content gets `navigator.mediaSession`
+ // - If a media instance (HTMLMediaElement so far) starts playing media.
+ let global = self.global();
+ let window = global.as_window();
+ MediaSession::new(window)
+ })
+ }
}
diff --git a/components/script/dom/webidls/MediaMetadata.webidl b/components/script/dom/webidls/MediaMetadata.webidl
new file mode 100644
index 00000000000..495aeef8e35
--- /dev/null
+++ b/components/script/dom/webidls/MediaMetadata.webidl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+/*
+ * The origin of this IDL file is
+ * https://w3c.github.io/mediasession/#mediametadata
+ */
+
+dictionary MediaImage {
+ required USVString src;
+ DOMString sizes = "";
+ DOMString type = "";
+};
+
+[Exposed=Window]
+interface MediaMetadata {
+ [Throws] constructor(optional MediaMetadataInit init = {});
+ attribute DOMString title;
+ attribute DOMString artist;
+ attribute DOMString album;
+ // TODO: https://github.com/servo/servo/issues/10072
+ // attribute FrozenArray<MediaImage> artwork;
+};
+
+dictionary MediaMetadataInit {
+ DOMString title = "";
+ DOMString artist = "";
+ DOMString album = "";
+ sequence<MediaImage> artwork = [];
+};
diff --git a/components/script/dom/webidls/MediaSession.webidl b/components/script/dom/webidls/MediaSession.webidl
new file mode 100644
index 00000000000..12b3fe062ba
--- /dev/null
+++ b/components/script/dom/webidls/MediaSession.webidl
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+/*
+ * The origin of this IDL file is
+ * https://w3c.github.io/mediasession/#mediasession
+ */
+
+[Exposed=Window]
+partial interface Navigator {
+ [SameObject] readonly attribute MediaSession mediaSession;
+};
+
+enum MediaSessionPlaybackState {
+ "none",
+ "paused",
+ "playing"
+};
+
+enum MediaSessionAction {
+ "play",
+ "pause",
+ "seekbackward",
+ "seekforward",
+ "previoustrack",
+ "nexttrack",
+ "skipad",
+ "stop",
+ "seekto"
+};
+
+dictionary MediaSessionActionDetails {
+ required MediaSessionAction action;
+};
+
+dictionary MediaSessionSeekActionDetails : MediaSessionActionDetails {
+ double? seekOffset;
+};
+
+dictionary MediaSessionSeekToActionDetails : MediaSessionActionDetails {
+ required double seekTime;
+ boolean? fastSeek;
+};
+
+callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/);
+
+[Exposed=Window]
+interface MediaSession {
+ attribute MediaMetadata? metadata;
+
+ attribute MediaSessionPlaybackState playbackState;
+
+ void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler);
+
+ //void setPositionState(optional MediaPositionState? state);
+};
+