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