/src/mozilla-central/dom/media/mediasource/SourceBuffer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "SourceBuffer.h" |
8 | | |
9 | | #include "AsyncEventRunner.h" |
10 | | #include "MediaData.h" |
11 | | #include "MediaSourceDemuxer.h" |
12 | | #include "MediaSourceUtils.h" |
13 | | #include "mozilla/ErrorResult.h" |
14 | | #include "mozilla/FloatingPoint.h" |
15 | | #include "mozilla/Preferences.h" |
16 | | #include "mozilla/dom/MediaSourceBinding.h" |
17 | | #include "mozilla/dom/TimeRanges.h" |
18 | | #include "nsError.h" |
19 | | #include "nsIEventTarget.h" |
20 | | #include "nsIRunnable.h" |
21 | | #include "nsThreadUtils.h" |
22 | | #include "mozilla/Logging.h" |
23 | | #include <time.h> |
24 | | #include "TimeUnits.h" |
25 | | |
26 | | // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to |
27 | | // GetTickCount() and conflicts with MediaDecoder::GetCurrentTime implementation. |
28 | | #ifdef GetCurrentTime |
29 | | #undef GetCurrentTime |
30 | | #endif |
31 | | |
32 | | struct JSContext; |
33 | | class JSObject; |
34 | | |
35 | | extern mozilla::LogModule* GetMediaSourceLog(); |
36 | | extern mozilla::LogModule* GetMediaSourceAPILog(); |
37 | | |
38 | | #define MSE_DEBUG(arg, ...) \ |
39 | 0 | DDMOZ_LOG(GetMediaSourceLog(), \ |
40 | 0 | mozilla::LogLevel::Debug, \ |
41 | 0 | "(%s)::%s: " arg, \ |
42 | 0 | mType.OriginalString().Data(), \ |
43 | 0 | __func__, \ |
44 | 0 | ##__VA_ARGS__) |
45 | | #define MSE_DEBUGV(arg, ...) \ |
46 | 0 | DDMOZ_LOG(GetMediaSourceLog(), \ |
47 | 0 | mozilla::LogLevel::Verbose, \ |
48 | 0 | "(%s)::%s: " arg, \ |
49 | 0 | mType.OriginalString().Data(), \ |
50 | 0 | __func__, \ |
51 | 0 | ##__VA_ARGS__) |
52 | | #define MSE_API(arg, ...) \ |
53 | 0 | DDMOZ_LOG(GetMediaSourceAPILog(), \ |
54 | 0 | mozilla::LogLevel::Debug, \ |
55 | 0 | "(%s)::%s: " arg, \ |
56 | 0 | mType.OriginalString().Data(), \ |
57 | 0 | __func__, \ |
58 | 0 | ##__VA_ARGS__) |
59 | | |
60 | | namespace mozilla { |
61 | | |
62 | | using media::TimeUnit; |
63 | | typedef SourceBufferAttributes::AppendState AppendState; |
64 | | |
65 | | namespace dom { |
66 | | |
67 | | void |
68 | | SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv) |
69 | 0 | { |
70 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
71 | 0 | MSE_API("SetMode(aMode=%" PRIu32 ")", static_cast<uint32_t>(aMode)); |
72 | 0 | if (!IsAttached() || mUpdating) { |
73 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
74 | 0 | return; |
75 | 0 | } |
76 | 0 | if (mCurrentAttributes.mGenerateTimestamps && |
77 | 0 | aMode == SourceBufferAppendMode::Segments) { |
78 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
79 | 0 | return; |
80 | 0 | } |
81 | 0 | MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); |
82 | 0 | if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { |
83 | 0 | mMediaSource->SetReadyState(MediaSourceReadyState::Open); |
84 | 0 | } |
85 | 0 | if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){ |
86 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
87 | 0 | return; |
88 | 0 | } |
89 | 0 | |
90 | 0 | if (aMode == SourceBufferAppendMode::Sequence) { |
91 | 0 | // Will set GroupStartTimestamp to GroupEndTimestamp. |
92 | 0 | mCurrentAttributes.RestartGroupStartTimestamp(); |
93 | 0 | } |
94 | 0 |
|
95 | 0 | mCurrentAttributes.SetAppendMode(aMode); |
96 | 0 | } |
97 | | |
98 | | void |
99 | | SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv) |
100 | 0 | { |
101 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
102 | 0 | MSE_API("SetTimestampOffset(aTimestampOffset=%f)", aTimestampOffset); |
103 | 0 | if (!IsAttached() || mUpdating) { |
104 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
105 | 0 | return; |
106 | 0 | } |
107 | 0 | MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); |
108 | 0 | if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { |
109 | 0 | mMediaSource->SetReadyState(MediaSourceReadyState::Open); |
110 | 0 | } |
111 | 0 | if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){ |
112 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
113 | 0 | return; |
114 | 0 | } |
115 | 0 | mCurrentAttributes.SetApparentTimestampOffset(aTimestampOffset); |
116 | 0 | if (mCurrentAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) { |
117 | 0 | mCurrentAttributes.SetGroupStartTimestamp(mCurrentAttributes.GetTimestampOffset()); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | TimeRanges* |
122 | | SourceBuffer::GetBuffered(ErrorResult& aRv) |
123 | 0 | { |
124 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
125 | 0 | // http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered |
126 | 0 | // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an InvalidStateError exception and abort these steps. |
127 | 0 | if (!IsAttached()) { |
128 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
129 | 0 | return nullptr; |
130 | 0 | } |
131 | 0 | bool rangeChanged = true; |
132 | 0 | media::TimeIntervals intersection = mTrackBuffersManager->Buffered(); |
133 | 0 | MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get()); |
134 | 0 | if (mBuffered) { |
135 | 0 | media::TimeIntervals currentValue(mBuffered->ToTimeIntervals()); |
136 | 0 | rangeChanged = (intersection != currentValue); |
137 | 0 | MSE_DEBUGV("currentValue=%s", DumpTimeRanges(currentValue).get()); |
138 | 0 | } |
139 | 0 | // 5. If intersection ranges does not contain the exact same range information as the current value of this attribute, then update the current value of this attribute to intersection ranges. |
140 | 0 | if (rangeChanged) { |
141 | 0 | mBuffered = new TimeRanges(ToSupports(this), intersection); |
142 | 0 | } |
143 | 0 | // 6. Return the current value of this attribute. |
144 | 0 | return mBuffered; |
145 | 0 | } |
146 | | |
147 | | media::TimeIntervals |
148 | | SourceBuffer::GetTimeIntervals() |
149 | 0 | { |
150 | 0 | return mTrackBuffersManager->Buffered(); |
151 | 0 | } |
152 | | |
153 | | void |
154 | | SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv) |
155 | 0 | { |
156 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
157 | 0 | MSE_API("SetAppendWindowStart(aAppendWindowStart=%f)", aAppendWindowStart); |
158 | 0 | DDLOG(DDLogCategory::API, "SetAppendWindowStart", aAppendWindowStart); |
159 | 0 | if (!IsAttached() || mUpdating) { |
160 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
161 | 0 | return; |
162 | 0 | } |
163 | 0 | if (aAppendWindowStart < 0 || |
164 | 0 | aAppendWindowStart >= mCurrentAttributes.GetAppendWindowEnd()) { |
165 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
166 | 0 | return; |
167 | 0 | } |
168 | 0 | mCurrentAttributes.SetAppendWindowStart(aAppendWindowStart); |
169 | 0 | } |
170 | | |
171 | | void |
172 | | SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv) |
173 | 0 | { |
174 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
175 | 0 | MSE_API("SetAppendWindowEnd(aAppendWindowEnd=%f)", aAppendWindowEnd); |
176 | 0 | DDLOG(DDLogCategory::API, "SetAppendWindowEnd", aAppendWindowEnd); |
177 | 0 | if (!IsAttached() || mUpdating) { |
178 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
179 | 0 | return; |
180 | 0 | } |
181 | 0 | if (IsNaN(aAppendWindowEnd) || |
182 | 0 | aAppendWindowEnd <= mCurrentAttributes.GetAppendWindowStart()) { |
183 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
184 | 0 | return; |
185 | 0 | } |
186 | 0 | mCurrentAttributes.SetAppendWindowEnd(aAppendWindowEnd); |
187 | 0 | } |
188 | | |
189 | | void |
190 | | SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv) |
191 | 0 | { |
192 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
193 | 0 | MSE_API("AppendBuffer(ArrayBuffer)"); |
194 | 0 | aData.ComputeLengthAndData(); |
195 | 0 | DDLOG(DDLogCategory::API, "AppendBuffer", aData.Length()); |
196 | 0 | AppendData(aData.Data(), aData.Length(), aRv); |
197 | 0 | } |
198 | | |
199 | | void |
200 | | SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv) |
201 | 0 | { |
202 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
203 | 0 | MSE_API("AppendBuffer(ArrayBufferView)"); |
204 | 0 | aData.ComputeLengthAndData(); |
205 | 0 | DDLOG(DDLogCategory::API, "AppendBuffer", aData.Length()); |
206 | 0 | AppendData(aData.Data(), aData.Length(), aRv); |
207 | 0 | } |
208 | | |
209 | | already_AddRefed<Promise> |
210 | | SourceBuffer::AppendBufferAsync(const ArrayBuffer& aData, |
211 | | ErrorResult& aRv) |
212 | 0 | { |
213 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
214 | 0 |
|
215 | 0 | MSE_API("AppendBufferAsync(ArrayBuffer)"); |
216 | 0 | aData.ComputeLengthAndData(); |
217 | 0 | DDLOG(DDLogCategory::API, "AppendBufferAsync", aData.Length()); |
218 | 0 |
|
219 | 0 | return AppendDataAsync(aData.Data(), aData.Length(), aRv); |
220 | 0 | } |
221 | | |
222 | | already_AddRefed<Promise> |
223 | | SourceBuffer::AppendBufferAsync(const ArrayBufferView& aData, |
224 | | ErrorResult& aRv) |
225 | 0 | { |
226 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
227 | 0 |
|
228 | 0 | MSE_API("AppendBufferAsync(ArrayBufferView)"); |
229 | 0 | aData.ComputeLengthAndData(); |
230 | 0 | DDLOG(DDLogCategory::API, "AppendBufferAsync", aData.Length()); |
231 | 0 |
|
232 | 0 | return AppendDataAsync(aData.Data(), aData.Length(), aRv); |
233 | 0 | } |
234 | | |
235 | | void |
236 | | SourceBuffer::Abort(ErrorResult& aRv) |
237 | 0 | { |
238 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
239 | 0 | MSE_API("Abort()"); |
240 | 0 | if (!IsAttached()) { |
241 | 0 | DDLOG(DDLogCategory::API, "Abort", NS_ERROR_DOM_INVALID_STATE_ERR); |
242 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
243 | 0 | return; |
244 | 0 | } |
245 | 0 | if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) { |
246 | 0 | DDLOG(DDLogCategory::API, "Abort", NS_ERROR_DOM_INVALID_STATE_ERR); |
247 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | if (mPendingRemoval.Exists()) { |
251 | 0 | DDLOG(DDLogCategory::API, "Abort", NS_ERROR_DOM_INVALID_STATE_ERR); |
252 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
253 | 0 | return; |
254 | 0 | } |
255 | 0 | DDLOG(DDLogCategory::API, "Abort", NS_OK); |
256 | 0 | AbortBufferAppend(); |
257 | 0 | ResetParserState(); |
258 | 0 | mCurrentAttributes.SetAppendWindowStart(0); |
259 | 0 | mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>()); |
260 | 0 | } |
261 | | |
262 | | void |
263 | | SourceBuffer::AbortBufferAppend() |
264 | 0 | { |
265 | 0 | if (mUpdating) { |
266 | 0 | mCompletionPromise.DisconnectIfExists(); |
267 | 0 | if (mPendingAppend.Exists()) { |
268 | 0 | mPendingAppend.Disconnect(); |
269 | 0 | mTrackBuffersManager->AbortAppendData(); |
270 | 0 | } |
271 | 0 | AbortUpdating(); |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | void |
276 | | SourceBuffer::ResetParserState() |
277 | 0 | { |
278 | 0 | mTrackBuffersManager->ResetParserState(mCurrentAttributes); |
279 | 0 | } |
280 | | |
281 | | void |
282 | | SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv) |
283 | 0 | { |
284 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
285 | 0 | MSE_API("Remove(aStart=%f, aEnd=%f)", aStart, aEnd); |
286 | 0 | DDLOG(DDLogCategory::API, "Remove-from", aStart); |
287 | 0 | DDLOG(DDLogCategory::API, "Remove-until", aEnd); |
288 | 0 |
|
289 | 0 | PrepareRemove(aStart, aEnd, aRv); |
290 | 0 | if (aRv.Failed()) { |
291 | 0 | return; |
292 | 0 | } |
293 | 0 | RangeRemoval(aStart, aEnd); |
294 | 0 | } |
295 | | |
296 | | already_AddRefed<Promise> |
297 | | SourceBuffer::RemoveAsync(double aStart, double aEnd, ErrorResult& aRv) |
298 | 0 | { |
299 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
300 | 0 | MSE_API("RemoveAsync(aStart=%f, aEnd=%f)", aStart, aEnd); |
301 | 0 | DDLOG(DDLogCategory::API, "Remove-from", aStart); |
302 | 0 | DDLOG(DDLogCategory::API, "Remove-until", aEnd); |
303 | 0 |
|
304 | 0 | if (!IsAttached()) { |
305 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
306 | 0 | return nullptr; |
307 | 0 | } |
308 | 0 | |
309 | 0 | nsCOMPtr<nsIGlobalObject> parentObject = |
310 | 0 | do_QueryInterface(mMediaSource->GetParentObject()); |
311 | 0 | if (!parentObject) { |
312 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
313 | 0 | return nullptr; |
314 | 0 | } |
315 | 0 | |
316 | 0 | RefPtr<Promise> promise = Promise::Create(parentObject, aRv); |
317 | 0 | if (aRv.Failed()) { |
318 | 0 | return nullptr; |
319 | 0 | } |
320 | 0 | |
321 | 0 | PrepareRemove(aStart, aEnd, aRv); |
322 | 0 |
|
323 | 0 | if (aRv.Failed()) { |
324 | 0 | // The bindings will automatically return a rejected promise. |
325 | 0 | return nullptr; |
326 | 0 | } |
327 | 0 | MOZ_ASSERT(!mDOMPromise, "Can't have a pending operation going"); |
328 | 0 | mDOMPromise = promise; |
329 | 0 | RangeRemoval(aStart, aEnd); |
330 | 0 |
|
331 | 0 | return promise.forget(); |
332 | 0 | } |
333 | | |
334 | | void |
335 | | SourceBuffer::PrepareRemove(double aStart, double aEnd, ErrorResult& aRv) |
336 | 0 | { |
337 | 0 | if (!IsAttached()) { |
338 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
339 | 0 | return; |
340 | 0 | } |
341 | 0 | if (mUpdating) { |
342 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
343 | 0 | return; |
344 | 0 | } |
345 | 0 | if (IsNaN(mMediaSource->Duration()) || |
346 | 0 | aStart < 0 || aStart > mMediaSource->Duration() || |
347 | 0 | aEnd <= aStart || IsNaN(aEnd)) { |
348 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
349 | 0 | return; |
350 | 0 | } |
351 | 0 | if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { |
352 | 0 | mMediaSource->SetReadyState(MediaSourceReadyState::Open); |
353 | 0 | } |
354 | 0 | } |
355 | | |
356 | | void |
357 | | SourceBuffer::RangeRemoval(double aStart, double aEnd) |
358 | 0 | { |
359 | 0 | StartUpdating(); |
360 | 0 |
|
361 | 0 | RefPtr<SourceBuffer> self = this; |
362 | 0 | mTrackBuffersManager->RangeRemoval(TimeUnit::FromSeconds(aStart), |
363 | 0 | TimeUnit::FromSeconds(aEnd)) |
364 | 0 | ->Then(mAbstractMainThread, __func__, |
365 | 0 | [self] (bool) { |
366 | 0 | self->mPendingRemoval.Complete(); |
367 | 0 | self->StopUpdating(); |
368 | 0 | }, |
369 | 0 | []() { MOZ_ASSERT(false); }) |
370 | 0 | ->Track(mPendingRemoval); |
371 | 0 | } |
372 | | |
373 | | void |
374 | | SourceBuffer::ChangeType(const nsAString& aType, ErrorResult& aRv) |
375 | 0 | { |
376 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
377 | 0 |
|
378 | 0 | // 1. If type is an empty string then throw a TypeError exception and abort |
379 | 0 | // these steps. |
380 | 0 | if (aType.IsEmpty()) { |
381 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
382 | 0 | return; |
383 | 0 | } |
384 | 0 | |
385 | 0 | // 2. If this object has been removed from the sourceBuffers attribute of the |
386 | 0 | // parent media source , then throw an InvalidStateError exception and |
387 | 0 | // abort these steps. |
388 | 0 | // 3. If the updating attribute equals true, then throw an InvalidStateError |
389 | 0 | // exception and abort these steps. |
390 | 0 | if (!IsAttached() || mUpdating) { |
391 | 0 | DDLOG(DDLogCategory::API, "ChangeType", NS_ERROR_DOM_INVALID_STATE_ERR); |
392 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
393 | 0 | return; |
394 | 0 | } |
395 | 0 |
|
396 | 0 | // 4. If type contains a MIME type that is not supported or contains a MIME |
397 | 0 | // type that is not supported with the types specified (currently or |
398 | 0 | // previously) of SourceBuffer objects in the sourceBuffers attribute of |
399 | 0 | // the parent media source , then throw a NotSupportedError exception and |
400 | 0 | // abort these steps. |
401 | 0 | DecoderDoctorDiagnostics diagnostics; |
402 | 0 | nsresult rv = MediaSource::IsTypeSupported(aType, &diagnostics); |
403 | 0 | diagnostics.StoreFormatDiagnostics(mMediaSource->GetOwner() |
404 | 0 | ? mMediaSource->GetOwner()->GetExtantDoc() |
405 | 0 | : nullptr, |
406 | 0 | aType, NS_SUCCEEDED(rv), __func__); |
407 | 0 | MSE_API("ChangeType(aType=%s)%s", |
408 | 0 | NS_ConvertUTF16toUTF8(aType).get(), |
409 | 0 | rv == NS_OK ? "" : " [not supported]"); |
410 | 0 | if (NS_FAILED(rv)) { |
411 | 0 | DDLOG(DDLogCategory::API, "ChangeType", rv); |
412 | 0 | aRv.Throw(rv); |
413 | 0 | return; |
414 | 0 | } |
415 | 0 |
|
416 | 0 | // 5. If the readyState attribute of the parent media source is in the "ended" |
417 | 0 | // state then run the following steps: |
418 | 0 | // 1. Set the readyState attribute of the parent media source to "open" |
419 | 0 | // 2. Queue a task to fire a simple event named sourceopen at the parent |
420 | 0 | // media source . |
421 | 0 | MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed); |
422 | 0 | if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { |
423 | 0 | mMediaSource->SetReadyState(MediaSourceReadyState::Open); |
424 | 0 | } |
425 | 0 | Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); |
426 | 0 | MOZ_ASSERT(containerType); |
427 | 0 | mType = *containerType; |
428 | 0 | // 6. Run the reset parser state algorithm . |
429 | 0 | ResetParserState(); |
430 | 0 |
|
431 | 0 | // 7. Update the generate timestamps flag on this SourceBuffer object to the |
432 | 0 | // value in the "Generate Timestamps Flag" column of the byte stream format |
433 | 0 | // registry [ MSE-REGISTRY ] entry that is associated with type . |
434 | 0 | if (mType.Type() == MEDIAMIMETYPE("audio/mpeg") || |
435 | 0 | mType.Type() == MEDIAMIMETYPE("audio/aac")) { |
436 | 0 | mCurrentAttributes.mGenerateTimestamps = true; |
437 | 0 | // 8. If the generate timestamps flag equals true: |
438 | 0 | // Set the mode attribute on this SourceBuffer object to "sequence" , |
439 | 0 | // including running the associated steps for that attribute being set. |
440 | 0 | ErrorResult dummy; |
441 | 0 | SetMode(SourceBufferAppendMode::Sequence, dummy); |
442 | 0 | } else { |
443 | 0 | mCurrentAttributes.mGenerateTimestamps = false; |
444 | 0 | // Otherwise: Keep the previous value of the mode attribute on this |
445 | 0 | // SourceBuffer object, without running any associated steps for that |
446 | 0 | // attribute being set. |
447 | 0 | } |
448 | 0 |
|
449 | 0 | // 9. Set pending initialization segment for changeType flag to true. |
450 | 0 | mTrackBuffersManager->ChangeType(mType); |
451 | 0 | } |
452 | | |
453 | | void |
454 | | SourceBuffer::Detach() |
455 | 0 | { |
456 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
457 | 0 | MSE_DEBUG("Detach"); |
458 | 0 | if (!mMediaSource) { |
459 | 0 | MSE_DEBUG("Already detached"); |
460 | 0 | return; |
461 | 0 | } |
462 | 0 | AbortBufferAppend(); |
463 | 0 | if (mTrackBuffersManager) { |
464 | 0 | mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer( |
465 | 0 | mTrackBuffersManager); |
466 | 0 | mTrackBuffersManager->Detach(); |
467 | 0 | } |
468 | 0 | mTrackBuffersManager = nullptr; |
469 | 0 | mMediaSource = nullptr; |
470 | 0 | } |
471 | | |
472 | | void |
473 | | SourceBuffer::Ended() |
474 | 0 | { |
475 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
476 | 0 | MOZ_ASSERT(IsAttached()); |
477 | 0 | MSE_DEBUG("Ended"); |
478 | 0 | mTrackBuffersManager->Ended(); |
479 | 0 | } |
480 | | |
481 | | SourceBuffer::SourceBuffer(MediaSource* aMediaSource, |
482 | | const MediaContainerType& aType) |
483 | | : DOMEventTargetHelper(aMediaSource->GetParentObject()) |
484 | | , mMediaSource(aMediaSource) |
485 | | , mAbstractMainThread(aMediaSource->AbstractMainThread()) |
486 | | , mCurrentAttributes(aType.Type() == MEDIAMIMETYPE("audio/mpeg") || |
487 | | aType.Type() == MEDIAMIMETYPE("audio/aac")) |
488 | | , mUpdating(false) |
489 | | , mActive(false) |
490 | | , mType(aType) |
491 | 0 | { |
492 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
493 | 0 | MOZ_ASSERT(aMediaSource); |
494 | 0 |
|
495 | 0 | mTrackBuffersManager = |
496 | 0 | new TrackBuffersManager(aMediaSource->GetDecoder(), aType); |
497 | 0 | DDLINKCHILD("track buffers manager", mTrackBuffersManager.get()); |
498 | 0 |
|
499 | 0 | MSE_DEBUG("Create mTrackBuffersManager=%p", |
500 | 0 | mTrackBuffersManager.get()); |
501 | 0 |
|
502 | 0 | ErrorResult dummy; |
503 | 0 | if (mCurrentAttributes.mGenerateTimestamps) { |
504 | 0 | SetMode(SourceBufferAppendMode::Sequence, dummy); |
505 | 0 | } else { |
506 | 0 | SetMode(SourceBufferAppendMode::Segments, dummy); |
507 | 0 | } |
508 | 0 | mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer( |
509 | 0 | mTrackBuffersManager); |
510 | 0 | } |
511 | | |
512 | | SourceBuffer::~SourceBuffer() |
513 | 0 | { |
514 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
515 | 0 | MOZ_ASSERT(!mMediaSource); |
516 | 0 | MSE_DEBUG(""); |
517 | 0 | } |
518 | | |
519 | | MediaSource* |
520 | | SourceBuffer::GetParentObject() const |
521 | 0 | { |
522 | 0 | return mMediaSource; |
523 | 0 | } |
524 | | |
525 | | JSObject* |
526 | | SourceBuffer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
527 | 0 | { |
528 | 0 | return SourceBuffer_Binding::Wrap(aCx, this, aGivenProto); |
529 | 0 | } |
530 | | |
531 | | void |
532 | | SourceBuffer::DispatchSimpleEvent(const char* aName) |
533 | 0 | { |
534 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
535 | 0 | MSE_API("Dispatch event '%s'", aName); |
536 | 0 | DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName)); |
537 | 0 | } |
538 | | |
539 | | void |
540 | | SourceBuffer::QueueAsyncSimpleEvent(const char* aName) |
541 | 0 | { |
542 | 0 | MSE_DEBUG("Queuing event '%s'", aName); |
543 | 0 | nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName); |
544 | 0 | mAbstractMainThread->Dispatch(event.forget()); |
545 | 0 | } |
546 | | |
547 | | void |
548 | | SourceBuffer::StartUpdating() |
549 | 0 | { |
550 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
551 | 0 | MOZ_ASSERT(!mUpdating); |
552 | 0 | mUpdating = true; |
553 | 0 | QueueAsyncSimpleEvent("updatestart"); |
554 | 0 | } |
555 | | |
556 | | void |
557 | | SourceBuffer::StopUpdating() |
558 | 0 | { |
559 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
560 | 0 | if (!mUpdating) { |
561 | 0 | // The buffer append or range removal algorithm has been interrupted by |
562 | 0 | // abort(). |
563 | 0 | return; |
564 | 0 | } |
565 | 0 | mUpdating = false; |
566 | 0 | QueueAsyncSimpleEvent("update"); |
567 | 0 | QueueAsyncSimpleEvent("updateend"); |
568 | 0 | if (mDOMPromise) { |
569 | 0 | mDOMPromise->MaybeResolveWithUndefined(); |
570 | 0 | mDOMPromise = nullptr; |
571 | 0 | } |
572 | 0 | } |
573 | | |
574 | | void |
575 | | SourceBuffer::AbortUpdating() |
576 | 0 | { |
577 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
578 | 0 | mUpdating = false; |
579 | 0 | QueueAsyncSimpleEvent("abort"); |
580 | 0 | QueueAsyncSimpleEvent("updateend"); |
581 | 0 | if (mDOMPromise) { |
582 | 0 | mDOMPromise->MaybeReject(NS_ERROR_DOM_MEDIA_ABORT_ERR); |
583 | 0 | mDOMPromise = nullptr; |
584 | 0 | } |
585 | 0 | } |
586 | | |
587 | | void |
588 | | SourceBuffer::CheckEndTime() |
589 | 0 | { |
590 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
591 | 0 | // Check if we need to update mMediaSource duration |
592 | 0 | double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds(); |
593 | 0 | double duration = mMediaSource->Duration(); |
594 | 0 | if (endTime > duration) { |
595 | 0 | mMediaSource->SetDuration(endTime); |
596 | 0 | } |
597 | 0 | } |
598 | | |
599 | | void |
600 | | SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv) |
601 | 0 | { |
602 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
603 | 0 | MSE_DEBUG("AppendData(aLength=%u)", aLength); |
604 | 0 |
|
605 | 0 | RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv); |
606 | 0 | if (!data) { |
607 | 0 | return; |
608 | 0 | } |
609 | 0 | StartUpdating(); |
610 | 0 |
|
611 | 0 | mTrackBuffersManager->AppendData(data.forget(), mCurrentAttributes) |
612 | 0 | ->Then(mAbstractMainThread, __func__, this, |
613 | 0 | &SourceBuffer::AppendDataCompletedWithSuccess, |
614 | 0 | &SourceBuffer::AppendDataErrored) |
615 | 0 | ->Track(mPendingAppend); |
616 | 0 | } |
617 | | |
618 | | already_AddRefed<Promise> |
619 | | SourceBuffer::AppendDataAsync(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv) |
620 | 0 | { |
621 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
622 | 0 |
|
623 | 0 | if (!IsAttached()) { |
624 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
625 | 0 | return nullptr; |
626 | 0 | } |
627 | 0 | |
628 | 0 | nsCOMPtr<nsIGlobalObject> parentObject = |
629 | 0 | do_QueryInterface(mMediaSource->GetParentObject()); |
630 | 0 | if (!parentObject) { |
631 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
632 | 0 | return nullptr; |
633 | 0 | } |
634 | 0 | |
635 | 0 | RefPtr<Promise> promise = Promise::Create(parentObject, aRv); |
636 | 0 | if (aRv.Failed()) { |
637 | 0 | return nullptr; |
638 | 0 | } |
639 | 0 | |
640 | 0 | AppendData(aData, aLength, aRv); |
641 | 0 |
|
642 | 0 | if (aRv.Failed()) { |
643 | 0 | return nullptr; |
644 | 0 | } |
645 | 0 | |
646 | 0 | MOZ_ASSERT(!mDOMPromise, "Can't have a pending operation going"); |
647 | 0 | mDOMPromise = promise; |
648 | 0 |
|
649 | 0 | return promise.forget(); |
650 | 0 | } |
651 | | |
652 | | void |
653 | | SourceBuffer::AppendDataCompletedWithSuccess( |
654 | | const SourceBufferTask::AppendBufferResult& aResult) |
655 | 0 | { |
656 | 0 | MOZ_ASSERT(mUpdating); |
657 | 0 | mPendingAppend.Complete(); |
658 | 0 | DDLOG(DDLogCategory::API, "AppendBuffer-completed", NS_OK); |
659 | 0 |
|
660 | 0 | if (aResult.first()) { |
661 | 0 | if (!mActive) { |
662 | 0 | mActive = true; |
663 | 0 | MSE_DEBUG("Init segment received"); |
664 | 0 | RefPtr<SourceBuffer> self = this; |
665 | 0 | mMediaSource->SourceBufferIsActive(this) |
666 | 0 | ->Then(mAbstractMainThread, __func__, |
667 | 0 | [self, this]() { |
668 | 0 | MSE_DEBUG("Complete AppendBuffer operation"); |
669 | 0 | mCompletionPromise.Complete(); |
670 | 0 | StopUpdating(); |
671 | 0 | }) |
672 | 0 | ->Track(mCompletionPromise); |
673 | 0 | } |
674 | 0 | } |
675 | 0 | if (mActive) { |
676 | 0 | // Tell our parent decoder that we have received new data |
677 | 0 | // and send progress event. |
678 | 0 | mMediaSource->GetDecoder()->NotifyDataArrived(); |
679 | 0 | } |
680 | 0 |
|
681 | 0 | mCurrentAttributes = aResult.second(); |
682 | 0 |
|
683 | 0 | CheckEndTime(); |
684 | 0 |
|
685 | 0 | if (!mCompletionPromise.Exists()) { |
686 | 0 | StopUpdating(); |
687 | 0 | } |
688 | 0 | } |
689 | | |
690 | | void |
691 | | SourceBuffer::AppendDataErrored(const MediaResult& aError) |
692 | 0 | { |
693 | 0 | MOZ_ASSERT(mUpdating); |
694 | 0 | mPendingAppend.Complete(); |
695 | 0 | DDLOG(DDLogCategory::API, "AppendBuffer-error", aError); |
696 | 0 |
|
697 | 0 | switch (aError.Code()) { |
698 | 0 | case NS_ERROR_DOM_MEDIA_CANCELED: |
699 | 0 | // Nothing further to do as the trackbuffer has been shutdown. |
700 | 0 | // or append was aborted and abort() has handled all the events. |
701 | 0 | break; |
702 | 0 | default: |
703 | 0 | AppendError(aError); |
704 | 0 | break; |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | | void |
709 | | SourceBuffer::AppendError(const MediaResult& aDecodeError) |
710 | 0 | { |
711 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
712 | 0 |
|
713 | 0 | ResetParserState(); |
714 | 0 |
|
715 | 0 | mUpdating = false; |
716 | 0 |
|
717 | 0 | QueueAsyncSimpleEvent("error"); |
718 | 0 | QueueAsyncSimpleEvent("updateend"); |
719 | 0 |
|
720 | 0 | MOZ_ASSERT(NS_FAILED(aDecodeError)); |
721 | 0 |
|
722 | 0 | mMediaSource->EndOfStream(aDecodeError); |
723 | 0 |
|
724 | 0 | if (mDOMPromise) { |
725 | 0 | mDOMPromise->MaybeReject(aDecodeError); |
726 | 0 | mDOMPromise = nullptr; |
727 | 0 | } |
728 | 0 | } |
729 | | |
730 | | already_AddRefed<MediaByteBuffer> |
731 | | SourceBuffer::PrepareAppend(const uint8_t* aData, |
732 | | uint32_t aLength, |
733 | | ErrorResult& aRv) |
734 | 0 | { |
735 | 0 | typedef TrackBuffersManager::EvictDataResult Result; |
736 | 0 |
|
737 | 0 | if (!IsAttached() || mUpdating) { |
738 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
739 | 0 | return nullptr; |
740 | 0 | } |
741 | 0 | |
742 | 0 | // If the HTMLMediaElement.error attribute is not null, then throw an |
743 | 0 | // InvalidStateError exception and abort these steps. |
744 | 0 | if (!mMediaSource->GetDecoder() || |
745 | 0 | mMediaSource->GetDecoder()->OwnerHasError()) { |
746 | 0 | MSE_DEBUG("HTMLMediaElement.error is not null"); |
747 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
748 | 0 | return nullptr; |
749 | 0 | } |
750 | 0 |
|
751 | 0 | if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { |
752 | 0 | mMediaSource->SetReadyState(MediaSourceReadyState::Open); |
753 | 0 | } |
754 | 0 |
|
755 | 0 | // Eviction uses a byte threshold. If the buffer is greater than the |
756 | 0 | // number of bytes then data is evicted. |
757 | 0 | // TODO: Drive evictions off memory pressure notifications. |
758 | 0 | // TODO: Consider a global eviction threshold rather than per TrackBuffer. |
759 | 0 | // Give a chance to the TrackBuffersManager to evict some data if needed. |
760 | 0 | Result evicted = |
761 | 0 | mTrackBuffersManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()), |
762 | 0 | aLength); |
763 | 0 |
|
764 | 0 | // See if we have enough free space to append our new data. |
765 | 0 | if (evicted == Result::BUFFER_FULL) { |
766 | 0 | aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); |
767 | 0 | return nullptr; |
768 | 0 | } |
769 | 0 | |
770 | 0 | RefPtr<MediaByteBuffer> data = new MediaByteBuffer(); |
771 | 0 | if (!data->AppendElements(aData, aLength, fallible)) { |
772 | 0 | aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); |
773 | 0 | return nullptr; |
774 | 0 | } |
775 | 0 | return data.forget(); |
776 | 0 | } |
777 | | |
778 | | double |
779 | | SourceBuffer::GetBufferedStart() |
780 | 0 | { |
781 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
782 | 0 | ErrorResult dummy; |
783 | 0 | RefPtr<TimeRanges> ranges = GetBuffered(dummy); |
784 | 0 | return ranges->Length() > 0 ? ranges->GetStartTime() : 0; |
785 | 0 | } |
786 | | |
787 | | double |
788 | | SourceBuffer::GetBufferedEnd() |
789 | 0 | { |
790 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
791 | 0 | ErrorResult dummy; |
792 | 0 | RefPtr<TimeRanges> ranges = GetBuffered(dummy); |
793 | 0 | return ranges->Length() > 0 ? ranges->GetEndTime() : 0; |
794 | 0 | } |
795 | | |
796 | | double |
797 | | SourceBuffer::HighestStartTime() |
798 | 0 | { |
799 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
800 | 0 | return mTrackBuffersManager |
801 | 0 | ? mTrackBuffersManager->HighestStartTime().ToSeconds() |
802 | 0 | : 0.0; |
803 | 0 | } |
804 | | |
805 | | double |
806 | | SourceBuffer::HighestEndTime() |
807 | 0 | { |
808 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
809 | 0 | return mTrackBuffersManager |
810 | 0 | ? mTrackBuffersManager->HighestEndTime().ToSeconds() |
811 | 0 | : 0.0; |
812 | 0 | } |
813 | | |
814 | | NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer) |
815 | | |
816 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer) |
817 | 0 | tmp->Detach(); |
818 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource) |
819 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mBuffered) |
820 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMPromise) |
821 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DOMEventTargetHelper) |
822 | | |
823 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SourceBuffer, |
824 | 0 | DOMEventTargetHelper) |
825 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource) |
826 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBuffered) |
827 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMPromise) |
828 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
829 | | |
830 | | NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper) |
831 | | NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper) |
832 | | |
833 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SourceBuffer) |
834 | 0 | NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
835 | | |
836 | | #undef MSE_DEBUG |
837 | | #undef MSE_DEBUGV |
838 | | #undef MSE_API |
839 | | |
840 | | } // namespace dom |
841 | | |
842 | | } // namespace mozilla |