tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaCache.cpp (105557B)


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