content/media/MediaCache.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/MediaCache.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2427 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/ReentrantMonitor.h"
    1.11 +
    1.12 +#include "MediaCache.h"
    1.13 +#include "prio.h"
    1.14 +#include "nsContentUtils.h"
    1.15 +#include "nsThreadUtils.h"
    1.16 +#include "MediaResource.h"
    1.17 +#include "prlog.h"
    1.18 +#include "mozilla/Preferences.h"
    1.19 +#include "FileBlockCache.h"
    1.20 +#include "nsAnonymousTemporaryFile.h"
    1.21 +#include "nsIObserverService.h"
    1.22 +#include "nsISeekableStream.h"
    1.23 +#include "nsIPrincipal.h"
    1.24 +#include "mozilla/Attributes.h"
    1.25 +#include "mozilla/Services.h"
    1.26 +#include <algorithm>
    1.27 +
    1.28 +namespace mozilla {
    1.29 +
    1.30 +#ifdef PR_LOGGING
    1.31 +PRLogModuleInfo* gMediaCacheLog;
    1.32 +#define CACHE_LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
    1.33 +#else
    1.34 +#define CACHE_LOG(type, msg)
    1.35 +#endif
    1.36 +
    1.37 +// Readahead blocks for non-seekable streams will be limited to this
    1.38 +// fraction of the cache space. We don't normally evict such blocks
    1.39 +// because replacing them requires a seek, but we need to make sure
    1.40 +// they don't monopolize the cache.
    1.41 +static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
    1.42 +
    1.43 +// Data N seconds before the current playback position is given the same priority
    1.44 +// as data REPLAY_PENALTY_FACTOR*N seconds ahead of the current playback
    1.45 +// position. REPLAY_PENALTY_FACTOR is greater than 1 to reflect that
    1.46 +// data in the past is less likely to be played again than data in the future.
    1.47 +// We want to give data just behind the current playback position reasonably
    1.48 +// high priority in case codecs need to retrieve that data (e.g. because
    1.49 +// tracks haven't been muxed well or are being decoded at uneven rates).
    1.50 +// 1/REPLAY_PENALTY_FACTOR as much data will be kept behind the
    1.51 +// current playback position as will be kept ahead of the current playback
    1.52 +// position.
    1.53 +static const uint32_t REPLAY_PENALTY_FACTOR = 3;
    1.54 +
    1.55 +// When looking for a reusable block, scan forward this many blocks
    1.56 +// from the desired "best" block location to look for free blocks,
    1.57 +// before we resort to scanning the whole cache. The idea is to try to
    1.58 +// store runs of stream blocks close-to-consecutively in the cache if we
    1.59 +// can.
    1.60 +static const uint32_t FREE_BLOCK_SCAN_LIMIT = 16;
    1.61 +
    1.62 +// Try to save power by not resuming paused reads if the stream won't need new
    1.63 +// data within this time interval in the future
    1.64 +static const uint32_t CACHE_POWERSAVE_WAKEUP_LOW_THRESHOLD_MS = 10000;
    1.65 +
    1.66 +#ifdef DEBUG
    1.67 +// Turn this on to do very expensive cache state validation
    1.68 +// #define DEBUG_VERIFY_CACHE
    1.69 +#endif
    1.70 +
    1.71 +// There is at most one media cache (although that could quite easily be
    1.72 +// relaxed if we wanted to manage multiple caches with independent
    1.73 +// size limits).
    1.74 +static MediaCache* gMediaCache;
    1.75 +
    1.76 +class MediaCacheFlusher MOZ_FINAL : public nsIObserver,
    1.77 +                                      public nsSupportsWeakReference {
    1.78 +  MediaCacheFlusher() {}
    1.79 +  ~MediaCacheFlusher();
    1.80 +public:
    1.81 +  NS_DECL_ISUPPORTS
    1.82 +  NS_DECL_NSIOBSERVER
    1.83 +
    1.84 +  static void Init();
    1.85 +};
    1.86 +
    1.87 +static MediaCacheFlusher* gMediaCacheFlusher;
    1.88 +
    1.89 +NS_IMPL_ISUPPORTS(MediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
    1.90 +
    1.91 +MediaCacheFlusher::~MediaCacheFlusher()
    1.92 +{
    1.93 +  gMediaCacheFlusher = nullptr;
    1.94 +}
    1.95 +
    1.96 +void MediaCacheFlusher::Init()
    1.97 +{
    1.98 +  if (gMediaCacheFlusher) {
    1.99 +    return;
   1.100 +  }
   1.101 +
   1.102 +  gMediaCacheFlusher = new MediaCacheFlusher();
   1.103 +  NS_ADDREF(gMediaCacheFlusher);
   1.104 +
   1.105 +  nsCOMPtr<nsIObserverService> observerService =
   1.106 +    mozilla::services::GetObserverService();
   1.107 +  if (observerService) {
   1.108 +    observerService->AddObserver(gMediaCacheFlusher, "last-pb-context-exited", true);
   1.109 +    observerService->AddObserver(gMediaCacheFlusher, "network-clear-cache-stored-anywhere", true);
   1.110 +  }
   1.111 +}
   1.112 +
   1.113 +class MediaCache {
   1.114 +public:
   1.115 +  friend class MediaCacheStream::BlockList;
   1.116 +  typedef MediaCacheStream::BlockList BlockList;
   1.117 +  enum {
   1.118 +    BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE
   1.119 +  };
   1.120 +
   1.121 +  MediaCache() : mNextResourceID(1),
   1.122 +    mReentrantMonitor("MediaCache.mReentrantMonitor"),
   1.123 +    mUpdateQueued(false)
   1.124 +#ifdef DEBUG
   1.125 +    , mInUpdate(false)
   1.126 +#endif
   1.127 +  {
   1.128 +    MOZ_COUNT_CTOR(MediaCache);
   1.129 +  }
   1.130 +  ~MediaCache() {
   1.131 +    NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
   1.132 +    Truncate();
   1.133 +    NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
   1.134 +    if (mFileCache) {
   1.135 +      mFileCache->Close();
   1.136 +      mFileCache = nullptr;
   1.137 +    }
   1.138 +    MOZ_COUNT_DTOR(MediaCache);
   1.139 +  }
   1.140 +
   1.141 +  // Main thread only. Creates the backing cache file. If this fails,
   1.142 +  // then the cache is still in a semi-valid state; mFD will be null,
   1.143 +  // so all I/O on the cache file will fail.
   1.144 +  nsresult Init();
   1.145 +  // Shut down the global cache if it's no longer needed. We shut down
   1.146 +  // the cache as soon as there are no streams. This means that during
   1.147 +  // normal operation we are likely to start up the cache and shut it down
   1.148 +  // many times, but that's OK since starting it up is cheap and
   1.149 +  // shutting it down cleans things up and releases disk space.
   1.150 +  static void MaybeShutdown();
   1.151 +
   1.152 +  // Brutally flush the cache contents. Main thread only.
   1.153 +  static void Flush();
   1.154 +  void FlushInternal();
   1.155 +
   1.156 +  // Cache-file access methods. These are the lowest-level cache methods.
   1.157 +  // mReentrantMonitor must be held; these can be called on any thread.
   1.158 +  // This can return partial reads.
   1.159 +  nsresult ReadCacheFile(int64_t aOffset, void* aData, int32_t aLength,
   1.160 +                         int32_t* aBytes);
   1.161 +  // This will fail if all aLength bytes are not read
   1.162 +  nsresult ReadCacheFileAllBytes(int64_t aOffset, void* aData, int32_t aLength);
   1.163 +
   1.164 +  int64_t AllocateResourceID()
   1.165 +  {
   1.166 +    mReentrantMonitor.AssertCurrentThreadIn();
   1.167 +    return mNextResourceID++;
   1.168 +  }
   1.169 +
   1.170 +  // mReentrantMonitor must be held, called on main thread.
   1.171 +  // These methods are used by the stream to set up and tear down streams,
   1.172 +  // and to handle reads and writes.
   1.173 +  // Add aStream to the list of streams.
   1.174 +  void OpenStream(MediaCacheStream* aStream);
   1.175 +  // Remove aStream from the list of streams.
   1.176 +  void ReleaseStream(MediaCacheStream* aStream);
   1.177 +  // Free all blocks belonging to aStream.
   1.178 +  void ReleaseStreamBlocks(MediaCacheStream* aStream);
   1.179 +  // Find a cache entry for this data, and write the data into it
   1.180 +  void AllocateAndWriteBlock(MediaCacheStream* aStream, const void* aData,
   1.181 +                             MediaCacheStream::ReadMode aMode);
   1.182 +
   1.183 +  // mReentrantMonitor must be held; can be called on any thread
   1.184 +  // Notify the cache that a seek has been requested. Some blocks may
   1.185 +  // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
   1.186 +  // This does not trigger channel seeks directly, the next Update()
   1.187 +  // will do that if necessary. The caller will call QueueUpdate().
   1.188 +  void NoteSeek(MediaCacheStream* aStream, int64_t aOldOffset);
   1.189 +  // Notify the cache that a block has been read from. This is used
   1.190 +  // to update last-use times. The block may not actually have a
   1.191 +  // cache entry yet since Read can read data from a stream's
   1.192 +  // in-memory mPartialBlockBuffer while the block is only partly full,
   1.193 +  // and thus hasn't yet been committed to the cache. The caller will
   1.194 +  // call QueueUpdate().
   1.195 +  void NoteBlockUsage(MediaCacheStream* aStream, int32_t aBlockIndex,
   1.196 +                      MediaCacheStream::ReadMode aMode, TimeStamp aNow);
   1.197 +  // Mark aStream as having the block, adding it as an owner.
   1.198 +  void AddBlockOwnerAsReadahead(int32_t aBlockIndex, MediaCacheStream* aStream,
   1.199 +                                int32_t aStreamBlockIndex);
   1.200 +
   1.201 +  // This queues a call to Update() on the main thread.
   1.202 +  void QueueUpdate();
   1.203 +
   1.204 +  // Updates the cache state asynchronously on the main thread:
   1.205 +  // -- try to trim the cache back to its desired size, if necessary
   1.206 +  // -- suspend channels that are going to read data that's lower priority
   1.207 +  // than anything currently cached
   1.208 +  // -- resume channels that are going to read data that's higher priority
   1.209 +  // than something currently cached
   1.210 +  // -- seek channels that need to seek to a new location
   1.211 +  void Update();
   1.212 +
   1.213 +#ifdef DEBUG_VERIFY_CACHE
   1.214 +  // Verify invariants, especially block list invariants
   1.215 +  void Verify();
   1.216 +#else
   1.217 +  void Verify() {}
   1.218 +#endif
   1.219 +
   1.220 +  ReentrantMonitor& GetReentrantMonitor() { return mReentrantMonitor; }
   1.221 +
   1.222 +  /**
   1.223 +   * An iterator that makes it easy to iterate through all streams that
   1.224 +   * have a given resource ID and are not closed.
   1.225 +   * Can be used on the main thread or while holding the media cache lock.
   1.226 +   */
   1.227 +  class ResourceStreamIterator {
   1.228 +  public:
   1.229 +    ResourceStreamIterator(int64_t aResourceID) :
   1.230 +      mResourceID(aResourceID), mNext(0) {}
   1.231 +    MediaCacheStream* Next()
   1.232 +    {
   1.233 +      while (mNext < gMediaCache->mStreams.Length()) {
   1.234 +        MediaCacheStream* stream = gMediaCache->mStreams[mNext];
   1.235 +        ++mNext;
   1.236 +        if (stream->GetResourceID() == mResourceID && !stream->IsClosed())
   1.237 +          return stream;
   1.238 +      }
   1.239 +      return nullptr;
   1.240 +    }
   1.241 +  private:
   1.242 +    int64_t  mResourceID;
   1.243 +    uint32_t mNext;
   1.244 +  };
   1.245 +
   1.246 +protected:
   1.247 +  // Find a free or reusable block and return its index. If there are no
   1.248 +  // free blocks and no reusable blocks, add a new block to the cache
   1.249 +  // and return it. Can return -1 on OOM.
   1.250 +  int32_t FindBlockForIncomingData(TimeStamp aNow, MediaCacheStream* aStream);
   1.251 +  // Find a reusable block --- a free block, if there is one, otherwise
   1.252 +  // the reusable block with the latest predicted-next-use, or -1 if
   1.253 +  // there aren't any freeable blocks. Only block indices less than
   1.254 +  // aMaxSearchBlockIndex are considered. If aForStream is non-null,
   1.255 +  // then aForStream and aForStreamBlock indicate what media data will
   1.256 +  // be placed; FindReusableBlock will favour returning free blocks
   1.257 +  // near other blocks for that point in the stream.
   1.258 +  int32_t FindReusableBlock(TimeStamp aNow,
   1.259 +                            MediaCacheStream* aForStream,
   1.260 +                            int32_t aForStreamBlock,
   1.261 +                            int32_t aMaxSearchBlockIndex);
   1.262 +  bool BlockIsReusable(int32_t aBlockIndex);
   1.263 +  // Given a list of blocks sorted with the most reusable blocks at the
   1.264 +  // end, find the last block whose stream is not pinned (if any)
   1.265 +  // and whose cache entry index is less than aBlockIndexLimit
   1.266 +  // and append it to aResult.
   1.267 +  void AppendMostReusableBlock(BlockList* aBlockList,
   1.268 +                               nsTArray<uint32_t>* aResult,
   1.269 +                               int32_t aBlockIndexLimit);
   1.270 +
   1.271 +  enum BlockClass {
   1.272 +    // block belongs to mMetadataBlockList because data has been consumed
   1.273 +    // from it in "metadata mode" --- in particular blocks read during
   1.274 +    // Ogg seeks go into this class. These blocks may have played data
   1.275 +    // in them too.
   1.276 +    METADATA_BLOCK,
   1.277 +    // block belongs to mPlayedBlockList because its offset is
   1.278 +    // less than the stream's current reader position
   1.279 +    PLAYED_BLOCK,
   1.280 +    // block belongs to the stream's mReadaheadBlockList because its
   1.281 +    // offset is greater than or equal to the stream's current
   1.282 +    // reader position
   1.283 +    READAHEAD_BLOCK
   1.284 +  };
   1.285 +
   1.286 +  struct BlockOwner {
   1.287 +    BlockOwner() : mStream(nullptr), mClass(READAHEAD_BLOCK) {}
   1.288 +
   1.289 +    // The stream that owns this block, or null if the block is free.
   1.290 +    MediaCacheStream* mStream;
   1.291 +    // The block index in the stream. Valid only if mStream is non-null.
   1.292 +    uint32_t            mStreamBlock;
   1.293 +    // Time at which this block was last used. Valid only if
   1.294 +    // mClass is METADATA_BLOCK or PLAYED_BLOCK.
   1.295 +    TimeStamp           mLastUseTime;
   1.296 +    BlockClass          mClass;
   1.297 +  };
   1.298 +
   1.299 +  struct Block {
   1.300 +    // Free blocks have an empty mOwners array
   1.301 +    nsTArray<BlockOwner> mOwners;
   1.302 +  };
   1.303 +
   1.304 +  // Get the BlockList that the block should belong to given its
   1.305 +  // current owner
   1.306 +  BlockList* GetListForBlock(BlockOwner* aBlock);
   1.307 +  // Get the BlockOwner for the given block index and owning stream
   1.308 +  // (returns null if the stream does not own the block)
   1.309 +  BlockOwner* GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream);
   1.310 +  // Returns true iff the block is free
   1.311 +  bool IsBlockFree(int32_t aBlockIndex)
   1.312 +  { return mIndex[aBlockIndex].mOwners.IsEmpty(); }
   1.313 +  // Add the block to the free list and mark its streams as not having
   1.314 +  // the block in cache
   1.315 +  void FreeBlock(int32_t aBlock);
   1.316 +  // Mark aStream as not having the block, removing it as an owner. If
   1.317 +  // the block has no more owners it's added to the free list.
   1.318 +  void RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream);
   1.319 +  // Swap all metadata associated with the two blocks. The caller
   1.320 +  // is responsible for swapping up any cache file state.
   1.321 +  void SwapBlocks(int32_t aBlockIndex1, int32_t aBlockIndex2);
   1.322 +  // Insert the block into the readahead block list for the stream
   1.323 +  // at the right point in the list.
   1.324 +  void InsertReadaheadBlock(BlockOwner* aBlockOwner, int32_t aBlockIndex);
   1.325 +
   1.326 +  // Guess the duration until block aBlock will be next used
   1.327 +  TimeDuration PredictNextUse(TimeStamp aNow, int32_t aBlock);
   1.328 +  // Guess the duration until the next incoming data on aStream will be used
   1.329 +  TimeDuration PredictNextUseForIncomingData(MediaCacheStream* aStream);
   1.330 +
   1.331 +  // Truncate the file and index array if there are free blocks at the
   1.332 +  // end
   1.333 +  void Truncate();
   1.334 +
   1.335 +  // This member is main-thread only. It's used to allocate unique
   1.336 +  // resource IDs to streams.
   1.337 +  int64_t                       mNextResourceID;
   1.338 +
   1.339 +  // The monitor protects all the data members here. Also, off-main-thread
   1.340 +  // readers that need to block will Wait() on this monitor. When new
   1.341 +  // data becomes available in the cache, we NotifyAll() on this monitor.
   1.342 +  ReentrantMonitor         mReentrantMonitor;
   1.343 +  // This is only written while on the main thread and the monitor is held.
   1.344 +  // Thus, it can be safely read from the main thread or while holding the monitor.
   1.345 +  nsTArray<MediaCacheStream*> mStreams;
   1.346 +  // The Blocks describing the cache entries.
   1.347 +  nsTArray<Block> mIndex;
   1.348 +  // Writer which performs IO, asynchronously writing cache blocks.
   1.349 +  nsRefPtr<FileBlockCache> mFileCache;
   1.350 +  // The list of free blocks; they are not ordered.
   1.351 +  BlockList       mFreeBlocks;
   1.352 +  // True if an event to run Update() has been queued but not processed
   1.353 +  bool            mUpdateQueued;
   1.354 +#ifdef DEBUG
   1.355 +  bool            mInUpdate;
   1.356 +#endif
   1.357 +};
   1.358 +
   1.359 +NS_IMETHODIMP
   1.360 +MediaCacheFlusher::Observe(nsISupports *aSubject, char const *aTopic, char16_t const *aData)
   1.361 +{
   1.362 +  if (strcmp(aTopic, "last-pb-context-exited") == 0) {
   1.363 +    MediaCache::Flush();
   1.364 +  }
   1.365 +  if (strcmp(aTopic, "network-clear-cache-stored-anywhere") == 0) {
   1.366 +    MediaCache::Flush();
   1.367 +  }
   1.368 +  return NS_OK;
   1.369 +}
   1.370 +
   1.371 +MediaCacheStream::MediaCacheStream(ChannelMediaResource* aClient)
   1.372 +  : mClient(aClient),
   1.373 +    mInitialized(false),
   1.374 +    mHasHadUpdate(false),
   1.375 +    mClosed(false),
   1.376 +    mDidNotifyDataEnded(false),
   1.377 +    mResourceID(0),
   1.378 +    mIsTransportSeekable(false),
   1.379 +    mCacheSuspended(false),
   1.380 +    mChannelEnded(false),
   1.381 +    mChannelOffset(0),
   1.382 +    mStreamLength(-1),
   1.383 +    mStreamOffset(0),
   1.384 +    mPlaybackBytesPerSecond(10000),
   1.385 +    mPinCount(0),
   1.386 +    mCurrentMode(MODE_PLAYBACK),
   1.387 +    mMetadataInPartialBlockBuffer(false),
   1.388 +    mPartialBlockBuffer(new int64_t[BLOCK_SIZE/sizeof(int64_t)])
   1.389 +{
   1.390 +}
   1.391 +
   1.392 +size_t MediaCacheStream::SizeOfExcludingThis(
   1.393 +                                MallocSizeOf aMallocSizeOf) const
   1.394 +{
   1.395 +  // Looks like these are not owned:
   1.396 +  // - mClient
   1.397 +  // - mPrincipal
   1.398 +  size_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf);
   1.399 +  size += mReadaheadBlocks.SizeOfExcludingThis(aMallocSizeOf);
   1.400 +  size += mMetadataBlocks.SizeOfExcludingThis(aMallocSizeOf);
   1.401 +  size += mPlayedBlocks.SizeOfExcludingThis(aMallocSizeOf);
   1.402 +  size += mPartialBlockBuffer.SizeOfExcludingThis(aMallocSizeOf);
   1.403 +
   1.404 +  return size;
   1.405 +}
   1.406 +
   1.407 +size_t MediaCacheStream::BlockList::SizeOfExcludingThis(
   1.408 +                                MallocSizeOf aMallocSizeOf) const
   1.409 +{
   1.410 +  return mEntries.SizeOfExcludingThis(/* sizeOfEntryExcludingThis = */ nullptr,
   1.411 +                                      aMallocSizeOf);
   1.412 +}
   1.413 +
   1.414 +void MediaCacheStream::BlockList::AddFirstBlock(int32_t aBlock)
   1.415 +{
   1.416 +  NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
   1.417 +  Entry* entry = mEntries.PutEntry(aBlock);
   1.418 +
   1.419 +  if (mFirstBlock < 0) {
   1.420 +    entry->mNextBlock = entry->mPrevBlock = aBlock;
   1.421 +  } else {
   1.422 +    entry->mNextBlock = mFirstBlock;
   1.423 +    entry->mPrevBlock = mEntries.GetEntry(mFirstBlock)->mPrevBlock;
   1.424 +    mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
   1.425 +    mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
   1.426 +  }
   1.427 +  mFirstBlock = aBlock;
   1.428 +  ++mCount;
   1.429 +}
   1.430 +
   1.431 +void MediaCacheStream::BlockList::AddAfter(int32_t aBlock, int32_t aBefore)
   1.432 +{
   1.433 +  NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
   1.434 +  Entry* entry = mEntries.PutEntry(aBlock);
   1.435 +
   1.436 +  Entry* addAfter = mEntries.GetEntry(aBefore);
   1.437 +  NS_ASSERTION(addAfter, "aBefore not in list");
   1.438 +
   1.439 +  entry->mNextBlock = addAfter->mNextBlock;
   1.440 +  entry->mPrevBlock = aBefore;
   1.441 +  mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
   1.442 +  mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
   1.443 +  ++mCount;
   1.444 +}
   1.445 +
   1.446 +void MediaCacheStream::BlockList::RemoveBlock(int32_t aBlock)
   1.447 +{
   1.448 +  Entry* entry = mEntries.GetEntry(aBlock);
   1.449 +  NS_ASSERTION(entry, "Block not in list");
   1.450 +
   1.451 +  if (entry->mNextBlock == aBlock) {
   1.452 +    NS_ASSERTION(entry->mPrevBlock == aBlock, "Linked list inconsistency");
   1.453 +    NS_ASSERTION(mFirstBlock == aBlock, "Linked list inconsistency");
   1.454 +    mFirstBlock = -1;
   1.455 +  } else {
   1.456 +    if (mFirstBlock == aBlock) {
   1.457 +      mFirstBlock = entry->mNextBlock;
   1.458 +    }
   1.459 +    mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = entry->mPrevBlock;
   1.460 +    mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = entry->mNextBlock;
   1.461 +  }
   1.462 +  mEntries.RemoveEntry(aBlock);
   1.463 +  --mCount;
   1.464 +}
   1.465 +
   1.466 +int32_t MediaCacheStream::BlockList::GetLastBlock() const
   1.467 +{
   1.468 +  if (mFirstBlock < 0)
   1.469 +    return -1;
   1.470 +  return mEntries.GetEntry(mFirstBlock)->mPrevBlock;
   1.471 +}
   1.472 +
   1.473 +int32_t MediaCacheStream::BlockList::GetNextBlock(int32_t aBlock) const
   1.474 +{
   1.475 +  int32_t block = mEntries.GetEntry(aBlock)->mNextBlock;
   1.476 +  if (block == mFirstBlock)
   1.477 +    return -1;
   1.478 +  return block;
   1.479 +}
   1.480 +
   1.481 +int32_t MediaCacheStream::BlockList::GetPrevBlock(int32_t aBlock) const
   1.482 +{
   1.483 +  if (aBlock == mFirstBlock)
   1.484 +    return -1;
   1.485 +  return mEntries.GetEntry(aBlock)->mPrevBlock;
   1.486 +}
   1.487 +
   1.488 +#ifdef DEBUG
   1.489 +void MediaCacheStream::BlockList::Verify()
   1.490 +{
   1.491 +  int32_t count = 0;
   1.492 +  if (mFirstBlock >= 0) {
   1.493 +    int32_t block = mFirstBlock;
   1.494 +    do {
   1.495 +      Entry* entry = mEntries.GetEntry(block);
   1.496 +      NS_ASSERTION(mEntries.GetEntry(entry->mNextBlock)->mPrevBlock == block,
   1.497 +                   "Bad prev link");
   1.498 +      NS_ASSERTION(mEntries.GetEntry(entry->mPrevBlock)->mNextBlock == block,
   1.499 +                   "Bad next link");
   1.500 +      block = entry->mNextBlock;
   1.501 +      ++count;
   1.502 +    } while (block != mFirstBlock);
   1.503 +  }
   1.504 +  NS_ASSERTION(count == mCount, "Bad count");
   1.505 +}
   1.506 +#endif
   1.507 +
   1.508 +static void UpdateSwappedBlockIndex(int32_t* aBlockIndex,
   1.509 +    int32_t aBlock1Index, int32_t aBlock2Index)
   1.510 +{
   1.511 +  int32_t index = *aBlockIndex;
   1.512 +  if (index == aBlock1Index) {
   1.513 +    *aBlockIndex = aBlock2Index;
   1.514 +  } else if (index == aBlock2Index) {
   1.515 +    *aBlockIndex = aBlock1Index;
   1.516 +  }
   1.517 +}
   1.518 +
   1.519 +void
   1.520 +MediaCacheStream::BlockList::NotifyBlockSwapped(int32_t aBlockIndex1,
   1.521 +                                                  int32_t aBlockIndex2)
   1.522 +{
   1.523 +  Entry* e1 = mEntries.GetEntry(aBlockIndex1);
   1.524 +  Entry* e2 = mEntries.GetEntry(aBlockIndex2);
   1.525 +  int32_t e1Prev = -1, e1Next = -1, e2Prev = -1, e2Next = -1;
   1.526 +
   1.527 +  // Fix mFirstBlock
   1.528 +  UpdateSwappedBlockIndex(&mFirstBlock, aBlockIndex1, aBlockIndex2);
   1.529 +
   1.530 +  // Fix mNextBlock/mPrevBlock links. First capture previous/next links
   1.531 +  // so we don't get confused due to aliasing.
   1.532 +  if (e1) {
   1.533 +    e1Prev = e1->mPrevBlock;
   1.534 +    e1Next = e1->mNextBlock;
   1.535 +  }
   1.536 +  if (e2) {
   1.537 +    e2Prev = e2->mPrevBlock;
   1.538 +    e2Next = e2->mNextBlock;
   1.539 +  }
   1.540 +  // Update the entries.
   1.541 +  if (e1) {
   1.542 +    mEntries.GetEntry(e1Prev)->mNextBlock = aBlockIndex2;
   1.543 +    mEntries.GetEntry(e1Next)->mPrevBlock = aBlockIndex2;
   1.544 +  }
   1.545 +  if (e2) {
   1.546 +    mEntries.GetEntry(e2Prev)->mNextBlock = aBlockIndex1;
   1.547 +    mEntries.GetEntry(e2Next)->mPrevBlock = aBlockIndex1;
   1.548 +  }
   1.549 +
   1.550 +  // Fix hashtable keys. First remove stale entries.
   1.551 +  if (e1) {
   1.552 +    e1Prev = e1->mPrevBlock;
   1.553 +    e1Next = e1->mNextBlock;
   1.554 +    mEntries.RemoveEntry(aBlockIndex1);
   1.555 +    // Refresh pointer after hashtable mutation.
   1.556 +    e2 = mEntries.GetEntry(aBlockIndex2);
   1.557 +  }
   1.558 +  if (e2) {
   1.559 +    e2Prev = e2->mPrevBlock;
   1.560 +    e2Next = e2->mNextBlock;
   1.561 +    mEntries.RemoveEntry(aBlockIndex2);
   1.562 +  }
   1.563 +  // Put new entries back.
   1.564 +  if (e1) {
   1.565 +    e1 = mEntries.PutEntry(aBlockIndex2);
   1.566 +    e1->mNextBlock = e1Next;
   1.567 +    e1->mPrevBlock = e1Prev;
   1.568 +  }
   1.569 +  if (e2) {
   1.570 +    e2 = mEntries.PutEntry(aBlockIndex1);
   1.571 +    e2->mNextBlock = e2Next;
   1.572 +    e2->mPrevBlock = e2Prev;
   1.573 +  }
   1.574 +}
   1.575 +
   1.576 +nsresult
   1.577 +MediaCache::Init()
   1.578 +{
   1.579 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.580 +  NS_ASSERTION(!mFileCache, "Cache file already open?");
   1.581 +
   1.582 +  PRFileDesc* fileDesc = nullptr;
   1.583 +  nsresult rv = NS_OpenAnonymousTemporaryFile(&fileDesc);
   1.584 +  NS_ENSURE_SUCCESS(rv,rv);
   1.585 +
   1.586 +  mFileCache = new FileBlockCache();
   1.587 +  rv = mFileCache->Open(fileDesc);
   1.588 +  NS_ENSURE_SUCCESS(rv,rv);
   1.589 +
   1.590 +#ifdef PR_LOGGING
   1.591 +  if (!gMediaCacheLog) {
   1.592 +    gMediaCacheLog = PR_NewLogModule("MediaCache");
   1.593 +  }
   1.594 +#endif
   1.595 +
   1.596 +  MediaCacheFlusher::Init();
   1.597 +
   1.598 +  return NS_OK;
   1.599 +}
   1.600 +
   1.601 +void
   1.602 +MediaCache::Flush()
   1.603 +{
   1.604 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.605 +
   1.606 +  if (!gMediaCache)
   1.607 +    return;
   1.608 +
   1.609 +  gMediaCache->FlushInternal();
   1.610 +}
   1.611 +
   1.612 +void
   1.613 +MediaCache::FlushInternal()
   1.614 +{
   1.615 +  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   1.616 +
   1.617 +  for (uint32_t blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
   1.618 +    FreeBlock(blockIndex);
   1.619 +  }
   1.620 +
   1.621 +  // Truncate file, close it, and reopen
   1.622 +  Truncate();
   1.623 +  NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
   1.624 +  if (mFileCache) {
   1.625 +    mFileCache->Close();
   1.626 +    mFileCache = nullptr;
   1.627 +  }
   1.628 +  Init();
   1.629 +}
   1.630 +
   1.631 +void
   1.632 +MediaCache::MaybeShutdown()
   1.633 +{
   1.634 +  NS_ASSERTION(NS_IsMainThread(),
   1.635 +               "MediaCache::MaybeShutdown called on non-main thread");
   1.636 +  if (!gMediaCache->mStreams.IsEmpty()) {
   1.637 +    // Don't shut down yet, streams are still alive
   1.638 +    return;
   1.639 +  }
   1.640 +
   1.641 +  // Since we're on the main thread, no-one is going to add a new stream
   1.642 +  // while we shut down.
   1.643 +  // This function is static so we don't have to delete 'this'.
   1.644 +  delete gMediaCache;
   1.645 +  gMediaCache = nullptr;
   1.646 +  NS_IF_RELEASE(gMediaCacheFlusher);
   1.647 +}
   1.648 +
   1.649 +static void
   1.650 +InitMediaCache()
   1.651 +{
   1.652 +  if (gMediaCache)
   1.653 +    return;
   1.654 +
   1.655 +  gMediaCache = new MediaCache();
   1.656 +  if (!gMediaCache)
   1.657 +    return;
   1.658 +
   1.659 +  nsresult rv = gMediaCache->Init();
   1.660 +  if (NS_FAILED(rv)) {
   1.661 +    delete gMediaCache;
   1.662 +    gMediaCache = nullptr;
   1.663 +  }
   1.664 +}
   1.665 +
   1.666 +nsresult
   1.667 +MediaCache::ReadCacheFile(int64_t aOffset, void* aData, int32_t aLength,
   1.668 +                            int32_t* aBytes)
   1.669 +{
   1.670 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.671 +
   1.672 +  if (!mFileCache)
   1.673 +    return NS_ERROR_FAILURE;
   1.674 +
   1.675 +  return mFileCache->Read(aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
   1.676 +}
   1.677 +
   1.678 +nsresult
   1.679 +MediaCache::ReadCacheFileAllBytes(int64_t aOffset, void* aData, int32_t aLength)
   1.680 +{
   1.681 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.682 +
   1.683 +  int64_t offset = aOffset;
   1.684 +  int32_t count = aLength;
   1.685 +  // Cast to char* so we can do byte-wise pointer arithmetic
   1.686 +  char* data = static_cast<char*>(aData);
   1.687 +  while (count > 0) {
   1.688 +    int32_t bytes;
   1.689 +    nsresult rv = ReadCacheFile(offset, data, count, &bytes);
   1.690 +    if (NS_FAILED(rv))
   1.691 +      return rv;
   1.692 +    if (bytes == 0)
   1.693 +      return NS_ERROR_FAILURE;
   1.694 +    count -= bytes;
   1.695 +    data += bytes;
   1.696 +    offset += bytes;
   1.697 +  }
   1.698 +  return NS_OK;
   1.699 +}
   1.700 +
   1.701 +static int32_t GetMaxBlocks()
   1.702 +{
   1.703 +  // We look up the cache size every time. This means dynamic changes
   1.704 +  // to the pref are applied.
   1.705 +  // Cache size is in KB
   1.706 +  int32_t cacheSize = Preferences::GetInt("media.cache_size", 500*1024);
   1.707 +  int64_t maxBlocks = static_cast<int64_t>(cacheSize)*1024/MediaCache::BLOCK_SIZE;
   1.708 +  maxBlocks = std::max<int64_t>(maxBlocks, 1);
   1.709 +  return int32_t(std::min<int64_t>(maxBlocks, INT32_MAX));
   1.710 +}
   1.711 +
   1.712 +int32_t
   1.713 +MediaCache::FindBlockForIncomingData(TimeStamp aNow,
   1.714 +                                       MediaCacheStream* aStream)
   1.715 +{
   1.716 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.717 +
   1.718 +  int32_t blockIndex = FindReusableBlock(aNow, aStream,
   1.719 +      aStream->mChannelOffset/BLOCK_SIZE, INT32_MAX);
   1.720 +
   1.721 +  if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
   1.722 +    // The block returned is already allocated.
   1.723 +    // Don't reuse it if a) there's room to expand the cache or
   1.724 +    // b) the data we're going to store in the free block is not higher
   1.725 +    // priority than the data already stored in the free block.
   1.726 +    // The latter can lead us to go over the cache limit a bit.
   1.727 +    if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
   1.728 +         PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
   1.729 +      blockIndex = mIndex.Length();
   1.730 +      if (!mIndex.AppendElement())
   1.731 +        return -1;
   1.732 +      mFreeBlocks.AddFirstBlock(blockIndex);
   1.733 +      return blockIndex;
   1.734 +    }
   1.735 +  }
   1.736 +
   1.737 +  return blockIndex;
   1.738 +}
   1.739 +
   1.740 +bool
   1.741 +MediaCache::BlockIsReusable(int32_t aBlockIndex)
   1.742 +{
   1.743 +  Block* block = &mIndex[aBlockIndex];
   1.744 +  for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
   1.745 +    MediaCacheStream* stream = block->mOwners[i].mStream;
   1.746 +    if (stream->mPinCount > 0 ||
   1.747 +        stream->mStreamOffset/BLOCK_SIZE == block->mOwners[i].mStreamBlock) {
   1.748 +      return false;
   1.749 +    }
   1.750 +  }
   1.751 +  return true;
   1.752 +}
   1.753 +
   1.754 +void
   1.755 +MediaCache::AppendMostReusableBlock(BlockList* aBlockList,
   1.756 +                                      nsTArray<uint32_t>* aResult,
   1.757 +                                      int32_t aBlockIndexLimit)
   1.758 +{
   1.759 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.760 +
   1.761 +  int32_t blockIndex = aBlockList->GetLastBlock();
   1.762 +  if (blockIndex < 0)
   1.763 +    return;
   1.764 +  do {
   1.765 +    // Don't consider blocks for pinned streams, or blocks that are
   1.766 +    // beyond the specified limit, or a block that contains a stream's
   1.767 +    // current read position (such a block contains both played data
   1.768 +    // and readahead data)
   1.769 +    if (blockIndex < aBlockIndexLimit && BlockIsReusable(blockIndex)) {
   1.770 +      aResult->AppendElement(blockIndex);
   1.771 +      return;
   1.772 +    }
   1.773 +    blockIndex = aBlockList->GetPrevBlock(blockIndex);
   1.774 +  } while (blockIndex >= 0);
   1.775 +}
   1.776 +
   1.777 +int32_t
   1.778 +MediaCache::FindReusableBlock(TimeStamp aNow,
   1.779 +                                MediaCacheStream* aForStream,
   1.780 +                                int32_t aForStreamBlock,
   1.781 +                                int32_t aMaxSearchBlockIndex)
   1.782 +{
   1.783 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.784 +
   1.785 +  uint32_t length = std::min(uint32_t(aMaxSearchBlockIndex), mIndex.Length());
   1.786 +
   1.787 +  if (aForStream && aForStreamBlock > 0 &&
   1.788 +      uint32_t(aForStreamBlock) <= aForStream->mBlocks.Length()) {
   1.789 +    int32_t prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
   1.790 +    if (prevCacheBlock >= 0) {
   1.791 +      uint32_t freeBlockScanEnd =
   1.792 +        std::min(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
   1.793 +      for (uint32_t i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
   1.794 +        if (IsBlockFree(i))
   1.795 +          return i;
   1.796 +      }
   1.797 +    }
   1.798 +  }
   1.799 +
   1.800 +  if (!mFreeBlocks.IsEmpty()) {
   1.801 +    int32_t blockIndex = mFreeBlocks.GetFirstBlock();
   1.802 +    do {
   1.803 +      if (blockIndex < aMaxSearchBlockIndex)
   1.804 +        return blockIndex;
   1.805 +      blockIndex = mFreeBlocks.GetNextBlock(blockIndex);
   1.806 +    } while (blockIndex >= 0);
   1.807 +  }
   1.808 +
   1.809 +  // Build a list of the blocks we should consider for the "latest
   1.810 +  // predicted time of next use". We can exploit the fact that the block
   1.811 +  // linked lists are ordered by increasing time of next use. This is
   1.812 +  // actually the whole point of having the linked lists.
   1.813 +  nsAutoTArray<uint32_t,8> candidates;
   1.814 +  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
   1.815 +    MediaCacheStream* stream = mStreams[i];
   1.816 +    if (stream->mPinCount > 0) {
   1.817 +      // No point in even looking at this stream's blocks
   1.818 +      continue;
   1.819 +    }
   1.820 +
   1.821 +    AppendMostReusableBlock(&stream->mMetadataBlocks, &candidates, length);
   1.822 +    AppendMostReusableBlock(&stream->mPlayedBlocks, &candidates, length);
   1.823 +
   1.824 +    // Don't consider readahead blocks in non-seekable streams. If we
   1.825 +    // remove the block we won't be able to seek back to read it later.
   1.826 +    if (stream->mIsTransportSeekable) {
   1.827 +      AppendMostReusableBlock(&stream->mReadaheadBlocks, &candidates, length);
   1.828 +    }
   1.829 +  }
   1.830 +
   1.831 +  TimeDuration latestUse;
   1.832 +  int32_t latestUseBlock = -1;
   1.833 +  for (uint32_t i = 0; i < candidates.Length(); ++i) {
   1.834 +    TimeDuration nextUse = PredictNextUse(aNow, candidates[i]);
   1.835 +    if (nextUse > latestUse) {
   1.836 +      latestUse = nextUse;
   1.837 +      latestUseBlock = candidates[i];
   1.838 +    }
   1.839 +  }
   1.840 +
   1.841 +  return latestUseBlock;
   1.842 +}
   1.843 +
   1.844 +MediaCache::BlockList*
   1.845 +MediaCache::GetListForBlock(BlockOwner* aBlock)
   1.846 +{
   1.847 +  switch (aBlock->mClass) {
   1.848 +  case METADATA_BLOCK:
   1.849 +    NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
   1.850 +    return &aBlock->mStream->mMetadataBlocks;
   1.851 +  case PLAYED_BLOCK:
   1.852 +    NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
   1.853 +    return &aBlock->mStream->mPlayedBlocks;
   1.854 +  case READAHEAD_BLOCK:
   1.855 +    NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
   1.856 +    return &aBlock->mStream->mReadaheadBlocks;
   1.857 +  default:
   1.858 +    NS_ERROR("Invalid block class");
   1.859 +    return nullptr;
   1.860 +  }
   1.861 +}
   1.862 +
   1.863 +MediaCache::BlockOwner*
   1.864 +MediaCache::GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
   1.865 +{
   1.866 +  Block* block = &mIndex[aBlockIndex];
   1.867 +  for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
   1.868 +    if (block->mOwners[i].mStream == aStream)
   1.869 +      return &block->mOwners[i];
   1.870 +  }
   1.871 +  return nullptr;
   1.872 +}
   1.873 +
   1.874 +void
   1.875 +MediaCache::SwapBlocks(int32_t aBlockIndex1, int32_t aBlockIndex2)
   1.876 +{
   1.877 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.878 +
   1.879 +  Block* block1 = &mIndex[aBlockIndex1];
   1.880 +  Block* block2 = &mIndex[aBlockIndex2];
   1.881 +
   1.882 +  block1->mOwners.SwapElements(block2->mOwners);
   1.883 +
   1.884 +  // Now all references to block1 have to be replaced with block2 and
   1.885 +  // vice versa.
   1.886 +  // First update stream references to blocks via mBlocks.
   1.887 +  const Block* blocks[] = { block1, block2 };
   1.888 +  int32_t blockIndices[] = { aBlockIndex1, aBlockIndex2 };
   1.889 +  for (int32_t i = 0; i < 2; ++i) {
   1.890 +    for (uint32_t j = 0; j < blocks[i]->mOwners.Length(); ++j) {
   1.891 +      const BlockOwner* b = &blocks[i]->mOwners[j];
   1.892 +      b->mStream->mBlocks[b->mStreamBlock] = blockIndices[i];
   1.893 +    }
   1.894 +  }
   1.895 +
   1.896 +  // Now update references to blocks in block lists.
   1.897 +  mFreeBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
   1.898 +
   1.899 +  nsTHashtable<nsPtrHashKey<MediaCacheStream> > visitedStreams;
   1.900 +
   1.901 +  for (int32_t i = 0; i < 2; ++i) {
   1.902 +    for (uint32_t j = 0; j < blocks[i]->mOwners.Length(); ++j) {
   1.903 +      MediaCacheStream* stream = blocks[i]->mOwners[j].mStream;
   1.904 +      // Make sure that we don't update the same stream twice --- that
   1.905 +      // would result in swapping the block references back again!
   1.906 +      if (visitedStreams.GetEntry(stream))
   1.907 +        continue;
   1.908 +      visitedStreams.PutEntry(stream);
   1.909 +      stream->mReadaheadBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
   1.910 +      stream->mPlayedBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
   1.911 +      stream->mMetadataBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
   1.912 +    }
   1.913 +  }
   1.914 +
   1.915 +  Verify();
   1.916 +}
   1.917 +
   1.918 +void
   1.919 +MediaCache::RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
   1.920 +{
   1.921 +  Block* block = &mIndex[aBlockIndex];
   1.922 +  for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
   1.923 +    BlockOwner* bo = &block->mOwners[i];
   1.924 +    if (bo->mStream == aStream) {
   1.925 +      GetListForBlock(bo)->RemoveBlock(aBlockIndex);
   1.926 +      bo->mStream->mBlocks[bo->mStreamBlock] = -1;
   1.927 +      block->mOwners.RemoveElementAt(i);
   1.928 +      if (block->mOwners.IsEmpty()) {
   1.929 +        mFreeBlocks.AddFirstBlock(aBlockIndex);
   1.930 +      }
   1.931 +      return;
   1.932 +    }
   1.933 +  }
   1.934 +}
   1.935 +
   1.936 +void
   1.937 +MediaCache::AddBlockOwnerAsReadahead(int32_t aBlockIndex,
   1.938 +                                       MediaCacheStream* aStream,
   1.939 +                                       int32_t aStreamBlockIndex)
   1.940 +{
   1.941 +  Block* block = &mIndex[aBlockIndex];
   1.942 +  if (block->mOwners.IsEmpty()) {
   1.943 +    mFreeBlocks.RemoveBlock(aBlockIndex);
   1.944 +  }
   1.945 +  BlockOwner* bo = block->mOwners.AppendElement();
   1.946 +  bo->mStream = aStream;
   1.947 +  bo->mStreamBlock = aStreamBlockIndex;
   1.948 +  aStream->mBlocks[aStreamBlockIndex] = aBlockIndex;
   1.949 +  bo->mClass = READAHEAD_BLOCK;
   1.950 +  InsertReadaheadBlock(bo, aBlockIndex);
   1.951 +}
   1.952 +
   1.953 +void
   1.954 +MediaCache::FreeBlock(int32_t aBlock)
   1.955 +{
   1.956 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.957 +
   1.958 +  Block* block = &mIndex[aBlock];
   1.959 +  if (block->mOwners.IsEmpty()) {
   1.960 +    // already free
   1.961 +    return;
   1.962 +  }
   1.963 +
   1.964 +  CACHE_LOG(PR_LOG_DEBUG, ("Released block %d", aBlock));
   1.965 +
   1.966 +  for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
   1.967 +    BlockOwner* bo = &block->mOwners[i];
   1.968 +    GetListForBlock(bo)->RemoveBlock(aBlock);
   1.969 +    bo->mStream->mBlocks[bo->mStreamBlock] = -1;
   1.970 +  }
   1.971 +  block->mOwners.Clear();
   1.972 +  mFreeBlocks.AddFirstBlock(aBlock);
   1.973 +  Verify();
   1.974 +}
   1.975 +
   1.976 +TimeDuration
   1.977 +MediaCache::PredictNextUse(TimeStamp aNow, int32_t aBlock)
   1.978 +{
   1.979 +  mReentrantMonitor.AssertCurrentThreadIn();
   1.980 +  NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
   1.981 +
   1.982 +  Block* block = &mIndex[aBlock];
   1.983 +  // Blocks can be belong to multiple streams. The predicted next use
   1.984 +  // time is the earliest time predicted by any of the streams.
   1.985 +  TimeDuration result;
   1.986 +  for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
   1.987 +    BlockOwner* bo = &block->mOwners[i];
   1.988 +    TimeDuration prediction;
   1.989 +    switch (bo->mClass) {
   1.990 +    case METADATA_BLOCK:
   1.991 +      // This block should be managed in LRU mode. For metadata we predict
   1.992 +      // that the time until the next use is the time since the last use.
   1.993 +      prediction = aNow - bo->mLastUseTime;
   1.994 +      break;
   1.995 +    case PLAYED_BLOCK: {
   1.996 +      // This block should be managed in LRU mode, and we should impose
   1.997 +      // a "replay delay" to reflect the likelihood of replay happening
   1.998 +      NS_ASSERTION(static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE <
   1.999 +                   bo->mStream->mStreamOffset,
  1.1000 +                   "Played block after the current stream position?");
  1.1001 +      int64_t bytesBehind =
  1.1002 +        bo->mStream->mStreamOffset - static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE;
  1.1003 +      int64_t millisecondsBehind =
  1.1004 +        bytesBehind*1000/bo->mStream->mPlaybackBytesPerSecond;
  1.1005 +      prediction = TimeDuration::FromMilliseconds(
  1.1006 +          std::min<int64_t>(millisecondsBehind*REPLAY_PENALTY_FACTOR, INT32_MAX));
  1.1007 +      break;
  1.1008 +    }
  1.1009 +    case READAHEAD_BLOCK: {
  1.1010 +      int64_t bytesAhead =
  1.1011 +        static_cast<int64_t>(bo->mStreamBlock)*BLOCK_SIZE - bo->mStream->mStreamOffset;
  1.1012 +      NS_ASSERTION(bytesAhead >= 0,
  1.1013 +                   "Readahead block before the current stream position?");
  1.1014 +      int64_t millisecondsAhead =
  1.1015 +        bytesAhead*1000/bo->mStream->mPlaybackBytesPerSecond;
  1.1016 +      prediction = TimeDuration::FromMilliseconds(
  1.1017 +          std::min<int64_t>(millisecondsAhead, INT32_MAX));
  1.1018 +      break;
  1.1019 +    }
  1.1020 +    default:
  1.1021 +      NS_ERROR("Invalid class for predicting next use");
  1.1022 +      return TimeDuration(0);
  1.1023 +    }
  1.1024 +    if (i == 0 || prediction < result) {
  1.1025 +      result = prediction;
  1.1026 +    }
  1.1027 +  }
  1.1028 +  return result;
  1.1029 +}
  1.1030 +
  1.1031 +TimeDuration
  1.1032 +MediaCache::PredictNextUseForIncomingData(MediaCacheStream* aStream)
  1.1033 +{
  1.1034 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1035 +
  1.1036 +  int64_t bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
  1.1037 +  if (bytesAhead <= -BLOCK_SIZE) {
  1.1038 +    // Hmm, no idea when data behind us will be used. Guess 24 hours.
  1.1039 +    return TimeDuration::FromSeconds(24*60*60);
  1.1040 +  }
  1.1041 +  if (bytesAhead <= 0)
  1.1042 +    return TimeDuration(0);
  1.1043 +  int64_t millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond;
  1.1044 +  return TimeDuration::FromMilliseconds(
  1.1045 +      std::min<int64_t>(millisecondsAhead, INT32_MAX));
  1.1046 +}
  1.1047 +
  1.1048 +enum StreamAction { NONE, SEEK, SEEK_AND_RESUME, RESUME, SUSPEND };
  1.1049 +
  1.1050 +void
  1.1051 +MediaCache::Update()
  1.1052 +{
  1.1053 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1054 +
  1.1055 +  // The action to use for each stream. We store these so we can make
  1.1056 +  // decisions while holding the cache lock but implement those decisions
  1.1057 +  // without holding the cache lock, since we need to call out to
  1.1058 +  // stream, decoder and element code.
  1.1059 +  nsAutoTArray<StreamAction,10> actions;
  1.1060 +
  1.1061 +  {
  1.1062 +    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  1.1063 +    mUpdateQueued = false;
  1.1064 +#ifdef DEBUG
  1.1065 +    mInUpdate = true;
  1.1066 +#endif
  1.1067 +
  1.1068 +    int32_t maxBlocks = GetMaxBlocks();
  1.1069 +    TimeStamp now = TimeStamp::Now();
  1.1070 +
  1.1071 +    int32_t freeBlockCount = mFreeBlocks.GetCount();
  1.1072 +    TimeDuration latestPredictedUseForOverflow = 0;
  1.1073 +    if (mIndex.Length() > uint32_t(maxBlocks)) {
  1.1074 +      // Try to trim back the cache to its desired maximum size. The cache may
  1.1075 +      // have overflowed simply due to data being received when we have
  1.1076 +      // no blocks in the main part of the cache that are free or lower
  1.1077 +      // priority than the new data. The cache can also be overflowing because
  1.1078 +      // the media.cache_size preference was reduced.
  1.1079 +      // First, figure out what the least valuable block in the cache overflow
  1.1080 +      // is. We don't want to replace any blocks in the main part of the
  1.1081 +      // cache whose expected time of next use is earlier or equal to that.
  1.1082 +      // If we allow that, we can effectively end up discarding overflowing
  1.1083 +      // blocks (by moving an overflowing block to the main part of the cache,
  1.1084 +      // and then overwriting it with another overflowing block), and we try
  1.1085 +      // to avoid that since it requires HTTP seeks.
  1.1086 +      // We also use this loop to eliminate overflowing blocks from
  1.1087 +      // freeBlockCount.
  1.1088 +      for (int32_t blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
  1.1089 +           --blockIndex) {
  1.1090 +        if (IsBlockFree(blockIndex)) {
  1.1091 +          // Don't count overflowing free blocks in our free block count
  1.1092 +          --freeBlockCount;
  1.1093 +          continue;
  1.1094 +        }
  1.1095 +        TimeDuration predictedUse = PredictNextUse(now, blockIndex);
  1.1096 +        latestPredictedUseForOverflow = std::max(latestPredictedUseForOverflow, predictedUse);
  1.1097 +      }
  1.1098 +    } else {
  1.1099 +      freeBlockCount += maxBlocks - mIndex.Length();
  1.1100 +    }
  1.1101 +
  1.1102 +    // Now try to move overflowing blocks to the main part of the cache.
  1.1103 +    for (int32_t blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
  1.1104 +         --blockIndex) {
  1.1105 +      if (IsBlockFree(blockIndex))
  1.1106 +        continue;
  1.1107 +
  1.1108 +      Block* block = &mIndex[blockIndex];
  1.1109 +      // Try to relocate the block close to other blocks for the first stream.
  1.1110 +      // There is no point in trying to make it close to other blocks in
  1.1111 +      // *all* the streams it might belong to.
  1.1112 +      int32_t destinationBlockIndex =
  1.1113 +        FindReusableBlock(now, block->mOwners[0].mStream,
  1.1114 +                          block->mOwners[0].mStreamBlock, maxBlocks);
  1.1115 +      if (destinationBlockIndex < 0) {
  1.1116 +        // Nowhere to place this overflow block. We won't be able to
  1.1117 +        // place any more overflow blocks.
  1.1118 +        break;
  1.1119 +      }
  1.1120 +
  1.1121 +      if (IsBlockFree(destinationBlockIndex) ||
  1.1122 +          PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
  1.1123 +        // Reuse blocks in the main part of the cache that are less useful than
  1.1124 +        // the least useful overflow blocks
  1.1125 +
  1.1126 +        nsresult rv = mFileCache->MoveBlock(blockIndex, destinationBlockIndex);
  1.1127 +
  1.1128 +        if (NS_SUCCEEDED(rv)) {
  1.1129 +          // We successfully copied the file data.
  1.1130 +          CACHE_LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
  1.1131 +                    blockIndex, destinationBlockIndex));
  1.1132 +          // Swapping the block metadata here lets us maintain the
  1.1133 +          // correct positions in the linked lists
  1.1134 +          SwapBlocks(blockIndex, destinationBlockIndex);
  1.1135 +          //Free the overflowing block even if the copy failed.
  1.1136 +          CACHE_LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)", blockIndex));
  1.1137 +          FreeBlock(blockIndex);
  1.1138 +        }
  1.1139 +      } else {
  1.1140 +        CACHE_LOG(PR_LOG_DEBUG, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f",
  1.1141 +                                 blockIndex, destinationBlockIndex,
  1.1142 +                                 PredictNextUse(now, destinationBlockIndex).ToSeconds(),
  1.1143 +                                 latestPredictedUseForOverflow.ToSeconds()));
  1.1144 +      }
  1.1145 +    }
  1.1146 +    // Try chopping back the array of cache entries and the cache file.
  1.1147 +    Truncate();
  1.1148 +
  1.1149 +    // Count the blocks allocated for readahead of non-seekable streams
  1.1150 +    // (these blocks can't be freed but we don't want them to monopolize the
  1.1151 +    // cache)
  1.1152 +    int32_t nonSeekableReadaheadBlockCount = 0;
  1.1153 +    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
  1.1154 +      MediaCacheStream* stream = mStreams[i];
  1.1155 +      if (!stream->mIsTransportSeekable) {
  1.1156 +        nonSeekableReadaheadBlockCount += stream->mReadaheadBlocks.GetCount();
  1.1157 +      }
  1.1158 +    }
  1.1159 +
  1.1160 +    // If freeBlockCount is zero, then compute the latest of
  1.1161 +    // the predicted next-uses for all blocks
  1.1162 +    TimeDuration latestNextUse;
  1.1163 +    if (freeBlockCount == 0) {
  1.1164 +      int32_t reusableBlock = FindReusableBlock(now, nullptr, 0, maxBlocks);
  1.1165 +      if (reusableBlock >= 0) {
  1.1166 +        latestNextUse = PredictNextUse(now, reusableBlock);
  1.1167 +      }
  1.1168 +    }
  1.1169 +
  1.1170 +    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
  1.1171 +      actions.AppendElement(NONE);
  1.1172 +
  1.1173 +      MediaCacheStream* stream = mStreams[i];
  1.1174 +      if (stream->mClosed)
  1.1175 +        continue;
  1.1176 +
  1.1177 +      // Figure out where we should be reading from. It's the first
  1.1178 +      // uncached byte after the current mStreamOffset.
  1.1179 +      int64_t dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
  1.1180 +      MOZ_ASSERT(dataOffset >= 0);
  1.1181 +
  1.1182 +      // Compute where we'd actually seek to to read at readOffset
  1.1183 +      int64_t desiredOffset = dataOffset;
  1.1184 +      if (stream->mIsTransportSeekable) {
  1.1185 +        if (desiredOffset > stream->mChannelOffset &&
  1.1186 +            desiredOffset <= stream->mChannelOffset + SEEK_VS_READ_THRESHOLD) {
  1.1187 +          // Assume it's more efficient to just keep reading up to the
  1.1188 +          // desired position instead of trying to seek
  1.1189 +          desiredOffset = stream->mChannelOffset;
  1.1190 +        }
  1.1191 +      } else {
  1.1192 +        // We can't seek directly to the desired offset...
  1.1193 +        if (stream->mChannelOffset > desiredOffset) {
  1.1194 +          // Reading forward won't get us anywhere, we need to go backwards.
  1.1195 +          // Seek back to 0 (the client will reopen the stream) and then
  1.1196 +          // read forward.
  1.1197 +          NS_WARNING("Can't seek backwards, so seeking to 0");
  1.1198 +          desiredOffset = 0;
  1.1199 +          // Flush cached blocks out, since if this is a live stream
  1.1200 +          // the cached data may be completely different next time we
  1.1201 +          // read it. We have to assume that live streams don't
  1.1202 +          // advertise themselves as being seekable...
  1.1203 +          ReleaseStreamBlocks(stream);
  1.1204 +        } else {
  1.1205 +          // otherwise reading forward is looking good, so just stay where we
  1.1206 +          // are and don't trigger a channel seek!
  1.1207 +          desiredOffset = stream->mChannelOffset;
  1.1208 +        }
  1.1209 +      }
  1.1210 +
  1.1211 +      // Figure out if we should be reading data now or not. It's amazing
  1.1212 +      // how complex this is, but each decision is simple enough.
  1.1213 +      bool enableReading;
  1.1214 +      if (stream->mStreamLength >= 0 && dataOffset >= stream->mStreamLength) {
  1.1215 +        // We want data at the end of the stream, where there's nothing to
  1.1216 +        // read. We don't want to try to read if we're suspended, because that
  1.1217 +        // might create a new channel and seek unnecessarily (and incorrectly,
  1.1218 +        // since HTTP doesn't allow seeking to the actual EOF), and we don't want
  1.1219 +        // to suspend if we're not suspended and already reading at the end of
  1.1220 +        // the stream, since there just might be more data than the server
  1.1221 +        // advertised with Content-Length, and we may as well keep reading.
  1.1222 +        // But we don't want to seek to the end of the stream if we're not
  1.1223 +        // already there.
  1.1224 +        CACHE_LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream));
  1.1225 +        enableReading = !stream->mCacheSuspended &&
  1.1226 +          stream->mStreamLength == stream->mChannelOffset;
  1.1227 +      } else if (desiredOffset < stream->mStreamOffset) {
  1.1228 +        // We're reading to try to catch up to where the current stream
  1.1229 +        // reader wants to be. Better not stop.
  1.1230 +        CACHE_LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream));
  1.1231 +        enableReading = true;
  1.1232 +      } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) {
  1.1233 +        // The stream reader is waiting for us, or nearly so. Better feed it.
  1.1234 +        CACHE_LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream));
  1.1235 +        enableReading = true;
  1.1236 +      } else if (!stream->mIsTransportSeekable &&
  1.1237 +                 nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) {
  1.1238 +        // This stream is not seekable and there are already too many blocks
  1.1239 +        // being cached for readahead for nonseekable streams (which we can't
  1.1240 +        // free). So stop reading ahead now.
  1.1241 +        CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream));
  1.1242 +        enableReading = false;
  1.1243 +      } else if (mIndex.Length() > uint32_t(maxBlocks)) {
  1.1244 +        // We're in the process of bringing the cache size back to the
  1.1245 +        // desired limit, so don't bring in more data yet
  1.1246 +        CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream));
  1.1247 +        enableReading = false;
  1.1248 +      } else {
  1.1249 +        TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream);
  1.1250 +
  1.1251 +        if (stream->mCacheSuspended &&
  1.1252 +            predictedNewDataUse.ToMilliseconds() > CACHE_POWERSAVE_WAKEUP_LOW_THRESHOLD_MS) {
  1.1253 +          // Don't need data for a while, so don't bother waking up the stream
  1.1254 +          CACHE_LOG(PR_LOG_DEBUG, ("Stream %p avoiding wakeup since more data is not needed", stream));
  1.1255 +          enableReading = false;
  1.1256 +        } else if (freeBlockCount > 0) {
  1.1257 +          // Free blocks in the cache, so keep reading
  1.1258 +          CACHE_LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream));
  1.1259 +          enableReading = true;
  1.1260 +        } else if (latestNextUse <= TimeDuration(0)) {
  1.1261 +          // No reusable blocks, so can't read anything
  1.1262 +          CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream));
  1.1263 +          enableReading = false;
  1.1264 +        } else {
  1.1265 +          // Read ahead if the data we expect to read is more valuable than
  1.1266 +          // the least valuable block in the main part of the cache
  1.1267 +          CACHE_LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %f, current worst block is %f",
  1.1268 +                    stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds()));
  1.1269 +          enableReading = predictedNewDataUse < latestNextUse;
  1.1270 +        }
  1.1271 +      }
  1.1272 +
  1.1273 +      if (enableReading) {
  1.1274 +        for (uint32_t j = 0; j < i; ++j) {
  1.1275 +          MediaCacheStream* other = mStreams[j];
  1.1276 +          if (other->mResourceID == stream->mResourceID &&
  1.1277 +              !other->mClient->IsSuspended() &&
  1.1278 +              other->mChannelOffset/BLOCK_SIZE == desiredOffset/BLOCK_SIZE) {
  1.1279 +            // This block is already going to be read by the other stream.
  1.1280 +            // So don't try to read it from this stream as well.
  1.1281 +            enableReading = false;
  1.1282 +            CACHE_LOG(PR_LOG_DEBUG, ("Stream %p waiting on same block (%lld) from stream %p",
  1.1283 +                                     stream, desiredOffset/BLOCK_SIZE, other));
  1.1284 +            break;
  1.1285 +          }
  1.1286 +        }
  1.1287 +      }
  1.1288 +
  1.1289 +      if (stream->mChannelOffset != desiredOffset && enableReading) {
  1.1290 +        // We need to seek now.
  1.1291 +        NS_ASSERTION(stream->mIsTransportSeekable || desiredOffset == 0,
  1.1292 +                     "Trying to seek in a non-seekable stream!");
  1.1293 +        // Round seek offset down to the start of the block. This is essential
  1.1294 +        // because we don't want to think we have part of a block already
  1.1295 +        // in mPartialBlockBuffer.
  1.1296 +        stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE;
  1.1297 +        actions[i] = stream->mCacheSuspended ? SEEK_AND_RESUME : SEEK;
  1.1298 +      } else if (enableReading && stream->mCacheSuspended) {
  1.1299 +        actions[i] = RESUME;
  1.1300 +      } else if (!enableReading && !stream->mCacheSuspended) {
  1.1301 +        actions[i] = SUSPEND;
  1.1302 +      }
  1.1303 +    }
  1.1304 +#ifdef DEBUG
  1.1305 +    mInUpdate = false;
  1.1306 +#endif
  1.1307 +  }
  1.1308 +
  1.1309 +  // Update the channel state without holding our cache lock. While we're
  1.1310 +  // doing this, decoder threads may be running and seeking, reading or changing
  1.1311 +  // other cache state. That's OK, they'll trigger new Update events and we'll
  1.1312 +  // get back here and revise our decisions. The important thing here is that
  1.1313 +  // performing these actions only depends on mChannelOffset and
  1.1314 +  // the action, which can only be written by the main thread (i.e., this
  1.1315 +  // thread), so we don't have races here.
  1.1316 +
  1.1317 +  // First, update the mCacheSuspended/mCacheEnded flags so that they're all correct
  1.1318 +  // when we fire our CacheClient commands below. Those commands can rely on these flags
  1.1319 +  // being set correctly for all streams.
  1.1320 +  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
  1.1321 +    MediaCacheStream* stream = mStreams[i];
  1.1322 +    switch (actions[i]) {
  1.1323 +    case SEEK:
  1.1324 +	case SEEK_AND_RESUME:
  1.1325 +      stream->mCacheSuspended = false;
  1.1326 +      stream->mChannelEnded = false;
  1.1327 +      break;
  1.1328 +    case RESUME:
  1.1329 +      stream->mCacheSuspended = false;
  1.1330 +      break;
  1.1331 +    case SUSPEND:
  1.1332 +      stream->mCacheSuspended = true;
  1.1333 +      break;
  1.1334 +    default:
  1.1335 +      break;
  1.1336 +    }
  1.1337 +    stream->mHasHadUpdate = true;
  1.1338 +  }
  1.1339 +
  1.1340 +  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
  1.1341 +    MediaCacheStream* stream = mStreams[i];
  1.1342 +    nsresult rv;
  1.1343 +    switch (actions[i]) {
  1.1344 +    case SEEK:
  1.1345 +	case SEEK_AND_RESUME:
  1.1346 +      CACHE_LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream,
  1.1347 +                (long long)stream->mChannelOffset, actions[i] == SEEK_AND_RESUME));
  1.1348 +      rv = stream->mClient->CacheClientSeek(stream->mChannelOffset,
  1.1349 +                                            actions[i] == SEEK_AND_RESUME);
  1.1350 +      break;
  1.1351 +    case RESUME:
  1.1352 +      CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream));
  1.1353 +      rv = stream->mClient->CacheClientResume();
  1.1354 +      break;
  1.1355 +    case SUSPEND:
  1.1356 +      CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream));
  1.1357 +      rv = stream->mClient->CacheClientSuspend();
  1.1358 +      break;
  1.1359 +    default:
  1.1360 +      rv = NS_OK;
  1.1361 +      break;
  1.1362 +    }
  1.1363 +
  1.1364 +    if (NS_FAILED(rv)) {
  1.1365 +      // Close the streams that failed due to error. This will cause all
  1.1366 +      // client Read and Seek operations on those streams to fail. Blocked
  1.1367 +      // Reads will also be woken up.
  1.1368 +      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  1.1369 +      stream->CloseInternal(mon);
  1.1370 +    }
  1.1371 +  }
  1.1372 +}
  1.1373 +
  1.1374 +class UpdateEvent : public nsRunnable
  1.1375 +{
  1.1376 +public:
  1.1377 +  NS_IMETHOD Run()
  1.1378 +  {
  1.1379 +    if (gMediaCache) {
  1.1380 +      gMediaCache->Update();
  1.1381 +    }
  1.1382 +    return NS_OK;
  1.1383 +  }
  1.1384 +};
  1.1385 +
  1.1386 +void
  1.1387 +MediaCache::QueueUpdate()
  1.1388 +{
  1.1389 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1390 +
  1.1391 +  // Queuing an update while we're in an update raises a high risk of
  1.1392 +  // triggering endless events
  1.1393 +  NS_ASSERTION(!mInUpdate,
  1.1394 +               "Queuing an update while we're in an update");
  1.1395 +  if (mUpdateQueued)
  1.1396 +    return;
  1.1397 +  mUpdateQueued = true;
  1.1398 +  nsCOMPtr<nsIRunnable> event = new UpdateEvent();
  1.1399 +  NS_DispatchToMainThread(event);
  1.1400 +}
  1.1401 +
  1.1402 +#ifdef DEBUG_VERIFY_CACHE
  1.1403 +void
  1.1404 +MediaCache::Verify()
  1.1405 +{
  1.1406 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1407 +
  1.1408 +  mFreeBlocks.Verify();
  1.1409 +  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
  1.1410 +    MediaCacheStream* stream = mStreams[i];
  1.1411 +    stream->mReadaheadBlocks.Verify();
  1.1412 +    stream->mPlayedBlocks.Verify();
  1.1413 +    stream->mMetadataBlocks.Verify();
  1.1414 +
  1.1415 +    // Verify that the readahead blocks are listed in stream block order
  1.1416 +    int32_t block = stream->mReadaheadBlocks.GetFirstBlock();
  1.1417 +    int32_t lastStreamBlock = -1;
  1.1418 +    while (block >= 0) {
  1.1419 +      uint32_t j = 0;
  1.1420 +      while (mIndex[block].mOwners[j].mStream != stream) {
  1.1421 +        ++j;
  1.1422 +      }
  1.1423 +      int32_t nextStreamBlock =
  1.1424 +        int32_t(mIndex[block].mOwners[j].mStreamBlock);
  1.1425 +      NS_ASSERTION(lastStreamBlock < nextStreamBlock,
  1.1426 +                   "Blocks not increasing in readahead stream");
  1.1427 +      lastStreamBlock = nextStreamBlock;
  1.1428 +      block = stream->mReadaheadBlocks.GetNextBlock(block);
  1.1429 +    }
  1.1430 +  }
  1.1431 +}
  1.1432 +#endif
  1.1433 +
  1.1434 +void
  1.1435 +MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
  1.1436 +                                   int32_t aBlockIndex)
  1.1437 +{
  1.1438 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1439 +
  1.1440 +  // Find the last block whose stream block is before aBlockIndex's
  1.1441 +  // stream block, and insert after it
  1.1442 +  MediaCacheStream* stream = aBlockOwner->mStream;
  1.1443 +  int32_t readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
  1.1444 +  while (readaheadIndex >= 0) {
  1.1445 +    BlockOwner* bo = GetBlockOwner(readaheadIndex, stream);
  1.1446 +    NS_ASSERTION(bo, "stream must own its blocks");
  1.1447 +    if (bo->mStreamBlock < aBlockOwner->mStreamBlock) {
  1.1448 +      stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
  1.1449 +      return;
  1.1450 +    }
  1.1451 +    NS_ASSERTION(bo->mStreamBlock > aBlockOwner->mStreamBlock,
  1.1452 +                 "Duplicated blocks??");
  1.1453 +    readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex);
  1.1454 +  }
  1.1455 +
  1.1456 +  stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
  1.1457 +  Verify();
  1.1458 +}
  1.1459 +
  1.1460 +void
  1.1461 +MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream, const void* aData,
  1.1462 +                                    MediaCacheStream::ReadMode aMode)
  1.1463 +{
  1.1464 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1465 +
  1.1466 +  int32_t streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
  1.1467 +
  1.1468 +  // Remove all cached copies of this block
  1.1469 +  ResourceStreamIterator iter(aStream->mResourceID);
  1.1470 +  while (MediaCacheStream* stream = iter.Next()) {
  1.1471 +    while (streamBlockIndex >= int32_t(stream->mBlocks.Length())) {
  1.1472 +      stream->mBlocks.AppendElement(-1);
  1.1473 +    }
  1.1474 +    if (stream->mBlocks[streamBlockIndex] >= 0) {
  1.1475 +      // We no longer want to own this block
  1.1476 +      int32_t globalBlockIndex = stream->mBlocks[streamBlockIndex];
  1.1477 +      CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
  1.1478 +                globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
  1.1479 +      RemoveBlockOwner(globalBlockIndex, stream);
  1.1480 +    }
  1.1481 +  }
  1.1482 +
  1.1483 +  // Extend the mBlocks array as necessary
  1.1484 +
  1.1485 +  TimeStamp now = TimeStamp::Now();
  1.1486 +  int32_t blockIndex = FindBlockForIncomingData(now, aStream);
  1.1487 +  if (blockIndex >= 0) {
  1.1488 +    FreeBlock(blockIndex);
  1.1489 +
  1.1490 +    Block* block = &mIndex[blockIndex];
  1.1491 +    CACHE_LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
  1.1492 +              blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
  1.1493 +
  1.1494 +    mFreeBlocks.RemoveBlock(blockIndex);
  1.1495 +
  1.1496 +    // Tell each stream using this resource about the new block.
  1.1497 +    ResourceStreamIterator iter(aStream->mResourceID);
  1.1498 +    while (MediaCacheStream* stream = iter.Next()) {
  1.1499 +      BlockOwner* bo = block->mOwners.AppendElement();
  1.1500 +      if (!bo)
  1.1501 +        return;
  1.1502 +
  1.1503 +      bo->mStream = stream;
  1.1504 +      bo->mStreamBlock = streamBlockIndex;
  1.1505 +      bo->mLastUseTime = now;
  1.1506 +      stream->mBlocks[streamBlockIndex] = blockIndex;
  1.1507 +      if (streamBlockIndex*BLOCK_SIZE < stream->mStreamOffset) {
  1.1508 +        bo->mClass = aMode == MediaCacheStream::MODE_PLAYBACK
  1.1509 +          ? PLAYED_BLOCK : METADATA_BLOCK;
  1.1510 +        // This must be the most-recently-used block, since we
  1.1511 +        // marked it as used now (which may be slightly bogus, but we'll
  1.1512 +        // treat it as used for simplicity).
  1.1513 +        GetListForBlock(bo)->AddFirstBlock(blockIndex);
  1.1514 +        Verify();
  1.1515 +      } else {
  1.1516 +        // This may not be the latest readahead block, although it usually
  1.1517 +        // will be. We may have to scan for the right place to insert
  1.1518 +        // the block in the list.
  1.1519 +        bo->mClass = READAHEAD_BLOCK;
  1.1520 +        InsertReadaheadBlock(bo, blockIndex);
  1.1521 +      }
  1.1522 +    }
  1.1523 +
  1.1524 +    nsresult rv = mFileCache->WriteBlock(blockIndex, reinterpret_cast<const uint8_t*>(aData));
  1.1525 +    if (NS_FAILED(rv)) {
  1.1526 +      CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
  1.1527 +                blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
  1.1528 +      FreeBlock(blockIndex);
  1.1529 +    }
  1.1530 +  }
  1.1531 +
  1.1532 +  // Queue an Update since the cache state has changed (for example
  1.1533 +  // we might want to stop loading because the cache is full)
  1.1534 +  QueueUpdate();
  1.1535 +}
  1.1536 +
  1.1537 +void
  1.1538 +MediaCache::OpenStream(MediaCacheStream* aStream)
  1.1539 +{
  1.1540 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1541 +
  1.1542 +  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  1.1543 +  CACHE_LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream));
  1.1544 +  mStreams.AppendElement(aStream);
  1.1545 +  aStream->mResourceID = AllocateResourceID();
  1.1546 +
  1.1547 +  // Queue an update since a new stream has been opened.
  1.1548 +  gMediaCache->QueueUpdate();
  1.1549 +}
  1.1550 +
  1.1551 +void
  1.1552 +MediaCache::ReleaseStream(MediaCacheStream* aStream)
  1.1553 +{
  1.1554 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1555 +
  1.1556 +  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
  1.1557 +  CACHE_LOG(PR_LOG_DEBUG, ("Stream %p closed", aStream));
  1.1558 +  mStreams.RemoveElement(aStream);
  1.1559 +
  1.1560 +  // Update MediaCache again for |mStreams| is changed.
  1.1561 +  // We need to re-run Update() to ensure streams reading from the same resource
  1.1562 +  // as the removed stream get a chance to continue reading.
  1.1563 +  gMediaCache->QueueUpdate();
  1.1564 +}
  1.1565 +
  1.1566 +void
  1.1567 +MediaCache::ReleaseStreamBlocks(MediaCacheStream* aStream)
  1.1568 +{
  1.1569 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1570 +
  1.1571 +  // XXX scanning the entire stream doesn't seem great, if not much of it
  1.1572 +  // is cached, but the only easy alternative is to scan the entire cache
  1.1573 +  // which isn't better
  1.1574 +  uint32_t length = aStream->mBlocks.Length();
  1.1575 +  for (uint32_t i = 0; i < length; ++i) {
  1.1576 +    int32_t blockIndex = aStream->mBlocks[i];
  1.1577 +    if (blockIndex >= 0) {
  1.1578 +      CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
  1.1579 +                blockIndex, aStream, i, (long long)i*BLOCK_SIZE));
  1.1580 +      RemoveBlockOwner(blockIndex, aStream);
  1.1581 +    }
  1.1582 +  }
  1.1583 +}
  1.1584 +
  1.1585 +void
  1.1586 +MediaCache::Truncate()
  1.1587 +{
  1.1588 +  uint32_t end;
  1.1589 +  for (end = mIndex.Length(); end > 0; --end) {
  1.1590 +    if (!IsBlockFree(end - 1))
  1.1591 +      break;
  1.1592 +    mFreeBlocks.RemoveBlock(end - 1);
  1.1593 +  }
  1.1594 +
  1.1595 +  if (end < mIndex.Length()) {
  1.1596 +    mIndex.TruncateLength(end);
  1.1597 +    // XXX We could truncate the cache file here, but we don't seem
  1.1598 +    // to have a cross-platform API for doing that. At least when all
  1.1599 +    // streams are closed we shut down the cache, which erases the
  1.1600 +    // file at that point.
  1.1601 +  }
  1.1602 +}
  1.1603 +
  1.1604 +void
  1.1605 +MediaCache::NoteBlockUsage(MediaCacheStream* aStream, int32_t aBlockIndex,
  1.1606 +                             MediaCacheStream::ReadMode aMode,
  1.1607 +                             TimeStamp aNow)
  1.1608 +{
  1.1609 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1610 +
  1.1611 +  if (aBlockIndex < 0) {
  1.1612 +    // this block is not in the cache yet
  1.1613 +    return;
  1.1614 +  }
  1.1615 +
  1.1616 +  BlockOwner* bo = GetBlockOwner(aBlockIndex, aStream);
  1.1617 +  if (!bo) {
  1.1618 +    // this block is not in the cache yet
  1.1619 +    return;
  1.1620 +  }
  1.1621 +
  1.1622 +  // The following check has to be <= because the stream offset has
  1.1623 +  // not yet been updated for the data read from this block
  1.1624 +  NS_ASSERTION(bo->mStreamBlock*BLOCK_SIZE <= bo->mStream->mStreamOffset,
  1.1625 +               "Using a block that's behind the read position?");
  1.1626 +
  1.1627 +  GetListForBlock(bo)->RemoveBlock(aBlockIndex);
  1.1628 +  bo->mClass =
  1.1629 +    (aMode == MediaCacheStream::MODE_METADATA || bo->mClass == METADATA_BLOCK)
  1.1630 +    ? METADATA_BLOCK : PLAYED_BLOCK;
  1.1631 +  // Since this is just being used now, it can definitely be at the front
  1.1632 +  // of mMetadataBlocks or mPlayedBlocks
  1.1633 +  GetListForBlock(bo)->AddFirstBlock(aBlockIndex);
  1.1634 +  bo->mLastUseTime = aNow;
  1.1635 +  Verify();
  1.1636 +}
  1.1637 +
  1.1638 +void
  1.1639 +MediaCache::NoteSeek(MediaCacheStream* aStream, int64_t aOldOffset)
  1.1640 +{
  1.1641 +  mReentrantMonitor.AssertCurrentThreadIn();
  1.1642 +
  1.1643 +  if (aOldOffset < aStream->mStreamOffset) {
  1.1644 +    // We seeked forward. Convert blocks from readahead to played.
  1.1645 +    // Any readahead block that intersects the seeked-over range must
  1.1646 +    // be converted.
  1.1647 +    int32_t blockIndex = aOldOffset/BLOCK_SIZE;
  1.1648 +    int32_t endIndex =
  1.1649 +      std::min<int64_t>((aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
  1.1650 +             aStream->mBlocks.Length());
  1.1651 +    TimeStamp now = TimeStamp::Now();
  1.1652 +    while (blockIndex < endIndex) {
  1.1653 +      int32_t cacheBlockIndex = aStream->mBlocks[blockIndex];
  1.1654 +      if (cacheBlockIndex >= 0) {
  1.1655 +        // Marking the block used may not be exactly what we want but
  1.1656 +        // it's simple
  1.1657 +        NoteBlockUsage(aStream, cacheBlockIndex, MediaCacheStream::MODE_PLAYBACK,
  1.1658 +                       now);
  1.1659 +      }
  1.1660 +      ++blockIndex;
  1.1661 +    }
  1.1662 +  } else {
  1.1663 +    // We seeked backward. Convert from played to readahead.
  1.1664 +    // Any played block that is entirely after the start of the seeked-over
  1.1665 +    // range must be converted.
  1.1666 +    int32_t blockIndex =
  1.1667 +      (aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE;
  1.1668 +    int32_t endIndex =
  1.1669 +      std::min<int64_t>((aOldOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
  1.1670 +             aStream->mBlocks.Length());
  1.1671 +    while (blockIndex < endIndex) {
  1.1672 +      int32_t cacheBlockIndex = aStream->mBlocks[endIndex - 1];
  1.1673 +      if (cacheBlockIndex >= 0) {
  1.1674 +        BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
  1.1675 +        NS_ASSERTION(bo, "Stream doesn't own its blocks?");
  1.1676 +        if (bo->mClass == PLAYED_BLOCK) {
  1.1677 +          aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
  1.1678 +          bo->mClass = READAHEAD_BLOCK;
  1.1679 +          // Adding this as the first block is sure to be OK since
  1.1680 +          // this must currently be the earliest readahead block
  1.1681 +          // (that's why we're proceeding backwards from the end of
  1.1682 +          // the seeked range to the start)
  1.1683 +          aStream->mReadaheadBlocks.AddFirstBlock(cacheBlockIndex);
  1.1684 +          Verify();
  1.1685 +        }
  1.1686 +      }
  1.1687 +      --endIndex;
  1.1688 +    }
  1.1689 +  }
  1.1690 +}
  1.1691 +
  1.1692 +void
  1.1693 +MediaCacheStream::NotifyDataLength(int64_t aLength)
  1.1694 +{
  1.1695 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1696 +
  1.1697 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1698 +  mStreamLength = aLength;
  1.1699 +}
  1.1700 +
  1.1701 +void
  1.1702 +MediaCacheStream::NotifyDataStarted(int64_t aOffset)
  1.1703 +{
  1.1704 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1705 +
  1.1706 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1707 +  NS_WARN_IF_FALSE(aOffset == mChannelOffset,
  1.1708 +                   "Server is giving us unexpected offset");
  1.1709 +  MOZ_ASSERT(aOffset >= 0);
  1.1710 +  mChannelOffset = aOffset;
  1.1711 +  if (mStreamLength >= 0) {
  1.1712 +    // If we started reading at a certain offset, then for sure
  1.1713 +    // the stream is at least that long.
  1.1714 +    mStreamLength = std::max(mStreamLength, mChannelOffset);
  1.1715 +  }
  1.1716 +}
  1.1717 +
  1.1718 +bool
  1.1719 +MediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
  1.1720 +{
  1.1721 +  return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
  1.1722 +}
  1.1723 +
  1.1724 +void
  1.1725 +MediaCacheStream::NotifyDataReceived(int64_t aSize, const char* aData,
  1.1726 +    nsIPrincipal* aPrincipal)
  1.1727 +{
  1.1728 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1729 +
  1.1730 +  // Update principals before putting the data in the cache. This is important,
  1.1731 +  // we want to make sure all principals are updated before any consumer
  1.1732 +  // can see the new data.
  1.1733 +  // We do this without holding the cache monitor, in case the client wants
  1.1734 +  // to do something that takes a lock.
  1.1735 +  {
  1.1736 +    MediaCache::ResourceStreamIterator iter(mResourceID);
  1.1737 +    while (MediaCacheStream* stream = iter.Next()) {
  1.1738 +      if (stream->UpdatePrincipal(aPrincipal)) {
  1.1739 +        stream->mClient->CacheClientNotifyPrincipalChanged();
  1.1740 +      }
  1.1741 +    }
  1.1742 +  }
  1.1743 +
  1.1744 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1745 +  int64_t size = aSize;
  1.1746 +  const char* data = aData;
  1.1747 +
  1.1748 +  CACHE_LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld",
  1.1749 +            this, (long long)mChannelOffset, (long long)aSize));
  1.1750 +
  1.1751 +  // We process the data one block (or part of a block) at a time
  1.1752 +  while (size > 0) {
  1.1753 +    uint32_t blockIndex = mChannelOffset/BLOCK_SIZE;
  1.1754 +    int32_t blockOffset = int32_t(mChannelOffset - blockIndex*BLOCK_SIZE);
  1.1755 +    int32_t chunkSize = std::min<int64_t>(BLOCK_SIZE - blockOffset, size);
  1.1756 +
  1.1757 +    // This gets set to something non-null if we have a whole block
  1.1758 +    // of data to write to the cache
  1.1759 +    const char* blockDataToStore = nullptr;
  1.1760 +    ReadMode mode = MODE_PLAYBACK;
  1.1761 +    if (blockOffset == 0 && chunkSize == BLOCK_SIZE) {
  1.1762 +      // We received a whole block, so avoid a useless copy through
  1.1763 +      // mPartialBlockBuffer
  1.1764 +      blockDataToStore = data;
  1.1765 +    } else {
  1.1766 +      if (blockOffset == 0) {
  1.1767 +        // We've just started filling this buffer so now is a good time
  1.1768 +        // to clear this flag.
  1.1769 +        mMetadataInPartialBlockBuffer = false;
  1.1770 +      }
  1.1771 +      memcpy(reinterpret_cast<char*>(mPartialBlockBuffer.get()) + blockOffset,
  1.1772 +             data, chunkSize);
  1.1773 +
  1.1774 +      if (blockOffset + chunkSize == BLOCK_SIZE) {
  1.1775 +        // We completed a block, so lets write it out.
  1.1776 +        blockDataToStore = reinterpret_cast<char*>(mPartialBlockBuffer.get());
  1.1777 +        if (mMetadataInPartialBlockBuffer) {
  1.1778 +          mode = MODE_METADATA;
  1.1779 +        }
  1.1780 +      }
  1.1781 +    }
  1.1782 +
  1.1783 +    if (blockDataToStore) {
  1.1784 +      gMediaCache->AllocateAndWriteBlock(this, blockDataToStore, mode);
  1.1785 +    }
  1.1786 +
  1.1787 +    mChannelOffset += chunkSize;
  1.1788 +    size -= chunkSize;
  1.1789 +    data += chunkSize;
  1.1790 +  }
  1.1791 +
  1.1792 +  MediaCache::ResourceStreamIterator iter(mResourceID);
  1.1793 +  while (MediaCacheStream* stream = iter.Next()) {
  1.1794 +    if (stream->mStreamLength >= 0) {
  1.1795 +      // The stream is at least as long as what we've read
  1.1796 +      stream->mStreamLength = std::max(stream->mStreamLength, mChannelOffset);
  1.1797 +    }
  1.1798 +    stream->mClient->CacheClientNotifyDataReceived();
  1.1799 +  }
  1.1800 +
  1.1801 +  // Notify in case there's a waiting reader
  1.1802 +  // XXX it would be fairly easy to optimize things a lot more to
  1.1803 +  // avoid waking up reader threads unnecessarily
  1.1804 +  mon.NotifyAll();
  1.1805 +}
  1.1806 +
  1.1807 +void
  1.1808 +MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll)
  1.1809 +{
  1.1810 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1811 +
  1.1812 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1813 +
  1.1814 +  int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
  1.1815 +  if (blockOffset > 0) {
  1.1816 +    CACHE_LOG(PR_LOG_DEBUG,
  1.1817 +              ("Stream %p writing partial block: [%d] bytes; "
  1.1818 +               "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
  1.1819 +               "notifying: [%s]",
  1.1820 +               this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
  1.1821 +               aNotifyAll ? "yes" : "no"));
  1.1822 +
  1.1823 +    // Write back the partial block
  1.1824 +    memset(reinterpret_cast<char*>(mPartialBlockBuffer.get()) + blockOffset, 0,
  1.1825 +           BLOCK_SIZE - blockOffset);
  1.1826 +    gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
  1.1827 +        mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
  1.1828 +    if (aNotifyAll) {
  1.1829 +      // Wake up readers who may be waiting for this data
  1.1830 +      mon.NotifyAll();
  1.1831 +    }
  1.1832 +  }
  1.1833 +}
  1.1834 +
  1.1835 +void
  1.1836 +MediaCacheStream::FlushPartialBlock()
  1.1837 +{
  1.1838 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1839 +
  1.1840 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1841 +
  1.1842 +  // Write the current partial block to memory.
  1.1843 +  // Note: This writes a full block, so if data is not at the end of the
  1.1844 +  // stream, the decoder must subsequently choose correct start and end offsets
  1.1845 +  // for reading/seeking.
  1.1846 +  FlushPartialBlockInternal(false);
  1.1847 +
  1.1848 +  gMediaCache->QueueUpdate();
  1.1849 +}
  1.1850 +
  1.1851 +void
  1.1852 +MediaCacheStream::NotifyDataEnded(nsresult aStatus)
  1.1853 +{
  1.1854 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1855 +
  1.1856 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1857 +
  1.1858 +  if (NS_FAILED(aStatus)) {
  1.1859 +    // Disconnect from other streams sharing our resource, since they
  1.1860 +    // should continue trying to load. Our load might have been deliberately
  1.1861 +    // canceled and that shouldn't affect other streams.
  1.1862 +    mResourceID = gMediaCache->AllocateResourceID();
  1.1863 +  }
  1.1864 +
  1.1865 +  FlushPartialBlockInternal(true);
  1.1866 +
  1.1867 +  if (!mDidNotifyDataEnded) {
  1.1868 +    MediaCache::ResourceStreamIterator iter(mResourceID);
  1.1869 +    while (MediaCacheStream* stream = iter.Next()) {
  1.1870 +      if (NS_SUCCEEDED(aStatus)) {
  1.1871 +        // We read the whole stream, so remember the true length
  1.1872 +        stream->mStreamLength = mChannelOffset;
  1.1873 +      }
  1.1874 +      NS_ASSERTION(!stream->mDidNotifyDataEnded, "Stream already ended!");
  1.1875 +      stream->mDidNotifyDataEnded = true;
  1.1876 +      stream->mNotifyDataEndedStatus = aStatus;
  1.1877 +      stream->mClient->CacheClientNotifyDataEnded(aStatus);
  1.1878 +    }
  1.1879 +  }
  1.1880 +
  1.1881 +  mChannelEnded = true;
  1.1882 +  gMediaCache->QueueUpdate();
  1.1883 +}
  1.1884 +
  1.1885 +MediaCacheStream::~MediaCacheStream()
  1.1886 +{
  1.1887 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1888 +  NS_ASSERTION(!mPinCount, "Unbalanced Pin");
  1.1889 +
  1.1890 +  if (gMediaCache) {
  1.1891 +    NS_ASSERTION(mClosed, "Stream was not closed");
  1.1892 +    gMediaCache->ReleaseStream(this);
  1.1893 +    MediaCache::MaybeShutdown();
  1.1894 +  }
  1.1895 +}
  1.1896 +
  1.1897 +void
  1.1898 +MediaCacheStream::SetTransportSeekable(bool aIsTransportSeekable)
  1.1899 +{
  1.1900 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1901 +  NS_ASSERTION(mIsTransportSeekable || aIsTransportSeekable ||
  1.1902 +               mChannelOffset == 0, "channel offset must be zero when we become non-seekable");
  1.1903 +  mIsTransportSeekable = aIsTransportSeekable;
  1.1904 +  // Queue an Update since we may change our strategy for dealing
  1.1905 +  // with this stream
  1.1906 +  gMediaCache->QueueUpdate();
  1.1907 +}
  1.1908 +
  1.1909 +bool
  1.1910 +MediaCacheStream::IsTransportSeekable()
  1.1911 +{
  1.1912 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1913 +  return mIsTransportSeekable;
  1.1914 +}
  1.1915 +
  1.1916 +bool
  1.1917 +MediaCacheStream::AreAllStreamsForResourceSuspended()
  1.1918 +{
  1.1919 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1920 +  MediaCache::ResourceStreamIterator iter(mResourceID);
  1.1921 +  // Look for a stream that's able to read the data we need
  1.1922 +  int64_t dataOffset = -1;
  1.1923 +  while (MediaCacheStream* stream = iter.Next()) {
  1.1924 +    if (stream->mCacheSuspended || stream->mChannelEnded || stream->mClosed) {
  1.1925 +      continue;
  1.1926 +    }
  1.1927 +    if (dataOffset < 0) {
  1.1928 +      dataOffset = GetCachedDataEndInternal(mStreamOffset);
  1.1929 +    }
  1.1930 +    // Ignore streams that are reading beyond the data we need
  1.1931 +    if (stream->mChannelOffset > dataOffset) {
  1.1932 +      continue;
  1.1933 +    }
  1.1934 +    return false;
  1.1935 +  }
  1.1936 +
  1.1937 +  return true;
  1.1938 +}
  1.1939 +
  1.1940 +void
  1.1941 +MediaCacheStream::Close()
  1.1942 +{
  1.1943 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1944 +
  1.1945 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1946 +  CloseInternal(mon);
  1.1947 +  // Queue an Update since we may have created more free space. Don't do
  1.1948 +  // it from CloseInternal since that gets called by Update() itself
  1.1949 +  // sometimes, and we try to not to queue updates from Update().
  1.1950 +  gMediaCache->QueueUpdate();
  1.1951 +}
  1.1952 +
  1.1953 +void
  1.1954 +MediaCacheStream::EnsureCacheUpdate()
  1.1955 +{
  1.1956 +  if (mHasHadUpdate)
  1.1957 +    return;
  1.1958 +  gMediaCache->Update();
  1.1959 +}
  1.1960 +
  1.1961 +void
  1.1962 +MediaCacheStream::CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor)
  1.1963 +{
  1.1964 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.1965 +
  1.1966 +  if (mClosed)
  1.1967 +    return;
  1.1968 +  mClosed = true;
  1.1969 +  gMediaCache->ReleaseStreamBlocks(this);
  1.1970 +  // Wake up any blocked readers
  1.1971 +  aReentrantMonitor.NotifyAll();
  1.1972 +}
  1.1973 +
  1.1974 +void
  1.1975 +MediaCacheStream::Pin()
  1.1976 +{
  1.1977 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1978 +  ++mPinCount;
  1.1979 +  // Queue an Update since we may no longer want to read more into the
  1.1980 +  // cache, if this stream's block have become non-evictable
  1.1981 +  gMediaCache->QueueUpdate();
  1.1982 +}
  1.1983 +
  1.1984 +void
  1.1985 +MediaCacheStream::Unpin()
  1.1986 +{
  1.1987 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1988 +  NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
  1.1989 +  --mPinCount;
  1.1990 +  // Queue an Update since we may be able to read more into the
  1.1991 +  // cache, if this stream's block have become evictable
  1.1992 +  gMediaCache->QueueUpdate();
  1.1993 +}
  1.1994 +
  1.1995 +int64_t
  1.1996 +MediaCacheStream::GetLength()
  1.1997 +{
  1.1998 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.1999 +  return mStreamLength;
  1.2000 +}
  1.2001 +
  1.2002 +int64_t
  1.2003 +MediaCacheStream::GetNextCachedData(int64_t aOffset)
  1.2004 +{
  1.2005 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2006 +  return GetNextCachedDataInternal(aOffset);
  1.2007 +}
  1.2008 +
  1.2009 +int64_t
  1.2010 +MediaCacheStream::GetCachedDataEnd(int64_t aOffset)
  1.2011 +{
  1.2012 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2013 +  return GetCachedDataEndInternal(aOffset);
  1.2014 +}
  1.2015 +
  1.2016 +bool
  1.2017 +MediaCacheStream::IsDataCachedToEndOfStream(int64_t aOffset)
  1.2018 +{
  1.2019 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2020 +  if (mStreamLength < 0)
  1.2021 +    return false;
  1.2022 +  return GetCachedDataEndInternal(aOffset) >= mStreamLength;
  1.2023 +}
  1.2024 +
  1.2025 +int64_t
  1.2026 +MediaCacheStream::GetCachedDataEndInternal(int64_t aOffset)
  1.2027 +{
  1.2028 +  gMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
  1.2029 +  uint32_t startBlockIndex = aOffset/BLOCK_SIZE;
  1.2030 +  uint32_t blockIndex = startBlockIndex;
  1.2031 +  while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
  1.2032 +    ++blockIndex;
  1.2033 +  }
  1.2034 +  int64_t result = blockIndex*BLOCK_SIZE;
  1.2035 +  if (blockIndex == mChannelOffset/BLOCK_SIZE) {
  1.2036 +    // The block containing mChannelOffset may be partially read but not
  1.2037 +    // yet committed to the main cache
  1.2038 +    result = mChannelOffset;
  1.2039 +  }
  1.2040 +  if (mStreamLength >= 0) {
  1.2041 +    // The last block in the cache may only be partially valid, so limit
  1.2042 +    // the cached range to the stream length
  1.2043 +    result = std::min(result, mStreamLength);
  1.2044 +  }
  1.2045 +  return std::max(result, aOffset);
  1.2046 +}
  1.2047 +
  1.2048 +int64_t
  1.2049 +MediaCacheStream::GetNextCachedDataInternal(int64_t aOffset)
  1.2050 +{
  1.2051 +  gMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
  1.2052 +  if (aOffset == mStreamLength)
  1.2053 +    return -1;
  1.2054 +
  1.2055 +  uint32_t startBlockIndex = aOffset/BLOCK_SIZE;
  1.2056 +  uint32_t channelBlockIndex = mChannelOffset/BLOCK_SIZE;
  1.2057 +
  1.2058 +  if (startBlockIndex == channelBlockIndex &&
  1.2059 +      aOffset < mChannelOffset) {
  1.2060 +    // The block containing mChannelOffset is partially read, but not
  1.2061 +    // yet committed to the main cache. aOffset lies in the partially
  1.2062 +    // read portion, thus it is effectively cached.
  1.2063 +    return aOffset;
  1.2064 +  }
  1.2065 +
  1.2066 +  if (startBlockIndex >= mBlocks.Length())
  1.2067 +    return -1;
  1.2068 +
  1.2069 +  // Is the current block cached?
  1.2070 +  if (mBlocks[startBlockIndex] != -1)
  1.2071 +    return aOffset;
  1.2072 +
  1.2073 +  // Count the number of uncached blocks
  1.2074 +  bool hasPartialBlock = (mChannelOffset % BLOCK_SIZE) != 0;
  1.2075 +  uint32_t blockIndex = startBlockIndex + 1;
  1.2076 +  while (true) {
  1.2077 +    if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
  1.2078 +        (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
  1.2079 +      // We at the incoming channel block, which has has data in it,
  1.2080 +      // or are we at a cached block. Return index of block start.
  1.2081 +      return blockIndex * BLOCK_SIZE;
  1.2082 +    }
  1.2083 +
  1.2084 +    // No more cached blocks?
  1.2085 +    if (blockIndex >= mBlocks.Length())
  1.2086 +      return -1;
  1.2087 +
  1.2088 +    ++blockIndex;
  1.2089 +  }
  1.2090 +
  1.2091 +  NS_NOTREACHED("Should return in loop");
  1.2092 +  return -1;
  1.2093 +}
  1.2094 +
  1.2095 +void
  1.2096 +MediaCacheStream::SetReadMode(ReadMode aMode)
  1.2097 +{
  1.2098 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2099 +  if (aMode == mCurrentMode)
  1.2100 +    return;
  1.2101 +  mCurrentMode = aMode;
  1.2102 +  gMediaCache->QueueUpdate();
  1.2103 +}
  1.2104 +
  1.2105 +void
  1.2106 +MediaCacheStream::SetPlaybackRate(uint32_t aBytesPerSecond)
  1.2107 +{
  1.2108 +  NS_ASSERTION(aBytesPerSecond > 0, "Zero playback rate not allowed");
  1.2109 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2110 +  if (aBytesPerSecond == mPlaybackBytesPerSecond)
  1.2111 +    return;
  1.2112 +  mPlaybackBytesPerSecond = aBytesPerSecond;
  1.2113 +  gMediaCache->QueueUpdate();
  1.2114 +}
  1.2115 +
  1.2116 +nsresult
  1.2117 +MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset)
  1.2118 +{
  1.2119 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.2120 +
  1.2121 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2122 +  if (mClosed)
  1.2123 +    return NS_ERROR_FAILURE;
  1.2124 +
  1.2125 +  int64_t oldOffset = mStreamOffset;
  1.2126 +  int64_t newOffset = mStreamOffset;
  1.2127 +  switch (aWhence) {
  1.2128 +  case PR_SEEK_END:
  1.2129 +    if (mStreamLength < 0)
  1.2130 +      return NS_ERROR_FAILURE;
  1.2131 +    newOffset = mStreamLength + aOffset;
  1.2132 +    break;
  1.2133 +  case PR_SEEK_CUR:
  1.2134 +    newOffset += aOffset;
  1.2135 +    break;
  1.2136 +  case PR_SEEK_SET:
  1.2137 +    newOffset = aOffset;
  1.2138 +    break;
  1.2139 +  default:
  1.2140 +    NS_ERROR("Unknown whence");
  1.2141 +    return NS_ERROR_FAILURE;
  1.2142 +  }
  1.2143 +
  1.2144 +  if (newOffset < 0)
  1.2145 +    return NS_ERROR_FAILURE;
  1.2146 +  mStreamOffset = newOffset;
  1.2147 +
  1.2148 +  CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset));
  1.2149 +  gMediaCache->NoteSeek(this, oldOffset);
  1.2150 +
  1.2151 +  gMediaCache->QueueUpdate();
  1.2152 +  return NS_OK;
  1.2153 +}
  1.2154 +
  1.2155 +int64_t
  1.2156 +MediaCacheStream::Tell()
  1.2157 +{
  1.2158 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.2159 +
  1.2160 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2161 +  return mStreamOffset;
  1.2162 +}
  1.2163 +
  1.2164 +nsresult
  1.2165 +MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
  1.2166 +{
  1.2167 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.2168 +
  1.2169 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2170 +  if (mClosed)
  1.2171 +    return NS_ERROR_FAILURE;
  1.2172 +
  1.2173 +  uint32_t count = 0;
  1.2174 +  // Read one block (or part of a block) at a time
  1.2175 +  while (count < aCount) {
  1.2176 +    uint32_t streamBlock = uint32_t(mStreamOffset/BLOCK_SIZE);
  1.2177 +    uint32_t offsetInStreamBlock =
  1.2178 +      uint32_t(mStreamOffset - streamBlock*BLOCK_SIZE);
  1.2179 +    int64_t size = std::min(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
  1.2180 +
  1.2181 +    if (mStreamLength >= 0) {
  1.2182 +      // Don't try to read beyond the end of the stream
  1.2183 +      int64_t bytesRemaining = mStreamLength - mStreamOffset;
  1.2184 +      if (bytesRemaining <= 0) {
  1.2185 +        // Get out of here and return NS_OK
  1.2186 +        break;
  1.2187 +      }
  1.2188 +      size = std::min(size, bytesRemaining);
  1.2189 +      // Clamp size until 64-bit file size issues are fixed.
  1.2190 +      size = std::min(size, int64_t(INT32_MAX));
  1.2191 +    }
  1.2192 +
  1.2193 +    int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
  1.2194 +    if (cacheBlock < 0) {
  1.2195 +      // We don't have a complete cached block here.
  1.2196 +
  1.2197 +      if (count > 0) {
  1.2198 +        // Some data has been read, so return what we've got instead of
  1.2199 +        // blocking or trying to find a stream with a partial block.
  1.2200 +        break;
  1.2201 +      }
  1.2202 +
  1.2203 +      // See if the data is available in the partial cache block of any
  1.2204 +      // stream reading this resource. We need to do this in case there is
  1.2205 +      // another stream with this resource that has all the data to the end of
  1.2206 +      // the stream but the data doesn't end on a block boundary.
  1.2207 +      MediaCacheStream* streamWithPartialBlock = nullptr;
  1.2208 +      MediaCache::ResourceStreamIterator iter(mResourceID);
  1.2209 +      while (MediaCacheStream* stream = iter.Next()) {
  1.2210 +        if (uint32_t(stream->mChannelOffset/BLOCK_SIZE) == streamBlock &&
  1.2211 +            mStreamOffset < stream->mChannelOffset) {
  1.2212 +          streamWithPartialBlock = stream;
  1.2213 +          break;
  1.2214 +        }
  1.2215 +      }
  1.2216 +      if (streamWithPartialBlock) {
  1.2217 +        // We can just use the data in mPartialBlockBuffer. In fact we should
  1.2218 +        // use it rather than waiting for the block to fill and land in
  1.2219 +        // the cache.
  1.2220 +        int64_t bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - mStreamOffset);
  1.2221 +        // Clamp bytes until 64-bit file size issues are fixed.
  1.2222 +        bytes = std::min(bytes, int64_t(INT32_MAX));
  1.2223 +        NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
  1.2224 +        memcpy(aBuffer,
  1.2225 +          reinterpret_cast<char*>(streamWithPartialBlock->mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes);
  1.2226 +        if (mCurrentMode == MODE_METADATA) {
  1.2227 +          streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
  1.2228 +        }
  1.2229 +        mStreamOffset += bytes;
  1.2230 +        count = bytes;
  1.2231 +        break;
  1.2232 +      }
  1.2233 +
  1.2234 +      // No data has been read yet, so block
  1.2235 +      mon.Wait();
  1.2236 +      if (mClosed) {
  1.2237 +        // We may have successfully read some data, but let's just throw
  1.2238 +        // that out.
  1.2239 +        return NS_ERROR_FAILURE;
  1.2240 +      }
  1.2241 +      continue;
  1.2242 +    }
  1.2243 +
  1.2244 +    gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
  1.2245 +
  1.2246 +    int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
  1.2247 +    int32_t bytes;
  1.2248 +    NS_ABORT_IF_FALSE(size >= 0 && size <= INT32_MAX, "Size out of range.");
  1.2249 +    nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, int32_t(size), &bytes);
  1.2250 +    if (NS_FAILED(rv)) {
  1.2251 +      if (count == 0)
  1.2252 +        return rv;
  1.2253 +      // If we did successfully read some data, may as well return it
  1.2254 +      break;
  1.2255 +    }
  1.2256 +    mStreamOffset += bytes;
  1.2257 +    count += bytes;
  1.2258 +  }
  1.2259 +
  1.2260 +  if (count > 0) {
  1.2261 +    // Some data was read, so queue an update since block priorities may
  1.2262 +    // have changed
  1.2263 +    gMediaCache->QueueUpdate();
  1.2264 +  }
  1.2265 +  CACHE_LOG(PR_LOG_DEBUG,
  1.2266 +            ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count));
  1.2267 +  *aBytes = count;
  1.2268 +  return NS_OK;
  1.2269 +}
  1.2270 +
  1.2271 +nsresult
  1.2272 +MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
  1.2273 +                         uint32_t aCount, uint32_t* aBytes)
  1.2274 +{
  1.2275 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
  1.2276 +
  1.2277 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2278 +  nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
  1.2279 +  if (NS_FAILED(rv)) return rv;
  1.2280 +  return Read(aBuffer, aCount, aBytes);
  1.2281 +}
  1.2282 +
  1.2283 +nsresult
  1.2284 +MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount)
  1.2285 +{
  1.2286 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2287 +  if (mClosed)
  1.2288 +    return NS_ERROR_FAILURE;
  1.2289 +
  1.2290 +  // Read one block (or part of a block) at a time
  1.2291 +  uint32_t count = 0;
  1.2292 +  int64_t streamOffset = aOffset;
  1.2293 +  while (count < aCount) {
  1.2294 +    uint32_t streamBlock = uint32_t(streamOffset/BLOCK_SIZE);
  1.2295 +    uint32_t offsetInStreamBlock =
  1.2296 +      uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
  1.2297 +    int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
  1.2298 +
  1.2299 +    if (mStreamLength >= 0) {
  1.2300 +      // Don't try to read beyond the end of the stream
  1.2301 +      int64_t bytesRemaining = mStreamLength - streamOffset;
  1.2302 +      if (bytesRemaining <= 0) {
  1.2303 +        return NS_ERROR_FAILURE;
  1.2304 +      }
  1.2305 +      size = std::min(size, bytesRemaining);
  1.2306 +      // Clamp size until 64-bit file size issues are fixed.
  1.2307 +      size = std::min(size, int64_t(INT32_MAX));
  1.2308 +    }
  1.2309 +
  1.2310 +    int32_t bytes;
  1.2311 +    uint32_t channelBlock = uint32_t(mChannelOffset/BLOCK_SIZE);
  1.2312 +    int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
  1.2313 +    if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
  1.2314 +      // We can just use the data in mPartialBlockBuffer. In fact we should
  1.2315 +      // use it rather than waiting for the block to fill and land in
  1.2316 +      // the cache.
  1.2317 +      // Clamp bytes until 64-bit file size issues are fixed.
  1.2318 +      int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
  1.2319 +      bytes = std::min(toCopy, int64_t(INT32_MAX));
  1.2320 +      NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");
  1.2321 +      memcpy(aBuffer + count,
  1.2322 +        reinterpret_cast<char*>(mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes);
  1.2323 +    } else {
  1.2324 +      if (cacheBlock < 0) {
  1.2325 +        // We expect all blocks to be cached! Fail!
  1.2326 +        return NS_ERROR_FAILURE;
  1.2327 +      }
  1.2328 +      int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
  1.2329 +      NS_ABORT_IF_FALSE(size >= 0 && size <= INT32_MAX, "Size out of range.");
  1.2330 +      nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, int32_t(size), &bytes);
  1.2331 +      if (NS_FAILED(rv)) {
  1.2332 +        return rv;
  1.2333 +      }
  1.2334 +    }
  1.2335 +    streamOffset += bytes;
  1.2336 +    count += bytes;
  1.2337 +  }
  1.2338 +
  1.2339 +  return NS_OK;
  1.2340 +}
  1.2341 +
  1.2342 +nsresult
  1.2343 +MediaCacheStream::Init()
  1.2344 +{
  1.2345 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  1.2346 +
  1.2347 +  if (mInitialized)
  1.2348 +    return NS_OK;
  1.2349 +
  1.2350 +  InitMediaCache();
  1.2351 +  if (!gMediaCache)
  1.2352 +    return NS_ERROR_FAILURE;
  1.2353 +  gMediaCache->OpenStream(this);
  1.2354 +  mInitialized = true;
  1.2355 +  return NS_OK;
  1.2356 +}
  1.2357 +
  1.2358 +nsresult
  1.2359 +MediaCacheStream::InitAsClone(MediaCacheStream* aOriginal)
  1.2360 +{
  1.2361 +  if (!aOriginal->IsAvailableForSharing())
  1.2362 +    return NS_ERROR_FAILURE;
  1.2363 +
  1.2364 +  if (mInitialized)
  1.2365 +    return NS_OK;
  1.2366 +
  1.2367 +  nsresult rv = Init();
  1.2368 +  if (NS_FAILED(rv))
  1.2369 +    return rv;
  1.2370 +  mResourceID = aOriginal->mResourceID;
  1.2371 +
  1.2372 +  // Grab cache blocks from aOriginal as readahead blocks for our stream
  1.2373 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2374 +
  1.2375 +  mPrincipal = aOriginal->mPrincipal;
  1.2376 +  mStreamLength = aOriginal->mStreamLength;
  1.2377 +  mIsTransportSeekable = aOriginal->mIsTransportSeekable;
  1.2378 +
  1.2379 +  // Cloned streams are initially suspended, since there is no channel open
  1.2380 +  // initially for a clone.
  1.2381 +  mCacheSuspended = true;
  1.2382 +  mChannelEnded = true;
  1.2383 +
  1.2384 +  if (aOriginal->mDidNotifyDataEnded) {
  1.2385 +    mNotifyDataEndedStatus = aOriginal->mNotifyDataEndedStatus;
  1.2386 +    mDidNotifyDataEnded = true;
  1.2387 +    mClient->CacheClientNotifyDataEnded(mNotifyDataEndedStatus);
  1.2388 +  }
  1.2389 +
  1.2390 +  for (uint32_t i = 0; i < aOriginal->mBlocks.Length(); ++i) {
  1.2391 +    int32_t cacheBlockIndex = aOriginal->mBlocks[i];
  1.2392 +    if (cacheBlockIndex < 0)
  1.2393 +      continue;
  1.2394 +
  1.2395 +    while (i >= mBlocks.Length()) {
  1.2396 +      mBlocks.AppendElement(-1);
  1.2397 +    }
  1.2398 +    // Every block is a readahead block for the clone because the clone's initial
  1.2399 +    // stream offset is zero
  1.2400 +    gMediaCache->AddBlockOwnerAsReadahead(cacheBlockIndex, this, i);
  1.2401 +  }
  1.2402 +
  1.2403 +  return NS_OK;
  1.2404 +}
  1.2405 +
  1.2406 +nsresult MediaCacheStream::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
  1.2407 +{
  1.2408 +  // Take the monitor, so that the cached data ranges can't grow while we're
  1.2409 +  // trying to loop over them.
  1.2410 +  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
  1.2411 +
  1.2412 +  // We must be pinned while running this, otherwise the cached data ranges may
  1.2413 +  // shrink while we're trying to loop over them.
  1.2414 +  NS_ASSERTION(mPinCount > 0, "Must be pinned");
  1.2415 +
  1.2416 +  int64_t startOffset = GetNextCachedData(0);
  1.2417 +  while (startOffset >= 0) {
  1.2418 +    int64_t endOffset = GetCachedDataEnd(startOffset);
  1.2419 +    NS_ASSERTION(startOffset < endOffset, "Buffered range must end after its start");
  1.2420 +    // Bytes [startOffset..endOffset] are cached.
  1.2421 +    aRanges.AppendElement(MediaByteRange(startOffset, endOffset));
  1.2422 +    startOffset = GetNextCachedData(endOffset);
  1.2423 +    NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
  1.2424 +      "Must have advanced to start of next range, or hit end of stream");
  1.2425 +  }
  1.2426 +  return NS_OK;
  1.2427 +}
  1.2428 +
  1.2429 +} // namespace mozilla
  1.2430 +

mercurial