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