Bug 1062733 - Keep track of all TrackBuffer decoders rather than only fully initialized ones. r=cajbir
authorMatthew Gregan <kinetik@flim.org>
Thu, 04 Sep 2014 19:35:01 +1200
changeset 203716 3c00146008c293a783f7b30cef3b73d578c8a146
parent 203715 0d8adc6d6faacb7a29a187b40833747f6935ea67
child 203717 3de899a46769ed9d10f8180d23807281f2c28fb8
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerscajbir
bugs1062733
milestone35.0a1
Bug 1062733 - Keep track of all TrackBuffer decoders rather than only fully initialized ones. r=cajbir
content/media/mediasource/TrackBuffer.cpp
content/media/mediasource/TrackBuffer.h
content/media/mediasource/test/mochitest.ini
content/media/mediasource/test/test_BufferedSeek.html
content/media/mediasource/test/test_MediaSource.html
content/media/mediasource/test/test_SplitAppend.html
content/media/mediasource/test/test_SplitAppendDelay.html
--- a/content/media/mediasource/TrackBuffer.cpp
+++ b/content/media/mediasource/TrackBuffer.cpp
@@ -86,16 +86,17 @@ TrackBuffer::Shutdown()
   mTaskQueue->Shutdown();
   mTaskQueue = nullptr;
 
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   DiscardDecoder();
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     mDecoders[i]->GetReader()->Shutdown();
   }
+  mInitializedDecoders.Clear();
   NS_DispatchToMainThread(new ReleaseDecoderTask(mDecoders));
   MOZ_ASSERT(mDecoders.IsEmpty());
   mParentDecoder = nullptr;
 }
 
 bool
 TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
 {
@@ -130,18 +131,19 @@ TrackBuffer::EvictBefore(double aTime)
     mCurrentDecoder->GetResource()->EvictBefore(endOffset);
   }
   MSE_DEBUG("TrackBuffer(%p)::EvictBefore offset=%lld", this, endOffset);
 }
 
 double
 TrackBuffer::Buffered(dom::TimeRanges* aRanges)
 {
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   MOZ_ASSERT(NS_IsMainThread());
-  // XXX check default if mDecoders empty?
+
   double highestEndTime = 0;
 
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
     mDecoders[i]->GetBuffered(r);
     if (r->Length() > 0) {
       highestEndTime = std::max(highestEndTime, r->GetEndTime());
       aRanges->Union(r);
@@ -158,16 +160,17 @@ TrackBuffer::NewDecoder()
   MOZ_ASSERT(!mCurrentDecoder && mParentDecoder);
 
   nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType);
   if (!decoder) {
     return false;
   }
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   mCurrentDecoder = decoder;
+  mDecoders.AppendElement(decoder);
 
   mLastStartTimestamp = 0;
   mLastEndTimestamp = UnspecifiedNaN<double>();
   mHasInit = true;
 
   return QueueInitializeDecoder(decoder);
 }
 
@@ -201,16 +204,20 @@ TrackBuffer::InitializeDecoder(nsRefPtr<
   nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
   nsresult rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
   reader->SetIdle();
   if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) {
     // XXX: Need to signal error back to owning SourceBuffer.
     MSE_DEBUG("TrackBuffer(%p): Reader %p failed to initialize rv=%x audio=%d video=%d",
               this, reader, rv, mi.HasAudio(), mi.HasVideo());
     aDecoder->SetTaskQueue(nullptr);
+    {
+        ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+        mDecoders.RemoveElement(aDecoder);
+    }
     NS_DispatchToMainThread(new ReleaseDecoderTask(aDecoder));
     return;
   }
 
   if (mi.HasVideo()) {
     MSE_DEBUG("TrackBuffer(%p): Reader %p video resolution=%dx%d",
               this, reader, mi.mVideo.mDisplay.width, mi.mVideo.mDisplay.height);
   }
@@ -225,24 +232,24 @@ TrackBuffer::InitializeDecoder(nsRefPtr<
 
 void
 TrackBuffer::RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   aDecoder->SetTaskQueue(nullptr);
   const MediaInfo& info = aDecoder->GetReader()->GetMediaInfo();
   // Initialize the track info since this is the first decoder.
-  if (mDecoders.IsEmpty()) {
+  if (mInitializedDecoders.IsEmpty()) {
     mHasAudio = info.HasAudio();
     mHasVideo = info.HasVideo();
     mParentDecoder->OnTrackBufferConfigured(this, info);
   } else if ((info.HasAudio() && !mHasAudio) || (info.HasVideo() && !mHasVideo)) {
     MSE_DEBUG("TrackBuffer(%p)::RegisterDecoder with mismatched audio/video tracks", this);
   }
-  mDecoders.AppendElement(aDecoder);
+  mInitializedDecoders.AppendElement(aDecoder);
 }
 
 void
 TrackBuffer::DiscardDecoder()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   if (mCurrentDecoder) {
     mCurrentDecoder->GetResource()->Ended();
@@ -265,17 +272,17 @@ TrackBuffer::HasInitSegment()
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   return mHasInit;
 }
 
 bool
 TrackBuffer::IsReady()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  MOZ_ASSERT((mHasAudio || mHasVideo) || mDecoders.IsEmpty());
+  MOZ_ASSERT((mHasAudio || mHasVideo) || mInitializedDecoders.IsEmpty());
   return HasInitSegment() && (mHasAudio || mHasVideo);
 }
 
 void
 TrackBuffer::LastTimestamp(double& aStart, double& aEnd)
 {
   MOZ_ASSERT(NS_IsMainThread());
   aStart = mLastStartTimestamp;
@@ -295,45 +302,47 @@ TrackBuffer::SetLastEndTimestamp(double 
   MOZ_ASSERT(NS_IsMainThread());
   mLastEndTimestamp = aEnd;
 }
 
 bool
 TrackBuffer::ContainsTime(double aTime)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+  for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
-    mDecoders[i]->GetBuffered(r);
+    mInitializedDecoders[i]->GetBuffered(r);
     if (r->Find(aTime) != dom::TimeRanges::NoIndex) {
       return true;
     }
   }
 
   return false;
 }
 
 void
 TrackBuffer::BreakCycles()
 {
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     mDecoders[i]->GetReader()->BreakCycles();
   }
-  mDecoders.Clear();
+  mInitializedDecoders.Clear();
+  NS_DispatchToMainThread(new ReleaseDecoderTask(mDecoders));
+  MOZ_ASSERT(mDecoders.IsEmpty());
   mParentDecoder = nullptr;
 }
 
 void
 TrackBuffer::ResetDecode()
 {
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     mDecoders[i]->GetReader()->ResetDecode();
   }
 }
 
 const nsTArray<nsRefPtr<SourceBufferDecoder>>&
 TrackBuffer::Decoders()
 {
   // XXX assert OnDecodeThread
-  return mDecoders;
+  return mInitializedDecoders;
 }
 
 } // namespace mozilla
--- a/content/media/mediasource/TrackBuffer.h
+++ b/content/media/mediasource/TrackBuffer.h
@@ -72,19 +72,19 @@ public:
   // contain aTime in their buffered ranges.
   bool ContainsTime(double aTime);
 
   void BreakCycles();
 
   // Call ResetDecode() on each decoder in mDecoders.
   void ResetDecode();
 
-  // Returns a reference to mDecoders, used by MediaSourceReader to select
-  // decoders.
-  // TODO: Refactor to a clenaer interface between TrackBuffer and MediaSourceReader.
+  // Returns a reference to mInitializedDecoders, used by MediaSourceReader
+  // to select decoders.
+  // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader.
   const nsTArray<nsRefPtr<SourceBufferDecoder>>& Decoders();
 
 private:
   ~TrackBuffer();
 
   // Queue execution of InitializeDecoder on mTaskQueue.
   bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
@@ -97,20 +97,24 @@ private:
   // from the decode thread pool.
   void RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
   // A task queue using the shared media thread pool.  Used exclusively to
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
-  // All of the initialized decoders managed by this TrackBuffer.  Access
-  // protected by mParentDecoder's monitor.
+  // All of the decoders managed by this TrackBuffer.  Access protected by
+  // mParentDecoder's monitor.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
 
+  // Contains only the initialized decoders managed by this TrackBuffer.
+  // Access protected by mParentDecoder's monitor.
+  nsTArray<nsRefPtr<SourceBufferDecoder>> mInitializedDecoders;
+
   // The decoder that the owning SourceBuffer is currently appending data to.
   nsRefPtr<SourceBufferDecoder> mCurrentDecoder;
 
   nsRefPtr<MediaSourceDecoder> mParentDecoder;
   const nsCString mType;
 
   // The last start and end timestamps added to the TrackBuffer via
   // AppendData.  Accessed on the main thread only.
--- a/content/media/mediasource/test/mochitest.ini
+++ b/content/media/mediasource/test/mochitest.ini
@@ -5,8 +5,11 @@ support-files = seek.webm seek.webm^head
 [test_MediaSource.html]
 skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
 
 [test_SplitAppend.html]
 skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
 
 [test_SplitAppendDelay.html]
 skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
+
+[test_BufferedSeek.html]
+skip-if = buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
new file mode 100644
--- /dev/null
+++ b/content/media/mediasource/test/test_BufferedSeek.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>MSE: seeking in buffered range</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+  ok(!window.MediaSource, "MediaSource should be hidden behind a pref");
+  var accessThrows = false;
+  try {
+    new MediaSource();
+  } catch (e) {
+    accessThrows = true;
+  }
+  ok(accessThrows, "MediaSource should be hidden behind a pref");
+  SpecialPowers.pushPrefEnv({"set": [[ "media.mediasource.enabled", true ]]},
+                            function () {
+    SpecialPowers.setBoolPref("media.mediasource.enabled", true);
+    var ms = new MediaSource();
+    ok(ms, "Create a MediaSource object");
+    ok(ms instanceof EventTarget, "MediaSource must be an EventTarget");
+    is(ms.readyState, "closed", "New MediaSource must be in closed state");
+    // Force wrapper creation, tests for leaks.
+    ms.foo = null;
+    var o = URL.createObjectURL(ms);
+    ok(o, "Create an objectURL from the MediaSource");
+    var v = document.createElement("video");
+    v.preload = "auto";
+    document.body.appendChild(v);
+    v.src = o;
+    ms.addEventListener("sourceopen", function () {
+      ok(true, "Receive a sourceopen event");
+      is(ms.readyState, "open", "MediaSource must be in open state after sourceopen");
+      var sb = ms.addSourceBuffer("video/webm");
+      ok(sb, "Create a SourceBuffer");
+      is(ms.sourceBuffers.length, 1, "MediaSource.sourceBuffers is expected length");
+      is(ms.sourceBuffers[0], sb, "SourceBuffer in list matches our SourceBuffer");
+      fetch("seek.webm", function (blob) {
+        var r = new FileReader();
+        r.addEventListener("load", function (e) {
+          sb.appendBuffer(new Uint8Array(e.target.result));
+          ms.endOfStream();
+          var target = 2;
+          v.addEventListener("loadedmetadata", function () {
+            if (v.buffered.length && v.currentTime != target &&
+                target >= v.buffered.start(0) &&
+                target < v.buffered.end(0)) {
+              v.currentTime = target;
+            }
+          });
+          var wasSeeking = false;
+          v.addEventListener("seeking", function () {
+            wasSeeking = true;
+            is(v.currentTime, target, "Video currentTime not at target");
+          });
+          v.addEventListener("seeked", function () {
+            ok(wasSeeking, "Received expected seeking and seeked events");
+            is(v.currentTime, target, "Video currentTime not at target");
+            v.parentNode.removeChild(v);
+            SimpleTest.finish();
+          });
+        });
+        r.readAsArrayBuffer(blob);
+      });
+    });
+    ms.addEventListener("sourceended", function () {
+      ok(true, "Receive a sourceended event");
+      is(ms.readyState, "ended", "MediaSource must be in ended state after sourceended");
+    });
+  });
+});
+
+function fetch(src, cb) {
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", src, true);
+  xhr.responseType = "blob";
+  xhr.addEventListener("load", function (e) {
+    if (xhr.status != 200) {
+      return false;
+    }
+    cb(xhr.response);
+  });
+  xhr.send();
+};
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/mediasource/test/test_MediaSource.html
+++ b/content/media/mediasource/test/test_MediaSource.html
@@ -1,12 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Test whether we can create an MediaSource interface</title>
+  <title>MSE: basic functionality</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
--- a/content/media/mediasource/test/test_SplitAppend.html
+++ b/content/media/mediasource/test/test_SplitAppend.html
@@ -1,12 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Test whether we can create an MediaSource interface</title>
+  <title>MSE: append initialization and media segment separately</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
--- a/content/media/mediasource/test/test_SplitAppendDelay.html
+++ b/content/media/mediasource/test/test_SplitAppendDelay.html
@@ -1,12 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>Test whether we can create an MediaSource interface</title>
+  <title>MSE: append segments with delay</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();