[JAEGER] Cache atom in PIC directly, instead of index, for simplicity.
[mozilla-central.git] / content / media / nsMediaCache.cpp
blob9477f8ec9b5a4b2c3b79d5df0312dfb66538d69f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla code.
18 * The Initial Developer of the Original Code is the Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2009
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Robert O'Callahan <robert@ocallahan.org>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "mozilla/XPCOM.h"
41 #include "nsMediaCache.h"
42 #include "nsAutoLock.h"
43 #include "nsContentUtils.h"
44 #include "nsDirectoryServiceUtils.h"
45 #include "nsDirectoryServiceDefs.h"
46 #include "nsNetUtil.h"
47 #include "prio.h"
48 #include "nsThreadUtils.h"
49 #include "nsMediaStream.h"
50 #include "nsMathUtils.h"
51 #include "prlog.h"
53 #ifdef PR_LOGGING
54 PRLogModuleInfo* gMediaCacheLog;
55 #define LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
56 #else
57 #define LOG(type, msg)
58 #endif
60 // Readahead blocks for non-seekable streams will be limited to this
61 // fraction of the cache space. We don't normally evict such blocks
62 // because replacing them requires a seek, but we need to make sure
63 // they don't monopolize the cache.
64 static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
66 // Assume that any replaying or backward seeking will happen
67 // this far in the future (in seconds). This is a random guess/estimate
68 // penalty to account for the possibility that we might not replay at
69 // all.
70 static const PRUint32 REPLAY_DELAY = 30;
72 // When looking for a reusable block, scan forward this many blocks
73 // from the desired "best" block location to look for free blocks,
74 // before we resort to scanning the whole cache. The idea is to try to
75 // store runs of stream blocks close-to-consecutively in the cache if we
76 // can.
77 static const PRUint32 FREE_BLOCK_SCAN_LIMIT = 16;
79 using mozilla::TimeStamp;
80 using mozilla::TimeDuration;
82 #ifdef DEBUG
83 // Turn this on to do very expensive cache state validation
84 // #define DEBUG_VERIFY_CACHE
85 #endif
87 // There is at most one media cache (although that could quite easily be
88 // relaxed if we wanted to manage multiple caches with independent
89 // size limits).
90 static nsMediaCache* gMediaCache;
92 class nsMediaCache {
93 public:
94 friend class nsMediaCacheStream::BlockList;
95 typedef nsMediaCacheStream::BlockList BlockList;
96 enum {
97 BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
100 nsMediaCache() : mNextResourceID(1),
101 mMonitor(nsAutoMonitor::NewMonitor("media.cache")),
102 mFD(nsnull), mFDCurrentPos(0), mUpdateQueued(PR_FALSE)
103 #ifdef DEBUG
104 , mInUpdate(PR_FALSE)
105 #endif
107 MOZ_COUNT_CTOR(nsMediaCache);
109 ~nsMediaCache() {
110 NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
111 Truncate();
112 NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
113 if (mFD) {
114 PR_Close(mFD);
116 if (mMonitor) {
117 nsAutoMonitor::DestroyMonitor(mMonitor);
119 MOZ_COUNT_DTOR(nsMediaCache);
122 // Main thread only. Creates the backing cache file.
123 nsresult Init();
124 // Shut down the global cache if it's no longer needed. We shut down
125 // the cache as soon as there are no streams. This means that during
126 // normal operation we are likely to start up the cache and shut it down
127 // many times, but that's OK since starting it up is cheap and
128 // shutting it down cleans things up and releases disk space.
129 static void MaybeShutdown();
131 // Cache-file access methods. These are the lowest-level cache methods.
132 // mMonitor must be held; these can be called on any thread.
133 // This can return partial reads.
134 nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
135 PRInt32* aBytes);
136 // This will fail if all aLength bytes are not read
137 nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
138 // This will fail if all aLength bytes are not written
139 nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
141 // mMonitor must be held, called on main thread.
142 // These methods are used by the stream to set up and tear down streams,
143 // and to handle reads and writes.
144 // Add aStream to the list of streams.
145 void OpenStream(nsMediaCacheStream* aStream);
146 // Remove aStream from the list of streams.
147 void ReleaseStream(nsMediaCacheStream* aStream);
148 // Free all blocks belonging to aStream.
149 void ReleaseStreamBlocks(nsMediaCacheStream* aStream);
150 // Find a cache entry for this data, and write the data into it
151 void AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
152 nsMediaCacheStream::ReadMode aMode);
154 // mMonitor must be held; can be called on any thread
155 // Notify the cache that a seek has been requested. Some blocks may
156 // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
157 // This does not trigger channel seeks directly, the next Update()
158 // will do that if necessary. The caller will call QueueUpdate().
159 void NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset);
160 // Notify the cache that a block has been read from. This is used
161 // to update last-use times. The block may not actually have a
162 // cache entry yet since Read can read data from a stream's
163 // in-memory mPartialBlockBuffer while the block is only partly full,
164 // and thus hasn't yet been committed to the cache. The caller will
165 // call QueueUpdate().
166 void NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
167 nsMediaCacheStream::ReadMode aMode, TimeStamp aNow);
168 // Mark aStream as having the block, adding it as an owner.
169 void AddBlockOwnerAsReadahead(PRInt32 aBlockIndex, nsMediaCacheStream* aStream,
170 PRInt32 aStreamBlockIndex);
172 // This queues a call to Update() on the main thread.
173 void QueueUpdate();
175 // Updates the cache state asynchronously on the main thread:
176 // -- try to trim the cache back to its desired size, if necessary
177 // -- suspend channels that are going to read data that's lower priority
178 // than anything currently cached
179 // -- resume channels that are going to read data that's higher priority
180 // than something currently cached
181 // -- seek channels that need to seek to a new location
182 void Update();
184 #ifdef DEBUG_VERIFY_CACHE
185 // Verify invariants, especially block list invariants
186 void Verify();
187 #else
188 void Verify() {}
189 #endif
191 PRMonitor* Monitor() { return mMonitor; }
194 * An iterator that makes it easy to iterate through all streams that
195 * have a given resource ID and are not closed.
197 class ResourceStreamIterator {
198 public:
199 ResourceStreamIterator(PRInt64 aResourceID) :
200 mResourceID(aResourceID), mNext(0) {}
201 nsMediaCacheStream* Next()
203 while (mNext < gMediaCache->mStreams.Length()) {
204 nsMediaCacheStream* stream = gMediaCache->mStreams[mNext];
205 ++mNext;
206 if (stream->GetResourceID() == mResourceID && !stream->IsClosed())
207 return stream;
209 return nsnull;
211 private:
212 PRInt64 mResourceID;
213 PRUint32 mNext;
216 protected:
217 // Find a free or reusable block and return its index. If there are no
218 // free blocks and no reusable blocks, add a new block to the cache
219 // and return it. Can return -1 on OOM.
220 PRInt32 FindBlockForIncomingData(TimeStamp aNow, nsMediaCacheStream* aStream);
221 // Find a reusable block --- a free block, if there is one, otherwise
222 // the reusable block with the latest predicted-next-use, or -1 if
223 // there aren't any freeable blocks. Only block indices less than
224 // aMaxSearchBlockIndex are considered. If aForStream is non-null,
225 // then aForStream and aForStreamBlock indicate what media data will
226 // be placed; FindReusableBlock will favour returning free blocks
227 // near other blocks for that point in the stream.
228 PRInt32 FindReusableBlock(TimeStamp aNow,
229 nsMediaCacheStream* aForStream,
230 PRInt32 aForStreamBlock,
231 PRInt32 aMaxSearchBlockIndex);
232 PRBool BlockIsReusable(PRInt32 aBlockIndex);
233 // Given a list of blocks sorted with the most reusable blocks at the
234 // end, find the last block whose stream is not pinned (if any)
235 // and whose cache entry index is less than aBlockIndexLimit
236 // and append it to aResult.
237 void AppendMostReusableBlock(BlockList* aBlockList,
238 nsTArray<PRUint32>* aResult,
239 PRInt32 aBlockIndexLimit);
241 enum BlockClass {
242 // block belongs to mMetadataBlockList because data has been consumed
243 // from it in "metadata mode" --- in particular blocks read during
244 // Ogg seeks go into this class. These blocks may have played data
245 // in them too.
246 METADATA_BLOCK,
247 // block belongs to mPlayedBlockList because its offset is
248 // less than the stream's current reader position
249 PLAYED_BLOCK,
250 // block belongs to the stream's mReadaheadBlockList because its
251 // offset is greater than or equal to the stream's current
252 // reader position
253 READAHEAD_BLOCK
256 struct BlockOwner {
257 BlockOwner() : mStream(nsnull), mClass(READAHEAD_BLOCK) {}
259 // The stream that owns this block, or null if the block is free.
260 nsMediaCacheStream* mStream;
261 // The block index in the stream. Valid only if mStream is non-null.
262 PRUint32 mStreamBlock;
263 // Time at which this block was last used. Valid only if
264 // mClass is METADATA_BLOCK or PLAYED_BLOCK.
265 TimeStamp mLastUseTime;
266 BlockClass mClass;
269 struct Block {
270 // Free blocks have an empty mOwners array
271 nsTArray<BlockOwner> mOwners;
274 // Get the BlockList that the block should belong to given its
275 // current owner
276 BlockList* GetListForBlock(BlockOwner* aBlock);
277 // Get the BlockOwner for the given block index and owning stream
278 // (returns null if the stream does not own the block)
279 BlockOwner* GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
280 // Returns true iff the block is free
281 PRBool IsBlockFree(PRInt32 aBlockIndex)
282 { return mIndex[aBlockIndex].mOwners.IsEmpty(); }
283 // Add the block to the free list and mark its streams as not having
284 // the block in cache
285 void FreeBlock(PRInt32 aBlock);
286 // Mark aStream as not having the block, removing it as an owner. If
287 // the block has no more owners it's added to the free list.
288 void RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
289 // Swap all metadata associated with the two blocks. The caller
290 // is responsible for swapping up any cache file state.
291 void SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2);
292 // Insert the block into the readahead block list for the stream
293 // at the right point in the list.
294 void InsertReadaheadBlock(BlockOwner* aBlockOwner, PRInt32 aBlockIndex);
296 // Guess the duration until block aBlock will be next used
297 TimeDuration PredictNextUse(TimeStamp aNow, PRInt32 aBlock);
298 // Guess the duration until the next incoming data on aStream will be used
299 TimeDuration PredictNextUseForIncomingData(nsMediaCacheStream* aStream);
301 // Truncate the file and index array if there are free blocks at the
302 // end
303 void Truncate();
305 // This member is main-thread only. It's used to allocate unique
306 // resource IDs to streams.
307 PRInt64 mNextResourceID;
308 // This member is main-thread only. It contains all the streams.
309 nsTArray<nsMediaCacheStream*> mStreams;
311 // The monitor protects all the data members here. Also, off-main-thread
312 // readers that need to block will Wait() on this monitor. When new
313 // data becomes available in the cache, we NotifyAll() on this monitor.
314 PRMonitor* mMonitor;
315 // The Blocks describing the cache entries.
316 nsTArray<Block> mIndex;
317 // The file descriptor of the cache file. The file will be deleted
318 // by the operating system when this is closed.
319 PRFileDesc* mFD;
320 // The current file offset in the cache file.
321 PRInt64 mFDCurrentPos;
322 // The list of free blocks; they are not ordered.
323 BlockList mFreeBlocks;
324 // True if an event to run Update() has been queued but not processed
325 PRPackedBool mUpdateQueued;
326 #ifdef DEBUG
327 PRPackedBool mInUpdate;
328 #endif
331 void nsMediaCacheStream::BlockList::AddFirstBlock(PRInt32 aBlock)
333 NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
334 Entry* entry = mEntries.PutEntry(aBlock);
336 if (mFirstBlock < 0) {
337 entry->mNextBlock = entry->mPrevBlock = aBlock;
338 } else {
339 entry->mNextBlock = mFirstBlock;
340 entry->mPrevBlock = mEntries.GetEntry(mFirstBlock)->mPrevBlock;
341 mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
342 mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
344 mFirstBlock = aBlock;
345 ++mCount;
348 void nsMediaCacheStream::BlockList::AddAfter(PRInt32 aBlock, PRInt32 aBefore)
350 NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
351 Entry* entry = mEntries.PutEntry(aBlock);
353 Entry* addAfter = mEntries.GetEntry(aBefore);
354 NS_ASSERTION(addAfter, "aBefore not in list");
356 entry->mNextBlock = addAfter->mNextBlock;
357 entry->mPrevBlock = aBefore;
358 mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
359 mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
360 ++mCount;
363 void nsMediaCacheStream::BlockList::RemoveBlock(PRInt32 aBlock)
365 Entry* entry = mEntries.GetEntry(aBlock);
366 NS_ASSERTION(entry, "Block not in list");
368 if (entry->mNextBlock == aBlock) {
369 NS_ASSERTION(entry->mPrevBlock == aBlock, "Linked list inconsistency");
370 NS_ASSERTION(mFirstBlock == aBlock, "Linked list inconsistency");
371 mFirstBlock = -1;
372 } else {
373 if (mFirstBlock == aBlock) {
374 mFirstBlock = entry->mNextBlock;
376 mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = entry->mPrevBlock;
377 mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = entry->mNextBlock;
379 mEntries.RemoveEntry(aBlock);
380 --mCount;
383 PRInt32 nsMediaCacheStream::BlockList::GetLastBlock() const
385 if (mFirstBlock < 0)
386 return -1;
387 return mEntries.GetEntry(mFirstBlock)->mPrevBlock;
390 PRInt32 nsMediaCacheStream::BlockList::GetNextBlock(PRInt32 aBlock) const
392 PRInt32 block = mEntries.GetEntry(aBlock)->mNextBlock;
393 if (block == mFirstBlock)
394 return -1;
395 return block;
398 PRInt32 nsMediaCacheStream::BlockList::GetPrevBlock(PRInt32 aBlock) const
400 if (aBlock == mFirstBlock)
401 return -1;
402 return mEntries.GetEntry(aBlock)->mPrevBlock;
405 #ifdef DEBUG
406 void nsMediaCacheStream::BlockList::Verify()
408 PRInt32 count = 0;
409 if (mFirstBlock >= 0) {
410 PRInt32 block = mFirstBlock;
411 do {
412 Entry* entry = mEntries.GetEntry(block);
413 NS_ASSERTION(mEntries.GetEntry(entry->mNextBlock)->mPrevBlock == block,
414 "Bad prev link");
415 NS_ASSERTION(mEntries.GetEntry(entry->mPrevBlock)->mNextBlock == block,
416 "Bad next link");
417 block = entry->mNextBlock;
418 ++count;
419 } while (block != mFirstBlock);
421 NS_ASSERTION(count == mCount, "Bad count");
423 #endif
425 static void UpdateSwappedBlockIndex(PRInt32* aBlockIndex,
426 PRInt32 aBlock1Index, PRInt32 aBlock2Index)
428 PRInt32 index = *aBlockIndex;
429 if (index == aBlock1Index) {
430 *aBlockIndex = aBlock2Index;
431 } else if (index == aBlock2Index) {
432 *aBlockIndex = aBlock1Index;
436 void
437 nsMediaCacheStream::BlockList::NotifyBlockSwapped(PRInt32 aBlockIndex1,
438 PRInt32 aBlockIndex2)
440 Entry* e1 = mEntries.GetEntry(aBlockIndex1);
441 Entry* e2 = mEntries.GetEntry(aBlockIndex2);
442 PRInt32 e1Prev = -1, e1Next = -1, e2Prev = -1, e2Next = -1;
444 // Fix mFirstBlock
445 UpdateSwappedBlockIndex(&mFirstBlock, aBlockIndex1, aBlockIndex2);
447 // Fix mNextBlock/mPrevBlock links. First capture previous/next links
448 // so we don't get confused due to aliasing.
449 if (e1) {
450 e1Prev = e1->mPrevBlock;
451 e1Next = e1->mNextBlock;
453 if (e2) {
454 e2Prev = e2->mPrevBlock;
455 e2Next = e2->mNextBlock;
457 // Update the entries.
458 if (e1) {
459 mEntries.GetEntry(e1Prev)->mNextBlock = aBlockIndex2;
460 mEntries.GetEntry(e1Next)->mPrevBlock = aBlockIndex2;
462 if (e2) {
463 mEntries.GetEntry(e2Prev)->mNextBlock = aBlockIndex1;
464 mEntries.GetEntry(e2Next)->mPrevBlock = aBlockIndex1;
467 // Fix hashtable keys. First remove stale entries.
468 if (e1) {
469 e1Prev = e1->mPrevBlock;
470 e1Next = e1->mNextBlock;
471 mEntries.RemoveEntry(aBlockIndex1);
472 // Refresh pointer after hashtable mutation.
473 e2 = mEntries.GetEntry(aBlockIndex2);
475 if (e2) {
476 e2Prev = e2->mPrevBlock;
477 e2Next = e2->mNextBlock;
478 mEntries.RemoveEntry(aBlockIndex2);
480 // Put new entries back.
481 if (e1) {
482 e1 = mEntries.PutEntry(aBlockIndex2);
483 e1->mNextBlock = e1Next;
484 e1->mPrevBlock = e1Prev;
486 if (e2) {
487 e2 = mEntries.PutEntry(aBlockIndex1);
488 e2->mNextBlock = e2Next;
489 e2->mPrevBlock = e2Prev;
493 nsresult
494 nsMediaCache::Init()
496 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
498 if (!mMonitor) {
499 // the constructor failed
500 return NS_ERROR_OUT_OF_MEMORY;
503 nsCOMPtr<nsIFile> tmp;
504 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp));
505 if (NS_FAILED(rv))
506 return rv;
507 nsCOMPtr<nsILocalFile> tmpFile = do_QueryInterface(tmp);
508 if (!tmpFile)
509 return NS_ERROR_FAILURE;
510 rv = tmpFile->AppendNative(nsDependentCString("moz_media_cache"));
511 if (NS_FAILED(rv))
512 return rv;
513 rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
514 if (NS_FAILED(rv))
515 return rv;
516 rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsILocalFile::DELETE_ON_CLOSE,
517 PR_IRWXU, &mFD);
518 if (NS_FAILED(rv))
519 return rv;
521 #ifdef PR_LOGGING
522 if (!gMediaCacheLog) {
523 gMediaCacheLog = PR_NewLogModule("nsMediaCache");
525 #endif
527 return NS_OK;
530 void
531 nsMediaCache::MaybeShutdown()
533 NS_ASSERTION(NS_IsMainThread(),
534 "nsMediaCache::MaybeShutdown called on non-main thread");
535 if (!gMediaCache->mStreams.IsEmpty()) {
536 // Don't shut down yet, streams are still alive
537 return;
540 // Since we're on the main thread, no-one is going to add a new stream
541 // while we shut down.
542 // This function is static so we don't have to delete 'this'.
543 delete gMediaCache;
544 gMediaCache = nsnull;
547 static void
548 InitMediaCache()
550 if (gMediaCache)
551 return;
553 gMediaCache = new nsMediaCache();
554 if (!gMediaCache)
555 return;
557 nsresult rv = gMediaCache->Init();
558 if (NS_FAILED(rv)) {
559 delete gMediaCache;
560 gMediaCache = nsnull;
564 nsresult
565 nsMediaCache::ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
566 PRInt32* aBytes)
568 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
570 if (!mFD)
571 return NS_ERROR_FAILURE;
573 if (mFDCurrentPos != aOffset) {
574 PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
575 if (offset != aOffset)
576 return NS_ERROR_FAILURE;
577 mFDCurrentPos = aOffset;
579 PRInt32 amount = PR_Read(mFD, aData, aLength);
580 if (amount <= 0)
581 return NS_ERROR_FAILURE;
582 mFDCurrentPos += amount;
583 *aBytes = amount;
584 return NS_OK;
587 nsresult
588 nsMediaCache::ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength)
590 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
592 PRInt64 offset = aOffset;
593 PRInt32 count = aLength;
594 // Cast to char* so we can do byte-wise pointer arithmetic
595 char* data = static_cast<char*>(aData);
596 while (count > 0) {
597 PRInt32 bytes;
598 nsresult rv = ReadCacheFile(offset, data, count, &bytes);
599 if (NS_FAILED(rv))
600 return rv;
601 if (bytes == 0)
602 return NS_ERROR_FAILURE;
603 count -= bytes;
604 data += bytes;
605 offset += bytes;
607 return NS_OK;
610 nsresult
611 nsMediaCache::WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength)
613 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
615 if (!mFD)
616 return NS_ERROR_FAILURE;
618 if (mFDCurrentPos != aOffset) {
619 PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
620 if (offset != aOffset)
621 return NS_ERROR_FAILURE;
622 mFDCurrentPos = aOffset;
625 const char* data = static_cast<const char*>(aData);
626 PRInt32 length = aLength;
627 while (length > 0) {
628 PRInt32 amount = PR_Write(mFD, data, length);
629 if (amount <= 0)
630 return NS_ERROR_FAILURE;
631 mFDCurrentPos += amount;
632 length -= amount;
633 data += amount;
636 return NS_OK;
639 static PRInt32 GetMaxBlocks()
641 // We look up the cache size every time. This means dynamic changes
642 // to the pref are applied.
643 // Cache size is in KB
644 PRInt32 cacheSize = nsContentUtils::GetIntPref("media.cache_size", 50*1024);
645 PRInt64 maxBlocks = PRInt64(cacheSize)*1024/nsMediaCache::BLOCK_SIZE;
646 maxBlocks = PR_MAX(maxBlocks, 1);
647 return PRInt32(PR_MIN(maxBlocks, PR_INT32_MAX));
650 PRInt32
651 nsMediaCache::FindBlockForIncomingData(TimeStamp aNow,
652 nsMediaCacheStream* aStream)
654 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
656 PRInt32 blockIndex = FindReusableBlock(aNow, aStream,
657 aStream->mChannelOffset/BLOCK_SIZE, PR_INT32_MAX);
659 if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
660 // The block returned is already allocated.
661 // Don't reuse it if a) there's room to expand the cache or
662 // b) the data we're going to store in the free block is not higher
663 // priority than the data already stored in the free block.
664 // The latter can lead us to go over the cache limit a bit.
665 if ((mIndex.Length() < PRUint32(GetMaxBlocks()) || blockIndex < 0 ||
666 PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
667 blockIndex = mIndex.Length();
668 if (!mIndex.AppendElement())
669 return -1;
670 mFreeBlocks.AddFirstBlock(blockIndex);
671 return blockIndex;
675 return blockIndex;
678 PRBool
679 nsMediaCache::BlockIsReusable(PRInt32 aBlockIndex)
681 Block* block = &mIndex[aBlockIndex];
682 for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
683 nsMediaCacheStream* stream = block->mOwners[i].mStream;
684 if (stream->mPinCount > 0 ||
685 stream->mStreamOffset/BLOCK_SIZE == block->mOwners[i].mStreamBlock) {
686 return PR_FALSE;
689 return PR_TRUE;
692 void
693 nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList,
694 nsTArray<PRUint32>* aResult,
695 PRInt32 aBlockIndexLimit)
697 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
699 PRInt32 blockIndex = aBlockList->GetLastBlock();
700 if (blockIndex < 0)
701 return;
702 do {
703 // Don't consider blocks for pinned streams, or blocks that are
704 // beyond the specified limit, or a block that contains a stream's
705 // current read position (such a block contains both played data
706 // and readahead data)
707 if (blockIndex < aBlockIndexLimit && BlockIsReusable(blockIndex)) {
708 aResult->AppendElement(blockIndex);
709 return;
711 blockIndex = aBlockList->GetPrevBlock(blockIndex);
712 } while (blockIndex >= 0);
715 PRInt32
716 nsMediaCache::FindReusableBlock(TimeStamp aNow,
717 nsMediaCacheStream* aForStream,
718 PRInt32 aForStreamBlock,
719 PRInt32 aMaxSearchBlockIndex)
721 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
723 PRUint32 length = PR_MIN(PRUint32(aMaxSearchBlockIndex), mIndex.Length());
725 if (aForStream && aForStreamBlock > 0 &&
726 PRUint32(aForStreamBlock) <= aForStream->mBlocks.Length()) {
727 PRInt32 prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
728 if (prevCacheBlock >= 0) {
729 PRUint32 freeBlockScanEnd =
730 PR_MIN(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
731 for (PRUint32 i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
732 if (IsBlockFree(i))
733 return i;
738 if (!mFreeBlocks.IsEmpty()) {
739 PRInt32 blockIndex = mFreeBlocks.GetFirstBlock();
740 do {
741 if (blockIndex < aMaxSearchBlockIndex)
742 return blockIndex;
743 blockIndex = mFreeBlocks.GetNextBlock(blockIndex);
744 } while (blockIndex >= 0);
747 // Build a list of the blocks we should consider for the "latest
748 // predicted time of next use". We can exploit the fact that the block
749 // linked lists are ordered by increasing time of next use. This is
750 // actually the whole point of having the linked lists.
751 nsAutoTArray<PRUint32,8> candidates;
752 for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
753 nsMediaCacheStream* stream = mStreams[i];
754 if (stream->mPinCount > 0) {
755 // No point in even looking at this stream's blocks
756 continue;
759 AppendMostReusableBlock(&stream->mMetadataBlocks, &candidates, length);
760 AppendMostReusableBlock(&stream->mPlayedBlocks, &candidates, length);
762 // Don't consider readahead blocks in non-seekable streams. If we
763 // remove the block we won't be able to seek back to read it later.
764 if (stream->mIsSeekable) {
765 AppendMostReusableBlock(&stream->mReadaheadBlocks, &candidates, length);
769 TimeDuration latestUse;
770 PRInt32 latestUseBlock = -1;
771 for (PRUint32 i = 0; i < candidates.Length(); ++i) {
772 TimeDuration nextUse = PredictNextUse(aNow, candidates[i]);
773 if (nextUse > latestUse) {
774 latestUse = nextUse;
775 latestUseBlock = candidates[i];
779 return latestUseBlock;
782 nsMediaCache::BlockList*
783 nsMediaCache::GetListForBlock(BlockOwner* aBlock)
785 switch (aBlock->mClass) {
786 case METADATA_BLOCK:
787 NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
788 return &aBlock->mStream->mMetadataBlocks;
789 case PLAYED_BLOCK:
790 NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
791 return &aBlock->mStream->mPlayedBlocks;
792 case READAHEAD_BLOCK:
793 NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
794 return &aBlock->mStream->mReadaheadBlocks;
795 default:
796 NS_ERROR("Invalid block class");
797 return nsnull;
801 nsMediaCache::BlockOwner*
802 nsMediaCache::GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
804 Block* block = &mIndex[aBlockIndex];
805 for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
806 if (block->mOwners[i].mStream == aStream)
807 return &block->mOwners[i];
809 return nsnull;
812 void
813 nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2)
815 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
817 Block* block1 = &mIndex[aBlockIndex1];
818 Block* block2 = &mIndex[aBlockIndex2];
820 block1->mOwners.SwapElements(block2->mOwners);
822 // Now all references to block1 have to be replaced with block2 and
823 // vice versa.
824 // First update stream references to blocks via mBlocks.
825 const Block* blocks[] = { block1, block2 };
826 PRInt32 blockIndices[] = { aBlockIndex1, aBlockIndex2 };
827 for (PRInt32 i = 0; i < 2; ++i) {
828 for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
829 const BlockOwner* b = &blocks[i]->mOwners[j];
830 b->mStream->mBlocks[b->mStreamBlock] = blockIndices[i];
834 // Now update references to blocks in block lists.
835 mFreeBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
837 nsTHashtable<nsPtrHashKey<nsMediaCacheStream> > visitedStreams;
838 visitedStreams.Init();
840 for (PRInt32 i = 0; i < 2; ++i) {
841 for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
842 nsMediaCacheStream* stream = blocks[i]->mOwners[j].mStream;
843 // Make sure that we don't update the same stream twice --- that
844 // would result in swapping the block references back again!
845 if (visitedStreams.GetEntry(stream))
846 continue;
847 visitedStreams.PutEntry(stream);
848 stream->mReadaheadBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
849 stream->mPlayedBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
850 stream->mMetadataBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
854 Verify();
857 void
858 nsMediaCache::RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
860 Block* block = &mIndex[aBlockIndex];
861 for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
862 BlockOwner* bo = &block->mOwners[i];
863 if (bo->mStream == aStream) {
864 GetListForBlock(bo)->RemoveBlock(aBlockIndex);
865 bo->mStream->mBlocks[bo->mStreamBlock] = -1;
866 block->mOwners.RemoveElementAt(i);
867 if (block->mOwners.IsEmpty()) {
868 mFreeBlocks.AddFirstBlock(aBlockIndex);
870 return;
875 void
876 nsMediaCache::AddBlockOwnerAsReadahead(PRInt32 aBlockIndex,
877 nsMediaCacheStream* aStream,
878 PRInt32 aStreamBlockIndex)
880 Block* block = &mIndex[aBlockIndex];
881 if (block->mOwners.IsEmpty()) {
882 mFreeBlocks.RemoveBlock(aBlockIndex);
884 BlockOwner* bo = block->mOwners.AppendElement();
885 bo->mStream = aStream;
886 bo->mStreamBlock = aStreamBlockIndex;
887 aStream->mBlocks[aStreamBlockIndex] = aBlockIndex;
888 bo->mClass = READAHEAD_BLOCK;
889 InsertReadaheadBlock(bo, aBlockIndex);
892 void
893 nsMediaCache::FreeBlock(PRInt32 aBlock)
895 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
897 Block* block = &mIndex[aBlock];
898 if (block->mOwners.IsEmpty()) {
899 // already free
900 return;
903 LOG(PR_LOG_DEBUG, ("Released block %d", aBlock));
905 for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
906 BlockOwner* bo = &block->mOwners[i];
907 GetListForBlock(bo)->RemoveBlock(aBlock);
908 bo->mStream->mBlocks[bo->mStreamBlock] = -1;
910 block->mOwners.Clear();
911 mFreeBlocks.AddFirstBlock(aBlock);
912 Verify();
915 TimeDuration
916 nsMediaCache::PredictNextUse(TimeStamp aNow, PRInt32 aBlock)
918 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
919 NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
921 Block* block = &mIndex[aBlock];
922 // Blocks can be belong to multiple streams. The predicted next use
923 // time is the earliest time predicted by any of the streams.
924 TimeDuration result;
925 for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
926 BlockOwner* bo = &block->mOwners[i];
927 TimeDuration prediction;
928 switch (bo->mClass) {
929 case METADATA_BLOCK:
930 // This block should be managed in LRU mode. For metadata we predict
931 // that the time until the next use is the time since the last use.
932 prediction = aNow - bo->mLastUseTime;
933 break;
934 case PLAYED_BLOCK:
935 // This block should be managed in LRU mode, and we should impose
936 // a "replay delay" to reflect the likelihood of replay happening
937 NS_ASSERTION(PRInt64(bo->mStreamBlock)*BLOCK_SIZE <
938 bo->mStream->mStreamOffset,
939 "Played block after the current stream position?");
940 prediction = aNow - bo->mLastUseTime +
941 TimeDuration::FromSeconds(REPLAY_DELAY);
942 break;
943 case READAHEAD_BLOCK: {
944 PRInt64 bytesAhead =
945 PRInt64(bo->mStreamBlock)*BLOCK_SIZE - bo->mStream->mStreamOffset;
946 NS_ASSERTION(bytesAhead >= 0,
947 "Readahead block before the current stream position?");
948 PRInt64 millisecondsAhead =
949 bytesAhead*1000/bo->mStream->mPlaybackBytesPerSecond;
950 prediction = TimeDuration::FromMilliseconds(
951 PR_MIN(millisecondsAhead, PR_INT32_MAX));
952 break;
954 default:
955 NS_ERROR("Invalid class for predicting next use");
956 return TimeDuration(0);
958 if (i == 0 || prediction < result) {
959 result = prediction;
962 return result;
965 TimeDuration
966 nsMediaCache::PredictNextUseForIncomingData(nsMediaCacheStream* aStream)
968 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
970 PRInt64 bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
971 if (bytesAhead <= -BLOCK_SIZE) {
972 // Hmm, no idea when data behind us will be used. Guess 24 hours.
973 return TimeDuration::FromSeconds(24*60*60);
975 if (bytesAhead <= 0)
976 return TimeDuration(0);
977 PRInt64 millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond;
978 return TimeDuration::FromMilliseconds(
979 PR_MIN(millisecondsAhead, PR_INT32_MAX));
982 enum StreamAction { NONE, SEEK, RESUME, SUSPEND };
984 void
985 nsMediaCache::Update()
987 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
989 // The action to use for each stream. We store these so we can make
990 // decisions while holding the cache lock but implement those decisions
991 // without holding the cache lock, since we need to call out to
992 // stream, decoder and element code.
993 nsAutoTArray<StreamAction,10> actions;
996 nsAutoMonitor mon(mMonitor);
997 mUpdateQueued = PR_FALSE;
998 #ifdef DEBUG
999 mInUpdate = PR_TRUE;
1000 #endif
1002 PRInt32 maxBlocks = GetMaxBlocks();
1003 TimeStamp now = TimeStamp::Now();
1005 PRInt32 freeBlockCount = mFreeBlocks.GetCount();
1006 // Try to trim back the cache to its desired maximum size. The cache may
1007 // have overflowed simply due to data being received when we have
1008 // no blocks in the main part of the cache that are free or lower
1009 // priority than the new data. The cache can also be overflowing because
1010 // the media.cache_size preference was reduced.
1011 // First, figure out what the least valuable block in the cache overflow
1012 // is. We don't want to replace any blocks in the main part of the
1013 // cache whose expected time of next use is earlier or equal to that.
1014 // If we allow that, we can effectively end up discarding overflowing
1015 // blocks (by moving an overflowing block to the main part of the cache,
1016 // and then overwriting it with another overflowing block), and we try
1017 // to avoid that since it requires HTTP seeks.
1018 // We also use this loop to eliminate overflowing blocks from
1019 // freeBlockCount.
1020 TimeDuration latestPredictedUseForOverflow = 0;
1021 for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
1022 --blockIndex) {
1023 if (IsBlockFree(blockIndex)) {
1024 // Don't count overflowing free blocks in our free block count
1025 --freeBlockCount;
1026 continue;
1028 TimeDuration predictedUse = PredictNextUse(now, blockIndex);
1029 latestPredictedUseForOverflow = PR_MAX(latestPredictedUseForOverflow, predictedUse);
1032 // Now try to move overflowing blocks to the main part of the cache.
1033 for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
1034 --blockIndex) {
1035 if (IsBlockFree(blockIndex))
1036 continue;
1038 Block* block = &mIndex[blockIndex];
1039 // Try to relocate the block close to other blocks for the first stream.
1040 // There is no point in trying to make it close to other blocks in
1041 // *all* the streams it might belong to.
1042 PRInt32 destinationBlockIndex =
1043 FindReusableBlock(now, block->mOwners[0].mStream,
1044 block->mOwners[0].mStreamBlock, maxBlocks);
1045 if (destinationBlockIndex < 0) {
1046 // Nowhere to place this overflow block. We won't be able to
1047 // place any more overflow blocks.
1048 break;
1051 if (IsBlockFree(destinationBlockIndex) ||
1052 PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
1053 // Reuse blocks in the main part of the cache that are less useful than
1054 // the least useful overflow blocks
1055 char buf[BLOCK_SIZE];
1056 nsresult rv = ReadCacheFileAllBytes(blockIndex*BLOCK_SIZE, buf, sizeof(buf));
1057 if (NS_SUCCEEDED(rv)) {
1058 rv = WriteCacheFile(destinationBlockIndex*BLOCK_SIZE, buf, BLOCK_SIZE);
1059 if (NS_SUCCEEDED(rv)) {
1060 // We successfully copied the file data.
1061 LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
1062 blockIndex, destinationBlockIndex));
1063 // Swapping the block metadata here lets us maintain the
1064 // correct positions in the linked lists
1065 SwapBlocks(blockIndex, destinationBlockIndex);
1066 } else {
1067 // If the write fails we may have corrupted the destination
1068 // block. Free it now.
1069 LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
1070 destinationBlockIndex));
1071 FreeBlock(destinationBlockIndex);
1073 // Free the overflowing block even if the copy failed.
1074 LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
1075 blockIndex));
1076 FreeBlock(blockIndex);
1080 // Try chopping back the array of cache entries and the cache file.
1081 Truncate();
1083 // Count the blocks allocated for readahead of non-seekable streams
1084 // (these blocks can't be freed but we don't want them to monopolize the
1085 // cache)
1086 PRInt32 nonSeekableReadaheadBlockCount = 0;
1087 for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
1088 nsMediaCacheStream* stream = mStreams[i];
1089 if (!stream->mIsSeekable) {
1090 nonSeekableReadaheadBlockCount += stream->mReadaheadBlocks.GetCount();
1094 // If freeBlockCount is zero, then compute the latest of
1095 // the predicted next-uses for all blocks
1096 TimeDuration latestNextUse;
1097 if (freeBlockCount == 0) {
1098 PRInt32 reusableBlock = FindReusableBlock(now, nsnull, 0, maxBlocks);
1099 if (reusableBlock >= 0) {
1100 latestNextUse = PredictNextUse(now, reusableBlock);
1104 for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
1105 actions.AppendElement(NONE);
1107 nsMediaCacheStream* stream = mStreams[i];
1108 if (stream->mClosed)
1109 continue;
1111 // Figure out where we should be reading from. It's the first
1112 // uncached byte after the current mStreamOffset.
1113 PRInt64 dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
1115 // Compute where we'd actually seek to to read at readOffset
1116 PRInt64 desiredOffset = dataOffset;
1117 if (stream->mIsSeekable) {
1118 if (desiredOffset > stream->mChannelOffset &&
1119 desiredOffset <= stream->mChannelOffset + SEEK_VS_READ_THRESHOLD) {
1120 // Assume it's more efficient to just keep reading up to the
1121 // desired position instead of trying to seek
1122 desiredOffset = stream->mChannelOffset;
1124 } else {
1125 // We can't seek directly to the desired offset...
1126 if (stream->mChannelOffset > desiredOffset) {
1127 // Reading forward won't get us anywhere, we need to go backwards.
1128 // Seek back to 0 (the client will reopen the stream) and then
1129 // read forward.
1130 NS_WARNING("Can't seek backwards, so seeking to 0");
1131 desiredOffset = 0;
1132 // Flush cached blocks out, since if this is a live stream
1133 // the cached data may be completely different next time we
1134 // read it. We have to assume that live streams don't
1135 // advertise themselves as being seekable...
1136 ReleaseStreamBlocks(stream);
1137 } else {
1138 // otherwise reading forward is looking good, so just stay where we
1139 // are and don't trigger a channel seek!
1140 desiredOffset = stream->mChannelOffset;
1144 // Figure out if we should be reading data now or not. It's amazing
1145 // how complex this is, but each decision is simple enough.
1146 PRBool enableReading;
1147 if (stream->mStreamLength >= 0 && dataOffset >= stream->mStreamLength) {
1148 // We want data at the end of the stream, where there's nothing to
1149 // read. We don't want to try to read if we're suspended, because that
1150 // might create a new channel and seek unnecessarily (and incorrectly,
1151 // since HTTP doesn't allow seeking to the actual EOF), and we don't want
1152 // to suspend if we're not suspended and already reading at the end of
1153 // the stream, since there just might be more data than the server
1154 // advertised with Content-Length, and we may as well keep reading.
1155 // But we don't want to seek to the end of the stream if we're not
1156 // already there.
1157 LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream));
1158 enableReading = !stream->mCacheSuspended &&
1159 stream->mStreamLength == stream->mChannelOffset;
1160 } else if (desiredOffset < stream->mStreamOffset) {
1161 // We're reading to try to catch up to where the current stream
1162 // reader wants to be. Better not stop.
1163 LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream));
1164 enableReading = PR_TRUE;
1165 } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) {
1166 // The stream reader is waiting for us, or nearly so. Better feed it.
1167 LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream));
1168 enableReading = PR_TRUE;
1169 } else if (!stream->mIsSeekable &&
1170 nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) {
1171 // This stream is not seekable and there are already too many blocks
1172 // being cached for readahead for nonseekable streams (which we can't
1173 // free). So stop reading ahead now.
1174 LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream));
1175 enableReading = PR_FALSE;
1176 } else if (mIndex.Length() > PRUint32(maxBlocks)) {
1177 // We're in the process of bringing the cache size back to the
1178 // desired limit, so don't bring in more data yet
1179 LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream));
1180 enableReading = PR_FALSE;
1181 } else if (freeBlockCount > 0 || mIndex.Length() < PRUint32(maxBlocks)) {
1182 // Free blocks in the cache, so keep reading
1183 LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream));
1184 enableReading = PR_TRUE;
1185 } else if (latestNextUse <= TimeDuration(0)) {
1186 // No reusable blocks, so can't read anything
1187 LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream));
1188 enableReading = PR_FALSE;
1189 } else {
1190 // Read ahead if the data we expect to read is more valuable than
1191 // the least valuable block in the main part of the cache
1192 TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream);
1193 LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %f, current worst block is %f",
1194 stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds()));
1195 enableReading = predictedNewDataUse < latestNextUse;
1198 if (enableReading) {
1199 for (PRUint32 j = 0; j < i; ++j) {
1200 nsMediaCacheStream* other = mStreams[j];
1201 if (other->mResourceID == stream->mResourceID &&
1202 !other->mCacheSuspended &&
1203 other->mChannelOffset/BLOCK_SIZE == desiredOffset/BLOCK_SIZE) {
1204 // This block is already going to be read by the other stream.
1205 // So don't try to read it from this stream as well.
1206 enableReading = PR_FALSE;
1207 break;
1212 if (stream->mChannelOffset != desiredOffset && enableReading) {
1213 // We need to seek now.
1214 NS_ASSERTION(stream->mIsSeekable || desiredOffset == 0,
1215 "Trying to seek in a non-seekable stream!");
1216 // Round seek offset down to the start of the block. This is essential
1217 // because we don't want to think we have part of a block already
1218 // in mPartialBlockBuffer.
1219 stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE;
1220 actions[i] = SEEK;
1221 } else if (enableReading && stream->mCacheSuspended) {
1222 actions[i] = RESUME;
1223 } else if (!enableReading && !stream->mCacheSuspended) {
1224 actions[i] = SUSPEND;
1227 #ifdef DEBUG
1228 mInUpdate = PR_FALSE;
1229 #endif
1232 // Update the channel state without holding our cache lock. While we're
1233 // doing this, decoder threads may be running and seeking, reading or changing
1234 // other cache state. That's OK, they'll trigger new Update events and we'll
1235 // get back here and revise our decisions. The important thing here is that
1236 // performing these actions only depends on mChannelOffset and
1237 // mCacheSuspended, which can only be written by the main thread (i.e., this
1238 // thread), so we don't have races here.
1239 for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
1240 nsMediaCacheStream* stream = mStreams[i];
1241 nsresult rv = NS_OK;
1242 switch (actions[i]) {
1243 case SEEK:
1244 LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream,
1245 (long long)stream->mChannelOffset, stream->mCacheSuspended));
1246 rv = stream->mClient->CacheClientSeek(stream->mChannelOffset,
1247 stream->mCacheSuspended);
1248 stream->mCacheSuspended = PR_FALSE;
1249 break;
1251 case RESUME:
1252 LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream));
1253 rv = stream->mClient->CacheClientResume();
1254 stream->mCacheSuspended = PR_FALSE;
1255 break;
1257 case SUSPEND:
1258 LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream));
1259 rv = stream->mClient->CacheClientSuspend();
1260 stream->mCacheSuspended = PR_TRUE;
1261 break;
1263 default:
1264 break;
1267 if (NS_FAILED(rv)) {
1268 // Close the streams that failed due to error. This will cause all
1269 // client Read and Seek operations on those streams to fail. Blocked
1270 // Reads will also be woken up.
1271 nsAutoMonitor mon(mMonitor);
1272 stream->CloseInternal(&mon);
1277 class UpdateEvent : public nsRunnable
1279 public:
1280 NS_IMETHOD Run()
1282 if (gMediaCache) {
1283 gMediaCache->Update();
1285 return NS_OK;
1289 void
1290 nsMediaCache::QueueUpdate()
1292 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1294 // Queuing an update while we're in an update raises a high risk of
1295 // triggering endless events
1296 NS_ASSERTION(!mInUpdate,
1297 "Queuing an update while we're in an update");
1298 if (mUpdateQueued)
1299 return;
1300 mUpdateQueued = PR_TRUE;
1301 nsCOMPtr<nsIRunnable> event = new UpdateEvent();
1302 NS_DispatchToMainThread(event);
1305 #ifdef DEBUG_VERIFY_CACHE
1306 void
1307 nsMediaCache::Verify()
1309 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1311 mFreeBlocks.Verify();
1312 for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
1313 nsMediaCacheStream* stream = mStreams[i];
1314 stream->mReadaheadBlocks.Verify();
1315 stream->mPlayedBlocks.Verify();
1316 stream->mMetadataBlocks.Verify();
1318 // Verify that the readahead blocks are listed in stream block order
1319 PRInt32 block = stream->mReadaheadBlocks.GetFirstBlock();
1320 PRInt32 lastStreamBlock = -1;
1321 while (block >= 0) {
1322 PRUint32 j = 0;
1323 while (mIndex[block].mOwners[j].mStream != stream) {
1324 ++j;
1326 PRInt32 nextStreamBlock =
1327 PRInt32(mIndex[block].mOwners[j].mStreamBlock);
1328 NS_ASSERTION(lastStreamBlock < nextStreamBlock,
1329 "Blocks not increasing in readahead stream");
1330 lastStreamBlock = nextStreamBlock;
1331 block = stream->mReadaheadBlocks.GetNextBlock(block);
1335 #endif
1337 void
1338 nsMediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
1339 PRInt32 aBlockIndex)
1341 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1343 // Find the last block whose stream block is before aBlockIndex's
1344 // stream block, and insert after it
1345 nsMediaCacheStream* stream = aBlockOwner->mStream;
1346 PRInt32 readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
1347 while (readaheadIndex >= 0) {
1348 BlockOwner* bo = GetBlockOwner(readaheadIndex, stream);
1349 NS_ASSERTION(bo, "stream must own its blocks");
1350 if (bo->mStreamBlock < aBlockOwner->mStreamBlock) {
1351 stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
1352 return;
1354 NS_ASSERTION(bo->mStreamBlock > aBlockOwner->mStreamBlock,
1355 "Duplicated blocks??");
1356 readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex);
1359 stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
1360 Verify();
1363 void
1364 nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
1365 nsMediaCacheStream::ReadMode aMode)
1367 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1369 PRInt32 streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
1371 // Remove all cached copies of this block
1372 ResourceStreamIterator iter(aStream->mResourceID);
1373 while (nsMediaCacheStream* stream = iter.Next()) {
1374 while (streamBlockIndex >= PRInt32(stream->mBlocks.Length())) {
1375 stream->mBlocks.AppendElement(-1);
1377 if (stream->mBlocks[streamBlockIndex] >= 0) {
1378 // We no longer want to own this block
1379 PRInt32 globalBlockIndex = stream->mBlocks[streamBlockIndex];
1380 LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
1381 globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
1382 RemoveBlockOwner(globalBlockIndex, stream);
1386 // Extend the mBlocks array as necessary
1388 TimeStamp now = TimeStamp::Now();
1389 PRInt32 blockIndex = FindBlockForIncomingData(now, aStream);
1390 if (blockIndex >= 0) {
1391 FreeBlock(blockIndex);
1393 Block* block = &mIndex[blockIndex];
1394 LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
1395 blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
1397 mFreeBlocks.RemoveBlock(blockIndex);
1399 // Tell each stream using this resource about the new block.
1400 ResourceStreamIterator iter(aStream->mResourceID);
1401 while (nsMediaCacheStream* stream = iter.Next()) {
1402 BlockOwner* bo = block->mOwners.AppendElement();
1403 if (!bo)
1404 return;
1406 bo->mStream = stream;
1407 bo->mStreamBlock = streamBlockIndex;
1408 bo->mLastUseTime = now;
1409 stream->mBlocks[streamBlockIndex] = blockIndex;
1410 if (streamBlockIndex*BLOCK_SIZE < stream->mStreamOffset) {
1411 bo->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK
1412 ? PLAYED_BLOCK : METADATA_BLOCK;
1413 // This must be the most-recently-used block, since we
1414 // marked it as used now (which may be slightly bogus, but we'll
1415 // treat it as used for simplicity).
1416 GetListForBlock(bo)->AddFirstBlock(blockIndex);
1417 Verify();
1418 } else {
1419 // This may not be the latest readahead block, although it usually
1420 // will be. We may have to scan for the right place to insert
1421 // the block in the list.
1422 bo->mClass = READAHEAD_BLOCK;
1423 InsertReadaheadBlock(bo, blockIndex);
1427 nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
1428 if (NS_FAILED(rv)) {
1429 LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
1430 blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
1431 FreeBlock(blockIndex);
1435 // Queue an Update since the cache state has changed (for example
1436 // we might want to stop loading because the cache is full)
1437 QueueUpdate();
1440 void
1441 nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
1443 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1445 nsAutoMonitor mon(mMonitor);
1446 LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream));
1447 mStreams.AppendElement(aStream);
1448 aStream->mResourceID = mNextResourceID++;
1451 void
1452 nsMediaCache::ReleaseStream(nsMediaCacheStream* aStream)
1454 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1456 nsAutoMonitor mon(mMonitor);
1457 LOG(PR_LOG_DEBUG, ("Stream %p closed", aStream));
1458 mStreams.RemoveElement(aStream);
1461 void
1462 nsMediaCache::ReleaseStreamBlocks(nsMediaCacheStream* aStream)
1464 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1466 // XXX scanning the entire stream doesn't seem great, if not much of it
1467 // is cached, but the only easy alternative is to scan the entire cache
1468 // which isn't better
1469 PRUint32 length = aStream->mBlocks.Length();
1470 for (PRUint32 i = 0; i < length; ++i) {
1471 PRInt32 blockIndex = aStream->mBlocks[i];
1472 if (blockIndex >= 0) {
1473 LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
1474 blockIndex, aStream, i, (long long)i*BLOCK_SIZE));
1475 RemoveBlockOwner(blockIndex, aStream);
1480 void
1481 nsMediaCache::Truncate()
1483 PRUint32 end;
1484 for (end = mIndex.Length(); end > 0; --end) {
1485 if (!IsBlockFree(end - 1))
1486 break;
1487 mFreeBlocks.RemoveBlock(end - 1);
1490 if (end < mIndex.Length()) {
1491 mIndex.TruncateLength(end);
1492 // XXX We could truncate the cache file here, but we don't seem
1493 // to have a cross-platform API for doing that. At least when all
1494 // streams are closed we shut down the cache, which erases the
1495 // file at that point.
1499 void
1500 nsMediaCache::NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
1501 nsMediaCacheStream::ReadMode aMode,
1502 TimeStamp aNow)
1504 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1506 if (aBlockIndex < 0) {
1507 // this block is not in the cache yet
1508 return;
1511 BlockOwner* bo = GetBlockOwner(aBlockIndex, aStream);
1512 if (!bo) {
1513 // this block is not in the cache yet
1514 return;
1517 // The following check has to be <= because the stream offset has
1518 // not yet been updated for the data read from this block
1519 NS_ASSERTION(bo->mStreamBlock*BLOCK_SIZE <= bo->mStream->mStreamOffset,
1520 "Using a block that's behind the read position?");
1522 GetListForBlock(bo)->RemoveBlock(aBlockIndex);
1523 bo->mClass =
1524 (aMode == nsMediaCacheStream::MODE_METADATA || bo->mClass == METADATA_BLOCK)
1525 ? METADATA_BLOCK : PLAYED_BLOCK;
1526 // Since this is just being used now, it can definitely be at the front
1527 // of mMetadataBlocks or mPlayedBlocks
1528 GetListForBlock(bo)->AddFirstBlock(aBlockIndex);
1529 bo->mLastUseTime = aNow;
1530 Verify();
1533 void
1534 nsMediaCache::NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset)
1536 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
1538 if (aOldOffset < aStream->mStreamOffset) {
1539 // We seeked forward. Convert blocks from readahead to played.
1540 // Any readahead block that intersects the seeked-over range must
1541 // be converted.
1542 PRInt32 blockIndex = aOldOffset/BLOCK_SIZE;
1543 PRInt32 endIndex =
1544 PR_MIN((aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
1545 aStream->mBlocks.Length());
1546 TimeStamp now = TimeStamp::Now();
1547 while (blockIndex < endIndex) {
1548 PRInt32 cacheBlockIndex = aStream->mBlocks[blockIndex];
1549 if (cacheBlockIndex >= 0) {
1550 // Marking the block used may not be exactly what we want but
1551 // it's simple
1552 NoteBlockUsage(aStream, cacheBlockIndex, nsMediaCacheStream::MODE_PLAYBACK,
1553 now);
1555 ++blockIndex;
1557 } else {
1558 // We seeked backward. Convert from played to readahead.
1559 // Any played block that is entirely after the start of the seeked-over
1560 // range must be converted.
1561 PRInt32 blockIndex =
1562 (aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE;
1563 PRInt32 endIndex =
1564 PR_MIN((aOldOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
1565 aStream->mBlocks.Length());
1566 while (blockIndex < endIndex) {
1567 PRInt32 cacheBlockIndex = aStream->mBlocks[endIndex - 1];
1568 if (cacheBlockIndex >= 0) {
1569 BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
1570 NS_ASSERTION(bo, "Stream doesn't own its blocks?");
1571 if (bo->mClass == PLAYED_BLOCK) {
1572 aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
1573 bo->mClass = READAHEAD_BLOCK;
1574 // Adding this as the first block is sure to be OK since
1575 // this must currently be the earliest readahead block
1576 // (that's why we're proceeding backwards from the end of
1577 // the seeked range to the start)
1578 aStream->mReadaheadBlocks.AddFirstBlock(cacheBlockIndex);
1579 Verify();
1582 --endIndex;
1587 void
1588 nsMediaCacheStream::NotifyDataLength(PRInt64 aLength)
1590 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1592 nsAutoMonitor mon(gMediaCache->Monitor());
1593 mStreamLength = aLength;
1596 void
1597 nsMediaCacheStream::NotifyDataStarted(PRInt64 aOffset)
1599 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1601 nsAutoMonitor mon(gMediaCache->Monitor());
1602 NS_WARN_IF_FALSE(aOffset == mChannelOffset,
1603 "Server is giving us unexpected offset");
1604 mChannelOffset = aOffset;
1605 if (mStreamLength >= 0) {
1606 // If we started reading at a certain offset, then for sure
1607 // the stream is at least that long.
1608 mStreamLength = PR_MAX(mStreamLength, mChannelOffset);
1612 void
1613 nsMediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
1615 if (!mPrincipal) {
1616 NS_ASSERTION(!mUsingNullPrincipal, "Are we using a null principal or not?");
1617 if (mUsingNullPrincipal) {
1618 // Don't let mPrincipal be set to anything
1619 return;
1621 mPrincipal = aPrincipal;
1622 return;
1625 if (mPrincipal == aPrincipal) {
1626 // Common case
1627 NS_ASSERTION(!mUsingNullPrincipal, "We can't receive data from a null principal");
1628 return;
1630 if (mUsingNullPrincipal) {
1631 // We've already fallen back to a null principal, so nothing more
1632 // to do.
1633 return;
1636 PRBool equal;
1637 nsresult rv = mPrincipal->Equals(aPrincipal, &equal);
1638 if (NS_SUCCEEDED(rv) && equal)
1639 return;
1641 // Principals are not equal, so set mPrincipal to a null principal.
1642 mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
1643 mUsingNullPrincipal = PR_TRUE;
1646 void
1647 nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData,
1648 nsIPrincipal* aPrincipal)
1650 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1652 nsAutoMonitor mon(gMediaCache->Monitor());
1653 PRInt64 size = aSize;
1654 const char* data = aData;
1656 LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld",
1657 this, (long long)mChannelOffset, (long long)aSize));
1659 // We process the data one block (or part of a block) at a time
1660 while (size > 0) {
1661 PRUint32 blockIndex = mChannelOffset/BLOCK_SIZE;
1662 PRInt32 blockOffset = PRInt32(mChannelOffset - blockIndex*BLOCK_SIZE);
1663 PRInt32 chunkSize = PRInt32(PR_MIN(BLOCK_SIZE - blockOffset, size));
1665 // This gets set to something non-null if we have a whole block
1666 // of data to write to the cache
1667 const char* blockDataToStore = nsnull;
1668 ReadMode mode = MODE_PLAYBACK;
1669 if (blockOffset == 0 && chunkSize == BLOCK_SIZE) {
1670 // We received a whole block, so avoid a useless copy through
1671 // mPartialBlockBuffer
1672 blockDataToStore = data;
1673 } else {
1674 if (blockOffset == 0) {
1675 // We've just started filling this buffer so now is a good time
1676 // to clear this flag.
1677 mMetadataInPartialBlockBuffer = PR_FALSE;
1679 memcpy(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset,
1680 data, chunkSize);
1682 if (blockOffset + chunkSize == BLOCK_SIZE) {
1683 // We completed a block, so lets write it out.
1684 blockDataToStore = reinterpret_cast<char*>(mPartialBlockBuffer);
1685 if (mMetadataInPartialBlockBuffer) {
1686 mode = MODE_METADATA;
1691 if (blockDataToStore) {
1692 gMediaCache->AllocateAndWriteBlock(this, blockDataToStore, mode);
1695 mChannelOffset += chunkSize;
1696 size -= chunkSize;
1697 data += chunkSize;
1700 nsMediaCache::ResourceStreamIterator iter(mResourceID);
1701 while (nsMediaCacheStream* stream = iter.Next()) {
1702 if (stream->mStreamLength >= 0) {
1703 // The stream is at least as long as what we've read
1704 stream->mStreamLength = PR_MAX(stream->mStreamLength, mChannelOffset);
1706 stream->UpdatePrincipal(aPrincipal);
1707 stream->mClient->CacheClientNotifyDataReceived();
1710 // Notify in case there's a waiting reader
1711 // XXX it would be fairly easy to optimize things a lot more to
1712 // avoid waking up reader threads unnecessarily
1713 mon.NotifyAll();
1716 void
1717 nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
1719 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1721 nsAutoMonitor mon(gMediaCache->Monitor());
1723 PRInt32 blockOffset = PRInt32(mChannelOffset%BLOCK_SIZE);
1724 if (blockOffset > 0) {
1725 // Write back the partial block
1726 memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
1727 BLOCK_SIZE - blockOffset);
1728 gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
1729 mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
1730 // Wake up readers who may be waiting for this data
1731 mon.NotifyAll();
1734 nsMediaCache::ResourceStreamIterator iter(mResourceID);
1735 while (nsMediaCacheStream* stream = iter.Next()) {
1736 if (NS_SUCCEEDED(aStatus)) {
1737 // We read the whole stream, so remember the true length
1738 stream->mStreamLength = mChannelOffset;
1740 stream->mClient->CacheClientNotifyDataEnded(aStatus);
1744 nsMediaCacheStream::~nsMediaCacheStream()
1746 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1747 NS_ASSERTION(mClosed, "Stream was not closed");
1748 NS_ASSERTION(!mPinCount, "Unbalanced Pin");
1750 gMediaCache->ReleaseStream(this);
1751 nsMediaCache::MaybeShutdown();
1754 void
1755 nsMediaCacheStream::SetSeekable(PRBool aIsSeekable)
1757 nsAutoMonitor mon(gMediaCache->Monitor());
1758 NS_ASSERTION(mIsSeekable || aIsSeekable ||
1759 mChannelOffset == 0, "channel offset must be zero when we become non-seekable");
1760 mIsSeekable = aIsSeekable;
1761 // Queue an Update since we may change our strategy for dealing
1762 // with this stream
1763 gMediaCache->QueueUpdate();
1766 PRBool
1767 nsMediaCacheStream::IsSeekable()
1769 nsAutoMonitor mon(gMediaCache->Monitor());
1770 return mIsSeekable;
1773 void
1774 nsMediaCacheStream::Close()
1776 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1778 nsAutoMonitor mon(gMediaCache->Monitor());
1779 CloseInternal(&mon);
1780 // Queue an Update since we may have created more free space. Don't do
1781 // it from CloseInternal since that gets called by Update() itself
1782 // sometimes, and we try to not to queue updates from Update().
1783 gMediaCache->QueueUpdate();
1786 void
1787 nsMediaCacheStream::CloseInternal(nsAutoMonitor* aMonitor)
1789 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1791 if (mClosed)
1792 return;
1793 mClosed = PR_TRUE;
1794 gMediaCache->ReleaseStreamBlocks(this);
1795 // Wake up any blocked readers
1796 aMonitor->NotifyAll();
1799 void
1800 nsMediaCacheStream::Pin()
1802 nsAutoMonitor mon(gMediaCache->Monitor());
1803 ++mPinCount;
1804 // Queue an Update since we may no longer want to read more into the
1805 // cache, if this stream's block have become non-evictable
1806 gMediaCache->QueueUpdate();
1809 void
1810 nsMediaCacheStream::Unpin()
1812 nsAutoMonitor mon(gMediaCache->Monitor());
1813 NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
1814 --mPinCount;
1815 // Queue an Update since we may be able to read more into the
1816 // cache, if this stream's block have become evictable
1817 gMediaCache->QueueUpdate();
1820 PRInt64
1821 nsMediaCacheStream::GetLength()
1823 nsAutoMonitor mon(gMediaCache->Monitor());
1824 return mStreamLength;
1827 PRInt64
1828 nsMediaCacheStream::GetNextCachedData(PRInt64 aOffset)
1830 nsAutoMonitor mon(gMediaCache->Monitor());
1831 return GetNextCachedDataInternal(aOffset);
1834 PRInt64
1835 nsMediaCacheStream::GetCachedDataEnd(PRInt64 aOffset)
1837 nsAutoMonitor mon(gMediaCache->Monitor());
1838 return GetCachedDataEndInternal(aOffset);
1841 PRBool
1842 nsMediaCacheStream::IsDataCachedToEndOfStream(PRInt64 aOffset)
1844 nsAutoMonitor mon(gMediaCache->Monitor());
1845 if (mStreamLength < 0)
1846 return PR_FALSE;
1847 return GetCachedDataEndInternal(aOffset) >= mStreamLength;
1850 PRInt64
1851 nsMediaCacheStream::GetCachedDataEndInternal(PRInt64 aOffset)
1853 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(gMediaCache->Monitor());
1854 PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
1855 PRUint32 blockIndex = startBlockIndex;
1856 while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
1857 ++blockIndex;
1859 PRInt64 result = blockIndex*BLOCK_SIZE;
1860 if (blockIndex == mChannelOffset/BLOCK_SIZE) {
1861 // The block containing mChannelOffset may be partially read but not
1862 // yet committed to the main cache
1863 result = mChannelOffset;
1865 if (mStreamLength >= 0) {
1866 // The last block in the cache may only be partially valid, so limit
1867 // the cached range to the stream length
1868 result = PR_MIN(result, mStreamLength);
1870 return PR_MAX(result, aOffset);
1873 PRInt64
1874 nsMediaCacheStream::GetNextCachedDataInternal(PRInt64 aOffset)
1876 PR_ASSERT_CURRENT_THREAD_IN_MONITOR(gMediaCache->Monitor());
1877 if (aOffset == mStreamLength)
1878 return -1;
1880 PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
1881 PRUint32 channelBlockIndex = mChannelOffset/BLOCK_SIZE;
1883 if (startBlockIndex == channelBlockIndex &&
1884 aOffset < mChannelOffset) {
1885 // The block containing mChannelOffset is partially read, but not
1886 // yet committed to the main cache. aOffset lies in the partially
1887 // read portion, thus it is effectively cached.
1888 return aOffset;
1891 if (startBlockIndex >= mBlocks.Length())
1892 return -1;
1894 // Is the current block cached?
1895 if (mBlocks[startBlockIndex] != -1)
1896 return aOffset;
1898 // Count the number of uncached blocks
1899 PRBool hasPartialBlock = (mChannelOffset % BLOCK_SIZE) != 0;
1900 PRUint32 blockIndex = startBlockIndex + 1;
1901 while (PR_TRUE) {
1902 if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
1903 (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
1904 // We at the incoming channel block, which has has data in it,
1905 // or are we at a cached block. Return index of block start.
1906 return blockIndex * BLOCK_SIZE;
1909 // No more cached blocks?
1910 if (blockIndex >= mBlocks.Length())
1911 return -1;
1913 ++blockIndex;
1916 NS_NOTREACHED("Should return in loop");
1917 return -1;
1920 void
1921 nsMediaCacheStream::SetReadMode(ReadMode aMode)
1923 nsAutoMonitor mon(gMediaCache->Monitor());
1924 if (aMode == mCurrentMode)
1925 return;
1926 mCurrentMode = aMode;
1927 gMediaCache->QueueUpdate();
1930 void
1931 nsMediaCacheStream::SetPlaybackRate(PRUint32 aBytesPerSecond)
1933 NS_ASSERTION(aBytesPerSecond > 0, "Zero playback rate not allowed");
1934 nsAutoMonitor mon(gMediaCache->Monitor());
1935 if (aBytesPerSecond == mPlaybackBytesPerSecond)
1936 return;
1937 mPlaybackBytesPerSecond = aBytesPerSecond;
1938 gMediaCache->QueueUpdate();
1941 nsresult
1942 nsMediaCacheStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
1944 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1946 nsAutoMonitor mon(gMediaCache->Monitor());
1947 if (mClosed)
1948 return NS_ERROR_FAILURE;
1950 PRInt64 oldOffset = mStreamOffset;
1951 switch (aWhence) {
1952 case PR_SEEK_END:
1953 if (mStreamLength < 0)
1954 return NS_ERROR_FAILURE;
1955 mStreamOffset = mStreamLength + aOffset;
1956 break;
1957 case PR_SEEK_CUR:
1958 mStreamOffset += aOffset;
1959 break;
1960 case PR_SEEK_SET:
1961 mStreamOffset = aOffset;
1962 break;
1963 default:
1964 NS_ERROR("Unknown whence");
1965 return NS_ERROR_FAILURE;
1968 LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset));
1969 gMediaCache->NoteSeek(this, oldOffset);
1971 gMediaCache->QueueUpdate();
1972 return NS_OK;
1975 PRInt64
1976 nsMediaCacheStream::Tell()
1978 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1980 nsAutoMonitor mon(gMediaCache->Monitor());
1981 return mStreamOffset;
1984 nsresult
1985 nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
1987 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
1989 nsAutoMonitor mon(gMediaCache->Monitor());
1990 if (mClosed)
1991 return NS_ERROR_FAILURE;
1993 PRUint32 count = 0;
1994 // Read one block (or part of a block) at a time
1995 while (count < aCount) {
1996 PRUint32 streamBlock = PRUint32(mStreamOffset/BLOCK_SIZE);
1997 PRUint32 offsetInStreamBlock =
1998 PRUint32(mStreamOffset - streamBlock*BLOCK_SIZE);
1999 PRInt32 size = PR_MIN(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
2001 if (mStreamLength >= 0) {
2002 // Don't try to read beyond the end of the stream
2003 PRInt64 bytesRemaining = mStreamLength - mStreamOffset;
2004 if (bytesRemaining <= 0) {
2005 // Get out of here and return NS_OK
2006 break;
2008 size = PR_MIN(size, PRInt32(bytesRemaining));
2011 PRInt32 bytes;
2012 PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
2013 PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
2014 if (channelBlock == streamBlock && mStreamOffset < mChannelOffset) {
2015 // We can just use the data in mPartialBlockBuffer. In fact we should
2016 // use it rather than waiting for the block to fill and land in
2017 // the cache.
2018 bytes = PR_MIN(size, mChannelOffset - mStreamOffset);
2019 memcpy(aBuffer + count,
2020 reinterpret_cast<char*>(mPartialBlockBuffer) + offsetInStreamBlock, bytes);
2021 if (mCurrentMode == MODE_METADATA) {
2022 mMetadataInPartialBlockBuffer = PR_TRUE;
2024 gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
2025 } else {
2026 if (cacheBlock < 0) {
2027 if (count > 0) {
2028 // Some data has been read, so return what we've got instead of
2029 // blocking
2030 break;
2033 // No data has been read yet, so block
2034 mon.Wait();
2035 if (mClosed) {
2036 // We may have successfully read some data, but let's just throw
2037 // that out.
2038 return NS_ERROR_FAILURE;
2040 continue;
2043 gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
2045 PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
2046 nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes);
2047 if (NS_FAILED(rv)) {
2048 if (count == 0)
2049 return rv;
2050 // If we did successfully read some data, may as well return it
2051 break;
2054 mStreamOffset += bytes;
2055 count += bytes;
2058 if (count > 0) {
2059 // Some data was read, so queue an update since block priorities may
2060 // have changed
2061 gMediaCache->QueueUpdate();
2063 LOG(PR_LOG_DEBUG,
2064 ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count));
2065 *aBytes = count;
2066 return NS_OK;
2069 nsresult
2070 nsMediaCacheStream::ReadFromCache(char* aBuffer,
2071 PRInt64 aOffset,
2072 PRInt64 aCount)
2074 nsAutoMonitor mon(gMediaCache->Monitor());
2075 if (mClosed)
2076 return NS_ERROR_FAILURE;
2078 // Read one block (or part of a block) at a time
2079 PRUint32 count = 0;
2080 PRInt64 streamOffset = aOffset;
2081 while (count < aCount) {
2082 PRUint32 streamBlock = PRUint32(streamOffset/BLOCK_SIZE);
2083 PRUint32 offsetInStreamBlock =
2084 PRUint32(streamOffset - streamBlock*BLOCK_SIZE);
2085 PRInt32 size = PR_MIN(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
2087 if (mStreamLength >= 0) {
2088 // Don't try to read beyond the end of the stream
2089 PRInt64 bytesRemaining = mStreamLength - streamOffset;
2090 if (bytesRemaining <= 0) {
2091 return NS_ERROR_FAILURE;
2093 size = PR_MIN(size, PRInt32(bytesRemaining));
2096 PRInt32 bytes;
2097 PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
2098 PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
2099 if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
2100 // We can just use the data in mPartialBlockBuffer. In fact we should
2101 // use it rather than waiting for the block to fill and land in
2102 // the cache.
2103 bytes = PR_MIN(size, mChannelOffset - streamOffset);
2104 memcpy(aBuffer + count,
2105 reinterpret_cast<char*>(mPartialBlockBuffer) + offsetInStreamBlock, bytes);
2106 } else {
2107 if (cacheBlock < 0) {
2108 // We expect all blocks to be cached! Fail!
2109 return NS_ERROR_FAILURE;
2111 PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
2112 nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes);
2113 if (NS_FAILED(rv)) {
2114 return rv;
2117 streamOffset += bytes;
2118 count += bytes;
2121 return NS_OK;
2124 nsresult
2125 nsMediaCacheStream::Init()
2127 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
2129 if (mInitialized)
2130 return NS_OK;
2132 InitMediaCache();
2133 if (!gMediaCache)
2134 return NS_ERROR_FAILURE;
2135 gMediaCache->OpenStream(this);
2136 mInitialized = PR_TRUE;
2137 return NS_OK;
2140 nsresult
2141 nsMediaCacheStream::InitAsClone(nsMediaCacheStream* aOriginal)
2143 if (mInitialized)
2144 return NS_OK;
2146 nsresult rv = Init();
2147 if (NS_FAILED(rv))
2148 return rv;
2149 mResourceID = aOriginal->mResourceID;
2151 // Grab cache blocks from aOriginal as readahead blocks for our stream
2152 nsAutoMonitor mon(gMediaCache->Monitor());
2154 mPrincipal = aOriginal->mPrincipal;
2155 mStreamLength = aOriginal->mStreamLength;
2156 mIsSeekable = aOriginal->mIsSeekable;
2158 // Cloned streams are initially suspended, since there is no channel open
2159 // initially for a clone.
2160 mCacheSuspended = PR_TRUE;
2162 for (PRUint32 i = 0; i < aOriginal->mBlocks.Length(); ++i) {
2163 PRInt32 cacheBlockIndex = aOriginal->mBlocks[i];
2164 if (cacheBlockIndex < 0)
2165 continue;
2167 while (i >= mBlocks.Length()) {
2168 mBlocks.AppendElement(-1);
2170 // Every block is a readahead block for the clone because the clone's initial
2171 // stream offset is zero
2172 gMediaCache->AddBlockOwnerAsReadahead(cacheBlockIndex, this, i);
2175 return NS_OK;