--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -152,30 +152,35 @@ public:
NEXT_FRAME_UNAVAILABLE
};
void UpdateReadyStateForData(NextFrameStatus aNextFrame);
// Use this method to change the mReadyState member, so required
// events can be fired.
void ChangeReadyState(nsMediaReadyState aState);
+ // Notify that enough data has arrived to start autoplaying.
+ // If the element is 'autoplay' and is ready to play back (not paused,
+ // autoplay pref enabled, etc), it should start playing back.
+ void NotifyAutoplayDataReady();
+
// Gets the pref media.enforce_same_site_origin, which determines
// if we should check Access Controls, or allow cross domain loads.
PRBool ShouldCheckAllowOrigin();
// Is the media element potentially playing as defined by the HTML 5 specification.
// http://www.whatwg.org/specs/web-apps/current-work/#potentially-playing
PRBool IsPotentiallyPlaying() const;
// Has playback ended as defined by the HTML 5 specification.
// http://www.whatwg.org/specs/web-apps/current-work/#ended
PRBool IsPlaybackEnded() const;
// principal of the currently playing stream
- nsIPrincipal* GetCurrentPrincipal();
+ already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
// Update the visual size of the media. Called from the decoder on the
// main thread when/if the size changes.
void UpdateMediaSize(nsIntSize size);
// Handle moving into and out of the bfcache by pausing and playing
// as needed.
void Freeze();
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1218,19 +1218,20 @@ PRBool nsHTMLMediaElement::ShouldCheckAl
// Number of bytes to add to the download size when we're computing
// when the download will finish --- a safety margin in case bandwidth
// or other conditions are worse than expected
static const PRInt32 gDownloadSizeSafetyMargin = 1000000;
void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
{
if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
- NS_ASSERTION(aNextFrame != NEXT_FRAME_AVAILABLE,
- "How can we have a frame but no metadata?");
- // The arrival of more data can't change us out of this state.
+ // aNextFrame might have a next frame because the decoder can advance
+ // on its own thread before ResourceLoaded or MetadataLoaded gets
+ // a chance to run.
+ // The arrival of more data can't change us out of this readyState.
return;
}
nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
if (aNextFrame != NEXT_FRAME_AVAILABLE &&
!mDecoder->IsEnded() &&
stats.mDownloadPosition < stats.mTotalBytes) {
@@ -1306,40 +1307,46 @@ void nsHTMLMediaElement::ChangeReadyStat
mWaitingFired = PR_FALSE;
}
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
}
- if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
- mAutoplaying &&
- mPaused &&
- HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
- mAutoplayEnabled) {
- mPaused = PR_FALSE;
- if (mDecoder) {
- mDecoder->Play();
- }
- DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
+ NotifyAutoplayDataReady();
}
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
IsPotentiallyPlaying()) {
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
}
if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
}
}
+void nsHTMLMediaElement::NotifyAutoplayDataReady()
+{
+ if (mAutoplaying &&
+ mPaused &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
+ mAutoplayEnabled) {
+ mPaused = PR_FALSE;
+ if (mDecoder) {
+ mDecoder->Play();
+ }
+ DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
+ }
+}
+
void nsHTMLMediaElement::Paint(gfxContext* aContext, const gfxRect& aRect)
{
if (mDecoder)
mDecoder->Paint(aContext, aRect);
}
nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
{
@@ -1426,17 +1433,17 @@ PRBool nsHTMLMediaElement::IsPlaybackEnd
{
// TODO:
// the current playback position is equal to the effective end of the media resource.
// See bug 449157.
return mNetworkState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
mDecoder ? mDecoder->IsEnded() : PR_FALSE;
}
-nsIPrincipal* nsHTMLMediaElement::GetCurrentPrincipal()
+already_AddRefed<nsIPrincipal> nsHTMLMediaElement::GetCurrentPrincipal()
{
if (!mDecoder)
return nsnull;
return mDecoder->GetCurrentPrincipal();
}
void nsHTMLMediaElement::UpdateMediaSize(nsIntSize size)
--- a/content/media/video/public/Makefile.in
+++ b/content/media/video/public/Makefile.in
@@ -40,25 +40,20 @@ srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = content
EXPORTS = \
nsMediaDecoder.h \
+ nsMediaStream.h \
+ nsMediaCache.h \
$(NULL)
-ifdef MOZ_MEDIA
-EXPORTS += \
- nsChannelToPipeListener.h \
- nsMediaStream.h \
- $(NULL)
-endif
-
ifdef MOZ_SYDNEYAUDIO
EXPORTS += \
nsAudioStream.h \
$(NULL)
endif
ifdef MOZ_OGG
EXPORTS += \
--- a/content/media/video/public/nsChannelReader.h
+++ b/content/media/video/public/nsChannelReader.h
@@ -34,23 +34,23 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#if !defined(nsChannelReader_h_)
#define nsChannelReader_h_
#include "nsAutoPtr.h"
#include "nsMediaStream.h"
-#include "nsMediaDecoder.h"
-#include "nsIPrincipal.h"
#include "oggplay/oggplay.h"
+class nsIURI;
class nsIChannel;
class nsIStreamListener;
+class nsMediaDecoder;
class nsChannelReader : public OggPlayReader
{
public:
nsChannelReader();
~nsChannelReader();
/**
@@ -58,25 +58,17 @@ public:
* optional channel.
* @param aChannel may be null
* @param aStreamListener if aChannel is non-null, this will return
* a stream listener which should be attached to the channel.
*/
nsresult Init(nsMediaDecoder* aDecoder, nsIURI* aURI, nsIChannel* aChannel,
nsIStreamListener** aStreamListener);
- // Cancel any blocking request currently in progress and cause that
- // request to return an error. Call on main thread only.
- void Cancel();
-
- // Suspend any downloads that are in progress.
- void Suspend();
-
- // Resume any downloads that have been suspended.
- void Resume();
+ nsMediaStream* Stream() { return mStream; }
// Set the duration of the media resource. Call with decoder lock
// obtained so that the decoder thread does not request the duration
// while it is changing.
void SetDuration(PRInt64 aDuration);
nsIPrincipal* GetCurrentPrincipal();
deleted file mode 100644
--- a/content/media/video/public/nsChannelToPipeListener.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla code.
- *
- * The Initial Developer of the Original Code is the Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Chris Double <chris.double@double.co.nz>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-#if !defined(nsChannelToPipeListener_h_)
-#define nsChannelToPipeListener_h_
-
-#include "nsCOMPtr.h"
-#include "nsIInputStream.h"
-#include "nsIOutputStream.h"
-#include "nsIRequestObserver.h"
-#include "nsIStreamListener.h"
-#include "nsIPrincipal.h"
-
-class nsMediaDecoder;
-
-/*
- Reads all data on the input stream of a channel and
- writes it to a pipe. This allows a seperate thread to
- read data from a channel running on the main thread
-*/
-class nsChannelToPipeListener : public nsIStreamListener
-{
- // ISupports
- NS_DECL_ISUPPORTS
-
- // IRequestObserver
- NS_DECL_NSIREQUESTOBSERVER
-
- // IStreamListener
- NS_DECL_NSISTREAMLISTENER
-
- public:
- // If aSeeking is PR_TRUE then this listener was created as part of a
- // seek request and is expecting a byte range partial result. aOffset
- // is the offset in bytes that this listener started reading from.
- nsChannelToPipeListener(nsMediaDecoder* aDecoder,
- PRBool aSeeking = PR_FALSE);
- nsresult Init();
- nsresult GetInputStream(nsIInputStream** aStream);
- void Stop();
- void Cancel();
-
- nsIPrincipal* GetCurrentPrincipal();
-
-private:
- nsCOMPtr<nsIInputStream> mInput;
- nsCOMPtr<nsIOutputStream> mOutput;
- nsCOMPtr<nsIPrincipal> mPrincipal;
- nsRefPtr<nsMediaDecoder> mDecoder;
-
- // PR_TRUE if this listener is expecting a byte range request result
- PRPackedBool mSeeking;
-};
-
-#endif
new file mode 100644
--- /dev/null
+++ b/content/media/video/public/nsMediaCache.h
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Robert O'Callahan <robert@ocallahan.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsMediaCache_h_
+#define nsMediaCache_h_
+
+#include "nsTArray.h"
+#include "prinrval.h"
+#include "nsAutoLock.h"
+
+/**
+ * Media applications want fast, "on demand" random access to media data,
+ * for pausing, seeking, etc. But we are primarily interested
+ * in transporting media data using HTTP over the Internet, which has
+ * high latency to open a connection, requires a new connection for every
+ * seek, may not even support seeking on some connections (especially
+ * live streams), and uses a push model --- data comes from the server
+ * and you don't have much control over the rate. Also, transferring data
+ * over the Internet can be slow and/or unpredictable, so we want to read
+ * ahead to buffer and cache as much data as possible.
+ *
+ * The job of the media cache is to resolve this impedance mismatch.
+ * The media cache reads data from Necko channels into file-backed storage,
+ * and offers a random-access file-like API to the stream data
+ * (nsMediaCacheStream). Along the way it solves several problems:
+ * -- The cache intelligently reads ahead to prefetch data that may be
+ * needed in the future
+ * -- The size of the cache is bounded so that we don't fill up
+ * storage with read-ahead data
+ * -- Cache replacement is managed globally so that the most valuable
+ * data (across all streams) is retained
+ * -- The cache can suspend Necko channels temporarily when their data is
+ * not wanted (yet)
+ * -- The cache translates file-like seek requests to HTTP seeks,
+ * including optimizations like not triggering a new seek if it would
+ * be faster to just keep reading until we reach the seek point. The
+ * "seek to EOF" idiom to determine file size is also handled efficiently
+ * (seeking to EOF and then seeking back to the previous offset does not
+ * trigger any Necko activity)
+ * -- The cache also handles the case where the server does not support
+ * seeking
+ * -- Necko can only send data to the main thread, but nsMediaCacheStream
+ * can distribute data to any thread
+ * -- The cache exposes APIs so clients can detect what data is
+ * currently held
+ *
+ * Note that although HTTP is the most important transport and we only
+ * support transport-level seeking via HTTP byte-ranges, the media cache
+ * works with any kind of Necko channels and provides random access to
+ * cached data even for, e.g., FTP streams.
+ *
+ * The media cache is not persistent. It does not currently allow
+ * data from one load to be used by other loads, either within the same
+ * browser session or across browser sessions. The media cache file
+ * is marked "delete on close" so it will automatically disappear in the
+ * event of a browser crash or shutdown.
+ *
+ * The media cache is block-based. Streams are divided into blocks of a
+ * fixed size (currently 4K) and we cache blocks. A single cache contains
+ * blocks for all streams.
+ *
+ * The cache size is controlled by the media.cache_size preference
+ * (which is in KB). The default size is 50MB.
+ *
+ * The replacement policy predicts a "time of next use" for each block
+ * in the cache. When we need to free a block, the block with the latest
+ * "time of next use" will be evicted. Blocks are divided into
+ * different classes, each class having its own predictor:
+ * FREE_BLOCK: these blocks are effectively infinitely far in the future;
+ * a free block will always be chosen for replacement before other classes
+ * of blocks.
+ * METADATA_BLOCK: these are blocks that contain data that has been read
+ * by the decoder in "metadata mode", e.g. while the decoder is searching
+ * the stream during a seek operation. These blocks are managed with an
+ * LRU policy; the "time of next use" is predicted to be as far in the
+ * future as the last use was in the past.
+ * PLAYED_BLOCK: these are blocks that have not been read in "metadata
+ * mode", and contain data behind the current decoder read point. (They
+ * may not actually have been read by the decoder, if the decoder seeked
+ * forward.) These blocks are managed with an LRU policy except that we add
+ * REPLAY_DELAY seconds of penalty to their predicted "time of next use",
+ * to reflect the uncertainty about whether replay will actually happen
+ * or not.
+ * READAHEAD_BLOCK: these are blocks that have not been read in
+ * "metadata mode" and that are entirely ahead of the current decoder
+ * read point. (They may actually have been read by the decoder in the
+ * past if the decoder has since seeked backward.) We predict the
+ * time of next use for these blocks by assuming steady playback and
+ * dividing the number of bytes between the block and the current decoder
+ * read point by the decoder's estimate of its playback rate in bytes
+ * per second. This ensures that the blocks farthest ahead are considered
+ * least valuable.
+ * For efficient prediction of the "latest time of next use", we maintain
+ * linked lists of blocks in each class, ordering blocks by time of
+ * next use. READAHEAD_BLOCKS have one linked list per stream, since their
+ * time of next use depends on stream parameters, but the other lists
+ * are global.
+ *
+ * A block containing a current decoder read point can contain data
+ * both behind and ahead of the read point. It will be classified as a
+ * PLAYED_BLOCK but we will give it special treatment so it is never
+ * evicted --- it actually contains the highest-priority readahead data
+ * as well as played data.
+ *
+ * "Time of next use" estimates are also used for flow control. When
+ * reading ahead we can predict the time of next use for the data that
+ * will be read. If the predicted time of next use is later then the
+ * prediction for all currently cached blocks, and the cache is full, then
+ * we should suspend reading from the Necko channel.
+ *
+ * Unfortunately suspending the Necko channel can't immediately stop the
+ * flow of data from the server. First our desire to suspend has to be
+ * transmitted to the server (in practice, Necko stops reading from the
+ * socket, which causes the kernel to shrink its advertised TCP receive
+ * window size to zero). Then the server can stop sending the data, but
+ * we will receive data roughly corresponding to the product of the link
+ * bandwidth multiplied by the round-trip latency. We deal with this by
+ * letting the cache overflow temporarily and then trimming it back by
+ * moving overflowing blocks back into the body of the cache, replacing
+ * less valuable blocks as they become available. We try to avoid simply
+ * discarding overflowing readahead data.
+ *
+ * All changes to the actual contents of the cache happen on the main
+ * thread, since that's where Necko's notifications happen.
+ *
+ * The media cache maintains at most one Necko channel for each stream.
+ * (In the future it might be advantageous to relax this, e.g. so that a
+ * seek to near the end of the file can happen without disturbing
+ * the loading of data from the beginning of the file.) The Necko channel
+ * is managed through nsMediaChannelStream; nsMediaCache does not
+ * depend on Necko directly.
+ *
+ * Every time something changes that might affect whether we want to
+ * read from a Necko channel, or whether we want to seek on the Necko
+ * channel --- such as data arriving or data being consumed by the
+ * decoder --- we asynchronously trigger nsMediaCache::Update on the main
+ * thread. That method implements most cache policy. It evaluates for
+ * each stream whether we want to suspend or resume the stream and what
+ * offset we should seek to, if any. It is also responsible for trimming
+ * back the cache size to its desired limit by moving overflowing blocks
+ * into the main part of the cache.
+ *
+ * Streams can be opened in non-seekable mode. In non-seekable mode,
+ * the cache will only call nsMediaChannelStream::CacheClientSeek with
+ * a 0 offset. The cache tries hard not to discard readahead data
+ * for non-seekable streams, since that could trigger a potentially
+ * disastrous re-read of the entire stream. It's up to cache clients
+ * to try to avoid requesting seeks on such streams.
+ *
+ * nsMediaCache has a single internal monitor for all synchronization.
+ * This is treated as the lowest level monitor in the media code. So,
+ * we must not acquire any nsMediaDecoder locks or nsMediaStream locks
+ * while holding the nsMediaCache lock. But it's OK to hold those locks
+ * and then get the nsMediaCache lock.
+ */
+class nsMediaCache;
+// defined in nsMediaStream.h
+class nsMediaChannelStream;
+
+/**
+ * If the cache fails to initialize then Init will fail, so nonstatic
+ * methods of this class can assume gMediaCache is non-null.
+ *
+ * This class can be directly embedded as a value.
+ */
+class nsMediaCacheStream {
+public:
+ enum {
+ // This needs to be a power of two
+ BLOCK_SIZE = 4096
+ };
+ enum ReadMode {
+ MODE_METADATA,
+ MODE_PLAYBACK
+ };
+
+ // aClient provides the underlying transport that cache will use to read
+ // data for this stream.
+ nsMediaCacheStream(nsMediaChannelStream* aClient)
+ : mClient(aClient), mChannelOffset(0),
+ mStreamOffset(0), mStreamLength(-1), mPlaybackBytesPerSecond(10000),
+ mPinCount(0), mCurrentMode(MODE_PLAYBACK), mClosed(PR_FALSE),
+ mIsSeekable(PR_FALSE), mCacheSuspended(PR_FALSE),
+ mMetadataInPartialBlockBuffer(PR_FALSE) {}
+ ~nsMediaCacheStream();
+
+ // Set up this stream with the cache. Can fail on OOM. Must be called
+ // before other methods on this object; no other methods may be called
+ // if this fails.
+ nsresult Init();
+
+ // These are called on the main thread.
+ // Tell us whether the stream is seekable or not. Non-seekable streams
+ // will always pass 0 for aOffset to CacheClientSeek. This should only
+ // be called while the stream is at channel offset 0. Seekability can
+ // change during the lifetime of the nsMediaCacheStream --- every time
+ // we do an HTTP load the seekability may be different (and sometimes
+ // is, in practice, due to the effects of caching proxies).
+ void SetSeekable(PRBool aIsSeekable);
+ // This must be called (and return) before the nsMediaChannelStream
+ // used to create this nsMediaCacheStream is deleted.
+ void Close();
+ // This returns true when the stream has been closed
+ PRBool IsClosed() const { return mClosed; }
+
+ // These callbacks are called on the main thread by the client
+ // when data has been received via the channel.
+ // Tells the cache what the server said the data length is going to be.
+ // The actual data length may be greater (we receive more data than
+ // specified) or smaller (the stream ends before we reach the given
+ // length), because servers can lie. The server's reported data length
+ // *and* the actual data length can even vary over time because a
+ // misbehaving server may feed us a different stream after each seek
+ // operation. So this is really just a hint. The cache may however
+ // stop reading (suspend the channel) when it thinks we've read all the
+ // data available based on an incorrect reported length. Seeks relative
+ // EOF also depend on the reported length if we haven't managed to
+ // read the whole stream yet.
+ void NotifyDataLength(PRInt64 aLength);
+ // Notifies the cache that a load has begun. We pass the offset
+ // because in some cases the offset might not be what the cache
+ // requested. In particular we might unexpectedly start providing
+ // data at offset 0. This need not be called if the offset is the
+ // offset that the cache requested in
+ // nsMediaChannelStream::CacheClientSeek.
+ void NotifyDataStarted(PRInt64 aOffset);
+ // Notifies the cache that data has been received. The stream already
+ // knows the offset because data is received in sequence and
+ // the starting offset is known via NotifyDataStarted or because
+ // the cache requested the offset in
+ // nsMediaChannelStream::CacheClientSeek, or because it defaulted to 0.
+ void NotifyDataReceived(PRInt64 aSize, const char* aData);
+ // Notifies the cache that the channel has closed with the given status.
+ void NotifyDataEnded(nsresult aStatus);
+
+ // These methods can be called on any thread.
+ // Cached blocks associated with this stream will not be evicted
+ // while the stream is pinned.
+ void Pin();
+ void Unpin();
+ // See comments above for NotifyDataLength about how the length
+ // can vary over time. Returns -1 if no length is known. Returns the
+ // reported length if we haven't got any better information. If
+ // the stream ended normally we return the length we actually got.
+ // 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 end of the bytes starting at the given offset
+ // which are in cache.
+ PRInt64 GetCachedDataEnd(PRInt64 aOffset);
+ // XXX we may need to add GetUncachedDataEnd at some point.
+ // IsDataCachedToEndOfStream returns true if all the data from
+ // aOffset to the end of the stream (the server-reported end, if the
+ // real end is not known) is in cache. If we know nothing about the
+ // end of the stream, this returns false.
+ PRBool IsDataCachedToEndOfStream(PRInt64 aOffset);
+ // The mode is initially MODE_PLAYBACK.
+ void SetReadMode(ReadMode aMode);
+ // This is the client's estimate of the playback rate assuming
+ // the media plays continuously. The cache can't guess this itself
+ // because it doesn't know when the decoder was paused, buffering, etc.
+ // Do not pass zero.
+ void SetPlaybackRate(PRUint32 aBytesPerSecond);
+
+ // These methods must be called on a different thread from the main
+ // thread. They should always be called on the same thread for a given
+ // stream.
+ // This can fail when aWhence is NS_SEEK_END and no stream length
+ // is known.
+ nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+ PRInt64 Tell();
+ // *aBytes gets the number of bytes that were actually read. This can
+ // be less than aCount. If the first byte of data is not in the cache,
+ // this will block until the data is available or the stream is
+ // closed, otherwise it won't block.
+ nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+
+private:
+ friend class nsMediaCache;
+
+ /**
+ * A doubly-linked list of blocks. Each block can belong to at most
+ * one list at a time. Add/Remove/Get methods are all constant time.
+ * We declare this here so that a stream can contain a BlockList of its
+ * read-ahead blocks. Blocks are referred to by index into the
+ * nsMediaCache::mIndex array.
+ */
+ class BlockList {
+ public:
+ BlockList() : mFirstBlock(-1), mCount(0) {}
+ ~BlockList() {
+ NS_ASSERTION(mFirstBlock == -1 && mCount == 0,
+ "Destroying non-empty block list");
+ }
+ void AddFirstBlock(PRInt32 aBlock);
+ void AddAfter(PRInt32 aBlock, PRInt32 aBefore);
+ void RemoveBlock(PRInt32 aBlock);
+ // Returns the first block in the list, or -1 if empty
+ PRInt32 GetFirstBlock() const { return mFirstBlock; }
+ // Returns the last block in the list, or -1 if empty
+ PRInt32 GetLastBlock() const;
+ PRBool IsEmpty() const { return mFirstBlock < 0; }
+ PRInt32 GetCount() const { return mCount; }
+ // The contents of aBlockIndex1 and aBlockIndex2 have been swapped;
+ // update mFirstBlock if it refers to either of these
+ void NotifyBlockSwapped(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2);
+#ifdef DEBUG
+ // Verify linked-list invariants
+ void Verify();
+#else
+ void Verify() {}
+#endif
+
+ private:
+ // The index of the first block in the list, or -1 if the list is empty.
+ PRInt32 mFirstBlock;
+ // The number of blocks in the list.
+ PRInt32 mCount;
+ };
+
+ // Returns the end of the bytes starting at the given offset
+ // which are in cache.
+ // This method assumes that the cache monitor is held and can be called on
+ // any thread.
+ PRInt64 GetCachedDataEndInternal(PRInt64 aOffset);
+ // A helper function to do the work of closing the stream. Assumes
+ // that the cache monitor is held. Main thread only.
+ // aMonitor is the nsAutoMonitor wrapper holding the cache monitor.
+ // This is used to NotifyAll to wake up threads that might be
+ // blocked on reading from this stream.
+ void CloseInternal(nsAutoMonitor* aMonitor);
+
+ // This field is main-thread-only.
+ nsMediaChannelStream* mClient;
+
+ // All other fields are all protected by the cache's monitor and
+ // can be accessed by by any thread.
+ // The offset where the next data from the channel will arrive
+ PRInt64 mChannelOffset;
+ // The offset where the reader is positioned in the stream
+ PRInt64 mStreamOffset;
+ // The reported or discovered length of the data, or -1 if nothing is
+ // known
+ PRInt64 mStreamLength;
+ // For each block in the stream data, maps to the cache entry for the
+ // block, or -1 if the block is not cached.
+ nsTArray<PRInt32> mBlocks;
+ // The list of read-ahead blocks, ordered by stream offset; the first
+ // block is the earliest in the stream (so the last block will be the
+ // least valuable).
+ BlockList mReadaheadBlocks;
+ // The last reported estimate of the decoder's playback rate
+ PRUint32 mPlaybackBytesPerSecond;
+ // The number of times this stream has been Pinned without a
+ // corresponding Unpin
+ PRUint32 mPinCount;
+ // The last reported read mode
+ ReadMode mCurrentMode;
+ // Set to true when the stream has been closed either explicitly or
+ // due to an internal cache error
+ PRPackedBool mClosed;
+ // The last reported seekability state for the underlying channel
+ PRPackedBool mIsSeekable;
+ // true if the cache has suspended our channel because the cache is
+ // full and the priority of the data that would be received is lower
+ // than the priority of the data already in the cache
+ PRPackedBool mCacheSuspended;
+ // true if some data in mPartialBlockBuffer has been read as metadata
+ PRPackedBool mMetadataInPartialBlockBuffer;
+
+ // Data received for the block containing mChannelOffset. Data needs
+ // to wait here so we can write back a complete block. The first
+ // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
+ // the rest are garbage.
+ // Use PRInt64 so that the data is well-aligned.
+ PRInt64 mPartialBlockBuffer[BLOCK_SIZE/sizeof(PRInt64)];
+};
+
+#endif
--- a/content/media/video/public/nsMediaDecoder.h
+++ b/content/media/video/public/nsMediaDecoder.h
@@ -72,17 +72,17 @@ class nsMediaDecoder : public nsIObserve
// Return PR_TRUE on successful initialisation, PR_FALSE
// on failure.
virtual PRBool Init(nsHTMLMediaElement* aElement);
// Return the current URI being played or downloaded.
virtual void GetCurrentURI(nsIURI** aURI) = 0;
// Return the principal of the current URI being played or downloaded.
- virtual nsIPrincipal* GetCurrentPrincipal() = 0;
+ virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
// Return the time position in the video stream being
// played measured in seconds.
virtual float GetCurrentTime() = 0;
// Seek to the time position in (seconds) from the start of the video.
virtual nsresult Seek(float time) = 0;
@@ -134,25 +134,23 @@ class nsMediaDecoder : public nsIObserve
// Return PR_TRUE if the decoder has reached the end of playback.
// Call in the main thread only.
virtual PRBool IsEnded() const = 0;
struct Statistics {
// Estimate of the current playback rate (bytes/second).
double mPlaybackRate;
- // Estimate of the current download rate (bytes/second)
+ // Estimate of the current download rate (bytes/second). This
+ // ignores time that the channel was paused by Gecko.
double mDownloadRate;
// Total length of media stream in bytes; -1 if not known
PRInt64 mTotalBytes;
- // Current position of the download, in bytes. This position (and
- // the other positions) should only increase unless the current
- // playback position is explicitly changed. This may require
- // some fudging by the decoder if operations like seeking or finding the
- // duration require seeks in the underlying stream.
+ // Current position of the download, in bytes. This is the offset of
+ // the first uncached byte after the decoder position.
PRInt64 mDownloadPosition;
// Current position of decoding, in bytes (how much of the stream
// has been consumed)
PRInt64 mDecoderPosition;
// Current position of playback, in bytes
PRInt64 mPlaybackPosition;
// If false, then mDownloadRate cannot be considered a reliable
// estimate (probably because the download has only been running
@@ -165,19 +163,16 @@ class nsMediaDecoder : public nsIObserve
};
// Return statistics. This is used for progress events and other things.
// This can be called from any thread. It's only a snapshot of the
// current state, since other threads might be changing the state
// at any time.
virtual Statistics GetStatistics() = 0;
- // Set the size of the video file in bytes.
- virtual void SetTotalBytes(PRInt64 aBytes) = 0;
-
// Set the duration of the media resource in units of milliseconds.
// This is called via a channel listener if it can pick up the duration
// from a content header. Must be called from the main thread only.
virtual void SetDuration(PRInt64 aDuration) = 0;
// Set a flag indicating whether seeking is supported
virtual void SetSeekable(PRBool aSeekable) = 0;
@@ -188,43 +183,31 @@ class nsMediaDecoder : public nsIObserve
virtual void Invalidate();
// Fire progress events if needed according to the time and byte
// constraints outlined in the specification. aTimer is PR_TRUE
// if the method is called as a result of the progress timer rather
// than the result of downloaded data.
virtual void Progress(PRBool aTimer);
- // Called by nsMediaStream when a seek operation happens (could be
- // called either before or after the seek completes). Called on the main
- // thread. This may be called as a result of the stream opening (the
- // offset should be zero in that case).
- // Reads from streams after a seek MUST NOT complete before
- // NotifyDownloadSeeked has been delivered. (We assume the reads
- // and the seeks happen on the same calling thread.)
- virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes) = 0;
-
- // Called by nsChannelToPipeListener or nsMediaStream when data has
- // been received.
- // Call on the main thread only. aBytes of data have just been received.
- // Reads from streams MUST NOT complete before the NotifyBytesDownloaded
- // for those bytes has been delivered. (We assume reads and seeks
- // happen on the same calling thread.)
- virtual void NotifyBytesDownloaded(PRInt64 aBytes) = 0;
+ // Called by nsMediaStream when the "cache suspended" status changes.
+ // If nsMediaStream::IsSuspendedByCache returns true, then the decoder
+ // should stop buffering or otherwise waiting for download progress and
+ // start consuming data, if possible, because the cache is full.
+ virtual void NotifySuspendedStatusChanged() = 0;
+
+ // Called by nsMediaStream when some data has been received.
+ // Call on the main thread only.
+ virtual void NotifyBytesDownloaded() = 0;
// Called by nsChannelToPipeListener or nsMediaStream when the
// download has ended. Called on the main thread only. aStatus is
// the result from OnStopRequest.
virtual void NotifyDownloadEnded(nsresult aStatus) = 0;
- // Called by nsMediaStream when data has been read from the stream
- // for playback.
- // Call on any thread. aBytes of data have just been consumed.
- virtual void NotifyBytesConsumed(PRInt64 aBytes) = 0;
-
// Cleanup internal data structures. Must be called on the main
// thread by the owning object before that object disposes of this object.
virtual void Shutdown();
// Suspend any media downloads that are in progress. Called by the
// media element when it is sent to the bfcache. Call on the main
// thread only.
virtual void Suspend() = 0;
@@ -249,80 +232,16 @@ protected:
// Set the RGB width, height and framerate. Ownership of the passed RGB
// buffer is transferred to the decoder. This is the only nsMediaDecoder
// method that may be called from threads other than the main thread.
void SetRGBData(PRInt32 aWidth,
PRInt32 aHeight,
float aFramerate,
unsigned char* aRGBBuffer);
- /**
- * This class is useful for estimating rates of data passing through
- * some channel. The idea is that activity on the channel "starts"
- * and "stops" over time. At certain times data passes through the
- * channel (usually while the channel is active; data passing through
- * an inactive channel is ignored). The GetRate() function computes
- * an estimate of the "current rate" of the channel, which is some
- * kind of average of the data passing through over the time the
- * channel is active.
- *
- * Timestamps and time durations are measured in PRIntervalTimes, but
- * all methods take "now" as a parameter so the user of this class can
- * define what the timeline means.
- */
- class ChannelStatistics {
- public:
- ChannelStatistics() { Reset(); }
- void Reset() {
- mLastStartTime = mAccumulatedTime = 0;
- mAccumulatedBytes = 0;
- mIsStarted = PR_FALSE;
- }
- void Start(PRIntervalTime aNow) {
- if (mIsStarted)
- return;
- mLastStartTime = aNow;
- mIsStarted = PR_TRUE;
- }
- void Stop(PRIntervalTime aNow) {
- if (!mIsStarted)
- return;
- mAccumulatedTime += aNow - mLastStartTime;
- mIsStarted = PR_FALSE;
- }
- void AddBytes(PRInt64 aBytes) {
- if (!mIsStarted) {
- // ignore this data, it may be related to seeking or some other
- // operation we don't care about
- return;
- }
- mAccumulatedBytes += aBytes;
- }
- double GetRateAtLastStop(PRPackedBool* aReliable) {
- *aReliable = mAccumulatedTime >= PR_TicksPerSecond();
- return double(mAccumulatedBytes)*PR_TicksPerSecond()/mAccumulatedTime;
- }
- double GetRate(PRIntervalTime aNow, PRPackedBool* aReliable) {
- PRIntervalTime time = mAccumulatedTime;
- if (mIsStarted) {
- time += aNow - mLastStartTime;
- }
- *aReliable = time >= PR_TicksPerSecond();
- NS_ASSERTION(time >= 0, "Time wraparound?");
- if (time <= 0)
- return 0.0;
- return double(mAccumulatedBytes)*PR_TicksPerSecond()/time;
- }
- private:
- PRInt64 mAccumulatedBytes;
- PRIntervalTime mAccumulatedTime;
- PRIntervalTime mLastStartTime;
- PRPackedBool mIsStarted;
- };
-
protected:
// Timer used for updating progress events
nsCOMPtr<nsITimer> mProgressTimer;
// The element is not reference counted. Instead the decoder is
// notified when it is able to be used. It should only ever be
// accessed from the main thread.
nsHTMLMediaElement* mElement;
--- a/content/media/video/public/nsMediaStream.h
+++ b/content/media/video/public/nsMediaStream.h
@@ -29,76 +29,157 @@
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
+
#if !defined(nsMediaStream_h_)
#define nsMediaStream_h_
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsIChannel.h"
#include "nsIPrincipal.h"
#include "nsIURI.h"
#include "nsIStreamListener.h"
#include "prlock.h"
+#include "nsMediaCache.h"
// For HTTP seeking, if number of bytes needing to be
// seeked forward is less than this value then a read is
// done rather than a byte range request.
#define SEEK_VS_READ_THRESHOLD (32*1024)
class nsMediaDecoder;
+/**
+ * This class is useful for estimating rates of data passing through
+ * some channel. The idea is that activity on the channel "starts"
+ * and "stops" over time. At certain times data passes through the
+ * channel (usually while the channel is active; data passing through
+ * an inactive channel is ignored). The GetRate() function computes
+ * an estimate of the "current rate" of the channel, which is some
+ * kind of average of the data passing through over the time the
+ * channel is active.
+ *
+ * Timestamps and time durations are measured in PRIntervalTimes, but
+ * all methods take "now" as a parameter so the user of this class can
+ * define what the timeline means.
+ */
+class nsChannelStatistics {
+public:
+ nsChannelStatistics() { Reset(); }
+ void Reset() {
+ mLastStartTime = mAccumulatedTime = 0;
+ mAccumulatedBytes = 0;
+ mIsStarted = PR_FALSE;
+ }
+ void Start(PRIntervalTime aNow) {
+ if (mIsStarted)
+ return;
+ mLastStartTime = aNow;
+ mIsStarted = PR_TRUE;
+ }
+ void Stop(PRIntervalTime aNow) {
+ if (!mIsStarted)
+ return;
+ mAccumulatedTime += aNow - mLastStartTime;
+ mIsStarted = PR_FALSE;
+ }
+ void AddBytes(PRInt64 aBytes) {
+ if (!mIsStarted) {
+ // ignore this data, it may be related to seeking or some other
+ // operation we don't care about
+ return;
+ }
+ mAccumulatedBytes += aBytes;
+ }
+ double GetRateAtLastStop(PRPackedBool* aReliable) {
+ *aReliable = mAccumulatedTime >= PR_TicksPerSecond();
+ return double(mAccumulatedBytes)*PR_TicksPerSecond()/mAccumulatedTime;
+ }
+ double GetRate(PRIntervalTime aNow, PRPackedBool* aReliable) {
+ PRIntervalTime time = mAccumulatedTime;
+ if (mIsStarted) {
+ time += aNow - mLastStartTime;
+ }
+ *aReliable = time >= PR_TicksPerSecond();
+ NS_ASSERTION(time >= 0, "Time wraparound?");
+ if (time <= 0)
+ return 0.0;
+ return double(mAccumulatedBytes)*PR_TicksPerSecond()/time;
+ }
+private:
+ PRInt64 mAccumulatedBytes;
+ PRIntervalTime mAccumulatedTime;
+ PRIntervalTime mLastStartTime;
+ PRPackedBool mIsStarted;
+};
+
/*
Provides the ability to open, read and seek into a media stream
(audio, video). Handles the underlying machinery to do range
requests, etc as needed by the actual stream type. Instances of
this class must be created on the main thread.
- Open, Close and Cancel must be called on the main thread only. Once
- the stream is open the remaining methods (except for Close and
- Cancel) may be called on another thread which may be a non main
+ Most methods must be called on the main thread only. Read, Seek and
+ Tell may be called on another thread which may be a non main
thread. They may not be called on multiple other threads though. In
the case of the Ogg Decoder they are called on the Decode thread
for example. You must ensure that no threads are calling these
methods once Close is called.
-
+
Instances of this class are explicitly managed. 'delete' it when done.
*/
class nsMediaStream
{
public:
virtual ~nsMediaStream()
{
- PR_DestroyLock(mLock);
MOZ_COUNT_DTOR(nsMediaStream);
}
+ // The following can be called on the main thread only:
+ // Get the current principal for the channel
+ already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
+ // Get the decoder
+ nsMediaDecoder* Decoder() { return mDecoder; }
// Close the stream, stop any listeners, channels, etc.
- // Call on main thread only.
+ // Cancels any currently blocking Read request and forces that request to
+ // return an error.
virtual nsresult Close() = 0;
+ // Suspend any downloads that are in progress.
+ virtual void Suspend() = 0;
+ // Resume any downloads that have been suspended.
+ virtual void Resume() = 0;
+
+ // These methods are called off the main thread.
+ // The mode is initially MODE_PLAYBACK.
+ virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) = 0;
+ // This is the client's estimate of the playback rate assuming
+ // the media plays continuously. The cache can't guess this itself
+ // because it doesn't know when the decoder was paused, buffering, etc.
+ virtual void SetPlaybackRate(PRUint32 aBytesPerSecond) = 0;
// Read up to aCount bytes from the stream. The buffer must have
// enough room for at least aCount bytes. Stores the number of
- // actual bytes read in aBytes (0 on end of file). Can be called
- // from any thread. May read less than aCount bytes if the number of
+ // actual bytes read in aBytes (0 on end of file).
+ // May read less than aCount bytes if the number of
// available bytes is less than aCount. Always check *aBytes after
// read, and call again if necessary.
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
// Seek to the given bytes offset in the stream. aWhence can be
// one of:
// NS_SEEK_SET
// NS_SEEK_CUR
// NS_SEEK_END
//
- // Can be called from any thread.
// In the Http strategy case the cancel will cause the http
// channel's listener to close the pipe, forcing an i/o error on any
// blocked read. This will allow the decode thread to complete the
// event.
//
// In the case of a seek in progress, the byte range request creates
// a new listener. This is done on the main thread via seek
// synchronously dispatching an event. This avoids the issue of us
@@ -111,52 +192,65 @@ public:
// The default strategy does not do any seeking - the only issue is
// a blocked read which it handles by causing the listener to close
// the pipe, as per the http case.
//
// The file strategy doesn't block for any great length of time so
// is fine for a no-op cancel.
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
// Report the current offset in bytes from the start of the stream.
- // Can be called from any thread.
- virtual PRInt64 Tell() = 0;
- // Cancels any currently blocking request and forces that request to
- // return an error. Call on main thread only.
- virtual void Cancel() { }
- // Call on main thread only.
- virtual nsIPrincipal* GetCurrentPrincipal() = 0;
- // Suspend any downloads that are in progress. Call on the main thread
- // only.
- virtual void Suspend() = 0;
- // Resume any downloads that have been suspended. Call on the main thread
- // only.
- virtual void Resume() = 0;
+ virtual PRInt64 Tell() = 0;
- nsMediaDecoder* Decoder() { return mDecoder; }
+ // These can be called on any thread.
+ // Cached blocks associated with this stream will not be evicted
+ // while the stream is pinned.
+ virtual void Pin() = 0;
+ virtual void Unpin() = 0;
+ // Get the estimated download rate in bytes per second (assuming no
+ // pausing of the channel is requested by Gecko).
+ // *aIsReliable is set to true if we think the estimate is useful.
+ virtual double GetDownloadRate(PRPackedBool* aIsReliable) = 0;
+ // Get the length of the stream in bytes. Returns -1 if not known.
+ // This can change over time; after a seek operation, a misbehaving
+ // server may give us a resource of a different length to what it had
+ // reported previously --- or it may just lie in its Content-Length
+ // header and give us more or less data than it reported. We will adjust
+ // the result of GetLength to reflect the data that's actually arriving.
+ virtual PRInt64 GetLength() = 0;
+ // Returns the end of the bytes starting at the given offset
+ // which are in cache.
+ virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) = 0;
+ // Returns true if all the data from aOffset to the end of the stream
+ // is in cache. If the end of the stream is not known, we return false.
+ virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset) = 0;
+ // Returns true if this stream is suspended by the cache because the
+ // cache is full. If true then the decoder should try to start consuming
+ // data, otherwise we may not be able to make progress.
+ // nsMediaDecoder::NotifySuspendedStatusChanged is called when this
+ // changes.
+ virtual PRBool IsSuspendedByCache() = 0;
/**
* Create a stream, reading data from the
* media resource at the URI. Call on main thread only.
* @param aChannel if non-null, this channel is used and aListener
* is set to the listener we want for the channel. aURI must
* be the URI for the channel, obtained via NS_GetFinalChannelURI.
*/
static nsresult Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
nsIChannel* aChannel, nsMediaStream** aStream,
nsIStreamListener** aListener);
protected:
nsMediaStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
mDecoder(aDecoder),
mChannel(aChannel),
- mURI(aURI),
- mLock(nsnull)
+ mURI(aURI)
{
MOZ_COUNT_CTOR(nsMediaStream);
- mLock = PR_NewLock();
}
/**
* @param aStreamListener if null, the strategy should open mChannel
* itself. Otherwise, mChannel is already open and the strategy
* should just return its stream listener in aStreamListener (or set
* *aStreamListener to null, if it doesn't need a listener).
*/
@@ -169,17 +263,111 @@ protected:
// Channel used to download the media data. Must be accessed
// from the main thread only.
nsCOMPtr<nsIChannel> mChannel;
// URI in case the stream needs to be re-opened. Access from
// main thread only.
nsCOMPtr<nsIURI> mURI;
+};
- // This lock handles synchronisation between calls to Close() and
- // the Read, Seek, etc calls. Close must not be called while a
- // Read or Seek is in progress since it resets various internal
- // values to null.
+/**
+ * This is the nsMediaStream implementation that wraps Necko channels.
+ * Much of its functionality is actually delegated to nsMediaCache via
+ * an underlying nsMediaCacheStream.
+ *
+ * All synchronization is performed by nsMediaCacheStream; all off-main-
+ * thread operations are delegated directly to that object.
+ */
+class nsMediaChannelStream : public nsMediaStream
+{
+public:
+ nsMediaChannelStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI);
+ ~nsMediaChannelStream();
+
+ // These are called on the main thread by nsMediaCache. These must
+ // not block or grab locks.
+ // 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.
+ // This can fail.
+ nsresult CacheClientSeek(PRInt64 aOffset);
+ // Suspend the current load since data is currently not wanted
+ nsresult CacheClientSuspend();
+ // Resume the current load since data is wanted again
+ nsresult CacheClientResume();
+
+ // Main thread
+ virtual nsresult Open(nsIStreamListener** aStreamListener);
+ virtual nsresult Close();
+ virtual void Suspend();
+ virtual void Resume();
+ // Return PR_TRUE if the stream has been closed.
+ PRBool IsClosed() const { return mCacheStream.IsClosed(); }
+
+ // Other thread
+ virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode);
+ virtual void SetPlaybackRate(PRUint32 aBytesPerSecond);
+ virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
+ virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
+ virtual PRInt64 Tell();
+
+ // Any thread
+ virtual void Pin();
+ virtual void Unpin();
+ virtual double GetDownloadRate(PRPackedBool* aIsReliable);
+ virtual PRInt64 GetLength();
+ virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset);
+ virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset);
+ virtual PRBool IsSuspendedByCache();
+
+protected:
+ class Listener : public nsIStreamListener {
+ public:
+ Listener(nsMediaChannelStream* aStream) : mStream(aStream) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ void Revoke() { mStream = nsnull; }
+
+ private:
+ nsMediaChannelStream* mStream;
+ };
+ friend class Listener;
+
+ // These are called on the main thread by Listener.
+ nsresult OnStartRequest(nsIRequest* aRequest);
+ nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
+ nsresult OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ PRUint32 aCount);
+
+ // Opens the channel, using an HTTP byte range request to start at aOffset
+ // if possible. Main thread only.
+ nsresult OpenChannel(nsIStreamListener** aStreamListener, PRInt64 aOffset);
+ // Closes the channel. Main thread only.
+ void CloseChannel();
+
+ static NS_METHOD CopySegmentToCache(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ PRUint32 aToOffset,
+ PRUint32 aCount,
+ PRUint32 *aWriteCount);
+
+ // Main thread access only
+ nsRefPtr<Listener> mListener;
+ PRUint32 mSuspendCount;
+ PRPackedBool mSeeking;
+
+ // Any thread access
+ nsMediaCacheStream mCacheStream;
+
+ // This lock protects mChannelStatistics and mCacheSuspendCount
PRLock* mLock;
+ nsChannelStatistics mChannelStatistics;
+ PRUint32 mCacheSuspendCount;
};
#endif
--- a/content/media/video/public/nsOggDecoder.h
+++ b/content/media/video/public/nsOggDecoder.h
@@ -317,22 +317,23 @@ class nsOggDecoder : public nsMediaDecod
virtual nsresult PlaybackRateChanged();
virtual void Pause();
virtual void SetVolume(float volume);
virtual float GetDuration();
virtual void GetCurrentURI(nsIURI** aURI);
- virtual nsIPrincipal* GetCurrentPrincipal();
+ virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
- virtual void NotifyBytesDownloaded(PRInt64 aBytes);
- virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes);
+ virtual void NotifySuspendedStatusChanged();
+ virtual void NotifyBytesDownloaded();
virtual void NotifyDownloadEnded(nsresult aStatus);
- virtual void NotifyBytesConsumed(PRInt64 aBytes);
+ // Called by nsChannelReader on the decoder thread
+ void NotifyBytesConsumed(PRInt64 aBytes);
// Called when the video file has completed downloading.
// Call on the main thread only.
void ResourceLoaded();
// Called if the media file encounters a network error.
// Call on the main thread only.
virtual void NetworkError();
@@ -340,19 +341,16 @@ class nsOggDecoder : public nsMediaDecod
// Call from any thread safely. Return PR_TRUE if we are currently
// seeking in the media resource.
virtual PRBool IsSeeking() const;
// Return PR_TRUE if the decoder has reached the end of playback.
// Call on the main thread only.
virtual PRBool IsEnded() const;
- // Get the size of the media file in bytes. Called on the main thread only.
- virtual void SetTotalBytes(PRInt64 aBytes);
-
// Set the duration of the media resource in units of milliseconds.
// This is called via a channel listener if it can pick up the duration
// from a content header. Must be called from the main thread only.
virtual void SetDuration(PRInt64 aDuration);
// Set a flag indicating whether seeking is supported
virtual void SetSeekable(PRBool aSeekable);
@@ -394,16 +392,23 @@ protected:
// when seeking to prevent wild changes to the progress notification.
// Must be called with the decoder monitor held.
void StopProgressUpdates();
// Allow updating the bytes downloaded for progress notifications. Must
// be called with the decoder monitor held.
void StartProgressUpdates();
+ // Something has changed that could affect the computed playback rate,
+ // so recompute it. The monitor must be held.
+ void UpdatePlaybackRate();
+
+ // The actual playback rate computation. The monitor must be held.
+ double ComputePlaybackRate(PRPackedBool* aReliable);
+
/******
* The following methods must only be called on the main
* thread.
******/
// Change to a new play state. This updates the mState variable and
// notifies any thread blocking on this object's monitor of the
// change. Call on the main thread only.
@@ -433,49 +438,44 @@ protected:
// position. It dispatches a timeupdate event and invalidates the frame.
// This must be called on the main thread only.
void PlaybackPositionChanged();
// Calls mElement->UpdateReadyStateForData, telling it whether we have
// data for the next frame and if we're buffering. Main thread only.
void UpdateReadyStateForData();
+ // Find the end of the cached data starting at the current decoder
+ // position.
+ PRInt64 GetDownloadPosition();
+
private:
// Register/Unregister with Shutdown Observer.
// Call on main thread only.
void RegisterShutdownObserver();
void UnregisterShutdownObserver();
/******
* The following members should be accessed with the decoder lock held.
******/
- // Size of the media file in bytes. Set on the first
- // HTTP request from nsChannelToPipe Listener. -1 if not known.
- PRInt64 mTotalBytes;
- // Current download position in the stream.
- PRInt64 mDownloadPosition;
- // Download position to report if asked. This is the same as
- // mDownloadPosition normally, but we don't update it while ignoring
- // progress. This lets us avoid reporting progress changes due to reads
- // that are only servicing our seek operations.
- PRInt64 mProgressPosition;
// Current decoding position in the stream. This is where the decoder
- // is up to consuming the stream.
+ // is up to consuming the stream. This is not adjusted during decoder
+ // seek operations, but it's updated at the end when we start playing
+ // back again.
PRInt64 mDecoderPosition;
// Current playback position in the stream. This is (approximately)
- // where we're up to playing back the stream.
+ // where we're up to playing back the stream. This is not adjusted
+ // during decoder seek operations, but it's updated at the end when we
+ // start playing back again.
PRInt64 mPlaybackPosition;
- // Data needed to estimate download data rate. The timeline used for
- // this estimate is wall-clock time.
- ChannelStatistics mDownloadStatistics;
// Data needed to estimate playback data rate. The timeline used for
// this estimate is "decode time" (where the "current time" is the
// time of the last decoded video frame).
- ChannelStatistics mPlaybackStatistics;
+ nsChannelStatistics mPlaybackStatistics;
// The URI of the current resource
nsCOMPtr<nsIURI> mURI;
// Thread to handle decoding of Ogg data.
nsCOMPtr<nsIThread> mDecodeThread;
// The current playback position of the media resource in units of
--- a/content/media/video/public/nsWaveDecoder.h
+++ b/content/media/video/public/nsWaveDecoder.h
@@ -142,17 +142,17 @@ class nsWaveDecoder : public nsMediaDeco
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
public:
nsWaveDecoder();
~nsWaveDecoder();
virtual void GetCurrentURI(nsIURI** aURI);
- virtual nsIPrincipal* GetCurrentPrincipal();
+ virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
// Return the current playback position in the media in seconds.
virtual float GetCurrentTime();
// Return the total playback length of the media in seconds.
virtual float GetDuration();
// Set the audio playback volume; must be in range [0.0, 1.0].
@@ -183,25 +183,22 @@ class nsWaveDecoder : public nsMediaDeco
// Called by mStream (and possibly the nsChannelToPipeListener used
// internally by mStream) if the stream encounters a network error.
virtual void NetworkError();
// Element is notifying us that the requested playback rate has changed.
virtual nsresult PlaybackRateChanged();
- virtual void NotifyBytesDownloaded(PRInt64 aBytes);
- virtual void NotifyDownloadSeeked(PRInt64 aOffset);
+ virtual void NotifySuspendedStatusChanged();
+ virtual void NotifyBytesDownloaded();
virtual void NotifyDownloadEnded(nsresult aStatus);
- virtual void NotifyBytesConsumed(PRInt64 aBytes);
virtual Statistics GetStatistics();
- virtual void SetTotalBytes(PRInt64 aBytes);
-
void PlaybackPositionChanged();
// Setter for the duration. This is ignored by the wave decoder since it can
// compute the duration directly from the wave data.
virtual void SetDuration(PRInt64 aDuration);
// Getter/setter for mSeekable.
virtual void SetSeekable(PRBool aSeekable);
@@ -255,23 +252,16 @@ private:
// State machine that runs on mPlaybackThread. Methods on this object are
// safe to call from any thread.
nsCOMPtr<nsWaveStateMachine> mPlaybackStateMachine;
// Threadsafe wrapper around channels that provides seeking based on the
// underlying channel type.
nsAutoPtr<nsMediaStream> mStream;
- // The media time of the last requested seek. This has not been validated
- // against the current media, so may be out of bounds. Set when
- // Seek(float) is called, and passed to the state machine when the
- // SeekStarted event fires to tell it to update its time offset. The
- // state machine will validate the offset against the current media.
- float mTimeOffset;
-
// The current playback position of the media resource in units of
// seconds. This is updated every time a block of audio is passed to the
// backend (unless an prior update is still pending). It is read and
// written from the main thread only.
float mCurrentTime;
// Copy of the current time, duration, and ended state when the state
// machine was disposed. Used to respond to time and duration queries
--- a/content/media/video/src/Makefile.in
+++ b/content/media/video/src/Makefile.in
@@ -75,24 +75,19 @@ REQUIRES = \
intl \
plugin \
cairo \
libpixman \
$(NULL)
CPPSRCS = \
nsMediaDecoder.cpp \
- $(NULL)
-
-ifdef MOZ_MEDIA
-CPPSRCS += \
- nsChannelToPipeListener.cpp \
+ nsMediaCache.cpp \
nsMediaStream.cpp \
$(NULL)
-endif
ifdef MOZ_SYDNEYAUDIO
CPPSRCS += \
nsAudioStream.cpp \
$(NULL)
endif
ifdef MOZ_OGG
@@ -111,10 +106,8 @@ endif
FORCE_STATIC_LIB = 1
include $(topsrcdir)/config/rules.mk
INCLUDES += \
-I$(srcdir)/../../../base/src \
-I$(srcdir)/../../../html/content/src \
$(NULL)
-
-
--- a/content/media/video/src/nsChannelReader.cpp
+++ b/content/media/video/src/nsChannelReader.cpp
@@ -38,42 +38,27 @@
#include "nsAString.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "prlog.h"
#include "nsOggDecoder.h"
#include "nsChannelReader.h"
#include "nsIScriptSecurityManager.h"
-void nsChannelReader::Cancel()
-{
- mStream->Cancel();
-}
-
OggPlayErrorCode nsChannelReader::initialise(int aBlock)
{
return E_OGGPLAY_OK;
}
OggPlayErrorCode nsChannelReader::destroy()
{
- mStream->Close();
+ // We don't have to do anything here, the decoder will clean stuff up
return E_OGGPLAY_OK;
}
-void nsChannelReader::Suspend()
-{
- mStream->Suspend();
-}
-
-void nsChannelReader::Resume()
-{
- mStream->Resume();
-}
-
void nsChannelReader::SetDuration(PRInt64 aDuration)
{
mDuration = aDuration;
}
size_t nsChannelReader::io_read(char* aBuffer, size_t aCount)
{
PRUint32 bytes = 0;
@@ -167,14 +152,8 @@ nsChannelReader::nsChannelReader() :
reader->initialise = &oggplay_channel_reader_initialise;
reader->destroy = &oggplay_channel_reader_destroy;
reader->seek = nsnull;
reader->io_read = &oggplay_channel_reader_io_read;
reader->io_seek = &oggplay_channel_reader_io_seek;
reader->io_tell = &oggplay_channel_reader_io_tell;
reader->duration = &oggplay_channel_reader_duration;
}
-
-nsIPrincipal*
-nsChannelReader::GetCurrentPrincipal()
-{
- return mStream->GetCurrentPrincipal();
-}
deleted file mode 100644
--- a/content/media/video/src/nsChannelToPipeListener.cpp
+++ /dev/null
@@ -1,236 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla code.
- *
- * The Initial Developer of the Original Code is the Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Chris Double <chris.double@double.co.nz>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-#include "nsAString.h"
-#include "nsNetUtil.h"
-#include "nsMediaDecoder.h"
-#include "nsIScriptSecurityManager.h"
-#include "nsChannelToPipeListener.h"
-#include "nsICachingChannel.h"
-#include "nsDOMError.h"
-#include "nsHTMLMediaElement.h"
-
-#define HTTP_OK_CODE 200
-#define HTTP_PARTIAL_RESPONSE_CODE 206
-
-nsChannelToPipeListener::nsChannelToPipeListener(
- nsMediaDecoder* aDecoder,
- PRBool aSeeking) :
- mDecoder(aDecoder),
- mSeeking(aSeeking)
-{
-}
-
-nsresult nsChannelToPipeListener::Init()
-{
- nsresult rv = NS_NewPipe(getter_AddRefs(mInput),
- getter_AddRefs(mOutput),
- 0,
- PR_UINT32_MAX);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return NS_OK;
-}
-
-void nsChannelToPipeListener::Stop()
-{
- mDecoder = nsnull;
- mInput = nsnull;
- mOutput = nsnull;
-}
-
-void nsChannelToPipeListener::Cancel()
-{
- if (mOutput)
- mOutput->Close();
-
- if (mInput)
- mInput->Close();
-}
-
-nsresult nsChannelToPipeListener::GetInputStream(nsIInputStream** aStream)
-{
- NS_IF_ADDREF(*aStream = mInput);
- return NS_OK;
-}
-
-nsresult nsChannelToPipeListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
-{
- nsHTMLMediaElement* element = mDecoder->GetMediaElement();
- NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
- if (element->ShouldCheckAllowOrigin()) {
- // If the request was cancelled by nsCrossSiteListenerProxy due to failing
- // the Access Control check, send an error through to the media element.
- nsresult status;
- nsresult rv = aRequest->GetStatus(&status);
- if (NS_FAILED(rv) || status == NS_ERROR_DOM_BAD_URI) {
- mDecoder->NetworkError();
- return NS_ERROR_DOM_BAD_URI;
- }
- }
-
- nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
- if (hc) {
- nsCAutoString ranges;
- hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
- ranges);
- PRBool acceptsRanges = ranges.EqualsLiteral("bytes");
-
- if (!mSeeking) {
- // Look for duration headers from known Ogg content systems. In the case
- // of multiple options for obtaining the duration the order of precedence is;
- // 1) The Media resource metadata if possible (done by the decoder itself).
- // 2) X-Content-Duration.
- // 3) x-amz-meta-content-duration.
- // 4) Perform a seek in the decoder to find the value.
- nsCAutoString durationText;
- PRInt32 ec = 0;
- nsresult rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
- if (NS_FAILED(rv)) {
- rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
- }
-
- if (NS_SUCCEEDED(rv)) {
- float duration = durationText.ToFloat(&ec);
- if (ec == NS_OK && duration >= 0) {
- mDecoder->SetDuration(PRInt64(NS_round(duration*1000)));
- }
- }
- }
-
- PRUint32 responseStatus = 0;
- hc->GetResponseStatus(&responseStatus);
- if (mSeeking && responseStatus == HTTP_OK_CODE) {
- // If we get an OK response but we were seeking, and therefore
- // expecting a partial response of HTTP_PARTIAL_RESPONSE_CODE,
- // seeking should still be possible if the server is sending
- // Accept-Ranges:bytes.
- mDecoder->SetSeekable(acceptsRanges);
- }
- else if (!mSeeking &&
- (responseStatus == HTTP_OK_CODE ||
- responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
- // We weren't seeking and got a valid response status,
- // set the length of the content.
- PRInt32 cl = 0;
- hc->GetContentLength(&cl);
- mDecoder->SetTotalBytes(cl);
-
- // If we get an HTTP_OK_CODE response to our byte range request,
- // and the server isn't sending Accept-Ranges:bytes then we don't
- // support seeking.
- mDecoder->SetSeekable(responseStatus == HTTP_PARTIAL_RESPONSE_CODE ||
- acceptsRanges);
- }
- }
-
- nsCOMPtr<nsICachingChannel> cc = do_QueryInterface(aRequest);
- if (cc) {
- PRBool fromCache = PR_FALSE;
- nsresult rv = cc->IsFromCache(&fromCache);
- if (NS_SUCCEEDED(rv) && !fromCache) {
- cc->SetCacheAsFile(PR_TRUE);
- }
- }
-
- /* Get our principal */
- nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
- if (chan) {
- nsCOMPtr<nsIScriptSecurityManager> secMan =
- do_GetService("@mozilla.org/scriptsecuritymanager;1");
- if (secMan) {
- nsresult rv = secMan->GetChannelPrincipal(chan,
- getter_AddRefs(mPrincipal));
- if (NS_FAILED(rv)) {
- return rv;
- }
- }
- }
-
- // Fires an initial progress event and sets up the stall counter so stall events
- // fire if no download occurs within the required time frame.
- mDecoder->Progress(PR_FALSE);
-
- return NS_OK;
-}
-
-nsresult nsChannelToPipeListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
-{
- mOutput = nsnull;
- if (mDecoder) {
- mDecoder->NotifyDownloadEnded(aStatus);
- }
- return NS_OK;
-}
-
-nsresult nsChannelToPipeListener::OnDataAvailable(nsIRequest* aRequest,
- nsISupports* aContext,
- nsIInputStream* aStream,
- PRUint32 aOffset,
- PRUint32 aCount)
-{
- if (!mOutput)
- return NS_ERROR_FAILURE;
-
- mDecoder->NotifyBytesDownloaded(aCount);
-
- do {
- PRUint32 bytes;
- nsresult rv = mOutput->WriteFrom(aStream, aCount, &bytes);
- if (NS_FAILED(rv))
- return rv;
-
- aCount -= bytes;
- } while (aCount);
-
- nsresult rv = mOutput->Flush();
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Fire a progress events according to the time and byte constraints outlined
- // in the spec.
- mDecoder->Progress(PR_FALSE);
- return NS_OK;
-}
-
-nsIPrincipal*
-nsChannelToPipeListener::GetCurrentPrincipal()
-{
- return mPrincipal;
-}
-
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsChannelToPipeListener, nsIRequestObserver, nsIStreamListener)
-
new file mode 100644
--- /dev/null
+++ b/content/media/video/src/nsMediaCache.cpp
@@ -0,0 +1,1744 @@
+ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Robert O'Callahan <robert@ocallahan.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsMediaCache.h"
+#include "nsAutoLock.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "prio.h"
+#include "nsThreadUtils.h"
+#include "nsMediaStream.h"
+#include "nsMathUtils.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* gMediaCacheLog;
+#define LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
+#else
+#define LOG(type, msg)
+#endif
+
+// Readahead blocks for non-seekable streams will be limited to this
+// fraction of the cache space. We don't normally evict such blocks
+// because replacing them requires a seek, but we need to make sure
+// they don't monopolize the cache.
+static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
+
+// Assume that any replaying or backward seeking will happen
+// this far in the future (in seconds). This is a random guess/estimate
+// penalty to account for the possibility that we might not replay at
+// all.
+static const PRUint32 REPLAY_DELAY = 30;
+
+// When looking for a reusable block, scan forward this many blocks
+// from the desired "best" block location to look for free blocks,
+// before we resort to scanning the whole cache. The idea is to try to
+// store runs of stream blocks close-to-consecutively in the cache if we
+// can.
+static const PRUint32 FREE_BLOCK_SCAN_LIMIT = 16;
+
+class nsMediaCache {
+public:
+ friend class nsMediaCacheStream::BlockList;
+ typedef nsMediaCacheStream::BlockList BlockList;
+ enum {
+ BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
+ };
+
+ nsMediaCache() : mMonitor(nsAutoMonitor::NewMonitor("media.cache")),
+ mFD(nsnull), mFDCurrentPos(0), mUpdateQueued(PR_FALSE)
+#ifdef DEBUG
+ , mInUpdate(PR_FALSE)
+#endif
+ {
+ MOZ_COUNT_CTOR(nsMediaCache);
+ }
+ ~nsMediaCache() {
+ NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
+ Truncate();
+ NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
+ if (mFD) {
+ PR_Close(mFD);
+ }
+ if (mMonitor) {
+ nsAutoMonitor::DestroyMonitor(mMonitor);
+ }
+ MOZ_COUNT_DTOR(nsMediaCache);
+ }
+
+ // Main thread only. Creates the backing cache file.
+ nsresult Init();
+ // Shut down the global cache if it's no longer needed. We shut down
+ // the cache as soon as there are no streams. This means that during
+ // normal operation we are likely to start up the cache and shut it down
+ // many times, but that's OK since starting it up is cheap and
+ // shutting it down cleans things up and releases disk space.
+ static void MaybeShutdown();
+
+ // Cache-file access methods. These are the lowest-level cache methods.
+ // mMonitor must be held; these can be called on any thread.
+ // This can return partial reads.
+ nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
+ PRInt32* aBytes);
+ // This will fail if all aLength bytes are not read
+ nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
+ // This will fail if all aLength bytes are not written
+ nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
+
+ // mMonitor must be held, called on main thread.
+ // These methods are used by the stream to set up and tear down streams,
+ // and to handle reads and writes.
+ // Add aStream to the list of streams.
+ void OpenStream(nsMediaCacheStream* aStream);
+ // Remove aStream from the list of streams.
+ void ReleaseStream(nsMediaCacheStream* aStream);
+ // Free all blocks belonging to aStream.
+ void ReleaseStreamBlocks(nsMediaCacheStream* aStream);
+ // Find a cache entry for this data, and write the data into it
+ void AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
+ nsMediaCacheStream::ReadMode aMode);
+
+ // mMonitor must be held; can be called on any thread
+ // Notify the cache that a seek has been requested. Some blocks may
+ // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
+ // This does not trigger channel seeks directly, the next Update()
+ // will do that if necessary. The caller will call QueueUpdate().
+ void NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset);
+ // Notify the cache that a block has been read from. This is used
+ // to update last-use times. The block may not actually have a
+ // cache entry yet since Read can read data from a stream's
+ // in-memory mPartialBlockBuffer while the block is only partly full,
+ // and thus hasn't yet been committed to the cache. The caller will
+ // call QueueUpdate().
+ void NoteBlockUsage(PRInt32 aBlockIndex, nsMediaCacheStream::ReadMode aMode,
+ PRIntervalTime aNow);
+
+ // This queues a call to Update() on the main thread.
+ void QueueUpdate();
+
+ // Updates the cache state asynchronously on the main thread:
+ // -- try to trim the cache back to its desired size, if necessary
+ // -- suspend channels that are going to read data that's lower priority
+ // than anything currently cached
+ // -- resume channels that are going to read data that's higher priority
+ // than something currently cached
+ // -- seek channels that need to seek to a new location
+ void Update();
+
+#ifdef DEBUG
+ // Verify invariants, especially block list invariants
+ void Verify();
+#else
+ void Verify() {}
+#endif
+
+ PRMonitor* Monitor() { return mMonitor; }
+
+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
+ // and return it. Can return -1 on OOM.
+ PRInt32 FindBlockForIncomingData(PRIntervalTime aNow, nsMediaCacheStream* aStream);
+ // Find a reusable block --- a free block, if there is one, otherwise
+ // the reusable block with the latest predicted-next-use, or -1 if
+ // there aren't any freeable blocks. Only block indices less than
+ // aMaxSearchBlockIndex are considered. If aForStream is non-null,
+ // then aForStream and aForStreamBlock indicate what media data will
+ // be placed; FindReusableBlock will favour returning free blocks
+ // near other blocks for that point in the stream.
+ PRInt32 FindReusableBlock(PRIntervalTime aNow,
+ nsMediaCacheStream* aForStream,
+ PRInt32 aForStreamBlock,
+ PRInt32 aMaxSearchBlockIndex);
+ // Given a list of blocks sorted with the most reusable blocks at the
+ // end, find the last block whose stream is not pinned (if any)
+ // and whose cache entry index is less than aBlockIndexLimit
+ // and append it to aResult.
+ void AppendMostReusableBlock(BlockList* aBlockList,
+ nsTArray<PRUint32>* aResult,
+ PRInt32 aBlockIndexLimit);
+
+ enum BlockClass {
+ // block belongs to mFreeBlockList because it's free
+ FREE_BLOCK,
+ // block belongs to mMetadataBlockList because data has been consumed
+ // from it in "metadata mode" --- in particular blocks read during
+ // Ogg seeks go into this class. These blocks may have played data
+ // in them too.
+ METADATA_BLOCK,
+ // block belongs to mPlayedBlockList because its offset is
+ // less than the stream's current reader position
+ PLAYED_BLOCK,
+ // block belongs to the stream's mReadaheadBlockList because its
+ // offset is greater than or equal to the stream's current
+ // reader position
+ READAHEAD_BLOCK
+ };
+
+ struct Block {
+ Block() : mStream(nsnull), mClass(FREE_BLOCK),
+ mNextBlock(-1), mPrevBlock(-1) {}
+
+ // The stream that owns this block, or null if the block is free.
+ nsMediaCacheStream* mStream;
+ // The block index in the stream. Valid only if mStream is non-null.
+ PRUint32 mStreamBlock;
+ // Time at which this block was last used. Valid only if
+ // mClass is METADATA_BLOCK or PLAYED_BLOCK.
+ PRIntervalTime mLastUseTime;
+ // The class is FREE_BLOCK if and only if mStream is null
+ BlockClass mClass;
+ // Next and previous blocks of this class (circular links, so
+ // always nonnegative)
+ PRInt32 mNextBlock;
+ PRInt32 mPrevBlock;
+ };
+
+ // Get the BlockList that the block should belong to given its
+ // current mClass and mStream
+ BlockList* GetListForBlock(Block* aBlock);
+ // Add the block to the free list, mark it FREE_BLOCK, and mark
+ // its stream (if any) as not having the block in cache
+ void FreeBlock(PRInt32 aBlock);
+ // Swap all metadata associated with the two blocks. The caller
+ // is responsible for swapping up any cache file state.
+ void SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2);
+ // Insert the block into the readahead block list for its stream
+ // at the right point in the list.
+ void InsertReadaheadBlock(PRInt32 aBlockIndex);
+
+ // Guess the duration until block aBlock will be next used
+ PRIntervalTime PredictNextUse(PRIntervalTime aNow, PRInt32 aBlock);
+ // Guess the duration until the next incoming data on aStream will be used
+ PRIntervalTime PredictNextUseForIncomingData(nsMediaCacheStream* aStream);
+
+ // Truncate the file and index array if there are free blocks at the
+ // end
+ void Truncate();
+
+ // This member is main-thread only. It contains all the streams.
+ nsTArray<nsMediaCacheStream*> mStreams;
+
+ // The monitor protects all the data members here. Also, off-main-thread
+ // readers that need to block will Wait() on this monitor. When new
+ // data becomes available in the cache, we NotifyAll() on this monitor.
+ PRMonitor* mMonitor;
+ // The Blocks describing the cache entries.
+ nsTArray<Block> mIndex;
+ // The file descriptor of the cache file. The file will be deleted
+ // by the operating system when this is closed.
+ PRFileDesc* mFD;
+ // The current file offset in the cache file.
+ PRInt64 mFDCurrentPos;
+ // The list of free blocks; they are not ordered.
+ BlockList mFreeBlocks;
+ // The list of metadata blocks; the first block is the most recently used
+ BlockList mMetadataBlocks;
+ // The list of played-back blocks; the first block is the most recently used
+ BlockList mPlayedBlocks;
+ // True if an event to run Update() has been queued but not processed
+ PRPackedBool mUpdateQueued;
+#ifdef DEBUG
+ PRPackedBool mInUpdate;
+#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)
+{
+ nsMediaCache::Block* block = &gMediaCache->mIndex[aBlock];
+ NS_ASSERTION(block->mNextBlock == -1 && block->mPrevBlock == -1,
+ "Block already in list");
+ if (mFirstBlock < 0) {
+ block->mNextBlock = block->mPrevBlock = aBlock;
+ } else {
+ block->mNextBlock = mFirstBlock;
+ block->mPrevBlock = gMediaCache->mIndex[mFirstBlock].mPrevBlock;
+ gMediaCache->mIndex[block->mNextBlock].mPrevBlock = aBlock;
+ gMediaCache->mIndex[block->mPrevBlock].mNextBlock = aBlock;
+ }
+ mFirstBlock = aBlock;
+ ++mCount;
+}
+
+void nsMediaCacheStream::BlockList::AddAfter(PRInt32 aBlock, PRInt32 aBefore)
+{
+ nsMediaCache::Block* block = &gMediaCache->mIndex[aBlock];
+ NS_ASSERTION(block->mNextBlock == -1 && block->mPrevBlock == -1,
+ "Block already in list");
+ NS_ASSERTION(mFirstBlock >= 0, "Can't AddAfter to an empty list");
+
+ block->mNextBlock = gMediaCache->mIndex[aBefore].mNextBlock;
+ block->mPrevBlock = aBefore;
+ gMediaCache->mIndex[block->mNextBlock].mPrevBlock = aBlock;
+ gMediaCache->mIndex[block->mPrevBlock].mNextBlock = aBlock;
+ ++mCount;
+}
+
+void nsMediaCacheStream::BlockList::RemoveBlock(PRInt32 aBlock)
+{
+ nsMediaCache::Block* block = &gMediaCache->mIndex[aBlock];
+ if (block->mNextBlock == aBlock) {
+ NS_ASSERTION(block->mPrevBlock == aBlock, "Linked list inconsistency");
+ NS_ASSERTION(mFirstBlock == aBlock, "Linked list inconsistency");
+ mFirstBlock = -1;
+ } else {
+ if (mFirstBlock == aBlock) {
+ mFirstBlock = block->mNextBlock;
+ }
+ gMediaCache->mIndex[block->mNextBlock].mPrevBlock = block->mPrevBlock;
+ gMediaCache->mIndex[block->mPrevBlock].mNextBlock = block->mNextBlock;
+ }
+ block->mPrevBlock = block->mNextBlock = -1;
+ --mCount;
+}
+
+PRInt32 nsMediaCacheStream::BlockList::GetLastBlock() const
+{
+ if (mFirstBlock < 0)
+ return -1;
+ return gMediaCache->mIndex[mFirstBlock].mPrevBlock;
+}
+
+#ifdef DEBUG
+void nsMediaCacheStream::BlockList::Verify()
+{
+ PRInt32 count = 0;
+ if (mFirstBlock >= 0) {
+ PRInt32 block = mFirstBlock;
+ nsMediaCache::Block* elem = gMediaCache->mIndex.Elements();
+ do {
+ NS_ASSERTION(elem[elem[block].mNextBlock].mPrevBlock == block,
+ "Bad prev link");
+ NS_ASSERTION(elem[elem[block].mPrevBlock].mNextBlock == block,
+ "Bad prev link");
+ block = gMediaCache->mIndex[block].mNextBlock;
+ ++count;
+ } while (block != mFirstBlock);
+ }
+ NS_ASSERTION(count == mCount, "Bad count");
+}
+#endif
+
+static void UpdateSwappedBlockIndex(PRInt32* aBlockIndex,
+ PRInt32 aBlock1Index, PRInt32 aBlock2Index)
+{
+ PRInt32 index = *aBlockIndex;
+ if (index == aBlock1Index) {
+ *aBlockIndex = aBlock2Index;
+ } else if (index == aBlock2Index) {
+ *aBlockIndex = aBlock1Index;
+ }
+}
+
+void
+nsMediaCacheStream::BlockList::NotifyBlockSwapped(PRInt32 aBlockIndex1,
+ PRInt32 aBlockIndex2)
+{
+ UpdateSwappedBlockIndex(&mFirstBlock, aBlockIndex1, aBlockIndex2);
+}
+
+nsresult
+nsMediaCache::Init()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (!mMonitor) {
+ // the constructor failed
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIFile> tmp;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp));
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsILocalFile> tmpFile = do_QueryInterface(tmp);
+ if (!tmpFile)
+ return NS_ERROR_FAILURE;
+ rv = tmpFile->AppendNative(nsDependentCString("moz_media_cache"));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsILocalFile::DELETE_ON_CLOSE,
+ PR_IRWXU, &mFD);
+ if (NS_FAILED(rv))
+ return rv;
+
+#ifdef PR_LOGGING
+ if (!gMediaCacheLog) {
+ gMediaCacheLog = PR_NewLogModule("nsMediaCache");
+ }
+#endif
+
+ return NS_OK;
+}
+
+void
+nsMediaCache::MaybeShutdown()
+{
+ NS_ASSERTION(NS_IsMainThread(),
+ "nsMediaCache::MaybeShutdown called on non-main thread");
+ if (!gMediaCache->mStreams.IsEmpty()) {
+ // Don't shut down yet, streams are still alive
+ return;
+ }
+
+ // Since we're on the main thread, no-one is going to add a new stream
+ // while we shut down.
+ // This function is static so we don't have to delete 'this'.
+ delete gMediaCache;
+ gMediaCache = nsnull;
+}
+
+static void
+InitMediaCache()
+{
+ if (gMediaCache)
+ return;
+
+ gMediaCache = new nsMediaCache();
+ if (!gMediaCache)
+ return;
+
+ nsresult rv = gMediaCache->Init();
+ if (NS_FAILED(rv)) {
+ delete gMediaCache;
+ gMediaCache = nsnull;
+ }
+}
+
+nsresult
+nsMediaCache::ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
+ PRInt32* aBytes)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ if (!mFD)
+ return NS_ERROR_FAILURE;
+
+ if (mFDCurrentPos != aOffset) {
+ PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
+ if (offset != aOffset)
+ return NS_ERROR_FAILURE;
+ mFDCurrentPos = aOffset;
+ }
+ PRInt32 amount = PR_Read(mFD, aData, aLength);
+ if (amount <= 0)
+ return NS_ERROR_FAILURE;
+ mFDCurrentPos += amount;
+ *aBytes = amount;
+ return NS_OK;
+}
+
+nsresult
+nsMediaCache::ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRInt64 offset = aOffset;
+ PRInt32 count = aLength;
+ // Cast to char* so we can do byte-wise pointer arithmetic
+ char* data = static_cast<char*>(aData);
+ while (count > 0) {
+ PRInt32 bytes;
+ nsresult rv = ReadCacheFile(offset, data, count, &bytes);
+ if (NS_FAILED(rv))
+ return rv;
+ if (bytes == 0)
+ return NS_ERROR_FAILURE;
+ count -= bytes;
+ data += bytes;
+ offset += bytes;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMediaCache::WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ if (!mFD)
+ return NS_ERROR_FAILURE;
+
+ if (mFDCurrentPos != aOffset) {
+ PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
+ if (offset != aOffset)
+ return NS_ERROR_FAILURE;
+ mFDCurrentPos = aOffset;
+ }
+
+ const char* data = static_cast<const char*>(aData);
+ PRInt32 length = aLength;
+ while (length > 0) {
+ PRInt32 amount = PR_Write(mFD, data, length);
+ if (amount <= 0)
+ return NS_ERROR_FAILURE;
+ mFDCurrentPos += amount;
+ length -= amount;
+ data += amount;
+ }
+
+ return NS_OK;
+}
+
+static PRInt32 GetMaxBlocks()
+{
+ // We look up the cache size every time. This means dynamic changes
+ // to the pref are applied.
+ // Cache size is in KB
+ PRInt32 cacheSize = nsContentUtils::GetIntPref("media.cache_size", 50*1024);
+ PRInt64 maxBlocks = PRInt64(cacheSize)*1024/nsMediaCache::BLOCK_SIZE;
+ maxBlocks = PR_MAX(maxBlocks, 1);
+ return PRInt32(PR_MIN(maxBlocks, PR_INT32_MAX));
+}
+
+PRInt32
+nsMediaCache::FindBlockForIncomingData(PRIntervalTime aNow,
+ nsMediaCacheStream* aStream)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRInt32 blockIndex = FindReusableBlock(aNow, aStream,
+ aStream->mChannelOffset/BLOCK_SIZE, PR_INT32_MAX);
+
+ if (blockIndex < 0 || mIndex[blockIndex].mStream) {
+ // The block returned is already allocated.
+ // Don't reuse it if a) there's room to expand the cache or
+ // b) the data we're going to store in the free block is not higher
+ // priority than the data already stored in the free block.
+ // The latter can lead us to go over the cache limit a bit.
+ if ((mIndex.Length() < PRUint32(GetMaxBlocks()) || blockIndex < 0 ||
+ PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
+ blockIndex = mIndex.Length();
+ if (!mIndex.AppendElement())
+ return -1;
+ mFreeBlocks.AddFirstBlock(blockIndex);
+ return blockIndex;
+ }
+ }
+
+ return blockIndex;
+}
+
+void
+nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList,
+ nsTArray<PRUint32>* aResult,
+ PRInt32 aBlockIndexLimit)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRInt32 lastBlock = aBlockList->GetLastBlock();
+ if (lastBlock < 0)
+ return;
+ PRInt32 blockIndex = lastBlock;
+ do {
+ // Don't consider blocks for pinned streams, or blocks that are
+ // beyond the specified limit, or the block that contains its stream's
+ // current read position (such a block contains both played data
+ // and readahead data)
+ nsMediaCacheStream* stream = mIndex[blockIndex].mStream;
+ if (stream->mPinCount == 0 && blockIndex < aBlockIndexLimit &&
+ stream->mStreamOffset/BLOCK_SIZE != mIndex[blockIndex].mStreamBlock) {
+ aResult->AppendElement(blockIndex);
+ return;
+ }
+ blockIndex = mIndex[blockIndex].mPrevBlock;
+ } while (blockIndex != lastBlock);
+}
+
+PRInt32
+nsMediaCache::FindReusableBlock(PRIntervalTime aNow,
+ nsMediaCacheStream* aForStream,
+ PRInt32 aForStreamBlock,
+ PRInt32 aMaxSearchBlockIndex)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRUint32 length = PR_MIN(PRUint32(aMaxSearchBlockIndex), mIndex.Length());
+
+ if (aForStream && aForStreamBlock > 0 &&
+ PRUint32(aForStreamBlock) <= aForStream->mBlocks.Length()) {
+ PRInt32 prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
+ if (prevCacheBlock >= 0) {
+ PRUint32 freeBlockScanEnd =
+ PR_MIN(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
+ for (PRUint32 i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
+ if (mIndex[i].mClass == FREE_BLOCK)
+ return i;
+ }
+ }
+ }
+
+ if (!mFreeBlocks.IsEmpty()) {
+ PRInt32 firstBlock = mFreeBlocks.GetFirstBlock();
+ PRInt32 blockIndex = firstBlock;
+ do {
+ if (blockIndex < aMaxSearchBlockIndex)
+ return blockIndex;
+ blockIndex = mIndex[blockIndex].mNextBlock;
+ } while (blockIndex != firstBlock);
+ }
+
+ // Build a list of the blocks we should consider for the "latest
+ // predicted time of next use". We can exploit the fact that the block
+ // linked lists are ordered by increasing time of next use. This is
+ // actually the whole point of having the linked lists.
+ nsAutoTArray<PRUint32,8> candidates;
+ AppendMostReusableBlock(&mMetadataBlocks, &candidates, length);
+ AppendMostReusableBlock(&mPlayedBlocks, &candidates, length);
+ for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
+ nsMediaCacheStream* stream = mStreams[i];
+ // Don't consider a) blocks for pinned streams or b) blocks in
+ // non-seekable streams that contain data ahead of the current reader
+ // position. In the latter case, if we remove the block we won't be
+ // able to seek back to read it later.
+ if (!stream->mReadaheadBlocks.IsEmpty() && stream->mIsSeekable &&
+ stream->mPinCount == 0) {
+ // Find a readahead block that's in the given limit
+ PRInt32 lastBlock = stream->mReadaheadBlocks.GetLastBlock();
+ PRInt32 blockIndex = lastBlock;
+ do {
+ if (PRUint32(blockIndex) < length) {
+ candidates.AppendElement(blockIndex);
+ break;
+ }
+ blockIndex = mIndex[blockIndex].mPrevBlock;
+ } while (blockIndex != lastBlock);
+ }
+ }
+
+ PRIntervalTime latestUse = 0;
+ PRInt32 latestUseBlock = -1;
+ for (PRUint32 i = 0; i < candidates.Length(); ++i) {
+ PRIntervalTime nextUse = PredictNextUse(aNow, candidates[i]);
+ if (nextUse > latestUse) {
+ latestUse = nextUse;
+ latestUseBlock = candidates[i];
+ }
+ }
+
+#ifdef DEBUG
+ for (PRUint32 blockIndex = 0; blockIndex < length; ++blockIndex) {
+ Block* block = &mIndex[blockIndex];
+ nsMediaCacheStream* stream = block->mStream;
+ NS_ASSERTION(!stream || stream->mPinCount > 0 ||
+ (!stream->mIsSeekable && block->mClass == READAHEAD_BLOCK) ||
+ stream->mStreamOffset/BLOCK_SIZE == block->mStreamBlock ||
+ PredictNextUse(aNow, blockIndex) <= latestUse,
+ "We missed a block that should be replaced");
+ }
+#endif
+
+ return latestUseBlock;
+}
+
+nsMediaCache::BlockList*
+nsMediaCache::GetListForBlock(Block* aBlock)
+{
+ switch (aBlock->mClass) {
+ case FREE_BLOCK:
+ NS_ASSERTION(!aBlock->mStream, "Free block has a stream?");
+ return &mFreeBlocks;
+ case METADATA_BLOCK:
+ NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
+ return &mMetadataBlocks;
+ case PLAYED_BLOCK:
+ NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
+ return &mPlayedBlocks;
+ case READAHEAD_BLOCK:
+ NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
+ return &aBlock->mStream->mReadaheadBlocks;
+ default:
+ NS_ERROR("Invalid block class");
+ return nsnull;
+ }
+}
+
+void
+nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ Block* block1 = &mIndex[aBlockIndex1];
+ Block* block2 = &mIndex[aBlockIndex2];
+
+ Block tmp = *block1;
+ *block1 = *block2;
+ *block2 = tmp;
+
+ // Now all references to block1 have to be replaced with block2 and
+ // vice versa
+
+ if (block1->mStream) {
+ block1->mStream->mBlocks[block1->mStreamBlock] = aBlockIndex1;
+ }
+ if (block2->mStream) {
+ block2->mStream->mBlocks[block2->mStreamBlock] = aBlockIndex2;
+ }
+
+ BlockList* list1 = GetListForBlock(block1);
+ list1->NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
+ BlockList* list2 = GetListForBlock(block2);
+ // We have to be careful we don't swap the same reference twice!
+ if (list1 != list2) {
+ list2->NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
+ }
+
+ // Find all the blocks that have references to the swapped blocks
+ nsAutoTArray<PRInt32,4> blocksWithReferences;
+ blocksWithReferences.AppendElement(block1->mPrevBlock);
+ blocksWithReferences.AppendElement(block1->mNextBlock);
+ blocksWithReferences.AppendElement(block2->mPrevBlock);
+ blocksWithReferences.AppendElement(block2->mNextBlock);
+ blocksWithReferences.Sort();
+ for (PRUint32 i = 0; i < 4; ++i) {
+ // We have to be careful we don't swap the same reference twice!
+ if (i == 0 || blocksWithReferences[i] != blocksWithReferences[i - 1]) {
+ PRInt32 blockIndex = blocksWithReferences[i];
+ // Note that the references we collected may belong to swapped
+ // blocks ... make sure we update the right block
+ UpdateSwappedBlockIndex(&blockIndex, aBlockIndex1, aBlockIndex2);
+ Block* block = &mIndex[blockIndex];
+ UpdateSwappedBlockIndex(&block->mNextBlock, aBlockIndex1, aBlockIndex2);
+ UpdateSwappedBlockIndex(&block->mPrevBlock, aBlockIndex1, aBlockIndex2);
+ }
+ }
+
+ Verify();
+}
+
+void
+nsMediaCache::FreeBlock(PRInt32 aBlock)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ Block* block = &mIndex[aBlock];
+ GetListForBlock(block)->RemoveBlock(aBlock);
+ if (block->mStream) {
+ block->mStream->mBlocks[block->mStreamBlock] = -1;
+ }
+ block->mStream = nsnull;
+ block->mClass = FREE_BLOCK;
+ mFreeBlocks.AddFirstBlock(aBlock);
+ Verify();
+}
+
+PRIntervalTime
+nsMediaCache::PredictNextUse(PRIntervalTime aNow, PRInt32 aBlock)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ Block* block = &mIndex[aBlock];
+
+ switch (block->mClass) {
+ case METADATA_BLOCK:
+ // This block should be managed in LRU mode. For metadata we predict
+ // that the time until the next use is the time since the last use.
+ return aNow - block->mLastUseTime;
+ case PLAYED_BLOCK:
+ // This block should be managed in LRU mode, and we should impose
+ // a "replay delay" to reflect the likelihood of replay happening
+ NS_ASSERTION(PRInt64(block->mStreamBlock)*BLOCK_SIZE <
+ block->mStream->mStreamOffset,
+ "Played block after the current stream position?");
+ return aNow - block->mLastUseTime + PR_SecondsToInterval(REPLAY_DELAY);
+ case READAHEAD_BLOCK: {
+ PRInt64 bytesAhead =
+ PRInt64(block->mStreamBlock)*BLOCK_SIZE - block->mStream->mStreamOffset;
+ NS_ASSERTION(bytesAhead >= 0,
+ "Readahead block before the current stream position?");
+ PRInt64 millisecondsAhead =
+ bytesAhead*1000/block->mStream->mPlaybackBytesPerSecond;
+ return PR_MillisecondsToInterval(PRUint32(PR_MIN(millisecondsAhead, PR_INT32_MAX)));
+ }
+ default:
+ NS_ERROR("Invalid class for predicting next use");
+ return 0;
+ }
+}
+
+PRIntervalTime
+nsMediaCache::PredictNextUseForIncomingData(nsMediaCacheStream* aStream)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRInt64 bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
+ if (bytesAhead <= -BLOCK_SIZE) {
+ // Hmm, no idea when data behind us will be used. Guess 24 hours.
+ return PR_SecondsToInterval(24*60*60);
+ }
+ if (bytesAhead <= 0)
+ return PR_SecondsToInterval(0);
+ PRInt64 millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond;
+ return PR_MillisecondsToInterval(PRUint32(PR_MIN(millisecondsAhead, PR_INT32_MAX)));
+}
+
+void
+nsMediaCache::Update()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(mMonitor);
+ mUpdateQueued = PR_FALSE;
+#ifdef DEBUG
+ mInUpdate = PR_TRUE;
+#endif
+
+ PRInt32 maxBlocks = GetMaxBlocks();
+ PRIntervalTime now = PR_IntervalNow();
+
+ PRInt32 freeBlockCount = mFreeBlocks.GetCount();
+ // Try to trim back the cache to its desired maximum size. The cache may
+ // have overflowed simply due to data being received when we have
+ // no blocks in the main part of the cache that are free or lower
+ // priority than the new data. The cache can also be overflowing because
+ // the media.cache_size preference was reduced.
+ // First, figure out what the least valuable block in the cache overflow
+ // is. We don't want to replace any blocks in the main part of the
+ // cache whose expected time of next use is earlier or equal to that.
+ // If we allow that, we can effectively end up discarding overflowing
+ // blocks (by moving an overflowing block to the main part of the cache,
+ // and then overwriting it with another overflowing block), and we try
+ // to avoid that since it requires HTTP seeks.
+ // We also use this loop to eliminate overflowing blocks from
+ // freeBlockCount.
+ PRIntervalTime latestPredictedUseForOverflow = 0;
+ for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
+ --blockIndex) {
+ nsMediaCacheStream* stream = mIndex[blockIndex].mStream;
+ if (!stream) {
+ // Don't count overflowing free blocks in our free block count
+ --freeBlockCount;
+ continue;
+ }
+ PRIntervalTime predictedUse = PredictNextUse(now, blockIndex);
+ latestPredictedUseForOverflow = PR_MAX(latestPredictedUseForOverflow, predictedUse);
+ }
+
+ // Now try to move overflowing blocks to the main part of the cache.
+ for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
+ --blockIndex) {
+ Block* block = &mIndex[blockIndex];
+ nsMediaCacheStream* stream = block->mStream;
+ if (!stream)
+ continue;
+
+ PRInt32 destinationBlockIndex =
+ FindReusableBlock(now, stream, block->mStreamBlock, maxBlocks);
+ if (destinationBlockIndex < 0) {
+ // Nowhere to place this overflow block. We won't be able to
+ // place any more overflow blocks.
+ break;
+ }
+
+ Block* destinationBlock = &mIndex[destinationBlockIndex];
+ if (destinationBlock->mClass == FREE_BLOCK ||
+ PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
+ // Reuse blocks in the main part of the cache that are less useful than
+ // the least useful overflow blocks
+ char buf[BLOCK_SIZE];
+ nsresult rv = ReadCacheFileAllBytes(blockIndex*BLOCK_SIZE, buf, sizeof(buf));
+ if (NS_SUCCEEDED(rv)) {
+ rv = WriteCacheFile(destinationBlockIndex*BLOCK_SIZE, buf, BLOCK_SIZE);
+ if (NS_SUCCEEDED(rv)) {
+ // We successfully copied the file data.
+ LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
+ blockIndex, destinationBlockIndex));
+ // Swapping the block metadata here lets us maintain the
+ // correct positions in the linked lists
+ SwapBlocks(blockIndex, destinationBlockIndex);
+ } else {
+ // If the write fails we may have corrupted the destination
+ // block. Free it now.
+ LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld) (trimming cache)",
+ destinationBlockIndex, destinationBlock->mStream,
+ destinationBlock->mStreamBlock,
+ (long long)destinationBlock->mStreamBlock*BLOCK_SIZE));
+ FreeBlock(destinationBlockIndex);
+ }
+ // Free the overflowing block even if the copy failed.
+ if (block->mClass != FREE_BLOCK) {
+ LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld) (trimming cache)",
+ blockIndex, block->mStream, block->mStreamBlock,
+ (long long)block->mStreamBlock*BLOCK_SIZE));
+ FreeBlock(blockIndex);
+ }
+ }
+ }
+ }
+ // Try chopping back the array of cache entries and the cache file.
+ Truncate();
+
+ // Count the blocks allocated for readahead of non-seekable streams
+ // (these blocks can't be freed but we don't want them to monopolize the
+ // cache)
+ PRInt32 nonSeekableReadaheadBlockCount = 0;
+ for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
+ nsMediaCacheStream* stream = mStreams[i];
+ if (!stream->mIsSeekable) {
+ nonSeekableReadaheadBlockCount += stream->mReadaheadBlocks.GetCount();
+ }
+ }
+
+ // If freeBlockCount is zero, then compute the latest of
+ // the predicted next-uses for all blocks
+ PRIntervalTime latestNextUse = 0;
+ if (freeBlockCount == 0) {
+ PRInt32 reusableBlock = FindReusableBlock(now, nsnull, 0, maxBlocks);
+ if (reusableBlock >= 0) {
+ latestNextUse = PredictNextUse(now, reusableBlock);
+ }
+ }
+
+ // This array holds a list of streams which need to be closed due
+ // to fatal errors. We can't close streams immediately since it would
+ // confuse iteration over mStreams and generally just be confusing.
+ nsTArray<nsMediaCacheStream*> streamsToClose;
+ for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
+ nsMediaCacheStream* stream = mStreams[i];
+ if (stream->mClosed)
+ continue;
+
+ // Figure out where we should be reading from. It's normally the first
+ // uncached byte after the current mStreamOffset.
+ PRInt64 desiredOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
+ if (stream->mIsSeekable) {
+ if (desiredOffset > stream->mChannelOffset &&
+ desiredOffset <= stream->mChannelOffset + SEEK_VS_READ_THRESHOLD) {
+ // Assume it's more efficient to just keep reading up to the
+ // desired position instead of trying to seek
+ desiredOffset = stream->mChannelOffset;
+ }
+ } else {
+ // We can't seek directly to the desired offset...
+ if (stream->mChannelOffset > desiredOffset) {
+ // Reading forward won't get us anywhere, we need to go backwards.
+ // Seek back to 0 (the client will reopen the stream) and then
+ // read forward.
+ NS_WARNING("Can't seek backwards, so seeking to 0");
+ desiredOffset = 0;
+ // Flush cached blocks out, since if this is a live stream
+ // the cached data may be completely different next time we
+ // read it. We have to assume that live streams don't
+ // advertise themselves as being seekable...
+ ReleaseStreamBlocks(stream);
+ } else {
+ // otherwise reading forward is looking good, so just stay where we
+ // are and don't trigger a channel seek!
+ desiredOffset = stream->mChannelOffset;
+ }
+ }
+
+ // Figure out if we should be reading data now or not. It's amazing
+ // how complex this is, but each decision is simple enough.
+ PRBool enableReading;
+ if (stream->mStreamLength >= 0 &&
+ desiredOffset >= stream->mStreamLength) {
+ // We're at the end of the stream. Nothing to read.
+ LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream));
+ enableReading = PR_FALSE;
+ } else if (desiredOffset < stream->mStreamOffset) {
+ // We're reading to try to catch up to where the current stream
+ // reader wants to be. Better not stop.
+ LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream));
+ enableReading = PR_TRUE;
+ } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) {
+ // The stream reader is waiting for us, or nearly so. Better feed it.
+ LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream));
+ enableReading = PR_TRUE;
+ } else if (!stream->mIsSeekable &&
+ nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) {
+ // This stream is not seekable and there are already too many blocks
+ // being cached for readahead for nonseekable streams (which we can't
+ // free). So stop reading ahead now.
+ LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream));
+ enableReading = PR_FALSE;
+ } else if (mIndex.Length() > PRUint32(maxBlocks)) {
+ // We're in the process of bringing the cache size back to the
+ // desired limit, so don't bring in more data yet
+ LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream));
+ enableReading = PR_FALSE;
+ } else if (freeBlockCount > 0 || mIndex.Length() < PRUint32(maxBlocks)) {
+ // Free blocks in the cache, so keep reading
+ LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream));
+ enableReading = PR_TRUE;
+ } else if (latestNextUse <= 0) {
+ // No reusable blocks, so can't read anything
+ LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream));
+ enableReading = PR_FALSE;
+ } else {
+ // Read ahead if the data we expect to read is more valuable than
+ // the least valuable block in the main part of the cache
+ PRIntervalTime predictedNewDataUse = PredictNextUseForIncomingData(stream);
+ LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %lld, current worst block is %lld",
+ stream, (long long)predictedNewDataUse, (long long)latestNextUse));
+ enableReading = predictedNewDataUse < latestNextUse;
+ }
+
+ nsresult rv = NS_OK;
+ if (stream->mChannelOffset != desiredOffset && enableReading) {
+ // We need to seek now.
+ NS_ASSERTION(stream->mIsSeekable || desiredOffset == 0,
+ "Trying to seek in a non-seekable stream!");
+ if (stream->mCacheSuspended) {
+ LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream));
+ rv = stream->mClient->CacheClientResume();
+ stream->mCacheSuspended = PR_FALSE;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // Round seek offset down to the start of the block
+ stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE;
+ LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld", stream,
+ (long long)stream->mChannelOffset));
+ rv = stream->mClient->CacheClientSeek(stream->mChannelOffset);
+ }
+ } else if (enableReading && stream->mCacheSuspended) {
+ LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream));
+ rv = stream->mClient->CacheClientResume();
+ stream->mCacheSuspended = PR_FALSE;
+ } else if (!enableReading && !stream->mCacheSuspended) {
+ LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream));
+ rv = stream->mClient->CacheClientSuspend();
+ stream->mCacheSuspended = PR_TRUE;
+ }
+
+ if (NS_FAILED(rv)) {
+ streamsToClose.AppendElement(stream);
+ }
+ }
+
+ // Close the streams that failed due to error. This will cause all
+ // client Read and Seek operations on those streams to fail. Blocked
+ // Reads will also be woken up.
+ for (PRUint32 i = 0; i < streamsToClose.Length(); ++i) {
+ streamsToClose[i]->CloseInternal(&mon);
+ }
+#ifdef DEBUG
+ mInUpdate = PR_FALSE;
+#endif
+}
+
+class UpdateEvent : public nsRunnable
+{
+public:
+ NS_IMETHOD Run()
+ {
+ if (gMediaCache) {
+ gMediaCache->Update();
+ }
+ return NS_OK;
+ }
+};
+
+void
+nsMediaCache::QueueUpdate()
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ // Queuing an update while we're in an update raises a high risk of
+ // triggering endless events
+ NS_ASSERTION(!mInUpdate,
+ "Queuing an update while we're in an update");
+ if (mUpdateQueued)
+ return;
+ mUpdateQueued = PR_TRUE;
+ nsCOMPtr<nsIRunnable> event = new UpdateEvent();
+ NS_DispatchToMainThread(event);
+}
+
+#ifdef DEBUG
+void
+nsMediaCache::Verify()
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ mFreeBlocks.Verify();
+ mPlayedBlocks.Verify();
+ mMetadataBlocks.Verify();
+ for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
+ nsMediaCacheStream* stream = mStreams[i];
+ stream->mReadaheadBlocks.Verify();
+ if (!stream->mReadaheadBlocks.IsEmpty()) {
+ // Verify that the readahead blocks are listed in stream block order
+ PRInt32 firstBlock = stream->mReadaheadBlocks.GetFirstBlock();
+ PRInt32 block = firstBlock;
+ PRInt32 lastStreamBlock = -1;
+ do {
+ NS_ASSERTION(mIndex[block].mStream == stream, "Bad stream");
+ NS_ASSERTION(lastStreamBlock < PRInt32(mIndex[block].mStreamBlock),
+ "Blocks not increasing in readahead stream");
+ lastStreamBlock = PRInt32(mIndex[block].mStreamBlock);
+ block = mIndex[block].mNextBlock;
+ } while (block != firstBlock);
+ }
+ }
+}
+#endif
+
+void
+nsMediaCache::InsertReadaheadBlock(PRInt32 aBlockIndex)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ Block* block = &mIndex[aBlockIndex];
+ nsMediaCacheStream* stream = block->mStream;
+ if (stream->mReadaheadBlocks.IsEmpty()) {
+ stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
+ return;
+ }
+
+ // Find the last block whose stream block is before aBlockIndex's
+ // stream block, and insert after it
+ PRInt32 lastIndex = stream->mReadaheadBlocks.GetLastBlock();
+ PRInt32 readaheadIndex = lastIndex;
+ do {
+ if (mIndex[readaheadIndex].mStreamBlock < block->mStreamBlock) {
+ stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
+ return;
+ }
+ NS_ASSERTION(mIndex[readaheadIndex].mStreamBlock > block->mStreamBlock,
+ "Duplicated blocks??");
+ readaheadIndex = mIndex[readaheadIndex].mPrevBlock;
+ } while (readaheadIndex != lastIndex);
+ stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
+ Verify();
+}
+
+void
+nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
+ nsMediaCacheStream::ReadMode aMode)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ PRInt32 streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
+ // Extend the mBlocks array as necessary
+ while (streamBlockIndex >= PRInt32(aStream->mBlocks.Length())) {
+ aStream->mBlocks.AppendElement(-1);
+ }
+ if (aStream->mBlocks[streamBlockIndex] >= 0) {
+ // Release the existing cache entry for this stream 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));
+ FreeBlock(globalBlockIndex);
+ }
+
+ PRIntervalTime now = PR_IntervalNow();
+ PRInt32 blockIndex = FindBlockForIncomingData(now, aStream);
+ if (blockIndex >= 0) {
+ Block* block = &mIndex[blockIndex];
+ if (block->mClass != FREE_BLOCK) {
+ LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
+ blockIndex, block->mStream, block->mStreamBlock, (long long)block->mStreamBlock*BLOCK_SIZE));
+ FreeBlock(blockIndex);
+ }
+ NS_ASSERTION(block->mClass == FREE_BLOCK, "Block should be free now!");
+
+ LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
+ blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
+ block->mStream = aStream;
+ block->mStreamBlock = streamBlockIndex;
+ block->mLastUseTime = now;
+ aStream->mBlocks[streamBlockIndex] = blockIndex;
+ mFreeBlocks.RemoveBlock(blockIndex);
+ if (streamBlockIndex*BLOCK_SIZE < aStream->mStreamOffset) {
+ block->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(block)->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.
+ block->mClass = READAHEAD_BLOCK;
+ InsertReadaheadBlock(blockIndex);
+ }
+
+ nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
+ if (NS_FAILED(rv)) {
+ LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
+ blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
+ FreeBlock(blockIndex);
+ }
+ }
+
+ // Queue an Update since the cache state has changed (for example
+ // we might want to stop loading because the cache is full)
+ QueueUpdate();
+}
+
+void
+nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(mMonitor);
+ mStreams.AppendElement(aStream);
+}
+
+void
+nsMediaCache::ReleaseStream(nsMediaCacheStream* aStream)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(mMonitor);
+ mStreams.RemoveElement(aStream);
+}
+
+void
+nsMediaCache::ReleaseStreamBlocks(nsMediaCacheStream* aStream)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ // XXX scanning the entire stream doesn't seem great, if not much of it
+ // is cached, but the only easy alternative is to scan the entire cache
+ // which isn't better
+ PRUint32 length = aStream->mBlocks.Length();
+ for (PRUint32 i = 0; i < length; ++i) {
+ PRInt32 blockIndex = aStream->mBlocks[i];
+ if (blockIndex >= 0) {
+ LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
+ blockIndex, aStream, i, (long long)i*BLOCK_SIZE));
+ FreeBlock(blockIndex);
+ }
+ }
+}
+
+void
+nsMediaCache::Truncate()
+{
+ PRUint32 end;
+ for (end = mIndex.Length(); end > 0; --end) {
+ if (mIndex[end - 1].mStream)
+ break;
+ mFreeBlocks.RemoveBlock(end - 1);
+ }
+
+ if (end < mIndex.Length()) {
+ mIndex.TruncateLength(end);
+ // XXX We could truncate the cache file here, but we don't seem
+ // to have a cross-platform API for doing that. At least when all
+ // streams are closed we shut down the cache, which erases the
+ // file at that point.
+ }
+}
+
+void
+nsMediaCache::NoteBlockUsage(PRInt32 aBlockIndex,
+ nsMediaCacheStream::ReadMode aMode,
+ PRIntervalTime aNow)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ if (aBlockIndex < 0) {
+ // this block is not in the cache yet
+ return;
+ }
+
+ Block* block = &mIndex[aBlockIndex];
+ if (block->mClass == FREE_BLOCK) {
+ // this block is not in the cache yet
+ return;
+ }
+
+ // The following check has to be <= because the stream offset has
+ // not yet been updated for the data read from this block
+ NS_ASSERTION(block->mStreamBlock*BLOCK_SIZE <= block->mStream->mStreamOffset,
+ "Using a block that's behind the read position?");
+
+ GetListForBlock(block)->RemoveBlock(aBlockIndex);
+ block->mClass =
+ (aMode == nsMediaCacheStream::MODE_METADATA || block->mClass == METADATA_BLOCK)
+ ? METADATA_BLOCK : PLAYED_BLOCK;
+ // Since this is just being used now, it can definitely be at the front
+ // of mMetadataBlocks or mPlayedBlocks
+ GetListForBlock(block)->AddFirstBlock(aBlockIndex);
+ block->mLastUseTime = PR_IntervalNow();
+ Verify();
+}
+
+void
+nsMediaCache::NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset)
+{
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
+
+ if (aOldOffset < aStream->mStreamOffset) {
+ // We seeked forward. Convert blocks from readahead to played.
+ // Any readahead block that intersects the seeked-over range must
+ // be converted.
+ PRInt32 blockIndex = aOldOffset/BLOCK_SIZE;
+ PRInt32 endIndex =
+ PR_MIN((aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
+ aStream->mBlocks.Length());
+ PRIntervalTime now = PR_IntervalNow();
+ while (blockIndex < endIndex) {
+ PRInt32 cacheBlockIndex = aStream->mBlocks[blockIndex];
+ if (cacheBlockIndex >= 0) {
+ // Marking the block used may not be exactly what we want but
+ // it's simple
+ NoteBlockUsage(cacheBlockIndex, nsMediaCacheStream::MODE_PLAYBACK,
+ now);
+ }
+ ++blockIndex;
+ }
+ } else {
+ // We seeked backward. Convert from played to readahead.
+ // Any played block that is entirely after the start of the seeked-over
+ // range must be converted.
+ PRInt32 blockIndex =
+ (aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE;
+ PRInt32 endIndex =
+ PR_MIN((aOldOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
+ aStream->mBlocks.Length());
+ while (blockIndex < endIndex) {
+ PRInt32 cacheBlockIndex = aStream->mBlocks[endIndex - 1];
+ if (cacheBlockIndex >= 0) {
+ Block* block = &mIndex[cacheBlockIndex];
+ if (block->mClass != METADATA_BLOCK) {
+ mPlayedBlocks.RemoveBlock(cacheBlockIndex);
+ block->mClass = READAHEAD_BLOCK;
+ // Adding this as the first block is sure to be OK since
+ // this must currently be the earliest readahead block
+ // (that's why we're proceeding backwards from the end of
+ // the seeked range to the start)
+ GetListForBlock(block)->AddFirstBlock(cacheBlockIndex);
+ Verify();
+ }
+ }
+ --endIndex;
+ }
+ }
+}
+
+void
+nsMediaCacheStream::NotifyDataLength(PRInt64 aLength)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ mStreamLength = aLength;
+}
+
+void
+nsMediaCacheStream::NotifyDataStarted(PRInt64 aOffset)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ NS_WARN_IF_FALSE(aOffset == mChannelOffset,
+ "Server is giving us unexpected offset");
+ mChannelOffset = aOffset;
+ if (mStreamLength >= 0) {
+ // If we started reading at a certain offset, then for sure
+ // the stream is at least that long.
+ mStreamLength = PR_MAX(mStreamLength, mChannelOffset);
+ }
+}
+
+void
+nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ PRInt64 size = aSize;
+ const char* data = aData;
+
+ LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld",
+ this, (long long)mChannelOffset, (long long)aSize));
+
+ // We process the data one block (or part of a block) at a time
+ while (size > 0) {
+ PRUint32 blockIndex = mChannelOffset/BLOCK_SIZE;
+ PRInt32 blockOffset = PRInt32(mChannelOffset - blockIndex*BLOCK_SIZE);
+ PRInt32 chunkSize = PRInt32(PR_MIN(BLOCK_SIZE - blockOffset, size));
+
+ // This gets set to something non-null if we have a whole block
+ // of data to write to the cache
+ const char* blockDataToStore = nsnull;
+ ReadMode mode = MODE_PLAYBACK;
+ if (blockOffset == 0 && chunkSize == BLOCK_SIZE) {
+ // We received a whole block, so avoid a useless copy through
+ // mPartialBlockBuffer
+ blockDataToStore = data;
+ } else {
+ if (blockOffset == 0) {
+ // We've just started filling this buffer so now is a good time
+ // to clear this flag.
+ mMetadataInPartialBlockBuffer = PR_FALSE;
+ }
+ memcpy(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset,
+ data, chunkSize);
+
+ if (blockOffset + chunkSize == BLOCK_SIZE) {
+ // We completed a block, so lets write it out.
+ blockDataToStore = reinterpret_cast<char*>(mPartialBlockBuffer);
+ if (mMetadataInPartialBlockBuffer) {
+ mode = MODE_METADATA;
+ }
+ }
+ }
+
+ if (blockDataToStore) {
+ gMediaCache->AllocateAndWriteBlock(this, blockDataToStore, mode);
+ }
+
+ 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;
+ }
+
+ // 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
+ mon.NotifyAll();
+}
+
+void
+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) {
+ // Write back the partial block
+ memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
+ BLOCK_SIZE - blockOffset);
+ gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
+ mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
+ // Wake up readers who may be waiting for this data
+ mon.NotifyAll();
+ }
+}
+
+nsMediaCacheStream::~nsMediaCacheStream()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ NS_ASSERTION(mClosed, "Stream was not closed");
+ NS_ASSERTION(!mPinCount, "Unbalanced Pin");
+
+ gMediaCache->ReleaseStream(this);
+ nsMediaCache::MaybeShutdown();
+}
+
+void
+nsMediaCacheStream::SetSeekable(PRBool aIsSeekable)
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ NS_ASSERTION(mIsSeekable || aIsSeekable ||
+ mChannelOffset == 0, "channel offset must be zero when we become non-seekable");
+ mIsSeekable = aIsSeekable;
+ // Queue an Update since we may change our strategy for dealing
+ // with this stream
+ gMediaCache->QueueUpdate();
+}
+
+void
+nsMediaCacheStream::Close()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ CloseInternal(&mon);
+ // Queue an Update since we may have created more free space. Don't do
+ // it from CloseInternal since that gets called by Update() itself
+ // sometimes, and we try to not to queue updates from Update().
+ gMediaCache->QueueUpdate();
+}
+
+void
+nsMediaCacheStream::CloseInternal(nsAutoMonitor* aMonitor)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (mClosed)
+ return;
+ mClosed = PR_TRUE;
+ gMediaCache->ReleaseStreamBlocks(this);
+ // Wake up any blocked readers
+ aMonitor->NotifyAll();
+}
+
+void
+nsMediaCacheStream::Pin()
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ ++mPinCount;
+ // Queue an Update since we may no longer want to read more into the
+ // cache, if this stream's block have become non-evictable
+ gMediaCache->QueueUpdate();
+}
+
+void
+nsMediaCacheStream::Unpin()
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
+ --mPinCount;
+ // Queue an Update since we may be able to read more into the
+ // cache, if this stream's block have become evictable
+ gMediaCache->QueueUpdate();
+}
+
+PRInt64
+nsMediaCacheStream::GetLength()
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ return mStreamLength;
+}
+
+PRInt64
+nsMediaCacheStream::GetCachedDataEnd(PRInt64 aOffset)
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ return GetCachedDataEndInternal(aOffset);
+}
+
+PRBool
+nsMediaCacheStream::IsDataCachedToEndOfStream(PRInt64 aOffset)
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ if (mStreamLength < 0)
+ return PR_FALSE;
+ return GetCachedDataEndInternal(aOffset) >= mStreamLength;
+}
+
+PRInt64
+nsMediaCacheStream::GetCachedDataEndInternal(PRInt64 aOffset)
+{
+ PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
+ PRUint32 blockIndex = startBlockIndex;
+ while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
+ ++blockIndex;
+ }
+ PRInt64 result = blockIndex*BLOCK_SIZE;
+ if (blockIndex == mChannelOffset/BLOCK_SIZE) {
+ // The block containing mChannelOffset may be partially read but not
+ // yet committed to the main cache
+ result = mChannelOffset;
+ }
+ if (mStreamLength >= 0) {
+ // The last block in the cache may only be partially valid, so limit
+ // the cached range to the stream length
+ result = PR_MIN(result, mStreamLength);
+ }
+ return PR_MAX(result, aOffset);
+}
+
+void
+nsMediaCacheStream::SetReadMode(ReadMode aMode)
+{
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ if (aMode == mCurrentMode)
+ return;
+ mCurrentMode = aMode;
+ gMediaCache->QueueUpdate();
+}
+
+void
+nsMediaCacheStream::SetPlaybackRate(PRUint32 aBytesPerSecond)
+{
+ NS_ASSERTION(aBytesPerSecond > 0, "Zero playback rate not allowed");
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ if (aBytesPerSecond == mPlaybackBytesPerSecond)
+ return;
+ mPlaybackBytesPerSecond = aBytesPerSecond;
+ gMediaCache->QueueUpdate();
+}
+
+nsresult
+nsMediaCacheStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ if (mClosed)
+ return NS_ERROR_FAILURE;
+
+ PRInt64 oldOffset = mStreamOffset;
+ switch (aWhence) {
+ case PR_SEEK_END:
+ if (mStreamLength < 0)
+ return NS_ERROR_FAILURE;
+ mStreamOffset = mStreamLength + aOffset;
+ break;
+ case PR_SEEK_CUR:
+ mStreamOffset += aOffset;
+ break;
+ case PR_SEEK_SET:
+ mStreamOffset = aOffset;
+ break;
+ default:
+ NS_ERROR("Unknown whence");
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset));
+ gMediaCache->NoteSeek(this, oldOffset);
+
+ gMediaCache->QueueUpdate();
+ return NS_OK;
+}
+
+PRInt64
+nsMediaCacheStream::Tell()
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ return mStreamOffset;
+}
+
+nsresult
+nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ nsAutoMonitor mon(gMediaCache->Monitor());
+ if (mClosed)
+ return NS_ERROR_FAILURE;
+
+ PRUint32 count = 0;
+ // Read one block (or part of a block) at a time
+ while (count < aCount) {
+ PRUint32 streamBlock = PRUint32(mStreamOffset/BLOCK_SIZE);
+ PRUint32 offsetInStreamBlock =
+ PRUint32(mStreamOffset - streamBlock*BLOCK_SIZE);
+ PRInt32 size = PR_MIN(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
+
+ if (mStreamLength >= 0) {
+ // Don't try to read beyond the end of the stream
+ PRInt64 bytesRemaining = mStreamLength - mStreamOffset;
+ if (bytesRemaining <= 0) {
+ // Get out of here and return NS_OK
+ break;
+ }
+ size = PR_MIN(size, PRInt32(bytesRemaining));
+ }
+
+ PRInt32 bytes;
+ PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
+ PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
+ if (channelBlock == streamBlock && mStreamOffset < mChannelOffset) {
+ // We can just use the data in mPartialBuffer. In fact we should
+ // use it rather than waiting for the block to fill and land in
+ // the cache.
+ bytes = PR_MIN(size, mChannelOffset - mStreamOffset);
+ memcpy(aBuffer + count,
+ reinterpret_cast<char*>(mPartialBlockBuffer) + offsetInStreamBlock, bytes);
+ if (mCurrentMode == MODE_METADATA) {
+ mMetadataInPartialBlockBuffer = PR_TRUE;
+ }
+ gMediaCache->NoteBlockUsage(cacheBlock, mCurrentMode, PR_IntervalNow());
+ } else {
+ if (cacheBlock < 0) {
+ if (count > 0) {
+ // Some data has been read, so return what we've got instead of
+ // blocking
+ break;
+ }
+
+ // No data has been read yet, so block
+ mon.Wait();
+ if (mClosed) {
+ // We may have successfully read some data, but let's just throw
+ // that out.
+ return NS_ERROR_FAILURE;
+ }
+ continue;
+ }
+
+ gMediaCache->NoteBlockUsage(cacheBlock, mCurrentMode, PR_IntervalNow());
+
+ PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
+ nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes);
+ if (NS_FAILED(rv)) {
+ if (count == 0)
+ return rv;
+ // If we did successfully read some data, may as well return it
+ break;
+ }
+ }
+ mStreamOffset += bytes;
+ count += bytes;
+ }
+
+ if (count > 0) {
+ // Some data was read, so queue an update since block priorities may
+ // have changed
+ gMediaCache->QueueUpdate();
+ }
+ LOG(PR_LOG_DEBUG,
+ ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count));
+ *aBytes = count;
+ return NS_OK;
+}
+
+nsresult
+nsMediaCacheStream::Init()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ InitMediaCache();
+ if (!gMediaCache)
+ return NS_ERROR_FAILURE;
+ gMediaCache->OpenStream(this);
+ return NS_OK;
+}
--- a/content/media/video/src/nsMediaStream.cpp
+++ b/content/media/video/src/nsMediaStream.cpp
@@ -45,274 +45,639 @@
#include "nsIFileChannel.h"
#include "nsIHttpChannel.h"
#include "nsISeekableStream.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIRequestObserver.h"
#include "nsIStreamListener.h"
#include "nsIScriptSecurityManager.h"
-#include "nsChannelToPipeListener.h"
#include "nsCrossSiteListenerProxy.h"
#include "nsHTMLMediaElement.h"
#include "nsIDocument.h"
+#include "nsDOMError.h"
+#include "nsICachingChannel.h"
+#include "nsURILoader.h"
-class nsMediaChannelStream : public nsMediaStream
+#define HTTP_OK_CODE 200
+#define HTTP_PARTIAL_RESPONSE_CODE 206
+
+nsMediaChannelStream::nsMediaChannelStream(nsMediaDecoder* aDecoder,
+ nsIChannel* aChannel, nsIURI* aURI)
+ : nsMediaStream(aDecoder, aChannel, aURI),
+ mSuspendCount(0), mSeeking(PR_FALSE),
+ mCacheStream(this),
+ mLock(nsAutoLock::NewLock("media.channel.stream")),
+ mCacheSuspendCount(0)
{
-public:
- nsMediaChannelStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
- nsMediaStream(aDecoder, aChannel, aURI),
- mPosition(0), mCancelled(PR_FALSE)
- {
+}
+
+nsMediaChannelStream::~nsMediaChannelStream()
+{
+ if (mListener) {
+ // Kill its reference to us since we're going away
+ mListener->Revoke();
+ }
+ if (mLock) {
+ nsAutoLock::DestroyLock(mLock);
}
-
- virtual nsresult Open(nsIStreamListener** aStreamListener);
- virtual nsresult Close();
- virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
- virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
- virtual PRInt64 Tell();
- virtual void Cancel();
- virtual nsIPrincipal* GetCurrentPrincipal();
- virtual void Suspend();
- virtual void Resume();
+}
+
+// nsMediaChannelStream::Listener just observes the channel and
+// forwards notifications to the nsMediaChannelStream. We use multiple
+// listener objects so that when we open a new stream for a seek we can
+// disconnect the old listener from the nsMediaChannelStream and hook up
+// a new listener, so notifications from the old channel are discarded
+// and don't confuse us.
+NS_IMPL_ISUPPORTS2(nsMediaChannelStream::Listener, nsIRequestObserver, nsIStreamListener)
+
+nsresult
+nsMediaChannelStream::Listener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (!mStream)
+ return NS_OK;
+ return mStream->OnStartRequest(aRequest);
+}
+
+nsresult
+nsMediaChannelStream::Listener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (!mStream)
+ return NS_OK;
+ return mStream->OnStopRequest(aRequest, aStatus);
+}
- // Return PR_TRUE if the stream has been cancelled.
- PRBool IsCancelled() const { return mCancelled; }
+nsresult
+nsMediaChannelStream::Listener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ PRUint32 aOffset,
+ PRUint32 aCount)
+{
+ if (!mStream)
+ return NS_OK;
+ return mStream->OnDataAvailable(aRequest, aStream, aCount);
+}
+
+nsresult
+nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
+
+ nsHTMLMediaElement* element = mDecoder->GetMediaElement();
+ NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+ if (element->ShouldCheckAllowOrigin()) {
+ // If the request was cancelled by nsCrossSiteListenerProxy due to failing
+ // the Access Control check, send an error through to the media element.
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ if (NS_FAILED(rv) || status == NS_ERROR_DOM_BAD_URI) {
+ mDecoder->NetworkError();
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
+ PRBool seekable = PR_FALSE;
+ if (hc) {
+ nsCAutoString ranges;
+ hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
+ ranges);
+ PRBool acceptsRanges = ranges.EqualsLiteral("bytes");
+
+ if (!mSeeking) {
+ // Look for duration headers from known Ogg content systems. In the case
+ // of multiple options for obtaining the duration the order of precedence is;
+ // 1) The Media resource metadata if possible (done by the decoder itself).
+ // 2) X-Content-Duration.
+ // 3) x-amz-meta-content-duration.
+ // 4) Perform a seek in the decoder to find the value.
+ nsCAutoString durationText;
+ PRInt32 ec = 0;
+ nsresult rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-Content-Duration"), durationText);
+ if (NS_FAILED(rv)) {
+ rv = hc->GetResponseHeader(NS_LITERAL_CSTRING("X-AMZ-Meta-Content-Duration"), durationText);
+ }
- // Opens the channel, using an HTTP byte range request to start at aOffset
- // if possible
- nsresult OpenAtOffset(nsIStreamListener** aStreamListener, PRInt64 aOffset);
+ if (NS_SUCCEEDED(rv)) {
+ float duration = durationText.ToFloat(&ec);
+ if (ec == NS_OK && duration >= 0) {
+ mDecoder->SetDuration(PRInt64(NS_round(duration*1000)));
+ }
+ }
+ }
+
+ PRUint32 responseStatus = 0;
+ hc->GetResponseStatus(&responseStatus);
+ if (mSeeking && responseStatus == HTTP_OK_CODE) {
+ // If we get an OK response but we were seeking, we have to assume
+ // that seeking doesn't work. We also need to tell the cache that
+ // it's getting data for the start of the stream.
+ mCacheStream.NotifyDataStarted(0);
+ } else if (!mSeeking &&
+ (responseStatus == HTTP_OK_CODE ||
+ responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
+ // We weren't seeking and got a valid response status,
+ // set the length of the content.
+ PRInt32 cl = -1;
+ hc->GetContentLength(&cl);
+ if (cl >= 0) {
+ mCacheStream.NotifyDataLength(cl);
+ }
+ }
-protected:
- // Listener attached to channel to constantly download the
- // media data asynchronously and store it in the pipe. The
- // data is obtainable via the mPipeInput member. Use on
- // main thread only.
- nsCOMPtr<nsChannelToPipeListener> mListener;
+ // If we get an HTTP_OK_CODE response to our byte range request,
+ // and the server isn't sending Accept-Ranges:bytes then we don't
+ // support seeking.
+ seekable =
+ responseStatus == HTTP_PARTIAL_RESPONSE_CODE || acceptsRanges;
+ }
+ mDecoder->SetSeekable(seekable);
+ mCacheStream.SetSeekable(seekable);
+
+ nsCOMPtr<nsICachingChannel> cc = do_QueryInterface(aRequest);
+ if (cc) {
+ PRBool fromCache = PR_FALSE;
+ nsresult rv = cc->IsFromCache(&fromCache);
+ if (NS_SUCCEEDED(rv) && !fromCache) {
+ cc->SetCacheAsFile(PR_TRUE);
+ }
+ }
+
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.Start(PR_IntervalNow());
+ }
+
+ if (mSuspendCount > 0) {
+ // Re-suspend the channel if it needs to be suspended
+ mChannel->Suspend();
+ }
+
+ // Fires an initial progress event and sets up the stall counter so stall events
+ // fire if no download occurs within the required time frame.
+ mDecoder->Progress(PR_FALSE);
+
+ return NS_OK;
+}
- // Input stream for the media data currently downloaded
- // and stored in the pipe. This can be used from any thread.
- nsCOMPtr<nsIInputStream> mPipeInput;
+nsresult
+nsMediaChannelStream::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
+
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.Stop(PR_IntervalNow());
+ }
+
+ mCacheStream.NotifyDataEnded(aStatus);
+ if (mDecoder) {
+ mDecoder->NotifyDownloadEnded(aStatus);
+ }
+ NS_ASSERTION(mSuspendCount == 0,
+ "How can OnStopRequest fire while we're suspended?");
+ mChannel = nsnull;
+ return NS_OK;
+}
- // Current seek position. Need to compute this manually due to
- // seeking with byte range requests meaning the position in the pipe
- // is not valid. This is initially set on the main thread during the
- // Open call. After that it is read and written by a single thread
- // only (the thread that calls the read/seek operations).
- PRInt64 mPosition;
+NS_METHOD
+nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ PRUint32 aToOffset,
+ PRUint32 aCount,
+ PRUint32 *aWriteCount)
+{
+ nsMediaChannelStream* stream = static_cast<nsMediaChannelStream*>(aClosure);
+ stream->mCacheStream.NotifyDataReceived(aCount, aFromSegment);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+nsresult
+nsMediaChannelStream::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aStream,
+ PRUint32 aCount)
+{
+ NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
- // PR_TRUE if the media stream requested this strategy is cancelled.
- // This is read and written on the main thread only.
- PRPackedBool mCancelled;
-};
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.AddBytes(aCount);
+ }
+
+ PRUint32 count = aCount;
+ while (count > 0) {
+ PRUint32 read;
+ nsresult rv = aStream->ReadSegments(CopySegmentToCache, this, count,
+ &read);
+ if (NS_FAILED(rv))
+ return rv;
+ 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;
+}
nsresult nsMediaChannelStream::Open(nsIStreamListener **aStreamListener)
{
- return OpenAtOffset(aStreamListener, 0);
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (!mLock)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = mCacheStream.Init();
+ if (NS_FAILED(rv))
+ return rv;
+ return OpenChannel(aStreamListener, 0);
}
-nsresult nsMediaChannelStream::OpenAtOffset(nsIStreamListener** aStreamListener,
- PRInt64 aOffset)
+nsresult nsMediaChannelStream::OpenChannel(nsIStreamListener** aStreamListener,
+ PRInt64 aOffset)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
+ NS_ASSERTION(!mListener, "Listener should have been removed by now");
if (aStreamListener) {
*aStreamListener = nsnull;
}
- mListener = new nsChannelToPipeListener(mDecoder, aOffset != 0);
- NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
+ mSeeking = aOffset != 0;
- nsresult rv = mListener->Init();
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsCOMPtr<nsIStreamListener> listener = mListener.get();
+ mListener = new Listener(this);
+ NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
if (aStreamListener) {
*aStreamListener = mListener;
NS_ADDREF(*aStreamListener);
} else {
+ nsCOMPtr<nsIStreamListener> listener = mListener.get();
+
// Ensure that if we're loading cross domain, that the server is sending
// an authorizing Access-Control header.
nsHTMLMediaElement* element = mDecoder->GetMediaElement();
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (element->ShouldCheckAllowOrigin()) {
+ nsresult rv;
listener = new nsCrossSiteListenerProxy(mListener,
element->NodePrincipal(),
- mChannel,
+ mChannel,
PR_FALSE,
&rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
} else {
- rv = nsContentUtils::GetSecurityManager()->
- CheckLoadURIWithPrincipal(element->NodePrincipal(),
- mURI,
- nsIScriptSecurityManager::STANDARD);
+ nsresult rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(element->NodePrincipal(),
+ mURI,
+ nsIScriptSecurityManager::STANDARD);
NS_ENSURE_SUCCESS(rv, rv);
+ }
- }
// Use a byte range request from the start of the resource.
// This enables us to detect if the stream supports byte range
// requests, and therefore seeking, early.
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
nsCAutoString rangeString("bytes=");
rangeString.AppendInt(aOffset);
rangeString.Append("-");
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
} else {
NS_ASSERTION(aOffset == 0, "Don't know how to seek on this channel type");
}
- rv = mChannel->AsyncOpen(listener, nsnull);
+ nsresult rv = mChannel->AsyncOpen(listener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
}
- rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
- NS_ENSURE_SUCCESS(rv, rv);
-
- mDecoder->NotifyDownloadSeeked(aOffset);
-
return NS_OK;
}
nsresult nsMediaChannelStream::Close()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
- nsAutoLock lock(mLock);
+
+ mCacheStream.Close();
+ CloseChannel();
+ return NS_OK;
+}
+
+void nsMediaChannelStream::CloseChannel()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.Stop(PR_IntervalNow());
+ }
+
+ if (mListener) {
+ mListener->Revoke();
+ mListener = nsnull;
+ }
+
if (mChannel) {
- mChannel->Cancel(NS_BINDING_ABORTED);
+ if (mSuspendCount > 0) {
+ // Resume the channel before we cancel it
+ mChannel->Resume();
+ }
+ // The status we use here won't be passed to the decoder, since
+ // we've already revoked the listener. It can however be passed
+ // to DocumentViewerImpl::LoadComplete if our channel is the one
+ // that kicked off creation of a video document. We don't want that
+ // document load to think there was an error.
+ // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
+ // at the moment.
+ mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
mChannel = nsnull;
}
- if (mPipeInput) {
- mPipeInput->Close();
- mPipeInput = nsnull;
- }
- mListener = nsnull;
- return NS_OK;
}
nsresult nsMediaChannelStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
{
- // The read request pulls from the pipe, not the channels input
- // stream. This allows calling from any thread as the pipe is
- // threadsafe.
- nsAutoLock lock(mLock);
- if (!mPipeInput)
- return NS_ERROR_FAILURE;
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
- // If Cancel() is called then the read will fail with an error so we
- // can bail out of the blocking call.
- nsresult rv = mPipeInput->Read(aBuffer, aCount, aBytes);
- NS_ENSURE_SUCCESS(rv, rv);
- mPosition += *aBytes;
-
- return rv;
+ return mCacheStream.Read(aBuffer, aCount, aBytes);
}
nsresult nsMediaChannelStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
{
- // Default streams cannot be seeked
- return NS_ERROR_FAILURE;
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+ return mCacheStream.Seek(aWhence, aOffset);
}
PRInt64 nsMediaChannelStream::Tell()
{
- return mPosition;
-}
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-void nsMediaChannelStream::Cancel()
-{
- mCancelled = PR_TRUE;
- if (mListener)
- mListener->Cancel();
-}
-
-nsIPrincipal* nsMediaChannelStream::GetCurrentPrincipal()
-{
- if (!mListener)
- return nsnull;
-
- return mListener->GetCurrentPrincipal();
+ return mCacheStream.Tell();
}
void nsMediaChannelStream::Suspend()
{
- mChannel->Suspend();
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on main thread");
+
+ if (mSuspendCount == 0 && mChannel) {
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.Stop(PR_IntervalNow());
+ }
+ mChannel->Suspend();
+ }
+ ++mSuspendCount;
}
void nsMediaChannelStream::Resume()
{
- mChannel->Resume();
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on main thread");
+
+ --mSuspendCount;
+ if (mSuspendCount == 0 && mChannel) {
+ {
+ nsAutoLock lock(mLock);
+ mChannelStatistics.Start(PR_IntervalNow());
+ }
+ mChannel->Resume();
+ // XXX need to do something fancier here because we often won't
+ // be able to resume cleanly
+ }
+}
+
+nsresult
+nsMediaChannelStream::CacheClientSeek(PRInt64 aOffset)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Don't call on main thread");
+
+ CloseChannel();
+ if (!mDecoder->GetMediaElement()) {
+ // The decoder is being shut down, so don't bother opening a new channel
+ return NS_OK;
+ }
+
+ nsresult rv =
+ NS_NewChannel(getter_AddRefs(mChannel), mURI, nsnull, nsnull, nsnull,
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
+ if (NS_FAILED(rv))
+ return rv;
+ return OpenChannel(nsnull, aOffset);
+}
+
+class SuspendedStatusChanged : public nsRunnable
+{
+public:
+ SuspendedStatusChanged(nsMediaDecoder* aDecoder) :
+ mDecoder(aDecoder)
+ {
+ MOZ_COUNT_CTOR(SuspendedStatusChanged);
+ }
+ ~SuspendedStatusChanged()
+ {
+ MOZ_COUNT_DTOR(SuspendedStatusChanged);
+ }
+
+ NS_IMETHOD Run() {
+ mDecoder->NotifySuspendedStatusChanged();
+ return NS_OK;
+ }
+
+private:
+ nsRefPtr<nsMediaDecoder> mDecoder;
+};
+
+nsresult
+nsMediaChannelStream::CacheClientSuspend()
+{
+ {
+ nsAutoLock lock(mLock);
+ ++mCacheSuspendCount;
+ }
+ Suspend();
+
+ // We have to spawn an event here since we're being called back from
+ // a sensitive place in nsMediaCache, which doesn't want us to reenter
+ // the decoder and cause deadlocks or other unpleasantness
+ nsCOMPtr<nsIRunnable> event = new SuspendedStatusChanged(mDecoder);
+ NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult
+nsMediaChannelStream::CacheClientResume()
+{
+ Resume();
+ {
+ nsAutoLock lock(mLock);
+ --mCacheSuspendCount;
+ }
+
+ // We have to spawn an event here since we're being called back from
+ // a sensitive place in nsMediaCache, which doesn't want us to reenter
+ // the decoder and cause deadlocks or other unpleasantness
+ nsCOMPtr<nsIRunnable> event = new SuspendedStatusChanged(mDecoder);
+ NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+PRInt64
+nsMediaChannelStream::GetCachedDataEnd(PRInt64 aOffset)
+{
+ return mCacheStream.GetCachedDataEnd(aOffset);
+}
+
+PRBool
+nsMediaChannelStream::IsDataCachedToEndOfStream(PRInt64 aOffset)
+{
+ return mCacheStream.IsDataCachedToEndOfStream(aOffset);
+}
+
+PRBool
+nsMediaChannelStream::IsSuspendedByCache()
+{
+ nsAutoLock lock(mLock);
+ return mCacheSuspendCount > 0;
+}
+
+void
+nsMediaChannelStream::SetReadMode(nsMediaCacheStream::ReadMode aMode)
+{
+ mCacheStream.SetReadMode(aMode);
+}
+
+void
+nsMediaChannelStream::SetPlaybackRate(PRUint32 aBytesPerSecond)
+{
+ mCacheStream.SetPlaybackRate(aBytesPerSecond);
+}
+
+void
+nsMediaChannelStream::Pin()
+{
+ mCacheStream.Pin();
+}
+
+void
+nsMediaChannelStream::Unpin()
+{
+ mCacheStream.Unpin();
+}
+
+double
+nsMediaChannelStream::GetDownloadRate(PRPackedBool* aIsReliable)
+{
+ nsAutoLock lock(mLock);
+ return mChannelStatistics.GetRate(PR_IntervalNow(), aIsReliable);
+}
+
+PRInt64
+nsMediaChannelStream::GetLength()
+{
+ return mCacheStream.GetLength();
}
class nsMediaFileStream : public nsMediaStream
{
public:
nsMediaFileStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
- nsMediaStream(aDecoder, aChannel, aURI)
+ nsMediaStream(aDecoder, aChannel, aURI), mSize(-1),
+ mLock(nsAutoLock::NewLock("media.file.stream"))
{
}
-
+ ~nsMediaFileStream()
+ {
+ if (mLock) {
+ nsAutoLock::DestroyLock(mLock);
+ }
+ }
+
+ // Main thread
virtual nsresult Open(nsIStreamListener** aStreamListener);
virtual nsresult Close();
+ virtual void Suspend() {}
+ virtual void Resume() {}
+
+ // These methods are called off the main thread.
+
+ // Other thread
+ virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) {}
+ virtual void SetPlaybackRate(PRUint32 aBytesPerSecond) {}
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
virtual PRInt64 Tell();
- virtual nsIPrincipal* GetCurrentPrincipal();
- virtual void Suspend();
- virtual void Resume();
+
+ // Any thread
+ virtual void Pin() {}
+ virtual void Unpin() {}
+ virtual double GetDownloadRate(PRPackedBool* aIsReliable)
+ {
+ // The data's all already here
+ *aIsReliable = PR_TRUE;
+ return 100*1024*1024; // arbitray, use 100MB/s
+ }
+ virtual PRInt64 GetLength() { return mSize; }
+ virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) { return PR_MAX(aOffset, mSize); }
+ virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset) { return PR_TRUE; }
+ virtual PRBool IsSuspendedByCache() { return PR_FALSE; }
private:
+ // The file size, or -1 if not known. Immutable after Open().
+ PRInt64 mSize;
+
+ // This lock handles synchronisation between calls to Close() and
+ // the Read, Seek, etc calls. Close must not be called while a
+ // Read or Seek is in progress since it resets various internal
+ // values to null.
+ // This lock protects mSeekable and mInput.
+ PRLock* mLock;
+
// Seekable stream interface to file. This can be used from any
// thread.
nsCOMPtr<nsISeekableStream> mSeekable;
// Input stream for the media data. This can be used from any
// thread.
nsCOMPtr<nsIInputStream> mInput;
-
- // Security Principal
- nsCOMPtr<nsIPrincipal> mPrincipal;
};
class LoadedEvent : public nsRunnable
{
public:
- LoadedEvent(nsMediaDecoder* aDecoder, PRInt64 aOffset, PRInt64 aSize) :
- mOffset(aOffset), mSize(aSize), mDecoder(aDecoder)
+ LoadedEvent(nsMediaDecoder* aDecoder) :
+ mDecoder(aDecoder)
{
MOZ_COUNT_CTOR(LoadedEvent);
}
~LoadedEvent()
{
MOZ_COUNT_DTOR(LoadedEvent);
}
NS_IMETHOD Run() {
- if (mOffset >= 0) {
- mDecoder->NotifyDownloadSeeked(mOffset);
- }
- if (mSize > 0) {
- mDecoder->NotifyBytesDownloaded(mSize);
- }
mDecoder->NotifyDownloadEnded(NS_OK);
return NS_OK;
}
private:
- PRInt64 mOffset;
- PRInt64 mSize;
nsRefPtr<nsMediaDecoder> mDecoder;
};
nsresult nsMediaFileStream::Open(nsIStreamListener** aStreamListener)
{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
if (aStreamListener) {
*aStreamListener = nsnull;
}
nsresult rv;
if (aStreamListener) {
// The channel is already open. We need a synchronous stream that
// implements nsISeekableStream, so we have to find the underlying
@@ -346,50 +711,36 @@ nsresult nsMediaFileStream::Open(nsIStre
if (!mSeekable) {
// XXX The file may just be a .url or similar
// shortcut that points to a Web site. We need to fix this by
// doing an async open and waiting until we locate the real resource,
// then using that (if it's still a file!).
return NS_ERROR_FAILURE;
}
- /* Get our principal */
- nsCOMPtr<nsIScriptSecurityManager> secMan =
- do_GetService("@mozilla.org/scriptsecuritymanager;1");
- if (secMan) {
- rv = secMan->GetChannelPrincipal(mChannel,
- getter_AddRefs(mPrincipal));
- if (NS_FAILED(rv)) {
- return rv;
- }
- }
-
// Get the file size and inform the decoder. Only files up to 4GB are
// supported here.
PRUint32 size;
rv = mInput->Available(&size);
if (NS_SUCCEEDED(rv)) {
- mDecoder->SetTotalBytes(size);
+ mSize = size;
}
- // This must happen before we return from this function, we can't
- // defer it to the LoadedEvent because that would allow reads from
- // the stream to complete before this notification is sent.
- mDecoder->NotifyBytesDownloaded(size);
-
- nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder, -1, 0);
+ nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
return NS_OK;
}
nsresult nsMediaFileStream::Close()
{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
nsAutoLock lock(mLock);
if (mChannel) {
- mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
mChannel = nsnull;
mInput = nsnull;
mSeekable = nsnull;
}
return NS_OK;
}
@@ -397,304 +748,81 @@ nsresult nsMediaFileStream::Read(char* a
{
nsAutoLock lock(mLock);
if (!mInput)
return NS_ERROR_FAILURE;
return mInput->Read(aBuffer, aCount, aBytes);
}
nsresult nsMediaFileStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
-{
- PRUint32 size = 0;
- PRInt64 absoluteOffset = 0;
- nsresult rv;
- {
- nsAutoLock lock(mLock);
- if (!mSeekable)
- return NS_ERROR_FAILURE;
- rv = mSeekable->Seek(aWhence, aOffset);
- if (NS_SUCCEEDED(rv)) {
- mSeekable->Tell(&absoluteOffset);
- }
- mInput->Available(&size);
- }
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
- if (NS_SUCCEEDED(rv)) {
- nsCOMPtr<nsIRunnable> event = new LoadedEvent(mDecoder, absoluteOffset, size);
- // Synchronous dispatch to ensure the decoder is notified before our caller
- // proceeds and reads occur.
- NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
- }
-
- return rv;
+ nsAutoLock lock(mLock);
+ if (!mSeekable)
+ return NS_ERROR_FAILURE;
+ return mSeekable->Seek(aWhence, aOffset);
}
PRInt64 nsMediaFileStream::Tell()
{
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
nsAutoLock lock(mLock);
if (!mSeekable)
return 0;
PRInt64 offset = 0;
mSeekable->Tell(&offset);
return offset;
}
-nsIPrincipal* nsMediaFileStream::GetCurrentPrincipal()
-{
- return mPrincipal;
-}
-
-void nsMediaFileStream::Suspend()
-{
- mChannel->Suspend();
-}
-
-void nsMediaFileStream::Resume()
-{
- mChannel->Resume();
-}
-
-class nsMediaHttpStream : public nsMediaChannelStream
-{
-public:
- nsMediaHttpStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
- nsMediaChannelStream(aDecoder, aChannel, aURI),
- mAtEOF(PR_FALSE)
- {
- }
-
- virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
- virtual PRInt64 Tell();
-
- // This must be called on the main thread only, and at a time when the
- // strategy is not reading from the current channel/stream. It's primary
- // purpose is to be called from a Seek to reset to the new byte range
- // request HTTP channel.
- nsresult OpenInternal(nsIChannel* aChannel, PRInt64 aOffset);
-
-private:
- // PR_TRUE if we are positioned at the end of the file.
- // This is written and read from a single thread only (the thread that
- // calls the read/seek operations).
- PRPackedBool mAtEOF;
-};
-
-nsresult nsMediaHttpStream::OpenInternal(nsIChannel* aChannel,
- PRInt64 aOffset)
-{
- nsAutoLock lock(mLock);
- mChannel = aChannel;
- return OpenAtOffset(static_cast<nsIStreamListener**>(nsnull), aOffset);
-}
-
-class nsByteRangeEvent : public nsRunnable
-{
-public:
- nsByteRangeEvent(nsMediaHttpStream* aStrategy,
- nsIURI* aURI,
- PRInt64 aOffset) :
- mStrategy(aStrategy),
- mURI(aURI),
- mOffset(aOffset),
- mResult(NS_OK)
- {
- MOZ_COUNT_CTOR(nsByteRangeEvent);
- }
-
- ~nsByteRangeEvent()
- {
- MOZ_COUNT_DTOR(nsByteRangeEvent);
- }
-
- nsresult GetResult()
- {
- return mResult;
- }
-
- NS_IMETHOD Run() {
- // This event runs in the main thread. The same
- // thread as that which can block waiting for the
- // decode event to complete when the stream
- // is cancelled. Check to see if we are cancelled
- // in case this event is processed after the cancel flag
- // which would otherwise cause the listener to be recreated.
- if (mStrategy->IsCancelled()) {
- mResult = NS_ERROR_FAILURE;
- return NS_OK;
- }
-
- nsCOMPtr<nsIChannel> channel;
- mStrategy->Close();
- mResult = NS_NewChannel(getter_AddRefs(channel),
- mURI,
- nsnull,
- nsnull,
- nsnull,
- nsIRequest::LOAD_NORMAL);
- NS_ENSURE_SUCCESS(mResult, mResult);
- mResult = mStrategy->OpenInternal(channel, mOffset);
- return NS_OK;
- }
-
-private:
- nsMediaHttpStream* mStrategy;
- nsMediaDecoder* mDecoder;
- nsIURI* mURI;
- PRInt64 mOffset;
- nsresult mResult;
-};
-
-nsresult nsMediaHttpStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
-{
- PRInt64 totalBytes = mDecoder->GetStatistics().mTotalBytes;
- {
- nsAutoLock lock(mLock);
- if (!mChannel || !mPipeInput)
- return NS_ERROR_FAILURE;
-
- // When seeking liboggz will first seek to the end of the file to
- // obtain the length of the file. It immediately does a 'tell' to
- // get the position and reseeks somewhere else. This traps the seek
- // to end of file and sets mAtEOF. Tell() looks for this flag being
- // set and returns the content length.
- if(aWhence == nsISeekableStream::NS_SEEK_END && aOffset == 0) {
- if (totalBytes == -1)
- return NS_ERROR_FAILURE;
-
- mAtEOF = PR_TRUE;
- return NS_OK;
- }
- else {
- mAtEOF = PR_FALSE;
- }
-
- // Handle cases of aWhence not being NS_SEEK_SET by converting to
- // NS_SEEK_SET
- switch (aWhence) {
- case nsISeekableStream::NS_SEEK_END: {
- if (totalBytes == -1)
- return NS_ERROR_FAILURE;
-
- aOffset += totalBytes;
- aWhence = nsISeekableStream::NS_SEEK_SET;
- break;
- }
- case nsISeekableStream::NS_SEEK_CUR: {
- aOffset += mPosition;
- aWhence = nsISeekableStream::NS_SEEK_SET;
- break;
- }
- default:
- // Do nothing, we are NS_SEEK_SET
- break;
- };
-
- // If we are already at the correct position, do nothing
- if (aOffset == mPosition) {
- return NS_OK;
- }
-
- // If we are seeking to a byterange that we already have buffered in
- // the listener then move to that and avoid the need to send a byte
- // range request.
- PRInt32 bytesAhead = aOffset - mPosition;
- PRUint32 available = 0;
- nsresult rv = mPipeInput->Available(&available);
- PRInt32 diff = available - PRUint32(bytesAhead);
-
- // If we have enough buffered data to satisfy the seek request
- // just do a read and discard the data.
- // If the seek request is for a small amount ahead (defined by
- // SEEK_VS_READ_THRESHOLD) then also perform a read and discard
- // instead of doing a byte range request. This improves the speed
- // of the seeks quite a bit.
- if (NS_SUCCEEDED(rv) && bytesAhead > 0 && diff > -SEEK_VS_READ_THRESHOLD) {
- nsAutoArrayPtr<char> data(new char[bytesAhead]);
- if (!data)
- return NS_ERROR_OUT_OF_MEMORY;
- // Read until the read cursor reaches new seek point. If Cancel() is
- // called then the read will fail with an error so we can bail out of
- // the blocking call.
- PRInt32 bytesRead = 0;
- PRUint32 bytes = 0;
- do {
- nsresult rv = mPipeInput->Read(data.get(),
- (bytesAhead-bytesRead),
- &bytes);
- NS_ENSURE_SUCCESS(rv, rv);
- NS_ENSURE_TRUE(bytes != 0, NS_ERROR_FAILURE); // Tried to read past EOF.
- mPosition += bytes;
- bytesRead += bytes;
- } while (bytesRead != bytesAhead);
-
- // We don't need to notify the decoder here that we seeked, just that
- // we read ahead. In fact, we mustn't tell the decoder that we seeked,
- // since the seek notification might race with the "data downloaded"
- // notification after the data was written into the pipe, so that the
- // seek notification happens *first*, hopelessly confusing the
- // decoder.
- mDecoder->NotifyBytesConsumed(bytesRead);
- return rv;
- }
- }
-
- // Don't acquire mLock in this scope as we do a synchronous call to the main thread
- // which would deadlock if that thread is calling Close().
- nsCOMPtr<nsByteRangeEvent> event = new nsByteRangeEvent(this, mURI, aOffset);
- NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
-
- // If the sync request fails, or a call to Cancel() is made during the request,
- // don't update the position and return the error.
- nsresult rv = event->GetResult();
- if (NS_SUCCEEDED(rv)) {
- mPosition = aOffset;
- }
-
- return rv;
-}
-
-PRInt64 nsMediaHttpStream::Tell()
-{
- // Handle the case of a seek to EOF by liboggz
- // (See Seek for details)
- return mAtEOF ? mDecoder->GetStatistics().mTotalBytes : mPosition;
-}
-
nsresult
nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
nsIChannel* aChannel, nsMediaStream** aStream,
nsIStreamListener** aListener)
{
NS_ASSERTION(NS_IsMainThread(),
- "nsMediaStream::Open called on non-main thread");
+ "nsMediaStream::Open called on non-main thread");
+
+ *aStream = nsnull;
nsCOMPtr<nsIChannel> channel;
if (aChannel) {
channel = aChannel;
} else {
nsresult rv = NS_NewChannel(getter_AddRefs(channel),
aURI,
nsnull,
nsnull,
nsnull,
- nsIRequest::LOAD_NORMAL);
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
NS_ENSURE_SUCCESS(rv, rv);
}
nsMediaStream* stream;
- nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
- if (hc) {
- stream = new nsMediaHttpStream(aDecoder, channel, aURI);
+ nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(channel);
+ if (fc) {
+ stream = new nsMediaFileStream(aDecoder, channel, aURI);
} else {
- nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(channel);
- if (fc) {
- stream = new nsMediaFileStream(aDecoder, channel, aURI);
- } else {
- stream = new nsMediaChannelStream(aDecoder, channel, aURI);
- }
+ stream = new nsMediaChannelStream(aDecoder, channel, aURI);
}
if (!stream)
return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = stream->Open(aListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
*aStream = stream;
- return stream->Open(aListener);
+ return NS_OK;
}
+
+already_AddRefed<nsIPrincipal> nsMediaStream::GetCurrentPrincipal()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ if (!secMan || !mChannel)
+ return nsnull;
+ secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
+ return principal.forget();
+}
--- a/content/media/video/src/nsOggDecoder.cpp
+++ b/content/media/video/src/nsOggDecoder.cpp
@@ -293,20 +293,16 @@ public:
// Called from the main thread to get the current frame time. The decoder
// monitor must be obtained before calling this.
float GetCurrentTime();
// Called from the main thread to get the duration. The decoder monitor
// must be obtained before calling this. It is in units of milliseconds.
PRInt64 GetDuration();
- // Called from the main thread to set the content length of the media
- // resource. The decoder monitor must be obtained before calling this.
- void SetContentLength(PRInt64 aLength);
-
// Called from the main thread to set the duration of the media resource
// if it is able to be obtained via HTTP headers. The decoder monitor
// must be obtained before calling this.
void SetDuration(PRInt64 aDuration);
// Called from the main thread to set whether the media resource can
// be seeked. The decoder monitor must be obtained before calling this.
void SetSeekable(PRBool aSeekable);
@@ -482,21 +478,16 @@ private:
// monitor.
float mVolume;
// Duration of the media resource. It is accessed from the decoder and main
// threads. Synchronised via decoder monitor. It is in units of
// milliseconds.
PRInt64 mDuration;
- // Content Length of the media resource if known. If it is -1 then the
- // size is unknown. Accessed from the decoder and main threads. Synchronised
- // via decoder monitor.
- PRInt64 mContentLength;
-
// PR_TRUE if the media resource can be seeked. Accessed from the decoder
// and main threads. Synchronised via decoder monitor.
PRPackedBool mSeekable;
// PR_TRUE if an event to notify about a change in the playback
// position has been queued, but not yet run. It is set to PR_FALSE when
// the event is run. This allows coalescing of these events as they can be
// produced many times per second. Synchronised via decoder monitor.
@@ -520,17 +511,16 @@ nsOggDecodeStateMachine::nsOggDecodeStat
mBufferingEndOffset(0),
mLastFrameTime(0),
mLastFramePosition(-1),
mState(DECODER_STATE_DECODING_METADATA),
mSeekTime(0.0),
mCurrentFrameTime(0.0),
mVolume(1.0),
mDuration(-1),
- mContentLength(-1),
mSeekable(PR_TRUE),
mPositionChangeQueued(PR_FALSE)
{
}
nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
{
while (!mDecodedFrames.IsEmpty()) {
@@ -562,16 +552,17 @@ nsOggDecodeStateMachine::FrameData* nsOg
mLastFrameTime += mCallbackPeriod;
if (mLastFramePosition >= 0) {
NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
"Playback positions must not decrease without an intervening reset");
mDecoder->mPlaybackStatistics.Start(frame->mTime*PR_TicksPerSecond());
mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
mDecoder->mPlaybackStatistics.Stop(mLastFrameTime*PR_TicksPerSecond());
+ mDecoder->UpdatePlaybackRate();
}
mLastFramePosition = frame->mEndStreamPosition;
int num_tracks = oggplay_get_num_tracks(mPlayer);
float audioTime = 0.0;
float videoTime = 0.0;
if (mVideoTrack != -1 &&
@@ -852,22 +843,16 @@ float nsOggDecodeStateMachine::GetCurren
}
PRInt64 nsOggDecodeStateMachine::GetDuration()
{
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetDuration() called without acquiring decoder monitor");
return mDuration;
}
-void nsOggDecodeStateMachine::SetContentLength(PRInt64 aLength)
-{
- // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetContentLength() called without acquiring decoder monitor");
- mContentLength = aLength;
-}
-
void nsOggDecodeStateMachine::SetDuration(PRInt64 aDuration)
{
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetDuration() called without acquiring decoder monitor");
mDuration = aDuration;
}
void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable)
{
@@ -964,17 +949,18 @@ nsresult nsOggDecodeStateMachine::Run()
}
break;
case DECODER_STATE_DECODING:
{
PRBool bufferExhausted = PR_FALSE;
if (!mDecodedFrames.IsFull()) {
- PRInt64 initialDownloadPosition = mDecoder->mDownloadPosition;
+ PRInt64 initialDownloadPosition =
+ mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
mon.Exit();
OggPlayErrorCode r = DecodeFrame();
mon.Enter();
// Check whether decoding that frame required us to read data
// that wasn't available at the start of the frame. That means
// we should probably start buffering.
@@ -1001,18 +987,18 @@ nsresult nsOggDecodeStateMachine::Run()
// Show at least the first frame if we're not playing
// so we have a poster frame on initial load and after seek.
if (!mPlaying && !mDecodedFrames.IsEmpty()) {
PlayVideo(mDecodedFrames.Peek());
}
if (bufferExhausted && mState == DECODER_STATE_DECODING &&
mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
- (mDecoder->mTotalBytes < 0 ||
- mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
+ !mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+ !mDecoder->mReader->Stream()->IsSuspendedByCache()) {
// There is at most one frame in the queue and there's
// more data to load. Let's buffer to make sure we can play a
// decent amount of video in the future.
if (mPlaying) {
StopPlayback();
}
// We need to tell the element that buffering has started.
@@ -1024,18 +1010,19 @@ nsresult nsOggDecodeStateMachine::Run()
// we just trigger UpdateReadyStateForData; when it runs, it
// will check the current state and decide whether to tell
// the element we're buffering or not.
nsCOMPtr<nsIRunnable> event =
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, UpdateReadyStateForData);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
mBufferingStart = PR_IntervalNow();
- double playbackRate = mDecoder->GetStatistics().mPlaybackRate;
- mBufferingEndOffset = mDecoder->mDownloadPosition +
+ PRPackedBool reliable;
+ double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
+ mBufferingEndOffset = mDecoder->mDecoderPosition +
BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
mState = DECODER_STATE_BUFFERING;
LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
} else {
PlayFrame();
}
}
break;
@@ -1066,18 +1053,18 @@ nsresult nsOggDecodeStateMachine::Run()
// to start reading from them again.
for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
if (oggplay_set_track_active(mPlayer, i) < 0) {
LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
}
}
mon.Enter();
- mLastFramePosition = mDecoder->mDecoderPosition;
mDecoder->StartProgressUpdates();
+ mLastFramePosition = mDecoder->mPlaybackPosition;
if (mState == DECODER_STATE_SHUTDOWN)
continue;
// Remove all frames decoded prior to seek from the queue
while (!mDecodedFrames.IsEmpty()) {
delete mDecodedFrames.Pop();
}
@@ -1111,21 +1098,22 @@ nsresult nsOggDecodeStateMachine::Run()
}
}
break;
case DECODER_STATE_BUFFERING:
{
PRIntervalTime now = PR_IntervalNow();
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < BUFFERING_WAIT*1000) &&
- mDecoder->mDownloadPosition < mBufferingEndOffset &&
- (mDecoder->mTotalBytes < 0 || mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
+ mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
+ !mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
+ !mDecoder->mReader->Stream()->IsSuspendedByCache()) {
LOG(PR_LOG_DEBUG,
("In buffering: buffering data until %d bytes available or %d milliseconds",
- PRUint32(mBufferingEndOffset - mDecoder->mDownloadPosition),
+ PRUint32(mBufferingEndOffset - mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(now - mBufferingStart))));
mon.Wait(PR_MillisecondsToInterval(1000));
if (mState == DECODER_STATE_SHUTDOWN)
continue;
} else {
LOG(PR_LOG_DEBUG, ("Changed state from BUFFERING to DECODING"));
mState = DECODER_STATE_DECODING;
}
@@ -1232,29 +1220,30 @@ void nsOggDecodeStateMachine::LoadOggHea
// Get the duration from the Ogg file. We only do this if the
// content length of the resource is known as we need to seek
// to the end of the file to get the last time field. We also
// only do this if the resource is seekable and if we haven't
// already obtained the duration via an HTTP header.
{
nsAutoMonitor mon(mDecoder->GetMonitor());
if (mState != DECODER_STATE_SHUTDOWN &&
- mContentLength >= 0 &&
+ aReader->Stream()->GetLength() >= 0 &&
mSeekable &&
mDuration == -1) {
mDecoder->StopProgressUpdates();
// Don't hold the monitor during the duration
// call as it can issue seek requests
// and blocks until these are completed.
mon.Exit();
PRInt64 d = oggplay_get_duration(mPlayer);
oggplay_seek(mPlayer, 0);
mon.Enter();
mDuration = d;
mDecoder->StartProgressUpdates();
+ mDecoder->UpdatePlaybackRate();
}
if (mState == DECODER_STATE_SHUTDOWN)
return;
}
// Inform the element that we've loaded the Ogg metadata
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded);
@@ -1292,29 +1281,26 @@ float nsOggDecoder::GetDuration()
return static_cast<float>(mDuration) / 1000.0;
}
return std::numeric_limits<float>::quiet_NaN();
}
nsOggDecoder::nsOggDecoder() :
nsMediaDecoder(),
- mTotalBytes(-1),
- mDownloadPosition(0),
- mProgressPosition(0),
mDecoderPosition(0),
mPlaybackPosition(0),
mCurrentTime(0.0),
mInitialVolume(0.0),
mRequestedSeekTime(-1.0),
mDuration(-1),
mNotifyOnShutdown(PR_FALSE),
mSeekable(PR_TRUE),
- mReader(0),
- mMonitor(0),
+ mReader(nsnull),
+ mMonitor(nsnull),
mPlayState(PLAY_STATE_PAUSED),
mNextState(PLAY_STATE_PAUSED),
mResourceLoaded(PR_FALSE),
mIgnoreProgressData(PR_FALSE)
{
MOZ_COUNT_CTOR(nsOggDecoder);
}
@@ -1343,18 +1329,16 @@ nsOggDecoder::~nsOggDecoder()
nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
nsIStreamListener** aStreamListener)
{
// Reset Stop guard flag flag, else shutdown won't occur properly when
// reusing decoder.
mStopping = PR_FALSE;
// Reset progress member variables
- mDownloadPosition = 0;
- mProgressPosition = 0;
mDecoderPosition = 0;
mPlaybackPosition = 0;
mResourceLoaded = PR_FALSE;
NS_ASSERTION(!mReader, "Didn't shutdown properly!");
NS_ASSERTION(!mDecodeStateMachine, "Didn't shutdown properly!");
NS_ASSERTION(!mDecodeThread, "Didn't shutdown properly!");
@@ -1375,29 +1359,36 @@ nsresult nsOggDecoder::Load(nsIURI* aURI
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI));
NS_ENSURE_SUCCESS(rv, rv);
}
RegisterShutdownObserver();
mReader = new nsChannelReader();
NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
- mDownloadStatistics.Reset();
- mDownloadStatistics.Start(PR_IntervalNow());
- nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
- NS_ENSURE_SUCCESS(rv, rv);
+ {
+ nsAutoMonitor mon(mMonitor);
+ // Hold the lock while we do this to set proper lock ordering
+ // expectations for dynamic deadlock detectors: decoder lock(s)
+ // should be grabbed before the cache lock
+ nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
+ if (NS_FAILED(rv)) {
+ // Free the failed-to-initialize reader so we don't try to use it.
+ mReader = nsnull;
+ return rv;
+ }
+ }
- rv = NS_NewThread(getter_AddRefs(mDecodeThread));
+ nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
NS_ENSURE_SUCCESS(rv, rv);
mDecodeStateMachine = new nsOggDecodeStateMachine(this);
{
nsAutoMonitor mon(mMonitor);
- mDecodeStateMachine->SetContentLength(mTotalBytes);
mDecodeStateMachine->SetSeekable(mSeekable);
}
ChangeState(PLAY_STATE_LOADING);
return mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
}
@@ -1483,23 +1474,20 @@ void nsOggDecoder::Stop()
if (mStopping)
return;
mStopping = PR_TRUE;
ChangeState(PLAY_STATE_ENDED);
StopProgress();
- mDownloadStatistics.Stop(PR_IntervalNow());
// Force any outstanding seek and byterange requests to complete
// to prevent shutdown from deadlocking.
- if (mReader) {
- mReader->Cancel();
- }
+ mReader->Stream()->Close();
// Shutdown must be on called the mDecodeStateMachine before deleting.
// This is required to ensure that the state machine isn't running
// in the thread and using internal objects when it is deleted.
if (mDecodeStateMachine) {
mDecodeStateMachine->Shutdown();
}
@@ -1533,23 +1521,21 @@ float nsOggDecoder::GetCurrentTime()
return mCurrentTime;
}
void nsOggDecoder::GetCurrentURI(nsIURI** aURI)
{
NS_IF_ADDREF(*aURI = mURI);
}
-nsIPrincipal* nsOggDecoder::GetCurrentPrincipal()
+already_AddRefed<nsIPrincipal> nsOggDecoder::GetCurrentPrincipal()
{
- if (!mReader) {
+ if (!mReader)
return nsnull;
- }
-
- return mReader->GetCurrentPrincipal();
+ return mReader->Stream()->GetCurrentPrincipal();
}
void nsOggDecoder::MetadataLoaded()
{
if (mShuttingDown)
return;
// Only inform the element of MetadataLoaded if not doing a load() in order
@@ -1605,17 +1591,18 @@ void nsOggDecoder::FirstFrameLoaded()
if (mRequestedSeekTime >= 0.0) {
ChangeState(PLAY_STATE_SEEKING);
}
else {
ChangeState(mNextState);
}
}
- if (!mResourceLoaded && mDownloadPosition == mTotalBytes) {
+ if (!mResourceLoaded && mReader &&
+ mReader->Stream()->IsDataCachedToEndOfStream(mDecoderPosition)) {
ResourceLoaded();
}
}
void nsOggDecoder::ResourceLoaded()
{
// Don't handle ResourceLoaded if we are shutting down, or if
// we need to ignore progress data due to seeking (in the case
@@ -1628,20 +1615,16 @@ void nsOggDecoder::ResourceLoaded()
// If we are seeking or loading then the resource loaded notification we get
// should be ignored, since it represents the end of the seek request.
nsAutoMonitor mon(mMonitor);
if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
return;
Progress(PR_FALSE);
- // Note that mTotalBytes should not be -1 now; NotifyDownloadEnded
- // should have set it to the download position.
- NS_ASSERTION(mDownloadPosition == mTotalBytes, "Wrong byte count");
-
mResourceLoaded = PR_TRUE;
StopProgress();
}
// Ensure the final progress event gets fired
if (mElement) {
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
mElement->ResourceLoaded();
@@ -1691,95 +1674,91 @@ NS_IMETHODIMP nsOggDecoder::Observe(nsIS
}
nsMediaDecoder::Statistics
nsOggDecoder::GetStatistics()
{
Statistics result;
nsAutoMonitor mon(mMonitor);
- result.mDownloadRate =
- mDownloadStatistics.GetRate(PR_IntervalNow(), &result.mDownloadRateReliable);
- if (mDuration >= 0 && mTotalBytes >= 0) {
- result.mPlaybackRate = double(mTotalBytes)*1000.0/mDuration;
- result.mPlaybackRateReliable = PR_TRUE;
+ if (mReader) {
+ result.mDownloadRate =
+ mReader->Stream()->GetDownloadRate(&result.mDownloadRateReliable);
+ result.mDownloadPosition =
+ mReader->Stream()->GetCachedDataEnd(mDecoderPosition);
+ result.mTotalBytes = mReader->Stream()->GetLength();
+ result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable);
+ result.mDecoderPosition = mDecoderPosition;
+ result.mPlaybackPosition = mPlaybackPosition;
} else {
- result.mPlaybackRate =
- mPlaybackStatistics.GetRateAtLastStop(&result.mPlaybackRateReliable);
+ result.mDownloadRate = 0;
+ result.mDownloadRateReliable = PR_TRUE;
+ result.mPlaybackRate = 0;
+ result.mPlaybackRateReliable = PR_TRUE;
+ result.mDecoderPosition = 0;
+ result.mPlaybackPosition = 0;
+ result.mDownloadPosition = 0;
+ result.mTotalBytes = 0;
}
- result.mTotalBytes = mTotalBytes;
- // Use mProgressPosition here because we don't want changes in
- // mDownloadPosition due to intermediate seek operations etc to be
- // reported in progress events
- result.mDownloadPosition = mProgressPosition;
- result.mDecoderPosition = mDecoderPosition;
- result.mPlaybackPosition = mPlaybackPosition;
+
return result;
}
-void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
+double nsOggDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
{
- nsAutoMonitor mon(mMonitor);
+ PRInt64 length = mReader ? mReader->Stream()->GetLength() : -1;
+ if (mDuration >= 0 && length >= 0) {
+ *aReliable = PR_TRUE;
+ return double(length)*1000.0/mDuration;
+ }
+ return mPlaybackStatistics.GetRateAtLastStop(aReliable);
+}
- // Servers could lie to us about the size of the resource, so make
- // sure we don't set mTotalBytes to less than what we've already
- // downloaded
- mTotalBytes = PR_MAX(mDownloadPosition, aBytes);
- if (mDecodeStateMachine) {
- mDecodeStateMachine->SetContentLength(mTotalBytes);
+void nsOggDecoder::UpdatePlaybackRate()
+{
+ if (!mReader)
+ return;
+ PRPackedBool reliable;
+ PRUint32 rate = PRUint32(ComputePlaybackRate(&reliable));
+ if (!reliable) {
+ // Set a minimum rate of 10,000 bytes per second ... sometimes we just
+ // don't have good data
+ rate = PR_MAX(rate, 10000);
+ }
+ mReader->Stream()->SetPlaybackRate(rate);
+}
+
+void nsOggDecoder::NotifySuspendedStatusChanged()
+{
+ NS_ASSERTION(NS_IsMainThread(),
+ "nsOggDecoder::NotifyDownloadSuspended called on non-main thread");
+ if (!mReader)
+ return;
+ if (mReader->Stream()->IsSuspendedByCache() && mElement) {
+ // if this is an autoplay element, we need to kick off its autoplaying
+ // now so we consume data and hopefully free up cache space
+ mElement->NotifyAutoplayDataReady();
}
}
-void nsOggDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
+void nsOggDecoder::NotifyBytesDownloaded()
{
- NS_ASSERTION(NS_IsMainThread(),
+ NS_ASSERTION(NS_IsMainThread(),
"nsOggDecoder::NotifyBytesDownloaded called on non-main thread");
- {
- nsAutoMonitor mon(mMonitor);
-
- mDownloadPosition += aBytes;
- if (mTotalBytes >= 0) {
- // Ensure that mDownloadPosition <= mTotalBytes
- mTotalBytes = PR_MAX(mTotalBytes, mDownloadPosition);
- }
- if (!mIgnoreProgressData) {
- mDownloadStatistics.AddBytes(aBytes);
- mProgressPosition = mDownloadPosition;
- }
- }
-
UpdateReadyStateForData();
}
-void nsOggDecoder::NotifyDownloadSeeked(PRInt64 aOffsetBytes)
-{
- nsAutoMonitor mon(mMonitor);
- // Don't change mProgressPosition here, since mIgnoreProgressData is set
- mDownloadPosition = mDecoderPosition = mPlaybackPosition = aOffsetBytes;
- if (!mIgnoreProgressData) {
- mProgressPosition = mDownloadPosition;
- }
- if (mTotalBytes >= 0) {
- // Ensure that mDownloadPosition <= mTotalBytes
- mTotalBytes = PR_MAX(mTotalBytes, mDownloadPosition);
- }
-}
-
void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)
{
if (aStatus == NS_BINDING_ABORTED)
return;
{
nsAutoMonitor mon(mMonitor);
- mDownloadStatistics.Stop(PR_IntervalNow());
- if (NS_SUCCEEDED(aStatus)) {
- // Update total bytes now we know the end of the stream
- mTotalBytes = mDownloadPosition;
- }
+ UpdatePlaybackRate();
}
if (NS_SUCCEEDED(aStatus)) {
ResourceLoaded();
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
NetworkError();
}
UpdateReadyStateForData();
@@ -1964,16 +1943,17 @@ void nsOggDecoder::SetDuration(PRInt64 a
mDuration = aDuration;
if (mDecodeStateMachine) {
nsAutoMonitor mon(mMonitor);
mDecodeStateMachine->SetDuration(mDuration);
if (mReader) {
mReader->SetDuration(mDuration);
}
+ UpdatePlaybackRate();
}
}
void nsOggDecoder::SetSeekable(PRBool aSeekable)
{
mSeekable = aSeekable;
if (mDecodeStateMachine) {
nsAutoMonitor mon(mMonitor);
@@ -1983,35 +1963,36 @@ void nsOggDecoder::SetSeekable(PRBool aS
PRBool nsOggDecoder::GetSeekable()
{
return mSeekable;
}
void nsOggDecoder::Suspend()
{
- mDownloadStatistics.Stop(PR_IntervalNow());
if (mReader) {
- mReader->Suspend();
+ mReader->Stream()->Suspend();
}
}
void nsOggDecoder::Resume()
{
if (mReader) {
- mReader->Resume();
+ mReader->Stream()->Resume();
}
- mDownloadStatistics.Start(PR_IntervalNow());
}
void nsOggDecoder::StopProgressUpdates()
{
mIgnoreProgressData = PR_TRUE;
- mDownloadStatistics.Stop(PR_IntervalNow());
+ if (mReader) {
+ mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_METADATA);
+ }
}
void nsOggDecoder::StartProgressUpdates()
{
mIgnoreProgressData = PR_FALSE;
- // Resync progress position now
- mProgressPosition = mDownloadPosition;
- mDownloadStatistics.Start(PR_IntervalNow());
+ if (mReader) {
+ mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
+ mDecoderPosition = mPlaybackPosition = mReader->Stream()->Tell();
+ }
}
--- a/content/media/video/src/nsWaveDecoder.cpp
+++ b/content/media/video/src/nsWaveDecoder.cpp
@@ -148,26 +148,20 @@ public:
PRBool IsEnded();
// Main state machine loop. Runs forever, until shutdown state is reached.
NS_IMETHOD Run();
// Called by the decoder, on the main thread.
nsMediaDecoder::Statistics GetStatistics();
- // Called on the main thread only
- void SetTotalBytes(PRInt64 aBytes);
- // Called on the main thread
- void NotifyBytesDownloaded(PRInt64 aBytes);
- // Called on the main thread
- void NotifyDownloadSeeked(PRInt64 aOffset);
+ // Called on the decoder thread
+ void NotifyBytesConsumed(PRInt64 aBytes);
// Called on the main thread
void NotifyDownloadEnded(nsresult aStatus);
- // Called on any thread
- void NotifyBytesConsumed(PRInt64 aBytes);
// Called by the main thread only
nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
// Clear the flag indicating that a playback position change event is
// currently queued and return the current time. This is called from the
// main thread.
float GetTimeForPositionChange();
@@ -314,36 +308,22 @@ private:
State mState;
// A queued state transition. This is used to record the next state
// transition when play or pause is requested during seeking or metadata
// loading to ensure a completed metadata load or seek returns to the most
// recently requested state on completion.
State mNextState;
- // Length of the current resource, or -1 if not available.
- PRInt64 mTotalBytes;
- // Current download position in the stream.
- // NOTE: because we don't have to read when we seek, there is no need
- // to track a separate "progress position" which ignores download
- // position changes due to reads servicing seeks.
- PRInt64 mDownloadPosition;
// Current playback position in the stream.
PRInt64 mPlaybackPosition;
- // Data needed to estimate download data rate. The channel timeline is
- // wall-clock time.
- nsMediaDecoder::ChannelStatistics mDownloadStatistics;
// Volume that the audio backend will be initialized with.
float mInitialVolume;
- // Playback position (in bytes), updated as the playback loop runs and
- // upon seeking.
- PRInt64 mTimeOffset;
-
// Time position (in seconds) to seek to. Set by Seek(float).
float mSeekTime;
// True once metadata has been parsed and validated. Users of mSampleRate,
// mChannels, mSampleSize, mSampleFormat, mWaveLength, mWavePCMOffset must
// check this flag before assuming the values are valid.
PRPackedBool mMetadataValid;
@@ -368,28 +348,24 @@ nsWaveStateMachine::nsWaveStateMachine(n
mChannels(0),
mSampleSize(0),
mSampleFormat(nsAudioStream::FORMAT_S16_LE),
mWaveLength(0),
mWavePCMOffset(0),
mMonitor(nsnull),
mState(STATE_LOADING_METADATA),
mNextState(STATE_PAUSED),
- mTotalBytes(-1),
- mDownloadPosition(0),
mPlaybackPosition(0),
mInitialVolume(aInitialVolume),
- mTimeOffset(0),
mSeekTime(0.0),
mMetadataValid(PR_FALSE),
mPositionChangeQueued(PR_FALSE),
mPaused(mNextState == STATE_PAUSED)
{
mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
- mDownloadStatistics.Start(PR_IntervalNow());
}
nsWaveStateMachine::~nsWaveStateMachine()
{
nsAutoMonitor::DestroyMonitor(mMonitor);
}
void
@@ -458,17 +434,17 @@ nsWaveStateMachine::GetDuration()
return std::numeric_limits<float>::quiet_NaN();
}
float
nsWaveStateMachine::GetCurrentTime()
{
nsAutoMonitor monitor(mMonitor);
if (mMetadataValid) {
- return BytesToTime(mTimeOffset);
+ return BytesToTime(mPlaybackPosition - mWavePCMOffset);
}
return std::numeric_limits<float>::quiet_NaN();
}
PRBool
nsWaveStateMachine::IsSeeking()
{
nsAutoMonitor monitor(mMonitor);
@@ -483,17 +459,17 @@ nsWaveStateMachine::IsEnded()
}
nsHTMLMediaElement::NextFrameStatus
nsWaveStateMachine::GetNextFrameStatus()
{
nsAutoMonitor monitor(mMonitor);
if (mState == STATE_BUFFERING)
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
- if (mPlaybackPosition < mDownloadPosition)
+ if (mPlaybackPosition < mStream->GetCachedDataEnd(mPlaybackPosition))
return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
}
float
nsWaveStateMachine::GetTimeForPositionChange()
{
nsAutoMonitor monitor(mMonitor);
@@ -538,21 +514,22 @@ nsWaveStateMachine::Run()
ChangeState(newState);
}
}
break;
case STATE_BUFFERING: {
PRIntervalTime now = PR_IntervalNow();
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
- mDownloadPosition < mBufferingEndOffset &&
- (mTotalBytes < 0 || mDownloadPosition < mTotalBytes)) {
+ mStream->GetCachedDataEnd(mPlaybackPosition) < mBufferingEndOffset &&
+ !mStream->IsDataCachedToEndOfStream(mPlaybackPosition) &&
+ !mStream->IsSuspendedByCache()) {
LOG(PR_LOG_DEBUG,
("In buffering: buffering data until %d bytes available or %d milliseconds\n",
- PRUint32(mBufferingEndOffset - mDownloadPosition),
+ PRUint32(mBufferingEndOffset - mStream->GetCachedDataEnd(mPlaybackPosition)),
mBufferingWait - (PR_IntervalToMilliseconds(now - mBufferingStart))));
monitor.Wait(PR_MillisecondsToInterval(1000));
} else {
ChangeState(mNextState);
nsCOMPtr<nsIRunnable> event =
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, UpdateReadyStateForData);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
@@ -586,57 +563,62 @@ nsWaveStateMachine::Run()
// audio buffered.
PRInt32 targetTime = AUDIO_BUFFER_LENGTH;
if (sleepTime < targetTime) {
targetTime = sleepTime;
}
PRInt64 len = TimeToBytes(float(targetTime) / 1000.0f);
- PRInt64 leftToPlay = GetDataLength() - mTimeOffset;
+ PRInt64 leftToPlay =
+ GetDataLength() - (mPlaybackPosition - mWavePCMOffset);
if (leftToPlay <= len) {
len = leftToPlay;
ChangeState(STATE_ENDED);
}
- PRInt64 available = mDownloadPosition - mPlaybackPosition;
+ PRInt64 available =
+ mStream->GetCachedDataEnd(mPlaybackPosition) - mPlaybackPosition;
- // don't buffer if we're at the end of the stream
- if (mState != STATE_ENDED && available < len) {
+ // don't buffer if we're at the end of the stream, or if the
+ // load has been suspended by the cache (in the latter case
+ // we need to advance playback to free up cache space)
+ if (mState != STATE_ENDED && available < len &&
+ !mStream->IsSuspendedByCache()) {
mBufferingStart = PR_IntervalNow();
- mBufferingEndOffset = mDownloadPosition + TimeToBytes(float(mBufferingWait) / 1000.0f);
+ mBufferingEndOffset = mPlaybackPosition +
+ TimeToBytes(float(mBufferingWait) / 1000.0f);
mNextState = mState;
ChangeState(STATE_BUFFERING);
nsCOMPtr<nsIRunnable> event =
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, UpdateReadyStateForData);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
break;
}
if (len > 0) {
nsAutoArrayPtr<char> buf(new char[size_t(len)]);
PRInt64 got = 0;
monitor.Exit();
PRBool ok = ReadAll(buf.get(), len, &got);
- PRInt64 streamPos = mStream->Tell();
monitor.Enter();
// Reached EOF.
if (!ok) {
ChangeState(STATE_ENDED);
if (got == 0) {
break;
}
}
// Calculate difference between the current media stream position
// and the expected end of the PCM data.
- PRInt64 endDelta = mWavePCMOffset + mWaveLength - streamPos;
+ PRInt64 endDelta = mWavePCMOffset + mWaveLength - mPlaybackPosition;
if (endDelta < 0) {
// Read past the end of PCM data. Adjust got to avoid playing
// back trailing data.
got -= -endDelta;
ChangeState(STATE_ENDED);
}
if (mState == STATE_ENDED) {
@@ -646,17 +628,16 @@ nsWaveStateMachine::Run()
PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2;
NS_ABORT_IF_FALSE(got % sampleSize == 0, "Must write complete samples");
PRUint32 lengthInSamples = got / sampleSize;
monitor.Exit();
mAudioStream->Write(buf.get(), lengthInSamples);
monitor.Enter();
- mTimeOffset += got;
FirePositionChanged(PR_FALSE);
}
if (mState == STATE_PLAYING) {
monitor.Wait(PR_MillisecondsToInterval(AUDIO_BUFFER_WAKEUP));
}
} while (mState == STATE_PLAYING);
break;
@@ -677,44 +658,29 @@ nsWaveStateMachine::Run()
if (mState == STATE_SHUTDOWN) {
break;
}
// Calculate relative offset within PCM data.
PRInt64 position = RoundDownToSample(TimeToBytes(seekTime));
NS_ABORT_IF_FALSE(position >= 0 && position <= GetDataLength(), "Invalid seek position");
-
- mTimeOffset = position;
-
- // If position==0, instead of seeking to position+mWavePCMOffset,
- // we want to first seek to 0 before seeking to
- // position+mWavePCMOffset. This allows the request's data to come
- // from the netwerk cache (non-zero byte-range requests can't be cached
- // yet). The second seek will simply advance the read cursor, it won't
- // start a new HTTP request.
- PRBool seekToZeroFirst = position == 0 &&
- (mWavePCMOffset < SEEK_VS_READ_THRESHOLD);
-
// Convert to absolute offset within stream.
position += mWavePCMOffset;
monitor.Exit();
nsresult rv;
- if (seekToZeroFirst) {
- rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
- if (NS_FAILED(rv)) {
- NS_WARNING("Seek to zero failed");
- }
- }
rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, position);
if (NS_FAILED(rv)) {
NS_WARNING("Seek failed");
}
monitor.Enter();
+ if (NS_SUCCEEDED(rv)) {
+ mPlaybackPosition = position;
+ }
if (mState == STATE_SHUTDOWN) {
break;
}
if (mState == STATE_SEEKING && mSeekTime == seekTime) {
// Special case #1: if a seek was requested during metadata load,
// mNextState will have been clobbered. This can only happen when
@@ -875,59 +841,27 @@ nsWaveStateMachine::CloseAudioStream()
}
}
nsMediaDecoder::Statistics
nsWaveStateMachine::GetStatistics()
{
nsMediaDecoder::Statistics result;
nsAutoMonitor monitor(mMonitor);
- PRIntervalTime now = PR_IntervalNow();
- result.mDownloadRate = mDownloadStatistics.GetRate(now, &result.mDownloadRateReliable);
+ result.mDownloadRate = mStream->GetDownloadRate(&result.mDownloadRateReliable);
result.mPlaybackRate = mSampleRate*mChannels*mSampleSize;
result.mPlaybackRateReliable = PR_TRUE;
- result.mTotalBytes = mTotalBytes;
- result.mDownloadPosition = mDownloadPosition;
+ result.mTotalBytes = mStream->GetLength();
+ result.mDownloadPosition = mStream->GetCachedDataEnd(mPlaybackPosition);
result.mDecoderPosition = mPlaybackPosition;
result.mPlaybackPosition = mPlaybackPosition;
return result;
}
void
-nsWaveStateMachine::SetTotalBytes(PRInt64 aBytes)
-{
- nsAutoMonitor monitor(mMonitor);
- mTotalBytes = aBytes;
-}
-
-void
-nsWaveStateMachine::NotifyBytesDownloaded(PRInt64 aBytes)
-{
- nsAutoMonitor monitor(mMonitor);
- mDownloadStatistics.AddBytes(aBytes);
- mDownloadPosition += aBytes;
-}
-
-void
-nsWaveStateMachine::NotifyDownloadSeeked(PRInt64 aOffset)
-{
- nsAutoMonitor monitor(mMonitor);
- mDownloadPosition = mPlaybackPosition = aOffset;
-}
-
-void
-nsWaveStateMachine::NotifyDownloadEnded(nsresult aStatus)
-{
- if (aStatus == NS_BINDING_ABORTED)
- return;
- nsAutoMonitor monitor(mMonitor);
- mDownloadStatistics.Stop(PR_IntervalNow());
-}
-
-void
nsWaveStateMachine::NotifyBytesConsumed(PRInt64 aBytes)
{
nsAutoMonitor monitor(mMonitor);
mPlaybackPosition += aBytes;
}
static PRUint32
ReadUint32BE(const char** aBuffer)
@@ -1185,18 +1119,20 @@ nsWaveStateMachine::GetDataLength()
{
NS_ABORT_IF_FALSE(mMetadataValid,
"Attempting to initialize audio stream with invalid metadata");
PRInt64 length = mWaveLength;
// If the decoder has a valid content length, and it's shorter than the
// expected length of the PCM data, calculate the playback duration from
// the content length rather than the expected PCM data length.
- if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
- length = mTotalBytes - mWavePCMOffset;
+ PRInt64 streamLength = mStream->GetLength();
+ if (streamLength >= 0) {
+ PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset);
+ length = PR_MIN(dataLength, length);
}
return length;
}
void
nsWaveStateMachine::FirePositionChanged(PRBool aCoalesce)
{
if (aCoalesce && mPositionChangeQueued) {
@@ -1207,17 +1143,16 @@ nsWaveStateMachine::FirePositionChanged(
nsCOMPtr<nsIRunnable> event = NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, PlaybackPositionChanged);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
nsWaveDecoder::nsWaveDecoder()
: mInitialVolume(1.0),
- mTimeOffset(0.0),
mCurrentTime(0.0),
mEndedCurrentTime(0.0),
mEndedDuration(std::numeric_limits<float>::quiet_NaN()),
mEnded(PR_FALSE),
mNotifyOnShutdown(PR_FALSE),
mSeekable(PR_TRUE),
mResourceLoaded(PR_FALSE),
mMetadataLoadedReported(PR_FALSE),
@@ -1232,17 +1167,17 @@ nsWaveDecoder::~nsWaveDecoder()
}
void
nsWaveDecoder::GetCurrentURI(nsIURI** aURI)
{
NS_IF_ADDREF(*aURI = mURI);
}
-nsIPrincipal*
+already_AddRefed<nsIPrincipal>
nsWaveDecoder::GetCurrentPrincipal()
{
if (!mStream) {
return nsnull;
}
return mStream->GetCurrentPrincipal();
}
@@ -1250,24 +1185,22 @@ float
nsWaveDecoder::GetCurrentTime()
{
return mCurrentTime;
}
nsresult
nsWaveDecoder::Seek(float aTime)
{
- mTimeOffset = aTime;
-
if (!mPlaybackStateMachine) {
Load(mURI, nsnull, nsnull);
}
if (mPlaybackStateMachine) {
- mPlaybackStateMachine->Seek(mTimeOffset);
+ mPlaybackStateMachine->Seek(aTime);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
nsresult
nsWaveDecoder::PlaybackRateChanged()
@@ -1327,17 +1260,17 @@ nsWaveDecoder::Stop()
StopProgress();
if (mPlaybackStateMachine) {
mPlaybackStateMachine->Shutdown();
}
if (mStream) {
- mStream->Cancel();
+ mStream->Close();
}
if (mPlaybackThread) {
mPlaybackThread->Shutdown();
}
if (mPlaybackStateMachine) {
mEndedCurrentTime = mPlaybackStateMachine->GetCurrentTime();
@@ -1495,66 +1428,44 @@ nsMediaDecoder::Statistics
nsWaveDecoder::GetStatistics()
{
if (!mPlaybackStateMachine)
return Statistics();
return mPlaybackStateMachine->GetStatistics();
}
void
-nsWaveDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
+nsWaveDecoder::NotifySuspendedStatusChanged()
{
- if (mPlaybackStateMachine) {
- mPlaybackStateMachine->NotifyBytesDownloaded(aBytes);
- }
- UpdateReadyStateForData();
+ if (mStream->IsSuspendedByCache() && mElement) {
+ // if this is an autoplay element, we need to kick off its autoplaying
+ // now so we consume data and hopefully free up cache space
+ mElement->NotifyAutoplayDataReady();
+ }
}
void
-nsWaveDecoder::NotifyDownloadSeeked(PRInt64 aBytes)
+nsWaveDecoder::NotifyBytesDownloaded()
{
- if (mPlaybackStateMachine) {
- mPlaybackStateMachine->NotifyDownloadSeeked(aBytes);
- }
+ UpdateReadyStateForData();
}
void
nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
{
- if (mPlaybackStateMachine) {
- mPlaybackStateMachine->NotifyDownloadEnded(aStatus);
- }
if (aStatus != NS_BINDING_ABORTED) {
if (NS_SUCCEEDED(aStatus)) {
ResourceLoaded();
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
NetworkError();
}
}
UpdateReadyStateForData();
}
-void
-nsWaveDecoder::NotifyBytesConsumed(PRInt64 aBytes)
-{
- if (mPlaybackStateMachine) {
- mPlaybackStateMachine->NotifyBytesConsumed(aBytes);
- }
-}
-
-void
-nsWaveDecoder::SetTotalBytes(PRInt64 aBytes)
-{
- if (mPlaybackStateMachine) {
- mPlaybackStateMachine->SetTotalBytes(aBytes);
- } else {
- NS_WARNING("Forgot total bytes since there is no state machine set up");
- }
-}
-
// An event that gets posted to the main thread, when the media element is
// being destroyed, to destroy the decoder. Since the decoder shutdown can
// block and post events this cannot be done inside destructor calls. So
// this event is posted asynchronously to the main thread to perform the
// shutdown. It keeps a strong reference to the decoder to ensure it does
// not get deleted when the element is deleted.
class nsWaveDecoderShutdown : public nsRunnable
{
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -131,16 +131,19 @@ pref("browser.chrome.toolbar_style",
pref("browser.chrome.image_icons.max_size", 1024);
pref("browser.triple_click_selects_paragraph", true);
// When loading <video> or <audio>, check for Access-Control-Allow-Origin
// header, and disallow the connection if not present or permitted.
pref("media.enforce_same_site_origin", false);
+// Media cache size in kilobytes
+pref("media.cache_size", 51200);
+
#ifdef MOZ_OGG
pref("media.ogg.enabled", true);
#endif
#ifdef MOZ_WAVE
pref("media.wave.enabled", true);
#endif
// Whether to autostart a media element with an |autoplay| attribute