LCOV - app.info - content/media/nsMediaCache.cpp

LCOV - code coverage report
| Current view: | directory - content/media - nsMediaCache.cpp (source / functions) |  | Found | Hit | Coverage |
| Test: | app.info | Lines: | 1074 | 0 | 0.0 %
| Date: | 2012-07-07 | Functions: | 92 | 0 | 0.0 %

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

Generated by: LCOV version 1.7