Bug 881512 - Allow a SourceBuffer to own multiple subdecoders. Switch decoders on abort(). r=cajbir
authorMatthew Gregan <kinetik@flim.org>
Mon, 14 Apr 2014 23:24:00 +1200
changeset 180736 d978fe16633a715172f74b08a0feb8f522e83c2b
parent 180735 c570bcfd29b4d6df0b3ea5d92ec7e12e8f2e0ad3
child 180737 f8ece490919dd18b8f736e910d771ec3f7573fe3
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewerscajbir
bugs881512
milestone31.0a1
Bug 881512 - Allow a SourceBuffer to own multiple subdecoders. Switch decoders on abort(). r=cajbir
content/media/mediasource/MediaSource.cpp
content/media/mediasource/SourceBuffer.cpp
content/media/mediasource/SourceBuffer.h
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -160,16 +160,20 @@ MediaSource::AddSourceBuffer(const nsASt
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   rv = parser.GetType(mimeType);
   if (NS_FAILED(rv)) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
   nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, NS_ConvertUTF16toUTF8(mimeType));
+  if (!sourceBuffer->Init()) {
+    aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
+    return nullptr;
+  }
   mSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("%p AddSourceBuffer(Type=%s) -> %p", this,
             NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get());
   return sourceBuffer.forget();
 }
 
 void
 MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -150,17 +150,30 @@ SourceBuffer::SetTimestampOffset(double 
 already_AddRefed<TimeRanges>
 SourceBuffer::GetBuffered(ErrorResult& aRv)
 {
   if (!IsAttached()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
   nsRefPtr<TimeRanges> ranges = new TimeRanges();
-  mDecoder->GetBuffered(ranges);
+  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+    nsRefPtr<TimeRanges> r = new TimeRanges();
+    mDecoders[i]->GetBuffered(r);
+    if (r->Length() > 0) {
+      MSE_DEBUG("%p GetBuffered decoder=%u Length=%u Start=%f End=%f", this, i, r->Length(),
+                r->GetStartTime(), r->GetEndTime());
+      ranges->Add(r->GetStartTime(), r->GetEndTime());
+    } else {
+      MSE_DEBUG("%p GetBuffered decoder=%u Length=%u", this, i, r->Length());
+    }
+  }
+  ranges->Normalize();
+  MSE_DEBUG("%p GetBuffered Length=%u Start=%f End=%f", this, ranges->Length(),
+            ranges->GetStartTime(), ranges->GetEndTime());
   return ranges.forget();
 }
 
 void
 SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
 {
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@@ -198,36 +211,44 @@ void
 SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv)
 {
   AppendData(aData.Data(), aData.Length(), aRv);
 }
 
 void
 SourceBuffer::Abort(ErrorResult& aRv)
 {
+  MSE_DEBUG("%p Abort()", this);
   if (!IsAttached()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (mUpdating) {
     // TODO: Abort segment parser loop, buffer append, and stream append loop algorithms.
     AbortUpdating();
   }
   // TODO: Run reset parser algorithm.
   mAppendWindowStart = 0;
   mAppendWindowEnd = PositiveInfinity<double>();
+
+  MSE_DEBUG("%p Abort: Switching decoders.", this);
+  mCurrentDecoder->GetResource()->Ended();
+  if (!InitNewDecoder()) {
+    aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
+  }
 }
 
 void
 SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
 {
+  MSE_DEBUG("%p Remove(Start=%f End=%f)", this, aStart, aEnd);
   if (!IsAttached() || mUpdating ||
       mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (aStart < 0 || aStart > mMediaSource->Duration() ||
       aEnd <= aStart) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
@@ -237,45 +258,46 @@ SourceBuffer::Remove(double aStart, doub
   /// TODO: Run coded frame removal algorithm asynchronously (would call StopUpdating()).
   StopUpdating();
 }
 
 void
 SourceBuffer::Detach()
 {
   Ended();
-  mDecoder = nullptr;
+  mDecoders.Clear();
+  mCurrentDecoder = nullptr;
   mMediaSource = nullptr;
 }
 
 void
 SourceBuffer::Ended()
 {
-  mDecoder->GetResource()->Ended();
+  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+    mDecoders[i]->GetResource()->Ended();
+  }
 }
 
 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
+  , mType(aType)
   , mAppendWindowStart(0)
   , mAppendWindowEnd(PositiveInfinity<double>())
   , mTimestampOffset(0)
   , mAppendMode(SourceBufferAppendMode::Segments)
   , mUpdating(false)
 {
   MOZ_ASSERT(aMediaSource);
-  MediaSourceDecoder* parentDecoder = aMediaSource->GetDecoder();
-  mDecoder = parentDecoder->CreateSubDecoder(aType);
-  MOZ_ASSERT(mDecoder);
 }
 
 SourceBuffer::~SourceBuffer()
 {
-  if (mDecoder) {
-    mDecoder->GetResource()->Ended();
+  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+    mDecoders[i]->GetResource()->Ended();
   }
 }
 
 MediaSource*
 SourceBuffer::GetParentObject() const
 {
   return mMediaSource;
 }
@@ -296,16 +318,32 @@ SourceBuffer::DispatchSimpleEvent(const 
 void
 SourceBuffer::QueueAsyncSimpleEvent(const char* aName)
 {
   MSE_DEBUG("%p Queuing event %s to SourceBuffer", this, aName);
   nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName);
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
 }
 
+bool
+SourceBuffer::InitNewDecoder()
+{
+  MediaSourceDecoder* parentDecoder = mMediaSource->GetDecoder();
+  nsRefPtr<SubBufferDecoder> decoder = parentDecoder->CreateSubDecoder(mType);
+  if (!decoder) {
+    return false;
+  }
+  mDecoders.AppendElement(decoder);
+  // XXX: At this point, we really want to push through any remaining
+  // processing for the old decoder and discard it, rather than hanging on
+  // to all of them in mDecoders.
+  mCurrentDecoder = decoder;
+  return true;
+}
+
 void
 SourceBuffer::StartUpdating()
 {
   MOZ_ASSERT(!mUpdating);
   mUpdating = true;
   QueueAsyncSimpleEvent("updatestart");
 }
 
@@ -325,41 +363,41 @@ SourceBuffer::AbortUpdating()
   mUpdating = false;
   QueueAsyncSimpleEvent("abort");
   QueueAsyncSimpleEvent("updateend");
 }
 
 void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
+  MSE_DEBUG("%p AppendBuffer(Data=%u bytes)", this, aLength);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
   // TODO: Run coded frame eviction algorithm.
   // TODO: Test buffer full flag.
-  MSE_DEBUG("%p Append(ArrayBuffer=%u)", this, aLength);
   StartUpdating();
   // XXX: For future reference: NDA call must run on the main thread.
-  mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
-                              aLength,
-                              mDecoder->GetResource()->GetLength());
+  mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
+                                     aLength,
+                                     mCurrentDecoder->GetResource()->GetLength());
   // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
-  mDecoder->GetResource()->AppendData(aData, aLength);
+  mCurrentDecoder->GetResource()->AppendData(aData, aLength);
 
   // Eviction uses a byte threshold. If the buffer is greater than the
   // number of bytes then data is evicted. The time range for this
   // eviction is reported back to the media source. It will then
   // evict data before that range across all SourceBuffer's it knows
   // about.
   const int evict_threshold = 1000000;
-  bool evicted = mDecoder->GetResource()->EvictData(evict_threshold);
+  bool evicted = mCurrentDecoder->GetResource()->EvictData(evict_threshold);
   if (evicted) {
     double start = 0.0;
     double end = 0.0;
     GetBufferedStartEndTime(&start, &end);
 
     // We notify that we've evicted from the time range 0 through to
     // the current start point.
     mMediaSource->NotifyEvicted(0.0, start);
@@ -369,38 +407,37 @@ SourceBuffer::AppendData(const uint8_t* 
   // Schedule the state machine thread to ensure playback starts
   // if required when data is appended.
   mMediaSource->GetDecoder()->ScheduleStateMachineThread();
 }
 
 void
 SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd)
 {
-  nsRefPtr<TimeRanges> ranges = new TimeRanges();
-  mDecoder->GetBuffered(ranges);
-  ranges->Normalize();
-  int length = ranges->Length();
-  ErrorResult rv;
-
-  if (aStart) {
-    *aStart = length > 0 ? ranges->Start(0, rv) : 0.0;
+  ErrorResult dummy;
+  nsRefPtr<TimeRanges> ranges = GetBuffered(dummy);
+  if (!ranges || ranges->Length() == 0) {
+    *aStart = *aEnd = 0.0;
+    return;
   }
-
-  if (aEnd) {
-    *aEnd = length > 0 ? ranges->End(length - 1, rv) : 0.0;
-  }
+  *aStart = ranges->Start(0, dummy);
+  *aEnd = ranges->End(ranges->Length() - 1, dummy);
 }
 
 void
 SourceBuffer::Evict(double aStart, double aEnd)
 {
-  // Need to map time to byte offset then evict
-  int64_t end = mDecoder->ConvertToByteOffset(aEnd);
-  if (end > 0) {
-    mDecoder->GetResource()->EvictBefore(end);
+  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+    // Need to map time to byte offset then evict
+    int64_t end = mDecoders[i]->ConvertToByteOffset(aEnd);
+    if (end <= 0) {
+      NS_WARNING("SourceBuffer::Evict failed");
+      continue;
+    }
+    mDecoders[i]->GetResource()->EvictBefore(end);
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_1(SourceBuffer, DOMEventTargetHelper,
                                      mMediaSource)
 
 NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper)
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -103,36 +103,51 @@ public:
     return mMediaSource != nullptr;
   }
 
   void Ended();
 
   // Evict data in the source buffer in the given time range.
   void Evict(double aStart, double aEnd);
 
+  // Initialize this SourceBuffer.  Must be called before use.
+  bool Init()
+  {
+    MOZ_ASSERT(!mCurrentDecoder);
+    return InitNewDecoder();
+  }
+
 private:
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
+  // Create a new decoder for mType, add it to mDecoders and update mCurrentDecoder.
+  bool InitNewDecoder();
+
   // Update mUpdating and fire the appropriate events.
   void StartUpdating();
   void StopUpdating();
   void AbortUpdating();
 
   // Shared implementation of AppendBuffer overloads.
   void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
 
   // Provide the minimum start time and maximum end time that is available
   // in the data buffered by this SourceBuffer.
   void GetBufferedStartEndTime(double* aStart, double* aEnd);
 
   nsRefPtr<MediaSource> mMediaSource;
 
-  nsRefPtr<SubBufferDecoder> mDecoder;
+  const nsAutoCString mType;
+
+  // XXX: We only want to keep the current decoder alive, but need a way to
+  // query @buffered for everything this SourceBuffer is responsible for.
+  nsTArray<nsRefPtr<SubBufferDecoder>> mDecoders;
+  nsRefPtr<SubBufferDecoder> mCurrentDecoder;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;
 
   double mTimestampOffset;
 
   SourceBufferAppendMode mAppendMode;
   bool mUpdating;