1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "Index.h"
6 
7 #include <algorithm>
8 #include <limits>
9 
10 #include "BufferReader.h"
11 #include "mozilla/RefPtr.h"
12 #include "MP4Interval.h"
13 #include "MP4Metadata.h"
14 #include "SinfParser.h"
15 
16 using namespace mozilla::media;
17 
18 namespace mozilla {
19 
20 class MOZ_STACK_CLASS RangeFinder {
21  public:
22   // Given that we're processing this in order we don't use a binary search
23   // to find the apropriate time range. Instead we search linearly from the
24   // last used point.
RangeFinder(const MediaByteRangeSet & ranges)25   explicit RangeFinder(const MediaByteRangeSet& ranges)
26       : mRanges(ranges), mIndex(0) {
27     // Ranges must be normalised for this to work
28   }
29 
30   bool Contains(MediaByteRange aByteRange);
31 
32  private:
33   const MediaByteRangeSet& mRanges;
34   size_t mIndex;
35 };
36 
Contains(MediaByteRange aByteRange)37 bool RangeFinder::Contains(MediaByteRange aByteRange) {
38   if (mRanges.IsEmpty()) {
39     return false;
40   }
41 
42   if (mRanges[mIndex].ContainsStrict(aByteRange)) {
43     return true;
44   }
45 
46   if (aByteRange.mStart < mRanges[mIndex].mStart) {
47     // Search backwards
48     do {
49       if (!mIndex) {
50         return false;
51       }
52       --mIndex;
53       if (mRanges[mIndex].ContainsStrict(aByteRange)) {
54         return true;
55       }
56     } while (aByteRange.mStart < mRanges[mIndex].mStart);
57 
58     return false;
59   }
60 
61   while (aByteRange.mEnd > mRanges[mIndex].mEnd) {
62     if (mIndex == mRanges.Length() - 1) {
63       return false;
64     }
65     ++mIndex;
66     if (mRanges[mIndex].ContainsStrict(aByteRange)) {
67       return true;
68     }
69   }
70 
71   return false;
72 }
73 
SampleIterator(Index * aIndex)74 SampleIterator::SampleIterator(Index* aIndex)
75     : mIndex(aIndex), mCurrentMoof(0), mCurrentSample(0) {
76   mIndex->RegisterIterator(this);
77 }
78 
~SampleIterator()79 SampleIterator::~SampleIterator() { mIndex->UnregisterIterator(this); }
80 
GetNext()81 already_AddRefed<MediaRawData> SampleIterator::GetNext() {
82   Sample* s(Get());
83   if (!s) {
84     return nullptr;
85   }
86 
87   int64_t length = std::numeric_limits<int64_t>::max();
88   mIndex->mSource->Length(&length);
89   if (s->mByteRange.mEnd > length) {
90     // We don't have this complete sample.
91     return nullptr;
92   }
93 
94   RefPtr<MediaRawData> sample = new MediaRawData();
95   sample->mTimecode = TimeUnit::FromMicroseconds(s->mDecodeTime);
96   sample->mTime = TimeUnit::FromMicroseconds(s->mCompositionRange.start);
97   sample->mDuration = TimeUnit::FromMicroseconds(s->mCompositionRange.Length());
98   sample->mOffset = s->mByteRange.mStart;
99   sample->mKeyframe = s->mSync;
100 
101   UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
102   // Do the blocking read
103   if (!writer->SetSize(s->mByteRange.Length())) {
104     return nullptr;
105   }
106 
107   size_t bytesRead;
108   if (!mIndex->mSource->ReadAt(sample->mOffset, writer->Data(), sample->Size(),
109                                &bytesRead) ||
110       bytesRead != sample->Size()) {
111     return nullptr;
112   }
113 
114   MoofParser* moofParser = mIndex->mMoofParser.get();
115   if (!moofParser) {
116     // File is not fragmented, we can't have crypto, just early return.
117     Next();
118     return sample.forget();
119   }
120 
121   // We need to check if this moof has init data the CDM expects us to surface.
122   // This should happen when handling the first sample, even if that sample
123   // isn't encrypted (samples later in the moof may be).
124   if (mCurrentSample == 0) {
125     const nsTArray<Moof>& moofs = moofParser->Moofs();
126     const Moof* currentMoof = &moofs[mCurrentMoof];
127     if (!currentMoof->mPsshes.IsEmpty()) {
128       // This Moof contained crypto init data. Report that. We only report
129       // the init data on the Moof's first sample, to avoid reporting it more
130       // than once per Moof.
131       writer->mCrypto.mInitDatas.AppendElements(currentMoof->mPsshes);
132       writer->mCrypto.mInitDataType = u"cenc"_ns;
133     }
134   }
135 
136   auto cryptoSchemeResult = GetEncryptionScheme();
137   if (cryptoSchemeResult.isErr()) {
138     // Log the error here in future.
139     return nullptr;
140   }
141   CryptoScheme cryptoScheme = cryptoSchemeResult.unwrap();
142   if (cryptoScheme == CryptoScheme::None) {
143     // No crypto to handle, early return.
144     Next();
145     return sample.forget();
146   }
147 
148   writer->mCrypto.mCryptoScheme = cryptoScheme;
149   MOZ_ASSERT(writer->mCrypto.mCryptoScheme != CryptoScheme::None,
150              "Should have early returned if we don't have a crypto scheme!");
151   MOZ_ASSERT(writer->mCrypto.mKeyId.IsEmpty(),
152              "Sample should not already have a key ID");
153   MOZ_ASSERT(writer->mCrypto.mConstantIV.IsEmpty(),
154              "Sample should not already have a constant IV");
155   CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry();
156   if (sampleInfo) {
157     // Use sample group information if present, this supersedes track level
158     // information.
159     writer->mCrypto.mKeyId.AppendElements(sampleInfo->mKeyId);
160     writer->mCrypto.mIVSize = sampleInfo->mIVSize;
161     writer->mCrypto.mCryptByteBlock = sampleInfo->mCryptByteBlock;
162     writer->mCrypto.mSkipByteBlock = sampleInfo->mSkipByteBlock;
163     writer->mCrypto.mConstantIV.AppendElements(sampleInfo->mConsantIV);
164   } else {
165     // Use the crypto info from track metadata
166     writer->mCrypto.mKeyId.AppendElements(moofParser->mSinf.mDefaultKeyID, 16);
167     writer->mCrypto.mIVSize = moofParser->mSinf.mDefaultIVSize;
168     writer->mCrypto.mCryptByteBlock = moofParser->mSinf.mDefaultCryptByteBlock;
169     writer->mCrypto.mSkipByteBlock = moofParser->mSinf.mDefaultSkipByteBlock;
170     writer->mCrypto.mConstantIV.AppendElements(
171         moofParser->mSinf.mDefaultConstantIV);
172   }
173 
174   if ((writer->mCrypto.mIVSize == 0 && writer->mCrypto.mConstantIV.IsEmpty()) ||
175       (writer->mCrypto.mIVSize != 0 && s->mCencRange.IsEmpty())) {
176     // If mIVSize == 0, this indicates that a constant IV is in use, thus we
177     // should have a non empty constant IV. Alternatively if IV size is non
178     // zero, we should have an IV for this sample, which we need to look up
179     // in mCencRange (which must then be non empty). If neither of these are
180     // true we have bad crypto data, so bail.
181     return nullptr;
182   }
183   // Parse auxiliary information if present
184   if (!s->mCencRange.IsEmpty()) {
185     // The size comes from an 8 bit field
186     AutoTArray<uint8_t, 256> cencAuxInfo;
187     cencAuxInfo.SetLength(s->mCencRange.Length());
188     if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cencAuxInfo.Elements(),
189                                  cencAuxInfo.Length(), &bytesRead) ||
190         bytesRead != cencAuxInfo.Length()) {
191       return nullptr;
192     }
193     BufferReader reader(cencAuxInfo);
194     if (!reader.ReadArray(writer->mCrypto.mIV, writer->mCrypto.mIVSize)) {
195       return nullptr;
196     }
197 
198     // Parse the auxiliary information for subsample information
199     auto res = reader.ReadU16();
200     if (res.isOk() && res.unwrap() > 0) {
201       uint16_t count = res.unwrap();
202 
203       if (reader.Remaining() < count * 6) {
204         return nullptr;
205       }
206 
207       for (size_t i = 0; i < count; i++) {
208         auto res_16 = reader.ReadU16();
209         auto res_32 = reader.ReadU32();
210         if (res_16.isErr() || res_32.isErr()) {
211           return nullptr;
212         }
213         writer->mCrypto.mPlainSizes.AppendElement(res_16.unwrap());
214         writer->mCrypto.mEncryptedSizes.AppendElement(res_32.unwrap());
215       }
216     } else {
217       // No subsample information means the entire sample is encrypted.
218       writer->mCrypto.mPlainSizes.AppendElement(0);
219       writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
220     }
221   }
222 
223   Next();
224 
225   return sample.forget();
226 }
227 
GetSampleDescriptionEntry()228 SampleDescriptionEntry* SampleIterator::GetSampleDescriptionEntry() {
229   nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
230   Moof& currentMoof = moofs[mCurrentMoof];
231   uint32_t sampleDescriptionIndex =
232       currentMoof.mTfhd.mDefaultSampleDescriptionIndex;
233   // Mp4 indices start at 1, shift down 1 so we index our array correctly.
234   sampleDescriptionIndex--;
235   FallibleTArray<SampleDescriptionEntry>& sampleDescriptions =
236       mIndex->mMoofParser->mSampleDescriptions;
237   if (sampleDescriptionIndex >= sampleDescriptions.Length()) {
238     // The sample description index is invalid, the mp4 is malformed. Bail out.
239     return nullptr;
240   }
241   return &sampleDescriptions[sampleDescriptionIndex];
242 }
243 
GetSampleEncryptionEntry()244 CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry() {
245   nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
246   Moof* currentMoof = &moofs[mCurrentMoof];
247   SampleToGroupEntry* sampleToGroupEntry = nullptr;
248 
249   // Default to using the sample to group entries for the fragment, otherwise
250   // fall back to the sample to group entries for the track.
251   FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries =
252       currentMoof->mFragmentSampleToGroupEntries.Length() != 0
253           ? &currentMoof->mFragmentSampleToGroupEntries
254           : &mIndex->mMoofParser->mTrackSampleToGroupEntries;
255 
256   uint32_t seen = 0;
257 
258   for (SampleToGroupEntry& entry : *sampleToGroupEntries) {
259     if (seen + entry.mSampleCount > mCurrentSample) {
260       sampleToGroupEntry = &entry;
261       break;
262     }
263     seen += entry.mSampleCount;
264   }
265 
266   // ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index
267   // (1) ranges from 1 to the number of sample group entries in the track
268   // level SampleGroupDescription Box, or (2) takes the value 0 to
269   // indicate that this sample is a member of no group, in this case, the
270   // sample is associated with the default values specified in
271   // TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value
272   // 1, with the value 1 in the top 16 bits, to reference fragment-local
273   // SampleGroupDescription Box.
274 
275   // According to the spec, ISO-14496-12, the sum of the sample counts in this
276   // box should be equal to the total number of samples, and, if less, the
277   // reader should behave as if an extra SampleToGroupEntry existed, with
278   // groupDescriptionIndex 0.
279 
280   if (!sampleToGroupEntry || sampleToGroupEntry->mGroupDescriptionIndex == 0) {
281     return nullptr;
282   }
283 
284   FallibleTArray<CencSampleEncryptionInfoEntry>* entries =
285       &mIndex->mMoofParser->mTrackSampleEncryptionInfoEntries;
286 
287   uint32_t groupIndex = sampleToGroupEntry->mGroupDescriptionIndex;
288 
289   // If the first bit is set to a one, then we should use the sample group
290   // descriptions from the fragment.
291   if (groupIndex > SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) {
292     groupIndex -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase;
293     entries = &currentMoof->mFragmentSampleEncryptionInfoEntries;
294   }
295 
296   // The group_index is one based.
297   return groupIndex > entries->Length() ? nullptr
298                                         : &entries->ElementAt(groupIndex - 1);
299 }
300 
GetEncryptionScheme()301 Result<CryptoScheme, nsCString> SampleIterator::GetEncryptionScheme() {
302   // See ISO/IEC 23001-7 for information on the metadata being checked.
303   MoofParser* moofParser = mIndex->mMoofParser.get();
304   if (!moofParser) {
305     // This mp4 isn't fragmented so it can't be encrypted.
306     return CryptoScheme::None;
307   }
308 
309   SampleDescriptionEntry* sampleDescriptionEntry = GetSampleDescriptionEntry();
310   if (!sampleDescriptionEntry) {
311     // For the file to be valid the tfhd must reference a sample description
312     // entry.
313     // If we encounter this error often, we may consider using the first
314     // sample description entry if the index is out of bounds.
315     return mozilla::Err(nsLiteralCString(
316         "Could not determine encryption scheme due to bad index for sample "
317         "description entry."));
318   }
319 
320   if (!sampleDescriptionEntry->mIsEncryptedEntry) {
321     return CryptoScheme::None;
322   }
323 
324   if (!moofParser->mSinf.IsValid()) {
325     // The sample description entry says this sample is encrypted, but we
326     // don't have a valid sinf box. This shouldn't happen as the sinf box is
327     // part of the sample description entry. Suggests a malformed file, bail.
328     return mozilla::Err(nsLiteralCString(
329         "Could not determine encryption scheme. Sample description entry "
330         "indicates encryption, but could not find associated sinf box."));
331   }
332 
333   CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry();
334   if (sampleInfo && !sampleInfo->mIsEncrypted) {
335     // May not have sample encryption info, but if we do, it should match other
336     // metadata.
337     return mozilla::Err(nsLiteralCString(
338         "Could not determine encryption scheme. Sample description entry "
339         "indicates encryption, but sample encryption entry indicates sample is "
340         "not encrypted. These should be consistent."));
341   }
342 
343   if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cenc")) {
344     return CryptoScheme::Cenc;
345   } else if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cbcs")) {
346     return CryptoScheme::Cbcs;
347   }
348   return mozilla::Err(nsLiteralCString(
349       "Could not determine encryption scheme. Sample description entry "
350       "reports sample is encrypted, but no scheme, or an unsupported scheme "
351       "is in use."));
352 }
353 
Get()354 Sample* SampleIterator::Get() {
355   if (!mIndex->mMoofParser) {
356     MOZ_ASSERT(!mCurrentMoof);
357     return mCurrentSample < mIndex->mIndex.Length()
358                ? &mIndex->mIndex[mCurrentSample]
359                : nullptr;
360   }
361 
362   nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
363   while (true) {
364     if (mCurrentMoof == moofs.Length()) {
365       if (!mIndex->mMoofParser->BlockingReadNextMoof()) {
366         return nullptr;
367       }
368       MOZ_ASSERT(mCurrentMoof < moofs.Length());
369     }
370     if (mCurrentSample < moofs[mCurrentMoof].mIndex.Length()) {
371       break;
372     }
373     mCurrentSample = 0;
374     ++mCurrentMoof;
375   }
376   return &moofs[mCurrentMoof].mIndex[mCurrentSample];
377 }
378 
Next()379 void SampleIterator::Next() { ++mCurrentSample; }
380 
Seek(Microseconds aTime)381 void SampleIterator::Seek(Microseconds aTime) {
382   size_t syncMoof = 0;
383   size_t syncSample = 0;
384   mCurrentMoof = 0;
385   mCurrentSample = 0;
386   Sample* sample;
387   while (!!(sample = Get())) {
388     if (sample->mCompositionRange.start > aTime) {
389       break;
390     }
391     if (sample->mSync) {
392       syncMoof = mCurrentMoof;
393       syncSample = mCurrentSample;
394     }
395     if (sample->mCompositionRange.start == aTime) {
396       break;
397     }
398     Next();
399   }
400   mCurrentMoof = syncMoof;
401   mCurrentSample = syncSample;
402 }
403 
GetNextKeyframeTime()404 Microseconds SampleIterator::GetNextKeyframeTime() {
405   SampleIterator itr(*this);
406   Sample* sample;
407   while (!!(sample = itr.Get())) {
408     if (sample->mSync) {
409       return sample->mCompositionRange.start;
410     }
411     itr.Next();
412   }
413   return -1;
414 }
415 
Index(const IndiceWrapper & aIndices,ByteStream * aSource,uint32_t aTrackId,bool aIsAudio)416 Index::Index(const IndiceWrapper& aIndices, ByteStream* aSource,
417              uint32_t aTrackId, bool aIsAudio)
418     : mSource(aSource), mIsAudio(aIsAudio) {
419   if (!aIndices.Length()) {
420     mMoofParser =
421         MakeUnique<MoofParser>(aSource, AsVariant(aTrackId), aIsAudio);
422   } else {
423     if (!mIndex.SetCapacity(aIndices.Length(), fallible)) {
424       // OOM.
425       return;
426     }
427     media::IntervalSet<int64_t> intervalTime;
428     MediaByteRange intervalRange;
429     bool haveSync = false;
430     bool progressive = true;
431     int64_t lastOffset = 0;
432     for (size_t i = 0; i < aIndices.Length(); i++) {
433       Indice indice;
434       if (!aIndices.GetIndice(i, indice)) {
435         // Out of index?
436         return;
437       }
438       if (indice.sync || mIsAudio) {
439         haveSync = true;
440       }
441       if (!haveSync) {
442         continue;
443       }
444       Sample sample;
445       sample.mByteRange =
446           MediaByteRange(indice.start_offset, indice.end_offset);
447       sample.mCompositionRange = MP4Interval<Microseconds>(
448           indice.start_composition, indice.end_composition);
449       sample.mDecodeTime = indice.start_decode;
450       sample.mSync = indice.sync || mIsAudio;
451       // FIXME: Make this infallible after bug 968520 is done.
452       MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
453       if (indice.start_offset < lastOffset) {
454         NS_WARNING("Chunks in MP4 out of order, expect slow down");
455         progressive = false;
456       }
457       lastOffset = indice.end_offset;
458 
459       // Pack audio samples in group of 128.
460       if (sample.mSync && progressive && (!mIsAudio || !(i % 128))) {
461         if (mDataOffset.Length()) {
462           auto& last = mDataOffset.LastElement();
463           last.mEndOffset = intervalRange.mEnd;
464           NS_ASSERTION(intervalTime.Length() == 1,
465                        "Discontinuous samples between keyframes");
466           last.mTime.start = intervalTime.GetStart();
467           last.mTime.end = intervalTime.GetEnd();
468         }
469         if (!mDataOffset.AppendElement(
470                 MP4DataOffset(mIndex.Length() - 1, indice.start_offset),
471                 fallible)) {
472           // OOM.
473           return;
474         }
475         intervalTime = media::IntervalSet<int64_t>();
476         intervalRange = MediaByteRange();
477       }
478       intervalTime += media::Interval<int64_t>(sample.mCompositionRange.start,
479                                                sample.mCompositionRange.end);
480       intervalRange = intervalRange.Span(sample.mByteRange);
481     }
482 
483     if (mDataOffset.Length() && progressive) {
484       Indice indice;
485       if (!aIndices.GetIndice(aIndices.Length() - 1, indice)) {
486         return;
487       }
488       auto& last = mDataOffset.LastElement();
489       last.mEndOffset = indice.end_offset;
490       last.mTime =
491           MP4Interval<int64_t>(intervalTime.GetStart(), intervalTime.GetEnd());
492     } else {
493       mDataOffset.Clear();
494     }
495   }
496 }
497 
498 Index::~Index() = default;
499 
UpdateMoofIndex(const MediaByteRangeSet & aByteRanges)500 void Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges) {
501   UpdateMoofIndex(aByteRanges, false);
502 }
503 
UpdateMoofIndex(const MediaByteRangeSet & aByteRanges,bool aCanEvict)504 void Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges,
505                             bool aCanEvict) {
506   if (!mMoofParser) {
507     return;
508   }
509   size_t moofs = mMoofParser->Moofs().Length();
510   bool canEvict = aCanEvict && moofs > 1;
511   if (canEvict) {
512     // Check that we can trim the mMoofParser. We can only do so if all
513     // iterators have demuxed all possible samples.
514     for (const SampleIterator* iterator : mIterators) {
515       if ((iterator->mCurrentSample == 0 && iterator->mCurrentMoof == moofs) ||
516           iterator->mCurrentMoof == moofs - 1) {
517         continue;
518       }
519       canEvict = false;
520       break;
521     }
522   }
523   mMoofParser->RebuildFragmentedIndex(aByteRanges, &canEvict);
524   if (canEvict) {
525     // The moofparser got trimmed. Adjust all registered iterators.
526     for (SampleIterator* iterator : mIterators) {
527       iterator->mCurrentMoof -= moofs - 1;
528     }
529   }
530 }
531 
GetEndCompositionIfBuffered(const MediaByteRangeSet & aByteRanges)532 Microseconds Index::GetEndCompositionIfBuffered(
533     const MediaByteRangeSet& aByteRanges) {
534   FallibleTArray<Sample>* index;
535   if (mMoofParser) {
536     if (!mMoofParser->ReachedEnd() || mMoofParser->Moofs().IsEmpty()) {
537       return 0;
538     }
539     index = &mMoofParser->Moofs().LastElement().mIndex;
540   } else {
541     index = &mIndex;
542   }
543 
544   Microseconds lastComposition = 0;
545   RangeFinder rangeFinder(aByteRanges);
546   for (size_t i = index->Length(); i--;) {
547     const Sample& sample = (*index)[i];
548     if (!rangeFinder.Contains(sample.mByteRange)) {
549       return 0;
550     }
551     lastComposition = std::max(lastComposition, sample.mCompositionRange.end);
552     if (sample.mSync) {
553       return lastComposition;
554     }
555   }
556   return 0;
557 }
558 
ConvertByteRangesToTimeRanges(const MediaByteRangeSet & aByteRanges)559 TimeIntervals Index::ConvertByteRangesToTimeRanges(
560     const MediaByteRangeSet& aByteRanges) {
561   if (aByteRanges == mLastCachedRanges) {
562     return mLastBufferedRanges;
563   }
564   mLastCachedRanges = aByteRanges;
565 
566   if (mDataOffset.Length()) {
567     TimeIntervals timeRanges;
568     for (const auto& range : aByteRanges) {
569       uint32_t start = mDataOffset.IndexOfFirstElementGt(range.mStart - 1);
570       if (!mIsAudio && start == mDataOffset.Length()) {
571         continue;
572       }
573       uint32_t end = mDataOffset.IndexOfFirstElementGt(
574           range.mEnd, MP4DataOffset::EndOffsetComparator());
575       if (!mIsAudio && end < start) {
576         continue;
577       }
578       if (mIsAudio && start &&
579           range.Intersects(MediaByteRange(mDataOffset[start - 1].mStartOffset,
580                                           mDataOffset[start - 1].mEndOffset))) {
581         // Check if previous audio data block contains some available samples.
582         for (size_t i = mDataOffset[start - 1].mIndex; i < mIndex.Length();
583              i++) {
584           if (range.ContainsStrict(mIndex[i].mByteRange)) {
585             timeRanges += TimeInterval(
586                 TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
587                 TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
588           }
589         }
590       }
591       if (end > start) {
592         for (uint32_t i = start; i < end; i++) {
593           timeRanges += TimeInterval(
594               TimeUnit::FromMicroseconds(mDataOffset[i].mTime.start),
595               TimeUnit::FromMicroseconds(mDataOffset[i].mTime.end));
596         }
597       }
598       if (end < mDataOffset.Length()) {
599         // Find samples in partial block contained in the byte range.
600         for (size_t i = mDataOffset[end].mIndex;
601              i < mIndex.Length() && range.ContainsStrict(mIndex[i].mByteRange);
602              i++) {
603           timeRanges += TimeInterval(
604               TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
605               TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
606         }
607       }
608     }
609     mLastBufferedRanges = timeRanges;
610     return timeRanges;
611   }
612 
613   RangeFinder rangeFinder(aByteRanges);
614   nsTArray<MP4Interval<Microseconds>> timeRanges;
615   nsTArray<FallibleTArray<Sample>*> indexes;
616   if (mMoofParser) {
617     // We take the index out of the moof parser and move it into a local
618     // variable so we don't get concurrency issues. It gets freed when we
619     // exit this function.
620     for (int i = 0; i < mMoofParser->Moofs().Length(); i++) {
621       Moof& moof = mMoofParser->Moofs()[i];
622 
623       // We need the entire moof in order to play anything
624       if (rangeFinder.Contains(moof.mRange)) {
625         if (rangeFinder.Contains(moof.mMdatRange)) {
626           MP4Interval<Microseconds>::SemiNormalAppend(timeRanges,
627                                                       moof.mTimeRange);
628         } else {
629           indexes.AppendElement(&moof.mIndex);
630         }
631       }
632     }
633   } else {
634     indexes.AppendElement(&mIndex);
635   }
636 
637   bool hasSync = false;
638   for (size_t i = 0; i < indexes.Length(); i++) {
639     FallibleTArray<Sample>* index = indexes[i];
640     for (size_t j = 0; j < index->Length(); j++) {
641       const Sample& sample = (*index)[j];
642       if (!rangeFinder.Contains(sample.mByteRange)) {
643         // We process the index in decode order so we clear hasSync when we hit
644         // a range that isn't buffered.
645         hasSync = false;
646         continue;
647       }
648 
649       hasSync |= sample.mSync;
650       if (!hasSync) {
651         continue;
652       }
653 
654       MP4Interval<Microseconds>::SemiNormalAppend(timeRanges,
655                                                   sample.mCompositionRange);
656     }
657   }
658 
659   // This fixes up when the compositon order differs from the byte range order
660   nsTArray<MP4Interval<Microseconds>> timeRangesNormalized;
661   MP4Interval<Microseconds>::Normalize(timeRanges, &timeRangesNormalized);
662   // convert timeRanges.
663   media::TimeIntervals ranges;
664   for (size_t i = 0; i < timeRangesNormalized.Length(); i++) {
665     ranges += media::TimeInterval(
666         media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].start),
667         media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].end));
668   }
669   mLastBufferedRanges = ranges;
670   return ranges;
671 }
672 
GetEvictionOffset(Microseconds aTime)673 uint64_t Index::GetEvictionOffset(Microseconds aTime) {
674   uint64_t offset = std::numeric_limits<uint64_t>::max();
675   if (mMoofParser) {
676     // We need to keep the whole moof if we're keeping any of it because the
677     // parser doesn't keep parsed moofs.
678     for (int i = 0; i < mMoofParser->Moofs().Length(); i++) {
679       Moof& moof = mMoofParser->Moofs()[i];
680 
681       if (moof.mTimeRange.Length() && moof.mTimeRange.end > aTime) {
682         offset = std::min(offset, uint64_t(std::min(moof.mRange.mStart,
683                                                     moof.mMdatRange.mStart)));
684       }
685     }
686   } else {
687     // We've already parsed and stored the moov so we don't need to keep it.
688     // All we need to keep is the sample data itself.
689     for (size_t i = 0; i < mIndex.Length(); i++) {
690       const Sample& sample = mIndex[i];
691       if (aTime >= sample.mCompositionRange.end) {
692         offset = std::min(offset, uint64_t(sample.mByteRange.mEnd));
693       }
694     }
695   }
696   return offset;
697 }
698 
RegisterIterator(SampleIterator * aIterator)699 void Index::RegisterIterator(SampleIterator* aIterator) {
700   mIterators.AppendElement(aIterator);
701 }
702 
UnregisterIterator(SampleIterator * aIterator)703 void Index::UnregisterIterator(SampleIterator* aIterator) {
704   mIterators.RemoveElement(aIterator);
705 }
706 
707 }  // namespace mozilla
708