1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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/. */
7 #include "WebMBufferedParser.h"
11 #include "mozilla/CheckedInt.h"
12 #include "nsThreadUtils.h"
14 extern mozilla::LazyLogModule gMediaDemuxerLog
;
15 #define WEBM_DEBUG(arg, ...) \
16 MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, \
17 ("WebMBufferedParser(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
21 static uint32_t VIntLength(unsigned char aFirstByte
, uint32_t* aMask
) {
23 uint32_t mask
= 1 << 7;
25 if ((aFirstByte
& mask
) != 0) {
34 NS_ASSERTION(count
>= 1 && count
<= 8, "Insane VInt length.");
38 constexpr uint8_t EBML_MAX_ID_LENGTH_DEFAULT
= 4;
39 constexpr uint8_t EBML_MAX_SIZE_LENGTH_DEFAULT
= 8;
41 WebMBufferedParser::WebMBufferedParser(int64_t aOffset
)
42 : mStartOffset(aOffset
),
43 mCurrentOffset(aOffset
),
46 mState(READ_ELEMENT_ID
),
47 mNextState(READ_ELEMENT_ID
),
49 mLastInitStartOffset(-1),
51 mEBMLMaxIdLength(EBML_MAX_ID_LENGTH_DEFAULT
),
52 mEBMLMaxSizeLength(EBML_MAX_SIZE_LENGTH_DEFAULT
),
58 mClusterEndOffset(-1),
61 mBlockTimecodeLength(0),
63 mTimecodeScale(1000000),
64 mGotTimecodeScale(false),
65 mGotClusterTimecode(false) {
66 if (mStartOffset
!= 0) {
67 mState
= FIND_CLUSTER_SYNC
;
71 void WebMBufferedParser::SetTimecodeScale(uint32_t aTimecodeScale
) {
72 mTimecodeScale
= aTimecodeScale
;
73 WEBM_DEBUG("%" PRIu32
, mTimecodeScale
);
74 mGotTimecodeScale
= true;
77 MediaResult
WebMBufferedParser::Append(const unsigned char* aBuffer
,
79 nsTArray
<WebMTimeDataOffset
>& aMapping
) {
80 static const uint32_t EBML_ID
= 0x1a45dfa3;
81 static const uint32_t SEGMENT_ID
= 0x18538067;
82 static const uint32_t SEGINFO_ID
= 0x1549a966;
83 static const uint32_t TRACKS_ID
= 0x1654AE6B;
84 static const uint32_t CLUSTER_ID
= 0x1f43b675;
85 static const uint32_t TIMECODESCALE_ID
= 0x2ad7b1;
86 static const unsigned char TIMECODE_ID
= 0xe7;
87 static const unsigned char BLOCKGROUP_ID
= 0xa0;
88 static const unsigned char BLOCK_ID
= 0xa1;
89 static const unsigned char SIMPLEBLOCK_ID
= 0xa3;
90 static const uint16_t EBML_MAX_ID_LENGTH_ID
= 0x42f2;
91 static const uint16_t EBML_MAX_SIZE_LENGTH_ID
= 0x42f3;
92 static const uint32_t BLOCK_TIMECODE_LENGTH
= 2;
94 static const unsigned char CLUSTER_SYNC_ID
[] = {0x1f, 0x43, 0xb6, 0x75};
96 const unsigned char* p
= aBuffer
;
98 // Parse each byte in aBuffer one-by-one, producing timecodes and updating
99 // aMapping as we go. Parser pauses at end of stream (which may be at any
100 // point within the parse) and resumes parsing the next time Append is
101 // called with new data.
102 while (p
< aBuffer
+ aLength
) {
104 case READ_ELEMENT_ID
:
107 mNextState
= READ_ELEMENT_SIZE
;
109 case READ_ELEMENT_SIZE
:
110 if (mVInt
.mLength
> mEBMLMaxIdLength
) {
111 nsPrintfCString
detail("Invalid element id of length %" PRIu64
,
113 WEBM_DEBUG("%s", detail
.get());
114 return MediaResult(NS_ERROR_FAILURE
, detail
);
117 mElement
.mID
= mVInt
;
119 mNextState
= PARSE_ELEMENT
;
121 case FIND_CLUSTER_SYNC
:
122 if (*p
++ == CLUSTER_SYNC_ID
[mClusterSyncPos
]) {
123 mClusterSyncPos
+= 1;
127 if (mClusterSyncPos
== sizeof(CLUSTER_SYNC_ID
)) {
128 mVInt
.mValue
= CLUSTER_ID
;
129 mVInt
.mLength
= sizeof(CLUSTER_SYNC_ID
);
130 mState
= READ_ELEMENT_SIZE
;
134 if (mVInt
.mLength
> mEBMLMaxSizeLength
) {
135 nsPrintfCString
detail("Invalid element size of length %" PRIu64
,
137 WEBM_DEBUG("%s", detail
.get());
138 return MediaResult(NS_ERROR_FAILURE
, detail
);
140 mElement
.mSize
= mVInt
;
141 switch (mElement
.mID
.mValue
) {
143 mState
= READ_ELEMENT_ID
;
146 mGotTimecodeScale
= true;
147 mState
= READ_ELEMENT_ID
;
151 mVIntLeft
= mElement
.mSize
.mValue
;
152 mState
= READ_VINT_REST
;
153 mNextState
= READ_CLUSTER_TIMECODE
;
155 case TIMECODESCALE_ID
:
157 mVIntLeft
= mElement
.mSize
.mValue
;
158 mState
= READ_VINT_REST
;
159 mNextState
= READ_TIMECODESCALE
;
162 mClusterOffset
= mCurrentOffset
+ (p
- aBuffer
) -
163 (mElement
.mID
.mLength
+ mElement
.mSize
.mLength
);
164 // Handle "unknown" length;
165 if (mElement
.mSize
.mValue
+ 1 !=
166 uint64_t(1) << (mElement
.mSize
.mLength
* 7)) {
167 mClusterEndOffset
= mClusterOffset
+ mElement
.mID
.mLength
+
168 mElement
.mSize
.mLength
+
169 mElement
.mSize
.mValue
;
171 mClusterEndOffset
= -1;
173 mGotClusterTimecode
= false;
174 mState
= READ_ELEMENT_ID
;
177 mState
= READ_ELEMENT_ID
;
182 if (!mGotClusterTimecode
) {
184 "The Timecode element must appear before any Block or "
185 "SimpleBlock elements in a Cluster");
188 "The Timecode element must appear before any Block or "
189 "SimpleBlock elements in a Cluster");
191 mBlockSize
= mElement
.mSize
.mValue
;
193 mBlockTimecodeLength
= BLOCK_TIMECODE_LENGTH
;
194 mBlockOffset
= mCurrentOffset
+ (p
- aBuffer
) -
195 (mElement
.mID
.mLength
+ mElement
.mSize
.mLength
);
197 mNextState
= READ_BLOCK_TIMECODE
;
200 mSkipBytes
= mElement
.mSize
.mValue
;
201 mState
= CHECK_INIT_FOUND
;
203 case EBML_MAX_ID_LENGTH_ID
:
204 case EBML_MAX_SIZE_LENGTH_ID
:
205 if (int64_t currentOffset
= mCurrentOffset
+ (p
- aBuffer
);
206 currentOffset
< mLastInitStartOffset
||
207 currentOffset
>= mLastInitStartOffset
+ mLastInitSize
) {
208 nsPrintfCString
str("Unexpected %s outside init segment",
209 mElement
.mID
.mValue
== EBML_MAX_ID_LENGTH_ID
211 : "EBMLMaxSizeLength");
212 WEBM_DEBUG("%s", str
.get());
213 return MediaResult(NS_ERROR_FAILURE
, str
);
215 if (mElement
.mSize
.mValue
> 8) {
216 // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
217 // An Unsigned Integer Element MUST declare a length from zero
219 nsPrintfCString
str("Bad length of %s size",
220 mElement
.mID
.mValue
== EBML_MAX_ID_LENGTH_ID
222 : "EBMLMaxSizeLength");
223 WEBM_DEBUG("%s", str
.get());
224 return MediaResult(NS_ERROR_FAILURE
, str
);
227 mVIntLeft
= mElement
.mSize
.mValue
;
228 mState
= READ_VINT_REST
;
229 mNextState
= mElement
.mID
.mValue
== EBML_MAX_ID_LENGTH_ID
230 ? READ_EBML_MAX_ID_LENGTH
231 : READ_EBML_MAX_SIZE_LENGTH
;
234 mLastInitStartOffset
=
235 mCurrentOffset
+ (p
- aBuffer
) -
236 (mElement
.mID
.mLength
+ mElement
.mSize
.mLength
);
237 mLastInitSize
= mElement
.mSize
.mValue
;
238 mEBMLMaxIdLength
= EBML_MAX_ID_LENGTH_DEFAULT
;
239 mEBMLMaxSizeLength
= EBML_MAX_SIZE_LENGTH_DEFAULT
;
240 mState
= READ_ELEMENT_ID
;
243 mSkipBytes
= mElement
.mSize
.mValue
;
245 mNextState
= READ_ELEMENT_ID
;
250 unsigned char c
= *p
++;
252 mVInt
.mLength
= VIntLength(c
, &mask
);
253 mVIntLeft
= mVInt
.mLength
- 1;
254 mVInt
.mValue
= mVIntRaw
? c
: c
& ~mask
;
255 mState
= READ_VINT_REST
;
261 mVInt
.mValue
|= *p
++;
267 case READ_TIMECODESCALE
:
268 if (!mGotTimecodeScale
) {
269 WEBM_DEBUG("Should get the SegmentInfo first");
270 return MediaResult(NS_ERROR_FAILURE
,
271 "TimecodeScale appeared before SegmentInfo");
273 mTimecodeScale
= mVInt
.mValue
;
274 WEBM_DEBUG("READ_TIMECODESCALE %" PRIu32
, mTimecodeScale
);
275 mState
= READ_ELEMENT_ID
;
277 case READ_CLUSTER_TIMECODE
:
278 mClusterTimecode
= mVInt
.mValue
;
279 mGotClusterTimecode
= true;
280 mState
= READ_ELEMENT_ID
;
282 case READ_BLOCK_TIMECODE
:
283 if (mBlockTimecodeLength
) {
284 mBlockTimecode
<<= 8;
285 mBlockTimecode
|= *p
++;
286 mBlockTimecodeLength
-= 1;
288 // It's possible we've parsed this data before, so avoid inserting
289 // duplicate WebMTimeDataOffset entries.
291 int64_t endOffset
= mBlockOffset
+ mBlockSize
+
292 mElement
.mID
.mLength
+ mElement
.mSize
.mLength
;
293 uint32_t idx
= aMapping
.IndexOfFirstElementGt(endOffset
);
294 if (idx
== 0 || aMapping
[idx
- 1] != endOffset
) {
295 // Don't insert invalid negative timecodes.
296 if (mBlockTimecode
>= 0 ||
297 mClusterTimecode
>= uint16_t(abs(mBlockTimecode
))) {
298 if (!mGotTimecodeScale
) {
299 WEBM_DEBUG("Should get the TimecodeScale first");
300 return MediaResult(NS_ERROR_FAILURE
,
301 "Timecode appeared before SegmentInfo");
303 uint64_t absTimecode
= mClusterTimecode
+ mBlockTimecode
;
304 absTimecode
*= mTimecodeScale
;
305 // Avoid creating an entry if the timecode is out of order
306 // (invalid according to the WebM specification) so that
307 // ordering invariants of aMapping are not violated.
308 if (idx
== 0 || aMapping
[idx
- 1].mTimecode
<= absTimecode
||
309 (idx
+ 1 < aMapping
.Length() &&
310 aMapping
[idx
+ 1].mTimecode
>= absTimecode
)) {
311 WebMTimeDataOffset
entry(endOffset
, absTimecode
,
312 mLastInitStartOffset
, mClusterOffset
,
314 aMapping
.InsertElementAt(idx
, entry
);
316 WEBM_DEBUG("Out of order timecode %" PRIu64
317 " in Cluster at %" PRId64
" ignored",
318 absTimecode
, mClusterOffset
);
324 // Skip rest of block header and the block's payload.
325 mBlockSize
-= mVInt
.mLength
;
326 mBlockSize
-= BLOCK_TIMECODE_LENGTH
;
327 mSkipBytes
= uint32_t(mBlockSize
);
329 mNextState
= READ_ELEMENT_ID
;
332 case READ_EBML_MAX_ID_LENGTH
:
333 if (mElement
.mSize
.mLength
== 0) {
334 // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
335 // If an Empty Element has a default value declared, then the EBML
336 // Reader MUST interpret the value of the Empty Element as the
338 mVInt
.mValue
= EBML_MAX_ID_LENGTH_DEFAULT
;
340 if (mVInt
.mValue
< 4 || mVInt
.mValue
> 5) {
341 // https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-13.html
343 // The EBMLMaxIDLength of the EBML Header MUST be "4".
346 // Element IDs are encoded using the VINT mechanism described in
347 // Section 4 of [RFC8794] and can be between one and five octets
348 // long. Five-octet-long Element IDs are possible only if declared
349 // in the EBML header.
350 nsPrintfCString
detail("Invalid EMBLMaxIdLength %" PRIu64
,
352 WEBM_DEBUG("%s", detail
.get());
353 return MediaResult(NS_ERROR_FAILURE
, detail
);
355 mEBMLMaxIdLength
= mVInt
.mValue
;
356 mState
= READ_ELEMENT_ID
;
358 case READ_EBML_MAX_SIZE_LENGTH
:
359 if (mElement
.mSize
.mLength
== 0) {
360 // https://www.rfc-editor.org/rfc/rfc8794.html (EBML):
361 // If an Empty Element has a default value declared, then the EBML
362 // Reader MUST interpret the value of the Empty Element as the
364 mVInt
.mValue
= EBML_MAX_SIZE_LENGTH_DEFAULT
;
366 if (mVInt
.mValue
< 1 || mVInt
.mValue
> 8) {
367 // https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-13.html
369 // The EBMLMaxSizeLength of the EBML Header MUST be between "1" and
371 nsPrintfCString
detail("Invalid EMBLMaxSizeLength %" PRIu64
,
373 WEBM_DEBUG("%s", detail
.get());
374 return MediaResult(NS_ERROR_FAILURE
, detail
);
376 mEBMLMaxSizeLength
= mVInt
.mValue
;
377 mState
= READ_ELEMENT_ID
;
381 uint32_t left
= aLength
- (p
- aBuffer
);
382 left
= std::min(left
, mSkipBytes
);
387 mBlockEndOffset
= mCurrentOffset
+ (p
- aBuffer
);
391 case CHECK_INIT_FOUND
:
393 uint32_t left
= aLength
- (p
- aBuffer
);
394 left
= std::min(left
, mSkipBytes
);
399 if (mInitEndOffset
< 0) {
400 mInitEndOffset
= mCurrentOffset
+ (p
- aBuffer
);
401 mBlockEndOffset
= mCurrentOffset
+ (p
- aBuffer
);
403 mState
= READ_ELEMENT_ID
;
409 NS_ASSERTION(p
== aBuffer
+ aLength
, "Must have parsed to end of data.");
410 mCurrentOffset
+= aLength
;
415 int64_t WebMBufferedParser::EndSegmentOffset(int64_t aOffset
) {
416 if (mLastInitStartOffset
> aOffset
|| mClusterOffset
> aOffset
) {
418 mLastInitStartOffset
>= 0 ? mLastInitStartOffset
: INT64_MAX
,
419 mClusterOffset
>= 0 ? mClusterOffset
: INT64_MAX
);
421 return mBlockEndOffset
;
424 int64_t WebMBufferedParser::GetClusterOffset() const { return mClusterOffset
; }
426 // SyncOffsetComparator and TimeComparator are slightly confusing, in that
427 // the nsTArray they're used with (mTimeMapping) is sorted by mEndOffset and
428 // these comparators are used on the other fields of WebMTimeDataOffset.
429 // This is only valid because timecodes are required to be monotonically
430 // increasing within a file (thus establishing an ordering relationship with
431 // mTimecode), and mEndOffset is derived from mSyncOffset.
432 struct SyncOffsetComparator
{
433 bool Equals(const WebMTimeDataOffset
& a
, const int64_t& b
) const {
434 return a
.mSyncOffset
== b
;
437 bool LessThan(const WebMTimeDataOffset
& a
, const int64_t& b
) const {
438 return a
.mSyncOffset
< b
;
442 struct TimeComparator
{
443 bool Equals(const WebMTimeDataOffset
& a
, const uint64_t& b
) const {
444 return a
.mTimecode
== b
;
447 bool LessThan(const WebMTimeDataOffset
& a
, const uint64_t& b
) const {
448 return a
.mTimecode
< b
;
452 bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset
,
454 uint64_t* aStartTime
,
455 uint64_t* aEndTime
) {
456 MutexAutoLock
lock(mMutex
);
458 // Find the first WebMTimeDataOffset at or after aStartOffset.
459 uint32_t start
= mTimeMapping
.IndexOfFirstElementGt(aStartOffset
- 1,
460 SyncOffsetComparator());
461 if (start
== mTimeMapping
.Length()) {
465 // Find the first WebMTimeDataOffset at or before aEndOffset.
466 uint32_t end
= mTimeMapping
.IndexOfFirstElementGt(aEndOffset
);
476 NS_ASSERTION(mTimeMapping
[start
].mSyncOffset
>= aStartOffset
&&
477 mTimeMapping
[end
].mEndOffset
<= aEndOffset
,
478 "Computed time range must lie within data range.");
480 NS_ASSERTION(mTimeMapping
[start
- 1].mSyncOffset
< aStartOffset
,
481 "Must have found least WebMTimeDataOffset for start");
483 if (end
< mTimeMapping
.Length() - 1) {
484 NS_ASSERTION(mTimeMapping
[end
+ 1].mEndOffset
> aEndOffset
,
485 "Must have found greatest WebMTimeDataOffset for end");
488 MOZ_ASSERT(mTimeMapping
[end
].mTimecode
>= mTimeMapping
[end
- 1].mTimecode
);
489 uint64_t frameDuration
=
490 mTimeMapping
[end
].mTimecode
- mTimeMapping
[end
- 1].mTimecode
;
491 *aStartTime
= mTimeMapping
[start
].mTimecode
;
492 CheckedUint64 endTime
{mTimeMapping
[end
].mTimecode
};
493 endTime
+= frameDuration
;
494 if (!endTime
.isValid()) {
495 WEBM_DEBUG("End time overflow during CalculateBufferedForRange.");
498 *aEndTime
= endTime
.value();
502 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime
, int64_t* aOffset
) {
503 MutexAutoLock
lock(mMutex
);
505 if (mTimeMapping
.IsEmpty()) {
509 uint64_t time
= aTime
;
513 uint32_t idx
= mTimeMapping
.IndexOfFirstElementGt(time
, TimeComparator());
514 if (idx
== mTimeMapping
.Length()) {
516 *aOffset
= mTimeMapping
[mTimeMapping
.Length() - 1].mSyncOffset
;
518 // Idx is within array or has been clamped to start
519 *aOffset
= mTimeMapping
[idx
].mSyncOffset
;
524 void WebMBufferedState::NotifyDataArrived(const unsigned char* aBuffer
,
525 uint32_t aLength
, int64_t aOffset
) {
526 uint32_t idx
= mRangeParsers
.IndexOfFirstElementGt(aOffset
- 1);
527 if (idx
== 0 || !(mRangeParsers
[idx
- 1] == aOffset
)) {
528 // If the incoming data overlaps an already parsed range, adjust the
529 // buffer so that we only reparse the new data. It's also possible to
530 // have an overlap where the end of the incoming data is within an
531 // already parsed range, but we don't bother handling that other than by
532 // avoiding storing duplicate timecodes when the parser runs.
533 if (idx
!= mRangeParsers
.Length() &&
534 mRangeParsers
[idx
].mStartOffset
<= aOffset
) {
535 // Complete overlap, skip parsing.
536 if (aOffset
+ aLength
<= mRangeParsers
[idx
].mCurrentOffset
) {
540 // Partial overlap, adjust the buffer to parse only the new data.
541 int64_t adjust
= mRangeParsers
[idx
].mCurrentOffset
- aOffset
;
542 NS_ASSERTION(adjust
>= 0, "Overlap detection bug.");
544 aLength
-= uint32_t(adjust
);
546 mRangeParsers
.InsertElementAt(idx
, WebMBufferedParser(aOffset
));
548 mRangeParsers
[idx
].SetTimecodeScale(
549 mRangeParsers
[0].GetTimecodeScale());
555 MutexAutoLock
lock(mMutex
);
556 mRangeParsers
[idx
].Append(aBuffer
, aLength
, mTimeMapping
);
559 // Merge parsers with overlapping regions and clean up the remnants.
561 while (i
+ 1 < mRangeParsers
.Length()) {
562 if (mRangeParsers
[i
].mCurrentOffset
>= mRangeParsers
[i
+ 1].mStartOffset
) {
563 mRangeParsers
[i
+ 1].mStartOffset
= mRangeParsers
[i
].mStartOffset
;
564 mRangeParsers
[i
+ 1].mInitEndOffset
= mRangeParsers
[i
].mInitEndOffset
;
565 mRangeParsers
.RemoveElementAt(i
);
571 if (mRangeParsers
.IsEmpty()) {
575 MutexAutoLock
lock(mMutex
);
576 mLastBlockOffset
= mRangeParsers
.LastElement().mBlockEndOffset
;
579 void WebMBufferedState::Reset() {
580 MutexAutoLock
lock(mMutex
);
581 mRangeParsers
.Clear();
582 mTimeMapping
.Clear();
585 void WebMBufferedState::UpdateIndex(const MediaByteRangeSet
& aRanges
,
586 MediaResource
* aResource
) {
587 for (uint32_t index
= 0; index
< aRanges
.Length(); index
++) {
588 const MediaByteRange
& range
= aRanges
[index
];
589 int64_t offset
= range
.mStart
;
590 uint32_t length
= range
.mEnd
- range
.mStart
;
592 uint32_t idx
= mRangeParsers
.IndexOfFirstElementGt(offset
- 1);
593 if (!idx
|| !(mRangeParsers
[idx
- 1] == offset
)) {
594 // If the incoming data overlaps an already parsed range, adjust the
595 // buffer so that we only reparse the new data. It's also possible to
596 // have an overlap where the end of the incoming data is within an
597 // already parsed range, but we don't bother handling that other than by
598 // avoiding storing duplicate timecodes when the parser runs.
599 if (idx
!= mRangeParsers
.Length() &&
600 mRangeParsers
[idx
].mStartOffset
<= offset
) {
601 // Complete overlap, skip parsing.
602 if (offset
+ length
<= mRangeParsers
[idx
].mCurrentOffset
) {
606 // Partial overlap, adjust the buffer to parse only the new data.
607 int64_t adjust
= mRangeParsers
[idx
].mCurrentOffset
- offset
;
608 NS_ASSERTION(adjust
>= 0, "Overlap detection bug.");
610 length
-= uint32_t(adjust
);
612 mRangeParsers
.InsertElementAt(idx
, WebMBufferedParser(offset
));
614 mRangeParsers
[idx
].SetTimecodeScale(
615 mRangeParsers
[0].GetTimecodeScale());
620 MediaResourceIndex
res(aResource
);
622 static const uint32_t BLOCK_SIZE
= 1048576;
623 uint32_t block
= std::min(length
, BLOCK_SIZE
);
624 RefPtr
<MediaByteBuffer
> bytes
= res
.CachedMediaReadAt(offset
, block
);
628 NotifyDataArrived(bytes
->Elements(), bytes
->Length(), offset
);
629 length
-= bytes
->Length();
630 offset
+= bytes
->Length();
635 int64_t WebMBufferedState::GetInitEndOffset() {
636 if (mRangeParsers
.IsEmpty()) {
639 return mRangeParsers
[0].mInitEndOffset
;
642 int64_t WebMBufferedState::GetLastBlockOffset() {
643 MutexAutoLock
lock(mMutex
);
645 return mLastBlockOffset
;
648 bool WebMBufferedState::GetStartTime(uint64_t* aTime
) {
649 MutexAutoLock
lock(mMutex
);
651 if (mTimeMapping
.IsEmpty()) {
655 uint32_t idx
= mTimeMapping
.IndexOfFirstElementGt(0, SyncOffsetComparator());
656 if (idx
== mTimeMapping
.Length()) {
660 *aTime
= mTimeMapping
[idx
].mTimecode
;
664 bool WebMBufferedState::GetNextKeyframeTime(uint64_t aTime
,
665 uint64_t* aKeyframeTime
) {
666 MutexAutoLock
lock(mMutex
);
668 bool rv
= GetOffsetForTime(aTime
, &offset
);
673 mTimeMapping
.IndexOfFirstElementGt(offset
, SyncOffsetComparator());
674 if (idx
== mTimeMapping
.Length()) {
677 *aKeyframeTime
= mTimeMapping
[idx
].mTimecode
;
680 } // namespace mozilla