content/media/mediasource/SourceBuffer.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "SourceBuffer.h"
     8 #include "AsyncEventRunner.h"
     9 #include "DecoderTraits.h"
    10 #include "MediaDecoder.h"
    11 #include "MediaSourceDecoder.h"
    12 #include "SourceBufferResource.h"
    13 #include "mozilla/ErrorResult.h"
    14 #include "mozilla/FloatingPoint.h"
    15 #include "mozilla/dom/MediaSourceBinding.h"
    16 #include "mozilla/dom/TimeRanges.h"
    17 #include "nsError.h"
    18 #include "nsIEventTarget.h"
    19 #include "nsIRunnable.h"
    20 #include "nsThreadUtils.h"
    21 #include "prlog.h"
    22 #include "SubBufferDecoder.h"
    24 struct JSContext;
    25 class JSObject;
    27 #ifdef PR_LOGGING
    28 extern PRLogModuleInfo* gMediaSourceLog;
    29 #define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
    30 #else
    31 #define MSE_DEBUG(...)
    32 #endif
    34 namespace mozilla {
    36 class MediaResource;
    37 class ReentrantMonitor;
    39 namespace layers {
    41 class ImageContainer;
    43 } // namespace layers
    45 ReentrantMonitor&
    46 SubBufferDecoder::GetReentrantMonitor()
    47 {
    48   return mParentDecoder->GetReentrantMonitor();
    49 }
    51 bool
    52 SubBufferDecoder::OnStateMachineThread() const
    53 {
    54   return mParentDecoder->OnStateMachineThread();
    55 }
    57 bool
    58 SubBufferDecoder::OnDecodeThread() const
    59 {
    60   return mParentDecoder->OnDecodeThread();
    61 }
    63 SourceBufferResource*
    64 SubBufferDecoder::GetResource() const
    65 {
    66   return static_cast<SourceBufferResource*>(mResource.get());
    67 }
    69 void
    70 SubBufferDecoder::SetMediaDuration(int64_t aDuration)
    71 {
    72   mMediaDuration = aDuration;
    73 }
    75 void
    76 SubBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
    77 {
    78   //mParentDecoder->UpdateEstimatedMediaDuration(aDuration);
    79 }
    81 void
    82 SubBufferDecoder::SetMediaSeekable(bool aMediaSeekable)
    83 {
    84   //mParentDecoder->SetMediaSeekable(aMediaSeekable);
    85 }
    87 void
    88 SubBufferDecoder::SetTransportSeekable(bool aTransportSeekable)
    89 {
    90   //mParentDecoder->SetTransportSeekable(aTransportSeekable);
    91 }
    93 layers::ImageContainer*
    94 SubBufferDecoder::GetImageContainer()
    95 {
    96   return mParentDecoder->GetImageContainer();
    97 }
    99 MediaDecoderOwner*
   100 SubBufferDecoder::GetOwner()
   101 {
   102   return mParentDecoder->GetOwner();
   103 }
   105 int64_t
   106 SubBufferDecoder::ConvertToByteOffset(double aTime)
   107 {
   108   // Uses a conversion based on (aTime/duration) * length.  For the
   109   // purposes of eviction this should be adequate since we have the
   110   // byte threshold as well to ensure data actually gets evicted and
   111   // we ensure we don't evict before the current playable point.
   112   if (mMediaDuration == -1) {
   113     return -1;
   114   }
   115   int64_t length = GetResource()->GetLength();
   116   MOZ_ASSERT(length > 0);
   117   int64_t offset = (aTime / (double(mMediaDuration) / USECS_PER_S)) * length;
   118   return offset;
   119 }
   121 class ContainerParser {
   122 public:
   123   virtual ~ContainerParser() {}
   125   virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
   126   {
   127     return false;
   128   }
   129 };
   131 class WebMContainerParser : public ContainerParser {
   132 public:
   133   bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
   134   {
   135     // XXX: This is overly primitive, needs to collect data as it's appended
   136     // to the SB and handle, rather than assuming everything is present in a
   137     // single aData segment.
   138     // 0x1a45dfa3 // EBML
   139     // ...
   140     // DocType == "webm"
   141     // ...
   142     // 0x18538067 // Segment (must be "unknown" size)
   143     // 0x1549a966 // -> Segment Info
   144     // 0x1654ae6b // -> One or more Tracks
   145     if (aLength >= 4 &&
   146         aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) {
   147       return true;
   148     }
   149     return false;
   150   }
   151 };
   153 namespace dom {
   155 void
   156 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
   157 {
   158   if (!IsAttached() || mUpdating) {
   159     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   160     return;
   161   }
   162   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   163   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
   164     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   165   }
   166   // TODO: Test append state.
   167   // TODO: If aMode is "sequence", set sequence start time.
   168   mAppendMode = aMode;
   169 }
   171 void
   172 SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
   173 {
   174   if (!IsAttached() || mUpdating) {
   175     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   176     return;
   177   }
   178   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   179   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
   180     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   181   }
   182   // TODO: Test append state.
   183   // TODO: If aMode is "sequence", set sequence start time.
   184   mTimestampOffset = aTimestampOffset;
   185 }
   187 already_AddRefed<TimeRanges>
   188 SourceBuffer::GetBuffered(ErrorResult& aRv)
   189 {
   190   if (!IsAttached()) {
   191     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   192     return nullptr;
   193   }
   194   nsRefPtr<TimeRanges> ranges = new TimeRanges();
   195   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
   196     nsRefPtr<TimeRanges> r = new TimeRanges();
   197     mDecoders[i]->GetBuffered(r);
   198     if (r->Length() > 0) {
   199       MSE_DEBUG("%p GetBuffered decoder=%u Length=%u Start=%f End=%f", this, i, r->Length(),
   200                 r->GetStartTime(), r->GetEndTime());
   201       ranges->Add(r->GetStartTime(), r->GetEndTime());
   202     } else {
   203       MSE_DEBUG("%p GetBuffered decoder=%u Length=%u", this, i, r->Length());
   204     }
   205   }
   206   ranges->Normalize();
   207   MSE_DEBUG("%p GetBuffered Length=%u Start=%f End=%f", this, ranges->Length(),
   208             ranges->GetStartTime(), ranges->GetEndTime());
   209   return ranges.forget();
   210 }
   212 void
   213 SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
   214 {
   215   if (!IsAttached() || mUpdating) {
   216     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   217     return;
   218   }
   219   if (aAppendWindowStart < 0 || aAppendWindowStart >= mAppendWindowEnd) {
   220     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
   221     return;
   222   }
   223   mAppendWindowStart = aAppendWindowStart;
   224 }
   226 void
   227 SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv)
   228 {
   229   if (!IsAttached() || mUpdating) {
   230     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   231     return;
   232   }
   233   if (IsNaN(aAppendWindowEnd) ||
   234       aAppendWindowEnd <= mAppendWindowStart) {
   235     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
   236     return;
   237   }
   238   mAppendWindowEnd = aAppendWindowEnd;
   239 }
   241 void
   242 SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv)
   243 {
   244   aData.ComputeLengthAndData();
   246   AppendData(aData.Data(), aData.Length(), aRv);
   247 }
   249 void
   250 SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv)
   251 {
   252   aData.ComputeLengthAndData();
   254   AppendData(aData.Data(), aData.Length(), aRv);
   255 }
   257 void
   258 SourceBuffer::Abort(ErrorResult& aRv)
   259 {
   260   MSE_DEBUG("%p Abort()", this);
   261   if (!IsAttached()) {
   262     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   263     return;
   264   }
   265   if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
   266     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   267     return;
   268   }
   269   if (mUpdating) {
   270     // TODO: Abort segment parser loop, buffer append, and stream append loop algorithms.
   271     AbortUpdating();
   272   }
   273   // TODO: Run reset parser algorithm.
   274   mAppendWindowStart = 0;
   275   mAppendWindowEnd = PositiveInfinity<double>();
   277   MSE_DEBUG("%p Abort: Discarding decoders.", this);
   278   if (mCurrentDecoder) {
   279     mCurrentDecoder->GetResource()->Ended();
   280     mCurrentDecoder = nullptr;
   281   }
   282 }
   284 void
   285 SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
   286 {
   287   MSE_DEBUG("%p Remove(Start=%f End=%f)", this, aStart, aEnd);
   288   if (!IsAttached() || mUpdating ||
   289       mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
   290     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   291     return;
   292   }
   293   if (aStart < 0 || aStart > mMediaSource->Duration() ||
   294       aEnd <= aStart) {
   295     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
   296     return;
   297   }
   298   StartUpdating();
   299   /// TODO: Run coded frame removal algorithm asynchronously (would call StopUpdating()).
   300   StopUpdating();
   301 }
   303 void
   304 SourceBuffer::Detach()
   305 {
   306   Ended();
   307   mDecoders.Clear();
   308   mCurrentDecoder = nullptr;
   309   mMediaSource = nullptr;
   310 }
   312 void
   313 SourceBuffer::Ended()
   314 {
   315   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
   316     mDecoders[i]->GetResource()->Ended();
   317   }
   318 }
   320 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   321   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   322   , mMediaSource(aMediaSource)
   323   , mType(aType)
   324   , mAppendWindowStart(0)
   325   , mAppendWindowEnd(PositiveInfinity<double>())
   326   , mTimestampOffset(0)
   327   , mAppendMode(SourceBufferAppendMode::Segments)
   328   , mUpdating(false)
   329 {
   330   MOZ_ASSERT(aMediaSource);
   331   if (mType.EqualsIgnoreCase("video/webm") || mType.EqualsIgnoreCase("audio/webm")) {
   332     mParser = new WebMContainerParser();
   333   } else {
   334     // XXX: Plug in parsers for MPEG4, etc. here.
   335     mParser = new ContainerParser();
   336   }
   337 }
   339 already_AddRefed<SourceBuffer>
   340 SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType)
   341 {
   342   nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(aMediaSource, aType);
   343   return sourceBuffer.forget();
   344 }
   346 SourceBuffer::~SourceBuffer()
   347 {
   348   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
   349     mDecoders[i]->GetResource()->Ended();
   350   }
   351 }
   353 MediaSource*
   354 SourceBuffer::GetParentObject() const
   355 {
   356   return mMediaSource;
   357 }
   359 JSObject*
   360 SourceBuffer::WrapObject(JSContext* aCx)
   361 {
   362   return SourceBufferBinding::Wrap(aCx, this);
   363 }
   365 void
   366 SourceBuffer::DispatchSimpleEvent(const char* aName)
   367 {
   368   MSE_DEBUG("%p Dispatching event %s to SourceBuffer", this, aName);
   369   DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
   370 }
   372 void
   373 SourceBuffer::QueueAsyncSimpleEvent(const char* aName)
   374 {
   375   MSE_DEBUG("%p Queuing event %s to SourceBuffer", this, aName);
   376   nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName);
   377   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   378 }
   380 bool
   381 SourceBuffer::InitNewDecoder()
   382 {
   383   MediaSourceDecoder* parentDecoder = mMediaSource->GetDecoder();
   384   nsRefPtr<SubBufferDecoder> decoder = parentDecoder->CreateSubDecoder(mType);
   385   if (!decoder) {
   386     return false;
   387   }
   388   mDecoders.AppendElement(decoder);
   389   // XXX: At this point, we really want to push through any remaining
   390   // processing for the old decoder and discard it, rather than hanging on
   391   // to all of them in mDecoders.
   392   mCurrentDecoder = decoder;
   393   return true;
   394 }
   396 void
   397 SourceBuffer::StartUpdating()
   398 {
   399   MOZ_ASSERT(!mUpdating);
   400   mUpdating = true;
   401   QueueAsyncSimpleEvent("updatestart");
   402 }
   404 void
   405 SourceBuffer::StopUpdating()
   406 {
   407   MOZ_ASSERT(mUpdating);
   408   mUpdating = false;
   409   QueueAsyncSimpleEvent("update");
   410   QueueAsyncSimpleEvent("updateend");
   411 }
   413 void
   414 SourceBuffer::AbortUpdating()
   415 {
   416   MOZ_ASSERT(mUpdating);
   417   mUpdating = false;
   418   QueueAsyncSimpleEvent("abort");
   419   QueueAsyncSimpleEvent("updateend");
   420 }
   422 void
   423 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
   424 {
   425   MSE_DEBUG("%p AppendBuffer(Data=%u bytes)", this, aLength);
   426   if (!IsAttached() || mUpdating) {
   427     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   428     return;
   429   }
   430   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
   431     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   432   }
   433   // TODO: Run coded frame eviction algorithm.
   434   // TODO: Test buffer full flag.
   435   StartUpdating();
   436   // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
   437   if (mParser->IsInitSegmentPresent(aData, aLength) || !mCurrentDecoder) {
   438     MSE_DEBUG("%p AppendBuffer: New initialization segment, switching decoders.", this);
   439     if (mCurrentDecoder) {
   440       mCurrentDecoder->GetResource()->Ended();
   441     }
   442     if (!InitNewDecoder()) {
   443       aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
   444       return;
   445     }
   446   }
   447   // XXX: For future reference: NDA call must run on the main thread.
   448   mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
   449                                      aLength,
   450                                      mCurrentDecoder->GetResource()->GetLength());
   451   mCurrentDecoder->GetResource()->AppendData(aData, aLength);
   453   // Eviction uses a byte threshold. If the buffer is greater than the
   454   // number of bytes then data is evicted. The time range for this
   455   // eviction is reported back to the media source. It will then
   456   // evict data before that range across all SourceBuffer's it knows
   457   // about.
   458   const int evict_threshold = 1000000;
   459   bool evicted = mCurrentDecoder->GetResource()->EvictData(evict_threshold);
   460   if (evicted) {
   461     double start = 0.0;
   462     double end = 0.0;
   463     GetBufferedStartEndTime(&start, &end);
   465     // We notify that we've evicted from the time range 0 through to
   466     // the current start point.
   467     mMediaSource->NotifyEvicted(0.0, start);
   468   }
   469   StopUpdating();
   471   // Schedule the state machine thread to ensure playback starts
   472   // if required when data is appended.
   473   mMediaSource->GetDecoder()->ScheduleStateMachineThread();
   474 }
   476 void
   477 SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd)
   478 {
   479   ErrorResult dummy;
   480   nsRefPtr<TimeRanges> ranges = GetBuffered(dummy);
   481   if (!ranges || ranges->Length() == 0) {
   482     *aStart = *aEnd = 0.0;
   483     return;
   484   }
   485   *aStart = ranges->Start(0, dummy);
   486   *aEnd = ranges->End(ranges->Length() - 1, dummy);
   487 }
   489 void
   490 SourceBuffer::Evict(double aStart, double aEnd)
   491 {
   492   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
   493     // Need to map time to byte offset then evict
   494     int64_t end = mDecoders[i]->ConvertToByteOffset(aEnd);
   495     if (end <= 0) {
   496       NS_WARNING("SourceBuffer::Evict failed");
   497       continue;
   498     }
   499     mDecoders[i]->GetResource()->EvictBefore(end);
   500   }
   501 }
   503 bool
   504 SourceBuffer::ContainsTime(double aTime)
   505 {
   506   ErrorResult dummy;
   507   nsRefPtr<TimeRanges> ranges = GetBuffered(dummy);
   508   if (!ranges || ranges->Length() == 0) {
   509     return false;
   510   }
   511   for (uint32_t i = 0; i < ranges->Length(); ++i) {
   512     if (aTime >= ranges->Start(i, dummy) &&
   513         aTime <= ranges->End(i, dummy)) {
   514       return true;
   515     }
   516   }
   517   return false;
   518 }
   520 NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBuffer, DOMEventTargetHelper,
   521                                    mMediaSource)
   523 NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper)
   524 NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper)
   526 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer)
   527 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
   529 } // namespace dom
   531 } // namespace mozilla

mercurial