Backed out 7 changesets (bug 1942424) for causing frequent crashes. a=backout
[gecko.git] / dom / media / webm / WebMBufferedParser.cpp
blob3d2e9937cb44e3d11696bf8aca425335b970c663
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"
9 #include <algorithm>
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__))
19 namespace mozilla {
21 static uint32_t VIntLength(unsigned char aFirstByte, uint32_t* aMask) {
22 uint32_t count = 1;
23 uint32_t mask = 1 << 7;
24 while (count < 8) {
25 if ((aFirstByte & mask) != 0) {
26 break;
28 mask >>= 1;
29 count += 1;
31 if (aMask) {
32 *aMask = mask;
34 NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
35 return count;
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),
44 mInitEndOffset(-1),
45 mBlockEndOffset(-1),
46 mState(READ_ELEMENT_ID),
47 mNextState(READ_ELEMENT_ID),
48 mVIntRaw(false),
49 mLastInitStartOffset(-1),
50 mLastInitSize(0),
51 mEBMLMaxIdLength(EBML_MAX_ID_LENGTH_DEFAULT),
52 mEBMLMaxSizeLength(EBML_MAX_SIZE_LENGTH_DEFAULT),
53 mClusterSyncPos(0),
54 mVIntLeft(0),
55 mBlockSize(0),
56 mClusterTimecode(0),
57 mClusterOffset(-1),
58 mClusterEndOffset(-1),
59 mBlockOffset(0),
60 mBlockTimecode(0),
61 mBlockTimecodeLength(0),
62 mSkipBytes(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,
78 uint32_t aLength,
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) {
103 switch (mState) {
104 case READ_ELEMENT_ID:
105 mVIntRaw = true;
106 mState = READ_VINT;
107 mNextState = READ_ELEMENT_SIZE;
108 break;
109 case READ_ELEMENT_SIZE:
110 if (mVInt.mLength > mEBMLMaxIdLength) {
111 nsPrintfCString detail("Invalid element id of length %" PRIu64,
112 mVInt.mLength);
113 WEBM_DEBUG("%s", detail.get());
114 return MediaResult(NS_ERROR_FAILURE, detail);
116 mVIntRaw = false;
117 mElement.mID = mVInt;
118 mState = READ_VINT;
119 mNextState = PARSE_ELEMENT;
120 break;
121 case FIND_CLUSTER_SYNC:
122 if (*p++ == CLUSTER_SYNC_ID[mClusterSyncPos]) {
123 mClusterSyncPos += 1;
124 } else {
125 mClusterSyncPos = 0;
127 if (mClusterSyncPos == sizeof(CLUSTER_SYNC_ID)) {
128 mVInt.mValue = CLUSTER_ID;
129 mVInt.mLength = sizeof(CLUSTER_SYNC_ID);
130 mState = READ_ELEMENT_SIZE;
132 break;
133 case PARSE_ELEMENT:
134 if (mVInt.mLength > mEBMLMaxSizeLength) {
135 nsPrintfCString detail("Invalid element size of length %" PRIu64,
136 mVInt.mLength);
137 WEBM_DEBUG("%s", detail.get());
138 return MediaResult(NS_ERROR_FAILURE, detail);
140 mElement.mSize = mVInt;
141 switch (mElement.mID.mValue) {
142 case SEGMENT_ID:
143 mState = READ_ELEMENT_ID;
144 break;
145 case SEGINFO_ID:
146 mGotTimecodeScale = true;
147 mState = READ_ELEMENT_ID;
148 break;
149 case TIMECODE_ID:
150 mVInt = VInt();
151 mVIntLeft = mElement.mSize.mValue;
152 mState = READ_VINT_REST;
153 mNextState = READ_CLUSTER_TIMECODE;
154 break;
155 case TIMECODESCALE_ID:
156 mVInt = VInt();
157 mVIntLeft = mElement.mSize.mValue;
158 mState = READ_VINT_REST;
159 mNextState = READ_TIMECODESCALE;
160 break;
161 case CLUSTER_ID:
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;
170 } else {
171 mClusterEndOffset = -1;
173 mGotClusterTimecode = false;
174 mState = READ_ELEMENT_ID;
175 break;
176 case BLOCKGROUP_ID:
177 mState = READ_ELEMENT_ID;
178 break;
179 case SIMPLEBLOCK_ID:
180 /* FALLTHROUGH */
181 case BLOCK_ID:
182 if (!mGotClusterTimecode) {
183 WEBM_DEBUG(
184 "The Timecode element must appear before any Block or "
185 "SimpleBlock elements in a Cluster");
186 return MediaResult(
187 NS_ERROR_FAILURE,
188 "The Timecode element must appear before any Block or "
189 "SimpleBlock elements in a Cluster");
191 mBlockSize = mElement.mSize.mValue;
192 mBlockTimecode = 0;
193 mBlockTimecodeLength = BLOCK_TIMECODE_LENGTH;
194 mBlockOffset = mCurrentOffset + (p - aBuffer) -
195 (mElement.mID.mLength + mElement.mSize.mLength);
196 mState = READ_VINT;
197 mNextState = READ_BLOCK_TIMECODE;
198 break;
199 case TRACKS_ID:
200 mSkipBytes = mElement.mSize.mValue;
201 mState = CHECK_INIT_FOUND;
202 break;
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
210 ? "EBMLMaxIdLength"
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
218 // to eight octets.
219 nsPrintfCString str("Bad length of %s size",
220 mElement.mID.mValue == EBML_MAX_ID_LENGTH_ID
221 ? "EBMLMaxIdLength"
222 : "EBMLMaxSizeLength");
223 WEBM_DEBUG("%s", str.get());
224 return MediaResult(NS_ERROR_FAILURE, str);
226 mVInt = VInt();
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;
232 break;
233 case EBML_ID:
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;
241 break;
242 default:
243 mSkipBytes = mElement.mSize.mValue;
244 mState = SKIP_DATA;
245 mNextState = READ_ELEMENT_ID;
246 break;
248 break;
249 case READ_VINT: {
250 unsigned char c = *p++;
251 uint32_t mask;
252 mVInt.mLength = VIntLength(c, &mask);
253 mVIntLeft = mVInt.mLength - 1;
254 mVInt.mValue = mVIntRaw ? c : c & ~mask;
255 mState = READ_VINT_REST;
256 break;
258 case READ_VINT_REST:
259 if (mVIntLeft) {
260 mVInt.mValue <<= 8;
261 mVInt.mValue |= *p++;
262 mVIntLeft -= 1;
263 } else {
264 mState = mNextState;
266 break;
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;
276 break;
277 case READ_CLUSTER_TIMECODE:
278 mClusterTimecode = mVInt.mValue;
279 mGotClusterTimecode = true;
280 mState = READ_ELEMENT_ID;
281 break;
282 case READ_BLOCK_TIMECODE:
283 if (mBlockTimecodeLength) {
284 mBlockTimecode <<= 8;
285 mBlockTimecode |= *p++;
286 mBlockTimecodeLength -= 1;
287 } else {
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,
313 mClusterEndOffset);
314 aMapping.InsertElementAt(idx, entry);
315 } else {
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);
328 mState = SKIP_DATA;
329 mNextState = READ_ELEMENT_ID;
331 break;
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
337 // default value.
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
342 // (Matroska):
343 // The EBMLMaxIDLength of the EBML Header MUST be "4".
345 // Also Matroska:
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,
351 mVInt.mValue);
352 WEBM_DEBUG("%s", detail.get());
353 return MediaResult(NS_ERROR_FAILURE, detail);
355 mEBMLMaxIdLength = mVInt.mValue;
356 mState = READ_ELEMENT_ID;
357 break;
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
363 // default value.
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
368 // (Matroska):
369 // The EBMLMaxSizeLength of the EBML Header MUST be between "1" and
370 // "8" inclusive.
371 nsPrintfCString detail("Invalid EMBLMaxSizeLength %" PRIu64,
372 mVInt.mValue);
373 WEBM_DEBUG("%s", detail.get());
374 return MediaResult(NS_ERROR_FAILURE, detail);
376 mEBMLMaxSizeLength = mVInt.mValue;
377 mState = READ_ELEMENT_ID;
378 break;
379 case SKIP_DATA:
380 if (mSkipBytes) {
381 uint32_t left = aLength - (p - aBuffer);
382 left = std::min(left, mSkipBytes);
383 p += left;
384 mSkipBytes -= left;
386 if (!mSkipBytes) {
387 mBlockEndOffset = mCurrentOffset + (p - aBuffer);
388 mState = mNextState;
390 break;
391 case CHECK_INIT_FOUND:
392 if (mSkipBytes) {
393 uint32_t left = aLength - (p - aBuffer);
394 left = std::min(left, mSkipBytes);
395 p += left;
396 mSkipBytes -= left;
398 if (!mSkipBytes) {
399 if (mInitEndOffset < 0) {
400 mInitEndOffset = mCurrentOffset + (p - aBuffer);
401 mBlockEndOffset = mCurrentOffset + (p - aBuffer);
403 mState = READ_ELEMENT_ID;
405 break;
409 NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
410 mCurrentOffset += aLength;
412 return NS_OK;
415 int64_t WebMBufferedParser::EndSegmentOffset(int64_t aOffset) {
416 if (mLastInitStartOffset > aOffset || mClusterOffset > aOffset) {
417 return std::min(
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,
453 int64_t aEndOffset,
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()) {
462 return false;
465 // Find the first WebMTimeDataOffset at or before aEndOffset.
466 uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset);
467 if (end > 0) {
468 end -= 1;
471 // Range is empty.
472 if (end <= start) {
473 return false;
476 NS_ASSERTION(mTimeMapping[start].mSyncOffset >= aStartOffset &&
477 mTimeMapping[end].mEndOffset <= aEndOffset,
478 "Computed time range must lie within data range.");
479 if (start > 0) {
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.");
496 return false;
498 *aEndTime = endTime.value();
499 return true;
502 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset) {
503 MutexAutoLock lock(mMutex);
505 if (mTimeMapping.IsEmpty()) {
506 return false;
509 uint64_t time = aTime;
510 if (time > 0) {
511 time = time - 1;
513 uint32_t idx = mTimeMapping.IndexOfFirstElementGt(time, TimeComparator());
514 if (idx == mTimeMapping.Length()) {
515 // Clamp to end
516 *aOffset = mTimeMapping[mTimeMapping.Length() - 1].mSyncOffset;
517 } else {
518 // Idx is within array or has been clamped to start
519 *aOffset = mTimeMapping[idx].mSyncOffset;
521 return true;
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) {
537 return;
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.");
543 aBuffer += adjust;
544 aLength -= uint32_t(adjust);
545 } else {
546 mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset));
547 if (idx != 0) {
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.
560 uint32_t i = 0;
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);
566 } else {
567 i += 1;
571 if (mRangeParsers.IsEmpty()) {
572 return;
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) {
603 continue;
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.");
609 offset += adjust;
610 length -= uint32_t(adjust);
611 } else {
612 mRangeParsers.InsertElementAt(idx, WebMBufferedParser(offset));
613 if (idx) {
614 mRangeParsers[idx].SetTimecodeScale(
615 mRangeParsers[0].GetTimecodeScale());
620 MediaResourceIndex res(aResource);
621 while (length > 0) {
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);
625 if (!bytes) {
626 break;
628 NotifyDataArrived(bytes->Elements(), bytes->Length(), offset);
629 length -= bytes->Length();
630 offset += bytes->Length();
635 int64_t WebMBufferedState::GetInitEndOffset() {
636 if (mRangeParsers.IsEmpty()) {
637 return -1;
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()) {
652 return false;
655 uint32_t idx = mTimeMapping.IndexOfFirstElementGt(0, SyncOffsetComparator());
656 if (idx == mTimeMapping.Length()) {
657 return false;
660 *aTime = mTimeMapping[idx].mTimecode;
661 return true;
664 bool WebMBufferedState::GetNextKeyframeTime(uint64_t aTime,
665 uint64_t* aKeyframeTime) {
666 MutexAutoLock lock(mMutex);
667 int64_t offset = 0;
668 bool rv = GetOffsetForTime(aTime, &offset);
669 if (!rv) {
670 return false;
672 uint32_t idx =
673 mTimeMapping.IndexOfFirstElementGt(offset, SyncOffsetComparator());
674 if (idx == mTimeMapping.Length()) {
675 return false;
677 *aKeyframeTime = mTimeMapping[idx].mTimecode;
678 return true;
680 } // namespace mozilla
682 #undef WEBM_DEBUG