1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ReentrantMonitor.h"
9 #include "MediaCache.h"
11 #include "nsContentUtils.h"
12 #include "nsThreadUtils.h"
13 #include "MediaResource.h"
15 #include "mozilla/Preferences.h"
16 #include "FileBlockCache.h"
17 #include "nsAnonymousTemporaryFile.h"
18 #include "nsIObserverService.h"
19 #include "nsISeekableStream.h"
20 #include "nsIPrincipal.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/Services.h"
28 PRLogModuleInfo
* gMediaCacheLog
;
29 #define CACHE_LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
31 #define CACHE_LOG(type, msg)
34 // Readahead blocks for non-seekable streams will be limited to this
35 // fraction of the cache space. We don't normally evict such blocks
36 // because replacing them requires a seek, but we need to make sure
37 // they don't monopolize the cache.
38 static const double NONSEEKABLE_READAHEAD_MAX
= 0.5;
40 // Data N seconds before the current playback position is given the same priority
41 // as data REPLAY_PENALTY_FACTOR*N seconds ahead of the current playback
42 // position. REPLAY_PENALTY_FACTOR is greater than 1 to reflect that
43 // data in the past is less likely to be played again than data in the future.
44 // We want to give data just behind the current playback position reasonably
45 // high priority in case codecs need to retrieve that data (e.g. because
46 // tracks haven't been muxed well or are being decoded at uneven rates).
47 // 1/REPLAY_PENALTY_FACTOR as much data will be kept behind the
48 // current playback position as will be kept ahead of the current playback
50 static const uint32_t REPLAY_PENALTY_FACTOR
= 3;
52 // When looking for a reusable block, scan forward this many blocks
53 // from the desired "best" block location to look for free blocks,
54 // before we resort to scanning the whole cache. The idea is to try to
55 // store runs of stream blocks close-to-consecutively in the cache if we
57 static const uint32_t FREE_BLOCK_SCAN_LIMIT
= 16;
59 // Try to save power by not resuming paused reads if the stream won't need new
60 // data within this time interval in the future
61 static const uint32_t CACHE_POWERSAVE_WAKEUP_LOW_THRESHOLD_MS
= 10000;
64 // Turn this on to do very expensive cache state validation
65 // #define DEBUG_VERIFY_CACHE
68 // There is at most one media cache (although that could quite easily be
69 // relaxed if we wanted to manage multiple caches with independent
71 static MediaCache
* gMediaCache
;
73 class MediaCacheFlusher MOZ_FINAL
: public nsIObserver
,
74 public nsSupportsWeakReference
{
75 MediaCacheFlusher() {}
84 static MediaCacheFlusher
* gMediaCacheFlusher
;
86 NS_IMPL_ISUPPORTS2(MediaCacheFlusher
, nsIObserver
, nsISupportsWeakReference
)
88 MediaCacheFlusher::~MediaCacheFlusher()
90 gMediaCacheFlusher
= nullptr;
93 void MediaCacheFlusher::Init()
95 if (gMediaCacheFlusher
) {
99 gMediaCacheFlusher
= new MediaCacheFlusher();
100 NS_ADDREF(gMediaCacheFlusher
);
102 nsCOMPtr
<nsIObserverService
> observerService
=
103 mozilla::services::GetObserverService();
104 if (observerService
) {
105 observerService
->AddObserver(gMediaCacheFlusher
, "last-pb-context-exited", true);
106 observerService
->AddObserver(gMediaCacheFlusher
, "network-clear-cache-stored-anywhere", true);
112 friend class MediaCacheStream::BlockList
;
113 typedef MediaCacheStream::BlockList BlockList
;
115 BLOCK_SIZE
= MediaCacheStream::BLOCK_SIZE
118 MediaCache() : mNextResourceID(1),
119 mReentrantMonitor("MediaCache.mReentrantMonitor"),
125 MOZ_COUNT_CTOR(MediaCache
);
128 NS_ASSERTION(mStreams
.IsEmpty(), "Stream(s) still open!");
130 NS_ASSERTION(mIndex
.Length() == 0, "Blocks leaked?");
133 mFileCache
= nullptr;
135 MOZ_COUNT_DTOR(MediaCache
);
138 // Main thread only. Creates the backing cache file. If this fails,
139 // then the cache is still in a semi-valid state; mFD will be null,
140 // so all I/O on the cache file will fail.
142 // Shut down the global cache if it's no longer needed. We shut down
143 // the cache as soon as there are no streams. This means that during
144 // normal operation we are likely to start up the cache and shut it down
145 // many times, but that's OK since starting it up is cheap and
146 // shutting it down cleans things up and releases disk space.
147 static void MaybeShutdown();
149 // Brutally flush the cache contents. Main thread only.
151 void FlushInternal();
153 // Cache-file access methods. These are the lowest-level cache methods.
154 // mReentrantMonitor must be held; these can be called on any thread.
155 // This can return partial reads.
156 nsresult
ReadCacheFile(int64_t aOffset
, void* aData
, int32_t aLength
,
158 // This will fail if all aLength bytes are not read
159 nsresult
ReadCacheFileAllBytes(int64_t aOffset
, void* aData
, int32_t aLength
);
161 int64_t AllocateResourceID()
163 mReentrantMonitor
.AssertCurrentThreadIn();
164 return mNextResourceID
++;
167 // mReentrantMonitor must be held, called on main thread.
168 // These methods are used by the stream to set up and tear down streams,
169 // and to handle reads and writes.
170 // Add aStream to the list of streams.
171 void OpenStream(MediaCacheStream
* aStream
);
172 // Remove aStream from the list of streams.
173 void ReleaseStream(MediaCacheStream
* aStream
);
174 // Free all blocks belonging to aStream.
175 void ReleaseStreamBlocks(MediaCacheStream
* aStream
);
176 // Find a cache entry for this data, and write the data into it
177 void AllocateAndWriteBlock(MediaCacheStream
* aStream
, const void* aData
,
178 MediaCacheStream::ReadMode aMode
);
180 // mReentrantMonitor must be held; can be called on any thread
181 // Notify the cache that a seek has been requested. Some blocks may
182 // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
183 // This does not trigger channel seeks directly, the next Update()
184 // will do that if necessary. The caller will call QueueUpdate().
185 void NoteSeek(MediaCacheStream
* aStream
, int64_t aOldOffset
);
186 // Notify the cache that a block has been read from. This is used
187 // to update last-use times. The block may not actually have a
188 // cache entry yet since Read can read data from a stream's
189 // in-memory mPartialBlockBuffer while the block is only partly full,
190 // and thus hasn't yet been committed to the cache. The caller will
191 // call QueueUpdate().
192 void NoteBlockUsage(MediaCacheStream
* aStream
, int32_t aBlockIndex
,
193 MediaCacheStream::ReadMode aMode
, TimeStamp aNow
);
194 // Mark aStream as having the block, adding it as an owner.
195 void AddBlockOwnerAsReadahead(int32_t aBlockIndex
, MediaCacheStream
* aStream
,
196 int32_t aStreamBlockIndex
);
198 // This queues a call to Update() on the main thread.
201 // Updates the cache state asynchronously on the main thread:
202 // -- try to trim the cache back to its desired size, if necessary
203 // -- suspend channels that are going to read data that's lower priority
204 // than anything currently cached
205 // -- resume channels that are going to read data that's higher priority
206 // than something currently cached
207 // -- seek channels that need to seek to a new location
210 #ifdef DEBUG_VERIFY_CACHE
211 // Verify invariants, especially block list invariants
217 ReentrantMonitor
& GetReentrantMonitor() { return mReentrantMonitor
; }
220 * An iterator that makes it easy to iterate through all streams that
221 * have a given resource ID and are not closed.
222 * Can be used on the main thread or while holding the media cache lock.
224 class ResourceStreamIterator
{
226 ResourceStreamIterator(int64_t aResourceID
) :
227 mResourceID(aResourceID
), mNext(0) {}
228 MediaCacheStream
* Next()
230 while (mNext
< gMediaCache
->mStreams
.Length()) {
231 MediaCacheStream
* stream
= gMediaCache
->mStreams
[mNext
];
233 if (stream
->GetResourceID() == mResourceID
&& !stream
->IsClosed())
244 // Find a free or reusable block and return its index. If there are no
245 // free blocks and no reusable blocks, add a new block to the cache
246 // and return it. Can return -1 on OOM.
247 int32_t FindBlockForIncomingData(TimeStamp aNow
, MediaCacheStream
* aStream
);
248 // Find a reusable block --- a free block, if there is one, otherwise
249 // the reusable block with the latest predicted-next-use, or -1 if
250 // there aren't any freeable blocks. Only block indices less than
251 // aMaxSearchBlockIndex are considered. If aForStream is non-null,
252 // then aForStream and aForStreamBlock indicate what media data will
253 // be placed; FindReusableBlock will favour returning free blocks
254 // near other blocks for that point in the stream.
255 int32_t FindReusableBlock(TimeStamp aNow
,
256 MediaCacheStream
* aForStream
,
257 int32_t aForStreamBlock
,
258 int32_t aMaxSearchBlockIndex
);
259 bool BlockIsReusable(int32_t aBlockIndex
);
260 // Given a list of blocks sorted with the most reusable blocks at the
261 // end, find the last block whose stream is not pinned (if any)
262 // and whose cache entry index is less than aBlockIndexLimit
263 // and append it to aResult.
264 void AppendMostReusableBlock(BlockList
* aBlockList
,
265 nsTArray
<uint32_t>* aResult
,
266 int32_t aBlockIndexLimit
);
269 // block belongs to mMetadataBlockList because data has been consumed
270 // from it in "metadata mode" --- in particular blocks read during
271 // Ogg seeks go into this class. These blocks may have played data
274 // block belongs to mPlayedBlockList because its offset is
275 // less than the stream's current reader position
277 // block belongs to the stream's mReadaheadBlockList because its
278 // offset is greater than or equal to the stream's current
284 BlockOwner() : mStream(nullptr), mClass(READAHEAD_BLOCK
) {}
286 // The stream that owns this block, or null if the block is free.
287 MediaCacheStream
* mStream
;
288 // The block index in the stream. Valid only if mStream is non-null.
289 uint32_t mStreamBlock
;
290 // Time at which this block was last used. Valid only if
291 // mClass is METADATA_BLOCK or PLAYED_BLOCK.
292 TimeStamp mLastUseTime
;
297 // Free blocks have an empty mOwners array
298 nsTArray
<BlockOwner
> mOwners
;
301 // Get the BlockList that the block should belong to given its
303 BlockList
* GetListForBlock(BlockOwner
* aBlock
);
304 // Get the BlockOwner for the given block index and owning stream
305 // (returns null if the stream does not own the block)
306 BlockOwner
* GetBlockOwner(int32_t aBlockIndex
, MediaCacheStream
* aStream
);
307 // Returns true iff the block is free
308 bool IsBlockFree(int32_t aBlockIndex
)
309 { return mIndex
[aBlockIndex
].mOwners
.IsEmpty(); }
310 // Add the block to the free list and mark its streams as not having
311 // the block in cache
312 void FreeBlock(int32_t aBlock
);
313 // Mark aStream as not having the block, removing it as an owner. If
314 // the block has no more owners it's added to the free list.
315 void RemoveBlockOwner(int32_t aBlockIndex
, MediaCacheStream
* aStream
);
316 // Swap all metadata associated with the two blocks. The caller
317 // is responsible for swapping up any cache file state.
318 void SwapBlocks(int32_t aBlockIndex1
, int32_t aBlockIndex2
);
319 // Insert the block into the readahead block list for the stream
320 // at the right point in the list.
321 void InsertReadaheadBlock(BlockOwner
* aBlockOwner
, int32_t aBlockIndex
);
323 // Guess the duration until block aBlock will be next used
324 TimeDuration
PredictNextUse(TimeStamp aNow
, int32_t aBlock
);
325 // Guess the duration until the next incoming data on aStream will be used
326 TimeDuration
PredictNextUseForIncomingData(MediaCacheStream
* aStream
);
328 // Truncate the file and index array if there are free blocks at the
332 // This member is main-thread only. It's used to allocate unique
333 // resource IDs to streams.
334 int64_t mNextResourceID
;
336 // The monitor protects all the data members here. Also, off-main-thread
337 // readers that need to block will Wait() on this monitor. When new
338 // data becomes available in the cache, we NotifyAll() on this monitor.
339 ReentrantMonitor mReentrantMonitor
;
340 // This is only written while on the main thread and the monitor is held.
341 // Thus, it can be safely read from the main thread or while holding the monitor.
342 nsTArray
<MediaCacheStream
*> mStreams
;
343 // The Blocks describing the cache entries.
344 nsTArray
<Block
> mIndex
;
345 // Writer which performs IO, asynchronously writing cache blocks.
346 nsRefPtr
<FileBlockCache
> mFileCache
;
347 // The list of free blocks; they are not ordered.
348 BlockList mFreeBlocks
;
349 // True if an event to run Update() has been queued but not processed
357 MediaCacheFlusher::Observe(nsISupports
*aSubject
, char const *aTopic
, PRUnichar
const *aData
)
359 if (strcmp(aTopic
, "last-pb-context-exited") == 0) {
362 if (strcmp(aTopic
, "network-clear-cache-stored-anywhere") == 0) {
368 MediaCacheStream::MediaCacheStream(ChannelMediaResource
* aClient
)
371 mHasHadUpdate(false),
373 mDidNotifyDataEnded(false),
375 mIsTransportSeekable(false),
376 mCacheSuspended(false),
377 mChannelEnded(false),
381 mPlaybackBytesPerSecond(10000),
383 mCurrentMode(MODE_PLAYBACK
),
384 mMetadataInPartialBlockBuffer(false)
388 void MediaCacheStream::BlockList::AddFirstBlock(int32_t aBlock
)
390 NS_ASSERTION(!mEntries
.GetEntry(aBlock
), "Block already in list");
391 Entry
* entry
= mEntries
.PutEntry(aBlock
);
393 if (mFirstBlock
< 0) {
394 entry
->mNextBlock
= entry
->mPrevBlock
= aBlock
;
396 entry
->mNextBlock
= mFirstBlock
;
397 entry
->mPrevBlock
= mEntries
.GetEntry(mFirstBlock
)->mPrevBlock
;
398 mEntries
.GetEntry(entry
->mNextBlock
)->mPrevBlock
= aBlock
;
399 mEntries
.GetEntry(entry
->mPrevBlock
)->mNextBlock
= aBlock
;
401 mFirstBlock
= aBlock
;
405 void MediaCacheStream::BlockList::AddAfter(int32_t aBlock
, int32_t aBefore
)
407 NS_ASSERTION(!mEntries
.GetEntry(aBlock
), "Block already in list");
408 Entry
* entry
= mEntries
.PutEntry(aBlock
);
410 Entry
* addAfter
= mEntries
.GetEntry(aBefore
);
411 NS_ASSERTION(addAfter
, "aBefore not in list");
413 entry
->mNextBlock
= addAfter
->mNextBlock
;
414 entry
->mPrevBlock
= aBefore
;
415 mEntries
.GetEntry(entry
->mNextBlock
)->mPrevBlock
= aBlock
;
416 mEntries
.GetEntry(entry
->mPrevBlock
)->mNextBlock
= aBlock
;
420 void MediaCacheStream::BlockList::RemoveBlock(int32_t aBlock
)
422 Entry
* entry
= mEntries
.GetEntry(aBlock
);
423 NS_ASSERTION(entry
, "Block not in list");
425 if (entry
->mNextBlock
== aBlock
) {
426 NS_ASSERTION(entry
->mPrevBlock
== aBlock
, "Linked list inconsistency");
427 NS_ASSERTION(mFirstBlock
== aBlock
, "Linked list inconsistency");
430 if (mFirstBlock
== aBlock
) {
431 mFirstBlock
= entry
->mNextBlock
;
433 mEntries
.GetEntry(entry
->mNextBlock
)->mPrevBlock
= entry
->mPrevBlock
;
434 mEntries
.GetEntry(entry
->mPrevBlock
)->mNextBlock
= entry
->mNextBlock
;
436 mEntries
.RemoveEntry(aBlock
);
440 int32_t MediaCacheStream::BlockList::GetLastBlock() const
444 return mEntries
.GetEntry(mFirstBlock
)->mPrevBlock
;
447 int32_t MediaCacheStream::BlockList::GetNextBlock(int32_t aBlock
) const
449 int32_t block
= mEntries
.GetEntry(aBlock
)->mNextBlock
;
450 if (block
== mFirstBlock
)
455 int32_t MediaCacheStream::BlockList::GetPrevBlock(int32_t aBlock
) const
457 if (aBlock
== mFirstBlock
)
459 return mEntries
.GetEntry(aBlock
)->mPrevBlock
;
463 void MediaCacheStream::BlockList::Verify()
466 if (mFirstBlock
>= 0) {
467 int32_t block
= mFirstBlock
;
469 Entry
* entry
= mEntries
.GetEntry(block
);
470 NS_ASSERTION(mEntries
.GetEntry(entry
->mNextBlock
)->mPrevBlock
== block
,
472 NS_ASSERTION(mEntries
.GetEntry(entry
->mPrevBlock
)->mNextBlock
== block
,
474 block
= entry
->mNextBlock
;
476 } while (block
!= mFirstBlock
);
478 NS_ASSERTION(count
== mCount
, "Bad count");
482 static void UpdateSwappedBlockIndex(int32_t* aBlockIndex
,
483 int32_t aBlock1Index
, int32_t aBlock2Index
)
485 int32_t index
= *aBlockIndex
;
486 if (index
== aBlock1Index
) {
487 *aBlockIndex
= aBlock2Index
;
488 } else if (index
== aBlock2Index
) {
489 *aBlockIndex
= aBlock1Index
;
494 MediaCacheStream::BlockList::NotifyBlockSwapped(int32_t aBlockIndex1
,
495 int32_t aBlockIndex2
)
497 Entry
* e1
= mEntries
.GetEntry(aBlockIndex1
);
498 Entry
* e2
= mEntries
.GetEntry(aBlockIndex2
);
499 int32_t e1Prev
= -1, e1Next
= -1, e2Prev
= -1, e2Next
= -1;
502 UpdateSwappedBlockIndex(&mFirstBlock
, aBlockIndex1
, aBlockIndex2
);
504 // Fix mNextBlock/mPrevBlock links. First capture previous/next links
505 // so we don't get confused due to aliasing.
507 e1Prev
= e1
->mPrevBlock
;
508 e1Next
= e1
->mNextBlock
;
511 e2Prev
= e2
->mPrevBlock
;
512 e2Next
= e2
->mNextBlock
;
514 // Update the entries.
516 mEntries
.GetEntry(e1Prev
)->mNextBlock
= aBlockIndex2
;
517 mEntries
.GetEntry(e1Next
)->mPrevBlock
= aBlockIndex2
;
520 mEntries
.GetEntry(e2Prev
)->mNextBlock
= aBlockIndex1
;
521 mEntries
.GetEntry(e2Next
)->mPrevBlock
= aBlockIndex1
;
524 // Fix hashtable keys. First remove stale entries.
526 e1Prev
= e1
->mPrevBlock
;
527 e1Next
= e1
->mNextBlock
;
528 mEntries
.RemoveEntry(aBlockIndex1
);
529 // Refresh pointer after hashtable mutation.
530 e2
= mEntries
.GetEntry(aBlockIndex2
);
533 e2Prev
= e2
->mPrevBlock
;
534 e2Next
= e2
->mNextBlock
;
535 mEntries
.RemoveEntry(aBlockIndex2
);
537 // Put new entries back.
539 e1
= mEntries
.PutEntry(aBlockIndex2
);
540 e1
->mNextBlock
= e1Next
;
541 e1
->mPrevBlock
= e1Prev
;
544 e2
= mEntries
.PutEntry(aBlockIndex1
);
545 e2
->mNextBlock
= e2Next
;
546 e2
->mPrevBlock
= e2Prev
;
553 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
554 NS_ASSERTION(!mFileCache
, "Cache file already open?");
556 PRFileDesc
* fileDesc
= nullptr;
557 nsresult rv
= NS_OpenAnonymousTemporaryFile(&fileDesc
);
558 NS_ENSURE_SUCCESS(rv
,rv
);
560 mFileCache
= new FileBlockCache();
561 rv
= mFileCache
->Open(fileDesc
);
562 NS_ENSURE_SUCCESS(rv
,rv
);
565 if (!gMediaCacheLog
) {
566 gMediaCacheLog
= PR_NewLogModule("MediaCache");
570 MediaCacheFlusher::Init();
578 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
583 gMediaCache
->FlushInternal();
587 MediaCache::FlushInternal()
589 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
591 for (uint32_t blockIndex
= 0; blockIndex
< mIndex
.Length(); ++blockIndex
) {
592 FreeBlock(blockIndex
);
595 // Truncate file, close it, and reopen
597 NS_ASSERTION(mIndex
.Length() == 0, "Blocks leaked?");
600 mFileCache
= nullptr;
606 MediaCache::MaybeShutdown()
608 NS_ASSERTION(NS_IsMainThread(),
609 "MediaCache::MaybeShutdown called on non-main thread");
610 if (!gMediaCache
->mStreams
.IsEmpty()) {
611 // Don't shut down yet, streams are still alive
615 // Since we're on the main thread, no-one is going to add a new stream
616 // while we shut down.
617 // This function is static so we don't have to delete 'this'.
619 gMediaCache
= nullptr;
620 NS_IF_RELEASE(gMediaCacheFlusher
);
629 gMediaCache
= new MediaCache();
633 nsresult rv
= gMediaCache
->Init();
636 gMediaCache
= nullptr;
641 MediaCache::ReadCacheFile(int64_t aOffset
, void* aData
, int32_t aLength
,
644 mReentrantMonitor
.AssertCurrentThreadIn();
647 return NS_ERROR_FAILURE
;
649 return mFileCache
->Read(aOffset
, reinterpret_cast<uint8_t*>(aData
), aLength
, aBytes
);
653 MediaCache::ReadCacheFileAllBytes(int64_t aOffset
, void* aData
, int32_t aLength
)
655 mReentrantMonitor
.AssertCurrentThreadIn();
657 int64_t offset
= aOffset
;
658 int32_t count
= aLength
;
659 // Cast to char* so we can do byte-wise pointer arithmetic
660 char* data
= static_cast<char*>(aData
);
663 nsresult rv
= ReadCacheFile(offset
, data
, count
, &bytes
);
667 return NS_ERROR_FAILURE
;
675 static int32_t GetMaxBlocks()
677 // We look up the cache size every time. This means dynamic changes
678 // to the pref are applied.
679 // Cache size is in KB
680 int32_t cacheSize
= Preferences::GetInt("media.cache_size", 500*1024);
681 int64_t maxBlocks
= static_cast<int64_t>(cacheSize
)*1024/MediaCache::BLOCK_SIZE
;
682 maxBlocks
= std::max
<int64_t>(maxBlocks
, 1);
683 return int32_t(std::min
<int64_t>(maxBlocks
, INT32_MAX
));
687 MediaCache::FindBlockForIncomingData(TimeStamp aNow
,
688 MediaCacheStream
* aStream
)
690 mReentrantMonitor
.AssertCurrentThreadIn();
692 int32_t blockIndex
= FindReusableBlock(aNow
, aStream
,
693 aStream
->mChannelOffset
/BLOCK_SIZE
, INT32_MAX
);
695 if (blockIndex
< 0 || !IsBlockFree(blockIndex
)) {
696 // The block returned is already allocated.
697 // Don't reuse it if a) there's room to expand the cache or
698 // b) the data we're going to store in the free block is not higher
699 // priority than the data already stored in the free block.
700 // The latter can lead us to go over the cache limit a bit.
701 if ((mIndex
.Length() < uint32_t(GetMaxBlocks()) || blockIndex
< 0 ||
702 PredictNextUseForIncomingData(aStream
) >= PredictNextUse(aNow
, blockIndex
))) {
703 blockIndex
= mIndex
.Length();
704 if (!mIndex
.AppendElement())
706 mFreeBlocks
.AddFirstBlock(blockIndex
);
715 MediaCache::BlockIsReusable(int32_t aBlockIndex
)
717 Block
* block
= &mIndex
[aBlockIndex
];
718 for (uint32_t i
= 0; i
< block
->mOwners
.Length(); ++i
) {
719 MediaCacheStream
* stream
= block
->mOwners
[i
].mStream
;
720 if (stream
->mPinCount
> 0 ||
721 stream
->mStreamOffset
/BLOCK_SIZE
== block
->mOwners
[i
].mStreamBlock
) {
729 MediaCache::AppendMostReusableBlock(BlockList
* aBlockList
,
730 nsTArray
<uint32_t>* aResult
,
731 int32_t aBlockIndexLimit
)
733 mReentrantMonitor
.AssertCurrentThreadIn();
735 int32_t blockIndex
= aBlockList
->GetLastBlock();
739 // Don't consider blocks for pinned streams, or blocks that are
740 // beyond the specified limit, or a block that contains a stream's
741 // current read position (such a block contains both played data
742 // and readahead data)
743 if (blockIndex
< aBlockIndexLimit
&& BlockIsReusable(blockIndex
)) {
744 aResult
->AppendElement(blockIndex
);
747 blockIndex
= aBlockList
->GetPrevBlock(blockIndex
);
748 } while (blockIndex
>= 0);
752 MediaCache::FindReusableBlock(TimeStamp aNow
,
753 MediaCacheStream
* aForStream
,
754 int32_t aForStreamBlock
,
755 int32_t aMaxSearchBlockIndex
)
757 mReentrantMonitor
.AssertCurrentThreadIn();
759 uint32_t length
= std::min(uint32_t(aMaxSearchBlockIndex
), mIndex
.Length());
761 if (aForStream
&& aForStreamBlock
> 0 &&
762 uint32_t(aForStreamBlock
) <= aForStream
->mBlocks
.Length()) {
763 int32_t prevCacheBlock
= aForStream
->mBlocks
[aForStreamBlock
- 1];
764 if (prevCacheBlock
>= 0) {
765 uint32_t freeBlockScanEnd
=
766 std::min(length
, prevCacheBlock
+ FREE_BLOCK_SCAN_LIMIT
);
767 for (uint32_t i
= prevCacheBlock
; i
< freeBlockScanEnd
; ++i
) {
774 if (!mFreeBlocks
.IsEmpty()) {
775 int32_t blockIndex
= mFreeBlocks
.GetFirstBlock();
777 if (blockIndex
< aMaxSearchBlockIndex
)
779 blockIndex
= mFreeBlocks
.GetNextBlock(blockIndex
);
780 } while (blockIndex
>= 0);
783 // Build a list of the blocks we should consider for the "latest
784 // predicted time of next use". We can exploit the fact that the block
785 // linked lists are ordered by increasing time of next use. This is
786 // actually the whole point of having the linked lists.
787 nsAutoTArray
<uint32_t,8> candidates
;
788 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
789 MediaCacheStream
* stream
= mStreams
[i
];
790 if (stream
->mPinCount
> 0) {
791 // No point in even looking at this stream's blocks
795 AppendMostReusableBlock(&stream
->mMetadataBlocks
, &candidates
, length
);
796 AppendMostReusableBlock(&stream
->mPlayedBlocks
, &candidates
, length
);
798 // Don't consider readahead blocks in non-seekable streams. If we
799 // remove the block we won't be able to seek back to read it later.
800 if (stream
->mIsTransportSeekable
) {
801 AppendMostReusableBlock(&stream
->mReadaheadBlocks
, &candidates
, length
);
805 TimeDuration latestUse
;
806 int32_t latestUseBlock
= -1;
807 for (uint32_t i
= 0; i
< candidates
.Length(); ++i
) {
808 TimeDuration nextUse
= PredictNextUse(aNow
, candidates
[i
]);
809 if (nextUse
> latestUse
) {
811 latestUseBlock
= candidates
[i
];
815 return latestUseBlock
;
818 MediaCache::BlockList
*
819 MediaCache::GetListForBlock(BlockOwner
* aBlock
)
821 switch (aBlock
->mClass
) {
823 NS_ASSERTION(aBlock
->mStream
, "Metadata block has no stream?");
824 return &aBlock
->mStream
->mMetadataBlocks
;
826 NS_ASSERTION(aBlock
->mStream
, "Metadata block has no stream?");
827 return &aBlock
->mStream
->mPlayedBlocks
;
828 case READAHEAD_BLOCK
:
829 NS_ASSERTION(aBlock
->mStream
, "Readahead block has no stream?");
830 return &aBlock
->mStream
->mReadaheadBlocks
;
832 NS_ERROR("Invalid block class");
837 MediaCache::BlockOwner
*
838 MediaCache::GetBlockOwner(int32_t aBlockIndex
, MediaCacheStream
* aStream
)
840 Block
* block
= &mIndex
[aBlockIndex
];
841 for (uint32_t i
= 0; i
< block
->mOwners
.Length(); ++i
) {
842 if (block
->mOwners
[i
].mStream
== aStream
)
843 return &block
->mOwners
[i
];
849 MediaCache::SwapBlocks(int32_t aBlockIndex1
, int32_t aBlockIndex2
)
851 mReentrantMonitor
.AssertCurrentThreadIn();
853 Block
* block1
= &mIndex
[aBlockIndex1
];
854 Block
* block2
= &mIndex
[aBlockIndex2
];
856 block1
->mOwners
.SwapElements(block2
->mOwners
);
858 // Now all references to block1 have to be replaced with block2 and
860 // First update stream references to blocks via mBlocks.
861 const Block
* blocks
[] = { block1
, block2
};
862 int32_t blockIndices
[] = { aBlockIndex1
, aBlockIndex2
};
863 for (int32_t i
= 0; i
< 2; ++i
) {
864 for (uint32_t j
= 0; j
< blocks
[i
]->mOwners
.Length(); ++j
) {
865 const BlockOwner
* b
= &blocks
[i
]->mOwners
[j
];
866 b
->mStream
->mBlocks
[b
->mStreamBlock
] = blockIndices
[i
];
870 // Now update references to blocks in block lists.
871 mFreeBlocks
.NotifyBlockSwapped(aBlockIndex1
, aBlockIndex2
);
873 nsTHashtable
<nsPtrHashKey
<MediaCacheStream
> > visitedStreams
;
875 for (int32_t i
= 0; i
< 2; ++i
) {
876 for (uint32_t j
= 0; j
< blocks
[i
]->mOwners
.Length(); ++j
) {
877 MediaCacheStream
* stream
= blocks
[i
]->mOwners
[j
].mStream
;
878 // Make sure that we don't update the same stream twice --- that
879 // would result in swapping the block references back again!
880 if (visitedStreams
.GetEntry(stream
))
882 visitedStreams
.PutEntry(stream
);
883 stream
->mReadaheadBlocks
.NotifyBlockSwapped(aBlockIndex1
, aBlockIndex2
);
884 stream
->mPlayedBlocks
.NotifyBlockSwapped(aBlockIndex1
, aBlockIndex2
);
885 stream
->mMetadataBlocks
.NotifyBlockSwapped(aBlockIndex1
, aBlockIndex2
);
893 MediaCache::RemoveBlockOwner(int32_t aBlockIndex
, MediaCacheStream
* aStream
)
895 Block
* block
= &mIndex
[aBlockIndex
];
896 for (uint32_t i
= 0; i
< block
->mOwners
.Length(); ++i
) {
897 BlockOwner
* bo
= &block
->mOwners
[i
];
898 if (bo
->mStream
== aStream
) {
899 GetListForBlock(bo
)->RemoveBlock(aBlockIndex
);
900 bo
->mStream
->mBlocks
[bo
->mStreamBlock
] = -1;
901 block
->mOwners
.RemoveElementAt(i
);
902 if (block
->mOwners
.IsEmpty()) {
903 mFreeBlocks
.AddFirstBlock(aBlockIndex
);
911 MediaCache::AddBlockOwnerAsReadahead(int32_t aBlockIndex
,
912 MediaCacheStream
* aStream
,
913 int32_t aStreamBlockIndex
)
915 Block
* block
= &mIndex
[aBlockIndex
];
916 if (block
->mOwners
.IsEmpty()) {
917 mFreeBlocks
.RemoveBlock(aBlockIndex
);
919 BlockOwner
* bo
= block
->mOwners
.AppendElement();
920 bo
->mStream
= aStream
;
921 bo
->mStreamBlock
= aStreamBlockIndex
;
922 aStream
->mBlocks
[aStreamBlockIndex
] = aBlockIndex
;
923 bo
->mClass
= READAHEAD_BLOCK
;
924 InsertReadaheadBlock(bo
, aBlockIndex
);
928 MediaCache::FreeBlock(int32_t aBlock
)
930 mReentrantMonitor
.AssertCurrentThreadIn();
932 Block
* block
= &mIndex
[aBlock
];
933 if (block
->mOwners
.IsEmpty()) {
938 CACHE_LOG(PR_LOG_DEBUG
, ("Released block %d", aBlock
));
940 for (uint32_t i
= 0; i
< block
->mOwners
.Length(); ++i
) {
941 BlockOwner
* bo
= &block
->mOwners
[i
];
942 GetListForBlock(bo
)->RemoveBlock(aBlock
);
943 bo
->mStream
->mBlocks
[bo
->mStreamBlock
] = -1;
945 block
->mOwners
.Clear();
946 mFreeBlocks
.AddFirstBlock(aBlock
);
951 MediaCache::PredictNextUse(TimeStamp aNow
, int32_t aBlock
)
953 mReentrantMonitor
.AssertCurrentThreadIn();
954 NS_ASSERTION(!IsBlockFree(aBlock
), "aBlock is free");
956 Block
* block
= &mIndex
[aBlock
];
957 // Blocks can be belong to multiple streams. The predicted next use
958 // time is the earliest time predicted by any of the streams.
960 for (uint32_t i
= 0; i
< block
->mOwners
.Length(); ++i
) {
961 BlockOwner
* bo
= &block
->mOwners
[i
];
962 TimeDuration prediction
;
963 switch (bo
->mClass
) {
965 // This block should be managed in LRU mode. For metadata we predict
966 // that the time until the next use is the time since the last use.
967 prediction
= aNow
- bo
->mLastUseTime
;
970 // This block should be managed in LRU mode, and we should impose
971 // a "replay delay" to reflect the likelihood of replay happening
972 NS_ASSERTION(static_cast<int64_t>(bo
->mStreamBlock
)*BLOCK_SIZE
<
973 bo
->mStream
->mStreamOffset
,
974 "Played block after the current stream position?");
975 int64_t bytesBehind
=
976 bo
->mStream
->mStreamOffset
- static_cast<int64_t>(bo
->mStreamBlock
)*BLOCK_SIZE
;
977 int64_t millisecondsBehind
=
978 bytesBehind
*1000/bo
->mStream
->mPlaybackBytesPerSecond
;
979 prediction
= TimeDuration::FromMilliseconds(
980 std::min
<int64_t>(millisecondsBehind
*REPLAY_PENALTY_FACTOR
, INT32_MAX
));
983 case READAHEAD_BLOCK
: {
985 static_cast<int64_t>(bo
->mStreamBlock
)*BLOCK_SIZE
- bo
->mStream
->mStreamOffset
;
986 NS_ASSERTION(bytesAhead
>= 0,
987 "Readahead block before the current stream position?");
988 int64_t millisecondsAhead
=
989 bytesAhead
*1000/bo
->mStream
->mPlaybackBytesPerSecond
;
990 prediction
= TimeDuration::FromMilliseconds(
991 std::min
<int64_t>(millisecondsAhead
, INT32_MAX
));
995 NS_ERROR("Invalid class for predicting next use");
996 return TimeDuration(0);
998 if (i
== 0 || prediction
< result
) {
1006 MediaCache::PredictNextUseForIncomingData(MediaCacheStream
* aStream
)
1008 mReentrantMonitor
.AssertCurrentThreadIn();
1010 int64_t bytesAhead
= aStream
->mChannelOffset
- aStream
->mStreamOffset
;
1011 if (bytesAhead
<= -BLOCK_SIZE
) {
1012 // Hmm, no idea when data behind us will be used. Guess 24 hours.
1013 return TimeDuration::FromSeconds(24*60*60);
1015 if (bytesAhead
<= 0)
1016 return TimeDuration(0);
1017 int64_t millisecondsAhead
= bytesAhead
*1000/aStream
->mPlaybackBytesPerSecond
;
1018 return TimeDuration::FromMilliseconds(
1019 std::min
<int64_t>(millisecondsAhead
, INT32_MAX
));
1022 enum StreamAction
{ NONE
, SEEK
, SEEK_AND_RESUME
, RESUME
, SUSPEND
};
1025 MediaCache::Update()
1027 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1029 // The action to use for each stream. We store these so we can make
1030 // decisions while holding the cache lock but implement those decisions
1031 // without holding the cache lock, since we need to call out to
1032 // stream, decoder and element code.
1033 nsAutoTArray
<StreamAction
,10> actions
;
1036 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
1037 mUpdateQueued
= false;
1042 int32_t maxBlocks
= GetMaxBlocks();
1043 TimeStamp now
= TimeStamp::Now();
1045 int32_t freeBlockCount
= mFreeBlocks
.GetCount();
1046 TimeDuration latestPredictedUseForOverflow
= 0;
1047 if (mIndex
.Length() > uint32_t(maxBlocks
)) {
1048 // Try to trim back the cache to its desired maximum size. The cache may
1049 // have overflowed simply due to data being received when we have
1050 // no blocks in the main part of the cache that are free or lower
1051 // priority than the new data. The cache can also be overflowing because
1052 // the media.cache_size preference was reduced.
1053 // First, figure out what the least valuable block in the cache overflow
1054 // is. We don't want to replace any blocks in the main part of the
1055 // cache whose expected time of next use is earlier or equal to that.
1056 // If we allow that, we can effectively end up discarding overflowing
1057 // blocks (by moving an overflowing block to the main part of the cache,
1058 // and then overwriting it with another overflowing block), and we try
1059 // to avoid that since it requires HTTP seeks.
1060 // We also use this loop to eliminate overflowing blocks from
1062 for (int32_t blockIndex
= mIndex
.Length() - 1; blockIndex
>= maxBlocks
;
1064 if (IsBlockFree(blockIndex
)) {
1065 // Don't count overflowing free blocks in our free block count
1069 TimeDuration predictedUse
= PredictNextUse(now
, blockIndex
);
1070 latestPredictedUseForOverflow
= std::max(latestPredictedUseForOverflow
, predictedUse
);
1073 freeBlockCount
+= maxBlocks
- mIndex
.Length();
1076 // Now try to move overflowing blocks to the main part of the cache.
1077 for (int32_t blockIndex
= mIndex
.Length() - 1; blockIndex
>= maxBlocks
;
1079 if (IsBlockFree(blockIndex
))
1082 Block
* block
= &mIndex
[blockIndex
];
1083 // Try to relocate the block close to other blocks for the first stream.
1084 // There is no point in trying to make it close to other blocks in
1085 // *all* the streams it might belong to.
1086 int32_t destinationBlockIndex
=
1087 FindReusableBlock(now
, block
->mOwners
[0].mStream
,
1088 block
->mOwners
[0].mStreamBlock
, maxBlocks
);
1089 if (destinationBlockIndex
< 0) {
1090 // Nowhere to place this overflow block. We won't be able to
1091 // place any more overflow blocks.
1095 if (IsBlockFree(destinationBlockIndex
) ||
1096 PredictNextUse(now
, destinationBlockIndex
) > latestPredictedUseForOverflow
) {
1097 // Reuse blocks in the main part of the cache that are less useful than
1098 // the least useful overflow blocks
1100 nsresult rv
= mFileCache
->MoveBlock(blockIndex
, destinationBlockIndex
);
1102 if (NS_SUCCEEDED(rv
)) {
1103 // We successfully copied the file data.
1104 CACHE_LOG(PR_LOG_DEBUG
, ("Swapping blocks %d and %d (trimming cache)",
1105 blockIndex
, destinationBlockIndex
));
1106 // Swapping the block metadata here lets us maintain the
1107 // correct positions in the linked lists
1108 SwapBlocks(blockIndex
, destinationBlockIndex
);
1109 //Free the overflowing block even if the copy failed.
1110 CACHE_LOG(PR_LOG_DEBUG
, ("Released block %d (trimming cache)", blockIndex
));
1111 FreeBlock(blockIndex
);
1114 CACHE_LOG(PR_LOG_DEBUG
, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f",
1115 blockIndex
, destinationBlockIndex
,
1116 PredictNextUse(now
, destinationBlockIndex
).ToSeconds(),
1117 latestPredictedUseForOverflow
.ToSeconds()));
1120 // Try chopping back the array of cache entries and the cache file.
1123 // Count the blocks allocated for readahead of non-seekable streams
1124 // (these blocks can't be freed but we don't want them to monopolize the
1126 int32_t nonSeekableReadaheadBlockCount
= 0;
1127 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
1128 MediaCacheStream
* stream
= mStreams
[i
];
1129 if (!stream
->mIsTransportSeekable
) {
1130 nonSeekableReadaheadBlockCount
+= stream
->mReadaheadBlocks
.GetCount();
1134 // If freeBlockCount is zero, then compute the latest of
1135 // the predicted next-uses for all blocks
1136 TimeDuration latestNextUse
;
1137 if (freeBlockCount
== 0) {
1138 int32_t reusableBlock
= FindReusableBlock(now
, nullptr, 0, maxBlocks
);
1139 if (reusableBlock
>= 0) {
1140 latestNextUse
= PredictNextUse(now
, reusableBlock
);
1144 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
1145 actions
.AppendElement(NONE
);
1147 MediaCacheStream
* stream
= mStreams
[i
];
1148 if (stream
->mClosed
)
1151 // Figure out where we should be reading from. It's the first
1152 // uncached byte after the current mStreamOffset.
1153 int64_t dataOffset
= stream
->GetCachedDataEndInternal(stream
->mStreamOffset
);
1155 // Compute where we'd actually seek to to read at readOffset
1156 int64_t desiredOffset
= dataOffset
;
1157 if (stream
->mIsTransportSeekable
) {
1158 if (desiredOffset
> stream
->mChannelOffset
&&
1159 desiredOffset
<= stream
->mChannelOffset
+ SEEK_VS_READ_THRESHOLD
) {
1160 // Assume it's more efficient to just keep reading up to the
1161 // desired position instead of trying to seek
1162 desiredOffset
= stream
->mChannelOffset
;
1165 // We can't seek directly to the desired offset...
1166 if (stream
->mChannelOffset
> desiredOffset
) {
1167 // Reading forward won't get us anywhere, we need to go backwards.
1168 // Seek back to 0 (the client will reopen the stream) and then
1170 NS_WARNING("Can't seek backwards, so seeking to 0");
1172 // Flush cached blocks out, since if this is a live stream
1173 // the cached data may be completely different next time we
1174 // read it. We have to assume that live streams don't
1175 // advertise themselves as being seekable...
1176 ReleaseStreamBlocks(stream
);
1178 // otherwise reading forward is looking good, so just stay where we
1179 // are and don't trigger a channel seek!
1180 desiredOffset
= stream
->mChannelOffset
;
1184 // Figure out if we should be reading data now or not. It's amazing
1185 // how complex this is, but each decision is simple enough.
1187 if (stream
->mStreamLength
>= 0 && dataOffset
>= stream
->mStreamLength
) {
1188 // We want data at the end of the stream, where there's nothing to
1189 // read. We don't want to try to read if we're suspended, because that
1190 // might create a new channel and seek unnecessarily (and incorrectly,
1191 // since HTTP doesn't allow seeking to the actual EOF), and we don't want
1192 // to suspend if we're not suspended and already reading at the end of
1193 // the stream, since there just might be more data than the server
1194 // advertised with Content-Length, and we may as well keep reading.
1195 // But we don't want to seek to the end of the stream if we're not
1197 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p at end of stream", stream
));
1198 enableReading
= !stream
->mCacheSuspended
&&
1199 stream
->mStreamLength
== stream
->mChannelOffset
;
1200 } else if (desiredOffset
< stream
->mStreamOffset
) {
1201 // We're reading to try to catch up to where the current stream
1202 // reader wants to be. Better not stop.
1203 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p catching up", stream
));
1204 enableReading
= true;
1205 } else if (desiredOffset
< stream
->mStreamOffset
+ BLOCK_SIZE
) {
1206 // The stream reader is waiting for us, or nearly so. Better feed it.
1207 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p feeding reader", stream
));
1208 enableReading
= true;
1209 } else if (!stream
->mIsTransportSeekable
&&
1210 nonSeekableReadaheadBlockCount
>= maxBlocks
*NONSEEKABLE_READAHEAD_MAX
) {
1211 // This stream is not seekable and there are already too many blocks
1212 // being cached for readahead for nonseekable streams (which we can't
1213 // free). So stop reading ahead now.
1214 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p throttling non-seekable readahead", stream
));
1215 enableReading
= false;
1216 } else if (mIndex
.Length() > uint32_t(maxBlocks
)) {
1217 // We're in the process of bringing the cache size back to the
1218 // desired limit, so don't bring in more data yet
1219 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p throttling to reduce cache size", stream
));
1220 enableReading
= false;
1222 TimeDuration predictedNewDataUse
= PredictNextUseForIncomingData(stream
);
1224 if (stream
->mCacheSuspended
&&
1225 predictedNewDataUse
.ToMilliseconds() > CACHE_POWERSAVE_WAKEUP_LOW_THRESHOLD_MS
) {
1226 // Don't need data for a while, so don't bother waking up the stream
1227 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p avoiding wakeup since more data is not needed", stream
));
1228 enableReading
= false;
1229 } else if (freeBlockCount
> 0) {
1230 // Free blocks in the cache, so keep reading
1231 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p reading since there are free blocks", stream
));
1232 enableReading
= true;
1233 } else if (latestNextUse
<= TimeDuration(0)) {
1234 // No reusable blocks, so can't read anything
1235 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p throttling due to no reusable blocks", stream
));
1236 enableReading
= false;
1238 // Read ahead if the data we expect to read is more valuable than
1239 // the least valuable block in the main part of the cache
1240 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p predict next data in %f, current worst block is %f",
1241 stream
, predictedNewDataUse
.ToSeconds(), latestNextUse
.ToSeconds()));
1242 enableReading
= predictedNewDataUse
< latestNextUse
;
1246 if (enableReading
) {
1247 for (uint32_t j
= 0; j
< i
; ++j
) {
1248 MediaCacheStream
* other
= mStreams
[j
];
1249 if (other
->mResourceID
== stream
->mResourceID
&&
1250 !other
->mClient
->IsSuspended() &&
1251 other
->mChannelOffset
/BLOCK_SIZE
== desiredOffset
/BLOCK_SIZE
) {
1252 // This block is already going to be read by the other stream.
1253 // So don't try to read it from this stream as well.
1254 enableReading
= false;
1255 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p waiting on same block (%lld) from stream %p",
1256 stream
, desiredOffset
/BLOCK_SIZE
, other
));
1262 if (stream
->mChannelOffset
!= desiredOffset
&& enableReading
) {
1263 // We need to seek now.
1264 NS_ASSERTION(stream
->mIsTransportSeekable
|| desiredOffset
== 0,
1265 "Trying to seek in a non-seekable stream!");
1266 // Round seek offset down to the start of the block. This is essential
1267 // because we don't want to think we have part of a block already
1268 // in mPartialBlockBuffer.
1269 stream
->mChannelOffset
= (desiredOffset
/BLOCK_SIZE
)*BLOCK_SIZE
;
1270 actions
[i
] = stream
->mCacheSuspended
? SEEK_AND_RESUME
: SEEK
;
1271 } else if (enableReading
&& stream
->mCacheSuspended
) {
1272 actions
[i
] = RESUME
;
1273 } else if (!enableReading
&& !stream
->mCacheSuspended
) {
1274 actions
[i
] = SUSPEND
;
1282 // Update the channel state without holding our cache lock. While we're
1283 // doing this, decoder threads may be running and seeking, reading or changing
1284 // other cache state. That's OK, they'll trigger new Update events and we'll
1285 // get back here and revise our decisions. The important thing here is that
1286 // performing these actions only depends on mChannelOffset and
1287 // the action, which can only be written by the main thread (i.e., this
1288 // thread), so we don't have races here.
1290 // First, update the mCacheSuspended/mCacheEnded flags so that they're all correct
1291 // when we fire our CacheClient commands below. Those commands can rely on these flags
1292 // being set correctly for all streams.
1293 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
1294 MediaCacheStream
* stream
= mStreams
[i
];
1295 switch (actions
[i
]) {
1297 case SEEK_AND_RESUME
:
1298 stream
->mCacheSuspended
= false;
1299 stream
->mChannelEnded
= false;
1302 stream
->mCacheSuspended
= false;
1305 stream
->mCacheSuspended
= true;
1310 stream
->mHasHadUpdate
= true;
1313 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
1314 MediaCacheStream
* stream
= mStreams
[i
];
1316 switch (actions
[i
]) {
1318 case SEEK_AND_RESUME
:
1319 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p CacheSeek to %lld (resume=%d)", stream
,
1320 (long long)stream
->mChannelOffset
, actions
[i
] == SEEK_AND_RESUME
));
1321 rv
= stream
->mClient
->CacheClientSeek(stream
->mChannelOffset
,
1322 actions
[i
] == SEEK_AND_RESUME
);
1325 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p Resumed", stream
));
1326 rv
= stream
->mClient
->CacheClientResume();
1329 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p Suspended", stream
));
1330 rv
= stream
->mClient
->CacheClientSuspend();
1337 if (NS_FAILED(rv
)) {
1338 // Close the streams that failed due to error. This will cause all
1339 // client Read and Seek operations on those streams to fail. Blocked
1340 // Reads will also be woken up.
1341 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
1342 stream
->CloseInternal(mon
);
1347 class UpdateEvent
: public nsRunnable
1353 gMediaCache
->Update();
1360 MediaCache::QueueUpdate()
1362 mReentrantMonitor
.AssertCurrentThreadIn();
1364 // Queuing an update while we're in an update raises a high risk of
1365 // triggering endless events
1366 NS_ASSERTION(!mInUpdate
,
1367 "Queuing an update while we're in an update");
1370 mUpdateQueued
= true;
1371 nsCOMPtr
<nsIRunnable
> event
= new UpdateEvent();
1372 NS_DispatchToMainThread(event
);
1375 #ifdef DEBUG_VERIFY_CACHE
1377 MediaCache::Verify()
1379 mReentrantMonitor
.AssertCurrentThreadIn();
1381 mFreeBlocks
.Verify();
1382 for (uint32_t i
= 0; i
< mStreams
.Length(); ++i
) {
1383 MediaCacheStream
* stream
= mStreams
[i
];
1384 stream
->mReadaheadBlocks
.Verify();
1385 stream
->mPlayedBlocks
.Verify();
1386 stream
->mMetadataBlocks
.Verify();
1388 // Verify that the readahead blocks are listed in stream block order
1389 int32_t block
= stream
->mReadaheadBlocks
.GetFirstBlock();
1390 int32_t lastStreamBlock
= -1;
1391 while (block
>= 0) {
1393 while (mIndex
[block
].mOwners
[j
].mStream
!= stream
) {
1396 int32_t nextStreamBlock
=
1397 int32_t(mIndex
[block
].mOwners
[j
].mStreamBlock
);
1398 NS_ASSERTION(lastStreamBlock
< nextStreamBlock
,
1399 "Blocks not increasing in readahead stream");
1400 lastStreamBlock
= nextStreamBlock
;
1401 block
= stream
->mReadaheadBlocks
.GetNextBlock(block
);
1408 MediaCache::InsertReadaheadBlock(BlockOwner
* aBlockOwner
,
1409 int32_t aBlockIndex
)
1411 mReentrantMonitor
.AssertCurrentThreadIn();
1413 // Find the last block whose stream block is before aBlockIndex's
1414 // stream block, and insert after it
1415 MediaCacheStream
* stream
= aBlockOwner
->mStream
;
1416 int32_t readaheadIndex
= stream
->mReadaheadBlocks
.GetLastBlock();
1417 while (readaheadIndex
>= 0) {
1418 BlockOwner
* bo
= GetBlockOwner(readaheadIndex
, stream
);
1419 NS_ASSERTION(bo
, "stream must own its blocks");
1420 if (bo
->mStreamBlock
< aBlockOwner
->mStreamBlock
) {
1421 stream
->mReadaheadBlocks
.AddAfter(aBlockIndex
, readaheadIndex
);
1424 NS_ASSERTION(bo
->mStreamBlock
> aBlockOwner
->mStreamBlock
,
1425 "Duplicated blocks??");
1426 readaheadIndex
= stream
->mReadaheadBlocks
.GetPrevBlock(readaheadIndex
);
1429 stream
->mReadaheadBlocks
.AddFirstBlock(aBlockIndex
);
1434 MediaCache::AllocateAndWriteBlock(MediaCacheStream
* aStream
, const void* aData
,
1435 MediaCacheStream::ReadMode aMode
)
1437 mReentrantMonitor
.AssertCurrentThreadIn();
1439 int32_t streamBlockIndex
= aStream
->mChannelOffset
/BLOCK_SIZE
;
1441 // Remove all cached copies of this block
1442 ResourceStreamIterator
iter(aStream
->mResourceID
);
1443 while (MediaCacheStream
* stream
= iter
.Next()) {
1444 while (streamBlockIndex
>= int32_t(stream
->mBlocks
.Length())) {
1445 stream
->mBlocks
.AppendElement(-1);
1447 if (stream
->mBlocks
[streamBlockIndex
] >= 0) {
1448 // We no longer want to own this block
1449 int32_t globalBlockIndex
= stream
->mBlocks
[streamBlockIndex
];
1450 CACHE_LOG(PR_LOG_DEBUG
, ("Released block %d from stream %p block %d(%lld)",
1451 globalBlockIndex
, stream
, streamBlockIndex
, (long long)streamBlockIndex
*BLOCK_SIZE
));
1452 RemoveBlockOwner(globalBlockIndex
, stream
);
1456 // Extend the mBlocks array as necessary
1458 TimeStamp now
= TimeStamp::Now();
1459 int32_t blockIndex
= FindBlockForIncomingData(now
, aStream
);
1460 if (blockIndex
>= 0) {
1461 FreeBlock(blockIndex
);
1463 Block
* block
= &mIndex
[blockIndex
];
1464 CACHE_LOG(PR_LOG_DEBUG
, ("Allocated block %d to stream %p block %d(%lld)",
1465 blockIndex
, aStream
, streamBlockIndex
, (long long)streamBlockIndex
*BLOCK_SIZE
));
1467 mFreeBlocks
.RemoveBlock(blockIndex
);
1469 // Tell each stream using this resource about the new block.
1470 ResourceStreamIterator
iter(aStream
->mResourceID
);
1471 while (MediaCacheStream
* stream
= iter
.Next()) {
1472 BlockOwner
* bo
= block
->mOwners
.AppendElement();
1476 bo
->mStream
= stream
;
1477 bo
->mStreamBlock
= streamBlockIndex
;
1478 bo
->mLastUseTime
= now
;
1479 stream
->mBlocks
[streamBlockIndex
] = blockIndex
;
1480 if (streamBlockIndex
*BLOCK_SIZE
< stream
->mStreamOffset
) {
1481 bo
->mClass
= aMode
== MediaCacheStream::MODE_PLAYBACK
1482 ? PLAYED_BLOCK
: METADATA_BLOCK
;
1483 // This must be the most-recently-used block, since we
1484 // marked it as used now (which may be slightly bogus, but we'll
1485 // treat it as used for simplicity).
1486 GetListForBlock(bo
)->AddFirstBlock(blockIndex
);
1489 // This may not be the latest readahead block, although it usually
1490 // will be. We may have to scan for the right place to insert
1491 // the block in the list.
1492 bo
->mClass
= READAHEAD_BLOCK
;
1493 InsertReadaheadBlock(bo
, blockIndex
);
1497 nsresult rv
= mFileCache
->WriteBlock(blockIndex
, reinterpret_cast<const uint8_t*>(aData
));
1498 if (NS_FAILED(rv
)) {
1499 CACHE_LOG(PR_LOG_DEBUG
, ("Released block %d from stream %p block %d(%lld)",
1500 blockIndex
, aStream
, streamBlockIndex
, (long long)streamBlockIndex
*BLOCK_SIZE
));
1501 FreeBlock(blockIndex
);
1505 // Queue an Update since the cache state has changed (for example
1506 // we might want to stop loading because the cache is full)
1511 MediaCache::OpenStream(MediaCacheStream
* aStream
)
1513 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1515 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
1516 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p opened", aStream
));
1517 mStreams
.AppendElement(aStream
);
1518 aStream
->mResourceID
= AllocateResourceID();
1520 // Queue an update since a new stream has been opened.
1521 gMediaCache
->QueueUpdate();
1525 MediaCache::ReleaseStream(MediaCacheStream
* aStream
)
1527 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1529 ReentrantMonitorAutoEnter
mon(mReentrantMonitor
);
1530 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p closed", aStream
));
1531 mStreams
.RemoveElement(aStream
);
1535 MediaCache::ReleaseStreamBlocks(MediaCacheStream
* aStream
)
1537 mReentrantMonitor
.AssertCurrentThreadIn();
1539 // XXX scanning the entire stream doesn't seem great, if not much of it
1540 // is cached, but the only easy alternative is to scan the entire cache
1541 // which isn't better
1542 uint32_t length
= aStream
->mBlocks
.Length();
1543 for (uint32_t i
= 0; i
< length
; ++i
) {
1544 int32_t blockIndex
= aStream
->mBlocks
[i
];
1545 if (blockIndex
>= 0) {
1546 CACHE_LOG(PR_LOG_DEBUG
, ("Released block %d from stream %p block %d(%lld)",
1547 blockIndex
, aStream
, i
, (long long)i
*BLOCK_SIZE
));
1548 RemoveBlockOwner(blockIndex
, aStream
);
1554 MediaCache::Truncate()
1557 for (end
= mIndex
.Length(); end
> 0; --end
) {
1558 if (!IsBlockFree(end
- 1))
1560 mFreeBlocks
.RemoveBlock(end
- 1);
1563 if (end
< mIndex
.Length()) {
1564 mIndex
.TruncateLength(end
);
1565 // XXX We could truncate the cache file here, but we don't seem
1566 // to have a cross-platform API for doing that. At least when all
1567 // streams are closed we shut down the cache, which erases the
1568 // file at that point.
1573 MediaCache::NoteBlockUsage(MediaCacheStream
* aStream
, int32_t aBlockIndex
,
1574 MediaCacheStream::ReadMode aMode
,
1577 mReentrantMonitor
.AssertCurrentThreadIn();
1579 if (aBlockIndex
< 0) {
1580 // this block is not in the cache yet
1584 BlockOwner
* bo
= GetBlockOwner(aBlockIndex
, aStream
);
1586 // this block is not in the cache yet
1590 // The following check has to be <= because the stream offset has
1591 // not yet been updated for the data read from this block
1592 NS_ASSERTION(bo
->mStreamBlock
*BLOCK_SIZE
<= bo
->mStream
->mStreamOffset
,
1593 "Using a block that's behind the read position?");
1595 GetListForBlock(bo
)->RemoveBlock(aBlockIndex
);
1597 (aMode
== MediaCacheStream::MODE_METADATA
|| bo
->mClass
== METADATA_BLOCK
)
1598 ? METADATA_BLOCK
: PLAYED_BLOCK
;
1599 // Since this is just being used now, it can definitely be at the front
1600 // of mMetadataBlocks or mPlayedBlocks
1601 GetListForBlock(bo
)->AddFirstBlock(aBlockIndex
);
1602 bo
->mLastUseTime
= aNow
;
1607 MediaCache::NoteSeek(MediaCacheStream
* aStream
, int64_t aOldOffset
)
1609 mReentrantMonitor
.AssertCurrentThreadIn();
1611 if (aOldOffset
< aStream
->mStreamOffset
) {
1612 // We seeked forward. Convert blocks from readahead to played.
1613 // Any readahead block that intersects the seeked-over range must
1615 int32_t blockIndex
= aOldOffset
/BLOCK_SIZE
;
1617 std::min
<int64_t>((aStream
->mStreamOffset
+ BLOCK_SIZE
- 1)/BLOCK_SIZE
,
1618 aStream
->mBlocks
.Length());
1619 TimeStamp now
= TimeStamp::Now();
1620 while (blockIndex
< endIndex
) {
1621 int32_t cacheBlockIndex
= aStream
->mBlocks
[blockIndex
];
1622 if (cacheBlockIndex
>= 0) {
1623 // Marking the block used may not be exactly what we want but
1625 NoteBlockUsage(aStream
, cacheBlockIndex
, MediaCacheStream::MODE_PLAYBACK
,
1631 // We seeked backward. Convert from played to readahead.
1632 // Any played block that is entirely after the start of the seeked-over
1633 // range must be converted.
1634 int32_t blockIndex
=
1635 (aStream
->mStreamOffset
+ BLOCK_SIZE
- 1)/BLOCK_SIZE
;
1637 std::min
<int64_t>((aOldOffset
+ BLOCK_SIZE
- 1)/BLOCK_SIZE
,
1638 aStream
->mBlocks
.Length());
1639 while (blockIndex
< endIndex
) {
1640 int32_t cacheBlockIndex
= aStream
->mBlocks
[endIndex
- 1];
1641 if (cacheBlockIndex
>= 0) {
1642 BlockOwner
* bo
= GetBlockOwner(cacheBlockIndex
, aStream
);
1643 NS_ASSERTION(bo
, "Stream doesn't own its blocks?");
1644 if (bo
->mClass
== PLAYED_BLOCK
) {
1645 aStream
->mPlayedBlocks
.RemoveBlock(cacheBlockIndex
);
1646 bo
->mClass
= READAHEAD_BLOCK
;
1647 // Adding this as the first block is sure to be OK since
1648 // this must currently be the earliest readahead block
1649 // (that's why we're proceeding backwards from the end of
1650 // the seeked range to the start)
1651 aStream
->mReadaheadBlocks
.AddFirstBlock(cacheBlockIndex
);
1661 MediaCacheStream::NotifyDataLength(int64_t aLength
)
1663 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1665 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1666 mStreamLength
= aLength
;
1670 MediaCacheStream::NotifyDataStarted(int64_t aOffset
)
1672 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1674 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1675 NS_WARN_IF_FALSE(aOffset
== mChannelOffset
,
1676 "Server is giving us unexpected offset");
1677 mChannelOffset
= aOffset
;
1678 if (mStreamLength
>= 0) {
1679 // If we started reading at a certain offset, then for sure
1680 // the stream is at least that long.
1681 mStreamLength
= std::max(mStreamLength
, mChannelOffset
);
1686 MediaCacheStream::UpdatePrincipal(nsIPrincipal
* aPrincipal
)
1688 return nsContentUtils::CombineResourcePrincipals(&mPrincipal
, aPrincipal
);
1692 MediaCacheStream::NotifyDataReceived(int64_t aSize
, const char* aData
,
1693 nsIPrincipal
* aPrincipal
)
1695 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1697 // Update principals before putting the data in the cache. This is important,
1698 // we want to make sure all principals are updated before any consumer
1699 // can see the new data.
1700 // We do this without holding the cache monitor, in case the client wants
1701 // to do something that takes a lock.
1703 MediaCache::ResourceStreamIterator
iter(mResourceID
);
1704 while (MediaCacheStream
* stream
= iter
.Next()) {
1705 if (stream
->UpdatePrincipal(aPrincipal
)) {
1706 stream
->mClient
->CacheClientNotifyPrincipalChanged();
1711 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1712 int64_t size
= aSize
;
1713 const char* data
= aData
;
1715 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p DataReceived at %lld count=%lld",
1716 this, (long long)mChannelOffset
, (long long)aSize
));
1718 // We process the data one block (or part of a block) at a time
1720 uint32_t blockIndex
= mChannelOffset
/BLOCK_SIZE
;
1721 int32_t blockOffset
= int32_t(mChannelOffset
- blockIndex
*BLOCK_SIZE
);
1722 int32_t chunkSize
= std::min
<int64_t>(BLOCK_SIZE
- blockOffset
, size
);
1724 // This gets set to something non-null if we have a whole block
1725 // of data to write to the cache
1726 const char* blockDataToStore
= nullptr;
1727 ReadMode mode
= MODE_PLAYBACK
;
1728 if (blockOffset
== 0 && chunkSize
== BLOCK_SIZE
) {
1729 // We received a whole block, so avoid a useless copy through
1730 // mPartialBlockBuffer
1731 blockDataToStore
= data
;
1733 if (blockOffset
== 0) {
1734 // We've just started filling this buffer so now is a good time
1735 // to clear this flag.
1736 mMetadataInPartialBlockBuffer
= false;
1738 memcpy(reinterpret_cast<char*>(mPartialBlockBuffer
) + blockOffset
,
1741 if (blockOffset
+ chunkSize
== BLOCK_SIZE
) {
1742 // We completed a block, so lets write it out.
1743 blockDataToStore
= reinterpret_cast<char*>(mPartialBlockBuffer
);
1744 if (mMetadataInPartialBlockBuffer
) {
1745 mode
= MODE_METADATA
;
1750 if (blockDataToStore
) {
1751 gMediaCache
->AllocateAndWriteBlock(this, blockDataToStore
, mode
);
1754 mChannelOffset
+= chunkSize
;
1759 MediaCache::ResourceStreamIterator
iter(mResourceID
);
1760 while (MediaCacheStream
* stream
= iter
.Next()) {
1761 if (stream
->mStreamLength
>= 0) {
1762 // The stream is at least as long as what we've read
1763 stream
->mStreamLength
= std::max(stream
->mStreamLength
, mChannelOffset
);
1765 stream
->mClient
->CacheClientNotifyDataReceived();
1768 // Notify in case there's a waiting reader
1769 // XXX it would be fairly easy to optimize things a lot more to
1770 // avoid waking up reader threads unnecessarily
1775 MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll
)
1777 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1779 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1781 int32_t blockOffset
= int32_t(mChannelOffset
%BLOCK_SIZE
);
1782 if (blockOffset
> 0) {
1783 CACHE_LOG(PR_LOG_DEBUG
,
1784 ("Stream %p writing partial block: [%d] bytes; "
1785 "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
1787 this, blockOffset
, mStreamOffset
, mChannelOffset
, mStreamLength
,
1788 aNotifyAll
? "yes" : "no"));
1790 // Write back the partial block
1791 memset(reinterpret_cast<char*>(mPartialBlockBuffer
) + blockOffset
, 0,
1792 BLOCK_SIZE
- blockOffset
);
1793 gMediaCache
->AllocateAndWriteBlock(this, mPartialBlockBuffer
,
1794 mMetadataInPartialBlockBuffer
? MODE_METADATA
: MODE_PLAYBACK
);
1796 // Wake up readers who may be waiting for this data
1803 MediaCacheStream::FlushPartialBlock()
1805 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1807 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1809 // Write the current partial block to memory.
1810 // Note: This writes a full block, so if data is not at the end of the
1811 // stream, the decoder must subsequently choose correct start and end offsets
1812 // for reading/seeking.
1813 FlushPartialBlockInternal(false);
1815 gMediaCache
->QueueUpdate();
1819 MediaCacheStream::NotifyDataEnded(nsresult aStatus
)
1821 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1823 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1825 if (NS_FAILED(aStatus
)) {
1826 // Disconnect from other streams sharing our resource, since they
1827 // should continue trying to load. Our load might have been deliberately
1828 // canceled and that shouldn't affect other streams.
1829 mResourceID
= gMediaCache
->AllocateResourceID();
1832 FlushPartialBlockInternal(true);
1834 if (!mDidNotifyDataEnded
) {
1835 MediaCache::ResourceStreamIterator
iter(mResourceID
);
1836 while (MediaCacheStream
* stream
= iter
.Next()) {
1837 if (NS_SUCCEEDED(aStatus
)) {
1838 // We read the whole stream, so remember the true length
1839 stream
->mStreamLength
= mChannelOffset
;
1841 NS_ASSERTION(!stream
->mDidNotifyDataEnded
, "Stream already ended!");
1842 stream
->mDidNotifyDataEnded
= true;
1843 stream
->mNotifyDataEndedStatus
= aStatus
;
1844 stream
->mClient
->CacheClientNotifyDataEnded(aStatus
);
1848 mChannelEnded
= true;
1849 gMediaCache
->QueueUpdate();
1852 MediaCacheStream::~MediaCacheStream()
1854 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1855 NS_ASSERTION(!mPinCount
, "Unbalanced Pin");
1858 NS_ASSERTION(mClosed
, "Stream was not closed");
1859 gMediaCache
->ReleaseStream(this);
1860 MediaCache::MaybeShutdown();
1865 MediaCacheStream::SetTransportSeekable(bool aIsTransportSeekable
)
1867 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1868 NS_ASSERTION(mIsTransportSeekable
|| aIsTransportSeekable
||
1869 mChannelOffset
== 0, "channel offset must be zero when we become non-seekable");
1870 mIsTransportSeekable
= aIsTransportSeekable
;
1871 // Queue an Update since we may change our strategy for dealing
1873 gMediaCache
->QueueUpdate();
1877 MediaCacheStream::IsTransportSeekable()
1879 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1880 return mIsTransportSeekable
;
1884 MediaCacheStream::AreAllStreamsForResourceSuspended()
1886 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1887 MediaCache::ResourceStreamIterator
iter(mResourceID
);
1888 // Look for a stream that's able to read the data we need
1889 int64_t dataOffset
= -1;
1890 while (MediaCacheStream
* stream
= iter
.Next()) {
1891 if (stream
->mCacheSuspended
|| stream
->mChannelEnded
|| stream
->mClosed
) {
1894 if (dataOffset
< 0) {
1895 dataOffset
= GetCachedDataEndInternal(mStreamOffset
);
1897 // Ignore streams that are reading beyond the data we need
1898 if (stream
->mChannelOffset
> dataOffset
) {
1908 MediaCacheStream::Close()
1910 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1912 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1914 // Queue an Update since we may have created more free space. Don't do
1915 // it from CloseInternal since that gets called by Update() itself
1916 // sometimes, and we try to not to queue updates from Update().
1917 gMediaCache
->QueueUpdate();
1921 MediaCacheStream::EnsureCacheUpdate()
1925 gMediaCache
->Update();
1929 MediaCacheStream::CloseInternal(ReentrantMonitorAutoEnter
& aReentrantMonitor
)
1931 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
1936 gMediaCache
->ReleaseStreamBlocks(this);
1937 // Wake up any blocked readers
1938 aReentrantMonitor
.NotifyAll();
1942 MediaCacheStream::Pin()
1944 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1946 // Queue an Update since we may no longer want to read more into the
1947 // cache, if this stream's block have become non-evictable
1948 gMediaCache
->QueueUpdate();
1952 MediaCacheStream::Unpin()
1954 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1955 NS_ASSERTION(mPinCount
> 0, "Unbalanced Unpin");
1957 // Queue an Update since we may be able to read more into the
1958 // cache, if this stream's block have become evictable
1959 gMediaCache
->QueueUpdate();
1963 MediaCacheStream::GetLength()
1965 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1966 return mStreamLength
;
1970 MediaCacheStream::GetNextCachedData(int64_t aOffset
)
1972 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1973 return GetNextCachedDataInternal(aOffset
);
1977 MediaCacheStream::GetCachedDataEnd(int64_t aOffset
)
1979 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1980 return GetCachedDataEndInternal(aOffset
);
1984 MediaCacheStream::IsDataCachedToEndOfStream(int64_t aOffset
)
1986 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
1987 if (mStreamLength
< 0)
1989 return GetCachedDataEndInternal(aOffset
) >= mStreamLength
;
1993 MediaCacheStream::GetCachedDataEndInternal(int64_t aOffset
)
1995 gMediaCache
->GetReentrantMonitor().AssertCurrentThreadIn();
1996 uint32_t startBlockIndex
= aOffset
/BLOCK_SIZE
;
1997 uint32_t blockIndex
= startBlockIndex
;
1998 while (blockIndex
< mBlocks
.Length() && mBlocks
[blockIndex
] != -1) {
2001 int64_t result
= blockIndex
*BLOCK_SIZE
;
2002 if (blockIndex
== mChannelOffset
/BLOCK_SIZE
) {
2003 // The block containing mChannelOffset may be partially read but not
2004 // yet committed to the main cache
2005 result
= mChannelOffset
;
2007 if (mStreamLength
>= 0) {
2008 // The last block in the cache may only be partially valid, so limit
2009 // the cached range to the stream length
2010 result
= std::min(result
, mStreamLength
);
2012 return std::max(result
, aOffset
);
2016 MediaCacheStream::GetNextCachedDataInternal(int64_t aOffset
)
2018 gMediaCache
->GetReentrantMonitor().AssertCurrentThreadIn();
2019 if (aOffset
== mStreamLength
)
2022 uint32_t startBlockIndex
= aOffset
/BLOCK_SIZE
;
2023 uint32_t channelBlockIndex
= mChannelOffset
/BLOCK_SIZE
;
2025 if (startBlockIndex
== channelBlockIndex
&&
2026 aOffset
< mChannelOffset
) {
2027 // The block containing mChannelOffset is partially read, but not
2028 // yet committed to the main cache. aOffset lies in the partially
2029 // read portion, thus it is effectively cached.
2033 if (startBlockIndex
>= mBlocks
.Length())
2036 // Is the current block cached?
2037 if (mBlocks
[startBlockIndex
] != -1)
2040 // Count the number of uncached blocks
2041 bool hasPartialBlock
= (mChannelOffset
% BLOCK_SIZE
) != 0;
2042 uint32_t blockIndex
= startBlockIndex
+ 1;
2044 if ((hasPartialBlock
&& blockIndex
== channelBlockIndex
) ||
2045 (blockIndex
< mBlocks
.Length() && mBlocks
[blockIndex
] != -1)) {
2046 // We at the incoming channel block, which has has data in it,
2047 // or are we at a cached block. Return index of block start.
2048 return blockIndex
* BLOCK_SIZE
;
2051 // No more cached blocks?
2052 if (blockIndex
>= mBlocks
.Length())
2058 NS_NOTREACHED("Should return in loop");
2063 MediaCacheStream::SetReadMode(ReadMode aMode
)
2065 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2066 if (aMode
== mCurrentMode
)
2068 mCurrentMode
= aMode
;
2069 gMediaCache
->QueueUpdate();
2073 MediaCacheStream::SetPlaybackRate(uint32_t aBytesPerSecond
)
2075 NS_ASSERTION(aBytesPerSecond
> 0, "Zero playback rate not allowed");
2076 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2077 if (aBytesPerSecond
== mPlaybackBytesPerSecond
)
2079 mPlaybackBytesPerSecond
= aBytesPerSecond
;
2080 gMediaCache
->QueueUpdate();
2084 MediaCacheStream::Seek(int32_t aWhence
, int64_t aOffset
)
2086 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
2088 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2090 return NS_ERROR_FAILURE
;
2092 int64_t oldOffset
= mStreamOffset
;
2095 if (mStreamLength
< 0)
2096 return NS_ERROR_FAILURE
;
2097 mStreamOffset
= mStreamLength
+ aOffset
;
2100 mStreamOffset
+= aOffset
;
2103 mStreamOffset
= aOffset
;
2106 NS_ERROR("Unknown whence");
2107 return NS_ERROR_FAILURE
;
2110 CACHE_LOG(PR_LOG_DEBUG
, ("Stream %p Seek to %lld", this, (long long)mStreamOffset
));
2111 gMediaCache
->NoteSeek(this, oldOffset
);
2113 gMediaCache
->QueueUpdate();
2118 MediaCacheStream::Tell()
2120 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
2122 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2123 return mStreamOffset
;
2127 MediaCacheStream::Read(char* aBuffer
, uint32_t aCount
, uint32_t* aBytes
)
2129 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
2131 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2133 return NS_ERROR_FAILURE
;
2136 // Read one block (or part of a block) at a time
2137 while (count
< aCount
) {
2138 uint32_t streamBlock
= uint32_t(mStreamOffset
/BLOCK_SIZE
);
2139 uint32_t offsetInStreamBlock
=
2140 uint32_t(mStreamOffset
- streamBlock
*BLOCK_SIZE
);
2141 int64_t size
= std::min(aCount
- count
, BLOCK_SIZE
- offsetInStreamBlock
);
2143 if (mStreamLength
>= 0) {
2144 // Don't try to read beyond the end of the stream
2145 int64_t bytesRemaining
= mStreamLength
- mStreamOffset
;
2146 if (bytesRemaining
<= 0) {
2147 // Get out of here and return NS_OK
2150 size
= std::min(size
, bytesRemaining
);
2151 // Clamp size until 64-bit file size issues (bug 500784) are fixed.
2152 size
= std::min(size
, int64_t(INT32_MAX
));
2156 int32_t cacheBlock
= streamBlock
< mBlocks
.Length() ? mBlocks
[streamBlock
] : -1;
2157 if (cacheBlock
< 0) {
2158 // We don't have a complete cached block here.
2161 // Some data has been read, so return what we've got instead of
2162 // blocking or trying to find a stream with a partial block.
2166 // See if the data is available in the partial cache block of any
2167 // stream reading this resource. We need to do this in case there is
2168 // another stream with this resource that has all the data to the end of
2169 // the stream but the data doesn't end on a block boundary.
2170 MediaCacheStream
* streamWithPartialBlock
= nullptr;
2171 MediaCache::ResourceStreamIterator
iter(mResourceID
);
2172 while (MediaCacheStream
* stream
= iter
.Next()) {
2173 if (uint32_t(stream
->mChannelOffset
/BLOCK_SIZE
) == streamBlock
&&
2174 mStreamOffset
< stream
->mChannelOffset
) {
2175 streamWithPartialBlock
= stream
;
2179 if (streamWithPartialBlock
) {
2180 // We can just use the data in mPartialBlockBuffer. In fact we should
2181 // use it rather than waiting for the block to fill and land in
2183 bytes
= std::min
<int64_t>(size
, streamWithPartialBlock
->mChannelOffset
- mStreamOffset
);
2185 reinterpret_cast<char*>(streamWithPartialBlock
->mPartialBlockBuffer
) + offsetInStreamBlock
, bytes
);
2186 if (mCurrentMode
== MODE_METADATA
) {
2187 streamWithPartialBlock
->mMetadataInPartialBlockBuffer
= true;
2189 mStreamOffset
+= bytes
;
2194 // No data has been read yet, so block
2197 // We may have successfully read some data, but let's just throw
2199 return NS_ERROR_FAILURE
;
2204 gMediaCache
->NoteBlockUsage(this, cacheBlock
, mCurrentMode
, TimeStamp::Now());
2206 int64_t offset
= cacheBlock
*BLOCK_SIZE
+ offsetInStreamBlock
;
2207 NS_ABORT_IF_FALSE(size
>= 0 && size
<= INT32_MAX
, "Size out of range.");
2208 nsresult rv
= gMediaCache
->ReadCacheFile(offset
, aBuffer
+ count
, int32_t(size
), &bytes
);
2209 if (NS_FAILED(rv
)) {
2212 // If we did successfully read some data, may as well return it
2215 mStreamOffset
+= bytes
;
2220 // Some data was read, so queue an update since block priorities may
2222 gMediaCache
->QueueUpdate();
2224 CACHE_LOG(PR_LOG_DEBUG
,
2225 ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset
-count
), count
));
2231 MediaCacheStream::ReadAt(int64_t aOffset
, char* aBuffer
,
2232 uint32_t aCount
, uint32_t* aBytes
)
2234 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
2236 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2237 nsresult rv
= Seek(nsISeekableStream::NS_SEEK_SET
, aOffset
);
2238 if (NS_FAILED(rv
)) return rv
;
2239 return Read(aBuffer
, aCount
, aBytes
);
2243 MediaCacheStream::ReadFromCache(char* aBuffer
,
2247 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2249 return NS_ERROR_FAILURE
;
2251 // Read one block (or part of a block) at a time
2253 int64_t streamOffset
= aOffset
;
2254 while (count
< aCount
) {
2255 uint32_t streamBlock
= uint32_t(streamOffset
/BLOCK_SIZE
);
2256 uint32_t offsetInStreamBlock
=
2257 uint32_t(streamOffset
- streamBlock
*BLOCK_SIZE
);
2258 int64_t size
= std::min
<int64_t>(aCount
- count
, BLOCK_SIZE
- offsetInStreamBlock
);
2260 if (mStreamLength
>= 0) {
2261 // Don't try to read beyond the end of the stream
2262 int64_t bytesRemaining
= mStreamLength
- streamOffset
;
2263 if (bytesRemaining
<= 0) {
2264 return NS_ERROR_FAILURE
;
2266 size
= std::min(size
, bytesRemaining
);
2267 // Clamp size until 64-bit file size issues (bug 500784) are fixed.
2268 size
= std::min(size
, int64_t(INT32_MAX
));
2272 uint32_t channelBlock
= uint32_t(mChannelOffset
/BLOCK_SIZE
);
2273 int32_t cacheBlock
= streamBlock
< mBlocks
.Length() ? mBlocks
[streamBlock
] : -1;
2274 if (channelBlock
== streamBlock
&& streamOffset
< mChannelOffset
) {
2275 // We can just use the data in mPartialBlockBuffer. In fact we should
2276 // use it rather than waiting for the block to fill and land in
2278 bytes
= std::min
<int64_t>(size
, mChannelOffset
- streamOffset
);
2279 memcpy(aBuffer
+ count
,
2280 reinterpret_cast<char*>(mPartialBlockBuffer
) + offsetInStreamBlock
, bytes
);
2282 if (cacheBlock
< 0) {
2283 // We expect all blocks to be cached! Fail!
2284 return NS_ERROR_FAILURE
;
2286 int64_t offset
= cacheBlock
*BLOCK_SIZE
+ offsetInStreamBlock
;
2287 NS_ABORT_IF_FALSE(size
>= 0 && size
<= INT32_MAX
, "Size out of range.");
2288 nsresult rv
= gMediaCache
->ReadCacheFile(offset
, aBuffer
+ count
, int32_t(size
), &bytes
);
2289 if (NS_FAILED(rv
)) {
2293 streamOffset
+= bytes
;
2301 MediaCacheStream::Init()
2303 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
2310 return NS_ERROR_FAILURE
;
2311 gMediaCache
->OpenStream(this);
2312 mInitialized
= true;
2317 MediaCacheStream::InitAsClone(MediaCacheStream
* aOriginal
)
2319 if (!aOriginal
->IsAvailableForSharing())
2320 return NS_ERROR_FAILURE
;
2325 nsresult rv
= Init();
2328 mResourceID
= aOriginal
->mResourceID
;
2330 // Grab cache blocks from aOriginal as readahead blocks for our stream
2331 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2333 mPrincipal
= aOriginal
->mPrincipal
;
2334 mStreamLength
= aOriginal
->mStreamLength
;
2335 mIsTransportSeekable
= aOriginal
->mIsTransportSeekable
;
2337 // Cloned streams are initially suspended, since there is no channel open
2338 // initially for a clone.
2339 mCacheSuspended
= true;
2340 mChannelEnded
= true;
2342 if (aOriginal
->mDidNotifyDataEnded
) {
2343 mNotifyDataEndedStatus
= aOriginal
->mNotifyDataEndedStatus
;
2344 mDidNotifyDataEnded
= true;
2345 mClient
->CacheClientNotifyDataEnded(mNotifyDataEndedStatus
);
2348 for (uint32_t i
= 0; i
< aOriginal
->mBlocks
.Length(); ++i
) {
2349 int32_t cacheBlockIndex
= aOriginal
->mBlocks
[i
];
2350 if (cacheBlockIndex
< 0)
2353 while (i
>= mBlocks
.Length()) {
2354 mBlocks
.AppendElement(-1);
2356 // Every block is a readahead block for the clone because the clone's initial
2357 // stream offset is zero
2358 gMediaCache
->AddBlockOwnerAsReadahead(cacheBlockIndex
, this, i
);
2364 nsresult
MediaCacheStream::GetCachedRanges(nsTArray
<MediaByteRange
>& aRanges
)
2366 // Take the monitor, so that the cached data ranges can't grow while we're
2367 // trying to loop over them.
2368 ReentrantMonitorAutoEnter
mon(gMediaCache
->GetReentrantMonitor());
2370 // We must be pinned while running this, otherwise the cached data ranges may
2371 // shrink while we're trying to loop over them.
2372 NS_ASSERTION(mPinCount
> 0, "Must be pinned");
2374 int64_t startOffset
= GetNextCachedData(0);
2375 while (startOffset
>= 0) {
2376 int64_t endOffset
= GetCachedDataEnd(startOffset
);
2377 NS_ASSERTION(startOffset
< endOffset
, "Buffered range must end after its start");
2378 // Bytes [startOffset..endOffset] are cached.
2379 aRanges
.AppendElement(MediaByteRange(startOffset
, endOffset
));
2380 startOffset
= GetNextCachedData(endOffset
);
2381 NS_ASSERTION(startOffset
== -1 || startOffset
> endOffset
,
2382 "Must have advanced to start of next range, or hit end of stream");
2387 } // namespace mozilla