Bug 513144. Allow streams that are related by mozLoadFrom to share data loaded after the initial clone. r=doublec

--HG--
extra : rebase_source : 4dab31030b84ce6066ba694851bb7587a051fe2c
This commit is contained in:
Robert O'Callahan 2009-09-15 14:30:45 +12:00
Родитель 3e223fd14e
Коммит 666ddd4c00
6 изменённых файлов: 173 добавлений и 57 удалений

Просмотреть файл

@ -84,6 +84,11 @@ using mozilla::TimeDuration;
// #define DEBUG_VERIFY_CACHE
#endif
// There is at most one media cache (although that could quite easily be
// relaxed if we wanted to manage multiple caches with independent
// size limits).
static nsMediaCache* gMediaCache;
class nsMediaCache {
public:
friend class nsMediaCacheStream::BlockList;
@ -92,7 +97,8 @@ public:
BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
};
nsMediaCache() : mMonitor(nsAutoMonitor::NewMonitor("media.cache")),
nsMediaCache() : mNextResourceID(1),
mMonitor(nsAutoMonitor::NewMonitor("media.cache")),
mFD(nsnull), mFDCurrentPos(0), mUpdateQueued(PR_FALSE)
#ifdef DEBUG
, mInUpdate(PR_FALSE)
@ -184,6 +190,25 @@ public:
PRMonitor* Monitor() { return mMonitor; }
class ResourceStreamIterator {
public:
ResourceStreamIterator(PRInt64 aResourceID) :
mResourceID(aResourceID), mNext(0) {}
nsMediaCacheStream* Next()
{
while (mNext < gMediaCache->mStreams.Length()) {
nsMediaCacheStream* stream = gMediaCache->mStreams[mNext];
++mNext;
if (stream->GetResourceID() == mResourceID)
return stream;
}
return nsnull;
}
private:
PRInt64 mResourceID;
PRUint32 mNext;
};
protected:
// Find a free or reusable block and return its index. If there are no
// free blocks and no reusable blocks, add a new block to the cache
@ -273,6 +298,9 @@ protected:
// end
void Truncate();
// This member is main-thread only. It's used to allocate unique
// resource IDs to streams.
PRInt64 mNextResourceID;
// This member is main-thread only. It contains all the streams.
nsTArray<nsMediaCacheStream*> mStreams;
@ -296,11 +324,6 @@ protected:
#endif
};
// There is at most one media cache (although that could quite easily be
// relaxed if we wanted to manage multiple caches with independent
// size limits).
static nsMediaCache* gMediaCache;
void nsMediaCacheStream::BlockList::AddFirstBlock(PRInt32 aBlock)
{
NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
@ -999,7 +1022,7 @@ nsMediaCache::Update()
Block* block = &mIndex[blockIndex];
// Try to relocate the block close to other blocks for the first stream.
// There is no point in trying ot make it close to other blocks in
// There is no point in trying to make it close to other blocks in
// *all* the streams it might belong to.
PRInt32 destinationBlockIndex =
FindReusableBlock(now, block->mOwners[0].mStream,
@ -1150,6 +1173,20 @@ nsMediaCache::Update()
enableReading = predictedNewDataUse < latestNextUse;
}
if (enableReading) {
for (PRUint32 j = 0; j < i; ++j) {
nsMediaCacheStream* other = mStreams[j];
if (other->mResourceID == stream->mResourceID &&
!other->mCacheSuspended &&
other->mChannelOffset/BLOCK_SIZE == stream->mChannelOffset/BLOCK_SIZE) {
// This block is already going to be read by the other stream.
// So don't try to read it from this stream as well.
enableReading = PR_FALSE;
break;
}
}
}
nsresult rv = NS_OK;
if (stream->mChannelOffset != desiredOffset && enableReading) {
// We need to seek now.
@ -1281,17 +1318,23 @@ nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aDa
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
PRInt32 streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
// Remove all cached copies of this block
ResourceStreamIterator iter(aStream->mResourceID);
while (nsMediaCacheStream* stream = iter.Next()) {
while (streamBlockIndex >= PRInt32(stream->mBlocks.Length())) {
stream->mBlocks.AppendElement(-1);
}
if (stream->mBlocks[streamBlockIndex] >= 0) {
// We no longer want to own this block
PRInt32 globalBlockIndex = stream->mBlocks[streamBlockIndex];
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
RemoveBlockOwner(globalBlockIndex, stream);
}
}
// Extend the mBlocks array as necessary
while (streamBlockIndex >= PRInt32(aStream->mBlocks.Length())) {
aStream->mBlocks.AppendElement(-1);
}
if (aStream->mBlocks[streamBlockIndex] >= 0) {
// We no longer want to own this block
PRInt32 globalBlockIndex = aStream->mBlocks[streamBlockIndex];
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
globalBlockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
RemoveBlockOwner(globalBlockIndex, aStream);
}
TimeStamp now = TimeStamp::Now();
PRInt32 blockIndex = FindBlockForIncomingData(now, aStream);
@ -1301,29 +1344,35 @@ nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aDa
Block* block = &mIndex[blockIndex];
LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
BlockOwner* bo = block->mOwners.AppendElement();
if (!bo)
return;
bo->mStream = aStream;
bo->mStreamBlock = streamBlockIndex;
bo->mLastUseTime = now;
aStream->mBlocks[streamBlockIndex] = blockIndex;
mFreeBlocks.RemoveBlock(blockIndex);
if (streamBlockIndex*BLOCK_SIZE < aStream->mStreamOffset) {
bo->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK
? PLAYED_BLOCK : METADATA_BLOCK;
// This must be the most-recently-used block, since we
// marked it as used now (which may be slightly bogus, but we'll
// treat it as used for simplicity).
GetListForBlock(bo)->AddFirstBlock(blockIndex);
Verify();
} else {
// This may not be the latest readahead block, although it usually
// will be. We may have to scan for the right place to insert
// the block in the list.
bo->mClass = READAHEAD_BLOCK;
InsertReadaheadBlock(bo, blockIndex);
// Tell each stream using this resource about the new block.
ResourceStreamIterator iter(aStream->mResourceID);
while (nsMediaCacheStream* stream = iter.Next()) {
BlockOwner* bo = block->mOwners.AppendElement();
if (!bo)
return;
bo->mStream = stream;
bo->mStreamBlock = streamBlockIndex;
bo->mLastUseTime = now;
stream->mBlocks[streamBlockIndex] = blockIndex;
if (streamBlockIndex*BLOCK_SIZE < stream->mStreamOffset) {
bo->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK
? PLAYED_BLOCK : METADATA_BLOCK;
// This must be the most-recently-used block, since we
// marked it as used now (which may be slightly bogus, but we'll
// treat it as used for simplicity).
GetListForBlock(bo)->AddFirstBlock(blockIndex);
Verify();
} else {
// This may not be the latest readahead block, although it usually
// will be. We may have to scan for the right place to insert
// the block in the list.
bo->mClass = READAHEAD_BLOCK;
InsertReadaheadBlock(bo, blockIndex);
}
}
nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
@ -1346,6 +1395,7 @@ nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
nsAutoMonitor mon(mMonitor);
mStreams.AppendElement(aStream);
aStream->mResourceID = mNextResourceID++;
}
void
@ -1548,8 +1598,6 @@ nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData,
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
UpdatePrincipal(aPrincipal);
nsAutoMonitor mon(gMediaCache->Monitor());
PRInt64 size = aSize;
const char* data = aData;
@ -1594,14 +1642,20 @@ nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData,
}
mChannelOffset += chunkSize;
if (mStreamLength >= 0) {
// The stream is at least as long as what we've read
mStreamLength = PR_MAX(mStreamLength, mChannelOffset);
}
size -= chunkSize;
data += chunkSize;
}
nsMediaCache::ResourceStreamIterator iter(mResourceID);
while (nsMediaCacheStream* stream = iter.Next()) {
if (stream->mStreamLength >= 0) {
// The stream is at least as long as what we've read
stream->mStreamLength = PR_MAX(stream->mStreamLength, mChannelOffset);
}
stream->UpdatePrincipal(aPrincipal);
stream->mClient->CacheClientNotifyDataReceived();
}
// Notify in case there's a waiting reader
// XXX it would be fairly easy to optimize things a lot more to
// avoid waking up reader threads unnecessarily
@ -1614,10 +1668,6 @@ nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsAutoMonitor mon(gMediaCache->Monitor());
if (NS_SUCCEEDED(aStatus)) {
// We read the whole stream, so remember the true length
mStreamLength = mChannelOffset;
}
PRInt32 blockOffset = PRInt32(mChannelOffset%BLOCK_SIZE);
if (blockOffset > 0) {
@ -1629,6 +1679,15 @@ nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
// Wake up readers who may be waiting for this data
mon.NotifyAll();
}
nsMediaCache::ResourceStreamIterator iter(mResourceID);
while (nsMediaCacheStream* stream = iter.Next()) {
if (NS_SUCCEEDED(aStatus)) {
// We read the whole stream, so remember the true length
stream->mStreamLength = mChannelOffset;
}
stream->mClient->CacheClientNotifyDataEnded(aStatus);
}
}
nsMediaCacheStream::~nsMediaCacheStream()
@ -2036,6 +2095,7 @@ nsMediaCacheStream::InitAsClone(nsMediaCacheStream* aOriginal)
nsresult rv = Init();
if (NS_FAILED(rv))
return rv;
mResourceID = aOriginal->mResourceID;
// Grab cache blocks from aOriginal as readahead blocks for our stream
nsAutoMonitor mon(gMediaCache->Monitor());

Просмотреть файл

@ -220,7 +220,7 @@ public:
// aClient provides the underlying transport that cache will use to read
// data for this stream.
nsMediaCacheStream(nsMediaChannelStream* aClient)
: mClient(aClient), mChannelOffset(0),
: mClient(aClient), mResourceID(0), mChannelOffset(0),
mStreamOffset(0), mStreamLength(-1), mPlaybackBytesPerSecond(10000),
mPinCount(0), mCurrentMode(MODE_PLAYBACK),
mInitialized(PR_FALSE), mClosed(PR_FALSE),
@ -301,6 +301,8 @@ public:
// If we've successfully read data beyond the originally reported length,
// we return the end of the data we've read.
PRInt64 GetLength();
// Returns the unique resource ID
PRInt64 GetResourceID() { return mResourceID; }
// Returns the end of the bytes starting at the given offset
// which are in cache.
PRInt64 GetCachedDataEnd(PRInt64 aOffset);
@ -426,6 +428,10 @@ private:
// These fields are main-thread-only.
nsMediaChannelStream* mClient;
nsCOMPtr<nsIPrincipal> mPrincipal;
// This is a unique ID representing the resource we're loading.
// All streams with the same mResourceID are loading the same
// underlying resource and should share data.
PRInt64 mResourceID;
// All other fields are all protected by the cache's monitor and
// can be accessed by by any thread.

Просмотреть файл

@ -271,9 +271,6 @@ nsMediaChannelStream::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
if (!mIgnoreClose) {
mCacheStream.NotifyDataEnded(aStatus);
if (mDecoder) {
mDecoder->NotifyDownloadEnded(aStatus);
}
}
return NS_OK;
@ -339,11 +336,7 @@ nsMediaChannelStream::OnDataAvailable(nsIRequest* aRequest,
NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
count -= read;
}
mDecoder->NotifyBytesDownloaded();
// Fire a progress events according to the time and byte constraints outlined
// in the spec.
mDecoder->Progress(PR_FALSE);
return NS_OK;
}
@ -593,6 +586,48 @@ nsMediaChannelStream::RecreateChannel()
loadFlags);
}
void
nsMediaChannelStream::DoNotifyDataReceived()
{
mDataReceivedEvent.Revoke();
mDecoder->NotifyBytesDownloaded();
}
void
nsMediaChannelStream::CacheClientNotifyDataReceived()
{
NS_ASSERTION(NS_IsMainThread(), "Don't call on main thread");
if (mDataReceivedEvent.IsPending())
return;
mDataReceivedEvent =
new nsNonOwningRunnableMethod<nsMediaChannelStream>(this, &nsMediaChannelStream::DoNotifyDataReceived);
NS_DispatchToMainThread(mDataReceivedEvent.get(), NS_DISPATCH_NORMAL);
}
class DataEnded : public nsRunnable {
public:
DataEnded(nsMediaDecoder* aDecoder, nsresult aStatus) :
mDecoder(aDecoder), mStatus(aStatus) {}
NS_IMETHOD Run() {
mDecoder->NotifyDownloadEnded(mStatus);
return NS_OK;
}
private:
nsRefPtr<nsMediaDecoder> mDecoder;
nsresult mStatus;
};
void
nsMediaChannelStream::CacheClientNotifyDataEnded(nsresult aStatus)
{
NS_ASSERTION(NS_IsMainThread(), "Don't call on main thread");
nsCOMPtr<nsIRunnable> event = new DataEnded(mDecoder, aStatus);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
nsresult
nsMediaChannelStream::CacheClientSeek(PRInt64 aOffset, PRBool aResume)
{

Просмотреть файл

@ -309,6 +309,14 @@ public:
// These are called on the main thread by nsMediaCache. These must
// not block or grab locks.
// Notify that data is available from the cache. This can happen even
// if this stream didn't read any data, since another stream might have
// received data for the same resource.
void CacheClientNotifyDataReceived();
// Notify that we reached the end of the stream. This can happen even
// if this stream didn't read any data, since another stream might have
// received data for the same resource.
void CacheClientNotifyDataEnded(nsresult aStatus);
// Start a new load at the given aOffset. The old load is cancelled
// and no more data from the old load will be notified via
// nsMediaCacheStream::NotifyDataReceived/Ended.
@ -346,7 +354,6 @@ public:
virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset);
virtual PRBool IsSuspendedByCache();
protected:
class Listener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
@ -367,6 +374,7 @@ protected:
};
friend class Listener;
protected:
// These are called on the main thread by Listener.
nsresult OnStartRequest(nsIRequest* aRequest);
nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
@ -383,6 +391,8 @@ protected:
// Closes the channel. Main thread only.
void CloseChannel();
void DoNotifyDataReceived();
static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream,
void *aClosure,
const char *aFromSegment,
@ -393,6 +403,9 @@ protected:
// Main thread access only
PRInt64 mOffset;
nsRefPtr<Listener> mListener;
// A data received event for the decoder that has been dispatched but has
// not yet been processed.
nsRevocableEventPtr<nsNonOwningRunnableMethod<nsMediaChannelStream> > mDataReceivedEvent;
PRUint32 mSuspendCount;
// When this flag is set, if we get a network error we should silently
// reopen the stream.

Просмотреть файл

@ -2269,6 +2269,7 @@ void nsOggDecoder::NotifyBytesDownloaded()
NS_ASSERTION(NS_IsMainThread(),
"nsOggDecoder::NotifyBytesDownloaded called on non-main thread");
UpdateReadyStateForData();
Progress(PR_FALSE);
}
void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)

Просмотреть файл

@ -1418,6 +1418,7 @@ void
nsWaveDecoder::NotifyBytesDownloaded()
{
UpdateReadyStateForData();
Progress(PR_FALSE);
}
void