|
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/. */ |
|
5 |
|
6 #include "SourceBuffer.h" |
|
7 |
|
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" |
|
23 |
|
24 struct JSContext; |
|
25 class JSObject; |
|
26 |
|
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 |
|
33 |
|
34 namespace mozilla { |
|
35 |
|
36 class MediaResource; |
|
37 class ReentrantMonitor; |
|
38 |
|
39 namespace layers { |
|
40 |
|
41 class ImageContainer; |
|
42 |
|
43 } // namespace layers |
|
44 |
|
45 ReentrantMonitor& |
|
46 SubBufferDecoder::GetReentrantMonitor() |
|
47 { |
|
48 return mParentDecoder->GetReentrantMonitor(); |
|
49 } |
|
50 |
|
51 bool |
|
52 SubBufferDecoder::OnStateMachineThread() const |
|
53 { |
|
54 return mParentDecoder->OnStateMachineThread(); |
|
55 } |
|
56 |
|
57 bool |
|
58 SubBufferDecoder::OnDecodeThread() const |
|
59 { |
|
60 return mParentDecoder->OnDecodeThread(); |
|
61 } |
|
62 |
|
63 SourceBufferResource* |
|
64 SubBufferDecoder::GetResource() const |
|
65 { |
|
66 return static_cast<SourceBufferResource*>(mResource.get()); |
|
67 } |
|
68 |
|
69 void |
|
70 SubBufferDecoder::SetMediaDuration(int64_t aDuration) |
|
71 { |
|
72 mMediaDuration = aDuration; |
|
73 } |
|
74 |
|
75 void |
|
76 SubBufferDecoder::UpdateEstimatedMediaDuration(int64_t aDuration) |
|
77 { |
|
78 //mParentDecoder->UpdateEstimatedMediaDuration(aDuration); |
|
79 } |
|
80 |
|
81 void |
|
82 SubBufferDecoder::SetMediaSeekable(bool aMediaSeekable) |
|
83 { |
|
84 //mParentDecoder->SetMediaSeekable(aMediaSeekable); |
|
85 } |
|
86 |
|
87 void |
|
88 SubBufferDecoder::SetTransportSeekable(bool aTransportSeekable) |
|
89 { |
|
90 //mParentDecoder->SetTransportSeekable(aTransportSeekable); |
|
91 } |
|
92 |
|
93 layers::ImageContainer* |
|
94 SubBufferDecoder::GetImageContainer() |
|
95 { |
|
96 return mParentDecoder->GetImageContainer(); |
|
97 } |
|
98 |
|
99 MediaDecoderOwner* |
|
100 SubBufferDecoder::GetOwner() |
|
101 { |
|
102 return mParentDecoder->GetOwner(); |
|
103 } |
|
104 |
|
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 } |
|
120 |
|
121 class ContainerParser { |
|
122 public: |
|
123 virtual ~ContainerParser() {} |
|
124 |
|
125 virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength) |
|
126 { |
|
127 return false; |
|
128 } |
|
129 }; |
|
130 |
|
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 }; |
|
152 |
|
153 namespace dom { |
|
154 |
|
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 } |
|
170 |
|
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 } |
|
186 |
|
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 } |
|
211 |
|
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 } |
|
225 |
|
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 } |
|
240 |
|
241 void |
|
242 SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv) |
|
243 { |
|
244 aData.ComputeLengthAndData(); |
|
245 |
|
246 AppendData(aData.Data(), aData.Length(), aRv); |
|
247 } |
|
248 |
|
249 void |
|
250 SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv) |
|
251 { |
|
252 aData.ComputeLengthAndData(); |
|
253 |
|
254 AppendData(aData.Data(), aData.Length(), aRv); |
|
255 } |
|
256 |
|
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>(); |
|
276 |
|
277 MSE_DEBUG("%p Abort: Discarding decoders.", this); |
|
278 if (mCurrentDecoder) { |
|
279 mCurrentDecoder->GetResource()->Ended(); |
|
280 mCurrentDecoder = nullptr; |
|
281 } |
|
282 } |
|
283 |
|
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 } |
|
302 |
|
303 void |
|
304 SourceBuffer::Detach() |
|
305 { |
|
306 Ended(); |
|
307 mDecoders.Clear(); |
|
308 mCurrentDecoder = nullptr; |
|
309 mMediaSource = nullptr; |
|
310 } |
|
311 |
|
312 void |
|
313 SourceBuffer::Ended() |
|
314 { |
|
315 for (uint32_t i = 0; i < mDecoders.Length(); ++i) { |
|
316 mDecoders[i]->GetResource()->Ended(); |
|
317 } |
|
318 } |
|
319 |
|
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 } |
|
338 |
|
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 } |
|
345 |
|
346 SourceBuffer::~SourceBuffer() |
|
347 { |
|
348 for (uint32_t i = 0; i < mDecoders.Length(); ++i) { |
|
349 mDecoders[i]->GetResource()->Ended(); |
|
350 } |
|
351 } |
|
352 |
|
353 MediaSource* |
|
354 SourceBuffer::GetParentObject() const |
|
355 { |
|
356 return mMediaSource; |
|
357 } |
|
358 |
|
359 JSObject* |
|
360 SourceBuffer::WrapObject(JSContext* aCx) |
|
361 { |
|
362 return SourceBufferBinding::Wrap(aCx, this); |
|
363 } |
|
364 |
|
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 } |
|
371 |
|
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 } |
|
379 |
|
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 } |
|
395 |
|
396 void |
|
397 SourceBuffer::StartUpdating() |
|
398 { |
|
399 MOZ_ASSERT(!mUpdating); |
|
400 mUpdating = true; |
|
401 QueueAsyncSimpleEvent("updatestart"); |
|
402 } |
|
403 |
|
404 void |
|
405 SourceBuffer::StopUpdating() |
|
406 { |
|
407 MOZ_ASSERT(mUpdating); |
|
408 mUpdating = false; |
|
409 QueueAsyncSimpleEvent("update"); |
|
410 QueueAsyncSimpleEvent("updateend"); |
|
411 } |
|
412 |
|
413 void |
|
414 SourceBuffer::AbortUpdating() |
|
415 { |
|
416 MOZ_ASSERT(mUpdating); |
|
417 mUpdating = false; |
|
418 QueueAsyncSimpleEvent("abort"); |
|
419 QueueAsyncSimpleEvent("updateend"); |
|
420 } |
|
421 |
|
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); |
|
452 |
|
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); |
|
464 |
|
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(); |
|
470 |
|
471 // Schedule the state machine thread to ensure playback starts |
|
472 // if required when data is appended. |
|
473 mMediaSource->GetDecoder()->ScheduleStateMachineThread(); |
|
474 } |
|
475 |
|
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 } |
|
488 |
|
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 } |
|
502 |
|
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 } |
|
519 |
|
520 NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBuffer, DOMEventTargetHelper, |
|
521 mMediaSource) |
|
522 |
|
523 NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper) |
|
524 NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper) |
|
525 |
|
526 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer) |
|
527 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
|
528 |
|
529 } // namespace dom |
|
530 |
|
531 } // namespace mozilla |