Backed out changeset b6a1093e5814

This commit is contained in:
Robert O'Callahan 2009-04-01 16:19:00 +13:00
parent 6e281fcaab
commit 965a9ce568
18 changed files with 1187 additions and 3112 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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 \

View File

@ -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

View 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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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();
}

View 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

View File

@ -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());
}

View File

@ -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

View File

@ -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