mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Backed out changeset b6a1093e5814
This commit is contained in:
parent
6e281fcaab
commit
965a9ce568
@ -157,11 +157,6 @@ public:
|
||||
// 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();
|
||||
@ -175,7 +170,7 @@ public:
|
||||
PRBool IsPlaybackEnded() const;
|
||||
|
||||
// principal of the currently playing stream
|
||||
already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
||||
nsIPrincipal* GetCurrentPrincipal();
|
||||
|
||||
// Update the visual size of the media. Called from the decoder on the
|
||||
// main thread when/if the size changes.
|
||||
|
@ -1223,10 +1223,9 @@ static const PRInt32 gDownloadSizeSafetyMargin = 1000000;
|
||||
void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
|
||||
{
|
||||
if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
|
||||
// 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.
|
||||
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.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1312,8 +1311,16 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
||||
DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplay"));
|
||||
}
|
||||
|
||||
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
|
||||
NotifyAutoplayDataReady();
|
||||
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 (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
@ -1328,20 +1335,6 @@ void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -1438,7 +1431,7 @@ PRBool nsHTMLMediaElement::IsPlaybackEnded() const
|
||||
mDecoder ? mDecoder->IsEnded() : PR_FALSE;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal> nsHTMLMediaElement::GetCurrentPrincipal()
|
||||
nsIPrincipal* nsHTMLMediaElement::GetCurrentPrincipal()
|
||||
{
|
||||
if (!mDecoder)
|
||||
return nsnull;
|
||||
|
@ -45,10 +45,15 @@ 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 \
|
||||
|
@ -39,13 +39,13 @@
|
||||
|
||||
#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
|
||||
{
|
||||
@ -63,7 +63,15 @@ public:
|
||||
nsresult Init(nsMediaDecoder* aDecoder, nsIURI* aURI, nsIChannel* aChannel,
|
||||
nsIStreamListener** aStreamListener);
|
||||
|
||||
nsMediaStream* Stream() { return mStream; }
|
||||
// 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();
|
||||
|
||||
// Set the duration of the media resource. Call with decoder lock
|
||||
// obtained so that the decoder thread does not request the duration
|
||||
|
88
content/media/video/public/nsChannelToPipeListener.h
Normal file
88
content/media/video/public/nsChannelToPipeListener.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* 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
|
@ -1,414 +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) 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
|
@ -77,7 +77,7 @@ class nsMediaDecoder : public nsIObserver
|
||||
virtual void GetCurrentURI(nsIURI** aURI) = 0;
|
||||
|
||||
// Return the principal of the current URI being played or downloaded.
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
|
||||
virtual nsIPrincipal* GetCurrentPrincipal() = 0;
|
||||
|
||||
// Return the time position in the video stream being
|
||||
// played measured in seconds.
|
||||
@ -139,13 +139,15 @@ class nsMediaDecoder : public nsIObserver
|
||||
struct Statistics {
|
||||
// Estimate of the current playback rate (bytes/second).
|
||||
double mPlaybackRate;
|
||||
// Estimate of the current download rate (bytes/second). This
|
||||
// ignores time that the channel was paused by Gecko.
|
||||
// Estimate of the current download rate (bytes/second)
|
||||
double mDownloadRate;
|
||||
// Total length of media stream in bytes; -1 if not known
|
||||
PRInt64 mTotalBytes;
|
||||
// Current position of the download, in bytes. This is the offset of
|
||||
// the first uncached byte after the decoder position.
|
||||
// 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.
|
||||
PRInt64 mDownloadPosition;
|
||||
// Current position of decoding, in bytes (how much of the stream
|
||||
// has been consumed)
|
||||
@ -168,6 +170,9 @@ class nsMediaDecoder : public nsIObserver
|
||||
// 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.
|
||||
@ -188,21 +193,33 @@ class nsMediaDecoder : public nsIObserver
|
||||
// than the result of downloaded data.
|
||||
virtual void Progress(PRBool aTimer);
|
||||
|
||||
// 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 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 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();
|
||||
@ -237,6 +254,70 @@ protected:
|
||||
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;
|
||||
|
@ -34,7 +34,6 @@
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#if !defined(nsMediaStream_h_)
|
||||
#define nsMediaStream_h_
|
||||
|
||||
@ -45,7 +44,6 @@
|
||||
#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
|
||||
@ -54,83 +52,20 @@
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -138,34 +73,17 @@ 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.
|
||||
// Cancels any currently blocking Read request and forces that request to
|
||||
// return an error.
|
||||
// Call on main thread only.
|
||||
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).
|
||||
// May read less than aCount bytes if 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
|
||||
// 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;
|
||||
@ -175,6 +93,7 @@ public:
|
||||
// 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
|
||||
@ -197,36 +116,21 @@ public:
|
||||
// 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.
|
||||
virtual PRInt64 Tell() = 0;
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
nsMediaDecoder* Decoder() { return mDecoder; }
|
||||
|
||||
/**
|
||||
* Create a stream, reading data from the
|
||||
@ -243,9 +147,11 @@ protected:
|
||||
nsMediaStream(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
|
||||
mDecoder(aDecoder),
|
||||
mChannel(aChannel),
|
||||
mURI(aURI)
|
||||
mURI(aURI),
|
||||
mLock(nsnull)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsMediaStream);
|
||||
mLock = PR_NewLock();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,106 +174,12 @@ protected:
|
||||
// URI in case the stream needs to be re-opened. Access from
|
||||
// main thread only.
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
// 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.
|
||||
PRLock* mLock;
|
||||
nsChannelStatistics mChannelStatistics;
|
||||
PRUint32 mCacheSuspendCount;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -322,13 +322,12 @@ class nsOggDecoder : public nsMediaDecoder
|
||||
virtual float GetDuration();
|
||||
|
||||
virtual void GetCurrentURI(nsIURI** aURI);
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
|
||||
virtual void NotifySuspendedStatusChanged();
|
||||
virtual void NotifyBytesDownloaded();
|
||||
virtual void NotifyBytesDownloaded(PRInt64 aBytes);
|
||||
virtual void NotifyDownloadSeeked(PRInt64 aOffsetBytes);
|
||||
virtual void NotifyDownloadEnded(nsresult aStatus);
|
||||
// Called by nsChannelReader on the decoder thread
|
||||
void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
virtual void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
|
||||
// Called when the video file has completed downloading.
|
||||
// Call on the main thread only.
|
||||
@ -346,6 +345,9 @@ class nsOggDecoder : public nsMediaDecoder
|
||||
// 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.
|
||||
@ -397,13 +399,6 @@ protected:
|
||||
// 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.
|
||||
@ -443,10 +438,6 @@ protected:
|
||||
// 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.
|
||||
@ -457,20 +448,29 @@ private:
|
||||
* 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. This is not adjusted during decoder
|
||||
// seek operations, but it's updated at the end when we start playing
|
||||
// back again.
|
||||
// is up to consuming the stream.
|
||||
PRInt64 mDecoderPosition;
|
||||
// Current playback position in the stream. This is (approximately)
|
||||
// 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.
|
||||
// where we're up to playing back the stream.
|
||||
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).
|
||||
nsChannelStatistics mPlaybackStatistics;
|
||||
ChannelStatistics mPlaybackStatistics;
|
||||
|
||||
// The URI of the current resource
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
|
@ -147,7 +147,7 @@ class nsWaveDecoder : public nsMediaDecoder
|
||||
~nsWaveDecoder();
|
||||
|
||||
virtual void GetCurrentURI(nsIURI** aURI);
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
|
||||
// Return the current playback position in the media in seconds.
|
||||
virtual float GetCurrentTime();
|
||||
@ -188,12 +188,15 @@ class nsWaveDecoder : public nsMediaDecoder
|
||||
// Element is notifying us that the requested playback rate has changed.
|
||||
virtual nsresult PlaybackRateChanged();
|
||||
|
||||
virtual void NotifySuspendedStatusChanged();
|
||||
virtual void NotifyBytesDownloaded();
|
||||
virtual void NotifyBytesDownloaded(PRInt64 aBytes);
|
||||
virtual void NotifyDownloadSeeked(PRInt64 aOffset);
|
||||
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
|
||||
@ -257,6 +260,13 @@ private:
|
||||
// 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
|
||||
|
@ -80,9 +80,14 @@ REQUIRES = \
|
||||
|
||||
CPPSRCS = \
|
||||
nsMediaDecoder.cpp \
|
||||
nsMediaCache.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_MEDIA
|
||||
CPPSRCS += \
|
||||
nsChannelToPipeListener.cpp \
|
||||
nsMediaStream.cpp \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
ifdef MOZ_SYDNEYAUDIO
|
||||
CPPSRCS += \
|
||||
@ -111,3 +116,5 @@ INCLUDES += \
|
||||
-I$(srcdir)/../../../base/src \
|
||||
-I$(srcdir)/../../../html/content/src \
|
||||
$(NULL)
|
||||
|
||||
|
||||
|
@ -43,6 +43,11 @@
|
||||
#include "nsChannelReader.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
|
||||
void nsChannelReader::Cancel()
|
||||
{
|
||||
mStream->Cancel();
|
||||
}
|
||||
|
||||
OggPlayErrorCode nsChannelReader::initialise(int aBlock)
|
||||
{
|
||||
return E_OGGPLAY_OK;
|
||||
@ -50,10 +55,20 @@ OggPlayErrorCode nsChannelReader::initialise(int aBlock)
|
||||
|
||||
OggPlayErrorCode nsChannelReader::destroy()
|
||||
{
|
||||
// We don't have to do anything here, the decoder will clean stuff up
|
||||
mStream->Close();
|
||||
return E_OGGPLAY_OK;
|
||||
}
|
||||
|
||||
void nsChannelReader::Suspend()
|
||||
{
|
||||
mStream->Suspend();
|
||||
}
|
||||
|
||||
void nsChannelReader::Resume()
|
||||
{
|
||||
mStream->Resume();
|
||||
}
|
||||
|
||||
void nsChannelReader::SetDuration(PRInt64 aDuration)
|
||||
{
|
||||
mDuration = aDuration;
|
||||
@ -157,3 +172,9 @@ nsChannelReader::nsChannelReader() :
|
||||
reader->io_tell = &oggplay_channel_reader_io_tell;
|
||||
reader->duration = &oggplay_channel_reader_duration;
|
||||
}
|
||||
|
||||
nsIPrincipal*
|
||||
nsChannelReader::GetCurrentPrincipal()
|
||||
{
|
||||
return mStream->GetCurrentPrincipal();
|
||||
}
|
||||
|
236
content/media/video/src/nsChannelToPipeListener.cpp
Normal file
236
content/media/video/src/nsChannelToPipeListener.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
/* -*- 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)
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -298,6 +298,10 @@ public:
|
||||
// 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.
|
||||
@ -483,6 +487,11 @@ private:
|
||||
// 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;
|
||||
@ -516,6 +525,7 @@ nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
|
||||
mCurrentFrameTime(0.0),
|
||||
mVolume(1.0),
|
||||
mDuration(-1),
|
||||
mContentLength(-1),
|
||||
mSeekable(PR_TRUE),
|
||||
mPositionChangeQueued(PR_FALSE)
|
||||
{
|
||||
@ -557,7 +567,6 @@ nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
|
||||
mDecoder->mPlaybackStatistics.Start(frame->mTime*PR_TicksPerSecond());
|
||||
mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
|
||||
mDecoder->mPlaybackStatistics.Stop(mLastFrameTime*PR_TicksPerSecond());
|
||||
mDecoder->UpdatePlaybackRate();
|
||||
}
|
||||
mLastFramePosition = frame->mEndStreamPosition;
|
||||
|
||||
@ -848,6 +857,12 @@ PRInt64 nsOggDecodeStateMachine::GetDuration()
|
||||
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");
|
||||
@ -954,8 +969,7 @@ nsresult nsOggDecodeStateMachine::Run()
|
||||
PRBool bufferExhausted = PR_FALSE;
|
||||
|
||||
if (!mDecodedFrames.IsFull()) {
|
||||
PRInt64 initialDownloadPosition =
|
||||
mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
|
||||
PRInt64 initialDownloadPosition = mDecoder->mDownloadPosition;
|
||||
|
||||
mon.Exit();
|
||||
OggPlayErrorCode r = DecodeFrame();
|
||||
@ -992,8 +1006,8 @@ nsresult nsOggDecodeStateMachine::Run()
|
||||
|
||||
if (bufferExhausted && mState == DECODER_STATE_DECODING &&
|
||||
mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
|
||||
!mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
|
||||
!mDecoder->mReader->Stream()->IsSuspendedByCache()) {
|
||||
(mDecoder->mTotalBytes < 0 ||
|
||||
mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
|
||||
// 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.
|
||||
@ -1015,9 +1029,8 @@ nsresult nsOggDecodeStateMachine::Run()
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
|
||||
mBufferingStart = PR_IntervalNow();
|
||||
PRPackedBool reliable;
|
||||
double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
|
||||
mBufferingEndOffset = mDecoder->mDecoderPosition +
|
||||
double playbackRate = mDecoder->GetStatistics().mPlaybackRate;
|
||||
mBufferingEndOffset = mDecoder->mDownloadPosition +
|
||||
BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
|
||||
mState = DECODER_STATE_BUFFERING;
|
||||
LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
|
||||
@ -1058,8 +1071,8 @@ nsresult nsOggDecodeStateMachine::Run()
|
||||
}
|
||||
|
||||
mon.Enter();
|
||||
mLastFramePosition = mDecoder->mDecoderPosition;
|
||||
mDecoder->StartProgressUpdates();
|
||||
mLastFramePosition = mDecoder->mPlaybackPosition;
|
||||
if (mState == DECODER_STATE_SHUTDOWN)
|
||||
continue;
|
||||
|
||||
@ -1103,12 +1116,11 @@ nsresult nsOggDecodeStateMachine::Run()
|
||||
{
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < BUFFERING_WAIT*1000) &&
|
||||
mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
|
||||
!mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
|
||||
!mDecoder->mReader->Stream()->IsSuspendedByCache()) {
|
||||
mDecoder->mDownloadPosition < mBufferingEndOffset &&
|
||||
(mDecoder->mTotalBytes < 0 || mDecoder->mDownloadPosition < mDecoder->mTotalBytes)) {
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("In buffering: buffering data until %d bytes available or %d milliseconds",
|
||||
PRUint32(mBufferingEndOffset - mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
|
||||
PRUint32(mBufferingEndOffset - mDecoder->mDownloadPosition),
|
||||
BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(now - mBufferingStart))));
|
||||
mon.Wait(PR_MillisecondsToInterval(1000));
|
||||
if (mState == DECODER_STATE_SHUTDOWN)
|
||||
@ -1225,7 +1237,7 @@ void nsOggDecodeStateMachine::LoadOggHeaders(nsChannelReader* aReader)
|
||||
{
|
||||
nsAutoMonitor mon(mDecoder->GetMonitor());
|
||||
if (mState != DECODER_STATE_SHUTDOWN &&
|
||||
aReader->Stream()->GetLength() >= 0 &&
|
||||
mContentLength >= 0 &&
|
||||
mSeekable &&
|
||||
mDuration == -1) {
|
||||
mDecoder->StopProgressUpdates();
|
||||
@ -1238,7 +1250,6 @@ void nsOggDecodeStateMachine::LoadOggHeaders(nsChannelReader* aReader)
|
||||
mon.Enter();
|
||||
mDuration = d;
|
||||
mDecoder->StartProgressUpdates();
|
||||
mDecoder->UpdatePlaybackRate();
|
||||
}
|
||||
if (mState == DECODER_STATE_SHUTDOWN)
|
||||
return;
|
||||
@ -1286,6 +1297,9 @@ float nsOggDecoder::GetDuration()
|
||||
|
||||
nsOggDecoder::nsOggDecoder() :
|
||||
nsMediaDecoder(),
|
||||
mTotalBytes(-1),
|
||||
mDownloadPosition(0),
|
||||
mProgressPosition(0),
|
||||
mDecoderPosition(0),
|
||||
mPlaybackPosition(0),
|
||||
mCurrentTime(0.0),
|
||||
@ -1294,8 +1308,8 @@ nsOggDecoder::nsOggDecoder() :
|
||||
mDuration(-1),
|
||||
mNotifyOnShutdown(PR_FALSE),
|
||||
mSeekable(PR_TRUE),
|
||||
mReader(nsnull),
|
||||
mMonitor(nsnull),
|
||||
mReader(0),
|
||||
mMonitor(0),
|
||||
mPlayState(PLAY_STATE_PAUSED),
|
||||
mNextState(PLAY_STATE_PAUSED),
|
||||
mResourceLoaded(PR_FALSE),
|
||||
@ -1334,6 +1348,8 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
|
||||
mStopping = PR_FALSE;
|
||||
|
||||
// Reset progress member variables
|
||||
mDownloadPosition = 0;
|
||||
mProgressPosition = 0;
|
||||
mDecoderPosition = 0;
|
||||
mPlaybackPosition = 0;
|
||||
mResourceLoaded = PR_FALSE;
|
||||
@ -1364,26 +1380,19 @@ nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
|
||||
|
||||
mReader = new nsChannelReader();
|
||||
NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
|
||||
mDownloadStatistics.Reset();
|
||||
mDownloadStatistics.Start(PR_IntervalNow());
|
||||
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
|
||||
rv = NS_NewThread(getter_AddRefs(mDecodeThread));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mDecodeStateMachine = new nsOggDecodeStateMachine(this);
|
||||
{
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
mDecodeStateMachine->SetContentLength(mTotalBytes);
|
||||
mDecodeStateMachine->SetSeekable(mSeekable);
|
||||
}
|
||||
|
||||
@ -1479,10 +1488,13 @@ void nsOggDecoder::Stop()
|
||||
ChangeState(PLAY_STATE_ENDED);
|
||||
|
||||
StopProgress();
|
||||
mDownloadStatistics.Stop(PR_IntervalNow());
|
||||
|
||||
// Force any outstanding seek and byterange requests to complete
|
||||
// to prevent shutdown from deadlocking.
|
||||
mReader->Stream()->Close();
|
||||
if (mReader) {
|
||||
mReader->Cancel();
|
||||
}
|
||||
|
||||
// Shutdown must be on called the mDecodeStateMachine before deleting.
|
||||
// This is required to ensure that the state machine isn't running
|
||||
@ -1526,11 +1538,13 @@ void nsOggDecoder::GetCurrentURI(nsIURI** aURI)
|
||||
NS_IF_ADDREF(*aURI = mURI);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal> nsOggDecoder::GetCurrentPrincipal()
|
||||
nsIPrincipal* nsOggDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
if (!mReader)
|
||||
if (!mReader) {
|
||||
return nsnull;
|
||||
return mReader->Stream()->GetCurrentPrincipal();
|
||||
}
|
||||
|
||||
return mReader->GetCurrentPrincipal();
|
||||
}
|
||||
|
||||
void nsOggDecoder::MetadataLoaded()
|
||||
@ -1596,8 +1610,7 @@ void nsOggDecoder::FirstFrameLoaded()
|
||||
}
|
||||
}
|
||||
|
||||
if (!mResourceLoaded && mReader &&
|
||||
mReader->Stream()->IsDataCachedToEndOfStream(mDecoderPosition)) {
|
||||
if (!mResourceLoaded && mDownloadPosition == mTotalBytes) {
|
||||
ResourceLoaded();
|
||||
}
|
||||
}
|
||||
@ -1620,6 +1633,10 @@ void nsOggDecoder::ResourceLoaded()
|
||||
|
||||
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();
|
||||
}
|
||||
@ -1679,71 +1696,71 @@ nsOggDecoder::GetStatistics()
|
||||
Statistics result;
|
||||
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
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.mDownloadRate = 0;
|
||||
result.mDownloadRateReliable = PR_TRUE;
|
||||
result.mPlaybackRate = 0;
|
||||
result.mDownloadRate =
|
||||
mDownloadStatistics.GetRate(PR_IntervalNow(), &result.mDownloadRateReliable);
|
||||
if (mDuration >= 0 && mTotalBytes >= 0) {
|
||||
result.mPlaybackRate = double(mTotalBytes)*1000.0/mDuration;
|
||||
result.mPlaybackRateReliable = PR_TRUE;
|
||||
result.mDecoderPosition = 0;
|
||||
result.mPlaybackPosition = 0;
|
||||
result.mDownloadPosition = 0;
|
||||
result.mTotalBytes = 0;
|
||||
} else {
|
||||
result.mPlaybackRate =
|
||||
mPlaybackStatistics.GetRateAtLastStop(&result.mPlaybackRateReliable);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
double nsOggDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
|
||||
void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
|
||||
{
|
||||
PRInt64 length = mReader ? mReader->Stream()->GetLength() : -1;
|
||||
if (mDuration >= 0 && length >= 0) {
|
||||
*aReliable = PR_TRUE;
|
||||
return double(length)*1000.0/mDuration;
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
|
||||
// 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);
|
||||
}
|
||||
return mPlaybackStatistics.GetRateAtLastStop(aReliable);
|
||||
}
|
||||
|
||||
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()
|
||||
void nsOggDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
|
||||
{
|
||||
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();
|
||||
"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::NotifyBytesDownloaded()
|
||||
void nsOggDecoder::NotifyDownloadSeeked(PRInt64 aOffsetBytes)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(),
|
||||
"nsOggDecoder::NotifyBytesDownloaded called on non-main thread");
|
||||
UpdateReadyStateForData();
|
||||
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)
|
||||
@ -1753,7 +1770,11 @@ void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
|
||||
{
|
||||
nsAutoMonitor mon(mMonitor);
|
||||
UpdatePlaybackRate();
|
||||
mDownloadStatistics.Stop(PR_IntervalNow());
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
// Update total bytes now we know the end of the stream
|
||||
mTotalBytes = mDownloadPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
@ -1948,7 +1969,6 @@ void nsOggDecoder::SetDuration(PRInt64 aDuration)
|
||||
if (mReader) {
|
||||
mReader->SetDuration(mDuration);
|
||||
}
|
||||
UpdatePlaybackRate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1968,31 +1988,30 @@ PRBool nsOggDecoder::GetSeekable()
|
||||
|
||||
void nsOggDecoder::Suspend()
|
||||
{
|
||||
mDownloadStatistics.Stop(PR_IntervalNow());
|
||||
if (mReader) {
|
||||
mReader->Stream()->Suspend();
|
||||
mReader->Suspend();
|
||||
}
|
||||
}
|
||||
|
||||
void nsOggDecoder::Resume()
|
||||
{
|
||||
if (mReader) {
|
||||
mReader->Stream()->Resume();
|
||||
mReader->Resume();
|
||||
}
|
||||
mDownloadStatistics.Start(PR_IntervalNow());
|
||||
}
|
||||
|
||||
void nsOggDecoder::StopProgressUpdates()
|
||||
{
|
||||
mIgnoreProgressData = PR_TRUE;
|
||||
if (mReader) {
|
||||
mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_METADATA);
|
||||
}
|
||||
mDownloadStatistics.Stop(PR_IntervalNow());
|
||||
}
|
||||
|
||||
void nsOggDecoder::StartProgressUpdates()
|
||||
{
|
||||
mIgnoreProgressData = PR_FALSE;
|
||||
if (mReader) {
|
||||
mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
|
||||
mDecoderPosition = mPlaybackPosition = mReader->Stream()->Tell();
|
||||
}
|
||||
// Resync progress position now
|
||||
mProgressPosition = mDownloadPosition;
|
||||
mDownloadStatistics.Start(PR_IntervalNow());
|
||||
}
|
||||
|
@ -153,10 +153,16 @@ public:
|
||||
// Called by the decoder, on the main thread.
|
||||
nsMediaDecoder::Statistics GetStatistics();
|
||||
|
||||
// Called on the decoder thread
|
||||
void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
// 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 main thread
|
||||
void NotifyDownloadEnded(nsresult aStatus);
|
||||
// Called on any thread
|
||||
void NotifyBytesConsumed(PRInt64 aBytes);
|
||||
|
||||
// Called by the main thread only
|
||||
nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
|
||||
@ -313,12 +319,26 @@ private:
|
||||
// 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;
|
||||
|
||||
@ -353,14 +373,18 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder,
|
||||
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()
|
||||
@ -439,7 +463,7 @@ nsWaveStateMachine::GetCurrentTime()
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
if (mMetadataValid) {
|
||||
return BytesToTime(mPlaybackPosition - mWavePCMOffset);
|
||||
return BytesToTime(mTimeOffset);
|
||||
}
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
@ -464,7 +488,7 @@ nsWaveStateMachine::GetNextFrameStatus()
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
if (mState == STATE_BUFFERING)
|
||||
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
|
||||
if (mPlaybackPosition < mStream->GetCachedDataEnd(mPlaybackPosition))
|
||||
if (mPlaybackPosition < mDownloadPosition)
|
||||
return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
|
||||
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
|
||||
}
|
||||
@ -519,12 +543,11 @@ nsWaveStateMachine::Run()
|
||||
case STATE_BUFFERING: {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
|
||||
mStream->GetCachedDataEnd(mPlaybackPosition) < mBufferingEndOffset &&
|
||||
!mStream->IsDataCachedToEndOfStream(mPlaybackPosition) &&
|
||||
!mStream->IsSuspendedByCache()) {
|
||||
mDownloadPosition < mBufferingEndOffset &&
|
||||
(mTotalBytes < 0 || mDownloadPosition < mTotalBytes)) {
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("In buffering: buffering data until %d bytes available or %d milliseconds\n",
|
||||
PRUint32(mBufferingEndOffset - mStream->GetCachedDataEnd(mPlaybackPosition)),
|
||||
PRUint32(mBufferingEndOffset - mDownloadPosition),
|
||||
mBufferingWait - (PR_IntervalToMilliseconds(now - mBufferingStart))));
|
||||
monitor.Wait(PR_MillisecondsToInterval(1000));
|
||||
} else {
|
||||
@ -568,24 +591,18 @@ nsWaveStateMachine::Run()
|
||||
|
||||
PRInt64 len = TimeToBytes(float(targetTime) / 1000.0f);
|
||||
|
||||
PRInt64 leftToPlay =
|
||||
GetDataLength() - (mPlaybackPosition - mWavePCMOffset);
|
||||
PRInt64 leftToPlay = GetDataLength() - mTimeOffset;
|
||||
if (leftToPlay <= len) {
|
||||
len = leftToPlay;
|
||||
ChangeState(STATE_ENDED);
|
||||
}
|
||||
|
||||
PRInt64 available =
|
||||
mStream->GetCachedDataEnd(mPlaybackPosition) - mPlaybackPosition;
|
||||
PRInt64 available = mDownloadPosition - mPlaybackPosition;
|
||||
|
||||
// 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()) {
|
||||
// don't buffer if we're at the end of the stream
|
||||
if (mState != STATE_ENDED && available < len) {
|
||||
mBufferingStart = PR_IntervalNow();
|
||||
mBufferingEndOffset = mPlaybackPosition +
|
||||
TimeToBytes(float(mBufferingWait) / 1000.0f);
|
||||
mBufferingEndOffset = mDownloadPosition + TimeToBytes(float(mBufferingWait) / 1000.0f);
|
||||
mNextState = mState;
|
||||
ChangeState(STATE_BUFFERING);
|
||||
|
||||
@ -601,6 +618,7 @@ nsWaveStateMachine::Run()
|
||||
|
||||
monitor.Exit();
|
||||
PRBool ok = ReadAll(buf.get(), len, &got);
|
||||
PRInt64 streamPos = mStream->Tell();
|
||||
monitor.Enter();
|
||||
|
||||
// Reached EOF.
|
||||
@ -613,7 +631,7 @@ nsWaveStateMachine::Run()
|
||||
|
||||
// Calculate difference between the current media stream position
|
||||
// and the expected end of the PCM data.
|
||||
PRInt64 endDelta = mWavePCMOffset + mWaveLength - mPlaybackPosition;
|
||||
PRInt64 endDelta = mWavePCMOffset + mWaveLength - streamPos;
|
||||
if (endDelta < 0) {
|
||||
// Read past the end of PCM data. Adjust got to avoid playing
|
||||
// back trailing data.
|
||||
@ -633,6 +651,7 @@ nsWaveStateMachine::Run()
|
||||
mAudioStream->Write(buf.get(), lengthInSamples);
|
||||
monitor.Enter();
|
||||
|
||||
mTimeOffset += got;
|
||||
FirePositionChanged(PR_FALSE);
|
||||
}
|
||||
|
||||
@ -663,19 +682,34 @@ nsWaveStateMachine::Run()
|
||||
// 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;
|
||||
@ -846,16 +880,48 @@ nsWaveStateMachine::GetStatistics()
|
||||
{
|
||||
nsMediaDecoder::Statistics result;
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
result.mDownloadRate = mStream->GetDownloadRate(&result.mDownloadRateReliable);
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
result.mDownloadRate = mDownloadStatistics.GetRate(now, &result.mDownloadRateReliable);
|
||||
result.mPlaybackRate = mSampleRate*mChannels*mSampleSize;
|
||||
result.mPlaybackRateReliable = PR_TRUE;
|
||||
result.mTotalBytes = mStream->GetLength();
|
||||
result.mDownloadPosition = mStream->GetCachedDataEnd(mPlaybackPosition);
|
||||
result.mTotalBytes = mTotalBytes;
|
||||
result.mDownloadPosition = mDownloadPosition;
|
||||
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)
|
||||
{
|
||||
@ -1124,10 +1190,8 @@ nsWaveStateMachine::GetDataLength()
|
||||
// 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.
|
||||
PRInt64 streamLength = mStream->GetLength();
|
||||
if (streamLength >= 0) {
|
||||
PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset);
|
||||
length = PR_MIN(dataLength, length);
|
||||
if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
|
||||
length = mTotalBytes - mWavePCMOffset;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
@ -1148,6 +1212,7 @@ 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()),
|
||||
@ -1172,7 +1237,7 @@ nsWaveDecoder::GetCurrentURI(nsIURI** aURI)
|
||||
NS_IF_ADDREF(*aURI = mURI);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIPrincipal>
|
||||
nsIPrincipal*
|
||||
nsWaveDecoder::GetCurrentPrincipal()
|
||||
{
|
||||
if (!mStream) {
|
||||
@ -1190,12 +1255,14 @@ nsWaveDecoder::GetCurrentTime()
|
||||
nsresult
|
||||
nsWaveDecoder::Seek(float aTime)
|
||||
{
|
||||
mTimeOffset = aTime;
|
||||
|
||||
if (!mPlaybackStateMachine) {
|
||||
Load(mURI, nsnull, nsnull);
|
||||
}
|
||||
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->Seek(aTime);
|
||||
mPlaybackStateMachine->Seek(mTimeOffset);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1265,7 +1332,7 @@ nsWaveDecoder::Stop()
|
||||
}
|
||||
|
||||
if (mStream) {
|
||||
mStream->Close();
|
||||
mStream->Cancel();
|
||||
}
|
||||
|
||||
if (mPlaybackThread) {
|
||||
@ -1433,24 +1500,28 @@ nsWaveDecoder::GetStatistics()
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::NotifySuspendedStatusChanged()
|
||||
nsWaveDecoder::NotifyBytesDownloaded(PRInt64 aBytes)
|
||||
{
|
||||
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();
|
||||
}
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->NotifyBytesDownloaded(aBytes);
|
||||
}
|
||||
UpdateReadyStateForData();
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::NotifyBytesDownloaded()
|
||||
nsWaveDecoder::NotifyDownloadSeeked(PRInt64 aBytes)
|
||||
{
|
||||
UpdateReadyStateForData();
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->NotifyDownloadSeeked(aBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
{
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->NotifyDownloadEnded(aStatus);
|
||||
}
|
||||
if (aStatus != NS_BINDING_ABORTED) {
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
ResourceLoaded();
|
||||
@ -1461,6 +1532,24 @@ nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
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
|
||||
|
@ -136,9 +136,6 @@ pref("browser.triple_click_selects_paragraph", true);
|
||||
// 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
|
||||
|
Loading…
Reference in New Issue
Block a user