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