mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
Auto merge of #24885 - shnmorimoto:implement_mediasession_set_positon_state, r=ferjm
Implement mediasession set positon state <!-- Please describe your changes on the following line: --> fix #24808 > Bonus points if you want to tweak the existing UI by adding a progress bar, and the info about the current position and total duration. I haven't implemented this yet. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #24808 (GitHub issue number if applicable) <!-- Either: --> - [x] There are tests for these changes OR <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
commit
f31a88d85d
13 changed files with 183 additions and 34 deletions
|
@ -240,6 +240,24 @@ pub enum MediaSessionPlaybackState {
|
||||||
Paused,
|
Paused,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// https://w3c.github.io/mediasession/#dictdef-mediapositionstate
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct MediaPositionState {
|
||||||
|
pub duration: f64,
|
||||||
|
pub playback_rate: f64,
|
||||||
|
pub position: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaPositionState {
|
||||||
|
pub fn new(duration: f64, playback_rate: f64, position: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
duration,
|
||||||
|
playback_rate,
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Type of events sent from script to the embedder about the media session.
|
/// Type of events sent from script to the embedder about the media session.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum MediaSessionEvent {
|
pub enum MediaSessionEvent {
|
||||||
|
@ -247,4 +265,6 @@ pub enum MediaSessionEvent {
|
||||||
SetMetadata(MediaMetadata),
|
SetMetadata(MediaMetadata),
|
||||||
/// Indicates that the playback state has changed.
|
/// Indicates that the playback state has changed.
|
||||||
PlaybackStateChange(MediaSessionPlaybackState),
|
PlaybackStateChange(MediaSessionPlaybackState),
|
||||||
|
/// Indicates that the position state is set.
|
||||||
|
SetPositionState(MediaPositionState),
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ use crate::script_thread::ScriptThread;
|
||||||
use crate::task_source::TaskSource;
|
use crate::task_source::TaskSource;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use embedder_traits::resources::{self, Resource as EmbedderResource};
|
use embedder_traits::resources::{self, Resource as EmbedderResource};
|
||||||
use embedder_traits::{MediaSessionEvent, MediaSessionPlaybackState};
|
use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState};
|
||||||
use euclid::default::Size2D;
|
use euclid::default::Size2D;
|
||||||
use headers::{ContentLength, ContentRange, HeaderMapExt};
|
use headers::{ContentLength, ContentRange, HeaderMapExt};
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
@ -1780,6 +1780,15 @@ impl HTMLMediaElement {
|
||||||
.add(self.playback_position.get(), position);
|
.add(self.playback_position.get(), position);
|
||||||
self.playback_position.set(position);
|
self.playback_position.set(position);
|
||||||
self.time_marches_on();
|
self.time_marches_on();
|
||||||
|
let media_position_state =
|
||||||
|
MediaPositionState::new(self.duration.get(), self.playbackRate.get(), position);
|
||||||
|
debug!(
|
||||||
|
"Sending media session event set position state {:?}",
|
||||||
|
media_position_state
|
||||||
|
);
|
||||||
|
self.send_media_session_event(MediaSessionEvent::SetPositionState(
|
||||||
|
media_position_state,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
PlayerEvent::SeekData(p, ref seek_lock) => {
|
PlayerEvent::SeekData(p, ref seek_lock) => {
|
||||||
self.fetch_request(Some(p), Some(seek_lock.clone()));
|
self.fetch_request(Some(p), Some(seek_lock.clone()));
|
||||||
|
@ -1925,6 +1934,18 @@ impl HTMLMediaElement {
|
||||||
|
|
||||||
media_session.send_event(event);
|
media_session.send_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_duration(&self, duration: f64) {
|
||||||
|
self.duration.set(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self) {
|
||||||
|
if let Some(ref player) = *self.player.borrow() {
|
||||||
|
if let Err(e) = player.lock().unwrap().stop() {
|
||||||
|
eprintln!("Could not stop player {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
|
// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
|
||||||
|
|
|
@ -9,10 +9,13 @@ use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaE
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit;
|
use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods;
|
use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding;
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding;
|
||||||
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaPositionState;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionAction;
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionAction;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionActionHandler;
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionActionHandler;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionMethods;
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionPlaybackState;
|
use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionPlaybackState;
|
||||||
|
use crate::dom::bindings::error::{Error, Fallible};
|
||||||
|
use crate::dom::bindings::num::Finite;
|
||||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||||
use crate::dom::bindings::str::DOMString;
|
use crate::dom::bindings::str::DOMString;
|
||||||
|
@ -194,6 +197,62 @@ impl MediaSessionMethods for MediaSession {
|
||||||
None => self.action_handlers.borrow_mut().remove(&action.into()),
|
None => self.action_handlers.borrow_mut().remove(&action.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// https://w3c.github.io/mediasession/#dom-mediasession-setpositionstate
|
||||||
|
fn SetPositionState(&self, state: &MediaPositionState) -> Fallible<()> {
|
||||||
|
// If the state is an empty dictionary then clear the position state.
|
||||||
|
if state.duration.is_none() && state.position.is_none() && state.playbackRate.is_none() {
|
||||||
|
if let Some(media_instance) = self.media_instance.get() {
|
||||||
|
media_instance.reset();
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the duration is not present or its value is null, throw a TypeError.
|
||||||
|
if state.duration.is_none() {
|
||||||
|
return Err(Error::Type(
|
||||||
|
"duration is not present or its value is null".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the duration is negative, throw a TypeError.
|
||||||
|
if let Some(state_duration) = state.duration {
|
||||||
|
if *state_duration < 0.0 {
|
||||||
|
return Err(Error::Type("duration is negative".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the position is negative or greater than duration, throw a TypeError.
|
||||||
|
if let Some(state_position) = state.position {
|
||||||
|
if *state_position < 0.0 {
|
||||||
|
return Err(Error::Type("position is negative".to_owned()));
|
||||||
|
}
|
||||||
|
if let Some(state_duration) = state.duration {
|
||||||
|
if *state_position > *state_duration {
|
||||||
|
return Err(Error::Type("position is greater than duration".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the playbackRate is zero throw a TypeError.
|
||||||
|
if let Some(state_playback_rate) = state.playbackRate {
|
||||||
|
if *state_playback_rate <= 0.0 {
|
||||||
|
return Err(Error::Type("playbackRate is zero".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the position state and last position updated time.
|
||||||
|
if let Some(media_instance) = self.media_instance.get() {
|
||||||
|
media_instance.set_duration(state.duration.map(|v| *v).unwrap());
|
||||||
|
// If the playbackRate is not present or its value is null, set it to 1.0.
|
||||||
|
let _ =
|
||||||
|
media_instance.SetPlaybackRate(state.playbackRate.unwrap_or(Finite::wrap(1.0)))?;
|
||||||
|
// If the position is not present or its value is null, set it to zero.
|
||||||
|
media_instance.SetCurrentTime(state.position.unwrap_or(Finite::wrap(0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MediaSessionAction> for MediaSessionActionType {
|
impl From<MediaSessionAction> for MediaSessionActionType {
|
||||||
|
|
|
@ -42,6 +42,12 @@ dictionary MediaSessionSeekToActionDetails : MediaSessionActionDetails {
|
||||||
boolean? fastSeek;
|
boolean? fastSeek;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dictionary MediaPositionState {
|
||||||
|
double duration;
|
||||||
|
double playbackRate;
|
||||||
|
double position;
|
||||||
|
};
|
||||||
|
|
||||||
callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/);
|
callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/);
|
||||||
|
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
|
@ -52,6 +58,5 @@ interface MediaSession {
|
||||||
|
|
||||||
void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler);
|
void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler);
|
||||||
|
|
||||||
//void setPositionState(optional MediaPositionState? state);
|
[Throws] void setPositionState(optional MediaPositionState state = {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -132,8 +132,10 @@ pub trait HostTrait {
|
||||||
fn set_clipboard_contents(&self, contents: String);
|
fn set_clipboard_contents(&self, contents: String);
|
||||||
/// Called when we get the media session metadata/
|
/// Called when we get the media session metadata/
|
||||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
||||||
/// Called when the media sessoin playback state changes.
|
/// Called when the media session playback state changes.
|
||||||
fn on_media_session_playback_state_change(&self, state: i32);
|
fn on_media_session_playback_state_change(&self, state: i32);
|
||||||
|
/// Called when the media session position state is set.
|
||||||
|
fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ServoGlue {
|
pub struct ServoGlue {
|
||||||
|
@ -594,6 +596,14 @@ impl ServoGlue {
|
||||||
.callbacks
|
.callbacks
|
||||||
.host_callbacks
|
.host_callbacks
|
||||||
.on_media_session_playback_state_change(state as i32),
|
.on_media_session_playback_state_change(state as i32),
|
||||||
|
MediaSessionEvent::SetPositionState(position_state) => self
|
||||||
|
.callbacks
|
||||||
|
.host_callbacks
|
||||||
|
.on_media_session_set_position_state(
|
||||||
|
position_state.duration,
|
||||||
|
position_state.position,
|
||||||
|
position_state.playback_rate,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
EmbedderMsg::Status(..) |
|
EmbedderMsg::Status(..) |
|
||||||
|
|
|
@ -219,6 +219,8 @@ pub struct CHostCallbacks {
|
||||||
pub on_media_session_metadata:
|
pub on_media_session_metadata:
|
||||||
extern "C" fn(title: *const c_char, album: *const c_char, artist: *const c_char),
|
extern "C" fn(title: *const c_char, album: *const c_char, artist: *const c_char),
|
||||||
pub on_media_session_playback_state_change: extern "C" fn(state: i32),
|
pub on_media_session_playback_state_change: extern "C" fn(state: i32),
|
||||||
|
pub on_media_session_set_position_state:
|
||||||
|
extern "C" fn(duration: f64, position: f64, playback_rate: f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Servo options
|
/// Servo options
|
||||||
|
@ -648,7 +650,7 @@ impl HostTrait for HostCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_load_started(&self) {
|
fn on_load_started(&self) {
|
||||||
debug!("on_load_ended");
|
debug!("on_load_started");
|
||||||
(self.0.on_load_started)();
|
(self.0.on_load_started)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,4 +729,17 @@ impl HostTrait for HostCallbacks {
|
||||||
debug!("on_media_session_playback_state_change {:?}", state);
|
debug!("on_media_session_playback_state_change {:?}", state);
|
||||||
(self.0.on_media_session_playback_state_change)(state);
|
(self.0.on_media_session_playback_state_change)(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_media_session_set_position_state(
|
||||||
|
&self,
|
||||||
|
duration: f64,
|
||||||
|
position: f64,
|
||||||
|
playback_rate: f64,
|
||||||
|
) {
|
||||||
|
debug!(
|
||||||
|
"on_media_session_set_position_state ({:?} {:?} {:?})",
|
||||||
|
duration, position, playback_rate
|
||||||
|
);
|
||||||
|
(self.0.on_media_session_set_position_state)(duration, position, playback_rate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -560,6 +560,30 @@ impl HostTrait for HostCallbacks {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_media_session_set_position_state(
|
||||||
|
&self,
|
||||||
|
duration: f64,
|
||||||
|
position: f64,
|
||||||
|
playback_rate: f64,
|
||||||
|
) {
|
||||||
|
info!(
|
||||||
|
"on_media_session_playback_state_change ({:?}, {:?}, {:?})",
|
||||||
|
duration, position, playback_rate
|
||||||
|
);
|
||||||
|
let env = self.jvm.get_env().unwrap();
|
||||||
|
let duration = JValue::Float(duration as jfloat);
|
||||||
|
let position = JValue::Float(position as jfloat);
|
||||||
|
let playback_rate = JValue::Float(playback_rate as jfloat);
|
||||||
|
|
||||||
|
env.call_method(
|
||||||
|
self.callbacks.as_obj(),
|
||||||
|
"onMediaSessionSetPositionState",
|
||||||
|
"(FFF)V",
|
||||||
|
&[duration, position, playback_rate],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_android_glue(env: &JNIEnv, activity: JObject) {
|
fn initialize_android_glue(env: &JNIEnv, activity: JObject) {
|
||||||
|
|
|
@ -255,4 +255,15 @@ public class MainActivity extends Activity implements Servo.Client {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMediaSessionSetPositionState(float duration, float position, float playbackRate) {
|
||||||
|
Log.d("onMediaSessionSetPositionState", duration + " " + position + " " + playbackRate);
|
||||||
|
if (mMediaSession == null) {
|
||||||
|
mMediaSession = new MediaSession(mServoView, this, getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMediaSession.setPositionState(duration, position, playbackRate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,4 +192,8 @@ public class MediaSession {
|
||||||
showMediaSessionControls();
|
showMediaSessionControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Not implemented
|
||||||
|
// see https://github.com/servo/servo/pull/24885#discussion_r352496117
|
||||||
|
public void setPositionState(float duration, float position, float playbackRate) {}
|
||||||
|
}
|
|
@ -115,6 +115,8 @@ public class JNIServo {
|
||||||
void onMediaSessionMetadata(String title, String artist, String album);
|
void onMediaSessionMetadata(String title, String artist, String album);
|
||||||
|
|
||||||
void onMediaSessionPlaybackStateChange(int state);
|
void onMediaSessionPlaybackStateChange(int state);
|
||||||
|
|
||||||
|
void onMediaSessionSetPositionState(float duration, float position, float playbackRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,6 +192,8 @@ public class Servo {
|
||||||
void onMediaSessionMetadata(String title, String artist, String album);
|
void onMediaSessionMetadata(String title, String artist, String album);
|
||||||
|
|
||||||
void onMediaSessionPlaybackStateChange(int state);
|
void onMediaSessionPlaybackStateChange(int state);
|
||||||
|
|
||||||
|
void onMediaSessionSetPositionState(float duration, float position, float playbackRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface RunCallback {
|
public interface RunCallback {
|
||||||
|
@ -285,5 +287,9 @@ public class Servo {
|
||||||
public void onMediaSessionPlaybackStateChange(int state) {
|
public void onMediaSessionPlaybackStateChange(int state) {
|
||||||
mRunCallback.inUIThread(() -> mClient.onMediaSessionPlaybackStateChange(state));
|
mRunCallback.inUIThread(() -> mClient.onMediaSessionPlaybackStateChange(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onMediaSessionSetPositionState(float duration, float position, float playbackRate) {
|
||||||
|
mRunCallback.inUIThread(() -> mClient.onMediaSessionSetPositionState(duration, position, playbackRate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
[idlharness.window.html]
|
[idlharness.window.html]
|
||||||
[MediaSession interface: calling setPositionState(MediaPositionState) on navigator.mediaSession with too few arguments must throw TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[MediaSession interface: navigator.mediaSession must inherit property "setPositionState(MediaPositionState)" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[MediaSession interface: operation setPositionState(MediaPositionState)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[MediaMetadata interface: attribute artwork]
|
[MediaMetadata interface: attribute artwork]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
[positionstate.html]
|
|
||||||
[Test setPositionState with a null value]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Test setPositionState with zero duration]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Test setPositionState with a valid value for forward playback]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Test setPositionState with optional position]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Test setPositionState with only duration]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Test setPositionState with optional playback rate]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue