Tue, 06 Jan 2015 21:39:09 +0100
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