LCOV - code coverage report
Current view: top level - dom/media/ogg - OggDemuxer.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 1004 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 69 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "nsError.h"
       8             : #include "MediaDecoderStateMachine.h"
       9             : #include "AbstractMediaDecoder.h"
      10             : #include "OggDemuxer.h"
      11             : #include "OggCodecState.h"
      12             : #include "mozilla/AbstractThread.h"
      13             : #include "mozilla/Atomics.h"
      14             : #include "mozilla/PodOperations.h"
      15             : #include "mozilla/SharedThreadPool.h"
      16             : #include "mozilla/Telemetry.h"
      17             : #include "mozilla/TimeStamp.h"
      18             : #include "MediaDataDemuxer.h"
      19             : #include "nsAutoRef.h"
      20             : #include "XiphExtradata.h"
      21             : #include "MediaPrefs.h"
      22             : 
      23             : #include <algorithm>
      24             : 
      25             : extern mozilla::LazyLogModule gMediaDemuxerLog;
      26             : #define OGG_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("OggDemuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
      27             : 
      28             : // Un-comment to enable logging of seek bisections.
      29             : //#define SEEK_LOGGING
      30             : #ifdef SEEK_LOGGING
      31             : #define SEEK_LOG(type, msg) MOZ_LOG(gMediaDemuxerLog, type, msg)
      32             : #else
      33             : #define SEEK_LOG(type, msg)
      34             : #endif
      35             : 
      36             : namespace mozilla
      37             : {
      38             : 
      39             : using media::TimeUnit;
      40             : using media::TimeInterval;
      41             : using media::TimeIntervals;
      42             : 
      43             : // The number of microseconds of "fuzz" we use in a bisection search over
      44             : // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection
      45             : // lands between the seek target and OGG_SEEK_FUZZ_USECS microseconds before the
      46             : // seek target.  This is becaue it's usually quicker to just keep downloading
      47             : // from an exisiting connection than to do another bisection inside that
      48             : // small range, which would open a new HTTP connetion.
      49             : static const uint32_t OGG_SEEK_FUZZ_USECS = 500000;
      50             : 
      51             : // The number of microseconds of "pre-roll" we use for Opus streams.
      52             : // The specification recommends 80 ms.
      53             : static const int64_t OGG_SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
      54             : 
      55             : static Atomic<uint32_t> sStreamSourceID(0u);
      56             : 
      57             : // Return the corresponding category in aKind based on the following specs.
      58             : // (https://www.whatwg.org/specs/web-apps/current-
      59             : // work/multipage/embedded-content.html#dom-audiotrack-kind) &
      60             : // (http://wiki.xiph.org/SkeletonHeaders)
      61             : const nsString
      62           0 : OggDemuxer::GetKind(const nsCString& aRole)
      63             : {
      64           0 :   if (aRole.Find("audio/main") != -1 || aRole.Find("video/main") != -1) {
      65           0 :     return NS_LITERAL_STRING("main");
      66           0 :   } else if (aRole.Find("audio/alternate") != -1 ||
      67           0 :              aRole.Find("video/alternate") != -1) {
      68           0 :     return NS_LITERAL_STRING("alternative");
      69           0 :   } else if (aRole.Find("audio/audiodesc") != -1) {
      70           0 :     return NS_LITERAL_STRING("descriptions");
      71           0 :   } else if (aRole.Find("audio/described") != -1) {
      72           0 :     return NS_LITERAL_STRING("main-desc");
      73           0 :   } else if (aRole.Find("audio/dub") != -1) {
      74           0 :     return NS_LITERAL_STRING("translation");
      75           0 :   } else if (aRole.Find("audio/commentary") != -1) {
      76           0 :     return NS_LITERAL_STRING("commentary");
      77           0 :   } else if (aRole.Find("video/sign") != -1) {
      78           0 :     return NS_LITERAL_STRING("sign");
      79           0 :   } else if (aRole.Find("video/captioned") != -1) {
      80           0 :     return NS_LITERAL_STRING("captions");
      81           0 :   } else if (aRole.Find("video/subtitled") != -1) {
      82           0 :     return NS_LITERAL_STRING("subtitles");
      83             :   }
      84           0 :   return EmptyString();
      85             : }
      86             : 
      87             : void
      88           0 : OggDemuxer::InitTrack(MessageField* aMsgInfo,
      89             :                       TrackInfo* aInfo,
      90             :                       bool aEnable)
      91             : {
      92           0 :   MOZ_ASSERT(aMsgInfo);
      93           0 :   MOZ_ASSERT(aInfo);
      94             : 
      95           0 :   nsCString* sName = aMsgInfo->mValuesStore.Get(eName);
      96           0 :   nsCString* sRole = aMsgInfo->mValuesStore.Get(eRole);
      97           0 :   nsCString* sTitle = aMsgInfo->mValuesStore.Get(eTitle);
      98           0 :   nsCString* sLanguage = aMsgInfo->mValuesStore.Get(eLanguage);
      99           0 :   aInfo->Init(sName? NS_ConvertUTF8toUTF16(*sName):EmptyString(),
     100           0 :               sRole? GetKind(*sRole):EmptyString(),
     101           0 :               sTitle? NS_ConvertUTF8toUTF16(*sTitle):EmptyString(),
     102           0 :               sLanguage? NS_ConvertUTF8toUTF16(*sLanguage):EmptyString(),
     103           0 :               aEnable);
     104           0 : }
     105             : 
     106           0 : OggDemuxer::OggDemuxer(MediaResource* aResource)
     107             :   : mTheoraState(nullptr)
     108             :   , mVorbisState(nullptr)
     109             :   , mOpusState(nullptr)
     110             :   , mFlacState(nullptr)
     111           0 :   , mOpusEnabled(MediaDecoder::IsOpusEnabled())
     112             :   , mSkeletonState(nullptr)
     113             :   , mAudioOggState(aResource)
     114             :   , mVideoOggState(aResource)
     115             :   , mIsChained(false)
     116             :   , mTimedMetadataEvent(nullptr)
     117           0 :   , mOnSeekableEvent(nullptr)
     118             : {
     119           0 :   MOZ_COUNT_CTOR(OggDemuxer);
     120           0 : }
     121             : 
     122           0 : OggDemuxer::~OggDemuxer()
     123             : {
     124           0 :   MOZ_COUNT_DTOR(OggDemuxer);
     125           0 :   Reset(TrackInfo::kAudioTrack);
     126           0 :   Reset(TrackInfo::kVideoTrack);
     127           0 :   if (HasAudio() || HasVideo()) {
     128             :     // If we were able to initialize our decoders, report whether we encountered
     129             :     // a chained stream or not.
     130           0 :     bool isChained = mIsChained;
     131           0 :     void* ptr = this;
     132           0 :     nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
     133           0 :       "OggDemuxer::~OggDemuxer", [ptr, isChained]() -> void {
     134             :         // We can't use OGG_DEBUG here because it implicitly refers to `this`,
     135             :         // which we can't capture in this runnable.
     136           0 :         MOZ_LOG(gMediaDemuxerLog,
     137             :                 mozilla::LogLevel::Debug,
     138             :                 ("OggDemuxer(%p)::%s: Reporting telemetry "
     139             :                  "MEDIA_OGG_LOADED_IS_CHAINED=%d",
     140             :                  ptr,
     141             :                  __func__,
     142             :                  isChained));
     143           0 :         Telemetry::Accumulate(
     144           0 :           Telemetry::HistogramID::MEDIA_OGG_LOADED_IS_CHAINED, isChained);
     145           0 :       });
     146             :     SystemGroup::Dispatch("~OggDemuxer::report_telemetry",
     147             :                           TaskCategory::Other,
     148           0 :                           task.forget());
     149             :   }
     150           0 : }
     151             : 
     152             : void
     153           0 : OggDemuxer::SetChainingEvents(TimedMetadataEventProducer* aMetadataEvent,
     154             :                               MediaEventProducer<void>* aOnSeekableEvent)
     155             : {
     156           0 :   mTimedMetadataEvent = aMetadataEvent;
     157           0 :   mOnSeekableEvent = aOnSeekableEvent;
     158           0 : }
     159             : 
     160             : 
     161             : bool
     162           0 : OggDemuxer::HasAudio()
     163             : const
     164             : {
     165           0 :   return mVorbisState || mOpusState || mFlacState;
     166             : }
     167             : 
     168             : bool
     169           0 : OggDemuxer::HasVideo()
     170             : const
     171             : {
     172           0 :   return mTheoraState;
     173             : }
     174             : 
     175             : bool
     176           0 : OggDemuxer::HaveStartTime()
     177             : const
     178             : {
     179           0 :   return mStartTime.isSome();
     180             : }
     181             : 
     182             : int64_t
     183           0 : OggDemuxer::StartTime() const
     184             : {
     185           0 :   return mStartTime.refOr(0);
     186             : }
     187             : 
     188             : bool
     189           0 : OggDemuxer::HaveStartTime(TrackInfo::TrackType aType)
     190             : {
     191           0 :   return OggState(aType).mStartTime.isSome();
     192             : }
     193             : 
     194             : int64_t
     195           0 : OggDemuxer::StartTime(TrackInfo::TrackType aType)
     196             : {
     197           0 :   return OggState(aType).mStartTime.refOr(TimeUnit::Zero()).ToMicroseconds();
     198             : }
     199             : 
     200             : RefPtr<OggDemuxer::InitPromise>
     201           0 : OggDemuxer::Init()
     202             : {
     203           0 :   int ret = ogg_sync_init(OggSyncState(TrackInfo::kAudioTrack));
     204           0 :   if (ret != 0) {
     205           0 :     return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
     206             :   }
     207           0 :   ret = ogg_sync_init(OggSyncState(TrackInfo::kVideoTrack));
     208           0 :   if (ret != 0) {
     209           0 :     return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
     210             :   }
     211           0 :   if (ReadMetadata() != NS_OK) {
     212           0 :     return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
     213             :   }
     214             : 
     215           0 :   if (!GetNumberTracks(TrackInfo::kAudioTrack) &&
     216           0 :       !GetNumberTracks(TrackInfo::kVideoTrack)) {
     217           0 :     return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
     218             :   }
     219             : 
     220           0 :   return InitPromise::CreateAndResolve(NS_OK, __func__);
     221             : }
     222             : 
     223             : bool
     224           0 : OggDemuxer::HasTrackType(TrackInfo::TrackType aType) const
     225             : {
     226           0 :   return !!GetNumberTracks(aType);
     227             : }
     228             : 
     229             : OggCodecState*
     230           0 : OggDemuxer::GetTrackCodecState(TrackInfo::TrackType aType) const
     231             : {
     232           0 :   switch(aType) {
     233             :     case TrackInfo::kAudioTrack:
     234           0 :       if (mVorbisState) {
     235           0 :         return mVorbisState;
     236           0 :       } else if (mOpusState) {
     237           0 :         return mOpusState;
     238             :       } else {
     239           0 :         return mFlacState;
     240             :       }
     241             :     case TrackInfo::kVideoTrack:
     242           0 :       return mTheoraState;
     243             :     default:
     244           0 :       return 0;
     245             :   }
     246             : }
     247             : 
     248             : TrackInfo::TrackType
     249           0 : OggDemuxer::GetCodecStateType(OggCodecState* aState) const
     250             : {
     251           0 :   switch (aState->GetType()) {
     252             :     case OggCodecState::TYPE_THEORA:
     253           0 :       return TrackInfo::kVideoTrack;
     254             :     case OggCodecState::TYPE_OPUS:
     255             :     case OggCodecState::TYPE_VORBIS:
     256             :     case OggCodecState::TYPE_FLAC:
     257           0 :       return TrackInfo::kAudioTrack;
     258             :     default:
     259           0 :       return TrackInfo::kUndefinedTrack;
     260             :   }
     261             : }
     262             : 
     263             : uint32_t
     264           0 : OggDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const
     265             : {
     266           0 :   switch(aType) {
     267             :     case TrackInfo::kAudioTrack:
     268           0 :       return HasAudio() ? 1 : 0;
     269             :     case TrackInfo::kVideoTrack:
     270           0 :       return HasVideo() ? 1 : 0;
     271             :     default:
     272           0 :       return 0;
     273             :   }
     274             : }
     275             : 
     276             : UniquePtr<TrackInfo>
     277           0 : OggDemuxer::GetTrackInfo(TrackInfo::TrackType aType, size_t aTrackNumber) const
     278             : {
     279           0 :   switch(aType) {
     280             :     case TrackInfo::kAudioTrack:
     281           0 :       return mInfo.mAudio.Clone();
     282             :     case TrackInfo::kVideoTrack:
     283           0 :       return mInfo.mVideo.Clone();
     284             :     default:
     285           0 :       return nullptr;
     286             :   }
     287             : }
     288             : 
     289             : already_AddRefed<MediaTrackDemuxer>
     290           0 : OggDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
     291             : {
     292           0 :   if (GetNumberTracks(aType) <= aTrackNumber) {
     293           0 :     return nullptr;
     294             :   }
     295           0 :   RefPtr<OggTrackDemuxer> e = new OggTrackDemuxer(this, aType, aTrackNumber);
     296           0 :   mDemuxers.AppendElement(e);
     297             : 
     298           0 :   return e.forget();
     299             : }
     300             : 
     301             : nsresult
     302           0 : OggDemuxer::Reset(TrackInfo::TrackType aType)
     303             : {
     304             :   // Discard any previously buffered packets/pages.
     305           0 :   ogg_sync_reset(OggSyncState(aType));
     306           0 :   OggCodecState* trackState = GetTrackCodecState(aType);
     307           0 :   if (trackState) {
     308           0 :     return trackState->Reset();
     309             :   }
     310           0 :   OggState(aType).mNeedKeyframe = true;
     311           0 :   return NS_OK;
     312             : }
     313             : 
     314             : bool
     315           0 : OggDemuxer::ReadHeaders(TrackInfo::TrackType aType,
     316             :                         OggCodecState* aState)
     317             : {
     318           0 :   while (!aState->DoneReadingHeaders()) {
     319           0 :     DemuxUntilPacketAvailable(aType, aState);
     320           0 :     OggPacketPtr packet = aState->PacketOut();
     321           0 :     if (!packet) {
     322           0 :       OGG_DEBUG("Ran out of header packets early; deactivating stream %" PRIu32, aState->mSerial);
     323           0 :       aState->Deactivate();
     324           0 :       return false;
     325             :     }
     326             : 
     327             :     // Local OggCodecState needs to decode headers in order to process
     328             :     // packet granulepos -> time mappings, etc.
     329           0 :     if (!aState->DecodeHeader(Move(packet))) {
     330           0 :       OGG_DEBUG("Failed to decode ogg header packet; deactivating stream %" PRIu32, aState->mSerial);
     331           0 :       aState->Deactivate();
     332           0 :       return false;
     333             :     }
     334             :   }
     335             : 
     336           0 :   return aState->Init();
     337             : }
     338             : 
     339             : void
     340           0 : OggDemuxer::BuildSerialList(nsTArray<uint32_t>& aTracks)
     341             : {
     342             :   // Obtaining seek index information for currently active bitstreams.
     343           0 :   if (HasVideo()) {
     344           0 :     aTracks.AppendElement(mTheoraState->mSerial);
     345             :   }
     346           0 :   if (HasAudio()) {
     347           0 :     if (mVorbisState) {
     348           0 :       aTracks.AppendElement(mVorbisState->mSerial);
     349           0 :     } else if (mOpusState) {
     350           0 :       aTracks.AppendElement(mOpusState->mSerial);
     351             :     }
     352             :   }
     353           0 : }
     354             : 
     355             : void
     356           0 : OggDemuxer::SetupTarget(OggCodecState** aSavedState, OggCodecState* aNewState)
     357             : {
     358           0 :   if (*aSavedState) {
     359           0 :     (*aSavedState)->Reset();
     360             :   }
     361             : 
     362           0 :   if (aNewState->GetInfo()->GetAsAudioInfo()) {
     363           0 :     mInfo.mAudio = *aNewState->GetInfo()->GetAsAudioInfo();
     364             :   } else {
     365           0 :     mInfo.mVideo = *aNewState->GetInfo()->GetAsVideoInfo();
     366             :   }
     367           0 :   *aSavedState = aNewState;
     368           0 : }
     369             : 
     370             : void
     371           0 : OggDemuxer::SetupTargetSkeleton()
     372             : {
     373             :   // Setup skeleton related information after mVorbisState & mTheroState
     374             :   // being set (if they exist).
     375           0 :   if (mSkeletonState) {
     376           0 :     if (!HasAudio() && !HasVideo()) {
     377             :       // We have a skeleton track, but no audio or video, may as well disable
     378             :       // the skeleton, we can't do anything useful with this media.
     379           0 :       OGG_DEBUG("Deactivating skeleton stream %" PRIu32, mSkeletonState->mSerial);
     380           0 :       mSkeletonState->Deactivate();
     381           0 :     } else if (ReadHeaders(TrackInfo::kAudioTrack, mSkeletonState) &&
     382           0 :                mSkeletonState->HasIndex()) {
     383             :       // We don't particularly care about which track we are currently using
     384             :       // as both MediaResource points to the same content.
     385             :       // Extract the duration info out of the index, so we don't need to seek to
     386             :       // the end of resource to get it.
     387           0 :       nsTArray<uint32_t> tracks;
     388           0 :       BuildSerialList(tracks);
     389           0 :       int64_t duration = 0;
     390           0 :       if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
     391           0 :         OGG_DEBUG("Got duration from Skeleton index %" PRId64, duration);
     392           0 :         mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
     393             :       }
     394             :     }
     395             :   }
     396           0 : }
     397             : 
     398             : void
     399           0 : OggDemuxer::SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials)
     400             : {
     401             :   // For each serial number
     402             :   // 1. Retrieve a codecState from mCodecStore by this serial number.
     403             :   // 2. Retrieve a message field from mMsgFieldStore by this serial number.
     404             :   // 3. For now, skip if the serial number refers to a non-primary bitstream.
     405             :   // 4. Setup track and other audio/video related information per different types.
     406           0 :   for (size_t i = 0; i < aSerials.Length(); i++) {
     407           0 :     uint32_t serial = aSerials[i];
     408           0 :     OggCodecState* codecState = mCodecStore.Get(serial);
     409             : 
     410           0 :     MessageField* msgInfo = nullptr;
     411           0 :     if (mSkeletonState) {
     412           0 :       mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
     413             :     }
     414             : 
     415           0 :     OggCodecState* primeState = nullptr;
     416           0 :     switch (codecState->GetType()) {
     417             :       case OggCodecState::TYPE_THEORA:
     418           0 :         primeState = mTheoraState;
     419           0 :         break;
     420             :       case OggCodecState::TYPE_VORBIS:
     421           0 :         primeState = mVorbisState;
     422           0 :         break;
     423             :       case OggCodecState::TYPE_OPUS:
     424           0 :         primeState = mOpusState;
     425           0 :         break;
     426             :       case OggCodecState::TYPE_FLAC:
     427           0 :         primeState = mFlacState;
     428           0 :         break;
     429             :       default:
     430           0 :         break;
     431             :     }
     432           0 :     if (primeState && primeState == codecState) {
     433           0 :       bool isAudio = primeState->GetInfo()->GetAsAudioInfo();
     434           0 :       if (msgInfo) {
     435           0 :         InitTrack(msgInfo, isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio)
     436             :                                    : &mInfo.mVideo,
     437           0 :                   true);
     438             :       }
     439           0 :       FillTags(isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio) : &mInfo.mVideo,
     440           0 :                primeState->GetTags());
     441             :     }
     442             :   }
     443           0 : }
     444             : 
     445             : void
     446           0 : OggDemuxer::FillTags(TrackInfo* aInfo, MetadataTags* aTags)
     447             : {
     448           0 :   if (!aTags) {
     449           0 :     return;
     450             :   }
     451           0 :   nsAutoPtr<MetadataTags> tags(aTags);
     452           0 :   for (auto iter = aTags->Iter(); !iter.Done(); iter.Next()) {
     453           0 :     aInfo->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data()));
     454             :   }
     455             : }
     456             : 
     457             : nsresult
     458           0 : OggDemuxer::ReadMetadata()
     459             : {
     460           0 :   OGG_DEBUG("OggDemuxer::ReadMetadata called!");
     461             : 
     462             :   // We read packets until all bitstreams have read all their header packets.
     463             :   // We record the offset of the first non-header page so that we know
     464             :   // what page to seek to when seeking to the media start.
     465             : 
     466             :   // @FIXME we have to read all the header packets on all the streams
     467             :   // and THEN we can run SetupTarget*
     468             :   // @fixme fixme
     469             : 
     470             :   TrackInfo::TrackType tracks[2] =
     471           0 :     { TrackInfo::kAudioTrack, TrackInfo::kVideoTrack };
     472             : 
     473           0 :   nsTArray<OggCodecState*> bitstreams;
     474           0 :   nsTArray<uint32_t> serials;
     475             : 
     476           0 :   for (uint32_t i = 0; i < ArrayLength(tracks); i++) {
     477             :     ogg_page page;
     478           0 :     bool readAllBOS = false;
     479           0 :     while (!readAllBOS) {
     480           0 :       if (!ReadOggPage(tracks[i], &page)) {
     481             :         // Some kind of error...
     482           0 :         OGG_DEBUG("OggDemuxer::ReadOggPage failed? leaving ReadMetadata...");
     483           0 :         return NS_ERROR_FAILURE;
     484             :       }
     485             : 
     486           0 :       int serial = ogg_page_serialno(&page);
     487             : 
     488           0 :       if (!ogg_page_bos(&page)) {
     489             :         // We've encountered a non Beginning Of Stream page. No more BOS pages
     490             :         // can follow in this Ogg segment, so there will be no other bitstreams
     491             :         // in the Ogg (unless it's invalid).
     492           0 :         readAllBOS = true;
     493           0 :       } else if (!mCodecStore.Contains(serial)) {
     494             :         // We've not encountered a stream with this serial number before. Create
     495             :         // an OggCodecState to demux it, and map that to the OggCodecState
     496             :         // in mCodecStates.
     497           0 :         OggCodecState* codecState = OggCodecState::Create(&page);
     498           0 :         mCodecStore.Add(serial, codecState);
     499           0 :         bitstreams.AppendElement(codecState);
     500           0 :         serials.AppendElement(serial);
     501             :       }
     502           0 :       if (NS_FAILED(DemuxOggPage(tracks[i], &page))) {
     503           0 :         return NS_ERROR_FAILURE;
     504             :       }
     505             :     }
     506             :   }
     507             : 
     508             :   // We've read all BOS pages, so we know the streams contained in the media.
     509             :   // 1. Find the first encountered Theora/Vorbis/Opus bitstream, and configure
     510             :   //    it as the target A/V bitstream.
     511             :   // 2. Deactivate the rest of bitstreams for now, until we have MediaInfo
     512             :   //    support multiple track infos.
     513           0 :   for (uint32_t i = 0; i < bitstreams.Length(); ++i) {
     514           0 :     OggCodecState* s = bitstreams[i];
     515           0 :     if (s) {
     516           0 :       if (s->GetType() == OggCodecState::TYPE_THEORA &&
     517           0 :           ReadHeaders(TrackInfo::kVideoTrack, s)) {
     518           0 :         if (!mTheoraState) {
     519           0 :           SetupTarget(&mTheoraState, s);
     520             :         } else {
     521           0 :           s->Deactivate();
     522             :         }
     523           0 :       } else if (s->GetType() == OggCodecState::TYPE_VORBIS &&
     524           0 :                  ReadHeaders(TrackInfo::kAudioTrack, s)) {
     525           0 :         if (!mVorbisState) {
     526           0 :           SetupTarget(&mVorbisState, s);
     527             :         } else {
     528           0 :           s->Deactivate();
     529             :         }
     530           0 :       } else if (s->GetType() == OggCodecState::TYPE_OPUS &&
     531           0 :                  ReadHeaders(TrackInfo::kAudioTrack, s)) {
     532           0 :         if (mOpusEnabled) {
     533           0 :           if (!mOpusState) {
     534           0 :             SetupTarget(&mOpusState, s);
     535             :           } else {
     536           0 :             s->Deactivate();
     537             :           }
     538             :         } else {
     539             :           NS_WARNING("Opus decoding disabled."
     540           0 :                      " See media.opus.enabled in about:config");
     541             :         }
     542           0 :       } else if (MediaPrefs::FlacInOgg() &&
     543           0 :                  s->GetType() == OggCodecState::TYPE_FLAC &&
     544           0 :                  ReadHeaders(TrackInfo::kAudioTrack, s)) {
     545           0 :         if (!mFlacState) {
     546           0 :           SetupTarget(&mFlacState, s);
     547             :         } else {
     548           0 :           s->Deactivate();
     549             :         }
     550           0 :       } else if (s->GetType() == OggCodecState::TYPE_SKELETON && !mSkeletonState) {
     551           0 :         mSkeletonState = static_cast<SkeletonState*>(s);
     552             :       } else {
     553             :         // Deactivate any non-primary bitstreams.
     554           0 :         s->Deactivate();
     555             :       }
     556             :     }
     557             :   }
     558             : 
     559           0 :   SetupTargetSkeleton();
     560           0 :   SetupMediaTracksInfo(serials);
     561             : 
     562           0 :   if (HasAudio() || HasVideo()) {
     563           0 :     int64_t startTime = -1;
     564           0 :     FindStartTime(startTime);
     565           0 :     if (startTime >= 0) {
     566           0 :       OGG_DEBUG("Detected stream start time %" PRId64, startTime);
     567           0 :       mStartTime.emplace(startTime);
     568             :     }
     569             : 
     570           0 :     if (mInfo.mMetadataDuration.isNothing() &&
     571           0 :         Resource(TrackInfo::kAudioTrack)->GetLength() >= 0) {
     572             :       // We didn't get a duration from the index or a Content-Duration header.
     573             :       // Seek to the end of file to find the end time.
     574           0 :       int64_t length = Resource(TrackInfo::kAudioTrack)->GetLength();
     575             : 
     576           0 :       NS_ASSERTION(length > 0, "Must have a content length to get end time");
     577             : 
     578           0 :       int64_t endTime = RangeEndTime(TrackInfo::kAudioTrack, length);
     579             : 
     580           0 :       if (endTime != -1) {
     581           0 :         mInfo.mUnadjustedMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime));
     582           0 :         mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(endTime - mStartTime.refOr(0)));
     583           0 :         OGG_DEBUG("Got Ogg duration from seeking to end %" PRId64, endTime);
     584             :       }
     585             :     }
     586           0 :     if (mInfo.mMetadataDuration.isNothing()) {
     587           0 :       mInfo.mMetadataDuration.emplace(TimeUnit::FromInfinity());
     588             :     }
     589           0 :     if (HasAudio()) {
     590           0 :       mInfo.mAudio.mDuration = mInfo.mMetadataDuration.ref();
     591             :     }
     592           0 :     if (HasVideo()) {
     593           0 :       mInfo.mVideo.mDuration = mInfo.mMetadataDuration.ref();
     594             :     }
     595             :   } else {
     596           0 :     OGG_DEBUG("no audio or video tracks");
     597           0 :     return NS_ERROR_FAILURE;
     598             :   }
     599             : 
     600           0 :   OGG_DEBUG("success?!");
     601           0 :   return NS_OK;
     602             : }
     603             : 
     604             : void
     605           0 : OggDemuxer::SetChained() {
     606             :   {
     607           0 :     if (mIsChained) {
     608           0 :       return;
     609             :     }
     610           0 :     mIsChained = true;
     611             :   }
     612           0 :   if (mOnSeekableEvent) {
     613           0 :     mOnSeekableEvent->Notify();
     614             :   }
     615             : }
     616             : 
     617             : bool
     618           0 : OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime)
     619             : {
     620           0 :   bool chained = false;
     621           0 :   OpusState* newOpusState = nullptr;
     622           0 :   VorbisState* newVorbisState = nullptr;
     623           0 :   FlacState* newFlacState = nullptr;
     624           0 :   nsAutoPtr<MetadataTags> tags;
     625             : 
     626           0 :   if (HasVideo() || HasSkeleton() || !HasAudio()) {
     627           0 :     return false;
     628             :   }
     629             : 
     630             :   ogg_page page;
     631           0 :   if (!ReadOggPage(TrackInfo::kAudioTrack, &page) || !ogg_page_bos(&page)) {
     632             :     // Chaining is only supported for audio only ogg files.
     633           0 :     return false;
     634             :   }
     635             : 
     636           0 :   int serial = ogg_page_serialno(&page);
     637           0 :   if (mCodecStore.Contains(serial)) {
     638           0 :     return false;
     639             :   }
     640             : 
     641           0 :   nsAutoPtr<OggCodecState> codecState;
     642           0 :   codecState = OggCodecState::Create(&page);
     643           0 :   if (!codecState) {
     644           0 :     return false;
     645             :   }
     646             : 
     647           0 :   if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
     648           0 :     newVorbisState = static_cast<VorbisState*>(codecState.get());
     649           0 :   } else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
     650           0 :     newOpusState = static_cast<OpusState*>(codecState.get());
     651           0 :   } else if (mFlacState && (codecState->GetType() == OggCodecState::TYPE_FLAC)) {
     652           0 :     newFlacState = static_cast<FlacState*>(codecState.get());
     653             :   } else {
     654           0 :     return false;
     655             :   }
     656             : 
     657             :   OggCodecState* state;
     658             : 
     659           0 :   mCodecStore.Add(serial, codecState.forget());
     660           0 :   state = mCodecStore.Get(serial);
     661             : 
     662           0 :   NS_ENSURE_TRUE(state != nullptr, false);
     663             : 
     664           0 :   if (NS_FAILED(state->PageIn(&page))) {
     665           0 :     return false;
     666             :   }
     667             : 
     668           0 :   MessageField* msgInfo = nullptr;
     669           0 :   if (mSkeletonState) {
     670           0 :     mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
     671             :   }
     672             : 
     673           0 :   if ((newVorbisState &&
     674           0 :        ReadHeaders(TrackInfo::kAudioTrack, newVorbisState)) &&
     675           0 :       (mVorbisState->GetInfo()->GetAsAudioInfo()->mRate ==
     676           0 :        newVorbisState->GetInfo()->GetAsAudioInfo()->mRate) &&
     677           0 :       (mVorbisState->GetInfo()->GetAsAudioInfo()->mChannels ==
     678           0 :        newVorbisState->GetInfo()->GetAsAudioInfo()->mChannels)) {
     679             : 
     680           0 :     SetupTarget(&mVorbisState, newVorbisState);
     681           0 :     LOG(LogLevel::Debug,
     682             :         ("New vorbis ogg link, serial=%d\n", mVorbisState->mSerial));
     683             : 
     684           0 :     if (msgInfo) {
     685           0 :       InitTrack(msgInfo, &mInfo.mAudio, true);
     686             :     }
     687             : 
     688           0 :     chained = true;
     689           0 :     tags = newVorbisState->GetTags();
     690             :   }
     691             : 
     692           0 :   if ((newOpusState &&
     693           0 :        ReadHeaders(TrackInfo::kAudioTrack, newOpusState)) &&
     694           0 :       (mOpusState->GetInfo()->GetAsAudioInfo()->mRate ==
     695           0 :        newOpusState->GetInfo()->GetAsAudioInfo()->mRate) &&
     696           0 :       (mOpusState->GetInfo()->GetAsAudioInfo()->mChannels ==
     697           0 :        newOpusState->GetInfo()->GetAsAudioInfo()->mChannels)) {
     698             : 
     699           0 :     SetupTarget(&mOpusState, newOpusState);
     700             : 
     701           0 :     if (msgInfo) {
     702           0 :       InitTrack(msgInfo, &mInfo.mAudio, true);
     703             :     }
     704             : 
     705           0 :     chained = true;
     706           0 :     tags = newOpusState->GetTags();
     707             :   }
     708             : 
     709           0 :   if ((newFlacState &&
     710           0 :        ReadHeaders(TrackInfo::kAudioTrack, newFlacState)) &&
     711           0 :       (mFlacState->GetInfo()->GetAsAudioInfo()->mRate ==
     712           0 :        newFlacState->GetInfo()->GetAsAudioInfo()->mRate) &&
     713           0 :       (mFlacState->GetInfo()->GetAsAudioInfo()->mChannels ==
     714           0 :        newFlacState->GetInfo()->GetAsAudioInfo()->mChannels)) {
     715             : 
     716           0 :     SetupTarget(&mFlacState, newFlacState);
     717           0 :     LOG(LogLevel::Debug,
     718             :         ("New flac ogg link, serial=%d\n", mFlacState->mSerial));
     719             : 
     720           0 :     if (msgInfo) {
     721           0 :       InitTrack(msgInfo, &mInfo.mAudio, true);
     722             :     }
     723             : 
     724           0 :     chained = true;
     725           0 :     tags = newFlacState->GetTags();
     726             :   }
     727             : 
     728           0 :   if (chained) {
     729           0 :     SetChained();
     730           0 :     mInfo.mMediaSeekable = false;
     731           0 :     mDecodedAudioDuration += aLastEndTime;
     732           0 :     if (mTimedMetadataEvent) {
     733           0 :       mTimedMetadataEvent->Notify(
     734           0 :         TimedMetadata(mDecodedAudioDuration,
     735           0 :                       Move(tags),
     736           0 :                       nsAutoPtr<MediaInfo>(new MediaInfo(mInfo))));
     737             :     }
     738             :     // Setup a new TrackInfo so that the MediaFormatReader will flush the
     739             :     // current decoder.
     740           0 :     mSharedAudioTrackInfo = new TrackInfoSharedPtr(mInfo.mAudio, ++sStreamSourceID);
     741           0 :     return true;
     742             :   }
     743             : 
     744           0 :   return false;
     745             : }
     746             : 
     747             : OggDemuxer::OggStateContext&
     748           0 : OggDemuxer::OggState(TrackInfo::TrackType aType)
     749             : {
     750           0 :   if (aType == TrackInfo::kVideoTrack) {
     751           0 :     return mVideoOggState;
     752             :   }
     753           0 :   return mAudioOggState;
     754             : }
     755             : 
     756             : ogg_sync_state*
     757           0 : OggDemuxer::OggSyncState(TrackInfo::TrackType aType)
     758             : {
     759           0 :   return &OggState(aType).mOggState.mState;
     760             : }
     761             : 
     762             : MediaResourceIndex*
     763           0 : OggDemuxer::Resource(TrackInfo::TrackType aType)
     764             : {
     765           0 :   return &OggState(aType).mResource;
     766             : }
     767             : 
     768             : MediaResourceIndex*
     769           0 : OggDemuxer::CommonResource()
     770             : {
     771           0 :   return &mAudioOggState.mResource;
     772             : }
     773             : 
     774             : bool
     775           0 : OggDemuxer::ReadOggPage(TrackInfo::TrackType aType, ogg_page* aPage)
     776             : {
     777           0 :   int ret = 0;
     778           0 :   while((ret = ogg_sync_pageseek(OggSyncState(aType), aPage)) <= 0) {
     779           0 :     if (ret < 0) {
     780             :       // Lost page sync, have to skip up to next page.
     781           0 :       continue;
     782             :     }
     783             :     // Returns a buffer that can be written too
     784             :     // with the given size. This buffer is stored
     785             :     // in the ogg synchronisation structure.
     786           0 :     char* buffer = ogg_sync_buffer(OggSyncState(aType), 4096);
     787           0 :     NS_ASSERTION(buffer, "ogg_sync_buffer failed");
     788             : 
     789             :     // Read from the resource into the buffer
     790           0 :     uint32_t bytesRead = 0;
     791             : 
     792           0 :     nsresult rv = Resource(aType)->Read(buffer, 4096, &bytesRead);
     793           0 :     if (NS_FAILED(rv) || !bytesRead) {
     794             :       // End of file or error.
     795           0 :       return false;
     796             :     }
     797             : 
     798             :     // Update the synchronisation layer with the number
     799             :     // of bytes written to the buffer
     800           0 :     ret = ogg_sync_wrote(OggSyncState(aType), bytesRead);
     801           0 :     NS_ENSURE_TRUE(ret == 0, false);
     802             :   }
     803             : 
     804           0 :   return true;
     805             : }
     806             : 
     807             : nsresult
     808           0 : OggDemuxer::DemuxOggPage(TrackInfo::TrackType aType, ogg_page* aPage)
     809             : {
     810           0 :   int serial = ogg_page_serialno(aPage);
     811           0 :   OggCodecState* codecState = mCodecStore.Get(serial);
     812           0 :   if (codecState == nullptr) {
     813           0 :     OGG_DEBUG("encountered packet for unrecognized codecState");
     814           0 :     return NS_ERROR_FAILURE;
     815             :   }
     816           0 :   if (GetCodecStateType(codecState) != aType &&
     817           0 :       codecState->GetType() != OggCodecState::TYPE_SKELETON) {
     818             :     // Not a page we're interested in.
     819           0 :     return NS_OK;
     820             :   }
     821           0 :   if (NS_FAILED(codecState->PageIn(aPage))) {
     822           0 :     OGG_DEBUG("codecState->PageIn failed");
     823           0 :     return NS_ERROR_FAILURE;
     824             :   }
     825           0 :   return NS_OK;
     826             : }
     827             : 
     828             : bool
     829           0 : OggDemuxer::IsSeekable() const
     830             : {
     831           0 :   if (mIsChained) {
     832           0 :     return false;
     833             :   }
     834           0 :   return true;
     835             : }
     836             : 
     837             : UniquePtr<EncryptionInfo>
     838           0 : OggDemuxer::GetCrypto()
     839             : {
     840           0 :   return nullptr;
     841             : }
     842             : 
     843             : ogg_packet*
     844           0 : OggDemuxer::GetNextPacket(TrackInfo::TrackType aType)
     845             : {
     846           0 :   OggCodecState* state = GetTrackCodecState(aType);
     847           0 :   ogg_packet* packet = nullptr;
     848           0 :   OggStateContext& context = OggState(aType);
     849             : 
     850             :   while (true) {
     851           0 :     if (packet) {
     852           0 :       Unused << state->PacketOut();
     853             :     }
     854           0 :     DemuxUntilPacketAvailable(aType, state);
     855             : 
     856           0 :     packet = state->PacketPeek();
     857           0 :     if (!packet) {
     858           0 :       break;
     859             :     }
     860           0 :     if (state->IsHeader(packet)) {
     861           0 :       continue;
     862             :     }
     863           0 :     if (context.mNeedKeyframe && !state->IsKeyframe(packet)) {
     864           0 :       continue;
     865             :     }
     866           0 :     context.mNeedKeyframe = false;
     867           0 :     break;
     868             :   }
     869             : 
     870           0 :   return packet;
     871             : }
     872             : 
     873             : void
     874           0 : OggDemuxer::DemuxUntilPacketAvailable(TrackInfo::TrackType aType,
     875             :                                       OggCodecState* aState)
     876             : {
     877           0 :   while (!aState->IsPacketReady()) {
     878           0 :     OGG_DEBUG("no packet yet, reading some more");
     879             :     ogg_page page;
     880           0 :     if (!ReadOggPage(aType, &page)) {
     881           0 :       OGG_DEBUG("no more pages to read in resource?");
     882           0 :       return;
     883             :     }
     884           0 :     DemuxOggPage(aType, &page);
     885             :   }
     886             : }
     887             : 
     888             : TimeIntervals
     889           0 : OggDemuxer::GetBuffered(TrackInfo::TrackType aType)
     890             : {
     891           0 :   if (!HaveStartTime(aType)) {
     892           0 :     return TimeIntervals();
     893             :   }
     894           0 :   if (mIsChained) {
     895           0 :     return TimeIntervals::Invalid();
     896             :   }
     897           0 :   TimeIntervals buffered;
     898             :   // HasAudio and HasVideo are not used here as they take a lock and cause
     899             :   // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
     900             :   // after metadata is read.
     901           0 :   if (!mInfo.HasValidMedia()) {
     902             :     // No need to search through the file if there are no audio or video tracks
     903           0 :     return buffered;
     904             :   }
     905             : 
     906           0 :   AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
     907           0 :   MediaByteRangeSet ranges;
     908           0 :   nsresult res = resource->GetCachedRanges(ranges);
     909           0 :   NS_ENSURE_SUCCESS(res, TimeIntervals::Invalid());
     910             : 
     911             :   // Traverse across the buffered byte ranges, determining the time ranges
     912             :   // they contain. MediaResource::GetNextCachedData(offset) returns -1 when
     913             :   // offset is after the end of the media resource, or there's no more cached
     914             :   // data after the offset. This loop will run until we've checked every
     915             :   // buffered range in the media, in increasing order of offset.
     916           0 :   nsAutoOggSyncState sync;
     917           0 :   for (uint32_t index = 0; index < ranges.Length(); index++) {
     918             :     // Ensure the offsets are after the header pages.
     919           0 :     int64_t startOffset = ranges[index].mStart;
     920           0 :     int64_t endOffset = ranges[index].mEnd;
     921             : 
     922             :     // Because the granulepos time is actually the end time of the page,
     923             :     // we special-case (startOffset == 0) so that the first
     924             :     // buffered range always appears to be buffered from the media start
     925             :     // time, rather than from the end-time of the first page.
     926           0 :     int64_t startTime = (startOffset == 0) ? StartTime() : -1;
     927             : 
     928             :     // Find the start time of the range. Read pages until we find one with a
     929             :     // granulepos which we can convert into a timestamp to use as the time of
     930             :     // the start of the buffered range.
     931           0 :     ogg_sync_reset(&sync.mState);
     932           0 :     while (startTime == -1) {
     933             :       ogg_page page;
     934             :       int32_t discard;
     935           0 :       PageSyncResult pageSyncResult = PageSync(Resource(aType),
     936             :                                                &sync.mState,
     937             :                                                true,
     938             :                                                startOffset,
     939             :                                                endOffset,
     940             :                                                &page,
     941           0 :                                                discard);
     942           0 :       if (pageSyncResult == PAGE_SYNC_ERROR) {
     943           0 :         return TimeIntervals::Invalid();
     944           0 :       } else if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
     945             :         // Hit the end of range without reading a page, give up trying to
     946             :         // find a start time for this buffered range, skip onto the next one.
     947           0 :         break;
     948             :       }
     949             : 
     950           0 :       int64_t granulepos = ogg_page_granulepos(&page);
     951           0 :       if (granulepos == -1) {
     952             :         // Page doesn't have an end time, advance to the next page
     953             :         // until we find one.
     954           0 :         startOffset += page.header_len + page.body_len;
     955           0 :         continue;
     956             :       }
     957             : 
     958           0 :       uint32_t serial = ogg_page_serialno(&page);
     959           0 :       if (aType == TrackInfo::kAudioTrack && mVorbisState &&
     960           0 :           serial == mVorbisState->mSerial) {
     961           0 :         startTime = mVorbisState->Time(granulepos);
     962           0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
     963           0 :       } else if (aType == TrackInfo::kAudioTrack && mOpusState &&
     964           0 :                  serial == mOpusState->mSerial) {
     965           0 :         startTime = mOpusState->Time(granulepos);
     966           0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
     967           0 :       } else if (aType == TrackInfo::kAudioTrack && mFlacState &&
     968           0 :                  serial == mFlacState->mSerial) {
     969           0 :         startTime = mFlacState->Time(granulepos);
     970           0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
     971           0 :       } else if (aType == TrackInfo::kVideoTrack && mTheoraState &&
     972           0 :                  serial == mTheoraState->mSerial) {
     973           0 :         startTime = mTheoraState->Time(granulepos);
     974           0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
     975           0 :       } else if (mCodecStore.Contains(serial)) {
     976             :         // Stream is not the theora or vorbis stream we're playing,
     977             :         // but is one that we have header data for.
     978           0 :         startOffset += page.header_len + page.body_len;
     979           0 :         continue;
     980             :       } else {
     981             :         // Page is for a stream we don't know about (possibly a chained
     982             :         // ogg), return OK to abort the finding any further ranges. This
     983             :         // prevents us searching through the rest of the media when we
     984             :         // may not be able to extract timestamps from it.
     985           0 :         SetChained();
     986           0 :         return buffered;
     987             :       }
     988             :     }
     989             : 
     990           0 :     if (startTime != -1) {
     991             :       // We were able to find a start time for that range, see if we can
     992             :       // find an end time.
     993           0 :       int64_t endTime = RangeEndTime(aType, startOffset, endOffset, true);
     994           0 :       if (endTime > startTime) {
     995           0 :         buffered += TimeInterval(
     996           0 :            TimeUnit::FromMicroseconds(startTime - StartTime()),
     997           0 :            TimeUnit::FromMicroseconds(endTime - StartTime()));
     998             :       }
     999             :     }
    1000             :   }
    1001             : 
    1002           0 :   return buffered;
    1003             : }
    1004             : 
    1005             : void
    1006           0 : OggDemuxer::FindStartTime(int64_t& aOutStartTime)
    1007             : {
    1008             :   // Extract the start times of the bitstreams in order to calculate
    1009             :   // the duration.
    1010           0 :   int64_t videoStartTime = INT64_MAX;
    1011           0 :   int64_t audioStartTime = INT64_MAX;
    1012             : 
    1013           0 :   if (HasVideo()) {
    1014           0 :     FindStartTime(TrackInfo::kVideoTrack, videoStartTime);
    1015           0 :     if (videoStartTime != INT64_MAX) {
    1016           0 :       OGG_DEBUG("OggDemuxer::FindStartTime() video=%" PRId64, videoStartTime);
    1017             :       mVideoOggState.mStartTime =
    1018           0 :         Some(TimeUnit::FromMicroseconds(videoStartTime));
    1019             :     }
    1020             :   }
    1021           0 :   if (HasAudio()) {
    1022           0 :     FindStartTime(TrackInfo::kAudioTrack, audioStartTime);
    1023           0 :     if (audioStartTime != INT64_MAX) {
    1024           0 :       OGG_DEBUG("OggDemuxer::FindStartTime() audio=%" PRId64, audioStartTime);
    1025             :       mAudioOggState.mStartTime =
    1026           0 :         Some(TimeUnit::FromMicroseconds(audioStartTime));
    1027             :     }
    1028             :   }
    1029             : 
    1030           0 :   int64_t startTime = std::min(videoStartTime, audioStartTime);
    1031           0 :   if (startTime != INT64_MAX) {
    1032           0 :     aOutStartTime = startTime;
    1033             :   }
    1034           0 : }
    1035             : 
    1036             : void
    1037           0 : OggDemuxer::FindStartTime(TrackInfo::TrackType aType, int64_t& aOutStartTime)
    1038             : {
    1039           0 :   int64_t startTime = INT64_MAX;
    1040             : 
    1041           0 :   OggCodecState* state = GetTrackCodecState(aType);
    1042           0 :   ogg_packet* pkt = GetNextPacket(aType);
    1043           0 :   if (pkt) {
    1044           0 :     startTime = state->PacketStartTime(pkt);
    1045             :   }
    1046             : 
    1047           0 :   if (startTime != INT64_MAX) {
    1048           0 :     aOutStartTime = startTime;
    1049             :   }
    1050           0 : }
    1051             : 
    1052             : nsresult
    1053           0 : OggDemuxer::SeekInternal(TrackInfo::TrackType aType, const TimeUnit& aTarget)
    1054             : {
    1055           0 :   int64_t target = aTarget.ToMicroseconds();
    1056           0 :   OGG_DEBUG("About to seek to %" PRId64, target);
    1057             :   nsresult res;
    1058           0 :   int64_t adjustedTarget = target;
    1059           0 :   int64_t startTime = StartTime(aType);
    1060           0 :   int64_t endTime = mInfo.mMetadataDuration->ToMicroseconds();
    1061           0 :   if (aType == TrackInfo::kAudioTrack && mOpusState){
    1062           0 :     adjustedTarget = std::max(startTime, target - OGG_SEEK_OPUS_PREROLL);
    1063             :   }
    1064             : 
    1065           0 :   if (!HaveStartTime(aType) || adjustedTarget == startTime) {
    1066             :     // We've seeked to the media start or we can't seek.
    1067             :     // Just seek to the offset of the first content page.
    1068           0 :     res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
    1069           0 :     NS_ENSURE_SUCCESS(res,res);
    1070             : 
    1071           0 :     res = Reset(aType);
    1072           0 :     NS_ENSURE_SUCCESS(res,res);
    1073             :   } else {
    1074             :     // TODO: This may seek back unnecessarily far in the video, but we don't
    1075             :     // have a way of asking Skeleton to seek to a different target for each
    1076             :     // stream yet. Using adjustedTarget here is at least correct, if slow.
    1077           0 :     IndexedSeekResult sres = SeekToKeyframeUsingIndex(aType, adjustedTarget);
    1078           0 :     NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
    1079           0 :     if (sres == SEEK_INDEX_FAIL) {
    1080             :       // No index or other non-fatal index-related failure. Try to seek
    1081             :       // using a bisection search. Determine the already downloaded data
    1082             :       // in the media cache, so we can try to seek in the cached data first.
    1083           0 :       AutoTArray<SeekRange, 16> ranges;
    1084           0 :       res = GetSeekRanges(aType, ranges);
    1085           0 :       NS_ENSURE_SUCCESS(res,res);
    1086             : 
    1087             :       // Figure out if the seek target lies in a buffered range.
    1088           0 :       SeekRange r = SelectSeekRange(aType, ranges, target, startTime, endTime, true);
    1089             : 
    1090           0 :       if (!r.IsNull()) {
    1091             :         // We know the buffered range in which the seek target lies, do a
    1092             :         // bisection search in that buffered range.
    1093           0 :         res = SeekInBufferedRange(aType, target, adjustedTarget, startTime, endTime, ranges, r);
    1094           0 :         NS_ENSURE_SUCCESS(res,res);
    1095             :       } else {
    1096             :         // The target doesn't lie in a buffered range. Perform a bisection
    1097             :         // search over the whole media, using the known buffered ranges to
    1098             :         // reduce the search space.
    1099           0 :         res = SeekInUnbuffered(aType, target, startTime, endTime, ranges);
    1100           0 :         NS_ENSURE_SUCCESS(res,res);
    1101             :       }
    1102             :     }
    1103             :   }
    1104             : 
    1105             :   // Demux forwards until we find the first keyframe prior the target.
    1106             :   // there may be non-keyframes in the page before the keyframe.
    1107             :   // Additionally, we may have seeked to the first page referenced by the
    1108             :   // page index which may be quite far off the target.
    1109             :   // When doing fastSeek we display the first frame after the seek, so
    1110             :   // we need to advance the decode to the keyframe otherwise we'll get
    1111             :   // visual artifacts in the first frame output after the seek.
    1112           0 :   OggCodecState* state = GetTrackCodecState(aType);
    1113           0 :   OggPacketQueue tempPackets;
    1114           0 :   bool foundKeyframe = false;
    1115             :   while (true) {
    1116           0 :     DemuxUntilPacketAvailable(aType, state);
    1117           0 :     ogg_packet* packet = state->PacketPeek();
    1118           0 :     if (packet == nullptr) {
    1119           0 :       OGG_DEBUG("End of stream reached before keyframe found in indexed seek");
    1120           0 :       break;
    1121             :     }
    1122           0 :     int64_t startTstamp = state->PacketStartTime(packet);
    1123           0 :     if (foundKeyframe && startTstamp > adjustedTarget) {
    1124           0 :       break;
    1125             :     }
    1126           0 :     if (state->IsKeyframe(packet)) {
    1127           0 :       OGG_DEBUG("keyframe found after seeking at %" PRId64, startTstamp);
    1128           0 :       tempPackets.Erase();
    1129           0 :       foundKeyframe = true;
    1130             :     }
    1131           0 :     if (foundKeyframe && startTstamp == adjustedTarget) {
    1132           0 :       break;
    1133             :     }
    1134           0 :     if (foundKeyframe) {
    1135           0 :       tempPackets.Append(state->PacketOut());
    1136             :     } else {
    1137             :       // Discard video packets before the first keyframe.
    1138           0 :       Unused << state->PacketOut();
    1139             :     }
    1140           0 :   }
    1141             :   // Re-add all packet into the codec state in order.
    1142           0 :   state->PushFront(Move(tempPackets));
    1143             : 
    1144           0 :   return NS_OK;
    1145             : }
    1146             : 
    1147             : OggDemuxer::IndexedSeekResult
    1148           0 : OggDemuxer::RollbackIndexedSeek(TrackInfo::TrackType aType, int64_t aOffset)
    1149             : {
    1150           0 :   if (mSkeletonState) {
    1151           0 :     mSkeletonState->Deactivate();
    1152             :   }
    1153           0 :   nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
    1154           0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
    1155           0 :   return SEEK_INDEX_FAIL;
    1156             : }
    1157             : 
    1158             : OggDemuxer::IndexedSeekResult
    1159           0 : OggDemuxer::SeekToKeyframeUsingIndex(TrackInfo::TrackType aType, int64_t aTarget)
    1160             : {
    1161           0 :   if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
    1162           0 :     return SEEK_INDEX_FAIL;
    1163             :   }
    1164             :   // We have an index from the Skeleton track, try to use it to seek.
    1165           0 :   AutoTArray<uint32_t, 2> tracks;
    1166           0 :   BuildSerialList(tracks);
    1167           0 :   SkeletonState::nsSeekTarget keyframe;
    1168           0 :   if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget,
    1169             :                                                   tracks,
    1170             :                                                   keyframe))) {
    1171             :     // Could not locate a keypoint for the target in the index.
    1172           0 :     return SEEK_INDEX_FAIL;
    1173             :   }
    1174             : 
    1175             :   // Remember original resource read cursor position so we can rollback on failure.
    1176           0 :   int64_t tell = Resource(aType)->Tell();
    1177             : 
    1178             :   // Seek to the keypoint returned by the index.
    1179           0 :   if (keyframe.mKeyPoint.mOffset > Resource(aType)->GetLength() ||
    1180           0 :       keyframe.mKeyPoint.mOffset < 0) {
    1181             :     // Index must be invalid.
    1182           0 :     return RollbackIndexedSeek(aType, tell);
    1183             :   }
    1184           0 :   LOG(LogLevel::Debug, ("Seeking using index to keyframe at offset %" PRId64 "\n",
    1185             :                      keyframe.mKeyPoint.mOffset));
    1186           0 :   nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET,
    1187           0 :                                        keyframe.mKeyPoint.mOffset);
    1188           0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
    1189             : 
    1190             :   // We've moved the read set, so reset decode.
    1191           0 :   res = Reset(aType);
    1192           0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
    1193             : 
    1194             :   // Check that the page the index thinks is exactly here is actually exactly
    1195             :   // here. If not, the index is invalid.
    1196             :   ogg_page page;
    1197           0 :   int skippedBytes = 0;
    1198           0 :   PageSyncResult syncres = PageSync(Resource(aType),
    1199             :                                     OggSyncState(aType),
    1200             :                                     false,
    1201             :                                     keyframe.mKeyPoint.mOffset,
    1202             :                                     Resource(aType)->GetLength(),
    1203             :                                     &page,
    1204           0 :                                     skippedBytes);
    1205           0 :   NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
    1206           0 :   if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
    1207           0 :     LOG(LogLevel::Debug, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
    1208             :                        "or sync error after seek"));
    1209           0 :     return RollbackIndexedSeek(aType, tell);
    1210             :   }
    1211           0 :   uint32_t serial = ogg_page_serialno(&page);
    1212           0 :   if (serial != keyframe.mSerial) {
    1213             :     // Serialno of page at offset isn't what the index told us to expect.
    1214             :     // Assume the index is invalid.
    1215           0 :     return RollbackIndexedSeek(aType, tell);
    1216             :   }
    1217           0 :   OggCodecState* codecState = mCodecStore.Get(serial);
    1218           0 :   if (codecState && codecState->mActive &&
    1219           0 :       ogg_stream_pagein(&codecState->mState, &page) != 0) {
    1220             :     // Couldn't insert page into the ogg resource, or somehow the resource
    1221             :     // is no longer active.
    1222           0 :     return RollbackIndexedSeek(aType, tell);
    1223             :   }
    1224           0 :   return SEEK_OK;
    1225             : }
    1226             : 
    1227             : // Reads a page from the media resource.
    1228             : OggDemuxer::PageSyncResult
    1229           0 : OggDemuxer::PageSync(MediaResourceIndex* aResource,
    1230             :                      ogg_sync_state* aState,
    1231             :                      bool aCachedDataOnly,
    1232             :                      int64_t aOffset,
    1233             :                      int64_t aEndOffset,
    1234             :                      ogg_page* aPage,
    1235             :                      int& aSkippedBytes)
    1236             : {
    1237           0 :   aSkippedBytes = 0;
    1238             :   // Sync to the next page.
    1239           0 :   int ret = 0;
    1240           0 :   uint32_t bytesRead = 0;
    1241           0 :   int64_t readHead = aOffset;
    1242           0 :   while (ret <= 0) {
    1243           0 :     ret = ogg_sync_pageseek(aState, aPage);
    1244           0 :     if (ret == 0) {
    1245           0 :       char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
    1246           0 :       NS_ASSERTION(buffer, "Must have a buffer");
    1247             : 
    1248             :       // Read from the file into the buffer
    1249             :       int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP),
    1250           0 :                                    aEndOffset - readHead);
    1251           0 :       NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check");
    1252           0 :       if (bytesToRead <= 0) {
    1253           0 :         return PAGE_SYNC_END_OF_RANGE;
    1254             :       }
    1255           0 :       nsresult rv = NS_OK;
    1256           0 :       if (aCachedDataOnly) {
    1257           0 :         rv = aResource->GetResource()->ReadFromCache(buffer, readHead,
    1258           0 :                                                      static_cast<uint32_t>(bytesToRead));
    1259           0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1260           0 :         bytesRead = static_cast<uint32_t>(bytesToRead);
    1261             :       } else {
    1262           0 :         rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
    1263           0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1264           0 :         rv = aResource->Read(buffer,
    1265             :                              static_cast<uint32_t>(bytesToRead),
    1266           0 :                              &bytesRead);
    1267           0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1268             :       }
    1269           0 :       if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
    1270             :         // End of file.
    1271           0 :         return PAGE_SYNC_END_OF_RANGE;
    1272             :       }
    1273           0 :       readHead += bytesRead;
    1274             : 
    1275             :       // Update the synchronisation layer with the number
    1276             :       // of bytes written to the buffer
    1277           0 :       ret = ogg_sync_wrote(aState, bytesRead);
    1278           0 :       NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);
    1279           0 :       continue;
    1280             :     }
    1281             : 
    1282           0 :     if (ret < 0) {
    1283           0 :       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
    1284           0 :       aSkippedBytes += -ret;
    1285           0 :       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
    1286           0 :       continue;
    1287             :     }
    1288             :   }
    1289             : 
    1290           0 :   return PAGE_SYNC_OK;
    1291             : }
    1292             : 
    1293             : //OggTrackDemuxer
    1294           0 : OggTrackDemuxer::OggTrackDemuxer(OggDemuxer* aParent,
    1295             :                                  TrackInfo::TrackType aType,
    1296           0 :                                  uint32_t aTrackNumber)
    1297             :   : mParent(aParent)
    1298           0 :   , mType(aType)
    1299             : {
    1300           0 :   mInfo = mParent->GetTrackInfo(aType, aTrackNumber);
    1301           0 :   MOZ_ASSERT(mInfo);
    1302           0 : }
    1303             : 
    1304           0 : OggTrackDemuxer::~OggTrackDemuxer()
    1305             : {
    1306           0 : }
    1307             : 
    1308             : UniquePtr<TrackInfo>
    1309           0 : OggTrackDemuxer::GetInfo() const
    1310             : {
    1311           0 :   return mInfo->Clone();
    1312             : }
    1313             : 
    1314             : RefPtr<OggTrackDemuxer::SeekPromise>
    1315           0 : OggTrackDemuxer::Seek(const TimeUnit& aTime)
    1316             : {
    1317             :   // Seeks to aTime. Upon success, SeekPromise will be resolved with the
    1318             :   // actual time seeked to. Typically the random access point time
    1319           0 :   mQueuedSample = nullptr;
    1320           0 :   TimeUnit seekTime = aTime;
    1321           0 :   if (mParent->SeekInternal(mType, aTime) == NS_OK) {
    1322           0 :     RefPtr<MediaRawData> sample(NextSample());
    1323             : 
    1324             :     // Check what time we actually seeked to.
    1325           0 :     if (sample != nullptr) {
    1326           0 :       seekTime = sample->mTime;
    1327           0 :       OGG_DEBUG("%p seeked to time %" PRId64, this, seekTime.ToMicroseconds());
    1328             :     }
    1329           0 :     mQueuedSample = sample;
    1330             : 
    1331           0 :     return SeekPromise::CreateAndResolve(seekTime, __func__);
    1332             :   } else {
    1333           0 :     return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__);
    1334             :   }
    1335             : }
    1336             : 
    1337             : RefPtr<MediaRawData>
    1338           0 : OggTrackDemuxer::NextSample()
    1339             : {
    1340           0 :   if (mQueuedSample) {
    1341           0 :     RefPtr<MediaRawData> nextSample = mQueuedSample;
    1342           0 :     mQueuedSample = nullptr;
    1343           0 :     if (mType == TrackInfo::kAudioTrack) {
    1344           0 :       nextSample->mTrackInfo = mParent->mSharedAudioTrackInfo;
    1345             :     }
    1346           0 :     return nextSample;
    1347             :   }
    1348           0 :   ogg_packet* packet = mParent->GetNextPacket(mType);
    1349           0 :   if (!packet) {
    1350           0 :     return nullptr;
    1351             :   }
    1352             :   // Check the eos state in case we need to look for chained streams.
    1353           0 :   bool eos = packet->e_o_s;
    1354           0 :   OggCodecState* state = mParent->GetTrackCodecState(mType);
    1355           0 :   RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData();
    1356           0 :   if (!data) {
    1357           0 :     return nullptr;
    1358             :   }
    1359           0 :   if (mType == TrackInfo::kAudioTrack) {
    1360           0 :     data->mTrackInfo = mParent->mSharedAudioTrackInfo;
    1361             :   }
    1362           0 :   if (eos) {
    1363             :     // We've encountered an end of bitstream packet; check for a chained
    1364             :     // bitstream following this one.
    1365             :     // This will also update mSharedAudioTrackInfo.
    1366           0 :     mParent->ReadOggChain(data->GetEndTime());
    1367             :   }
    1368           0 :   return data;
    1369             : }
    1370             : 
    1371             : RefPtr<OggTrackDemuxer::SamplesPromise>
    1372           0 : OggTrackDemuxer::GetSamples(int32_t aNumSamples)
    1373             : {
    1374           0 :   RefPtr<SamplesHolder> samples = new SamplesHolder;
    1375           0 :   if (!aNumSamples) {
    1376           0 :     return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__);
    1377             :   }
    1378             : 
    1379           0 :   while (aNumSamples) {
    1380           0 :     RefPtr<MediaRawData> sample(NextSample());
    1381           0 :     if (!sample) {
    1382           0 :       break;
    1383             :     }
    1384           0 :     samples->mSamples.AppendElement(sample);
    1385           0 :     aNumSamples--;
    1386             :   }
    1387             : 
    1388           0 :   if (samples->mSamples.IsEmpty()) {
    1389           0 :     return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
    1390             :   } else {
    1391           0 :     return SamplesPromise::CreateAndResolve(samples, __func__);
    1392             :   }
    1393             : }
    1394             : 
    1395             : void
    1396           0 : OggTrackDemuxer::Reset()
    1397             : {
    1398           0 :   mParent->Reset(mType);
    1399           0 :   mQueuedSample = nullptr;
    1400           0 : }
    1401             : 
    1402             : RefPtr<OggTrackDemuxer::SkipAccessPointPromise>
    1403           0 : OggTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
    1404             : {
    1405           0 :   uint32_t parsed = 0;
    1406           0 :   bool found = false;
    1407           0 :   RefPtr<MediaRawData> sample;
    1408             : 
    1409           0 :   OGG_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds());
    1410           0 :   while (!found && (sample = NextSample())) {
    1411           0 :     parsed++;
    1412           0 :     if (sample->mKeyframe && sample->mTime >= aTimeThreshold) {
    1413           0 :       found = true;
    1414           0 :       mQueuedSample = sample;
    1415             :     }
    1416             :   }
    1417           0 :   if (found) {
    1418           0 :     OGG_DEBUG("next sample: %f (parsed: %d)",
    1419             :                sample->mTime.ToSeconds(), parsed);
    1420           0 :     return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
    1421             :   } else {
    1422           0 :     SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed);
    1423           0 :     return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__);
    1424             :   }
    1425             : }
    1426             : 
    1427             : TimeIntervals
    1428           0 : OggTrackDemuxer::GetBuffered()
    1429             : {
    1430           0 :   return mParent->GetBuffered(mType);
    1431             : }
    1432             : 
    1433             : void
    1434           0 : OggTrackDemuxer::BreakCycles()
    1435             : {
    1436           0 :   mParent = nullptr;
    1437           0 : }
    1438             : 
    1439             : 
    1440             : // Returns an ogg page's checksum.
    1441             : ogg_uint32_t
    1442           0 : OggDemuxer::GetPageChecksum(ogg_page* page)
    1443             : {
    1444           0 :   if (page == 0 || page->header == 0 || page->header_len < 25) {
    1445           0 :     return 0;
    1446             :   }
    1447           0 :   const unsigned char* p = page->header + 22;
    1448           0 :   uint32_t c = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24);
    1449           0 :   return c;
    1450             : }
    1451             : 
    1452             : int64_t
    1453           0 : OggDemuxer::RangeStartTime(TrackInfo::TrackType aType, int64_t aOffset)
    1454             : {
    1455           0 :   int64_t position = Resource(aType)->Tell();
    1456           0 :   nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
    1457           0 :   NS_ENSURE_SUCCESS(res, 0);
    1458           0 :   int64_t startTime = 0;
    1459           0 :   FindStartTime(aType, startTime);
    1460           0 :   res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
    1461           0 :   NS_ENSURE_SUCCESS(res, -1);
    1462           0 :   return startTime;
    1463             : }
    1464             : 
    1465             : struct nsDemuxerAutoOggSyncState
    1466             : {
    1467           0 :   nsDemuxerAutoOggSyncState()
    1468           0 :   {
    1469           0 :     ogg_sync_init(&mState);
    1470           0 :   }
    1471           0 :   ~nsDemuxerAutoOggSyncState()
    1472           0 :   {
    1473           0 :     ogg_sync_clear(&mState);
    1474           0 :   }
    1475             :   ogg_sync_state mState;
    1476             : };
    1477             : 
    1478             : int64_t
    1479           0 : OggDemuxer::RangeEndTime(TrackInfo::TrackType aType, int64_t aEndOffset)
    1480             : {
    1481           0 :   int64_t position = Resource(aType)->Tell();
    1482           0 :   int64_t endTime = RangeEndTime(aType, 0, aEndOffset, false);
    1483           0 :   nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
    1484           0 :   NS_ENSURE_SUCCESS(res, -1);
    1485           0 :   return endTime;
    1486             : }
    1487             : 
    1488             : int64_t
    1489           0 : OggDemuxer::RangeEndTime(TrackInfo::TrackType aType,
    1490             :                          int64_t aStartOffset,
    1491             :                          int64_t aEndOffset,
    1492             :                          bool aCachedDataOnly)
    1493             : {
    1494           0 :   nsDemuxerAutoOggSyncState sync;
    1495             : 
    1496             :   // We need to find the last page which ends before aEndOffset that
    1497             :   // has a granulepos that we can convert to a timestamp. We do this by
    1498             :   // backing off from aEndOffset until we encounter a page on which we can
    1499             :   // interpret the granulepos. If while backing off we encounter a page which
    1500             :   // we've previously encountered before, we'll either backoff again if we
    1501             :   // haven't found an end time yet, or return the last end time found.
    1502           0 :   const int step = 5000;
    1503           0 :   const int maxOggPageSize = 65306;
    1504           0 :   int64_t readStartOffset = aEndOffset;
    1505           0 :   int64_t readLimitOffset = aEndOffset;
    1506           0 :   int64_t readHead = aEndOffset;
    1507           0 :   int64_t endTime = -1;
    1508           0 :   uint32_t checksumAfterSeek = 0;
    1509           0 :   uint32_t prevChecksumAfterSeek = 0;
    1510           0 :   bool mustBackOff = false;
    1511             :   while (true) {
    1512             :     ogg_page page;
    1513           0 :     int ret = ogg_sync_pageseek(&sync.mState, &page);
    1514           0 :     if (ret == 0) {
    1515             :       // We need more data if we've not encountered a page we've seen before,
    1516             :       // or we've read to the end of file.
    1517           0 :       if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
    1518           0 :         if (endTime != -1 || readStartOffset == 0) {
    1519             :           // We have encountered a page before, or we're at the end of file.
    1520             :           break;
    1521             :         }
    1522           0 :         mustBackOff = false;
    1523           0 :         prevChecksumAfterSeek = checksumAfterSeek;
    1524           0 :         checksumAfterSeek = 0;
    1525           0 :         ogg_sync_reset(&sync.mState);
    1526           0 :         readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step);
    1527             :         // There's no point reading more than the maximum size of
    1528             :         // an Ogg page into data we've previously scanned. Any data
    1529             :         // between readLimitOffset and aEndOffset must be garbage
    1530             :         // and we can ignore it thereafter.
    1531           0 :         readLimitOffset = std::min(readLimitOffset,
    1532           0 :                                    readStartOffset + maxOggPageSize);
    1533           0 :         readHead = std::max(aStartOffset, readStartOffset);
    1534             :       }
    1535             : 
    1536           0 :       int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX),
    1537           0 :                                aEndOffset - readHead);
    1538           0 :       limit = std::max(static_cast<int64_t>(0), limit);
    1539           0 :       limit = std::min(limit, static_cast<int64_t>(step));
    1540           0 :       uint32_t bytesToRead = static_cast<uint32_t>(limit);
    1541           0 :       uint32_t bytesRead = 0;
    1542           0 :       char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
    1543           0 :       NS_ASSERTION(buffer, "Must have buffer");
    1544             :       nsresult res;
    1545           0 :       if (aCachedDataOnly) {
    1546           0 :         res = Resource(aType)->GetResource()->ReadFromCache(buffer, readHead, bytesToRead);
    1547           0 :         NS_ENSURE_SUCCESS(res, -1);
    1548           0 :         bytesRead = bytesToRead;
    1549             :       } else {
    1550           0 :         NS_ASSERTION(readHead < aEndOffset,
    1551             :                      "resource pos must be before range end");
    1552           0 :         res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
    1553           0 :         NS_ENSURE_SUCCESS(res, -1);
    1554           0 :         res = Resource(aType)->Read(buffer, bytesToRead, &bytesRead);
    1555           0 :         NS_ENSURE_SUCCESS(res, -1);
    1556             :       }
    1557           0 :       readHead += bytesRead;
    1558           0 :       if (readHead > readLimitOffset) {
    1559           0 :         mustBackOff = true;
    1560             :       }
    1561             : 
    1562             :       // Update the synchronisation layer with the number
    1563             :       // of bytes written to the buffer
    1564           0 :       ret = ogg_sync_wrote(&sync.mState, bytesRead);
    1565           0 :       if (ret != 0) {
    1566           0 :         endTime = -1;
    1567           0 :         break;
    1568             :       }
    1569           0 :       continue;
    1570             :     }
    1571             : 
    1572           0 :     if (ret < 0 || ogg_page_granulepos(&page) < 0) {
    1573           0 :       continue;
    1574             :     }
    1575             : 
    1576           0 :     uint32_t checksum = GetPageChecksum(&page);
    1577           0 :     if (checksumAfterSeek == 0) {
    1578             :       // This is the first page we've decoded after a backoff/seek. Remember
    1579             :       // the page checksum. If we backoff further and encounter this page
    1580             :       // again, we'll know that we won't find a page with an end time after
    1581             :       // this one, so we'll know to back off again.
    1582           0 :       checksumAfterSeek = checksum;
    1583             :     }
    1584           0 :     if (checksum == prevChecksumAfterSeek) {
    1585             :       // This page has the same checksum as the first page we encountered
    1586             :       // after the last backoff/seek. Since we've already scanned after this
    1587             :       // page and failed to find an end time, we may as well backoff again and
    1588             :       // try to find an end time from an earlier page.
    1589           0 :       mustBackOff = true;
    1590           0 :       continue;
    1591             :     }
    1592             : 
    1593           0 :     int64_t granulepos = ogg_page_granulepos(&page);
    1594           0 :     int serial = ogg_page_serialno(&page);
    1595             : 
    1596           0 :     OggCodecState* codecState = nullptr;
    1597           0 :     codecState = mCodecStore.Get(serial);
    1598           0 :     if (!codecState) {
    1599             :       // This page is from a bitstream which we haven't encountered yet.
    1600             :       // It's probably from a new "link" in a "chained" ogg. Don't
    1601             :       // bother even trying to find a duration...
    1602           0 :       SetChained();
    1603           0 :       endTime = -1;
    1604           0 :       break;
    1605             :     }
    1606             : 
    1607           0 :     int64_t t = codecState->Time(granulepos);
    1608           0 :     if (t != -1) {
    1609           0 :       endTime = t;
    1610             :     }
    1611           0 :   }
    1612             : 
    1613           0 :   return endTime;
    1614             : }
    1615             : 
    1616             : nsresult
    1617           0 : OggDemuxer::GetSeekRanges(TrackInfo::TrackType aType,
    1618             :                           nsTArray<SeekRange>& aRanges)
    1619             : {
    1620           0 :   AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
    1621           0 :   MediaByteRangeSet cached;
    1622           0 :   nsresult res = resource->GetCachedRanges(cached);
    1623           0 :   NS_ENSURE_SUCCESS(res, res);
    1624             : 
    1625           0 :   for (uint32_t index = 0; index < cached.Length(); index++) {
    1626           0 :     auto& range = cached[index];
    1627           0 :     int64_t startTime = -1;
    1628           0 :     int64_t endTime = -1;
    1629           0 :     if (NS_FAILED(Reset(aType))) {
    1630           0 :       return NS_ERROR_FAILURE;
    1631             :     }
    1632           0 :     int64_t startOffset = range.mStart;
    1633           0 :     int64_t endOffset = range.mEnd;
    1634           0 :     startTime = RangeStartTime(aType, startOffset);
    1635           0 :     if (startTime != -1 &&
    1636             :         ((endTime = RangeEndTime(aType, endOffset)) != -1)) {
    1637           0 :       NS_WARNING_ASSERTION(startTime < endTime,
    1638             :                            "Start time must be before end time");
    1639           0 :       aRanges.AppendElement(SeekRange(startOffset,
    1640             :                                       endOffset,
    1641             :                                       startTime,
    1642           0 :                                       endTime));
    1643             :      }
    1644             :   }
    1645           0 :   if (NS_FAILED(Reset(aType))) {
    1646           0 :     return NS_ERROR_FAILURE;
    1647             :   }
    1648           0 :   return NS_OK;
    1649             : }
    1650             : 
    1651             : OggDemuxer::SeekRange
    1652           0 : OggDemuxer::SelectSeekRange(TrackInfo::TrackType aType,
    1653             :                             const nsTArray<SeekRange>& ranges,
    1654             :                             int64_t aTarget,
    1655             :                             int64_t aStartTime,
    1656             :                             int64_t aEndTime,
    1657             :                             bool aExact)
    1658             : {
    1659           0 :   int64_t so = 0;
    1660           0 :   int64_t eo = Resource(aType)->GetLength();
    1661           0 :   int64_t st = aStartTime;
    1662           0 :   int64_t et = aEndTime;
    1663           0 :   for (uint32_t i = 0; i < ranges.Length(); i++) {
    1664           0 :     const SeekRange& r = ranges[i];
    1665           0 :     if (r.mTimeStart < aTarget) {
    1666           0 :       so = r.mOffsetStart;
    1667           0 :       st = r.mTimeStart;
    1668             :     }
    1669           0 :     if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
    1670           0 :       eo = r.mOffsetEnd;
    1671           0 :       et = r.mTimeEnd;
    1672             :     }
    1673             : 
    1674           0 :     if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
    1675             :       // Target lies exactly in this range.
    1676           0 :       return ranges[i];
    1677             :     }
    1678             :   }
    1679           0 :   if (aExact || eo == -1) {
    1680           0 :     return SeekRange();
    1681             :   }
    1682           0 :   return SeekRange(so, eo, st, et);
    1683             : }
    1684             : 
    1685             : 
    1686             : nsresult
    1687           0 : OggDemuxer::SeekInBufferedRange(TrackInfo::TrackType aType,
    1688             :                                 int64_t aTarget,
    1689             :                                 int64_t aAdjustedTarget,
    1690             :                                 int64_t aStartTime,
    1691             :                                 int64_t aEndTime,
    1692             :                                 const nsTArray<SeekRange>& aRanges,
    1693             :                                 const SeekRange& aRange)
    1694             : {
    1695           0 :   OGG_DEBUG("Seeking in buffered data to %" PRId64 " using bisection search", aTarget);
    1696           0 :   if (aType == TrackInfo::kVideoTrack || aAdjustedTarget >= aTarget) {
    1697             :     // We know the exact byte range in which the target must lie. It must
    1698             :     // be buffered in the media cache. Seek there.
    1699           0 :     nsresult res = SeekBisection(aType, aTarget, aRange, 0);
    1700           0 :     if (NS_FAILED(res) || aType != TrackInfo::kVideoTrack) {
    1701           0 :       return res;
    1702             :     }
    1703             : 
    1704             :     // We have an active Theora bitstream. Peek the next Theora frame, and
    1705             :     // extract its keyframe's time.
    1706           0 :     DemuxUntilPacketAvailable(aType, mTheoraState);
    1707           0 :     ogg_packet* packet = mTheoraState->PacketPeek();
    1708           0 :     if (packet && !mTheoraState->IsKeyframe(packet)) {
    1709             :       // First post-seek frame isn't a keyframe, seek back to previous keyframe,
    1710             :       // otherwise we'll get visual artifacts.
    1711           0 :       NS_ASSERTION(packet->granulepos != -1, "Must have a granulepos");
    1712           0 :       int shift = mTheoraState->KeyFrameGranuleJobs();
    1713           0 :       int64_t keyframeGranulepos = (packet->granulepos >> shift) << shift;
    1714           0 :       int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
    1715             :       SEEK_LOG(LogLevel::Debug,
    1716             :                ("Keyframe for %lld is at %lld, seeking back to it", frameTime,
    1717             :                 keyframeTime));
    1718           0 :       aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime);
    1719             :     }
    1720             :   }
    1721             : 
    1722           0 :   nsresult res = NS_OK;
    1723           0 :   if (aAdjustedTarget < aTarget) {
    1724             :     SeekRange k = SelectSeekRange(aType,
    1725             :                                   aRanges,
    1726             :                                   aAdjustedTarget,
    1727             :                                   aStartTime,
    1728             :                                   aEndTime,
    1729           0 :                                   false);
    1730           0 :     res = SeekBisection(aType, aAdjustedTarget, k, OGG_SEEK_FUZZ_USECS);
    1731             :   }
    1732           0 :   return res;
    1733             : }
    1734             : 
    1735             : nsresult
    1736           0 : OggDemuxer::SeekInUnbuffered(TrackInfo::TrackType aType,
    1737             :                              int64_t aTarget,
    1738             :                              int64_t aStartTime,
    1739             :                              int64_t aEndTime,
    1740             :                              const nsTArray<SeekRange>& aRanges)
    1741             : {
    1742           0 :   OGG_DEBUG("Seeking in unbuffered data to %" PRId64 " using bisection search", aTarget);
    1743             : 
    1744             :   // If we've got an active Theora bitstream, determine the maximum possible
    1745             :   // time in usecs which a keyframe could be before a given interframe. We
    1746             :   // subtract this from our seek target, seek to the new target, and then
    1747             :   // will decode forward to the original seek target. We should encounter a
    1748             :   // keyframe in that interval. This prevents us from needing to run two
    1749             :   // bisections; one for the seek target frame, and another to find its
    1750             :   // keyframe. It's usually faster to just download this extra data, rather
    1751             :   // tham perform two bisections to find the seek target's keyframe. We
    1752             :   // don't do this offsetting when seeking in a buffered range,
    1753             :   // as the extra decoding causes a noticeable speed hit when all the data
    1754             :   // is buffered (compared to just doing a bisection to exactly find the
    1755             :   // keyframe).
    1756           0 :   int64_t keyframeOffsetMs = 0;
    1757           0 :   if (aType == TrackInfo::kVideoTrack && mTheoraState) {
    1758           0 :     keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
    1759             :   }
    1760             :   // Add in the Opus pre-roll if necessary, as well.
    1761           0 :   if (aType == TrackInfo::kAudioTrack && mOpusState) {
    1762           0 :     keyframeOffsetMs = std::max(keyframeOffsetMs, OGG_SEEK_OPUS_PREROLL);
    1763             :   }
    1764           0 :   int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
    1765             :   // Minimize the bisection search space using the known timestamps from the
    1766             :   // buffered ranges.
    1767             :   SeekRange k =
    1768           0 :     SelectSeekRange(aType, aRanges, seekTarget, aStartTime, aEndTime, false);
    1769           0 :   return SeekBisection(aType, seekTarget, k, OGG_SEEK_FUZZ_USECS);
    1770             : }
    1771             : 
    1772             : nsresult
    1773           0 : OggDemuxer::SeekBisection(TrackInfo::TrackType aType,
    1774             :                           int64_t aTarget,
    1775             :                           const SeekRange& aRange,
    1776             :                           uint32_t aFuzz)
    1777             : {
    1778             :   nsresult res;
    1779             : 
    1780           0 :   if (aTarget <= aRange.mTimeStart) {
    1781           0 :     if (NS_FAILED(Reset(aType))) {
    1782           0 :       return NS_ERROR_FAILURE;
    1783             :     }
    1784           0 :     res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
    1785           0 :     NS_ENSURE_SUCCESS(res,res);
    1786           0 :     return NS_OK;
    1787             :   }
    1788             : 
    1789             :   // Bisection search, find start offset of last page with end time less than
    1790             :   // the seek target.
    1791           0 :   ogg_int64_t startOffset = aRange.mOffsetStart;
    1792           0 :   ogg_int64_t startTime = aRange.mTimeStart;
    1793           0 :   ogg_int64_t startLength = 0; // Length of the page at startOffset.
    1794           0 :   ogg_int64_t endOffset = aRange.mOffsetEnd;
    1795           0 :   ogg_int64_t endTime = aRange.mTimeEnd;
    1796             : 
    1797           0 :   ogg_int64_t seekTarget = aTarget;
    1798           0 :   int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz);
    1799           0 :   int hops = 0;
    1800           0 :   DebugOnly<ogg_int64_t> previousGuess = -1;
    1801           0 :   int backsteps = 0;
    1802           0 :   const int maxBackStep = 10;
    1803           0 :   NS_ASSERTION(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX,
    1804             :                "Backstep calculation must not overflow");
    1805             : 
    1806             :   // Seek via bisection search. Loop until we find the offset where the page
    1807             :   // before the offset is before the seek target, and the page after the offset
    1808             :   // is after the seek target.
    1809             :   while (true) {
    1810           0 :     ogg_int64_t duration = 0;
    1811           0 :     double target = 0;
    1812           0 :     ogg_int64_t interval = 0;
    1813           0 :     ogg_int64_t guess = 0;
    1814             :     ogg_page page;
    1815           0 :     int skippedBytes = 0;
    1816           0 :     ogg_int64_t pageOffset = 0;
    1817           0 :     ogg_int64_t pageLength = 0;
    1818           0 :     ogg_int64_t granuleTime = -1;
    1819           0 :     bool mustBackoff = false;
    1820             : 
    1821             :     // Guess where we should bisect to, based on the bit rate and the time
    1822             :     // remaining in the interval. Loop until we can determine the time at
    1823             :     // the guess offset.
    1824             :     while (true) {
    1825             : 
    1826             :       // Discard any previously buffered packets/pages.
    1827           0 :       if (NS_FAILED(Reset(aType))) {
    1828           0 :         return NS_ERROR_FAILURE;
    1829             :       }
    1830             : 
    1831           0 :       interval = endOffset - startOffset - startLength;
    1832           0 :       if (interval == 0) {
    1833             :         // Our interval is empty, we've found the optimal seek point, as the
    1834             :         // page at the start offset is before the seek target, and the page
    1835             :         // at the end offset is after the seek target.
    1836             :         SEEK_LOG(LogLevel::Debug, ("Interval narrowed, terminating bisection."));
    1837           0 :         break;
    1838             :       }
    1839             : 
    1840             :       // Guess bisection point.
    1841           0 :       duration = endTime - startTime;
    1842           0 :       target = (double)(seekTarget - startTime) / (double)duration;
    1843           0 :       guess = startOffset + startLength +
    1844           0 :               static_cast<ogg_int64_t>((double)interval * target);
    1845           0 :       guess = std::min(guess, endOffset - PAGE_STEP);
    1846           0 :       if (mustBackoff) {
    1847             :         // We previously failed to determine the time at the guess offset,
    1848             :         // probably because we ran out of data to decode. This usually happens
    1849             :         // when we guess very close to the end offset. So reduce the guess
    1850             :         // offset using an exponential backoff until we determine the time.
    1851             :         SEEK_LOG(LogLevel::Debug, ("Backing off %d bytes, backsteps=%d",
    1852             :           static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps));
    1853           0 :         guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps));
    1854             : 
    1855           0 :         if (guess <= startOffset) {
    1856             :           // We've tried to backoff to before the start offset of our seek
    1857             :           // range. This means we couldn't find a seek termination position
    1858             :           // near the end of the seek range, so just set the seek termination
    1859             :           // condition, and break out of the bisection loop. We'll begin
    1860             :           // decoding from the start of the seek range.
    1861           0 :           interval = 0;
    1862           0 :           break;
    1863             :         }
    1864             : 
    1865           0 :         backsteps = std::min(backsteps + 1, maxBackStep);
    1866             :         // We reset mustBackoff. If we still need to backoff further, it will
    1867             :         // be set to true again.
    1868           0 :         mustBackoff = false;
    1869             :       } else {
    1870           0 :         backsteps = 0;
    1871             :       }
    1872           0 :       guess = std::max(guess, startOffset + startLength);
    1873             : 
    1874             :       SEEK_LOG(LogLevel::Debug, ("Seek loop start[o=%lld..%lld t=%lld] "
    1875             :                               "end[o=%lld t=%lld] "
    1876             :                               "interval=%lld target=%lf guess=%lld",
    1877             :                               startOffset, (startOffset+startLength), startTime,
    1878             :                               endOffset, endTime, interval, target, guess));
    1879             : 
    1880           0 :       NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
    1881           0 :       NS_ASSERTION(guess < endOffset, "Guess must be before range end");
    1882           0 :       NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
    1883           0 :       previousGuess = guess;
    1884             : 
    1885           0 :       hops++;
    1886             : 
    1887             :       // Locate the next page after our seek guess, and then figure out the
    1888             :       // granule time of the audio and video bitstreams there. We can then
    1889             :       // make a bisection decision based on our location in the media.
    1890           0 :       PageSyncResult pageSyncResult = PageSync(Resource(aType),
    1891             :                                                OggSyncState(aType),
    1892             :                                                false,
    1893             :                                                guess,
    1894             :                                                endOffset,
    1895             :                                                &page,
    1896           0 :                                                skippedBytes);
    1897           0 :       NS_ENSURE_TRUE(pageSyncResult != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
    1898             : 
    1899           0 :       if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
    1900             :         // Our guess was too close to the end, we've ended up reading the end
    1901             :         // page. Backoff exponentially from the end point, in case the last
    1902             :         // page/frame/sample is huge.
    1903           0 :         mustBackoff = true;
    1904             :         SEEK_LOG(LogLevel::Debug, ("Hit the end of range, backing off"));
    1905           0 :         continue;
    1906             :       }
    1907             : 
    1908             :       // We've located a page of length |ret| at |guess + skippedBytes|.
    1909             :       // Remember where the page is located.
    1910           0 :       pageOffset = guess + skippedBytes;
    1911           0 :       pageLength = page.header_len + page.body_len;
    1912             : 
    1913             :       // Read pages until we can determine the granule time of the audio and
    1914             :       // video bitstream.
    1915           0 :       ogg_int64_t audioTime = -1;
    1916           0 :       ogg_int64_t videoTime = -1;
    1917           0 :       do {
    1918             :         // Add the page to its codec state, determine its granule time.
    1919           0 :         uint32_t serial = ogg_page_serialno(&page);
    1920           0 :         OggCodecState* codecState = mCodecStore.Get(serial);
    1921           0 :         if (codecState && GetCodecStateType(codecState) == aType) {
    1922           0 :           if (codecState->mActive) {
    1923           0 :             int ret = ogg_stream_pagein(&codecState->mState, &page);
    1924           0 :             NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
    1925             :           }
    1926             : 
    1927           0 :           ogg_int64_t granulepos = ogg_page_granulepos(&page);
    1928             : 
    1929           0 :           if (aType == TrackInfo::kAudioTrack &&
    1930           0 :               granulepos > 0 && audioTime == -1) {
    1931           0 :             if (mVorbisState && serial == mVorbisState->mSerial) {
    1932           0 :               audioTime = mVorbisState->Time(granulepos);
    1933           0 :             } else if (mOpusState && serial == mOpusState->mSerial) {
    1934           0 :               audioTime = mOpusState->Time(granulepos);
    1935           0 :             } else if (mFlacState && serial == mFlacState->mSerial) {
    1936           0 :               audioTime = mFlacState->Time(granulepos);
    1937             :             }
    1938             :           }
    1939             : 
    1940           0 :           if (aType == TrackInfo::kVideoTrack &&
    1941           0 :               granulepos > 0 && serial == mTheoraState->mSerial &&
    1942             :               videoTime == -1) {
    1943           0 :             videoTime = mTheoraState->Time(granulepos);
    1944             :           }
    1945             : 
    1946           0 :           if (pageOffset + pageLength >= endOffset) {
    1947             :             // Hit end of readable data.
    1948           0 :             break;
    1949             :           }
    1950             :         }
    1951           0 :         if (!ReadOggPage(aType, &page)) {
    1952           0 :           break;
    1953             :         }
    1954             : 
    1955           0 :       } while ((aType == TrackInfo::kAudioTrack && audioTime == -1) ||
    1956           0 :                (aType == TrackInfo::kVideoTrack && videoTime == -1));
    1957             : 
    1958             : 
    1959           0 :       if ((aType == TrackInfo::kAudioTrack && audioTime == -1) ||
    1960           0 :           (aType == TrackInfo::kVideoTrack && videoTime == -1)) {
    1961             :         // We don't have timestamps for all active tracks...
    1962           0 :         if (pageOffset == startOffset + startLength &&
    1963           0 :             pageOffset + pageLength >= endOffset) {
    1964             :           // We read the entire interval without finding timestamps for all
    1965             :           // active tracks. We know the interval start offset is before the seek
    1966             :           // target, and the interval end is after the seek target, and we can't
    1967             :           // terminate inside the interval, so we terminate the seek at the
    1968             :           // start of the interval.
    1969           0 :           interval = 0;
    1970           0 :           break;
    1971             :         }
    1972             : 
    1973             :         // We should backoff; cause the guess to back off from the end, so
    1974             :         // that we've got more room to capture.
    1975           0 :         mustBackoff = true;
    1976           0 :         continue;
    1977             :       }
    1978             : 
    1979             :       // We've found appropriate time stamps here. Proceed to bisect
    1980             :       // the search space.
    1981           0 :       granuleTime = aType == TrackInfo::kAudioTrack ? audioTime : videoTime;
    1982           0 :       NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
    1983           0 :       break;
    1984           0 :     } // End of "until we determine time at guess offset" loop.
    1985             : 
    1986           0 :     if (interval == 0) {
    1987             :       // Seek termination condition; we've found the page boundary of the
    1988             :       // last page before the target, and the first page after the target.
    1989             :       SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", startOffset));
    1990           0 :       NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
    1991           0 :       res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
    1992           0 :       NS_ENSURE_SUCCESS(res,res);
    1993           0 :       if (NS_FAILED(Reset(aType))) {
    1994           0 :         return NS_ERROR_FAILURE;
    1995             :       }
    1996           0 :       break;
    1997             :     }
    1998             : 
    1999             :     SEEK_LOG(LogLevel::Debug, ("Time at offset %lld is %lld", guess, granuleTime));
    2000           0 :     if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
    2001             :       // We're within the fuzzy region in which we want to terminate the search.
    2002           0 :       res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
    2003           0 :       NS_ENSURE_SUCCESS(res,res);
    2004           0 :       if (NS_FAILED(Reset(aType))) {
    2005           0 :         return NS_ERROR_FAILURE;
    2006             :       }
    2007             :       SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", pageOffset));
    2008           0 :       break;
    2009             :     }
    2010             : 
    2011           0 :     if (granuleTime >= seekTarget) {
    2012             :       // We've landed after the seek target.
    2013           0 :       NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease");
    2014           0 :       endOffset = pageOffset;
    2015           0 :       endTime = granuleTime;
    2016           0 :     } else if (granuleTime < seekTarget) {
    2017             :       // Landed before seek target.
    2018           0 :       NS_ASSERTION(pageOffset >= startOffset + startLength,
    2019             :         "Bisection point should be at or after end of first page in interval");
    2020           0 :       startOffset = pageOffset;
    2021           0 :       startLength = pageLength;
    2022           0 :       startTime = granuleTime;
    2023             :     }
    2024           0 :     NS_ASSERTION(startTime <= seekTarget, "Must be before seek target");
    2025           0 :     NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
    2026           0 :   }
    2027             : 
    2028             :   SEEK_LOG(LogLevel::Debug, ("Seek complete in %d bisections.", hops));
    2029             : 
    2030           0 :   return NS_OK;
    2031             : }
    2032             : 
    2033             : #undef OGG_DEBUG
    2034             : #undef SEEK_DEBUG
    2035             : } // namespace mozilla

Generated by: LCOV version 1.13