mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Auto merge of #18582 - servo:media, r=emilio
Improve HTMLMediaElement <!-- Reviewable:start --> This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/18582) <!-- Reviewable:end -->
This commit is contained in:
commit
f3214372bf
10 changed files with 277 additions and 137 deletions
|
@ -39,6 +39,7 @@ use cssparser::RGBA;
|
||||||
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
|
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
|
||||||
use dom::abstractworker::SharedRt;
|
use dom::abstractworker::SharedRt;
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
|
use dom::bindings::error::Error;
|
||||||
use dom::bindings::js::{JS, Root};
|
use dom::bindings::js::{JS, Root};
|
||||||
use dom::bindings::refcounted::{Trusted, TrustedPromise};
|
use dom::bindings::refcounted::{Trusted, TrustedPromise};
|
||||||
use dom::bindings::reflector::{DomObject, Reflector};
|
use dom::bindings::reflector::{DomObject, Reflector};
|
||||||
|
@ -337,6 +338,7 @@ unsafe impl<A: JSTraceable, B: JSTraceable, C: JSTraceable> JSTraceable for (A,
|
||||||
unsafe_no_jsmanaged_fields!(bool, f32, f64, String, AtomicBool, AtomicUsize, Uuid, char);
|
unsafe_no_jsmanaged_fields!(bool, f32, f64, String, AtomicBool, AtomicUsize, Uuid, char);
|
||||||
unsafe_no_jsmanaged_fields!(usize, u8, u16, u32, u64);
|
unsafe_no_jsmanaged_fields!(usize, u8, u16, u32, u64);
|
||||||
unsafe_no_jsmanaged_fields!(isize, i8, i16, i32, i64);
|
unsafe_no_jsmanaged_fields!(isize, i8, i16, i32, i64);
|
||||||
|
unsafe_no_jsmanaged_fields!(Error);
|
||||||
unsafe_no_jsmanaged_fields!(ServoUrl, ImmutableOrigin, MutableOrigin);
|
unsafe_no_jsmanaged_fields!(ServoUrl, ImmutableOrigin, MutableOrigin);
|
||||||
unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCache, PendingImageId);
|
unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCache, PendingImageId);
|
||||||
unsafe_no_jsmanaged_fields!(Metadata);
|
unsafe_no_jsmanaged_fields!(Metadata);
|
||||||
|
|
|
@ -14,6 +14,7 @@ use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*;
|
||||||
use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
|
use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
|
||||||
use dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId};
|
use dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId};
|
||||||
use dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId};
|
use dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId};
|
||||||
|
use dom::bindings::error::{Error, ErrorResult};
|
||||||
use dom::bindings::inheritance::Castable;
|
use dom::bindings::inheritance::Castable;
|
||||||
use dom::bindings::js::{MutNullableJS, Root};
|
use dom::bindings::js::{MutNullableJS, Root};
|
||||||
use dom::bindings::refcounted::Trusted;
|
use dom::bindings::refcounted::Trusted;
|
||||||
|
@ -26,6 +27,7 @@ use dom::htmlelement::HTMLElement;
|
||||||
use dom::htmlsourceelement::HTMLSourceElement;
|
use dom::htmlsourceelement::HTMLSourceElement;
|
||||||
use dom::mediaerror::MediaError;
|
use dom::mediaerror::MediaError;
|
||||||
use dom::node::{window_from_node, document_from_node, Node, UnbindContext};
|
use dom::node::{window_from_node, document_from_node, Node, UnbindContext};
|
||||||
|
use dom::promise::Promise;
|
||||||
use dom::virtualmethods::VirtualMethods;
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
@ -39,22 +41,25 @@ use network_listener::{NetworkListener, PreInvoke};
|
||||||
use script_thread::ScriptThread;
|
use script_thread::ScriptThread;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::mem;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use task_source::TaskSource;
|
use task_source::TaskSource;
|
||||||
use time::{self, Timespec, Duration};
|
use time::{self, Timespec, Duration};
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
|
// FIXME(nox): A lot of tasks queued for this element should probably be in the
|
||||||
|
// media element event task source.
|
||||||
pub struct HTMLMediaElement {
|
pub struct HTMLMediaElement {
|
||||||
htmlelement: HTMLElement,
|
htmlelement: HTMLElement,
|
||||||
/// https://html.spec.whatwg.org/multipage/#dom-media-networkstate
|
/// https://html.spec.whatwg.org/multipage/#dom-media-networkstate
|
||||||
// FIXME(nox): Use an enum.
|
|
||||||
network_state: Cell<NetworkState>,
|
network_state: Cell<NetworkState>,
|
||||||
/// https://html.spec.whatwg.org/multipage/#dom-media-readystate
|
/// https://html.spec.whatwg.org/multipage/#dom-media-readystate
|
||||||
// FIXME(nox): Use an enum.
|
|
||||||
ready_state: Cell<ReadyState>,
|
ready_state: Cell<ReadyState>,
|
||||||
/// https://html.spec.whatwg.org/multipage/#dom-media-currentsrc
|
/// https://html.spec.whatwg.org/multipage/#dom-media-currentsrc
|
||||||
current_src: DOMRefCell<String>,
|
current_src: DOMRefCell<String>,
|
||||||
// FIXME(nox): Document this one, I have no idea what it is used for.
|
/// Incremented whenever tasks associated with this element are cancelled.
|
||||||
generation_id: Cell<u32>,
|
generation_id: Cell<u32>,
|
||||||
/// https://html.spec.whatwg.org/multipage/#fire-loadeddata
|
/// https://html.spec.whatwg.org/multipage/#fire-loadeddata
|
||||||
///
|
///
|
||||||
|
@ -66,6 +71,12 @@ pub struct HTMLMediaElement {
|
||||||
paused: Cell<bool>,
|
paused: Cell<bool>,
|
||||||
/// https://html.spec.whatwg.org/multipage/#attr-media-autoplay
|
/// https://html.spec.whatwg.org/multipage/#attr-media-autoplay
|
||||||
autoplaying: Cell<bool>,
|
autoplaying: Cell<bool>,
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises
|
||||||
|
#[ignore_heap_size_of = "promises are hard"]
|
||||||
|
pending_play_promises: DOMRefCell<Vec<Rc<Promise>>>,
|
||||||
|
/// Play promises which are soon to be fulfilled by a queued task.
|
||||||
|
#[ignore_heap_size_of = "promises are hard"]
|
||||||
|
in_flight_play_promises_queue: DOMRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
|
||||||
/// The details of the video currently related to this media element.
|
/// The details of the video currently related to this media element.
|
||||||
// FIXME(nox): Why isn't this in HTMLVideoElement?
|
// FIXME(nox): Why isn't this in HTMLVideoElement?
|
||||||
video: DOMRefCell<Option<VideoMedia>>,
|
video: DOMRefCell<Option<VideoMedia>>,
|
||||||
|
@ -74,7 +85,7 @@ pub struct HTMLMediaElement {
|
||||||
/// https://html.spec.whatwg.org/multipage/#dom-media-networkstate
|
/// https://html.spec.whatwg.org/multipage/#dom-media-networkstate
|
||||||
#[derive(Clone, Copy, HeapSizeOf, JSTraceable, PartialEq)]
|
#[derive(Clone, Copy, HeapSizeOf, JSTraceable, PartialEq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
enum NetworkState {
|
pub enum NetworkState {
|
||||||
Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
|
Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
|
||||||
Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
|
Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
|
||||||
Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
|
Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
|
||||||
|
@ -120,6 +131,8 @@ impl HTMLMediaElement {
|
||||||
paused: Cell::new(true),
|
paused: Cell::new(true),
|
||||||
// FIXME(nox): Why is this initialised to true?
|
// FIXME(nox): Why is this initialised to true?
|
||||||
autoplaying: Cell::new(true),
|
autoplaying: Cell::new(true),
|
||||||
|
pending_play_promises: Default::default(),
|
||||||
|
in_flight_play_promises_queue: Default::default(),
|
||||||
video: DOMRefCell::new(None),
|
video: DOMRefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +148,87 @@ impl HTMLMediaElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#dom-media-play
|
||||||
|
// FIXME(nox): Move this back to HTMLMediaElementMethods::Play once
|
||||||
|
// Rc<Promise> doesn't require #[allow(unrooted_must_root)] anymore.
|
||||||
|
fn play(&self, promise: &Rc<Promise>) {
|
||||||
|
// Step 1.
|
||||||
|
// FIXME(nox): Reject promise if not allowed to play.
|
||||||
|
|
||||||
|
// Step 2.
|
||||||
|
if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
|
||||||
|
promise.reject_error(Error::NotSupported);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3.
|
||||||
|
self.push_pending_play_promise(promise);
|
||||||
|
|
||||||
|
// Step 4.
|
||||||
|
if self.network_state.get() == NetworkState::Empty {
|
||||||
|
self.invoke_resource_selection_algorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5.
|
||||||
|
// FIXME(nox): Seek to earliest possible position if playback has ended
|
||||||
|
// and direction of playback is forwards.
|
||||||
|
|
||||||
|
let state = self.ready_state.get();
|
||||||
|
|
||||||
|
let window = window_from_node(self);
|
||||||
|
let task_source = window.dom_manipulation_task_source();
|
||||||
|
if self.Paused() {
|
||||||
|
// Step 6.1.
|
||||||
|
self.paused.set(false);
|
||||||
|
|
||||||
|
// Step 6.2.
|
||||||
|
// FIXME(nox): Set show poster flag to false and run time marches on
|
||||||
|
// steps if show poster flag is true.
|
||||||
|
|
||||||
|
// Step 6.3.
|
||||||
|
task_source.queue_simple_event(self.upcast(), atom!("play"), &window);
|
||||||
|
|
||||||
|
// Step 6.4.
|
||||||
|
match state {
|
||||||
|
ReadyState::HaveNothing |
|
||||||
|
ReadyState::HaveMetadata |
|
||||||
|
ReadyState::HaveCurrentData => {
|
||||||
|
task_source.queue_simple_event(
|
||||||
|
self.upcast(),
|
||||||
|
atom!("waiting"),
|
||||||
|
&window,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ReadyState::HaveFutureData |
|
||||||
|
ReadyState::HaveEnoughData => {
|
||||||
|
self.notify_about_playing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
|
||||||
|
// Step 7.
|
||||||
|
self.take_pending_play_promises(Ok(()));
|
||||||
|
let this = Trusted::new(self);
|
||||||
|
let generation_id = self.generation_id.get();
|
||||||
|
task_source.queue(
|
||||||
|
task!(resolve_pending_play_promises: move || {
|
||||||
|
let this = this.root();
|
||||||
|
if generation_id != this.generation_id.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fulfill_in_flight_play_promises(|| ());
|
||||||
|
}),
|
||||||
|
window.upcast(),
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 8.
|
||||||
|
self.autoplaying.set(false);
|
||||||
|
|
||||||
|
// Step 9.
|
||||||
|
// Not applicable here, the promise is returned from Play.
|
||||||
|
}
|
||||||
|
|
||||||
/// https://html.spec.whatwg.org/multipage/#internal-pause-steps
|
/// https://html.spec.whatwg.org/multipage/#internal-pause-steps
|
||||||
fn internal_pause_steps(&self) {
|
fn internal_pause_steps(&self) {
|
||||||
// Step 1.
|
// Step 1.
|
||||||
|
@ -146,26 +240,32 @@ impl HTMLMediaElement {
|
||||||
self.paused.set(true);
|
self.paused.set(true);
|
||||||
|
|
||||||
// Step 2.2.
|
// Step 2.2.
|
||||||
// FIXME(nox): Take pending play promises and let promises be the
|
self.take_pending_play_promises(Err(Error::Abort));
|
||||||
// result.
|
|
||||||
|
|
||||||
// Step 2.3.
|
// Step 2.3.
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
let target = Trusted::new(self.upcast::<EventTarget>());
|
let this = Trusted::new(self);
|
||||||
|
let generation_id = self.generation_id.get();
|
||||||
// FIXME(nox): Why are errors silenced here?
|
// FIXME(nox): Why are errors silenced here?
|
||||||
|
// FIXME(nox): Media element event task source should be used here.
|
||||||
let _ = window.dom_manipulation_task_source().queue(
|
let _ = window.dom_manipulation_task_source().queue(
|
||||||
task!(internal_pause_steps: move || {
|
task!(internal_pause_steps: move || {
|
||||||
let target = target.root();
|
let this = this.root();
|
||||||
|
if generation_id != this.generation_id.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fulfill_in_flight_play_promises(|| {
|
||||||
// Step 2.3.1.
|
// Step 2.3.1.
|
||||||
target.fire_event(atom!("timeupdate"));
|
this.upcast::<EventTarget>().fire_event(atom!("timeupdate"));
|
||||||
|
|
||||||
// Step 2.3.2.
|
// Step 2.3.2.
|
||||||
target.fire_event(atom!("pause"));
|
this.upcast::<EventTarget>().fire_event(atom!("pause"));
|
||||||
|
|
||||||
// Step 2.3.3.
|
// Step 2.3.3.
|
||||||
// FIXME(nox): Reject pending play promises with promises
|
// Done after running this closure in
|
||||||
// and an "AbortError" DOMException.
|
// `fulfill_in_flight_play_promises`.
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
window.upcast(),
|
window.upcast(),
|
||||||
);
|
);
|
||||||
|
@ -179,21 +279,30 @@ impl HTMLMediaElement {
|
||||||
// https://html.spec.whatwg.org/multipage/#notify-about-playing
|
// https://html.spec.whatwg.org/multipage/#notify-about-playing
|
||||||
fn notify_about_playing(&self) {
|
fn notify_about_playing(&self) {
|
||||||
// Step 1.
|
// Step 1.
|
||||||
// TODO(nox): Take pending play promises and let promises be the result.
|
self.take_pending_play_promises(Ok(()));
|
||||||
|
|
||||||
// Step 2.
|
// Step 2.
|
||||||
let target = Trusted::new(self.upcast::<EventTarget>());
|
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
|
let this = Trusted::new(self);
|
||||||
|
let generation_id = self.generation_id.get();
|
||||||
// FIXME(nox): Why are errors silenced here?
|
// FIXME(nox): Why are errors silenced here?
|
||||||
|
// FIXME(nox): Media element event task source should be used here.
|
||||||
let _ = window.dom_manipulation_task_source().queue(
|
let _ = window.dom_manipulation_task_source().queue(
|
||||||
task!(notify_about_playing: move || {
|
task!(notify_about_playing: move || {
|
||||||
let target = target.root();
|
let this = this.root();
|
||||||
|
if generation_id != this.generation_id.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fulfill_in_flight_play_promises(|| {
|
||||||
// Step 2.1.
|
// Step 2.1.
|
||||||
target.fire_event(atom!("playing"));
|
this.upcast::<EventTarget>().fire_event(atom!("playing"));
|
||||||
|
|
||||||
// Step 2.2.
|
// Step 2.2.
|
||||||
// FIXME(nox): Resolve pending play promises with promises.
|
// Done after running this closure in
|
||||||
|
// `fulfill_in_flight_play_promises`.
|
||||||
|
});
|
||||||
|
|
||||||
}),
|
}),
|
||||||
window.upcast(),
|
window.upcast(),
|
||||||
);
|
);
|
||||||
|
@ -291,9 +400,6 @@ impl HTMLMediaElement {
|
||||||
&window,
|
&window,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Step 2: Media controller.
|
|
||||||
// FIXME(nox): There is no step 2 in the spec.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm
|
// https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm
|
||||||
|
@ -327,7 +433,6 @@ impl HTMLMediaElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm
|
// https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm
|
||||||
// FIXME(nox): Why does this need to be passed the base URL?
|
|
||||||
fn resource_selection_algorithm_sync(&self, base_url: ServoUrl) {
|
fn resource_selection_algorithm_sync(&self, base_url: ServoUrl) {
|
||||||
// Step 5.
|
// Step 5.
|
||||||
// FIXME(nox): Maybe populate the list of pending text tracks.
|
// FIXME(nox): Maybe populate the list of pending text tracks.
|
||||||
|
@ -338,12 +443,23 @@ impl HTMLMediaElement {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
Object,
|
Object,
|
||||||
Attribute(String),
|
Attribute(String),
|
||||||
// FIXME(nox): Support source element child.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
Children(Root<HTMLSourceElement>),
|
Children(Root<HTMLSourceElement>),
|
||||||
}
|
}
|
||||||
let mode = if let Some(attr) = self.upcast::<Element>().get_attribute(&ns!(), &local_name!("src")) {
|
fn mode(media: &HTMLMediaElement) -> Option<Mode> {
|
||||||
Mode::Attribute(attr.Value().into())
|
if let Some(attr) = media.upcast::<Element>().get_attribute(&ns!(), &local_name!("src")) {
|
||||||
|
return Some(Mode::Attribute(attr.Value().into()));
|
||||||
|
}
|
||||||
|
let source_child_element = media.upcast::<Node>()
|
||||||
|
.children()
|
||||||
|
.filter_map(Root::downcast::<HTMLSourceElement>)
|
||||||
|
.next();
|
||||||
|
if let Some(element) = source_child_element {
|
||||||
|
return Some(Mode::Children(element));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
let mode = if let Some(mode) = mode(self) {
|
||||||
|
mode
|
||||||
} else {
|
} else {
|
||||||
self.network_state.set(NetworkState::Empty);
|
self.network_state.set(NetworkState::Empty);
|
||||||
return;
|
return;
|
||||||
|
@ -487,17 +603,24 @@ impl HTMLMediaElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queues the [dedicated media source failure steps][steps].
|
/// Queues a task to run the [dedicated media source failure steps][steps].
|
||||||
///
|
///
|
||||||
/// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps
|
/// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps
|
||||||
fn queue_dedicated_media_source_failure_steps(&self) {
|
fn queue_dedicated_media_source_failure_steps(&self) {
|
||||||
let this = Trusted::new(self);
|
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
|
let this = Trusted::new(self);
|
||||||
|
let generation_id = self.generation_id.get();
|
||||||
|
self.take_pending_play_promises(Err(Error::NotSupported));
|
||||||
// FIXME(nox): Why are errors silenced here?
|
// FIXME(nox): Why are errors silenced here?
|
||||||
|
// FIXME(nox): Media element event task source should be used here.
|
||||||
let _ = window.dom_manipulation_task_source().queue(
|
let _ = window.dom_manipulation_task_source().queue(
|
||||||
task!(dedicated_media_source_failure_steps: move || {
|
task!(dedicated_media_source_failure_steps: move || {
|
||||||
let this = this.root();
|
let this = this.root();
|
||||||
|
if generation_id != this.generation_id.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fulfill_in_flight_play_promises(|| {
|
||||||
// Step 1.
|
// Step 1.
|
||||||
this.error.set(Some(&*MediaError::new(
|
this.error.set(Some(&*MediaError::new(
|
||||||
&window_from_node(&*this),
|
&window_from_node(&*this),
|
||||||
|
@ -517,7 +640,9 @@ impl HTMLMediaElement {
|
||||||
this.upcast::<EventTarget>().fire_event(atom!("error"));
|
this.upcast::<EventTarget>().fire_event(atom!("error"));
|
||||||
|
|
||||||
// Step 6.
|
// Step 6.
|
||||||
// FIXME(nox): Reject pending play promises.
|
// Done after running this closure in
|
||||||
|
// `fulfill_in_flight_play_promises`.
|
||||||
|
});
|
||||||
|
|
||||||
// Step 7.
|
// Step 7.
|
||||||
// FIXME(nox): Set the delaying-the-load-event flag to false.
|
// FIXME(nox): Set the delaying-the-load-event flag to false.
|
||||||
|
@ -537,9 +662,10 @@ impl HTMLMediaElement {
|
||||||
// resource selection algorithm.
|
// resource selection algorithm.
|
||||||
|
|
||||||
// Steps 2-4.
|
// Steps 2-4.
|
||||||
// FIXME(nox): Cancel all tasks related to this element and resolve or
|
|
||||||
// reject all pending play promises.
|
|
||||||
self.generation_id.set(self.generation_id.get() + 1);
|
self.generation_id.set(self.generation_id.get() + 1);
|
||||||
|
while !self.in_flight_play_promises_queue.borrow().is_empty() {
|
||||||
|
self.fulfill_in_flight_play_promises(|| ());
|
||||||
|
}
|
||||||
|
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
let task_source = window.dom_manipulation_task_source();
|
let task_source = window.dom_manipulation_task_source();
|
||||||
|
@ -575,7 +701,8 @@ impl HTMLMediaElement {
|
||||||
self.paused.set(true);
|
self.paused.set(true);
|
||||||
|
|
||||||
// Step 6.6.2.
|
// Step 6.6.2.
|
||||||
// FIXME(nox): Reject pending play promises.
|
self.take_pending_play_promises(Err(Error::Abort));
|
||||||
|
self.fulfill_in_flight_play_promises(|| ());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6.7.
|
// Step 6.7.
|
||||||
|
@ -605,6 +732,73 @@ impl HTMLMediaElement {
|
||||||
// Step 10.
|
// Step 10.
|
||||||
// FIXME(nox): Stop playback of any previously running media resource.
|
// FIXME(nox): Stop playback of any previously running media resource.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a promise to the list of pending play promises.
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
|
||||||
|
self.pending_play_promises.borrow_mut().push(promise.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the pending play promises.
|
||||||
|
///
|
||||||
|
/// The result with which these promises will be fulfilled is passed here
|
||||||
|
/// and this method returns nothing because we actually just move the
|
||||||
|
/// current list of pending play promises to the
|
||||||
|
/// `in_flight_play_promises_queue` field.
|
||||||
|
///
|
||||||
|
/// Each call to this method must be followed by a call to
|
||||||
|
/// `fulfill_in_flight_play_promises`, to actually fulfill the promises
|
||||||
|
/// which were taken and moved to the in-flight queue.
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
fn take_pending_play_promises(&self, result: ErrorResult) {
|
||||||
|
let pending_play_promises = mem::replace(
|
||||||
|
&mut *self.pending_play_promises.borrow_mut(),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
self.in_flight_play_promises_queue.borrow_mut().push_back((
|
||||||
|
pending_play_promises.into(),
|
||||||
|
result,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fulfills the next in-flight play promises queue after running a closure.
|
||||||
|
///
|
||||||
|
/// See the comment on `take_pending_play_promises` for why this method
|
||||||
|
/// does not take a list of promises to fulfill. Callers cannot just pop
|
||||||
|
/// the front list off of `in_flight_play_promises_queue` and later fulfill
|
||||||
|
/// the promises because that would mean putting
|
||||||
|
/// `#[allow(unrooted_must_root)]` on even more functions, potentially
|
||||||
|
/// hiding actual safety bugs.
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
fn fulfill_in_flight_play_promises<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(),
|
||||||
|
{
|
||||||
|
let (promises, result) = self.in_flight_play_promises_queue
|
||||||
|
.borrow_mut()
|
||||||
|
.pop_front()
|
||||||
|
.expect("there should be at least one list of in flight play promises");
|
||||||
|
f();
|
||||||
|
for promise in &*promises {
|
||||||
|
match result {
|
||||||
|
Ok(ref value) => promise.resolve_native(value),
|
||||||
|
Err(ref error) => promise.reject_error(error.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles insertion of `source` children.
|
||||||
|
///
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted
|
||||||
|
pub fn handle_source_child_insertion(&self) {
|
||||||
|
if self.upcast::<Element>().has_attribute(&local_name!("src")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.network_state.get() != NetworkState::Empty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.media_element_load_algorithm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HTMLMediaElementMethods for HTMLMediaElement {
|
impl HTMLMediaElementMethods for HTMLMediaElement {
|
||||||
|
@ -661,70 +855,11 @@ impl HTMLMediaElementMethods for HTMLMediaElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-media-play
|
// https://html.spec.whatwg.org/multipage/#dom-media-play
|
||||||
// FIXME(nox): This should return a promise.
|
#[allow(unrooted_must_root)]
|
||||||
fn Play(&self) {
|
fn Play(&self) -> Rc<Promise> {
|
||||||
// Step 1.
|
let promise = Promise::new(&self.global());
|
||||||
// FIXME(nox): Return a rejected promise if not allowed to play.
|
self.play(&promise);
|
||||||
|
promise
|
||||||
// Step 2.
|
|
||||||
if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
|
|
||||||
// FIXME(nox): This should return a rejected promise.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3.
|
|
||||||
// Create promise and add it to list of pending play promises.
|
|
||||||
|
|
||||||
// Step 4.
|
|
||||||
if self.network_state.get() == NetworkState::Empty {
|
|
||||||
self.invoke_resource_selection_algorithm();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5.
|
|
||||||
// FIXME(nox): Seek to earliest possible position if playback has ended
|
|
||||||
// and direction of playback is forwards.
|
|
||||||
|
|
||||||
let state = self.ready_state.get();
|
|
||||||
|
|
||||||
if self.Paused() {
|
|
||||||
// Step 6.1.
|
|
||||||
self.paused.set(false);
|
|
||||||
|
|
||||||
// Step 6.2.
|
|
||||||
// FIXME(nox): Set show poster flag to false and run time marches on
|
|
||||||
// steps if show poster flag is true.
|
|
||||||
|
|
||||||
// Step 6.3.
|
|
||||||
let window = window_from_node(self);
|
|
||||||
let task_source = window.dom_manipulation_task_source();
|
|
||||||
task_source.queue_simple_event(self.upcast(), atom!("play"), &window);
|
|
||||||
|
|
||||||
// Step 7.4.
|
|
||||||
match state {
|
|
||||||
ReadyState::HaveNothing |
|
|
||||||
ReadyState::HaveMetadata |
|
|
||||||
ReadyState::HaveCurrentData => {
|
|
||||||
task_source.queue_simple_event(
|
|
||||||
self.upcast(),
|
|
||||||
atom!("waiting"),
|
|
||||||
&window,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ReadyState::HaveFutureData |
|
|
||||||
ReadyState::HaveEnoughData => {
|
|
||||||
self.notify_about_playing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
|
|
||||||
// Step 7.
|
|
||||||
// FIXME(nox): Queue a task to resolve pending play promises.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 8.
|
|
||||||
self.autoplaying.set(false);
|
|
||||||
|
|
||||||
// Step 9.
|
|
||||||
// FIXME(nox): Return promise created in step 3.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-media-pause
|
// https://html.spec.whatwg.org/multipage/#dom-media-pause
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use dom::bindings::codegen::Bindings::HTMLSourceElementBinding;
|
use dom::bindings::codegen::Bindings::HTMLSourceElementBinding;
|
||||||
|
use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
|
||||||
|
use dom::bindings::inheritance::Castable;
|
||||||
use dom::bindings::js::Root;
|
use dom::bindings::js::Root;
|
||||||
use dom::document::Document;
|
use dom::document::Document;
|
||||||
use dom::htmlelement::HTMLElement;
|
use dom::htmlelement::HTMLElement;
|
||||||
|
use dom::htmlmediaelement::HTMLMediaElement;
|
||||||
use dom::node::Node;
|
use dom::node::Node;
|
||||||
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
|
||||||
|
@ -34,3 +38,18 @@ impl HTMLSourceElement {
|
||||||
HTMLSourceElementBinding::Wrap)
|
HTMLSourceElementBinding::Wrap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VirtualMethods for HTMLSourceElement {
|
||||||
|
fn super_type(&self) -> Option<&VirtualMethods> {
|
||||||
|
Some(self.upcast::<HTMLElement>() as &VirtualMethods)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted
|
||||||
|
fn bind_to_tree(&self, tree_in_doc: bool) {
|
||||||
|
self.super_type().unwrap().bind_to_tree(tree_in_doc);
|
||||||
|
let parent = self.upcast::<Node>().GetParentNode().unwrap();
|
||||||
|
if let Some(media) = parent.downcast::<HTMLMediaElement>() {
|
||||||
|
media.handle_source_child_insertion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ use dom::htmloptionelement::HTMLOptionElement;
|
||||||
use dom::htmloutputelement::HTMLOutputElement;
|
use dom::htmloutputelement::HTMLOutputElement;
|
||||||
use dom::htmlscriptelement::HTMLScriptElement;
|
use dom::htmlscriptelement::HTMLScriptElement;
|
||||||
use dom::htmlselectelement::HTMLSelectElement;
|
use dom::htmlselectelement::HTMLSelectElement;
|
||||||
|
use dom::htmlsourceelement::HTMLSourceElement;
|
||||||
use dom::htmlstyleelement::HTMLStyleElement;
|
use dom::htmlstyleelement::HTMLStyleElement;
|
||||||
use dom::htmltablecellelement::HTMLTableCellElement;
|
use dom::htmltablecellelement::HTMLTableCellElement;
|
||||||
use dom::htmltableelement::HTMLTableElement;
|
use dom::htmltableelement::HTMLTableElement;
|
||||||
|
@ -231,6 +232,9 @@ pub fn vtable_for(node: &Node) -> &VirtualMethods {
|
||||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => {
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => {
|
||||||
node.downcast::<HTMLSelectElement>().unwrap() as &VirtualMethods
|
node.downcast::<HTMLSelectElement>().unwrap() as &VirtualMethods
|
||||||
}
|
}
|
||||||
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSourceElement)) => {
|
||||||
|
node.downcast::<HTMLSourceElement>().unwrap() as &VirtualMethods
|
||||||
|
}
|
||||||
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLStyleElement)) => {
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLStyleElement)) => {
|
||||||
node.downcast::<HTMLStyleElement>().unwrap() as &VirtualMethods
|
node.downcast::<HTMLStyleElement>().unwrap() as &VirtualMethods
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ interface HTMLMediaElement : HTMLElement {
|
||||||
attribute boolean autoplay;
|
attribute boolean autoplay;
|
||||||
// [CEReactions]
|
// [CEReactions]
|
||||||
// attribute boolean loop;
|
// attribute boolean loop;
|
||||||
void play();
|
Promise<void> play();
|
||||||
void pause();
|
void pause();
|
||||||
|
|
||||||
// media controller
|
// media controller
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
[load-removes-queued-error-event.html]
|
[load-removes-queued-error-event.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
expected: TIMEOUT
|
[source error event]
|
||||||
[video error event]
|
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[source error event]
|
|
||||||
expected: TIMEOUT
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
[resource-selection-candidate-insert-before.html]
|
[resource-selection-candidate-insert-before.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
expected: TIMEOUT
|
|
||||||
[inserting another source before the candidate]
|
[inserting another source before the candidate]
|
||||||
expected: TIMEOUT
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[resource-selection-invoke-insert-source-not-in-document.html]
|
|
||||||
type: testharness
|
|
||||||
[invoking resource selection by inserting <source> in video not in a document]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[resource-selection-invoke-insert-source.html]
|
|
||||||
type: testharness
|
|
||||||
[invoking resource selection by inserting <source>]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[resource-selection-remove-source.html]
|
|
||||||
type: testharness
|
|
||||||
[Changes to networkState when inserting and removing a <source>]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue