From 6d207e2c7ca715a6187367c61e01d8d68ca7b8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Jim=C3=A9nez=20Moreno?= Date: Mon, 27 Nov 2017 19:15:40 +0100 Subject: [PATCH] Use GeckoMedia in MSE implementation --- Cargo.lock | 24 +- components/atoms/static_atoms.txt | 1 + components/script/Cargo.toml | 2 +- components/script/dom/bindings/trace.rs | 4 + components/script/dom/mediasource.rs | 402 ++++++++++---- components/script/dom/sourcebuffer.rs | 621 ++++++++++++++-------- components/script/dom/sourcebufferlist.rs | 116 +++- 7 files changed, 833 insertions(+), 337 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ecdaed45bc181..e247bf28e24ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,11 @@ name = "alloc-no-stdlib" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "android_ffi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "android_glue" version = "0.2.2" @@ -1030,6 +1035,22 @@ dependencies = [ "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gecko-media" +version = "0.1.0" +dependencies = [ + "android_ffi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bindgen 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "cubeb-pulse 0.0.2 (git+https://github.com/djg/cubeb-pulse-rs?branch=dev)", + "encoding_c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mp4parse_capi 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "geckoservo" version = "0.0.1" @@ -2568,7 +2589,7 @@ dependencies = [ "encoding_rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gecko-media 0.1.0 (git+https://github.com/servo/gecko-media.git)", + "gecko-media 0.1.0", "gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", "half 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "html5ever 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3748,6 +3769,7 @@ dependencies = [ "checksum adler32 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff33fe13a08dbce05bcefa2c68eea4844941437e33d6f808240b54d7157b9cd" "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" "checksum alloc-no-stdlib 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b21f6ad9c9957eb5d70c3dee16d31c092b3cab339628f821766b05e6833d72b8" +"checksum android_ffi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8cba15211ad61eee72471d5c9478bc9749549b2249080fb2433cebaa863b702a" "checksum android_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8289e9637439939cc92b1995b0972117905be88bc28116c86b64d6e589bcd38" "checksum android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec08bc5e100186b5223a24dcfe5655d1488aed9eafeb44fb9a0f67a4f53d0fc" "checksum angle 0.5.0 (git+https://github.com/servo/angle?branch=servo)" = "" diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 4510b94a039c2..0f2740a619c91 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -66,6 +66,7 @@ screen search select serif +sourceclose sourceended sourceopen statechange diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 704d07f39f63d..e193a263c61c7 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -24,7 +24,7 @@ phf_shared = "0.7.18" serde_json = "1.0" [target.'cfg(all(any(target_os = "macos", target_os = "linux"), not(any(target_arch = "arm", target_arch = "aarch64"))))'.dependencies] -gecko-media = {git = "https://github.com/servo/gecko-media.git"} +gecko-media = {path = "/Users/ferjm/dev/mozilla/gecko-media/gecko-media"} [target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies] tinyfiledialogs = "2.5.9" diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 5f1b38771c3ef..5b45332618700 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -50,6 +50,7 @@ use dom::document::PendingRestyle; use encoding_rs::Encoding; use euclid::{Transform2D, Transform3D, Point2D, Vector2D, Rect, TypedSize2D, TypedScale}; use euclid::Length as EuclidLength; +use gecko_media::{GeckoMediaSource, GeckoMediaSourceBuffer, GeckoMediaSourceBufferList}; use html5ever::{Prefix, LocalName, Namespace, QualName}; use html5ever::buffer_queue::BufferQueue; use html5ever::tendril::IncompleteUtf8; @@ -429,6 +430,9 @@ unsafe_no_jsmanaged_fields!(WebVRGamepadHand); unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); unsafe_no_jsmanaged_fields!(InteractiveMetrics); unsafe_no_jsmanaged_fields!(InteractiveWindow); +unsafe_no_jsmanaged_fields!(GeckoMediaSource); +unsafe_no_jsmanaged_fields!(GeckoMediaSourceBuffer); +unsafe_no_jsmanaged_fields!(GeckoMediaSourceBufferList); unsafe impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/mediasource.rs b/components/script/dom/mediasource.rs index 95d7ee096d278..aa68cec239377 100644 --- a/components/script/dom/mediasource.rs +++ b/components/script/dom/mediasource.rs @@ -22,25 +22,130 @@ use dom::sourcebufferlist::{ListMode, SourceBufferList}; use dom::timeranges::TimeRanges; use dom::window::Window; use dom_struct::dom_struct; +use gecko_media::{GeckoMedia, GeckoMediaSource}; +use gecko_media::{GeckoMediaSourceImpl, GeckoMediaTimeInterval}; use mime::Mime; use std::cell::{Cell, Ref}; use std::f64; +use std::ptr; +use std::rc::Rc; -/// A `MediaSource` DOM instance. -/// -/// https://w3c.github.io/media-source/#idl-def-mediasource -#[dom_struct] -pub struct MediaSource { - eventtarget: EventTarget, +// Arbitrary limit set by GeckoMedia. +static MAX_SOURCE_BUFFERS: usize = 12; + +#[derive(JSTraceable, MallocSizeOf)] +#[allow(unrooted_must_root)] +struct MediaSourceAttributes { + owner: MutNullableDom, source_buffers: DomRefCell>>, + /// https://w3c.github.io/media-source/#dom-mediasource-activesourcebuffers source_buffers_list: MutNullableDom, + /// https://w3c.github.io/media-source/#dom-mediasource-activesourcebuffers active_source_buffers_list: MutNullableDom, + /// https://w3c.github.io/media-source/#dom-readystate ready_state: Cell, + /// https://w3c.github.io/media-source/#dom-mediasource-duration duration: Cell, /// https://w3c.github.io/media-source/#live-seekable-range live_seekable_range: MutNullableDom, } +impl MediaSourceAttributes { + pub fn new() -> Self { + Self { + owner: Default::default(), + source_buffers: Default::default(), + source_buffers_list: Default::default(), + active_source_buffers_list: Default::default(), + ready_state: Cell::new(ReadyState::Closed), + duration: Cell::new(f64::NAN), + live_seekable_range: Default::default(), + } + } + + pub fn set_owner(&self, owner: &MediaSource) { + self.owner.set(Some(owner)); + } +} + +impl GeckoMediaSourceImpl for MediaSourceAttributes { + fn get_ready_state(&self) -> i32 { + self.ready_state.get().into() + } + + fn set_ready_state(&self, ready_state: i32) { + match self.owner.get() { + Some(owner) => owner.set_ready_state(ready_state.into()), + None => warn!("Could not set ready state. MediaSource gone."), + }; + } + + fn get_duration(&self) -> f64 { + self.duration.get() + } + + fn has_live_seekable_range(&self) -> bool { + self.live_seekable_range.get().is_some() + } + + fn get_live_seekable_range(&self) -> GeckoMediaTimeInterval { + let mut start = 0.; + let mut end = 0.; + if let Some(time_ranges) = self.live_seekable_range.get() { + let ranges = time_ranges.ranges(); + if !ranges.is_empty() { + let range = ranges.get(0).ok_or(Some(start..end)).unwrap(); + start = *range.start; + end = *range.end; + } + } + GeckoMediaTimeInterval { + mStart: start, + mEnd: end, + } + } + + fn get_source_buffers(&self) -> *mut usize { + match self.owner.get() { + Some(owner) => { + let id = Box::new( + self.source_buffers_list + .or_init(|| SourceBufferList::new(&*owner, ListMode::All)) + .id(), + ); + Box::into_raw(id) + }, + None => ptr::null_mut(), + } + } + + fn get_active_source_buffers(&self) -> *mut usize { + match self.owner.get() { + Some(ref owner) => { + let id = Box::new( + self.source_buffers_list + .or_init(|| SourceBufferList::new(&*owner, ListMode::Active)) + .id(), + ); + Box::into_raw(id) + }, + None => ptr::null_mut(), + } + } +} + +/// A `MediaSource` DOM instance. +/// +/// https://w3c.github.io/media-source/#idl-def-mediasource +#[dom_struct] +pub struct MediaSource { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "Rc"] + attributes: Rc, + #[ignore_malloc_size_of = "Defined in GeckoMedia"] + gecko_media: GeckoMediaSource, +} + impl MediaSource { fn new(window: &Window) -> DomRoot { reflect_dom_object( @@ -50,40 +155,108 @@ impl MediaSource { ) } + #[allow(unrooted_must_root)] fn new_inherited() -> Self { - Self { + let attributes = Rc::new(MediaSourceAttributes::new()); + let weak_attributes = Rc::downgrade(&(&attributes)); + let this = Self { + attributes: attributes.clone(), eventtarget: EventTarget::new_inherited(), - source_buffers: Default::default(), - source_buffers_list: Default::default(), - active_source_buffers_list: Default::default(), - ready_state: Cell::new(ReadyState::Closed), - duration: Cell::new(f64::NAN), - live_seekable_range: Default::default(), - } + gecko_media: GeckoMedia::create_media_source(weak_attributes).unwrap(), + }; + attributes.set_owner(&this); + this + } + + pub fn id(&self) -> usize { + self.gecko_media.get_id() } pub fn source_buffers<'a>(&'a self) -> Ref<'a, [Dom]> { - Ref::map(self.source_buffers.borrow(), |buffers| &**buffers) + Ref::map( + self.attributes.source_buffers.borrow(), + |buffers| &**buffers, + ) + } + + pub fn append_source_buffer(&self, source_buffer: &SourceBuffer, notify: bool) { + self.attributes.source_buffers.borrow_mut().push( + Dom::from_ref( + &*source_buffer, + ), + ); + if !notify { + return; + } + // TODO(nox): If we do our own `Runnable`, we could avoid creating + // the `sourceBuffers` object if the user doesn't access it. + let global = self.global(); + let window = global.as_window(); + window.dom_manipulation_task_source().queue_simple_event( + self.SourceBuffers().upcast(), + atom!("addsourcebuffer"), + &window, + ); + } + + pub fn clear_source_buffers(&self, list_mode: &ListMode) { + let mut source_buffers = self.attributes.source_buffers.borrow_mut(); + match *list_mode { + ListMode::All => source_buffers.clear(), + ListMode::Active => { + source_buffers.retain(|ref buffer| !buffer.is_active()); + }, + }; } - /// https://w3c.github.io/media-source/#dom-mediasource-istypesupported fn parse_mime_type(input: &str) -> Option { - // Steps 1-2. let _mime = match input.parse::() { Ok(mime) => mime, Err(_) => return None, }; - // Steps 3-5. - // FIXME(nox): Implement the checks. + if let Ok(gecko_media) = GeckoMedia::get() { + if gecko_media.is_type_supported(input) { + return Some(_mime); + } + } - // Step 6. - // FIXME(nox): Should be Ok(mime). None } pub fn set_ready_state(&self, ready_state: ReadyState) { - self.ready_state.set(ready_state); + // https://w3c.github.io/media-source/#mediasource-events + let old_state = self.attributes.ready_state.get(); + self.attributes.ready_state.set(ready_state); + + let event = + if ready_state == ReadyState::Open && (old_state == ReadyState::Closed || old_state == ReadyState::Ended) { + if old_state == ReadyState::Ended { + // Notify reader that more data may come. + self.gecko_media.decoder_ended(false); + } + + // readyState transitions from "closed" to "open" or from "ended" to "open". + atom!("sourceopen") + } else if ready_state == ReadyState::Ended && old_state == ReadyState::Open { + // readyState transitions from "open" to "ended". + atom!("sourceended") + } else if ready_state == ReadyState::Closed && + (old_state == ReadyState::Open || old_state == ReadyState::Ended) + { + // readyState transitions from "open" to "closed" or "ended" to "closed". + atom!("sourceclose") + } else { + unreachable!("Invalid MediaSource readyState transition"); + }; + + let window = DomRoot::downcast::(self.global()).unwrap(); + + window.dom_manipulation_task_source().queue_simple_event( + self.upcast(), + event, + &window, + ); } } @@ -93,39 +266,43 @@ impl MediaSource { } /// https://w3c.github.io/media-source/#dom-mediasource-istypesupported - pub fn IsTypeSupported(_window: &Window, type_: DOMString) -> bool { - Self::parse_mime_type(&type_).is_some() + pub fn IsTypeSupported(_: &Window, type_: DOMString) -> bool { + if let Ok(gecko_media) = GeckoMedia::get() { + gecko_media.is_type_supported(&type_) + } else { + false + } } } impl MediaSourceMethods for MediaSource { /// https://w3c.github.io/media-source/#dom-mediasource-sourcebuffers fn SourceBuffers(&self) -> DomRoot { - self.source_buffers_list.or_init(|| { + self.attributes.source_buffers_list.or_init(|| { SourceBufferList::new(self, ListMode::All) }) } /// https://w3c.github.io/media-source/#dom-mediasource-sourcebuffers fn ActiveSourceBuffers(&self) -> DomRoot { - self.active_source_buffers_list.or_init(|| { + self.attributes.active_source_buffers_list.or_init(|| { SourceBufferList::new(self, ListMode::Active) }) } /// https://w3c.github.io/media-source/#dom-readystate fn ReadyState(&self) -> ReadyState { - self.ready_state.get() + self.attributes.ready_state.get() } /// https://w3c.github.io/media-source/#dom-mediasource-duration fn Duration(&self) -> f64 { // Step 1. - if self.ready_state.get() == ReadyState::Closed { + if self.attributes.ready_state.get() == ReadyState::Closed { return f64::NAN; } // Step 2. - self.duration.get() + self.attributes.duration.get() } /// https://w3c.github.io/media-source/#dom-mediasource-duration @@ -139,17 +316,20 @@ impl MediaSourceMethods for MediaSource { } // Step 2. - if self.ready_state.get() != ReadyState::Open { + if self.attributes.ready_state.get() != ReadyState::Open { return Err(Error::InvalidState); } // Step 3. - if self.source_buffers().iter().any(|buffer| buffer.is_active()) { + if self.source_buffers().iter().any( + |buffer| buffer.is_active(), + ) + { return Err(Error::InvalidState); } // Step 4. - self.change_duration(value) + self.duration_change(value) } event_handler!(sourceopen, GetOnsourceopen, SetOnsourceopen); @@ -165,31 +345,24 @@ impl MediaSourceMethods for MediaSource { // Step 2. let mime = Self::parse_mime_type(&type_).ok_or(Error::NotSupported)?; - if self.is_compatible_with_other_source_buffers(&mime) { - return Err(Error::NotSupported); - } // Step 3. - // FIXME(nox): Implement quota checks. + if self.attributes.source_buffers.borrow().len() >= MAX_SOURCE_BUFFERS { + return Err(Error::QuotaExceeded); + } // Step 4. - if self.ready_state.get() != ReadyState::Open { + if self.attributes.ready_state.get() != ReadyState::Open { return Err(Error::InvalidState); } - let window = DomRoot::downcast::(self.global()).unwrap(); - // Steps 5-7. let source_buffer = SourceBuffer::new(self, mime); // Step 8. - self.source_buffers.borrow_mut().push(Dom::from_ref(&*source_buffer)); - // TODO(nox): If we do our own `Runnable`, we could avoid creating - // the `sourceBuffers` object if the user doesn't access it. - window.dom_manipulation_task_source().queue_simple_event( - self.SourceBuffers().upcast(), - atom!("addsourcebuffer"), - &window, + self.append_source_buffer( + &source_buffer, + true, /* trigger addsourcebuffer event */ ); // Step 9. @@ -214,18 +387,10 @@ impl MediaSourceMethods for MediaSource { // and set the source buffer's updating flag to false. // Step 2.3. - task_source.queue_simple_event( - source_buffer.upcast(), - atom!("abort"), - &window, - ); + task_source.queue_simple_event(source_buffer.upcast(), atom!("abort"), &window); // Step 2.4. - task_source.queue_simple_event( - source_buffer.upcast(), - atom!("updateend"), - &window, - ); + task_source.queue_simple_event(source_buffer.upcast(), atom!("updateend"), &window); } // Steps 3-4. @@ -250,7 +415,7 @@ impl MediaSourceMethods for MediaSource { } // Step 10. - self.source_buffers.borrow_mut().remove(position); + self.attributes.source_buffers.borrow_mut().remove(position); source_buffer.clear_parent_media_source(); // TODO(nox): If we do our own `Runnable`, we could avoid creating // the `sourceBuffers` object if the user doesn't access it. @@ -269,29 +434,28 @@ impl MediaSourceMethods for MediaSource { /// https://w3c.github.io/media-source/#dom-mediasource-endofstream fn EndOfStream(&self, error: Option) -> ErrorResult { // Step 1. - if self.ready_state.get() != ReadyState::Open { + if self.attributes.ready_state.get() != ReadyState::Open { return Err(Error::InvalidState); } // Step 2. - if self.source_buffers.borrow().iter().any(|buffer| buffer.Updating()) { + if self.attributes.source_buffers.borrow().iter().any( + |buffer| { + buffer.Updating() + }, + ) + { return Err(Error::InvalidState); } // Step 3. - self.end_of_stream(error); - - Ok(()) + self.end_of_stream(error) } - /// https://w3c.github.io/media-source/#dom-mediasource-endofstream - fn SetLiveSeekableRange( - &self, - start: Finite, - end: Finite, - ) -> ErrorResult { + /// https://w3c.github.io/media-source/#dom-mediasource-setliveseekablerange + fn SetLiveSeekableRange(&self, start: Finite, end: Finite) -> ErrorResult { // Step 1. - if self.ready_state.get() != ReadyState::Open { + if self.attributes.ready_state.get() != ReadyState::Open { return Err(Error::InvalidState); } @@ -300,14 +464,19 @@ impl MediaSourceMethods for MediaSource { return Err(Error::Type("start should not be negative".to_owned())); } if *start > *end { - return Err(Error::Type("start should not be greater than end".to_owned())); + return Err(Error::Type( + "start should not be greater than end".to_owned(), + )); } // Step 3. - self.live_seekable_range.set(Some(&TimeRanges::new( - self.global().as_window(), - vec![start..end] - ))); + self.attributes.live_seekable_range.set( + Some(&TimeRanges::new( + self.global() + .as_window(), + vec![start..end], + )), + ); Ok(()) } @@ -316,17 +485,20 @@ impl MediaSourceMethods for MediaSource { /// https://w3c.github.io/media-source/#dom-mediasource-endofstream fn ClearLiveSeekableRange(&self) -> ErrorResult { // Step 1. - if self.ready_state.get() != ReadyState::Open { + if self.attributes.ready_state.get() != ReadyState::Open { return Err(Error::InvalidState); } // Step 2. - if let Some(time_ranges) = self.live_seekable_range.get() { + if let Some(time_ranges) = self.attributes.live_seekable_range.get() { if !time_ranges.ranges().is_empty() { - self.live_seekable_range.set(Some(&TimeRanges::new( - self.global().as_window(), - vec![], - ))); + self.attributes.live_seekable_range.set( + Some(&TimeRanges::new( + self.global() + .as_window(), + vec![], + )), + ); } } @@ -336,9 +508,9 @@ impl MediaSourceMethods for MediaSource { impl MediaSource { /// https://w3c.github.io/media-source/#duration-change-algorithm - fn change_duration(&self, new_duration: f64) -> ErrorResult { + fn duration_change(&self, new_duration: f64) -> ErrorResult { // Step 1. - if self.duration.get() == new_duration { + if self.attributes.duration.get() == new_duration { return Ok(()); } @@ -354,36 +526,32 @@ impl MediaSource { let new_duration = new_duration.max(highest_end_time); // Step 5. - self.duration.set(new_duration); + self.attributes.duration.set(new_duration); // Step 6. - // FIXME(nox): Update media duration and run the `HTMLMediaElement` - // duration change algorithm. + self.gecko_media.duration_change(new_duration); Ok(()) } /// https://w3c.github.io/media-source/#end-of-stream-algorithm - fn end_of_stream(&self, _error: Option) { - // Step 1. - self.ready_state.set(ReadyState::Closed); - - let window = DomRoot::downcast::(self.global()).unwrap(); - - // Step 2. - window.dom_manipulation_task_source().queue_simple_event( - self.upcast(), - atom!("sourceended"), - &window, - ); + pub fn end_of_stream(&self, error: Option) -> ErrorResult { + // Step 1 and step 2. + self.set_ready_state(ReadyState::Ended); // Step 3. - // FIXME(nox): Do the thing. - } + match error { + Some(error) => { + self.gecko_media.end_of_stream_error(error.into()); + }, + None => { + let _ = self.duration_change(self.highest_end_time())?; + // Notify reader that all data is now available. + self.gecko_media.decoder_ended(true); + }, + } - fn is_compatible_with_other_source_buffers(&self, _mime: &Mime) -> bool { - // FIXME(nox): Implement the checks. - false + Ok(()) } fn is_less_than_highest_presentation_time(&self, _value: f64) -> bool { @@ -396,3 +564,33 @@ impl MediaSource { unimplemented!(); } } + +impl From for i32 { + fn from(ready_state: ReadyState) -> Self { + match ready_state { + ReadyState::Closed => 0, + ReadyState::Open => 1, + ReadyState::Ended => 2, + } + } +} + +impl From for ReadyState { + fn from(ready_state: i32) -> Self { + match ready_state { + 0 => ReadyState::Closed, + 1 => ReadyState::Open, + 2 => ReadyState::Ended, + _ => unreachable!(), + } + } +} + +impl From for i32 { + fn from(error: EndOfStreamError) -> Self { + match error { + EndOfStreamError::Network => 0, + EndOfStreamError::Decode => 1, + } + } +} diff --git a/components/script/dom/sourcebuffer.rs b/components/script/dom/sourcebuffer.rs index f169f29cfa4aa..a1339604b1101 100644 --- a/components/script/dom/sourcebuffer.rs +++ b/components/script/dom/sourcebuffer.rs @@ -4,7 +4,7 @@ //! The `SourceBuffer` DOM implementation. -use dom::bindings::cell::DomRefCell; +use dom::bindings::codegen::Bindings::MediaSourceBinding::EndOfStreamError; use dom::bindings::codegen::Bindings::MediaSourceBinding::MediaSourceMethods; use dom::bindings::codegen::Bindings::MediaSourceBinding::ReadyState; use dom::bindings::codegen::Bindings::SourceBufferBinding; @@ -14,19 +14,225 @@ use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::inheritance::Castable; use dom::bindings::num::Finite; use dom::bindings::reflector::{DomObject, reflect_dom_object}; -use dom::bindings::refcounted::Trusted; use dom::bindings::root::{DomRoot, MutNullableDom}; use dom::eventtarget::EventTarget; use dom::mediasource::MediaSource; use dom::window::Window; use dom_struct::dom_struct; +use gecko_media::{GeckoMedia, GeckoMediaSourceBuffer, GeckoMediaSourceBufferImpl}; use js::jsapi::{JSContext, JSObject, Rooted}; use js::typedarray::{ArrayBuffer, ArrayBufferView}; use mime::{Mime, SubLevel, TopLevel}; use std::cell::Cell; -use std::collections::VecDeque; use std::f64; -use task_source::TaskSource; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; + +#[derive(JSTraceable, MallocSizeOf)] +#[allow(unrooted_must_root)] +pub struct SourceBufferAttributes { + #[ignore_malloc_size_of = "Rc"] + owner: MutNullableDom, + /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowstart + append_window_start: Cell>, + /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowend + append_window_end: Cell, + /// https://w3c.github.io/media-source/#dom-sourcebuffer-timestampoffset + timestamp_offset: Cell>, + /// https://w3c.github.io/media-source/#dom-sourcebuffer-mode + append_mode: Cell, + /// https://w3c.github.io/media-source/#sourcebuffer-generate-timestamps-flag + timestamp_mode: TimestampMode, + /// https://w3c.github.io/media-source/#sourcebuffer-append-state + append_state: Cell, + /// https://w3c.github.io/media-source/#sourcebuffer-group-start-timestamp + group_start_timestamp: Cell>>, + /// https://w3c.github.io/media-source/#sourcebuffer-group-end-timestamp + group_end_timestamp: Cell>, + /// https://w3c.github.io/media-source/#dom-sourcebuffer-updating + updating: Cell, + active: Cell, +} + +impl SourceBufferAttributes { + fn new(timestamp_mode: TimestampMode) -> Self { + SourceBufferAttributes { + owner: Default::default(), + // FIXME(nox): This assumes that the presentation start time is 0. + append_window_start: Default::default(), + append_window_end: Cell::new(f64::INFINITY), + timestamp_offset: Default::default(), + append_mode: Cell::new(timestamp_mode.into()), + timestamp_mode, + append_state: Cell::new(AppendState::WaitingForSegment), + group_start_timestamp: Default::default(), + group_end_timestamp: Default::default(), + updating: Default::default(), + active: Cell::new(false), + } + } + + fn set_owner(&self, owner: &SourceBuffer) { + self.owner.set(Some(owner)); + } +} + +impl GeckoMediaSourceBufferImpl for SourceBufferAttributes { + fn owner(&self) -> *mut c_void { + match self.owner.get() { + None => ptr::null_mut(), + Some(ref mut owner) => owner as *mut _ as *mut c_void, + } + } + + fn get_append_window_start(&self) -> f64 { + *self.append_window_start.get() + } + + fn set_append_window_start(&self, value: f64) { + self.append_window_start.set(Finite::wrap(value)); + } + + fn get_append_window_end(&self) -> f64 { + self.append_window_end.get() + } + + fn set_append_window_end(&self, value: f64) { + self.append_window_end.set(value); + } + + fn get_timestamp_offset(&self) -> f64 { + *self.timestamp_offset.get() + } + + fn set_timestamp_offset(&self, value: f64) { + self.timestamp_offset.set(Finite::wrap(value)); + } + + fn get_append_mode(&self) -> i32 { + self.append_mode.get().into() + } + + fn set_append_mode(&self, value: i32) { + self.append_mode.set(value.into()); + } + + #[allow(unsafe_code)] + fn get_group_start_timestamp(&self, timestamp: *mut f64) { + if timestamp == ptr::null_mut() { + return; + } + if let Some(timestamp_) = self.group_start_timestamp.get() { + unsafe { *timestamp = *timestamp_ } + } + } + + fn set_group_start_timestamp(&self, value: f64) { + self.group_start_timestamp.set(Some(Finite::wrap(value))); + } + + fn have_group_start_timestamp(&self) -> bool { + self.group_start_timestamp.get().is_some() + } + + fn reset_group_start_timestamp(&self) { + self.group_start_timestamp.set(None); + } + + fn restart_group_start_timestamp(&self) { + self.group_start_timestamp.set(Some( + self.group_end_timestamp.get(), + )); + } + + fn get_group_end_timestamp(&self) -> f64 { + *self.group_end_timestamp.get() + } + + fn set_group_end_timestamp(&self, value: f64) { + self.group_end_timestamp.set(Finite::wrap(value)); + } + + fn get_append_state(&self) -> i32 { + self.append_state.get().into() + } + + fn set_append_state(&self, value: i32) { + self.append_state.set(value.into()); + } + + fn get_updating(&self) -> bool { + self.updating.get() + } + + fn set_updating(&self, updating: bool) { + if let Some(owner) = self.owner.get() { + let window = DomRoot::downcast::(owner.global()).unwrap(); + let event = if updating { + atom!("updatestart") + } else { + atom!("updateend") + }; + window.dom_manipulation_task_source().queue_simple_event( + owner.upcast(), + event, + &window, + ); + self.updating.set(updating); + return; + } + unreachable!(); + + } + + fn get_active(&self) -> bool { + self.active.get() + } + + fn set_active(&self, active: bool) { + if let Some(owner) = self.owner.get() { + if let Some(media_source) = owner.parent_media_source.get() { + let window = DomRoot::downcast::(owner.global()).unwrap(); + let event = if active { + atom!("addsourcebuffer") + } else { + atom!("removesourcebuffer") + }; + window.dom_manipulation_task_source().queue_simple_event( + media_source + .ActiveSourceBuffers() + .upcast(), + event, + &window, + ); + self.active.set(active); + return; + } + } + unreachable!(); + } + + fn on_data_appended(&self, result: u32) { + if let Some(owner) = self.owner.get() { + if result == 0 { + owner.on_data_appended_success(); + } else { + owner.on_data_appended_error(result); + } + return; + } + unreachable!(); + } + + fn on_range_removed(&self) { + if let Some(owner) = self.owner.get() { + owner.on_range_removed(); + return; + } + unreachable!(); + } +} /// A `SourceBuffer` DOM instance. /// @@ -36,18 +242,6 @@ pub struct SourceBuffer { eventtarget: EventTarget, /// https://w3c.github.io/media-source/#parent-media-source parent_media_source: MutNullableDom, - /// https://w3c.github.io/media-source/#sourcebuffer-generate-timestamps-flag - timestamp_mode: TimestampMode, - /// https://w3c.github.io/media-source/#dom-sourcebuffer-mode - append_mode: Cell, - /// https://w3c.github.io/media-source/#sourcebuffer-append-state - append_state: Cell, - /// https://w3c.github.io/media-source/#sourcebuffer-group-start-timestamp - group_start_timestamp: Cell>>, - /// https://w3c.github.io/media-source/#sourcebuffer-group-end-timestamp - group_end_timestamp: Cell>, - /// https://w3c.github.io/media-source/#sourcebuffer-input-buffer - input_buffer: DomRefCell>, /// https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag buffer_full: Cell, /// The MIME type provided when that `SourceBuffer` was created. @@ -55,14 +249,10 @@ pub struct SourceBuffer { mime: Mime, /// Whether we are currently running the range removal algorithm. in_range_removal: Cell, - /// https://w3c.github.io/media-source/#dom-sourcebuffer-updating - updating: Cell, - /// https://w3c.github.io/media-source/#dom-sourcebuffer-timestampoffset - timestamp_offset: Cell>, - /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowstart - append_window_start: Cell>, - /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowend - append_window_end: Cell, + #[ignore_malloc_size_of = "Rc"] + attributes: Rc, + #[ignore_malloc_size_of = "Defined in GeckoMedia"] + gecko_media: GeckoMediaSourceBuffer, } /// https://w3c.github.io/media-source/#sourcebuffer-append-state @@ -73,6 +263,27 @@ enum AppendState { ParsingMediaSegment, } +impl From for i32 { + fn from(append_state: AppendState) -> Self { + match append_state { + AppendState::WaitingForSegment => 0, + AppendState::ParsingInitSegment => 1, + AppendState::ParsingMediaSegment => 2, + } + } +} + +impl From for AppendState { + fn from(value: i32) -> Self { + match value { + 0 => AppendState::WaitingForSegment, + 1 => AppendState::ParsingInitSegment, + 2 => AppendState::ParsingMediaSegment, + _ => unreachable!(), + } + } +} + /// https://w3c.github.io/media-source/#sourcebuffer-generate-timestamps-flag #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] enum TimestampMode { @@ -83,11 +294,7 @@ enum TimestampMode { } impl SourceBuffer { - pub fn new( - parent_media_source: &MediaSource, - mime: Mime, - ) -> DomRoot { - + pub fn new(parent_media_source: &MediaSource, mime: Mime) -> DomRoot { reflect_dom_object( Box::new(Self::new_inherited(parent_media_source, mime)), &*parent_media_source.global(), @@ -95,9 +302,12 @@ impl SourceBuffer { ) } + pub fn id(&self) -> usize { + self.gecko_media.get_id() + } + pub fn is_active(&self) -> bool { - // FIXME(nox): Actually implement this correctly. - false + self.attributes.active.get() } pub fn clear_parent_media_source(&self) { @@ -109,7 +319,7 @@ impl SourceBuffer { impl SourceBufferMethods for SourceBuffer { /// https://w3c.github.io/media-source/#dom-sourcebuffer-mode fn Mode(&self) -> AppendMode { - self.append_mode.get() + self.attributes.append_mode.get() } /// https://w3c.github.io/media-source/#dom-sourcebuffer-mode @@ -121,7 +331,7 @@ impl SourceBufferMethods for SourceBuffer { }; // Step 2. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } @@ -129,48 +339,46 @@ impl SourceBufferMethods for SourceBuffer { // The argument is already named new_mode. // Step 4. - if self.timestamp_mode == TimestampMode::Generated && new_mode == AppendMode::Segments { + if self.attributes.timestamp_mode == TimestampMode::Generated && new_mode == AppendMode::Segments { return Err(Error::Type("New mode cannot be \"segments\".".to_owned())); } // Step 5. if parent_media_source.ReadyState() == ReadyState::Ended { - // Step 5.1. + // Step 5.1 and 5.2. parent_media_source.set_ready_state(ReadyState::Open); - - // Step 5.2. - let window = DomRoot::downcast::(parent_media_source.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - parent_media_source.upcast(), - atom!("sourceopen"), - &window, - ); } // Step 6. - if self.append_state.get() == AppendState::ParsingMediaSegment { + if self.attributes.append_state.get() == AppendState::ParsingMediaSegment { return Err(Error::InvalidState); } // Step 7. if new_mode == AppendMode::Sequence { - self.group_start_timestamp.set(Some(self.group_end_timestamp.get())); + self.attributes.group_start_timestamp.set(Some( + self.attributes + .group_end_timestamp + .get(), + )); } // Step 8. - self.append_mode.set(new_mode); + self.attributes.append_mode.set(new_mode); Ok(()) } /// https://w3c.github.io/media-source/#dom-sourcebuffer-updating fn Updating(&self) -> bool { - self.updating.get() + self.attributes.updating.get() } + // TODO Buffered + /// https://w3c.github.io/media-source/#dom-sourcebuffer-timestampoffset fn TimestampOffset(&self) -> Finite { - self.timestamp_offset.get() + self.attributes.timestamp_offset.get() } /// https://w3c.github.io/media-source/#dom-sourcebuffer-timestampoffset @@ -185,43 +393,43 @@ impl SourceBufferMethods for SourceBuffer { }; // Step 3. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } // Step 4. if parent_media_source.ReadyState() == ReadyState::Ended { - // Step 4.1. + // Step 4.1. and 4.2. parent_media_source.set_ready_state(ReadyState::Open); - - // Step 4.2. - let window = DomRoot::downcast::(parent_media_source.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - parent_media_source.upcast(), - atom!("sourceopen"), - &window, - ); } // Step 5. - if self.append_state.get() == AppendState::ParsingMediaSegment { + if self.attributes.append_state.get() == AppendState::ParsingMediaSegment { return Err(Error::InvalidState); } // Step 6. - if self.append_mode.get() == AppendMode::Sequence { - self.group_start_timestamp.set(Some(new_timestamp_offset)); + if self.attributes.append_mode.get() == AppendMode::Sequence { + self.attributes.group_start_timestamp.set(Some( + new_timestamp_offset, + )); } // Step 7. - self.timestamp_offset.set(new_timestamp_offset); + self.attributes.timestamp_offset.set(new_timestamp_offset); Ok(()) } + // TODO AudioTracks. + + // TODO VideoTracks. + + // TODO TextTracks. + /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowstart fn AppendWindowStart(&self) -> Finite { - self.append_window_start.get() + self.attributes.append_window_start.get() } /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowstart @@ -232,24 +440,24 @@ impl SourceBufferMethods for SourceBuffer { } // Step 2. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } // Step 3. - if *value < 0. || *value >= self.append_window_end.get() { + if *value < 0. || *value >= self.attributes.append_window_end.get() { return Err(Error::Type("Value is out of range.".to_owned())); } // Step 4. - self.append_window_start.set(value); + self.attributes.append_window_start.set(value); Ok(()) } /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowend fn AppendWindowEnd(&self) -> f64 { - self.append_window_end.get() + self.attributes.append_window_end.get() } /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendwindowend @@ -260,7 +468,7 @@ impl SourceBufferMethods for SourceBuffer { } // Step 2. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } @@ -270,12 +478,12 @@ impl SourceBufferMethods for SourceBuffer { } // Step 4. - if value <= *self.append_window_start.get() { + if value <= *self.attributes.append_window_start.get() { return Err(Error::Type("Value is out of range.".to_owned())); } // Step 5. - self.append_window_end.set(value); + self.attributes.append_window_end.set(value); Ok(()) } @@ -288,11 +496,7 @@ impl SourceBufferMethods for SourceBuffer { /// https://w3c.github.io/media-source/#dom-sourcebuffer-appendbuffer #[allow(unsafe_code)] - unsafe fn AppendBuffer( - &self, - cx: *mut JSContext, - data: *mut JSObject, - ) -> ErrorResult { + unsafe fn AppendBuffer(&self, cx: *mut JSContext, data: *mut JSObject) -> ErrorResult { let mut root_1 = Rooted::new_unrooted(); let mut root_2 = Rooted::new_unrooted(); let mut buffer_source = BufferSource::new(cx, &mut root_1, &mut root_2, data)?; @@ -318,12 +522,12 @@ impl SourceBufferMethods for SourceBuffer { } // Step 4. - if self.updating.get() { + if self.attributes.updating.get() { // Step 4.1. - // FIXME(nox): Abort buffer append algorithm. + self.gecko_media.abort_buffer_append(); - // Step 4.2. - self.updating.set(false); + // Step 4.2. and 4.4. + self.attributes.set_updating(false); // Step 4.3. let window = DomRoot::downcast::(self.global()).unwrap(); @@ -332,24 +536,17 @@ impl SourceBufferMethods for SourceBuffer { atom!("abort"), &window, ); - - // Step 4.4. - window.dom_manipulation_task_source().queue_simple_event( - self.upcast(), - atom!("updateend"), - &window, - ); } // Step 5. - // FIXME(nox): Run the reset parser state algorithm. + self.gecko_media.reset_parser_state(); // Step 6. // This assumes that presentation start time is always 0. - self.append_window_start.set(Default::default()); + self.attributes.append_window_start.set(Default::default()); // Step 7. - self.append_window_end.set(f64::INFINITY); + self.attributes.append_window_end.set(f64::INFINITY); Ok(()) } @@ -363,14 +560,16 @@ impl SourceBufferMethods for SourceBuffer { }; // Step 2. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } // Step 3. let duration = parent_media_source.Duration(); if duration.is_nan() { - return Err(Error::Type("Parent media source's duration is NaN.".to_owned())); + return Err(Error::Type( + "Parent media source's duration is NaN.".to_owned(), + )); } // Step 4. @@ -388,16 +587,8 @@ impl SourceBufferMethods for SourceBuffer { // Step 6. if parent_media_source.ReadyState() == ReadyState::Ended { - // Step 6.1. + // Step 6.1. and 6.2. parent_media_source.set_ready_state(ReadyState::Open); - - // Step 6.2. - let window = DomRoot::downcast::(parent_media_source.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - parent_media_source.upcast(), - atom!("sourceopen"), - &window, - ); } // Step 7. @@ -408,34 +599,35 @@ impl SourceBufferMethods for SourceBuffer { } impl SourceBuffer { + #[allow(unrooted_must_root)] fn new_inherited(parent_media_source: &MediaSource, mime: Mime) -> Self { let timestamp_mode = Self::timestamp_mode(&mime); - Self { + let generate_timestamps = timestamp_mode == TimestampMode::Generated; + let attributes = Rc::new(SourceBufferAttributes::new(timestamp_mode)); + let weak_attributes = Rc::downgrade(&(&attributes)); + let this = Self { eventtarget: EventTarget::new_inherited(), parent_media_source: MutNullableDom::new(Some(parent_media_source)), - timestamp_mode, - append_mode: Cell::new(timestamp_mode.into()), - append_state: Cell::new(AppendState::WaitingForSegment), - group_start_timestamp: Default::default(), - group_end_timestamp: Default::default(), - input_buffer: Default::default(), buffer_full: Default::default(), - mime, + mime: mime.clone(), in_range_removal: Default::default(), - timestamp_offset: Default::default(), - updating: Default::default(), - // FIXME(nox): This assumes that the presentation start time is 0. - append_window_start: Default::default(), - append_window_end: Cell::new(f64::INFINITY), - } + attributes: attributes.clone(), + gecko_media: GeckoMedia::create_source_buffer( + weak_attributes, + parent_media_source.id(), + &mime.to_string(), + generate_timestamps, + ).unwrap(), + }; + attributes.set_owner(&this); + this } /// https://w3c.github.io/media-source/byte-stream-format-registry.html fn timestamp_mode(mime: &Mime) -> TimestampMode { match *mime { Mime(TopLevel::Audio, SubLevel::Mpeg, _) => TimestampMode::Generated, - Mime(TopLevel::Audio, SubLevel::Ext(ref ext), _) - if ext.eq_ignore_ascii_case("aac") => { + Mime(TopLevel::Audio, SubLevel::Ext(ref ext), _) if ext.eq_ignore_ascii_case("aac") => { TimestampMode::Generated }, _ => TimestampMode::FromSource, @@ -448,35 +640,17 @@ impl SourceBuffer { // Step 1. self.prepare_append(buffer_source)?; - // Step 2. - self.input_buffer.borrow_mut().extend(unsafe { buffer_source.as_slice() }); + // Step 3 and 4. + self.attributes.set_updating(true); - // Step 3. - self.updating.set(true); - - // Step 4. - let window = DomRoot::downcast::(self.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - self.upcast(), - atom!("updatestart"), - &window, - ); - - // Step 5. - // FIXME(nox): Return to caller and run the rest in parallel, instead - // of queueing a task. - let this = Trusted::new(self); - window.dom_manipulation_task_source().queue( - task!(buffer_append: move || { - this.root().buffer_append(); - }), - window.upcast(), - ).unwrap(); + // Step 2 and 5. + self.buffer_append(buffer_source); Ok(()) } /// https://w3c.github.io/media-source/#sourcebuffer-prepare-append + #[allow(unsafe_code)] fn prepare_append(&self, buffer_source: &mut BufferSource) -> ErrorResult { // Step 1. let parent_media_source = match self.parent_media_source.get() { @@ -485,7 +659,7 @@ impl SourceBuffer { }; // Step 2. - if self.updating.get() { + if self.attributes.updating.get() { return Err(Error::InvalidState); } @@ -494,20 +668,14 @@ impl SourceBuffer { // Step 4. if parent_media_source.ReadyState() == ReadyState::Ended { - // Step 4.1. + // Step 4.1. and 4.2. parent_media_source.set_ready_state(ReadyState::Open); - - // Step 4.2. - let window = DomRoot::downcast::(parent_media_source.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - parent_media_source.upcast(), - atom!("sourceopen"), - &window, - ); } // Step 5. - self.evict_coded_frames(buffer_source)?; + self.evict_coded_frames( + unsafe { buffer_source.as_slice().len() }, + )?; // Step 6. if self.buffer_full.get() { @@ -518,15 +686,28 @@ impl SourceBuffer { } /// https://w3c.github.io/media-source/#sourcebuffer-buffer-append - fn buffer_append(&self) { + #[allow(unsafe_code)] + fn buffer_append(&self, buffer_source: &mut BufferSource) { // Step 1. - // FIXME(nox): Run the segment parser loop algorithm. - - // Step 2. - // FIXME(nox): Propagate segment parser loop abortion if necessary. + unsafe { + self.gecko_media.append_data( + buffer_source.as_slice().as_ptr(), + buffer_source.as_slice().len(), + ); + } + // Step 2 is run in on_data_appended_error. + // Steps 3 to 5 are run in on_data_appended_success. + } - // Step 3. - self.updating.set(false); + /// Steps 3 to 5 of https://w3c.github.io/media-source/#sourcebuffer-buffer-append + pub fn on_data_appended_success(&self) { + if !self.attributes.get_updating() { + // The buffer append or range removal algorithm has been interrupted + // by abort(). + return; + } + // Step 3 and 5. + self.attributes.set_updating(false); // Step 4. let window = DomRoot::downcast::(self.global()).unwrap(); @@ -535,18 +716,48 @@ impl SourceBuffer { atom!("update"), &window, ); + } - // Step 5. + /// https://w3c.github.io/media-source/#sourcebuffer-append-error + pub fn on_data_appended_error(&self, _: u32) { + // Step 1 is run in gecko-media SourceBuffer::ApendDataErrored. + + // Steps 2 and 4. + self.attributes.set_updating(false); + + // Step 3. + let window = DomRoot::downcast::(self.global()).unwrap(); window.dom_manipulation_task_source().queue_simple_event( self.upcast(), - atom!("updateend"), + atom!("error"), &window, ); + + // Step 5. + if let Some(media_source) = self.parent_media_source.get() { + let _ = media_source.end_of_stream(Some(EndOfStreamError::Decode)); + } } /// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-eviction - fn evict_coded_frames(&self, _buffer_source: &mut BufferSource) -> ErrorResult { - // FIXME(nox): Actually implement this. + fn evict_coded_frames(&self, buffer_len: usize) -> ErrorResult { + // Step 1. + // Gecko only cares about the length of the about to be appended data, + // which is buffer_len. + + // Step 2. + if !self.buffer_full.get() { + return Ok(()); + } + + // Steps 3 and 4. + let mut buffer_full = true; + self.gecko_media.evict_coded_frames( + buffer_len, + &mut buffer_full, + ); + self.buffer_full.set(buffer_full); + Ok(()) } @@ -554,72 +765,55 @@ impl SourceBuffer { fn remove_range(&self, start: Finite, end: f64) { // Mark this SourceBuffer as being running the range removal algorithm, // so that the abort() method properly throws an exception. - assert!(self.updating.get()); + assert!(self.attributes.updating.get()); self.in_range_removal.set(true); // Steps 1-2. // We assume that presentation start time is 0, thus we can just use // the arguments directly. - // Step 3. - self.updating.set(true); + // Step 3 and 4. + self.attributes.set_updating(true); - // Step 4. + // Step 5 and 6. + self.gecko_media.range_removal(*start, end); + } + + /// https://w3c.github.io/media-source/#sourcebuffer-range-removal + pub fn on_range_removed(&self) { + // Step 7 and 9. + self.attributes.set_updating(false); + + // Step 8. let window = DomRoot::downcast::(self.global()).unwrap(); window.dom_manipulation_task_source().queue_simple_event( self.upcast(), - atom!("updatestart"), + atom!("update"), &window, ); - // Step 5. - // FIXME(nox): Return to caller and run the rest in parallel, instead - // of queueing a task. - let this = Trusted::new(self); - window.dom_manipulation_task_source().queue( - task!(range_removal: move || { - let this = this.root(); - - // Step 6. - this.remove_coded_frames(start, end); - - // Step 7. - this.updating.set(false); - - // Step 8. - let window = DomRoot::downcast::(this.global()).unwrap(); - window.dom_manipulation_task_source().queue_simple_event( - this.upcast(), - atom!("update"), - &window, - ); - - // Step 9. - window.dom_manipulation_task_source().queue_simple_event( - this.upcast(), - atom!("updateend"), - &window, - ); - - // FIXME(nox): I'm not too sure exactly if this should be done - // at the very end of the range removal algorithm. - this.in_range_removal.set(false); - }), - window.upcast(), - ).unwrap(); + // FIXME(nox): I'm not too sure exactly if this should be done + // at the very end of the range removal algorithm. + self.in_range_removal.set(false); } +} - /// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal - fn remove_coded_frames(&self, _start: Finite, _end: f64) { - // Steps 1-2. - // We assume that presentation start time is 0, thus we can just use - // the arguments directly. - - // Step 3. - // FIXME(nox): Loop through each track buffer and actually do work here. +impl From for i32 { + fn from(append_mode: AppendMode) -> Self { + match append_mode { + AppendMode::Segments => 0, + AppendMode::Sequence => 1, + } + } +} - // Step 4. - // FIXME(nox): Handle the buffer full flag. +impl From for AppendMode { + fn from(value: i32) -> Self { + match value { + 0 => AppendMode::Segments, + 1 => AppendMode::Sequence, + _ => unreachable!(), + } } } @@ -650,7 +844,10 @@ impl<'root> BufferSource<'root> { } else if let Ok(view) = ArrayBufferView::from(cx, root_2, data) { return Ok(BufferSource::ArrayBufferView(view)); } - Err(Error::Type("Object should be an ArrayBuffer or ArrayBufferView.".to_owned())) + Err(Error::Type( + "Object should be an ArrayBuffer or ArrayBufferView." + .to_owned(), + )) } #[allow(unsafe_code)] diff --git a/components/script/dom/sourcebufferlist.rs b/components/script/dom/sourcebufferlist.rs index 40820aa1eeabe..3d002b43ddeae 100644 --- a/components/script/dom/sourcebufferlist.rs +++ b/components/script/dom/sourcebufferlist.rs @@ -12,6 +12,84 @@ use dom::eventtarget::EventTarget; use dom::mediasource::MediaSource; use dom::sourcebuffer::SourceBuffer; use dom_struct::dom_struct; +use gecko_media::GeckoMedia; +use gecko_media::{GeckoMediaSourceBufferList, GeckoMediaSourceBufferListImpl}; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; + +#[derive(JSTraceable, MallocSizeOf)] +#[allow(unrooted_must_root)] +pub struct SourceBufferListInner { + media_source: Dom, + list_mode: ListMode, +} + +impl SourceBufferListInner { + fn new(media_source: &MediaSource, list_mode: ListMode) -> Self { + Self { + media_source: Dom::from_ref(media_source), + list_mode, + } + } +} + +impl GeckoMediaSourceBufferListImpl for SourceBufferListInner { + #[allow(unsafe_code)] + fn indexed_getter(&self, index: u32, source_buffer: *mut usize) -> bool { + if source_buffer == ptr::null_mut() { + return false; + } + let buffers = self.media_source.source_buffers(); + if index as usize >= buffers.len() { + return false; + } + let buffer = match self.list_mode { + ListMode::All => Some(&*buffers[index as usize]), + ListMode::Active => { + buffers + .iter() + .filter(|buffer| buffer.is_active()) + .nth(index as usize) + .map(|buffer| &**buffer) + }, + }; + match buffer { + Some(buffer) => { + unsafe { + *source_buffer = buffer.id(); + } + true + }, + None => false, + } + } + + fn length(&self) -> u32 { + let buffers = self.media_source.source_buffers(); + match self.list_mode { + ListMode::All => buffers.len() as u32, + ListMode::Active => { + // FIXME(nox): Inefficient af, should cache the number of + // active source buffers directly in the MediaSource instance. + buffers.iter().filter(|buffer| buffer.is_active()).count() as u32 + }, + } + } + + #[allow(unsafe_code)] + fn append(&self, source_buffer: *mut c_void, notify: bool) { + let source_buffer: &SourceBuffer = unsafe { &*(source_buffer as *mut SourceBuffer) }; + self.media_source.append_source_buffer( + source_buffer, + notify, + ); + } + + fn clear(&self) { + self.media_source.clear_source_buffers(&self.list_mode); + } +} /// A `SourceBufferList` DOM instance. /// @@ -19,8 +97,10 @@ use dom_struct::dom_struct; #[dom_struct] pub struct SourceBufferList { eventtarget: EventTarget, - media_source: Dom, - list_mode: ListMode, + #[ignore_malloc_size_of = "Rc"] + inner: Rc, + #[ignore_malloc_size_of = "Defined in GeckoMedia"] + gecko_media: GeckoMediaSourceBufferList, } #[derive(MallocSizeOf, JSTraceable)] @@ -31,10 +111,12 @@ pub enum ListMode { impl SourceBufferList { fn new_inherited(media_source: &MediaSource, list_mode: ListMode) -> Self { + let inner = Rc::new(SourceBufferListInner::new(&media_source, list_mode)); + let inner_weak = Rc::downgrade(&(&inner)); Self { eventtarget: EventTarget::new_inherited(), - media_source: Dom::from_ref(media_source), - list_mode, + inner, + gecko_media: GeckoMedia::create_source_buffer_list(inner_weak).unwrap(), } } @@ -45,27 +127,19 @@ impl SourceBufferList { SourceBufferListBinding::Wrap, ) } + + pub fn id(&self) -> usize { + self.gecko_media.get_id() + } } impl SourceBufferListMethods for SourceBufferList { /// https://w3c.github.io/media-source/#dom-sourcebufferlist-length fn Length(&self) -> u32 { - let buffers = self.media_source.source_buffers(); - match self.list_mode { - ListMode::All => buffers.len() as u32, - ListMode::Active => { - // FIXME(nox): Inefficient af, should cache the number of - // active source buffers directly in the MediaSource instance. - buffers.iter().filter(|buffer| buffer.is_active()).count() as u32 - }, - } + self.inner.length() } - event_handler!( - addsourcebuffer, - GetOnaddsourcebuffer, - SetOnaddsourcebuffer - ); + event_handler!(addsourcebuffer, GetOnaddsourcebuffer, SetOnaddsourcebuffer); event_handler!( removesourcebuffer, @@ -75,11 +149,11 @@ impl SourceBufferListMethods for SourceBufferList { /// https://w3c.github.io/media-source/#dfn-sourcebufferlist-getter fn IndexedGetter(&self, index: u32) -> Option> { - let buffers = self.media_source.source_buffers(); + let buffers = self.inner.media_source.source_buffers(); if index as usize >= buffers.len() { return None; } - match self.list_mode { + match self.inner.list_mode { ListMode::All => Some(DomRoot::from_ref(&*buffers[index as usize])), ListMode::Active => { // FIXME(nox): Inefficient af, should have a cache to the last @@ -89,7 +163,7 @@ impl SourceBufferListMethods for SourceBufferList { .filter(|buffer| buffer.is_active()) .nth(index as usize) .map(|buffer| DomRoot::from_ref(&**buffer)) - } + }, } } }