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