LCOV - code coverage report
Current view: top level - dom/media - MediaCache.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 1 1177 0.1 %
Date: 2017-07-14 16:53:18 Functions: 0 102 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 "MediaCache.h"
       8             : 
       9             : #include "FileBlockCache.h"
      10             : #include "MediaBlockCacheBase.h"
      11             : #include "MediaPrefs.h"
      12             : #include "MediaResource.h"
      13             : #include "MemoryBlockCache.h"
      14             : #include "mozilla/Attributes.h"
      15             : #include "mozilla/Logging.h"
      16             : #include "mozilla/Preferences.h"
      17             : #include "mozilla/ReentrantMonitor.h"
      18             : #include "mozilla/Services.h"
      19             : #include "mozilla/StaticPtr.h"
      20             : #include "mozilla/Telemetry.h"
      21             : #include "nsContentUtils.h"
      22             : #include "nsIObserverService.h"
      23             : #include "nsIPrincipal.h"
      24             : #include "nsISeekableStream.h"
      25             : #include "nsThreadUtils.h"
      26             : #include "prio.h"
      27             : #include <algorithm>
      28             : 
      29             : namespace mozilla {
      30             : 
      31             : #undef LOG
      32             : #undef LOGI
      33             : LazyLogModule gMediaCacheLog("MediaCache");
      34             : #define LOG(...) MOZ_LOG(gMediaCacheLog, LogLevel::Debug, (__VA_ARGS__))
      35             : #define LOGI(...) MOZ_LOG(gMediaCacheLog, LogLevel::Info, (__VA_ARGS__))
      36             : 
      37             : 
      38             : // Readahead blocks for non-seekable streams will be limited to this
      39             : // fraction of the cache space. We don't normally evict such blocks
      40             : // because replacing them requires a seek, but we need to make sure
      41             : // they don't monopolize the cache.
      42             : static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
      43             : 
      44             : // Data N seconds before the current playback position is given the same priority
      45             : // as data REPLAY_PENALTY_FACTOR*N seconds ahead of the current playback
      46             : // position. REPLAY_PENALTY_FACTOR is greater than 1 to reflect that
      47             : // data in the past is less likely to be played again than data in the future.
      48             : // We want to give data just behind the current playback position reasonably
      49             : // high priority in case codecs need to retrieve that data (e.g. because
      50             : // tracks haven't been muxed well or are being decoded at uneven rates).
      51             : // 1/REPLAY_PENALTY_FACTOR as much data will be kept behind the
      52             : // current playback position as will be kept ahead of the current playback
      53             : // position.
      54             : static const uint32_t REPLAY_PENALTY_FACTOR = 3;
      55             : 
      56             : // When looking for a reusable block, scan forward this many blocks
      57             : // from the desired "best" block location to look for free blocks,
      58             : // before we resort to scanning the whole cache. The idea is to try to
      59             : // store runs of stream blocks close-to-consecutively in the cache if we
      60             : // can.
      61             : static const uint32_t FREE_BLOCK_SCAN_LIMIT = 16;
      62             : 
      63             : #ifdef DEBUG
      64             : // Turn this on to do very expensive cache state validation
      65             : // #define DEBUG_VERIFY_CACHE
      66             : #endif
      67             : 
      68             : class MediaCacheFlusher final : public nsIObserver,
      69             :                                 public nsSupportsWeakReference
      70             : {
      71             : public:
      72             :   NS_DECL_ISUPPORTS
      73             :   NS_DECL_NSIOBSERVER
      74             : 
      75             :   static void RegisterMediaCache(MediaCache* aMediaCache);
      76             :   static void UnregisterMediaCache(MediaCache* aMediaCache);
      77             : 
      78             : private:
      79           0 :   MediaCacheFlusher() {}
      80           0 :   ~MediaCacheFlusher() {}
      81             : 
      82             :   // Singleton instance created when a first MediaCache is registered, and
      83             :   // released when the last MediaCache is unregistered.
      84             :   // The observer service will keep a weak reference to it, for notifications.
      85             :   static StaticRefPtr<MediaCacheFlusher> gMediaCacheFlusher;
      86             : 
      87             :   nsTArray<MediaCache*> mMediaCaches;
      88             : };
      89             : 
      90             : /* static */ StaticRefPtr<MediaCacheFlusher>
      91           3 :   MediaCacheFlusher::gMediaCacheFlusher;
      92             : 
      93           0 : NS_IMPL_ISUPPORTS(MediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
      94             : 
      95             : /* static */ void
      96           0 : MediaCacheFlusher::RegisterMediaCache(MediaCache* aMediaCache)
      97             : {
      98           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
      99             : 
     100           0 :   if (!gMediaCacheFlusher) {
     101           0 :     gMediaCacheFlusher = new MediaCacheFlusher();
     102             : 
     103             :     nsCOMPtr<nsIObserverService> observerService =
     104           0 :       mozilla::services::GetObserverService();
     105           0 :     if (observerService) {
     106           0 :       observerService->AddObserver(
     107           0 :         gMediaCacheFlusher, "last-pb-context-exited", true);
     108           0 :       observerService->AddObserver(
     109           0 :         gMediaCacheFlusher, "cacheservice:empty-cache", true);
     110             :     }
     111             :   }
     112             : 
     113           0 :   gMediaCacheFlusher->mMediaCaches.AppendElement(aMediaCache);
     114           0 : }
     115             : 
     116             : /* static */ void
     117           0 : MediaCacheFlusher::UnregisterMediaCache(MediaCache* aMediaCache)
     118             : {
     119           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     120             : 
     121           0 :   gMediaCacheFlusher->mMediaCaches.RemoveElement(aMediaCache);
     122             : 
     123           0 :   if (gMediaCacheFlusher->mMediaCaches.Length() == 0) {
     124           0 :     gMediaCacheFlusher = nullptr;
     125             :   }
     126           0 : }
     127             : 
     128             : class MediaCache
     129             : {
     130             : public:
     131           0 :   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaCache)
     132             : 
     133             :   friend class MediaCacheStream::BlockList;
     134             :   typedef MediaCacheStream::BlockList BlockList;
     135             :   static const int64_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
     136             : 
     137             :   // Get an instance of a MediaCache (or nullptr if initialization failed).
     138             :   // aContentLength is the content length if known already, otherwise -1.
     139             :   // If the length is known and considered small enough, a discrete MediaCache
     140             :   // with memory backing will be given. Otherwise the one MediaCache with
     141             :   // file backing will be provided.
     142             :   static RefPtr<MediaCache> GetMediaCache(int64_t aContentLength);
     143             : 
     144             :   // Brutally flush the cache contents. Main thread only.
     145             :   void Flush();
     146             : 
     147             :   // Close all streams associated with private browsing windows. This will
     148             :   // also remove the blocks from the cache since we don't want to leave any
     149             :   // traces when PB is done.
     150             :   void CloseStreamsForPrivateBrowsing();
     151             : 
     152             :   // Cache-file access methods. These are the lowest-level cache methods.
     153             :   // mReentrantMonitor must be held; these can be called on any thread.
     154             :   // This can return partial reads.
     155             :   // Note mReentrantMonitor will be dropped while doing IO. The caller need
     156             :   // to handle changes happening when the monitor is not held.
     157             :   nsresult ReadCacheFile(int64_t aOffset, void* aData, int32_t aLength,
     158             :                          int32_t* aBytes);
     159             : 
     160           0 :   int64_t AllocateResourceID()
     161             :   {
     162           0 :     mReentrantMonitor.AssertCurrentThreadIn();
     163           0 :     return mNextResourceID++;
     164             :   }
     165             : 
     166             :   // mReentrantMonitor must be held, called on main thread.
     167             :   // These methods are used by the stream to set up and tear down streams,
     168             :   // and to handle reads and writes.
     169             :   // Add aStream to the list of streams.
     170             :   void OpenStream(MediaCacheStream* aStream);
     171             :   // Remove aStream from the list of streams.
     172             :   void ReleaseStream(MediaCacheStream* aStream);
     173             :   // Free all blocks belonging to aStream.
     174             :   void ReleaseStreamBlocks(MediaCacheStream* aStream);
     175             :   // Find a cache entry for this data, and write the data into it
     176             :   void AllocateAndWriteBlock(MediaCacheStream* aStream,
     177             :     MediaCacheStream::ReadMode aMode, Span<const uint8_t> aData1,
     178             :     Span<const uint8_t> aData2 = Span<const uint8_t>());
     179             : 
     180             :   // mReentrantMonitor must be held; can be called on any thread
     181             :   // Notify the cache that a seek has been requested. Some blocks may
     182             :   // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
     183             :   // This does not trigger channel seeks directly, the next Update()
     184             :   // will do that if necessary. The caller will call QueueUpdate().
     185             :   void NoteSeek(MediaCacheStream* aStream, int64_t aOldOffset);
     186             :   // Notify the cache that a block has been read from. This is used
     187             :   // to update last-use times. The block may not actually have a
     188             :   // cache entry yet since Read can read data from a stream's
     189             :   // in-memory mPartialBlockBuffer while the block is only partly full,
     190             :   // and thus hasn't yet been committed to the cache. The caller will
     191             :   // call QueueUpdate().
     192             :   void NoteBlockUsage(MediaCacheStream* aStream, int32_t aBlockIndex,
     193             :                       int64_t aStreamOffset,
     194             :                       MediaCacheStream::ReadMode aMode, TimeStamp aNow);
     195             :   // Mark aStream as having the block, adding it as an owner.
     196             :   void AddBlockOwnerAsReadahead(int32_t aBlockIndex, MediaCacheStream* aStream,
     197             :                                 int32_t aStreamBlockIndex);
     198             : 
     199             :   // This queues a call to Update() on the main thread.
     200             :   void QueueUpdate();
     201             : 
     202             :   // Notify all streams for the resource ID that the suspended status changed
     203             :   // at the end of MediaCache::Update.
     204             :   void QueueSuspendedStatusUpdate(int64_t aResourceID);
     205             : 
     206             :   // Updates the cache state asynchronously on the main thread:
     207             :   // -- try to trim the cache back to its desired size, if necessary
     208             :   // -- suspend channels that are going to read data that's lower priority
     209             :   // than anything currently cached
     210             :   // -- resume channels that are going to read data that's higher priority
     211             :   // than something currently cached
     212             :   // -- seek channels that need to seek to a new location
     213             :   void Update();
     214             : 
     215             : #ifdef DEBUG_VERIFY_CACHE
     216             :   // Verify invariants, especially block list invariants
     217             :   void Verify();
     218             : #else
     219           0 :   void Verify() {}
     220             : #endif
     221             : 
     222           0 :   ReentrantMonitor& GetReentrantMonitor() { return mReentrantMonitor; }
     223             : 
     224             :   /**
     225             :    * An iterator that makes it easy to iterate through all streams that
     226             :    * have a given resource ID and are not closed.
     227             :    * Can be used on the main thread or while holding the media cache lock.
     228             :    */
     229             :   class ResourceStreamIterator
     230             :   {
     231             :   public:
     232           0 :     ResourceStreamIterator(MediaCache* aMediaCache, int64_t aResourceID)
     233           0 :       : mMediaCache(aMediaCache)
     234             :       , mResourceID(aResourceID)
     235           0 :       , mNext(0)
     236             :     {
     237           0 :     }
     238           0 :     MediaCacheStream* Next()
     239             :     {
     240           0 :       while (mNext < mMediaCache->mStreams.Length()) {
     241           0 :         MediaCacheStream* stream = mMediaCache->mStreams[mNext];
     242           0 :         ++mNext;
     243           0 :         if (stream->GetResourceID() == mResourceID && !stream->IsClosed())
     244           0 :           return stream;
     245             :       }
     246           0 :       return nullptr;
     247             :     }
     248             :   private:
     249             :     MediaCache* mMediaCache;
     250             :     int64_t  mResourceID;
     251             :     uint32_t mNext;
     252             :   };
     253             : 
     254             : protected:
     255           0 :   explicit MediaCache(MediaBlockCacheBase* aCache)
     256           0 :     : mNextResourceID(1)
     257             :     , mReentrantMonitor("MediaCache.mReentrantMonitor")
     258             :     , mBlockCache(aCache)
     259             :     , mUpdateQueued(false)
     260             : #ifdef DEBUG
     261           0 :     , mInUpdate(false)
     262             : #endif
     263             :   {
     264           0 :     NS_ASSERTION(NS_IsMainThread(), "Only construct MediaCache on main thread");
     265           0 :     MOZ_COUNT_CTOR(MediaCache);
     266           0 :     MediaCacheFlusher::RegisterMediaCache(this);
     267           0 :   }
     268             : 
     269           0 :   ~MediaCache()
     270           0 :   {
     271           0 :     NS_ASSERTION(NS_IsMainThread(), "Only destroy MediaCache on main thread");
     272           0 :     if (this == gMediaCache) {
     273           0 :       LOG("~MediaCache(Global file-backed MediaCache)");
     274             :       // This is the file-backed MediaCache, reset the global pointer.
     275           0 :       gMediaCache = nullptr;
     276             :       // Only gather "MEDIACACHE" telemetry for the file-based cache.
     277           0 :       LOG("MediaCache::~MediaCache(this=%p) MEDIACACHE_WATERMARK_KB=%u",
     278             :           this,
     279             :           unsigned(mIndexWatermark * MediaCache::BLOCK_SIZE / 1024));
     280           0 :       Telemetry::Accumulate(
     281             :         Telemetry::HistogramID::MEDIACACHE_WATERMARK_KB,
     282           0 :         uint32_t(mIndexWatermark * MediaCache::BLOCK_SIZE / 1024));
     283           0 :       LOG(
     284             :         "MediaCache::~MediaCache(this=%p) MEDIACACHE_BLOCKOWNERS_WATERMARK=%u",
     285             :         this,
     286             :         unsigned(mBlockOwnersWatermark));
     287           0 :       Telemetry::Accumulate(
     288             :         Telemetry::HistogramID::MEDIACACHE_BLOCKOWNERS_WATERMARK,
     289           0 :         mBlockOwnersWatermark);
     290             :     } else {
     291           0 :       LOG("~MediaCache(Memory-backed MediaCache %p)", this);
     292             :     }
     293           0 :     MediaCacheFlusher::UnregisterMediaCache(this);
     294           0 :     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     295           0 :     Truncate();
     296           0 :     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     297             : 
     298           0 :     MOZ_COUNT_DTOR(MediaCache);
     299           0 :   }
     300             : 
     301             :   // Find a free or reusable block and return its index. If there are no
     302             :   // free blocks and no reusable blocks, add a new block to the cache
     303             :   // and return it. Can return -1 on OOM.
     304             :   int32_t FindBlockForIncomingData(TimeStamp aNow, MediaCacheStream* aStream);
     305             :   // Find a reusable block --- a free block, if there is one, otherwise
     306             :   // the reusable block with the latest predicted-next-use, or -1 if
     307             :   // there aren't any freeable blocks. Only block indices less than
     308             :   // aMaxSearchBlockIndex are considered. If aForStream is non-null,
     309             :   // then aForStream and aForStreamBlock indicate what media data will
     310             :   // be placed; FindReusableBlock will favour returning free blocks
     311             :   // near other blocks for that point in the stream.
     312             :   int32_t FindReusableBlock(TimeStamp aNow,
     313             :                             MediaCacheStream* aForStream,
     314             :                             int32_t aForStreamBlock,
     315             :                             int32_t aMaxSearchBlockIndex);
     316             :   bool BlockIsReusable(int32_t aBlockIndex);
     317             :   // Given a list of blocks sorted with the most reusable blocks at the
     318             :   // end, find the last block whose stream is not pinned (if any)
     319             :   // and whose cache entry index is less than aBlockIndexLimit
     320             :   // and append it to aResult.
     321             :   void AppendMostReusableBlock(BlockList* aBlockList,
     322             :                                nsTArray<uint32_t>* aResult,
     323             :                                int32_t aBlockIndexLimit);
     324             : 
     325             :   enum BlockClass {
     326             :     // block belongs to mMetadataBlockList because data has been consumed
     327             :     // from it in "metadata mode" --- in particular blocks read during
     328             :     // Ogg seeks go into this class. These blocks may have played data
     329             :     // in them too.
     330             :     METADATA_BLOCK,
     331             :     // block belongs to mPlayedBlockList because its offset is
     332             :     // less than the stream's current reader position
     333             :     PLAYED_BLOCK,
     334             :     // block belongs to the stream's mReadaheadBlockList because its
     335             :     // offset is greater than or equal to the stream's current
     336             :     // reader position
     337             :     READAHEAD_BLOCK
     338             :   };
     339             : 
     340             :   struct BlockOwner {
     341           0 :     constexpr BlockOwner() {}
     342             : 
     343             :     // The stream that owns this block, or null if the block is free.
     344             :     MediaCacheStream* mStream = nullptr;
     345             :     // The block index in the stream. Valid only if mStream is non-null.
     346             :     // Initialized to an insane value to highlight misuse.
     347             :     uint32_t          mStreamBlock = UINT32_MAX;
     348             :     // Time at which this block was last used. Valid only if
     349             :     // mClass is METADATA_BLOCK or PLAYED_BLOCK.
     350             :     TimeStamp         mLastUseTime;
     351             :     BlockClass        mClass = READAHEAD_BLOCK;
     352             :   };
     353             : 
     354           0 :   struct Block {
     355             :     // Free blocks have an empty mOwners array
     356             :     nsTArray<BlockOwner> mOwners;
     357             :   };
     358             : 
     359             :   // Get the BlockList that the block should belong to given its
     360             :   // current owner
     361             :   BlockList* GetListForBlock(BlockOwner* aBlock);
     362             :   // Get the BlockOwner for the given block index and owning stream
     363             :   // (returns null if the stream does not own the block)
     364             :   BlockOwner* GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream);
     365             :   // Returns true iff the block is free
     366           0 :   bool IsBlockFree(int32_t aBlockIndex)
     367           0 :   { return mIndex[aBlockIndex].mOwners.IsEmpty(); }
     368             :   // Add the block to the free list and mark its streams as not having
     369             :   // the block in cache
     370             :   void FreeBlock(int32_t aBlock);
     371             :   // Mark aStream as not having the block, removing it as an owner. If
     372             :   // the block has no more owners it's added to the free list.
     373             :   void RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream);
     374             :   // Swap all metadata associated with the two blocks. The caller
     375             :   // is responsible for swapping up any cache file state.
     376             :   void SwapBlocks(int32_t aBlockIndex1, int32_t aBlockIndex2);
     377             :   // Insert the block into the readahead block list for the stream
     378             :   // at the right point in the list.
     379             :   void InsertReadaheadBlock(BlockOwner* aBlockOwner, int32_t aBlockIndex);
     380             : 
     381             :   // Guess the duration until block aBlock will be next used
     382             :   TimeDuration PredictNextUse(TimeStamp aNow, int32_t aBlock);
     383             :   // Guess the duration until the next incoming data on aStream will be used
     384             :   TimeDuration PredictNextUseForIncomingData(MediaCacheStream* aStream);
     385             : 
     386             :   // Truncate the file and index array if there are free blocks at the
     387             :   // end
     388             :   void Truncate();
     389             : 
     390             :   // There is at most one file-backed media cache.
     391             :   // It is owned by all MediaCacheStreams that use it.
     392             :   // This is a raw pointer set by GetMediaCache(), and reset by ~MediaCache(),
     393             :   // both on the main thread; and is not accessed anywhere else.
     394             :   static MediaCache* gMediaCache;
     395             : 
     396             :   // This member is main-thread only. It's used to allocate unique
     397             :   // resource IDs to streams.
     398             :   int64_t                       mNextResourceID;
     399             : 
     400             :   // The monitor protects all the data members here. Also, off-main-thread
     401             :   // readers that need to block will Wait() on this monitor. When new
     402             :   // data becomes available in the cache, we NotifyAll() on this monitor.
     403             :   ReentrantMonitor         mReentrantMonitor;
     404             :   // This is only written while on the main thread and the monitor is held.
     405             :   // Thus, it can be safely read from the main thread or while holding the monitor.
     406             :   nsTArray<MediaCacheStream*> mStreams;
     407             :   // The Blocks describing the cache entries.
     408             :   nsTArray<Block> mIndex;
     409             :   // Keep track for highest number of blocks used, for telemetry purposes.
     410             :   int32_t mIndexWatermark = 0;
     411             :   // Keep track for highest number of blocks owners, for telemetry purposes.
     412             :   uint32_t mBlockOwnersWatermark = 0;
     413             :   // Writer which performs IO, asynchronously writing cache blocks.
     414             :   RefPtr<MediaBlockCacheBase> mBlockCache;
     415             :   // The list of free blocks; they are not ordered.
     416             :   BlockList       mFreeBlocks;
     417             :   // True if an event to run Update() has been queued but not processed
     418             :   bool            mUpdateQueued;
     419             : #ifdef DEBUG
     420             :   bool            mInUpdate;
     421             : #endif
     422             :   // A list of resource IDs to notify about the change in suspended status.
     423             :   nsTArray<int64_t> mSuspendedStatusToNotify;
     424             : };
     425             : 
     426             : // Initialized to nullptr by non-local static initialization.
     427             : /* static */ MediaCache* MediaCache::gMediaCache;
     428             : 
     429             : NS_IMETHODIMP
     430           0 : MediaCacheFlusher::Observe(nsISupports *aSubject, char const *aTopic, char16_t const *aData)
     431             : {
     432           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     433             : 
     434           0 :   if (strcmp(aTopic, "last-pb-context-exited") == 0) {
     435           0 :     for (MediaCache* mc : mMediaCaches) {
     436           0 :       mc->CloseStreamsForPrivateBrowsing();
     437             :     }
     438           0 :     return NS_OK;
     439             :   }
     440           0 :   if (strcmp(aTopic, "cacheservice:empty-cache") == 0) {
     441           0 :     for (MediaCache* mc : mMediaCaches) {
     442           0 :       mc->Flush();
     443             :     }
     444           0 :     return NS_OK;
     445             :   }
     446           0 :   return NS_OK;
     447             : }
     448             : 
     449           0 : MediaCacheStream::MediaCacheStream(ChannelMediaResource* aClient,
     450           0 :                                    bool aIsPrivateBrowsing)
     451             :   : mMediaCache(nullptr)
     452             :   , mClient(aClient)
     453             :   , mHasHadUpdate(false)
     454             :   , mClosed(false)
     455             :   , mDidNotifyDataEnded(false)
     456             :   , mResourceID(0)
     457             :   , mIsTransportSeekable(false)
     458             :   , mCacheSuspended(false)
     459             :   , mChannelEnded(false)
     460             :   , mChannelOffset(0)
     461             :   , mStreamLength(-1)
     462             :   , mStreamOffset(0)
     463             :   , mPlaybackBytesPerSecond(10000)
     464             :   , mPinCount(0)
     465             :   , mCurrentMode(MODE_PLAYBACK)
     466             :   , mMetadataInPartialBlockBuffer(false)
     467           0 :   , mIsPrivateBrowsing(aIsPrivateBrowsing)
     468             : {
     469           0 : }
     470             : 
     471           0 : size_t MediaCacheStream::SizeOfExcludingThis(
     472             :                                 MallocSizeOf aMallocSizeOf) const
     473             : {
     474             :   // Looks like these are not owned:
     475             :   // - mClient
     476             :   // - mPrincipal
     477           0 :   size_t size = mBlocks.ShallowSizeOfExcludingThis(aMallocSizeOf);
     478           0 :   size += mReadaheadBlocks.SizeOfExcludingThis(aMallocSizeOf);
     479           0 :   size += mMetadataBlocks.SizeOfExcludingThis(aMallocSizeOf);
     480           0 :   size += mPlayedBlocks.SizeOfExcludingThis(aMallocSizeOf);
     481           0 :   size += aMallocSizeOf(mPartialBlockBuffer.get());
     482             : 
     483           0 :   return size;
     484             : }
     485             : 
     486           0 : size_t MediaCacheStream::BlockList::SizeOfExcludingThis(
     487             :                                 MallocSizeOf aMallocSizeOf) const
     488             : {
     489           0 :   return mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
     490             : }
     491             : 
     492           0 : void MediaCacheStream::BlockList::AddFirstBlock(int32_t aBlock)
     493             : {
     494           0 :   NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
     495           0 :   Entry* entry = mEntries.PutEntry(aBlock);
     496             : 
     497           0 :   if (mFirstBlock < 0) {
     498           0 :     entry->mNextBlock = entry->mPrevBlock = aBlock;
     499             :   } else {
     500           0 :     entry->mNextBlock = mFirstBlock;
     501           0 :     entry->mPrevBlock = mEntries.GetEntry(mFirstBlock)->mPrevBlock;
     502           0 :     mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
     503           0 :     mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
     504             :   }
     505           0 :   mFirstBlock = aBlock;
     506           0 :   ++mCount;
     507           0 : }
     508             : 
     509           0 : void MediaCacheStream::BlockList::AddAfter(int32_t aBlock, int32_t aBefore)
     510             : {
     511           0 :   NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
     512           0 :   Entry* entry = mEntries.PutEntry(aBlock);
     513             : 
     514           0 :   Entry* addAfter = mEntries.GetEntry(aBefore);
     515           0 :   NS_ASSERTION(addAfter, "aBefore not in list");
     516             : 
     517           0 :   entry->mNextBlock = addAfter->mNextBlock;
     518           0 :   entry->mPrevBlock = aBefore;
     519           0 :   mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
     520           0 :   mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
     521           0 :   ++mCount;
     522           0 : }
     523             : 
     524           0 : void MediaCacheStream::BlockList::RemoveBlock(int32_t aBlock)
     525             : {
     526           0 :   Entry* entry = mEntries.GetEntry(aBlock);
     527           0 :   MOZ_DIAGNOSTIC_ASSERT(entry, "Block not in list");
     528             : 
     529           0 :   if (entry->mNextBlock == aBlock) {
     530           0 :     MOZ_DIAGNOSTIC_ASSERT(entry->mPrevBlock == aBlock, "Linked list inconsistency");
     531           0 :     MOZ_DIAGNOSTIC_ASSERT(mFirstBlock == aBlock, "Linked list inconsistency");
     532           0 :     mFirstBlock = -1;
     533             :   } else {
     534           0 :     if (mFirstBlock == aBlock) {
     535           0 :       mFirstBlock = entry->mNextBlock;
     536             :     }
     537           0 :     mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = entry->mPrevBlock;
     538           0 :     mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = entry->mNextBlock;
     539             :   }
     540           0 :   mEntries.RemoveEntry(entry);
     541           0 :   --mCount;
     542           0 : }
     543             : 
     544           0 : int32_t MediaCacheStream::BlockList::GetLastBlock() const
     545             : {
     546           0 :   if (mFirstBlock < 0)
     547           0 :     return -1;
     548           0 :   return mEntries.GetEntry(mFirstBlock)->mPrevBlock;
     549             : }
     550             : 
     551           0 : int32_t MediaCacheStream::BlockList::GetNextBlock(int32_t aBlock) const
     552             : {
     553           0 :   int32_t block = mEntries.GetEntry(aBlock)->mNextBlock;
     554           0 :   if (block == mFirstBlock)
     555           0 :     return -1;
     556           0 :   return block;
     557             : }
     558             : 
     559           0 : int32_t MediaCacheStream::BlockList::GetPrevBlock(int32_t aBlock) const
     560             : {
     561           0 :   if (aBlock == mFirstBlock)
     562           0 :     return -1;
     563           0 :   return mEntries.GetEntry(aBlock)->mPrevBlock;
     564             : }
     565             : 
     566             : #ifdef DEBUG
     567           0 : void MediaCacheStream::BlockList::Verify()
     568             : {
     569           0 :   int32_t count = 0;
     570           0 :   if (mFirstBlock >= 0) {
     571           0 :     int32_t block = mFirstBlock;
     572           0 :     do {
     573           0 :       Entry* entry = mEntries.GetEntry(block);
     574           0 :       NS_ASSERTION(mEntries.GetEntry(entry->mNextBlock)->mPrevBlock == block,
     575             :                    "Bad prev link");
     576           0 :       NS_ASSERTION(mEntries.GetEntry(entry->mPrevBlock)->mNextBlock == block,
     577             :                    "Bad next link");
     578           0 :       block = entry->mNextBlock;
     579           0 :       ++count;
     580           0 :     } while (block != mFirstBlock);
     581             :   }
     582           0 :   NS_ASSERTION(count == mCount, "Bad count");
     583           0 : }
     584             : #endif
     585             : 
     586           0 : static void UpdateSwappedBlockIndex(int32_t* aBlockIndex,
     587             :     int32_t aBlock1Index, int32_t aBlock2Index)
     588             : {
     589           0 :   int32_t index = *aBlockIndex;
     590           0 :   if (index == aBlock1Index) {
     591           0 :     *aBlockIndex = aBlock2Index;
     592           0 :   } else if (index == aBlock2Index) {
     593           0 :     *aBlockIndex = aBlock1Index;
     594             :   }
     595           0 : }
     596             : 
     597             : void
     598           0 : MediaCacheStream::BlockList::NotifyBlockSwapped(int32_t aBlockIndex1,
     599             :                                                   int32_t aBlockIndex2)
     600             : {
     601           0 :   Entry* e1 = mEntries.GetEntry(aBlockIndex1);
     602           0 :   Entry* e2 = mEntries.GetEntry(aBlockIndex2);
     603           0 :   int32_t e1Prev = -1, e1Next = -1, e2Prev = -1, e2Next = -1;
     604             : 
     605             :   // Fix mFirstBlock
     606           0 :   UpdateSwappedBlockIndex(&mFirstBlock, aBlockIndex1, aBlockIndex2);
     607             : 
     608             :   // Fix mNextBlock/mPrevBlock links. First capture previous/next links
     609             :   // so we don't get confused due to aliasing.
     610           0 :   if (e1) {
     611           0 :     e1Prev = e1->mPrevBlock;
     612           0 :     e1Next = e1->mNextBlock;
     613             :   }
     614           0 :   if (e2) {
     615           0 :     e2Prev = e2->mPrevBlock;
     616           0 :     e2Next = e2->mNextBlock;
     617             :   }
     618             :   // Update the entries.
     619           0 :   if (e1) {
     620           0 :     mEntries.GetEntry(e1Prev)->mNextBlock = aBlockIndex2;
     621           0 :     mEntries.GetEntry(e1Next)->mPrevBlock = aBlockIndex2;
     622             :   }
     623           0 :   if (e2) {
     624           0 :     mEntries.GetEntry(e2Prev)->mNextBlock = aBlockIndex1;
     625           0 :     mEntries.GetEntry(e2Next)->mPrevBlock = aBlockIndex1;
     626             :   }
     627             : 
     628             :   // Fix hashtable keys. First remove stale entries.
     629           0 :   if (e1) {
     630           0 :     e1Prev = e1->mPrevBlock;
     631           0 :     e1Next = e1->mNextBlock;
     632           0 :     mEntries.RemoveEntry(e1);
     633             :     // Refresh pointer after hashtable mutation.
     634           0 :     e2 = mEntries.GetEntry(aBlockIndex2);
     635             :   }
     636           0 :   if (e2) {
     637           0 :     e2Prev = e2->mPrevBlock;
     638           0 :     e2Next = e2->mNextBlock;
     639           0 :     mEntries.RemoveEntry(e2);
     640             :   }
     641             :   // Put new entries back.
     642           0 :   if (e1) {
     643           0 :     e1 = mEntries.PutEntry(aBlockIndex2);
     644           0 :     e1->mNextBlock = e1Next;
     645           0 :     e1->mPrevBlock = e1Prev;
     646             :   }
     647           0 :   if (e2) {
     648           0 :     e2 = mEntries.PutEntry(aBlockIndex1);
     649           0 :     e2->mNextBlock = e2Next;
     650           0 :     e2->mPrevBlock = e2Prev;
     651             :   }
     652           0 : }
     653             : 
     654             : void
     655           0 : MediaCache::Flush()
     656             : {
     657           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     658           0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     659             : 
     660           0 :   for (uint32_t blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
     661           0 :     FreeBlock(blockIndex);
     662             :   }
     663             : 
     664             :   // Truncate index array.
     665           0 :   Truncate();
     666           0 :   NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     667             :   // Re-initialize block cache.
     668           0 :   nsresult rv = mBlockCache->Init();
     669           0 :   NS_ENSURE_SUCCESS_VOID(rv);
     670             : }
     671             : 
     672             : void
     673           0 : MediaCache::CloseStreamsForPrivateBrowsing()
     674             : {
     675           0 :   MOZ_ASSERT(NS_IsMainThread());
     676           0 :   for (MediaCacheStream* s : mStreams) {
     677           0 :     if (s->mIsPrivateBrowsing) {
     678           0 :       s->Close();
     679             :     }
     680             :   }
     681           0 : }
     682             : 
     683             : /* static */ RefPtr<MediaCache>
     684           0 : MediaCache::GetMediaCache(int64_t aContentLength)
     685             : {
     686           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     687           0 :   if (aContentLength > 0 &&
     688           0 :       aContentLength <= int64_t(MediaPrefs::MediaMemoryCacheMaxSize()) * 1024) {
     689             :     // Small-enough resource, use a new memory-backed MediaCache.
     690           0 :     RefPtr<MediaBlockCacheBase> bc = new MemoryBlockCache(aContentLength);
     691           0 :     nsresult rv = bc->Init();
     692           0 :     if (NS_SUCCEEDED(rv)) {
     693           0 :       RefPtr<MediaCache> mc = new MediaCache(bc);
     694           0 :       LOG("GetMediaCache(%" PRIi64 ") -> Memory MediaCache %p",
     695             :           aContentLength,
     696             :           mc.get());
     697           0 :       return mc;
     698             :     }
     699             :     // MemoryBlockCache initialization failed, clean up and try for a
     700             :     // file-backed MediaCache below.
     701             :   }
     702             : 
     703           0 :   if (gMediaCache) {
     704           0 :     LOG("GetMediaCache(%" PRIi64 ") -> Existing file-backed MediaCache",
     705             :         aContentLength);
     706           0 :     return gMediaCache;
     707             :   }
     708             : 
     709           0 :   RefPtr<MediaBlockCacheBase> bc = new FileBlockCache();
     710           0 :   nsresult rv = bc->Init();
     711           0 :   if (NS_SUCCEEDED(rv)) {
     712           0 :     gMediaCache = new MediaCache(bc);
     713           0 :     LOG("GetMediaCache(%" PRIi64 ") -> Created file-backed MediaCache",
     714             :         aContentLength);
     715             :   } else {
     716           0 :     LOG("GetMediaCache(%" PRIi64 ") -> Failed to create file-backed MediaCache",
     717             :         aContentLength);
     718             :   }
     719             : 
     720           0 :   return gMediaCache;
     721             : }
     722             : 
     723             : nsresult
     724           0 : MediaCache::ReadCacheFile(
     725             :   int64_t aOffset, void* aData, int32_t aLength, int32_t* aBytes)
     726             : {
     727           0 :   mReentrantMonitor.AssertCurrentThreadIn();
     728           0 :   RefPtr<MediaBlockCacheBase> blockCache = mBlockCache;
     729           0 :   if (!blockCache) {
     730           0 :     return NS_ERROR_FAILURE;
     731             :   }
     732             :   {
     733             :     // Since the monitor might be acquired on the main thread, we need to drop
     734             :     // the monitor while doing IO in order not to block the main thread.
     735           0 :     ReentrantMonitorAutoExit unlock(mReentrantMonitor);
     736           0 :     return blockCache->Read(
     737           0 :       aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
     738             :   }
     739             : }
     740             : 
     741             : // Allowed range is whatever can be accessed with an int32_t block index.
     742             : static bool
     743           0 : IsOffsetAllowed(int64_t aOffset)
     744             : {
     745           0 :   return aOffset < (int64_t(INT32_MAX) + 1) * MediaCache::BLOCK_SIZE &&
     746           0 :          aOffset >= 0;
     747             : }
     748             : 
     749             : // Convert 64-bit offset to 32-bit block index.
     750             : // Assumes offset range-check was already done.
     751             : static int32_t
     752           0 : OffsetToBlockIndexUnchecked(int64_t aOffset)
     753             : {
     754             :   // Still check for allowed range in debug builds, to catch out-of-range
     755             :   // issues early during development.
     756           0 :   MOZ_ASSERT(IsOffsetAllowed(aOffset));
     757           0 :   return int32_t(aOffset / MediaCache::BLOCK_SIZE);
     758             : }
     759             : 
     760             : // Convert 64-bit offset to 32-bit block index. -1 if out of allowed range.
     761             : static int32_t
     762           0 : OffsetToBlockIndex(int64_t aOffset)
     763             : {
     764           0 :   return IsOffsetAllowed(aOffset) ? OffsetToBlockIndexUnchecked(aOffset) : -1;
     765             : }
     766             : 
     767             : // Convert 64-bit offset to 32-bit offset inside a block.
     768             : // Will not fail (even if offset is outside allowed range), so there is no
     769             : // need to check for errors.
     770             : static int32_t
     771           0 : OffsetInBlock(int64_t aOffset)
     772             : {
     773             :   // Still check for allowed range in debug builds, to catch out-of-range
     774             :   // issues early during development.
     775           0 :   MOZ_ASSERT(IsOffsetAllowed(aOffset));
     776           0 :   return int32_t(aOffset % MediaCache::BLOCK_SIZE);
     777             : }
     778             : 
     779             : int32_t
     780           0 : MediaCache::FindBlockForIncomingData(TimeStamp aNow,
     781             :                                        MediaCacheStream* aStream)
     782             : {
     783           0 :   mReentrantMonitor.AssertCurrentThreadIn();
     784             : 
     785             :   int32_t blockIndex =
     786           0 :     FindReusableBlock(aNow,
     787             :                       aStream,
     788             :                       OffsetToBlockIndexUnchecked(aStream->mChannelOffset),
     789           0 :                       INT32_MAX);
     790             : 
     791           0 :   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     792             :     // The block returned is already allocated.
     793             :     // Don't reuse it if a) there's room to expand the cache or
     794             :     // b) the data we're going to store in the free block is not higher
     795             :     // priority than the data already stored in the free block.
     796             :     // The latter can lead us to go over the cache limit a bit.
     797           0 :     if ((mIndex.Length() < uint32_t(mBlockCache->GetMaxBlocks()) ||
     798           0 :          blockIndex < 0 ||
     799           0 :          PredictNextUseForIncomingData(aStream) >=
     800           0 :            PredictNextUse(aNow, blockIndex))) {
     801           0 :       blockIndex = mIndex.Length();
     802           0 :       if (!mIndex.AppendElement())
     803           0 :         return -1;
     804           0 :       mIndexWatermark = std::max(mIndexWatermark, blockIndex + 1);
     805           0 :       mFreeBlocks.AddFirstBlock(blockIndex);
     806           0 :       return blockIndex;
     807             :     }
     808             :   }
     809             : 
     810           0 :   return blockIndex;
     811             : }
     812             : 
     813             : bool
     814           0 : MediaCache::BlockIsReusable(int32_t aBlockIndex)
     815             : {
     816           0 :   Block* block = &mIndex[aBlockIndex];
     817           0 :   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     818           0 :     MediaCacheStream* stream = block->mOwners[i].mStream;
     819           0 :     if (stream->mPinCount > 0 ||
     820           0 :         uint32_t(OffsetToBlockIndex(stream->mStreamOffset)) ==
     821           0 :           block->mOwners[i].mStreamBlock) {
     822           0 :       return false;
     823             :     }
     824             :   }
     825           0 :   return true;
     826             : }
     827             : 
     828             : void
     829           0 : MediaCache::AppendMostReusableBlock(BlockList* aBlockList,
     830             :                                       nsTArray<uint32_t>* aResult,
     831             :                                       int32_t aBlockIndexLimit)
     832             : {
     833           0 :   mReentrantMonitor.AssertCurrentThreadIn();
     834             : 
     835           0 :   int32_t blockIndex = aBlockList->GetLastBlock();
     836           0 :   if (blockIndex < 0)
     837           0 :     return;
     838           0 :   do {
     839             :     // Don't consider blocks for pinned streams, or blocks that are
     840             :     // beyond the specified limit, or a block that contains a stream's
     841             :     // current read position (such a block contains both played data
     842             :     // and readahead data)
     843           0 :     if (blockIndex < aBlockIndexLimit && BlockIsReusable(blockIndex)) {
     844           0 :       aResult->AppendElement(blockIndex);
     845           0 :       return;
     846             :     }
     847           0 :     blockIndex = aBlockList->GetPrevBlock(blockIndex);
     848           0 :   } while (blockIndex >= 0);
     849             : }
     850             : 
     851             : int32_t
     852           0 : MediaCache::FindReusableBlock(TimeStamp aNow,
     853             :                                 MediaCacheStream* aForStream,
     854             :                                 int32_t aForStreamBlock,
     855             :                                 int32_t aMaxSearchBlockIndex)
     856             : {
     857           0 :   mReentrantMonitor.AssertCurrentThreadIn();
     858             : 
     859           0 :   uint32_t length = std::min(uint32_t(aMaxSearchBlockIndex), uint32_t(mIndex.Length()));
     860             : 
     861           0 :   if (aForStream && aForStreamBlock > 0 &&
     862           0 :       uint32_t(aForStreamBlock) <= aForStream->mBlocks.Length()) {
     863           0 :     int32_t prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
     864           0 :     if (prevCacheBlock >= 0) {
     865             :       uint32_t freeBlockScanEnd =
     866           0 :         std::min(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
     867           0 :       for (uint32_t i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
     868           0 :         if (IsBlockFree(i))
     869           0 :           return i;
     870             :       }
     871             :     }
     872             :   }
     873             : 
     874           0 :   if (!mFreeBlocks.IsEmpty()) {
     875           0 :     int32_t blockIndex = mFreeBlocks.GetFirstBlock();
     876           0 :     do {
     877           0 :       if (blockIndex < aMaxSearchBlockIndex)
     878           0 :         return blockIndex;
     879           0 :       blockIndex = mFreeBlocks.GetNextBlock(blockIndex);
     880           0 :     } while (blockIndex >= 0);
     881             :   }
     882             : 
     883             :   // Build a list of the blocks we should consider for the "latest
     884             :   // predicted time of next use". We can exploit the fact that the block
     885             :   // linked lists are ordered by increasing time of next use. This is
     886             :   // actually the whole point of having the linked lists.
     887           0 :   AutoTArray<uint32_t,8> candidates;
     888           0 :   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     889           0 :     MediaCacheStream* stream = mStreams[i];
     890           0 :     if (stream->mPinCount > 0) {
     891             :       // No point in even looking at this stream's blocks
     892           0 :       continue;
     893             :     }
     894             : 
     895           0 :     AppendMostReusableBlock(&stream->mMetadataBlocks, &candidates, length);
     896           0 :     AppendMostReusableBlock(&stream->mPlayedBlocks, &candidates, length);
     897             : 
     898             :     // Don't consider readahead blocks in non-seekable streams. If we
     899             :     // remove the block we won't be able to seek back to read it later.
     900           0 :     if (stream->mIsTransportSeekable) {
     901           0 :       AppendMostReusableBlock(&stream->mReadaheadBlocks, &candidates, length);
     902             :     }
     903             :   }
     904             : 
     905           0 :   TimeDuration latestUse;
     906           0 :   int32_t latestUseBlock = -1;
     907           0 :   for (uint32_t i = 0; i < candidates.Length(); ++i) {
     908           0 :     TimeDuration nextUse = PredictNextUse(aNow, candidates[i]);
     909           0 :     if (nextUse > latestUse) {
     910           0 :       latestUse = nextUse;
     911           0 :       latestUseBlock = candidates[i];
     912             :     }
     913             :   }
     914             : 
     915           0 :   return latestUseBlock;
     916             : }
     917             : 
     918             : MediaCache::BlockList*
     919           0 : MediaCache::GetListForBlock(BlockOwner* aBlock)
     920             : {
     921           0 :   switch (aBlock->mClass) {
     922             :   case METADATA_BLOCK:
     923           0 :     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     924           0 :     return &aBlock->mStream->mMetadataBlocks;
     925             :   case PLAYED_BLOCK:
     926           0 :     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     927           0 :     return &aBlock->mStream->mPlayedBlocks;
     928             :   case READAHEAD_BLOCK:
     929           0 :     NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
     930           0 :     return &aBlock->mStream->mReadaheadBlocks;
     931             :   default:
     932           0 :     NS_ERROR("Invalid block class");
     933           0 :     return nullptr;
     934             :   }
     935             : }
     936             : 
     937             : MediaCache::BlockOwner*
     938           0 : MediaCache::GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
     939             : {
     940           0 :   Block* block = &mIndex[aBlockIndex];
     941           0 :   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     942           0 :     if (block->mOwners[i].mStream == aStream)
     943           0 :       return &block->mOwners[i];
     944             :   }
     945           0 :   return nullptr;
     946             : }
     947             : 
     948             : void
     949           0 : MediaCache::SwapBlocks(int32_t aBlockIndex1, int32_t aBlockIndex2)
     950             : {
     951           0 :   mReentrantMonitor.AssertCurrentThreadIn();
     952             : 
     953           0 :   Block* block1 = &mIndex[aBlockIndex1];
     954           0 :   Block* block2 = &mIndex[aBlockIndex2];
     955             : 
     956           0 :   block1->mOwners.SwapElements(block2->mOwners);
     957             : 
     958             :   // Now all references to block1 have to be replaced with block2 and
     959             :   // vice versa.
     960             :   // First update stream references to blocks via mBlocks.
     961           0 :   const Block* blocks[] = { block1, block2 };
     962           0 :   int32_t blockIndices[] = { aBlockIndex1, aBlockIndex2 };
     963           0 :   for (int32_t i = 0; i < 2; ++i) {
     964           0 :     for (uint32_t j = 0; j < blocks[i]->mOwners.Length(); ++j) {
     965           0 :       const BlockOwner* b = &blocks[i]->mOwners[j];
     966           0 :       b->mStream->mBlocks[b->mStreamBlock] = blockIndices[i];
     967             :     }
     968             :   }
     969             : 
     970             :   // Now update references to blocks in block lists.
     971           0 :   mFreeBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     972             : 
     973           0 :   nsTHashtable<nsPtrHashKey<MediaCacheStream> > visitedStreams;
     974             : 
     975           0 :   for (int32_t i = 0; i < 2; ++i) {
     976           0 :     for (uint32_t j = 0; j < blocks[i]->mOwners.Length(); ++j) {
     977           0 :       MediaCacheStream* stream = blocks[i]->mOwners[j].mStream;
     978             :       // Make sure that we don't update the same stream twice --- that
     979             :       // would result in swapping the block references back again!
     980           0 :       if (visitedStreams.GetEntry(stream))
     981           0 :         continue;
     982           0 :       visitedStreams.PutEntry(stream);
     983           0 :       stream->mReadaheadBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     984           0 :       stream->mPlayedBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     985           0 :       stream->mMetadataBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     986             :     }
     987             :   }
     988             : 
     989           0 :   Verify();
     990           0 : }
     991             : 
     992             : void
     993           0 : MediaCache::RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
     994             : {
     995           0 :   Block* block = &mIndex[aBlockIndex];
     996           0 :   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
     997           0 :     BlockOwner* bo = &block->mOwners[i];
     998           0 :     if (bo->mStream == aStream) {
     999           0 :       GetListForBlock(bo)->RemoveBlock(aBlockIndex);
    1000           0 :       bo->mStream->mBlocks[bo->mStreamBlock] = -1;
    1001           0 :       block->mOwners.RemoveElementAt(i);
    1002           0 :       if (block->mOwners.IsEmpty()) {
    1003           0 :         mFreeBlocks.AddFirstBlock(aBlockIndex);
    1004             :       }
    1005           0 :       return;
    1006             :     }
    1007             :   }
    1008             : }
    1009             : 
    1010             : void
    1011           0 : MediaCache::AddBlockOwnerAsReadahead(int32_t aBlockIndex,
    1012             :                                        MediaCacheStream* aStream,
    1013             :                                        int32_t aStreamBlockIndex)
    1014             : {
    1015           0 :   Block* block = &mIndex[aBlockIndex];
    1016           0 :   if (block->mOwners.IsEmpty()) {
    1017           0 :     mFreeBlocks.RemoveBlock(aBlockIndex);
    1018             :   }
    1019           0 :   BlockOwner* bo = block->mOwners.AppendElement();
    1020           0 :   mBlockOwnersWatermark =
    1021           0 :     std::max(mBlockOwnersWatermark, uint32_t(block->mOwners.Length()));
    1022           0 :   bo->mStream = aStream;
    1023           0 :   bo->mStreamBlock = aStreamBlockIndex;
    1024           0 :   aStream->mBlocks[aStreamBlockIndex] = aBlockIndex;
    1025           0 :   bo->mClass = READAHEAD_BLOCK;
    1026           0 :   InsertReadaheadBlock(bo, aBlockIndex);
    1027           0 : }
    1028             : 
    1029             : void
    1030           0 : MediaCache::FreeBlock(int32_t aBlock)
    1031             : {
    1032           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1033             : 
    1034           0 :   Block* block = &mIndex[aBlock];
    1035           0 :   if (block->mOwners.IsEmpty()) {
    1036             :     // already free
    1037           0 :     return;
    1038             :   }
    1039             : 
    1040           0 :   LOG("Released block %d", aBlock);
    1041             : 
    1042           0 :   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
    1043           0 :     BlockOwner* bo = &block->mOwners[i];
    1044           0 :     GetListForBlock(bo)->RemoveBlock(aBlock);
    1045           0 :     bo->mStream->mBlocks[bo->mStreamBlock] = -1;
    1046             :   }
    1047           0 :   block->mOwners.Clear();
    1048           0 :   mFreeBlocks.AddFirstBlock(aBlock);
    1049           0 :   Verify();
    1050             : }
    1051             : 
    1052             : TimeDuration
    1053           0 : MediaCache::PredictNextUse(TimeStamp aNow, int32_t aBlock)
    1054             : {
    1055           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1056           0 :   NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
    1057             : 
    1058           0 :   Block* block = &mIndex[aBlock];
    1059             :   // Blocks can be belong to multiple streams. The predicted next use
    1060             :   // time is the earliest time predicted by any of the streams.
    1061           0 :   TimeDuration result;
    1062           0 :   for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
    1063           0 :     BlockOwner* bo = &block->mOwners[i];
    1064           0 :     TimeDuration prediction;
    1065           0 :     switch (bo->mClass) {
    1066             :     case METADATA_BLOCK:
    1067             :       // This block should be managed in LRU mode. For metadata we predict
    1068             :       // that the time until the next use is the time since the last use.
    1069           0 :       prediction = aNow - bo->mLastUseTime;
    1070           0 :       break;
    1071             :     case PLAYED_BLOCK: {
    1072             :       // This block should be managed in LRU mode, and we should impose
    1073             :       // a "replay delay" to reflect the likelihood of replay happening
    1074           0 :       NS_ASSERTION(static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE <
    1075             :                    bo->mStream->mStreamOffset,
    1076             :                    "Played block after the current stream position?");
    1077             :       int64_t bytesBehind =
    1078           0 :         bo->mStream->mStreamOffset - static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE;
    1079             :       int64_t millisecondsBehind =
    1080           0 :         bytesBehind*1000/bo->mStream->mPlaybackBytesPerSecond;
    1081             :       prediction = TimeDuration::FromMilliseconds(
    1082           0 :           std::min<int64_t>(millisecondsBehind*REPLAY_PENALTY_FACTOR, INT32_MAX));
    1083           0 :       break;
    1084             :     }
    1085             :     case READAHEAD_BLOCK: {
    1086             :       int64_t bytesAhead =
    1087           0 :         static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE - bo->mStream->mStreamOffset;
    1088           0 :       NS_ASSERTION(bytesAhead >= 0,
    1089             :                    "Readahead block before the current stream position?");
    1090             :       int64_t millisecondsAhead =
    1091           0 :         bytesAhead*1000/bo->mStream->mPlaybackBytesPerSecond;
    1092             :       prediction = TimeDuration::FromMilliseconds(
    1093           0 :           std::min<int64_t>(millisecondsAhead, INT32_MAX));
    1094           0 :       break;
    1095             :     }
    1096             :     default:
    1097           0 :       NS_ERROR("Invalid class for predicting next use");
    1098           0 :       return TimeDuration(0);
    1099             :     }
    1100           0 :     if (i == 0 || prediction < result) {
    1101           0 :       result = prediction;
    1102             :     }
    1103             :   }
    1104           0 :   return result;
    1105             : }
    1106             : 
    1107             : TimeDuration
    1108           0 : MediaCache::PredictNextUseForIncomingData(MediaCacheStream* aStream)
    1109             : {
    1110           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1111             : 
    1112           0 :   int64_t bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
    1113           0 :   if (bytesAhead <= -BLOCK_SIZE) {
    1114             :     // Hmm, no idea when data behind us will be used. Guess 24 hours.
    1115           0 :     return TimeDuration::FromSeconds(24*60*60);
    1116             :   }
    1117           0 :   if (bytesAhead <= 0)
    1118           0 :     return TimeDuration(0);
    1119           0 :   int64_t millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond;
    1120             :   return TimeDuration::FromMilliseconds(
    1121           0 :       std::min<int64_t>(millisecondsAhead, INT32_MAX));
    1122             : }
    1123             : 
    1124             : enum StreamAction { NONE, SEEK, SEEK_AND_RESUME, RESUME, SUSPEND };
    1125             : 
    1126             : void
    1127           0 : MediaCache::Update()
    1128             : {
    1129           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1130             : 
    1131             :   // The action to use for each stream. We store these so we can make
    1132             :   // decisions while holding the cache lock but implement those decisions
    1133             :   // without holding the cache lock, since we need to call out to
    1134             :   // stream, decoder and element code.
    1135           0 :   AutoTArray<StreamAction,10> actions;
    1136             : 
    1137             :   {
    1138           0 :     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1139           0 :     mUpdateQueued = false;
    1140             : #ifdef DEBUG
    1141           0 :     mInUpdate = true;
    1142             : #endif
    1143             : 
    1144           0 :     int32_t maxBlocks = mBlockCache->GetMaxBlocks();
    1145           0 :     TimeStamp now = TimeStamp::Now();
    1146             : 
    1147           0 :     int32_t freeBlockCount = mFreeBlocks.GetCount();
    1148           0 :     TimeDuration latestPredictedUseForOverflow = 0;
    1149           0 :     if (mIndex.Length() > uint32_t(maxBlocks)) {
    1150             :       // Try to trim back the cache to its desired maximum size. The cache may
    1151             :       // have overflowed simply due to data being received when we have
    1152             :       // no blocks in the main part of the cache that are free or lower
    1153             :       // priority than the new data. The cache can also be overflowing because
    1154             :       // the media.cache_size preference was reduced.
    1155             :       // First, figure out what the least valuable block in the cache overflow
    1156             :       // is. We don't want to replace any blocks in the main part of the
    1157             :       // cache whose expected time of next use is earlier or equal to that.
    1158             :       // If we allow that, we can effectively end up discarding overflowing
    1159             :       // blocks (by moving an overflowing block to the main part of the cache,
    1160             :       // and then overwriting it with another overflowing block), and we try
    1161             :       // to avoid that since it requires HTTP seeks.
    1162             :       // We also use this loop to eliminate overflowing blocks from
    1163             :       // freeBlockCount.
    1164           0 :       for (int32_t blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
    1165             :            --blockIndex) {
    1166           0 :         if (IsBlockFree(blockIndex)) {
    1167             :           // Don't count overflowing free blocks in our free block count
    1168           0 :           --freeBlockCount;
    1169           0 :           continue;
    1170             :         }
    1171           0 :         TimeDuration predictedUse = PredictNextUse(now, blockIndex);
    1172           0 :         latestPredictedUseForOverflow = std::max(latestPredictedUseForOverflow, predictedUse);
    1173             :       }
    1174             :     } else {
    1175           0 :       freeBlockCount += maxBlocks - mIndex.Length();
    1176             :     }
    1177             : 
    1178             :     // Now try to move overflowing blocks to the main part of the cache.
    1179           0 :     for (int32_t blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
    1180             :          --blockIndex) {
    1181           0 :       if (IsBlockFree(blockIndex))
    1182           0 :         continue;
    1183             : 
    1184           0 :       Block* block = &mIndex[blockIndex];
    1185             :       // Try to relocate the block close to other blocks for the first stream.
    1186             :       // There is no point in trying to make it close to other blocks in
    1187             :       // *all* the streams it might belong to.
    1188             :       int32_t destinationBlockIndex =
    1189           0 :         FindReusableBlock(now, block->mOwners[0].mStream,
    1190           0 :                           block->mOwners[0].mStreamBlock, maxBlocks);
    1191           0 :       if (destinationBlockIndex < 0) {
    1192             :         // Nowhere to place this overflow block. We won't be able to
    1193             :         // place any more overflow blocks.
    1194           0 :         break;
    1195             :       }
    1196             : 
    1197           0 :       if (IsBlockFree(destinationBlockIndex) ||
    1198           0 :           PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
    1199             :         // Reuse blocks in the main part of the cache that are less useful than
    1200             :         // the least useful overflow blocks
    1201             : 
    1202           0 :         nsresult rv = mBlockCache->MoveBlock(blockIndex, destinationBlockIndex);
    1203             : 
    1204           0 :         if (NS_SUCCEEDED(rv)) {
    1205             :           // We successfully copied the file data.
    1206           0 :           LOG("Swapping blocks %d and %d (trimming cache)",
    1207             :               blockIndex, destinationBlockIndex);
    1208             :           // Swapping the block metadata here lets us maintain the
    1209             :           // correct positions in the linked lists
    1210           0 :           SwapBlocks(blockIndex, destinationBlockIndex);
    1211             :           //Free the overflowing block even if the copy failed.
    1212           0 :           LOG("Released block %d (trimming cache)", blockIndex);
    1213           0 :           FreeBlock(blockIndex);
    1214             :         }
    1215             :       } else {
    1216           0 :         LOG("Could not trim cache block %d (destination %d, "
    1217             :             "predicted next use %f, latest predicted use for overflow %f",
    1218             :             blockIndex, destinationBlockIndex,
    1219             :             PredictNextUse(now, destinationBlockIndex).ToSeconds(),
    1220             :             latestPredictedUseForOverflow.ToSeconds());
    1221             :       }
    1222             :     }
    1223             :     // Try chopping back the array of cache entries and the cache file.
    1224           0 :     Truncate();
    1225             : 
    1226             :     // Count the blocks allocated for readahead of non-seekable streams
    1227             :     // (these blocks can't be freed but we don't want them to monopolize the
    1228             :     // cache)
    1229           0 :     int32_t nonSeekableReadaheadBlockCount = 0;
    1230           0 :     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
    1231           0 :       MediaCacheStream* stream = mStreams[i];
    1232           0 :       if (!stream->mIsTransportSeekable) {
    1233           0 :         nonSeekableReadaheadBlockCount += stream->mReadaheadBlocks.GetCount();
    1234             :       }
    1235             :     }
    1236             : 
    1237             :     // If freeBlockCount is zero, then compute the latest of
    1238             :     // the predicted next-uses for all blocks
    1239           0 :     TimeDuration latestNextUse;
    1240           0 :     if (freeBlockCount == 0) {
    1241           0 :       int32_t reusableBlock = FindReusableBlock(now, nullptr, 0, maxBlocks);
    1242           0 :       if (reusableBlock >= 0) {
    1243           0 :         latestNextUse = PredictNextUse(now, reusableBlock);
    1244             :       }
    1245             :     }
    1246             : 
    1247           0 :     int32_t resumeThreshold = Preferences::GetInt("media.cache_resume_threshold", 10);
    1248           0 :     int32_t readaheadLimit = Preferences::GetInt("media.cache_readahead_limit", 30);
    1249             : 
    1250           0 :     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
    1251           0 :       actions.AppendElement(NONE);
    1252             : 
    1253           0 :       MediaCacheStream* stream = mStreams[i];
    1254           0 :       if (stream->mClosed) {
    1255           0 :         LOG("Stream %p closed", stream);
    1256           0 :         continue;
    1257             :       }
    1258             : 
    1259             :       // Figure out where we should be reading from. It's the first
    1260             :       // uncached byte after the current mStreamOffset.
    1261           0 :       int64_t dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
    1262           0 :       MOZ_ASSERT(dataOffset >= 0);
    1263             : 
    1264             :       // Compute where we'd actually seek to to read at readOffset
    1265           0 :       int64_t desiredOffset = dataOffset;
    1266           0 :       if (stream->mIsTransportSeekable) {
    1267           0 :         if (desiredOffset > stream->mChannelOffset &&
    1268           0 :             desiredOffset <= stream->mChannelOffset + SEEK_VS_READ_THRESHOLD) {
    1269             :           // Assume it's more efficient to just keep reading up to the
    1270             :           // desired position instead of trying to seek
    1271           0 :           desiredOffset = stream->mChannelOffset;
    1272             :         }
    1273             :       } else {
    1274             :         // We can't seek directly to the desired offset...
    1275           0 :         if (stream->mChannelOffset > desiredOffset) {
    1276             :           // Reading forward won't get us anywhere, we need to go backwards.
    1277             :           // Seek back to 0 (the client will reopen the stream) and then
    1278             :           // read forward.
    1279           0 :           NS_WARNING("Can't seek backwards, so seeking to 0");
    1280           0 :           desiredOffset = 0;
    1281             :           // Flush cached blocks out, since if this is a live stream
    1282             :           // the cached data may be completely different next time we
    1283             :           // read it. We have to assume that live streams don't
    1284             :           // advertise themselves as being seekable...
    1285           0 :           ReleaseStreamBlocks(stream);
    1286             :         } else {
    1287             :           // otherwise reading forward is looking good, so just stay where we
    1288             :           // are and don't trigger a channel seek!
    1289           0 :           desiredOffset = stream->mChannelOffset;
    1290             :         }
    1291             :       }
    1292             : 
    1293             :       // Figure out if we should be reading data now or not. It's amazing
    1294             :       // how complex this is, but each decision is simple enough.
    1295             :       bool enableReading;
    1296           0 :       if (stream->mStreamLength >= 0 && dataOffset >= stream->mStreamLength) {
    1297             :         // We want data at the end of the stream, where there's nothing to
    1298             :         // read. We don't want to try to read if we're suspended, because that
    1299             :         // might create a new channel and seek unnecessarily (and incorrectly,
    1300             :         // since HTTP doesn't allow seeking to the actual EOF), and we don't want
    1301             :         // to suspend if we're not suspended and already reading at the end of
    1302             :         // the stream, since there just might be more data than the server
    1303             :         // advertised with Content-Length, and we may as well keep reading.
    1304             :         // But we don't want to seek to the end of the stream if we're not
    1305             :         // already there.
    1306           0 :         LOG("Stream %p at end of stream", stream);
    1307           0 :         enableReading = !stream->mCacheSuspended &&
    1308           0 :           stream->mStreamLength == stream->mChannelOffset;
    1309           0 :       } else if (desiredOffset < stream->mStreamOffset) {
    1310             :         // We're reading to try to catch up to where the current stream
    1311             :         // reader wants to be. Better not stop.
    1312           0 :         LOG("Stream %p catching up", stream);
    1313           0 :         enableReading = true;
    1314           0 :       } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) {
    1315             :         // The stream reader is waiting for us, or nearly so. Better feed it.
    1316           0 :         LOG("Stream %p feeding reader", stream);
    1317           0 :         enableReading = true;
    1318           0 :       } else if (!stream->mIsTransportSeekable &&
    1319           0 :                  nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) {
    1320             :         // This stream is not seekable and there are already too many blocks
    1321             :         // being cached for readahead for nonseekable streams (which we can't
    1322             :         // free). So stop reading ahead now.
    1323           0 :         LOG("Stream %p throttling non-seekable readahead", stream);
    1324           0 :         enableReading = false;
    1325           0 :       } else if (mIndex.Length() > uint32_t(maxBlocks)) {
    1326             :         // We're in the process of bringing the cache size back to the
    1327             :         // desired limit, so don't bring in more data yet
    1328           0 :         LOG("Stream %p throttling to reduce cache size", stream);
    1329           0 :         enableReading = false;
    1330             :       } else {
    1331           0 :         TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream);
    1332             : 
    1333           0 :         if (stream->mThrottleReadahead &&
    1334           0 :             stream->mCacheSuspended &&
    1335           0 :             predictedNewDataUse.ToSeconds() > resumeThreshold) {
    1336             :           // Don't need data for a while, so don't bother waking up the stream
    1337           0 :           LOG("Stream %p avoiding wakeup since more data is not needed", stream);
    1338           0 :           enableReading = false;
    1339           0 :         } else if (stream->mThrottleReadahead &&
    1340           0 :                    predictedNewDataUse.ToSeconds() > readaheadLimit) {
    1341             :           // Don't read ahead more than this much
    1342           0 :           LOG("Stream %p throttling to avoid reading ahead too far", stream);
    1343           0 :           enableReading = false;
    1344           0 :         } else if (freeBlockCount > 0) {
    1345             :           // Free blocks in the cache, so keep reading
    1346           0 :           LOG("Stream %p reading since there are free blocks", stream);
    1347           0 :           enableReading = true;
    1348           0 :         } else if (latestNextUse <= TimeDuration(0)) {
    1349             :           // No reusable blocks, so can't read anything
    1350           0 :           LOG("Stream %p throttling due to no reusable blocks", stream);
    1351           0 :           enableReading = false;
    1352             :         } else {
    1353             :           // Read ahead if the data we expect to read is more valuable than
    1354             :           // the least valuable block in the main part of the cache
    1355           0 :           LOG("Stream %p predict next data in %f, current worst block is %f",
    1356             :               stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds());
    1357           0 :           enableReading = predictedNewDataUse < latestNextUse;
    1358             :         }
    1359             :       }
    1360             : 
    1361           0 :       if (enableReading) {
    1362           0 :         for (uint32_t j = 0; j < i; ++j) {
    1363           0 :           MediaCacheStream* other = mStreams[j];
    1364           0 :           if (other->mResourceID == stream->mResourceID && !other->mClosed &&
    1365           0 :               !other->mClient->IsSuspended() &&
    1366           0 :               OffsetToBlockIndexUnchecked(other->mChannelOffset) ==
    1367           0 :                 OffsetToBlockIndexUnchecked(desiredOffset)) {
    1368             :             // This block is already going to be read by the other stream.
    1369             :             // So don't try to read it from this stream as well.
    1370           0 :             enableReading = false;
    1371           0 :             LOG("Stream %p waiting on same block (%" PRId32 ") from stream %p",
    1372             :                 stream,
    1373             :                 OffsetToBlockIndexUnchecked(desiredOffset),
    1374             :                 other);
    1375           0 :             break;
    1376             :           }
    1377             :         }
    1378             :       }
    1379             : 
    1380           0 :       if (stream->mChannelOffset != desiredOffset && enableReading) {
    1381             :         // We need to seek now.
    1382           0 :         NS_ASSERTION(stream->mIsTransportSeekable || desiredOffset == 0,
    1383             :                      "Trying to seek in a non-seekable stream!");
    1384             :         // Round seek offset down to the start of the block. This is essential
    1385             :         // because we don't want to think we have part of a block already
    1386             :         // in mPartialBlockBuffer.
    1387           0 :         stream->mChannelOffset =
    1388           0 :           OffsetToBlockIndexUnchecked(desiredOffset) * BLOCK_SIZE;
    1389           0 :         actions[i] = stream->mCacheSuspended ? SEEK_AND_RESUME : SEEK;
    1390           0 :       } else if (enableReading && stream->mCacheSuspended) {
    1391           0 :         actions[i] = RESUME;
    1392           0 :       } else if (!enableReading && !stream->mCacheSuspended) {
    1393           0 :         actions[i] = SUSPEND;
    1394             :       }
    1395             :     }
    1396             : #ifdef DEBUG
    1397           0 :     mInUpdate = false;
    1398             : #endif
    1399             :   }
    1400             : 
    1401             :   // Update the channel state without holding our cache lock. While we're
    1402             :   // doing this, decoder threads may be running and seeking, reading or changing
    1403             :   // other cache state. That's OK, they'll trigger new Update events and we'll
    1404             :   // get back here and revise our decisions. The important thing here is that
    1405             :   // performing these actions only depends on mChannelOffset and
    1406             :   // the action, which can only be written by the main thread (i.e., this
    1407             :   // thread), so we don't have races here.
    1408             : 
    1409             :   // First, update the mCacheSuspended/mCacheEnded flags so that they're all correct
    1410             :   // when we fire our CacheClient commands below. Those commands can rely on these flags
    1411             :   // being set correctly for all streams.
    1412           0 :   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
    1413           0 :     MediaCacheStream* stream = mStreams[i];
    1414           0 :     switch (actions[i]) {
    1415             :     case SEEK:
    1416             :         case SEEK_AND_RESUME:
    1417           0 :       stream->mCacheSuspended = false;
    1418           0 :       stream->mChannelEnded = false;
    1419           0 :       break;
    1420             :     case RESUME:
    1421           0 :       stream->mCacheSuspended = false;
    1422           0 :       break;
    1423             :     case SUSPEND:
    1424           0 :       stream->mCacheSuspended = true;
    1425           0 :       break;
    1426             :     default:
    1427           0 :       break;
    1428             :     }
    1429           0 :     stream->mHasHadUpdate = true;
    1430             :   }
    1431             : 
    1432           0 :   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
    1433           0 :     MediaCacheStream* stream = mStreams[i];
    1434             :     nsresult rv;
    1435           0 :     switch (actions[i]) {
    1436             :     case SEEK:
    1437             :         case SEEK_AND_RESUME:
    1438           0 :       LOG("Stream %p CacheSeek to %" PRId64 " (resume=%d)", stream,
    1439             :           stream->mChannelOffset, actions[i] == SEEK_AND_RESUME);
    1440           0 :       rv = stream->mClient->CacheClientSeek(stream->mChannelOffset,
    1441           0 :                                             actions[i] == SEEK_AND_RESUME);
    1442           0 :       break;
    1443             :     case RESUME:
    1444           0 :       LOG("Stream %p Resumed", stream);
    1445           0 :       rv = stream->mClient->CacheClientResume();
    1446           0 :       QueueSuspendedStatusUpdate(stream->mResourceID);
    1447           0 :       break;
    1448             :     case SUSPEND:
    1449           0 :       LOG("Stream %p Suspended", stream);
    1450           0 :       rv = stream->mClient->CacheClientSuspend();
    1451           0 :       QueueSuspendedStatusUpdate(stream->mResourceID);
    1452           0 :       break;
    1453             :     default:
    1454           0 :       rv = NS_OK;
    1455           0 :       break;
    1456             :     }
    1457             : 
    1458           0 :     if (NS_FAILED(rv)) {
    1459             :       // Close the streams that failed due to error. This will cause all
    1460             :       // client Read and Seek operations on those streams to fail. Blocked
    1461             :       // Reads will also be woken up.
    1462           0 :       ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1463           0 :       stream->CloseInternal(mon);
    1464             :     }
    1465             :   }
    1466             : 
    1467             :   // Notify streams about the suspended status changes.
    1468           0 :   for (uint32_t i = 0; i < mSuspendedStatusToNotify.Length(); ++i) {
    1469           0 :     MediaCache::ResourceStreamIterator iter(this, mSuspendedStatusToNotify[i]);
    1470           0 :     while (MediaCacheStream* stream = iter.Next()) {
    1471           0 :       stream->mClient->CacheClientNotifySuspendedStatusChanged();
    1472           0 :     }
    1473             :   }
    1474           0 :   mSuspendedStatusToNotify.Clear();
    1475           0 : }
    1476             : 
    1477           0 : class UpdateEvent : public Runnable
    1478             : {
    1479             : public:
    1480           0 :   explicit UpdateEvent(MediaCache* aMediaCache)
    1481           0 :     : Runnable("MediaCache::UpdateEvent")
    1482           0 :     , mMediaCache(aMediaCache)
    1483             :   {
    1484           0 :   }
    1485             : 
    1486           0 :   NS_IMETHOD Run() override
    1487             :   {
    1488           0 :     mMediaCache->Update();
    1489           0 :     return NS_OK;
    1490             :   }
    1491             : 
    1492             : private:
    1493             :   RefPtr<MediaCache> mMediaCache;
    1494             : };
    1495             : 
    1496             : void
    1497           0 : MediaCache::QueueUpdate()
    1498             : {
    1499           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1500             : 
    1501             :   // Queuing an update while we're in an update raises a high risk of
    1502             :   // triggering endless events
    1503           0 :   NS_ASSERTION(!mInUpdate,
    1504             :                "Queuing an update while we're in an update");
    1505           0 :   if (mUpdateQueued)
    1506           0 :     return;
    1507           0 :   mUpdateQueued = true;
    1508             :   // XXX MediaCache does updates when decoders are still running at
    1509             :   // shutdown and get freed in the final cycle-collector cleanup.  So
    1510             :   // don't leak a runnable in that case.
    1511           0 :   nsCOMPtr<nsIRunnable> event = new UpdateEvent(this);
    1512             :   SystemGroup::Dispatch("MediaCache::UpdateEvent",
    1513             :                         TaskCategory::Other,
    1514           0 :                         event.forget());
    1515             : }
    1516             : 
    1517             : void
    1518           0 : MediaCache::QueueSuspendedStatusUpdate(int64_t aResourceID)
    1519             : {
    1520           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1521           0 :   if (!mSuspendedStatusToNotify.Contains(aResourceID)) {
    1522           0 :     mSuspendedStatusToNotify.AppendElement(aResourceID);
    1523             :   }
    1524           0 : }
    1525             : 
    1526             : #ifdef DEBUG_VERIFY_CACHE
    1527             : void
    1528             : MediaCache::Verify()
    1529             : {
    1530             :   mReentrantMonitor.AssertCurrentThreadIn();
    1531             : 
    1532             :   mFreeBlocks.Verify();
    1533             :   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
    1534             :     MediaCacheStream* stream = mStreams[i];
    1535             :     stream->mReadaheadBlocks.Verify();
    1536             :     stream->mPlayedBlocks.Verify();
    1537             :     stream->mMetadataBlocks.Verify();
    1538             : 
    1539             :     // Verify that the readahead blocks are listed in stream block order
    1540             :     int32_t block = stream->mReadaheadBlocks.GetFirstBlock();
    1541             :     int32_t lastStreamBlock = -1;
    1542             :     while (block >= 0) {
    1543             :       uint32_t j = 0;
    1544             :       while (mIndex[block].mOwners[j].mStream != stream) {
    1545             :         ++j;
    1546             :       }
    1547             :       int32_t nextStreamBlock =
    1548             :         int32_t(mIndex[block].mOwners[j].mStreamBlock);
    1549             :       NS_ASSERTION(lastStreamBlock < nextStreamBlock,
    1550             :                    "Blocks not increasing in readahead stream");
    1551             :       lastStreamBlock = nextStreamBlock;
    1552             :       block = stream->mReadaheadBlocks.GetNextBlock(block);
    1553             :     }
    1554             :   }
    1555             : }
    1556             : #endif
    1557             : 
    1558             : void
    1559           0 : MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
    1560             :                                    int32_t aBlockIndex)
    1561             : {
    1562           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1563             : 
    1564             :   // Find the last block whose stream block is before aBlockIndex's
    1565             :   // stream block, and insert after it
    1566           0 :   MediaCacheStream* stream = aBlockOwner->mStream;
    1567           0 :   int32_t readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
    1568           0 :   while (readaheadIndex >= 0) {
    1569           0 :     BlockOwner* bo = GetBlockOwner(readaheadIndex, stream);
    1570           0 :     NS_ASSERTION(bo, "stream must own its blocks");
    1571           0 :     if (bo->mStreamBlock < aBlockOwner->mStreamBlock) {
    1572           0 :       stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
    1573           0 :       return;
    1574             :     }
    1575           0 :     NS_ASSERTION(bo->mStreamBlock > aBlockOwner->mStreamBlock,
    1576             :                  "Duplicated blocks??");
    1577           0 :     readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex);
    1578             :   }
    1579             : 
    1580           0 :   stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
    1581           0 :   Verify();
    1582             : }
    1583             : 
    1584             : void
    1585           0 : MediaCache::AllocateAndWriteBlock(
    1586             :   MediaCacheStream* aStream, MediaCacheStream::ReadMode aMode,
    1587             :   Span<const uint8_t> aData1, Span<const uint8_t> aData2)
    1588             : {
    1589           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1590             : 
    1591             :   int32_t streamBlockIndex =
    1592           0 :     OffsetToBlockIndexUnchecked(aStream->mChannelOffset);
    1593             : 
    1594             :   // Remove all cached copies of this block
    1595           0 :   ResourceStreamIterator iter(this, aStream->mResourceID);
    1596           0 :   while (MediaCacheStream* stream = iter.Next()) {
    1597           0 :     while (streamBlockIndex >= int32_t(stream->mBlocks.Length())) {
    1598           0 :       stream->mBlocks.AppendElement(-1);
    1599             :     }
    1600           0 :     if (stream->mBlocks[streamBlockIndex] >= 0) {
    1601             :       // We no longer want to own this block
    1602           0 :       int32_t globalBlockIndex = stream->mBlocks[streamBlockIndex];
    1603           0 :       LOG("Released block %d from stream %p block %d(%" PRId64 ")",
    1604             :           globalBlockIndex, stream, streamBlockIndex,
    1605             :           streamBlockIndex*BLOCK_SIZE);
    1606           0 :       RemoveBlockOwner(globalBlockIndex, stream);
    1607             :     }
    1608           0 :   }
    1609             : 
    1610             :   // Extend the mBlocks array as necessary
    1611             : 
    1612           0 :   TimeStamp now = TimeStamp::Now();
    1613           0 :   int32_t blockIndex = FindBlockForIncomingData(now, aStream);
    1614           0 :   if (blockIndex >= 0) {
    1615           0 :     FreeBlock(blockIndex);
    1616             : 
    1617           0 :     Block* block = &mIndex[blockIndex];
    1618           0 :     LOG("Allocated block %d to stream %p block %d(%" PRId64 ")",
    1619             :         blockIndex, aStream, streamBlockIndex, streamBlockIndex*BLOCK_SIZE);
    1620             : 
    1621           0 :     ResourceStreamIterator iter(this, aStream->mResourceID);
    1622           0 :     while (MediaCacheStream* stream = iter.Next()) {
    1623           0 :       BlockOwner* bo = block->mOwners.AppendElement();
    1624           0 :       if (!bo) {
    1625             :         // Roll back mOwners if any allocation fails.
    1626           0 :         block->mOwners.Clear();
    1627           0 :         return;
    1628             :       }
    1629           0 :       mBlockOwnersWatermark =
    1630           0 :         std::max(mBlockOwnersWatermark, uint32_t(block->mOwners.Length()));
    1631           0 :       bo->mStream = stream;
    1632           0 :     }
    1633             : 
    1634           0 :     if (block->mOwners.IsEmpty()) {
    1635             :       // This happens when all streams with the resource id are closed. We can
    1636             :       // just return here now and discard the data.
    1637           0 :       return;
    1638             :     }
    1639             : 
    1640             :     // Tell each stream using this resource about the new block.
    1641           0 :     for (auto& bo : block->mOwners) {
    1642           0 :       bo.mStreamBlock = streamBlockIndex;
    1643           0 :       bo.mLastUseTime = now;
    1644           0 :       bo.mStream->mBlocks[streamBlockIndex] = blockIndex;
    1645           0 :       if (streamBlockIndex*BLOCK_SIZE < bo.mStream->mStreamOffset) {
    1646           0 :         bo.mClass = aMode == MediaCacheStream::MODE_PLAYBACK ? PLAYED_BLOCK
    1647             :                                                              : METADATA_BLOCK;
    1648             :         // This must be the most-recently-used block, since we
    1649             :         // marked it as used now (which may be slightly bogus, but we'll
    1650             :         // treat it as used for simplicity).
    1651           0 :         GetListForBlock(&bo)->AddFirstBlock(blockIndex);
    1652           0 :         Verify();
    1653             :       } else {
    1654             :         // This may not be the latest readahead block, although it usually
    1655             :         // will be. We may have to scan for the right place to insert
    1656             :         // the block in the list.
    1657           0 :         bo.mClass = READAHEAD_BLOCK;
    1658           0 :         InsertReadaheadBlock(&bo, blockIndex);
    1659             :       }
    1660             :     }
    1661             : 
    1662             :     // Invariant: block->mOwners.IsEmpty() iff we can find an entry
    1663             :     // in mFreeBlocks for a given blockIndex.
    1664           0 :     MOZ_DIAGNOSTIC_ASSERT(!block->mOwners.IsEmpty());
    1665           0 :     mFreeBlocks.RemoveBlock(blockIndex);
    1666             : 
    1667           0 :     nsresult rv = mBlockCache->WriteBlock(blockIndex, aData1, aData2);
    1668           0 :     if (NS_FAILED(rv)) {
    1669           0 :       LOG("Released block %d from stream %p block %d(%" PRId64 ")",
    1670             :           blockIndex, aStream, streamBlockIndex, streamBlockIndex*BLOCK_SIZE);
    1671           0 :       FreeBlock(blockIndex);
    1672             :     }
    1673             :   }
    1674             : 
    1675             :   // Queue an Update since the cache state has changed (for example
    1676             :   // we might want to stop loading because the cache is full)
    1677           0 :   QueueUpdate();
    1678             : }
    1679             : 
    1680             : void
    1681           0 : MediaCache::OpenStream(MediaCacheStream* aStream)
    1682             : {
    1683           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1684             : 
    1685           0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1686           0 :   LOG("Stream %p opened", aStream);
    1687           0 :   mStreams.AppendElement(aStream);
    1688           0 :   aStream->mResourceID = AllocateResourceID();
    1689             : 
    1690             :   // Queue an update since a new stream has been opened.
    1691           0 :   QueueUpdate();
    1692           0 : }
    1693             : 
    1694             : void
    1695           0 : MediaCache::ReleaseStream(MediaCacheStream* aStream)
    1696             : {
    1697           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1698             : 
    1699           0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1700           0 :   LOG("Stream %p closed", aStream);
    1701           0 :   mStreams.RemoveElement(aStream);
    1702             : 
    1703             :   // Update MediaCache again for |mStreams| is changed.
    1704             :   // We need to re-run Update() to ensure streams reading from the same resource
    1705             :   // as the removed stream get a chance to continue reading.
    1706           0 :   QueueUpdate();
    1707           0 : }
    1708             : 
    1709             : void
    1710           0 : MediaCache::ReleaseStreamBlocks(MediaCacheStream* aStream)
    1711             : {
    1712           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1713             : 
    1714             :   // XXX scanning the entire stream doesn't seem great, if not much of it
    1715             :   // is cached, but the only easy alternative is to scan the entire cache
    1716             :   // which isn't better
    1717           0 :   uint32_t length = aStream->mBlocks.Length();
    1718           0 :   for (uint32_t i = 0; i < length; ++i) {
    1719           0 :     int32_t blockIndex = aStream->mBlocks[i];
    1720           0 :     if (blockIndex >= 0) {
    1721           0 :       LOG("Released block %d from stream %p block %d(%" PRId64 ")",
    1722             :           blockIndex, aStream, i, i*BLOCK_SIZE);
    1723           0 :       RemoveBlockOwner(blockIndex, aStream);
    1724             :     }
    1725             :   }
    1726           0 : }
    1727             : 
    1728             : void
    1729           0 : MediaCache::Truncate()
    1730             : {
    1731             :   uint32_t end;
    1732           0 :   for (end = mIndex.Length(); end > 0; --end) {
    1733           0 :     if (!IsBlockFree(end - 1))
    1734           0 :       break;
    1735           0 :     mFreeBlocks.RemoveBlock(end - 1);
    1736             :   }
    1737             : 
    1738           0 :   if (end < mIndex.Length()) {
    1739           0 :     mIndex.TruncateLength(end);
    1740             :     // XXX We could truncate the cache file here, but we don't seem
    1741             :     // to have a cross-platform API for doing that. At least when all
    1742             :     // streams are closed we shut down the cache, which erases the
    1743             :     // file at that point.
    1744             :   }
    1745           0 : }
    1746             : 
    1747             : void
    1748           0 : MediaCache::NoteBlockUsage(MediaCacheStream* aStream, int32_t aBlockIndex,
    1749             :                            int64_t aStreamOffset,
    1750             :                            MediaCacheStream::ReadMode aMode, TimeStamp aNow)
    1751             : {
    1752           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1753             : 
    1754           0 :   if (aBlockIndex < 0) {
    1755             :     // this block is not in the cache yet
    1756           0 :     return;
    1757             :   }
    1758             : 
    1759           0 :   BlockOwner* bo = GetBlockOwner(aBlockIndex, aStream);
    1760           0 :   if (!bo) {
    1761             :     // this block is not in the cache yet
    1762           0 :     return;
    1763             :   }
    1764             : 
    1765             :   // The following check has to be <= because the stream offset has
    1766             :   // not yet been updated for the data read from this block
    1767           0 :   NS_ASSERTION(bo->mStreamBlock*BLOCK_SIZE <= aStreamOffset,
    1768             :                "Using a block that's behind the read position?");
    1769             : 
    1770           0 :   GetListForBlock(bo)->RemoveBlock(aBlockIndex);
    1771           0 :   bo->mClass =
    1772           0 :     (aMode == MediaCacheStream::MODE_METADATA || bo->mClass == METADATA_BLOCK)
    1773           0 :     ? METADATA_BLOCK
    1774             :     : PLAYED_BLOCK;
    1775             :   // Since this is just being used now, it can definitely be at the front
    1776             :   // of mMetadataBlocks or mPlayedBlocks
    1777           0 :   GetListForBlock(bo)->AddFirstBlock(aBlockIndex);
    1778           0 :   bo->mLastUseTime = aNow;
    1779           0 :   Verify();
    1780             : }
    1781             : 
    1782             : void
    1783           0 : MediaCache::NoteSeek(MediaCacheStream* aStream, int64_t aOldOffset)
    1784             : {
    1785           0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1786             : 
    1787           0 :   if (aOldOffset < aStream->mStreamOffset) {
    1788             :     // We seeked forward. Convert blocks from readahead to played.
    1789             :     // Any readahead block that intersects the seeked-over range must
    1790             :     // be converted.
    1791           0 :     int32_t blockIndex = OffsetToBlockIndex(aOldOffset);
    1792           0 :     if (blockIndex < 0) {
    1793           0 :       return;
    1794             :     }
    1795             :     int32_t endIndex =
    1796           0 :       std::min(OffsetToBlockIndex(aStream->mStreamOffset + (BLOCK_SIZE - 1)),
    1797           0 :                int32_t(aStream->mBlocks.Length()));
    1798           0 :     if (endIndex < 0) {
    1799           0 :       return;
    1800             :     }
    1801           0 :     TimeStamp now = TimeStamp::Now();
    1802           0 :     while (blockIndex < endIndex) {
    1803           0 :       int32_t cacheBlockIndex = aStream->mBlocks[blockIndex];
    1804           0 :       if (cacheBlockIndex >= 0) {
    1805             :         // Marking the block used may not be exactly what we want but
    1806             :         // it's simple
    1807           0 :         NoteBlockUsage(aStream, cacheBlockIndex, aStream->mStreamOffset,
    1808           0 :                        MediaCacheStream::MODE_PLAYBACK, now);
    1809             :       }
    1810           0 :       ++blockIndex;
    1811             :     }
    1812             :   } else {
    1813             :     // We seeked backward. Convert from played to readahead.
    1814             :     // Any played block that is entirely after the start of the seeked-over
    1815             :     // range must be converted.
    1816             :     int32_t blockIndex =
    1817           0 :       OffsetToBlockIndex(aStream->mStreamOffset + (BLOCK_SIZE - 1));
    1818           0 :     if (blockIndex < 0) {
    1819           0 :       return;
    1820             :     }
    1821             :     int32_t endIndex =
    1822           0 :       std::min(OffsetToBlockIndex(aOldOffset + (BLOCK_SIZE - 1)),
    1823           0 :                int32_t(aStream->mBlocks.Length()));
    1824           0 :     if (endIndex < 0) {
    1825           0 :       return;
    1826             :     }
    1827           0 :     while (blockIndex < endIndex) {
    1828           0 :       MOZ_ASSERT(endIndex > 0);
    1829           0 :       int32_t cacheBlockIndex = aStream->mBlocks[endIndex - 1];
    1830           0 :       if (cacheBlockIndex >= 0) {
    1831           0 :         BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
    1832           0 :         NS_ASSERTION(bo, "Stream doesn't own its blocks?");
    1833           0 :         if (bo->mClass == PLAYED_BLOCK) {
    1834           0 :           aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
    1835           0 :           bo->mClass = READAHEAD_BLOCK;
    1836             :           // Adding this as the first block is sure to be OK since
    1837             :           // this must currently be the earliest readahead block
    1838             :           // (that's why we're proceeding backwards from the end of
    1839             :           // the seeked range to the start)
    1840           0 :           aStream->mReadaheadBlocks.AddFirstBlock(cacheBlockIndex);
    1841           0 :           Verify();
    1842             :         }
    1843             :       }
    1844           0 :       --endIndex;
    1845             :     }
    1846             :   }
    1847             : }
    1848             : 
    1849             : void
    1850           0 : MediaCacheStream::NotifyDataLength(int64_t aLength)
    1851             : {
    1852           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1853             : 
    1854           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    1855           0 :   mStreamLength = aLength;
    1856           0 : }
    1857             : 
    1858             : void
    1859           0 : MediaCacheStream::NotifyDataStarted(int64_t aOffset)
    1860             : {
    1861           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1862             : 
    1863           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    1864           0 :   NS_WARNING_ASSERTION(aOffset == mChannelOffset,
    1865             :                        "Server is giving us unexpected offset");
    1866           0 :   MOZ_ASSERT(aOffset >= 0);
    1867           0 :   mChannelOffset = aOffset;
    1868           0 :   if (mStreamLength >= 0) {
    1869             :     // If we started reading at a certain offset, then for sure
    1870             :     // the stream is at least that long.
    1871           0 :     mStreamLength = std::max(mStreamLength, mChannelOffset);
    1872             :   }
    1873           0 : }
    1874             : 
    1875             : bool
    1876           0 : MediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
    1877             : {
    1878           0 :   return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
    1879             : }
    1880             : 
    1881             : void
    1882           0 : MediaCacheStream::NotifyDataReceived(int64_t aSize, const char* aData,
    1883             :     nsIPrincipal* aPrincipal)
    1884             : {
    1885           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1886             : 
    1887           0 :   if (mClosed) {
    1888           0 :     return;
    1889             :   }
    1890             : 
    1891             :   // Update principals before putting the data in the cache. This is important,
    1892             :   // we want to make sure all principals are updated before any consumer
    1893             :   // can see the new data.
    1894             :   // We do this without holding the cache monitor, in case the client wants
    1895             :   // to do something that takes a lock.
    1896             :   {
    1897           0 :     MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
    1898           0 :     while (MediaCacheStream* stream = iter.Next()) {
    1899           0 :       if (stream->UpdatePrincipal(aPrincipal)) {
    1900           0 :         stream->mClient->CacheClientNotifyPrincipalChanged();
    1901             :       }
    1902           0 :     }
    1903             :   }
    1904             : 
    1905           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    1906           0 :   int64_t size = aSize;
    1907           0 :   const char* data = aData;
    1908             : 
    1909           0 :   LOG("Stream %p DataReceived at %" PRId64 " count=%" PRId64,
    1910             :       this, mChannelOffset, aSize);
    1911             : 
    1912             :   // We process the data one block (or part of a block) at a time
    1913           0 :   while (size > 0) {
    1914           0 :     uint32_t blockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
    1915           0 :     int32_t blockOffset = int32_t(mChannelOffset - blockIndex*BLOCK_SIZE);
    1916           0 :     int32_t chunkSize = std::min<int64_t>(BLOCK_SIZE - blockOffset, size);
    1917             : 
    1918           0 :     if (blockOffset == 0) {
    1919             :       // We've just started filling this buffer so now is a good time
    1920             :       // to clear this flag.
    1921           0 :       mMetadataInPartialBlockBuffer = false;
    1922             :     }
    1923             : 
    1924           0 :     ReadMode mode = mMetadataInPartialBlockBuffer
    1925           0 :       ? MODE_METADATA : MODE_PLAYBACK;
    1926             : 
    1927           0 :     if (blockOffset + chunkSize == BLOCK_SIZE) {
    1928             :       // We have a whole block now to write it out.
    1929             :       auto data1 = MakeSpan<const uint8_t>(
    1930           0 :         mPartialBlockBuffer.get(), blockOffset);
    1931             :       auto data2 = MakeSpan<const uint8_t>(
    1932           0 :         reinterpret_cast<const uint8_t*>(data), chunkSize);
    1933           0 :       mMediaCache->AllocateAndWriteBlock(this, mode, data1, data2);
    1934             :     } else {
    1935           0 :       memcpy(mPartialBlockBuffer.get() + blockOffset, data, chunkSize);
    1936             :     }
    1937             : 
    1938           0 :     mChannelOffset += chunkSize;
    1939           0 :     size -= chunkSize;
    1940           0 :     data += chunkSize;
    1941             :   }
    1942             : 
    1943           0 :   MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
    1944           0 :   while (MediaCacheStream* stream = iter.Next()) {
    1945           0 :     if (stream->mStreamLength >= 0) {
    1946             :       // The stream is at least as long as what we've read
    1947           0 :       stream->mStreamLength = std::max(stream->mStreamLength, mChannelOffset);
    1948             :     }
    1949           0 :     stream->mClient->CacheClientNotifyDataReceived();
    1950           0 :   }
    1951             : 
    1952             :   // Notify in case there's a waiting reader
    1953             :   // XXX it would be fairly easy to optimize things a lot more to
    1954             :   // avoid waking up reader threads unnecessarily
    1955           0 :   mon.NotifyAll();
    1956             : }
    1957             : 
    1958             : void
    1959           0 : MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
    1960             :                                             ReentrantMonitorAutoEnter& aReentrantMonitor)
    1961             : {
    1962           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1963             : 
    1964           0 :   int32_t blockOffset = OffsetInBlock(mChannelOffset);
    1965           0 :   if (blockOffset > 0) {
    1966           0 :     LOG("Stream %p writing partial block: [%d] bytes; "
    1967             :         "mStreamOffset [%" PRId64 "] mChannelOffset[%"
    1968             :         PRId64 "] mStreamLength [%" PRId64 "] notifying: [%s]",
    1969             :         this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
    1970             :         aNotifyAll ? "yes" : "no");
    1971             : 
    1972             :     // Write back the partial block
    1973           0 :     memset(mPartialBlockBuffer.get() + blockOffset, 0, BLOCK_SIZE - blockOffset);
    1974           0 :     auto data = MakeSpan<const uint8_t>(mPartialBlockBuffer.get(), BLOCK_SIZE);
    1975           0 :     mMediaCache->AllocateAndWriteBlock(
    1976             :       this,
    1977           0 :       mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK,
    1978           0 :       data);
    1979             :   }
    1980             : 
    1981             :   // |mChannelOffset == 0| means download ends with no bytes received.
    1982             :   // We should also wake up those readers who are waiting for data
    1983             :   // that will never come.
    1984           0 :   if ((blockOffset > 0 || mChannelOffset == 0) && aNotifyAll) {
    1985             :     // Wake up readers who may be waiting for this data
    1986           0 :     aReentrantMonitor.NotifyAll();
    1987             :   }
    1988           0 : }
    1989             : 
    1990             : void
    1991           0 : MediaCacheStream::FlushPartialBlock()
    1992             : {
    1993           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1994             : 
    1995           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    1996             : 
    1997             :   // Write the current partial block to memory.
    1998             :   // Note: This writes a full block, so if data is not at the end of the
    1999             :   // stream, the decoder must subsequently choose correct start and end offsets
    2000             :   // for reading/seeking.
    2001           0 :   FlushPartialBlockInternal(false, mon);
    2002             : 
    2003           0 :   mMediaCache->QueueUpdate();
    2004           0 : }
    2005             : 
    2006             : void
    2007           0 : MediaCacheStream::NotifyDataEnded(nsresult aStatus)
    2008             : {
    2009           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2010             : 
    2011           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2012             : 
    2013           0 :   if (NS_FAILED(aStatus)) {
    2014             :     // Disconnect from other streams sharing our resource, since they
    2015             :     // should continue trying to load. Our load might have been deliberately
    2016             :     // canceled and that shouldn't affect other streams.
    2017           0 :     mResourceID = mMediaCache->AllocateResourceID();
    2018             :   }
    2019             : 
    2020             :   // It is prudent to update channel/cache status before calling
    2021             :   // CacheClientNotifyDataEnded() which will read |mChannelEnded|.
    2022           0 :   FlushPartialBlockInternal(true, mon);
    2023           0 :   mChannelEnded = true;
    2024           0 :   mMediaCache->QueueUpdate();
    2025             : 
    2026           0 :   MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
    2027           0 :   while (MediaCacheStream* stream = iter.Next()) {
    2028           0 :     if (NS_SUCCEEDED(aStatus)) {
    2029             :       // We read the whole stream, so remember the true length
    2030           0 :       stream->mStreamLength = mChannelOffset;
    2031             :     }
    2032           0 :     if (!stream->mDidNotifyDataEnded) {
    2033           0 :       stream->mDidNotifyDataEnded = true;
    2034           0 :       stream->mNotifyDataEndedStatus = aStatus;
    2035           0 :       stream->mClient->CacheClientNotifyDataEnded(aStatus);
    2036             :     }
    2037           0 :   }
    2038           0 : }
    2039             : 
    2040             : void
    2041           0 : MediaCacheStream::NotifyChannelRecreated()
    2042             : {
    2043           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2044           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2045           0 :   mChannelEnded = false;
    2046           0 :   mDidNotifyDataEnded = false;
    2047           0 : }
    2048             : 
    2049           0 : MediaCacheStream::~MediaCacheStream()
    2050             : {
    2051           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2052           0 :   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
    2053             : 
    2054           0 :   if (mMediaCache) {
    2055           0 :     NS_ASSERTION(mClosed, "Stream was not closed");
    2056           0 :     mMediaCache->ReleaseStream(this);
    2057             :   }
    2058             : 
    2059             :   uint32_t lengthKb = uint32_t(
    2060           0 :     std::min(std::max(mStreamLength, int64_t(0)) / 1024, int64_t(UINT32_MAX)));
    2061           0 :   LOG("MediaCacheStream::~MediaCacheStream(this=%p) "
    2062             :       "MEDIACACHESTREAM_LENGTH_KB=%" PRIu32,
    2063             :       this,
    2064             :       lengthKb);
    2065             :   Telemetry::Accumulate(Telemetry::HistogramID::MEDIACACHESTREAM_LENGTH_KB,
    2066           0 :                         lengthKb);
    2067           0 : }
    2068             : 
    2069             : void
    2070           0 : MediaCacheStream::SetTransportSeekable(bool aIsTransportSeekable)
    2071             : {
    2072           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2073           0 :   NS_ASSERTION(mIsTransportSeekable || aIsTransportSeekable ||
    2074             :                mChannelOffset == 0, "channel offset must be zero when we become non-seekable");
    2075           0 :   mIsTransportSeekable = aIsTransportSeekable;
    2076             :   // Queue an Update since we may change our strategy for dealing
    2077             :   // with this stream
    2078           0 :   mMediaCache->QueueUpdate();
    2079           0 : }
    2080             : 
    2081             : bool
    2082           0 : MediaCacheStream::IsTransportSeekable()
    2083             : {
    2084           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2085           0 :   return mIsTransportSeekable;
    2086             : }
    2087             : 
    2088             : bool
    2089           0 : MediaCacheStream::AreAllStreamsForResourceSuspended()
    2090             : {
    2091           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2092           0 :   MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
    2093             :   // Look for a stream that's able to read the data we need
    2094           0 :   int64_t dataOffset = -1;
    2095           0 :   while (MediaCacheStream* stream = iter.Next()) {
    2096           0 :     if (stream->mCacheSuspended || stream->mChannelEnded || stream->mClosed) {
    2097           0 :       continue;
    2098             :     }
    2099           0 :     if (dataOffset < 0) {
    2100           0 :       dataOffset = GetCachedDataEndInternal(mStreamOffset);
    2101             :     }
    2102             :     // Ignore streams that are reading beyond the data we need
    2103           0 :     if (stream->mChannelOffset > dataOffset) {
    2104           0 :       continue;
    2105             :     }
    2106           0 :     return false;
    2107           0 :   }
    2108             : 
    2109           0 :   return true;
    2110             : }
    2111             : 
    2112             : void
    2113           0 : MediaCacheStream::Close()
    2114             : {
    2115           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2116             : 
    2117           0 :   if (!mMediaCache) {
    2118           0 :     return;
    2119             :   }
    2120             : 
    2121           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2122           0 :   CloseInternal(mon);
    2123             :   // Queue an Update since we may have created more free space. Don't do
    2124             :   // it from CloseInternal since that gets called by Update() itself
    2125             :   // sometimes, and we try to not to queue updates from Update().
    2126           0 :   mMediaCache->QueueUpdate();
    2127             : }
    2128             : 
    2129             : void
    2130           0 : MediaCacheStream::EnsureCacheUpdate()
    2131             : {
    2132           0 :   if (mHasHadUpdate)
    2133           0 :     return;
    2134           0 :   mMediaCache->Update();
    2135             : }
    2136             : 
    2137             : void
    2138           0 : MediaCacheStream::CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor)
    2139             : {
    2140           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2141             : 
    2142           0 :   if (mClosed)
    2143           0 :     return;
    2144           0 :   mClosed = true;
    2145             :   // Closing a stream will change the return value of
    2146             :   // MediaCacheStream::AreAllStreamsForResourceSuspended as well as
    2147             :   // ChannelMediaResource::IsSuspendedByCache. Let's notify it.
    2148           0 :   mMediaCache->QueueSuspendedStatusUpdate(mResourceID);
    2149           0 :   mMediaCache->ReleaseStreamBlocks(this);
    2150             :   // Wake up any blocked readers
    2151           0 :   aReentrantMonitor.NotifyAll();
    2152             : }
    2153             : 
    2154             : void
    2155           0 : MediaCacheStream::Pin()
    2156             : {
    2157           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2158           0 :   ++mPinCount;
    2159             :   // Queue an Update since we may no longer want to read more into the
    2160             :   // cache, if this stream's block have become non-evictable
    2161           0 :   mMediaCache->QueueUpdate();
    2162           0 : }
    2163             : 
    2164             : void
    2165           0 : MediaCacheStream::Unpin()
    2166             : {
    2167           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2168           0 :   NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
    2169           0 :   --mPinCount;
    2170             :   // Queue an Update since we may be able to read more into the
    2171             :   // cache, if this stream's block have become evictable
    2172           0 :   mMediaCache->QueueUpdate();
    2173           0 : }
    2174             : 
    2175             : int64_t
    2176           0 : MediaCacheStream::GetLength()
    2177             : {
    2178           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2179           0 :   return mStreamLength;
    2180             : }
    2181             : 
    2182             : int64_t
    2183           0 : MediaCacheStream::GetNextCachedData(int64_t aOffset)
    2184             : {
    2185           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2186           0 :   return GetNextCachedDataInternal(aOffset);
    2187             : }
    2188             : 
    2189             : int64_t
    2190           0 : MediaCacheStream::GetCachedDataEnd(int64_t aOffset)
    2191             : {
    2192           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2193           0 :   return GetCachedDataEndInternal(aOffset);
    2194             : }
    2195             : 
    2196             : bool
    2197           0 : MediaCacheStream::IsDataCachedToEndOfStream(int64_t aOffset)
    2198             : {
    2199           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2200           0 :   if (mStreamLength < 0)
    2201           0 :     return false;
    2202           0 :   return GetCachedDataEndInternal(aOffset) >= mStreamLength;
    2203             : }
    2204             : 
    2205             : int64_t
    2206           0 : MediaCacheStream::GetCachedDataEndInternal(int64_t aOffset)
    2207             : {
    2208           0 :   mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
    2209           0 :   int32_t blockIndex = OffsetToBlockIndex(aOffset);
    2210           0 :   if (blockIndex < 0) {
    2211           0 :     return aOffset;
    2212             :   }
    2213           0 :   while (size_t(blockIndex) < mBlocks.Length() && mBlocks[blockIndex] != -1) {
    2214           0 :     ++blockIndex;
    2215             :   }
    2216           0 :   int64_t result = blockIndex*BLOCK_SIZE;
    2217           0 :   if (blockIndex == OffsetToBlockIndexUnchecked(mChannelOffset)) {
    2218             :     // The block containing mChannelOffset may be partially read but not
    2219             :     // yet committed to the main cache
    2220           0 :     result = mChannelOffset;
    2221             :   }
    2222           0 :   if (mStreamLength >= 0) {
    2223             :     // The last block in the cache may only be partially valid, so limit
    2224             :     // the cached range to the stream length
    2225           0 :     result = std::min(result, mStreamLength);
    2226             :   }
    2227           0 :   return std::max(result, aOffset);
    2228             : }
    2229             : 
    2230             : int64_t
    2231           0 : MediaCacheStream::GetNextCachedDataInternal(int64_t aOffset)
    2232             : {
    2233           0 :   mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
    2234           0 :   if (aOffset == mStreamLength)
    2235           0 :     return -1;
    2236             : 
    2237           0 :   int32_t startBlockIndex = OffsetToBlockIndex(aOffset);
    2238           0 :   if (startBlockIndex < 0) {
    2239           0 :     return -1;
    2240             :   }
    2241           0 :   int32_t channelBlockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
    2242             : 
    2243           0 :   if (startBlockIndex == channelBlockIndex &&
    2244           0 :       aOffset < mChannelOffset) {
    2245             :     // The block containing mChannelOffset is partially read, but not
    2246             :     // yet committed to the main cache. aOffset lies in the partially
    2247             :     // read portion, thus it is effectively cached.
    2248           0 :     return aOffset;
    2249             :   }
    2250             : 
    2251           0 :   if (size_t(startBlockIndex) >= mBlocks.Length())
    2252           0 :     return -1;
    2253             : 
    2254             :   // Is the current block cached?
    2255           0 :   if (mBlocks[startBlockIndex] != -1)
    2256           0 :     return aOffset;
    2257             : 
    2258             :   // Count the number of uncached blocks
    2259           0 :   bool hasPartialBlock = OffsetInBlock(mChannelOffset) != 0;
    2260           0 :   int32_t blockIndex = startBlockIndex + 1;
    2261             :   while (true) {
    2262           0 :     if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
    2263           0 :         (size_t(blockIndex) < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
    2264             :       // We at the incoming channel block, which has has data in it,
    2265             :       // or are we at a cached block. Return index of block start.
    2266           0 :       return blockIndex * BLOCK_SIZE;
    2267             :     }
    2268             : 
    2269             :     // No more cached blocks?
    2270           0 :     if (size_t(blockIndex) >= mBlocks.Length())
    2271           0 :       return -1;
    2272             : 
    2273           0 :     ++blockIndex;
    2274             :   }
    2275             : 
    2276             :   NS_NOTREACHED("Should return in loop");
    2277             :   return -1;
    2278             : }
    2279             : 
    2280             : void
    2281           0 : MediaCacheStream::SetReadMode(ReadMode aMode)
    2282             : {
    2283           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2284           0 :   if (aMode == mCurrentMode)
    2285           0 :     return;
    2286           0 :   mCurrentMode = aMode;
    2287           0 :   mMediaCache->QueueUpdate();
    2288             : }
    2289             : 
    2290             : void
    2291           0 : MediaCacheStream::SetPlaybackRate(uint32_t aBytesPerSecond)
    2292             : {
    2293           0 :   NS_ASSERTION(aBytesPerSecond > 0, "Zero playback rate not allowed");
    2294           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2295           0 :   if (aBytesPerSecond == mPlaybackBytesPerSecond)
    2296           0 :     return;
    2297           0 :   mPlaybackBytesPerSecond = aBytesPerSecond;
    2298           0 :   mMediaCache->QueueUpdate();
    2299             : }
    2300             : 
    2301             : nsresult
    2302           0 : MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset)
    2303             : {
    2304           0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2305             : 
    2306           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2307           0 :   if (mClosed)
    2308           0 :     return NS_ERROR_FAILURE;
    2309             : 
    2310           0 :   int64_t oldOffset = mStreamOffset;
    2311           0 :   int64_t newOffset = mStreamOffset;
    2312           0 :   switch (aWhence) {
    2313             :   case PR_SEEK_END:
    2314           0 :     if (mStreamLength < 0)
    2315           0 :       return NS_ERROR_FAILURE;
    2316           0 :     newOffset = mStreamLength + aOffset;
    2317           0 :     break;
    2318             :   case PR_SEEK_CUR:
    2319           0 :     newOffset += aOffset;
    2320           0 :     break;
    2321             :   case PR_SEEK_SET:
    2322           0 :     newOffset = aOffset;
    2323           0 :     break;
    2324             :   default:
    2325           0 :     NS_ERROR("Unknown whence");
    2326           0 :     return NS_ERROR_FAILURE;
    2327             :   }
    2328             : 
    2329           0 :   if (!IsOffsetAllowed(newOffset)) {
    2330           0 :     return NS_ERROR_FAILURE;
    2331             :   }
    2332           0 :   mStreamOffset = newOffset;
    2333             : 
    2334           0 :   LOG("Stream %p Seek to %" PRId64, this, mStreamOffset);
    2335           0 :   mMediaCache->NoteSeek(this, oldOffset);
    2336             : 
    2337           0 :   mMediaCache->QueueUpdate();
    2338           0 :   return NS_OK;
    2339             : }
    2340             : 
    2341             : void
    2342           0 : MediaCacheStream::ThrottleReadahead(bool bThrottle)
    2343             : {
    2344           0 :   MOZ_ASSERT(NS_IsMainThread());
    2345           0 :   if (mThrottleReadahead != bThrottle) {
    2346           0 :     LOGI("Stream %p ThrottleReadahead %d", this, bThrottle);
    2347           0 :     mThrottleReadahead = bThrottle;
    2348           0 :     ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2349           0 :     mMediaCache->QueueUpdate();
    2350             :   }
    2351           0 : }
    2352             : 
    2353             : int64_t
    2354           0 : MediaCacheStream::Tell()
    2355             : {
    2356           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2357           0 :   return mStreamOffset;
    2358             : }
    2359             : 
    2360             : nsresult
    2361           0 : MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
    2362             : {
    2363           0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2364             : 
    2365           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2366           0 :   if (mClosed)
    2367           0 :     return NS_ERROR_FAILURE;
    2368             : 
    2369             :   // Cache the offset in case it is changed again when we are waiting for the
    2370             :   // monitor to be notified to avoid reading at the wrong position.
    2371           0 :   auto streamOffset = mStreamOffset;
    2372             : 
    2373           0 :   uint32_t count = 0;
    2374             :   // Read one block (or part of a block) at a time
    2375           0 :   while (count < aCount) {
    2376           0 :     int32_t streamBlock = OffsetToBlockIndex(streamOffset);
    2377           0 :     if (streamBlock < 0) {
    2378           0 :       break;
    2379             :     }
    2380           0 :     uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
    2381           0 :     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
    2382             : 
    2383           0 :     if (mStreamLength >= 0) {
    2384             :       // Don't try to read beyond the end of the stream
    2385           0 :       int64_t bytesRemaining = mStreamLength - streamOffset;
    2386           0 :       if (bytesRemaining <= 0) {
    2387             :         // Get out of here and return NS_OK
    2388           0 :         break;
    2389             :       }
    2390           0 :       size = std::min(size, bytesRemaining);
    2391             :       // Clamp size until 64-bit file size issues are fixed.
    2392           0 :       size = std::min(size, int64_t(INT32_MAX));
    2393             :     }
    2394             : 
    2395             :     int32_t cacheBlock =
    2396           0 :       size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
    2397           0 :     if (cacheBlock < 0) {
    2398             :       // We don't have a complete cached block here.
    2399             : 
    2400           0 :       if (count > 0) {
    2401             :         // Some data has been read, so return what we've got instead of
    2402             :         // blocking or trying to find a stream with a partial block.
    2403           0 :         break;
    2404             :       }
    2405             : 
    2406             :       // See if the data is available in the partial cache block of any
    2407             :       // stream reading this resource. We need to do this in case there is
    2408             :       // another stream with this resource that has all the data to the end of
    2409             :       // the stream but the data doesn't end on a block boundary.
    2410           0 :       MediaCacheStream* streamWithPartialBlock = nullptr;
    2411           0 :       MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
    2412           0 :       while (MediaCacheStream* stream = iter.Next()) {
    2413           0 :         if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
    2414           0 :               streamBlock &&
    2415           0 :             streamOffset < stream->mChannelOffset) {
    2416           0 :           streamWithPartialBlock = stream;
    2417           0 :           break;
    2418             :         }
    2419           0 :       }
    2420           0 :       if (streamWithPartialBlock) {
    2421             :         // We can just use the data in mPartialBlockBuffer. In fact we should
    2422             :         // use it rather than waiting for the block to fill and land in
    2423             :         // the cache.
    2424           0 :         int64_t bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - streamOffset);
    2425             :         // Clamp bytes until 64-bit file size issues are fixed.
    2426           0 :         bytes = std::min(bytes, int64_t(INT32_MAX));
    2427           0 :         MOZ_ASSERT(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
    2428           0 :         memcpy(aBuffer,
    2429           0 :           streamWithPartialBlock->mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
    2430           0 :         if (mCurrentMode == MODE_METADATA) {
    2431           0 :           streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
    2432             :         }
    2433           0 :         streamOffset += bytes;
    2434           0 :         count = bytes;
    2435           0 :         break;
    2436             :       }
    2437             : 
    2438             :       // No data has been read yet, so block
    2439           0 :       mon.Wait();
    2440           0 :       if (mClosed) {
    2441             :         // We may have successfully read some data, but let's just throw
    2442             :         // that out.
    2443           0 :         return NS_ERROR_FAILURE;
    2444             :       }
    2445           0 :       continue;
    2446             :     }
    2447             : 
    2448           0 :     mMediaCache->NoteBlockUsage(
    2449           0 :       this, cacheBlock, streamOffset, mCurrentMode, TimeStamp::Now());
    2450             : 
    2451           0 :     int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
    2452             :     int32_t bytes;
    2453           0 :     MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
    2454           0 :     nsresult rv = mMediaCache->ReadCacheFile(
    2455           0 :       offset, aBuffer + count, int32_t(size), &bytes);
    2456           0 :     if (NS_FAILED(rv)) {
    2457           0 :       if (count == 0)
    2458           0 :         return rv;
    2459             :       // If we did successfully read some data, may as well return it
    2460           0 :       break;
    2461             :     }
    2462           0 :     streamOffset += bytes;
    2463           0 :     count += bytes;
    2464             :   }
    2465             : 
    2466           0 :   if (count > 0) {
    2467             :     // Some data was read, so queue an update since block priorities may
    2468             :     // have changed
    2469           0 :     mMediaCache->QueueUpdate();
    2470             :   }
    2471           0 :   LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
    2472           0 :   *aBytes = count;
    2473           0 :   mStreamOffset = streamOffset;
    2474           0 :   return NS_OK;
    2475             : }
    2476             : 
    2477             : nsresult
    2478           0 : MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
    2479             :                          uint32_t aCount, uint32_t* aBytes)
    2480             : {
    2481           0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2482             : 
    2483           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2484           0 :   nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
    2485           0 :   if (NS_FAILED(rv)) return rv;
    2486           0 :   return Read(aBuffer, aCount, aBytes);
    2487             : }
    2488             : 
    2489             : nsresult
    2490           0 : MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount)
    2491             : {
    2492           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2493             : 
    2494             :   // Read one block (or part of a block) at a time
    2495           0 :   uint32_t count = 0;
    2496           0 :   int64_t streamOffset = aOffset;
    2497           0 :   while (count < aCount) {
    2498           0 :     if (mClosed) {
    2499             :       // We need to check |mClosed| in each iteration which might be changed
    2500             :       // after calling |mMediaCache->ReadCacheFile|.
    2501           0 :       return NS_ERROR_FAILURE;
    2502             :     }
    2503           0 :     int32_t streamBlock = OffsetToBlockIndex(streamOffset);
    2504           0 :     if (streamBlock < 0) {
    2505           0 :       break;
    2506             :     }
    2507             :     uint32_t offsetInStreamBlock =
    2508           0 :       uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
    2509           0 :     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
    2510             : 
    2511           0 :     if (mStreamLength >= 0) {
    2512             :       // Don't try to read beyond the end of the stream
    2513           0 :       int64_t bytesRemaining = mStreamLength - streamOffset;
    2514           0 :       if (bytesRemaining <= 0) {
    2515           0 :         return NS_ERROR_FAILURE;
    2516             :       }
    2517           0 :       size = std::min(size, bytesRemaining);
    2518             :       // Clamp size until 64-bit file size issues are fixed.
    2519           0 :       size = std::min(size, int64_t(INT32_MAX));
    2520             :     }
    2521             : 
    2522             :     int32_t bytes;
    2523           0 :     int32_t channelBlock = OffsetToBlockIndexUnchecked(mChannelOffset);
    2524             :     int32_t cacheBlock =
    2525           0 :       size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
    2526           0 :     if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
    2527             :       // We can just use the data in mPartialBlockBuffer. In fact we should
    2528             :       // use it rather than waiting for the block to fill and land in
    2529             :       // the cache.
    2530             :       // Clamp bytes until 64-bit file size issues are fixed.
    2531           0 :       int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
    2532           0 :       bytes = std::min(toCopy, int64_t(INT32_MAX));
    2533           0 :       MOZ_ASSERT(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");
    2534           0 :       memcpy(aBuffer + count,
    2535           0 :         mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
    2536             :     } else {
    2537           0 :       if (cacheBlock < 0) {
    2538             :         // We expect all blocks to be cached! Fail!
    2539           0 :         return NS_ERROR_FAILURE;
    2540             :       }
    2541           0 :       int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
    2542           0 :       MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
    2543           0 :       nsresult rv = mMediaCache->ReadCacheFile(
    2544           0 :         offset, aBuffer + count, int32_t(size), &bytes);
    2545           0 :       if (NS_FAILED(rv)) {
    2546           0 :         return rv;
    2547             :       }
    2548             :     }
    2549           0 :     streamOffset += bytes;
    2550           0 :     count += bytes;
    2551             :   }
    2552             : 
    2553           0 :   return NS_OK;
    2554             : }
    2555             : 
    2556             : nsresult
    2557           0 : MediaCacheStream::Init(int64_t aContentLength)
    2558             : {
    2559           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2560             : 
    2561           0 :   if (mMediaCache) {
    2562           0 :     return NS_OK;
    2563             :   }
    2564             : 
    2565           0 :   if (aContentLength > 0) {
    2566           0 :     uint32_t length = uint32_t(std::min(aContentLength, int64_t(UINT32_MAX)));
    2567           0 :     LOG("MediaCacheStream::NotifyDataLength(this=%p) "
    2568             :         "MEDIACACHESTREAM_NOTIFIED_LENGTH=%" PRIu32,
    2569             :         this,
    2570             :         length);
    2571             :     Telemetry::Accumulate(
    2572           0 :       Telemetry::HistogramID::MEDIACACHESTREAM_NOTIFIED_LENGTH, length);
    2573             : 
    2574           0 :     mStreamLength = aContentLength;
    2575             :   }
    2576             : 
    2577           0 :   mMediaCache = MediaCache::GetMediaCache(aContentLength);
    2578           0 :   if (!mMediaCache) {
    2579           0 :     return NS_ERROR_FAILURE;
    2580             :   }
    2581           0 :   mMediaCache->OpenStream(this);
    2582           0 :   return NS_OK;
    2583             : }
    2584             : 
    2585             : nsresult
    2586           0 : MediaCacheStream::InitAsClone(MediaCacheStream* aOriginal)
    2587             : {
    2588           0 :   if (!aOriginal->IsAvailableForSharing())
    2589           0 :     return NS_ERROR_FAILURE;
    2590             : 
    2591           0 :   if (mMediaCache) {
    2592           0 :     return NS_OK;
    2593             :   }
    2594             : 
    2595           0 :   NS_ASSERTION(aOriginal->mMediaCache, "Don't clone an uninitialized stream");
    2596             :   // Use the same MediaCache as our clone.
    2597           0 :   mMediaCache = aOriginal->mMediaCache;
    2598             : 
    2599           0 :   mMediaCache->OpenStream(this);
    2600             : 
    2601           0 :   mResourceID = aOriginal->mResourceID;
    2602             : 
    2603             :   // Grab cache blocks from aOriginal as readahead blocks for our stream
    2604           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2605             : 
    2606           0 :   mPrincipal = aOriginal->mPrincipal;
    2607           0 :   mStreamLength = aOriginal->mStreamLength;
    2608           0 :   mIsTransportSeekable = aOriginal->mIsTransportSeekable;
    2609             : 
    2610             :   // Cloned streams are initially suspended, since there is no channel open
    2611             :   // initially for a clone.
    2612           0 :   mCacheSuspended = true;
    2613           0 :   mChannelEnded = true;
    2614             : 
    2615           0 :   if (aOriginal->mDidNotifyDataEnded) {
    2616           0 :     mNotifyDataEndedStatus = aOriginal->mNotifyDataEndedStatus;
    2617           0 :     mDidNotifyDataEnded = true;
    2618           0 :     mClient->CacheClientNotifyDataEnded(mNotifyDataEndedStatus);
    2619             :   }
    2620             : 
    2621           0 :   for (uint32_t i = 0; i < aOriginal->mBlocks.Length(); ++i) {
    2622           0 :     int32_t cacheBlockIndex = aOriginal->mBlocks[i];
    2623           0 :     if (cacheBlockIndex < 0)
    2624           0 :       continue;
    2625             : 
    2626           0 :     while (i >= mBlocks.Length()) {
    2627           0 :       mBlocks.AppendElement(-1);
    2628             :     }
    2629             :     // Every block is a readahead block for the clone because the clone's initial
    2630             :     // stream offset is zero
    2631           0 :     mMediaCache->AddBlockOwnerAsReadahead(cacheBlockIndex, this, i);
    2632             :   }
    2633             : 
    2634           0 :   return NS_OK;
    2635             : }
    2636             : 
    2637           0 : nsresult MediaCacheStream::GetCachedRanges(MediaByteRangeSet& aRanges)
    2638             : {
    2639             :   // Take the monitor, so that the cached data ranges can't grow while we're
    2640             :   // trying to loop over them.
    2641           0 :   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
    2642             : 
    2643             :   // We must be pinned while running this, otherwise the cached data ranges may
    2644             :   // shrink while we're trying to loop over them.
    2645           0 :   NS_ASSERTION(mPinCount > 0, "Must be pinned");
    2646             : 
    2647           0 :   int64_t startOffset = GetNextCachedDataInternal(0);
    2648           0 :   while (startOffset >= 0) {
    2649           0 :     int64_t endOffset = GetCachedDataEndInternal(startOffset);
    2650           0 :     NS_ASSERTION(startOffset < endOffset, "Buffered range must end after its start");
    2651             :     // Bytes [startOffset..endOffset] are cached.
    2652           0 :     aRanges += MediaByteRange(startOffset, endOffset);
    2653           0 :     startOffset = GetNextCachedDataInternal(endOffset);
    2654           0 :     NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
    2655             :       "Must have advanced to start of next range, or hit end of stream");
    2656             :   }
    2657           0 :   return NS_OK;
    2658             : }
    2659             : 
    2660             : } // namespace mozilla
    2661             : 
    2662             : // avoid redefined macro in unified build
    2663             : #undef LOG
    2664             : #undef LOGI

Generated by: LCOV version 1.13