Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/webm/WebMBufferedParser.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "nsAlgorithm.h"
8
#include "WebMBufferedParser.h"
9
#include "nsThreadUtils.h"
10
#include <algorithm>
11
12
extern mozilla::LazyLogModule gMediaDemuxerLog;
13
0
#define WEBM_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("WebMBufferedParser(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
14
15
namespace mozilla {
16
17
static uint32_t
18
VIntLength(unsigned char aFirstByte, uint32_t* aMask)
19
0
{
20
0
  uint32_t count = 1;
21
0
  uint32_t mask = 1 << 7;
22
0
  while (count < 8) {
23
0
    if ((aFirstByte & mask) != 0) {
24
0
      break;
25
0
    }
26
0
    mask >>= 1;
27
0
    count += 1;
28
0
  }
29
0
  if (aMask) {
30
0
    *aMask = mask;
31
0
  }
32
0
  NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
33
0
  return count;
34
0
}
35
36
bool WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
37
                                nsTArray<WebMTimeDataOffset>& aMapping,
38
                                ReentrantMonitor& aReentrantMonitor)
39
0
{
40
0
  static const uint32_t EBML_ID = 0x1a45dfa3;
41
0
  static const uint32_t SEGMENT_ID = 0x18538067;
42
0
  static const uint32_t SEGINFO_ID = 0x1549a966;
43
0
  static const uint32_t TRACKS_ID = 0x1654AE6B;
44
0
  static const uint32_t CLUSTER_ID = 0x1f43b675;
45
0
  static const uint32_t TIMECODESCALE_ID = 0x2ad7b1;
46
0
  static const unsigned char TIMECODE_ID = 0xe7;
47
0
  static const unsigned char BLOCKGROUP_ID = 0xa0;
48
0
  static const unsigned char BLOCK_ID = 0xa1;
49
0
  static const unsigned char SIMPLEBLOCK_ID = 0xa3;
50
0
  static const uint32_t BLOCK_TIMECODE_LENGTH = 2;
51
0
52
0
  static const unsigned char CLUSTER_SYNC_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
53
0
54
0
  const unsigned char* p = aBuffer;
55
0
56
0
  // Parse each byte in aBuffer one-by-one, producing timecodes and updating
57
0
  // aMapping as we go.  Parser pauses at end of stream (which may be at any
58
0
  // point within the parse) and resumes parsing the next time Append is
59
0
  // called with new data.
60
0
  while (p < aBuffer + aLength) {
61
0
    switch (mState) {
62
0
    case READ_ELEMENT_ID:
63
0
      mVIntRaw = true;
64
0
      mState = READ_VINT;
65
0
      mNextState = READ_ELEMENT_SIZE;
66
0
      break;
67
0
    case READ_ELEMENT_SIZE:
68
0
      mVIntRaw = false;
69
0
      mElement.mID = mVInt;
70
0
      mState = READ_VINT;
71
0
      mNextState = PARSE_ELEMENT;
72
0
      break;
73
0
    case FIND_CLUSTER_SYNC:
74
0
      if (*p++ == CLUSTER_SYNC_ID[mClusterSyncPos]) {
75
0
        mClusterSyncPos += 1;
76
0
      } else {
77
0
        mClusterSyncPos = 0;
78
0
      }
79
0
      if (mClusterSyncPos == sizeof(CLUSTER_SYNC_ID)) {
80
0
        mVInt.mValue = CLUSTER_ID;
81
0
        mVInt.mLength = sizeof(CLUSTER_SYNC_ID);
82
0
        mState = READ_ELEMENT_SIZE;
83
0
      }
84
0
      break;
85
0
    case PARSE_ELEMENT:
86
0
      mElement.mSize = mVInt;
87
0
      switch (mElement.mID.mValue) {
88
0
      case SEGMENT_ID:
89
0
        mState = READ_ELEMENT_ID;
90
0
        break;
91
0
      case SEGINFO_ID:
92
0
        mGotTimecodeScale = true;
93
0
        mState = READ_ELEMENT_ID;
94
0
        break;
95
0
      case TIMECODE_ID:
96
0
        mVInt = VInt();
97
0
        mVIntLeft = mElement.mSize.mValue;
98
0
        mState = READ_VINT_REST;
99
0
        mNextState = READ_CLUSTER_TIMECODE;
100
0
        break;
101
0
      case TIMECODESCALE_ID:
102
0
        mVInt = VInt();
103
0
        mVIntLeft = mElement.mSize.mValue;
104
0
        mState = READ_VINT_REST;
105
0
        mNextState = READ_TIMECODESCALE;
106
0
        break;
107
0
      case CLUSTER_ID:
108
0
        mClusterOffset = mCurrentOffset + (p - aBuffer) -
109
0
                        (mElement.mID.mLength + mElement.mSize.mLength);
110
0
        // Handle "unknown" length;
111
0
        if (mElement.mSize.mValue + 1 != uint64_t(1) << (mElement.mSize.mLength * 7)) {
112
0
          mClusterEndOffset = mClusterOffset + mElement.mID.mLength + mElement.mSize.mLength + mElement.mSize.mValue;
113
0
        } else {
114
0
          mClusterEndOffset = -1;
115
0
        }
116
0
        mGotClusterTimecode = false;
117
0
        mState = READ_ELEMENT_ID;
118
0
        break;
119
0
      case BLOCKGROUP_ID:
120
0
        mState = READ_ELEMENT_ID;
121
0
        break;
122
0
      case SIMPLEBLOCK_ID:
123
0
        /* FALLTHROUGH */
124
0
      case BLOCK_ID:
125
0
        if (!mGotClusterTimecode) {
126
0
          WEBM_DEBUG("The Timecode element must appear before any Block or "
127
0
                     "SimpleBlock elements in a Cluster");
128
0
          return false;
129
0
        }
130
0
        mBlockSize = mElement.mSize.mValue;
131
0
        mBlockTimecode = 0;
132
0
        mBlockTimecodeLength = BLOCK_TIMECODE_LENGTH;
133
0
        mBlockOffset = mCurrentOffset + (p - aBuffer) -
134
0
                       (mElement.mID.mLength + mElement.mSize.mLength);
135
0
        mState = READ_VINT;
136
0
        mNextState = READ_BLOCK_TIMECODE;
137
0
        break;
138
0
      case TRACKS_ID:
139
0
        mSkipBytes = mElement.mSize.mValue;
140
0
        mState = CHECK_INIT_FOUND;
141
0
        break;
142
0
      case EBML_ID:
143
0
        mLastInitStartOffset = mCurrentOffset + (p - aBuffer) -
144
0
                            (mElement.mID.mLength + mElement.mSize.mLength);
145
0
        MOZ_FALLTHROUGH;
146
0
      default:
147
0
        mSkipBytes = mElement.mSize.mValue;
148
0
        mState = SKIP_DATA;
149
0
        mNextState = READ_ELEMENT_ID;
150
0
        break;
151
0
      }
152
0
      break;
153
0
    case READ_VINT: {
154
0
      unsigned char c = *p++;
155
0
      uint32_t mask;
156
0
      mVInt.mLength = VIntLength(c, &mask);
157
0
      mVIntLeft = mVInt.mLength - 1;
158
0
      mVInt.mValue = mVIntRaw ? c : c & ~mask;
159
0
      mState = READ_VINT_REST;
160
0
      break;
161
0
    }
162
0
    case READ_VINT_REST:
163
0
      if (mVIntLeft) {
164
0
        mVInt.mValue <<= 8;
165
0
        mVInt.mValue |= *p++;
166
0
        mVIntLeft -= 1;
167
0
      } else {
168
0
        mState = mNextState;
169
0
      }
170
0
      break;
171
0
    case READ_TIMECODESCALE:
172
0
      if (!mGotTimecodeScale) {
173
0
        WEBM_DEBUG("Should get the SegmentInfo first");
174
0
        return false;
175
0
      }
176
0
      mTimecodeScale = mVInt.mValue;
177
0
      mState = READ_ELEMENT_ID;
178
0
      break;
179
0
    case READ_CLUSTER_TIMECODE:
180
0
      mClusterTimecode = mVInt.mValue;
181
0
      mGotClusterTimecode = true;
182
0
      mState = READ_ELEMENT_ID;
183
0
      break;
184
0
    case READ_BLOCK_TIMECODE:
185
0
      if (mBlockTimecodeLength) {
186
0
        mBlockTimecode <<= 8;
187
0
        mBlockTimecode |= *p++;
188
0
        mBlockTimecodeLength -= 1;
189
0
      } else {
190
0
        // It's possible we've parsed this data before, so avoid inserting
191
0
        // duplicate WebMTimeDataOffset entries.
192
0
        {
193
0
          ReentrantMonitorAutoEnter mon(aReentrantMonitor);
194
0
          int64_t endOffset = mBlockOffset + mBlockSize +
195
0
                              mElement.mID.mLength + mElement.mSize.mLength;
196
0
          uint32_t idx = aMapping.IndexOfFirstElementGt(endOffset);
197
0
          if (idx == 0 || aMapping[idx - 1] != endOffset) {
198
0
            // Don't insert invalid negative timecodes.
199
0
            if (mBlockTimecode >= 0 || mClusterTimecode >= uint16_t(abs(mBlockTimecode))) {
200
0
              if (!mGotTimecodeScale) {
201
0
                WEBM_DEBUG("Should get the TimecodeScale first");
202
0
                return false;
203
0
              }
204
0
              uint64_t absTimecode = mClusterTimecode + mBlockTimecode;
205
0
              absTimecode *= mTimecodeScale;
206
0
              // Avoid creating an entry if the timecode is out of order
207
0
              // (invalid according to the WebM specification) so that
208
0
              // ordering invariants of aMapping are not violated.
209
0
              if (idx == 0 ||
210
0
                  aMapping[idx - 1].mTimecode <= absTimecode ||
211
0
                  (idx + 1 < aMapping.Length() &&
212
0
                   aMapping[idx + 1].mTimecode >= absTimecode)) {
213
0
                WebMTimeDataOffset entry(endOffset, absTimecode, mLastInitStartOffset,
214
0
                                         mClusterOffset, mClusterEndOffset);
215
0
                aMapping.InsertElementAt(idx, entry);
216
0
              } else {
217
0
                WEBM_DEBUG("Out of order timecode %" PRIu64 " in Cluster at %" PRId64 " ignored",
218
0
                           absTimecode, mClusterOffset);
219
0
              }
220
0
            }
221
0
          }
222
0
        }
223
0
224
0
        // Skip rest of block header and the block's payload.
225
0
        mBlockSize -= mVInt.mLength;
226
0
        mBlockSize -= BLOCK_TIMECODE_LENGTH;
227
0
        mSkipBytes = uint32_t(mBlockSize);
228
0
        mState = SKIP_DATA;
229
0
        mNextState = READ_ELEMENT_ID;
230
0
      }
231
0
      break;
232
0
    case SKIP_DATA:
233
0
      if (mSkipBytes) {
234
0
        uint32_t left = aLength - (p - aBuffer);
235
0
        left = std::min(left, mSkipBytes);
236
0
        p += left;
237
0
        mSkipBytes -= left;
238
0
      }
239
0
      if (!mSkipBytes) {
240
0
        mBlockEndOffset = mCurrentOffset + (p - aBuffer);
241
0
        mState = mNextState;
242
0
      }
243
0
      break;
244
0
    case CHECK_INIT_FOUND:
245
0
      if (mSkipBytes) {
246
0
        uint32_t left = aLength - (p - aBuffer);
247
0
        left = std::min(left, mSkipBytes);
248
0
        p += left;
249
0
        mSkipBytes -= left;
250
0
      }
251
0
      if (!mSkipBytes) {
252
0
        if (mInitEndOffset < 0) {
253
0
          mInitEndOffset = mCurrentOffset + (p - aBuffer);
254
0
          mBlockEndOffset = mCurrentOffset + (p - aBuffer);
255
0
        }
256
0
        mState = READ_ELEMENT_ID;
257
0
      }
258
0
      break;
259
0
    }
260
0
  }
261
0
262
0
  NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
263
0
  mCurrentOffset += aLength;
264
0
265
0
  return true;
266
0
}
267
268
int64_t
269
WebMBufferedParser::EndSegmentOffset(int64_t aOffset)
270
0
{
271
0
  if (mLastInitStartOffset > aOffset || mClusterOffset > aOffset) {
272
0
    return std::min(mLastInitStartOffset >= 0 ? mLastInitStartOffset : INT64_MAX,
273
0
                    mClusterOffset >= 0 ? mClusterOffset : INT64_MAX);
274
0
  }
275
0
  return mBlockEndOffset;
276
0
}
277
278
int64_t
279
WebMBufferedParser::GetClusterOffset() const
280
0
{
281
0
  return mClusterOffset;
282
0
}
283
284
// SyncOffsetComparator and TimeComparator are slightly confusing, in that
285
// the nsTArray they're used with (mTimeMapping) is sorted by mEndOffset and
286
// these comparators are used on the other fields of WebMTimeDataOffset.
287
// This is only valid because timecodes are required to be monotonically
288
// increasing within a file (thus establishing an ordering relationship with
289
// mTimecode), and mEndOffset is derived from mSyncOffset.
290
struct SyncOffsetComparator {
291
0
  bool Equals(const WebMTimeDataOffset& a, const int64_t& b) const {
292
0
    return a.mSyncOffset == b;
293
0
  }
294
295
0
  bool LessThan(const WebMTimeDataOffset& a, const int64_t& b) const {
296
0
    return a.mSyncOffset < b;
297
0
  }
298
};
299
300
struct TimeComparator {
301
0
  bool Equals(const WebMTimeDataOffset& a, const uint64_t& b) const {
302
0
    return a.mTimecode == b;
303
0
  }
304
305
0
  bool LessThan(const WebMTimeDataOffset& a, const uint64_t& b) const {
306
0
    return a.mTimecode < b;
307
0
  }
308
};
309
310
bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
311
                                                  uint64_t* aStartTime, uint64_t* aEndTime)
312
0
{
313
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
314
0
315
0
  // Find the first WebMTimeDataOffset at or after aStartOffset.
316
0
  uint32_t start = mTimeMapping.IndexOfFirstElementGt(aStartOffset - 1, SyncOffsetComparator());
317
0
  if (start == mTimeMapping.Length()) {
318
0
    return false;
319
0
  }
320
0
321
0
  // Find the first WebMTimeDataOffset at or before aEndOffset.
322
0
  uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset);
323
0
  if (end > 0) {
324
0
    end -= 1;
325
0
  }
326
0
327
0
  // Range is empty.
328
0
  if (end <= start) {
329
0
    return false;
330
0
  }
331
0
332
0
  NS_ASSERTION(mTimeMapping[start].mSyncOffset >= aStartOffset &&
333
0
               mTimeMapping[end].mEndOffset <= aEndOffset,
334
0
               "Computed time range must lie within data range.");
335
0
  if (start > 0) {
336
0
    NS_ASSERTION(mTimeMapping[start - 1].mSyncOffset < aStartOffset,
337
0
                 "Must have found least WebMTimeDataOffset for start");
338
0
  }
339
0
  if (end < mTimeMapping.Length() - 1) {
340
0
    NS_ASSERTION(mTimeMapping[end + 1].mEndOffset > aEndOffset,
341
0
                 "Must have found greatest WebMTimeDataOffset for end");
342
0
  }
343
0
344
0
  MOZ_ASSERT(mTimeMapping[end].mTimecode >= mTimeMapping[end - 1].mTimecode);
345
0
  uint64_t frameDuration = mTimeMapping[end].mTimecode - mTimeMapping[end - 1].mTimecode;
346
0
  *aStartTime = mTimeMapping[start].mTimecode;
347
0
  *aEndTime = mTimeMapping[end].mTimecode + frameDuration;
348
0
  return true;
349
0
}
350
351
bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset)
352
0
{
353
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
354
0
355
0
  if(mTimeMapping.IsEmpty()) {
356
0
    return false;
357
0
  }
358
0
359
0
  uint64_t time = aTime;
360
0
  if (time > 0) {
361
0
    time = time - 1;
362
0
  }
363
0
  uint32_t idx = mTimeMapping.IndexOfFirstElementGt(time, TimeComparator());
364
0
  if (idx == mTimeMapping.Length()) {
365
0
    // Clamp to end
366
0
    *aOffset = mTimeMapping[mTimeMapping.Length() - 1].mSyncOffset;
367
0
  } else {
368
0
    // Idx is within array or has been clamped to start
369
0
    *aOffset = mTimeMapping[idx].mSyncOffset;
370
0
  }
371
0
  return true;
372
0
}
373
374
void WebMBufferedState::NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset)
375
0
{
376
0
  uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1);
377
0
  if (idx == 0 || !(mRangeParsers[idx-1] == aOffset)) {
378
0
    // If the incoming data overlaps an already parsed range, adjust the
379
0
    // buffer so that we only reparse the new data.  It's also possible to
380
0
    // have an overlap where the end of the incoming data is within an
381
0
    // already parsed range, but we don't bother handling that other than by
382
0
    // avoiding storing duplicate timecodes when the parser runs.
383
0
    if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
384
0
      // Complete overlap, skip parsing.
385
0
      if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
386
0
        return;
387
0
      }
388
0
389
0
      // Partial overlap, adjust the buffer to parse only the new data.
390
0
      int64_t adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
391
0
      NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
392
0
      aBuffer += adjust;
393
0
      aLength -= uint32_t(adjust);
394
0
    } else {
395
0
      mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset));
396
0
      if (idx != 0) {
397
0
        mRangeParsers[idx].SetTimecodeScale(mRangeParsers[0].GetTimecodeScale());
398
0
      }
399
0
    }
400
0
  }
401
0
402
0
  mRangeParsers[idx].Append(aBuffer,
403
0
                            aLength,
404
0
                            mTimeMapping,
405
0
                            mReentrantMonitor);
406
0
407
0
  // Merge parsers with overlapping regions and clean up the remnants.
408
0
  uint32_t i = 0;
409
0
  while (i + 1 < mRangeParsers.Length()) {
410
0
    if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
411
0
      mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
412
0
      mRangeParsers[i + 1].mInitEndOffset = mRangeParsers[i].mInitEndOffset;
413
0
      mRangeParsers.RemoveElementAt(i);
414
0
    } else {
415
0
      i += 1;
416
0
    }
417
0
  }
418
0
419
0
  if (mRangeParsers.IsEmpty()) {
420
0
    return;
421
0
  }
422
0
423
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
424
0
  mLastBlockOffset = mRangeParsers.LastElement().mBlockEndOffset;
425
0
}
426
427
0
void WebMBufferedState::Reset() {
428
0
  mRangeParsers.Clear();
429
0
  mTimeMapping.Clear();
430
0
}
431
432
void WebMBufferedState::UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource)
433
0
{
434
0
  for (uint32_t index = 0; index < aRanges.Length(); index++) {
435
0
    const MediaByteRange& range = aRanges[index];
436
0
    int64_t offset = range.mStart;
437
0
    uint32_t length = range.mEnd - range.mStart;
438
0
439
0
    uint32_t idx = mRangeParsers.IndexOfFirstElementGt(offset - 1);
440
0
    if (!idx || !(mRangeParsers[idx-1] == offset)) {
441
0
      // If the incoming data overlaps an already parsed range, adjust the
442
0
      // buffer so that we only reparse the new data.  It's also possible to
443
0
      // have an overlap where the end of the incoming data is within an
444
0
      // already parsed range, but we don't bother handling that other than by
445
0
      // avoiding storing duplicate timecodes when the parser runs.
446
0
      if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= offset) {
447
0
        // Complete overlap, skip parsing.
448
0
        if (offset + length <= mRangeParsers[idx].mCurrentOffset) {
449
0
          continue;
450
0
        }
451
0
452
0
        // Partial overlap, adjust the buffer to parse only the new data.
453
0
        int64_t adjust = mRangeParsers[idx].mCurrentOffset - offset;
454
0
        NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
455
0
        offset += adjust;
456
0
        length -= uint32_t(adjust);
457
0
      } else {
458
0
        mRangeParsers.InsertElementAt(idx, WebMBufferedParser(offset));
459
0
        if (idx) {
460
0
          mRangeParsers[idx].SetTimecodeScale(mRangeParsers[0].GetTimecodeScale());
461
0
        }
462
0
      }
463
0
    }
464
0
465
0
    MediaResourceIndex res(aResource);
466
0
    while (length > 0) {
467
0
      static const uint32_t BLOCK_SIZE = 1048576;
468
0
      uint32_t block = std::min(length, BLOCK_SIZE);
469
0
      RefPtr<MediaByteBuffer> bytes = res.CachedMediaReadAt(offset, block);
470
0
      if (!bytes) {
471
0
        break;
472
0
      }
473
0
      NotifyDataArrived(bytes->Elements(), bytes->Length(), offset);
474
0
      length -= bytes->Length();
475
0
      offset += bytes->Length();
476
0
    }
477
0
  }
478
0
}
479
480
int64_t WebMBufferedState::GetInitEndOffset()
481
0
{
482
0
  if (mRangeParsers.IsEmpty()) {
483
0
    return -1;
484
0
  }
485
0
  return mRangeParsers[0].mInitEndOffset;
486
0
}
487
488
int64_t WebMBufferedState::GetLastBlockOffset()
489
0
{
490
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
491
0
492
0
  return mLastBlockOffset;
493
0
}
494
495
bool WebMBufferedState::GetStartTime(uint64_t *aTime)
496
0
{
497
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
498
0
499
0
  if (mTimeMapping.IsEmpty()) {
500
0
    return false;
501
0
  }
502
0
503
0
  uint32_t idx = mTimeMapping.IndexOfFirstElementGt(0, SyncOffsetComparator());
504
0
  if (idx == mTimeMapping.Length()) {
505
0
    return false;
506
0
  }
507
0
508
0
  *aTime = mTimeMapping[idx].mTimecode;
509
0
  return true;
510
0
}
511
512
bool
513
WebMBufferedState::GetNextKeyframeTime(uint64_t aTime, uint64_t* aKeyframeTime)
514
0
{
515
0
  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
516
0
  int64_t offset = 0;
517
0
  bool rv = GetOffsetForTime(aTime, &offset);
518
0
  if (!rv) {
519
0
    return false;
520
0
  }
521
0
  uint32_t idx = mTimeMapping.IndexOfFirstElementGt(offset, SyncOffsetComparator());
522
0
  if (idx == mTimeMapping.Length()) {
523
0
    return false;
524
0
  }
525
0
  *aKeyframeTime = mTimeMapping[idx].mTimecode;
526
0
  return true;
527
0
}
528
} // namespace mozilla
529
530
#undef WEBM_DEBUG
531