2008-07-29 23:50:14 -07:00
|
|
|
/* -*- 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: ML 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 ***** */
|
2008-11-09 17:38:02 -08:00
|
|
|
#include <limits>
|
2008-07-29 23:50:14 -07:00
|
|
|
#include "prlog.h"
|
|
|
|
#include "prmem.h"
|
|
|
|
#include "nsIFrame.h"
|
|
|
|
#include "nsIDocument.h"
|
|
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "nsIDOMHTMLMediaElement.h"
|
|
|
|
#include "nsNetUtil.h"
|
|
|
|
#include "nsAudioStream.h"
|
|
|
|
#include "nsChannelReader.h"
|
|
|
|
#include "nsHTMLVideoElement.h"
|
|
|
|
#include "nsIObserver.h"
|
|
|
|
#include "nsIObserverService.h"
|
|
|
|
#include "nsAutoLock.h"
|
2008-10-19 00:39:21 -07:00
|
|
|
#include "nsTArray.h"
|
2008-10-29 22:20:08 -07:00
|
|
|
#include "nsNetUtil.h"
|
2008-07-29 23:50:14 -07:00
|
|
|
#include "nsOggDecoder.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
The maximum height and width of the video. Used for
|
|
|
|
sanitizing the memory allocation of the RGB buffer
|
|
|
|
*/
|
|
|
|
#define MAX_VIDEO_WIDTH 2000
|
|
|
|
#define MAX_VIDEO_HEIGHT 2000
|
|
|
|
|
|
|
|
// The number of entries in oggplay buffer list. This value
|
|
|
|
// is the one used by the oggplay examples.
|
|
|
|
#define OGGPLAY_BUFFER_SIZE 20
|
|
|
|
|
|
|
|
// The number of frames to read before audio callback is called.
|
|
|
|
// This value is the one used by the oggplay examples.
|
|
|
|
#define OGGPLAY_FRAMES_PER_CALLBACK 2048
|
|
|
|
|
|
|
|
// Offset into Ogg buffer containing audio information. This value
|
|
|
|
// is the one used by the oggplay examples.
|
|
|
|
#define OGGPLAY_AUDIO_OFFSET 250L
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Wait this number of seconds when buffering, then leave and play
|
|
|
|
// as best as we can if the required amount of data hasn't been
|
|
|
|
// retrieved.
|
|
|
|
#define BUFFERING_WAIT 15
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// The amount of data to retrieve during buffering is computed based
|
|
|
|
// on the download rate. BUFFERING_MIN_RATE is the minimum download
|
|
|
|
// rate to be used in that calculation to help avoid constant buffering
|
|
|
|
// attempts at a time when the average download rate has not stabilised.
|
|
|
|
#define BUFFERING_MIN_RATE 50000
|
|
|
|
#define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// The number of seconds of buffer data before buffering happens
|
|
|
|
// based on current playback rate.
|
|
|
|
#define BUFFERING_SECONDS_LOW_WATER_MARK 1
|
|
|
|
|
|
|
|
/*
|
|
|
|
All reading (including seeks) from the nsMediaStream are done on the
|
|
|
|
decoding thread. The decoder thread is informed before closing that
|
|
|
|
the stream is about to close via the Shutdown
|
|
|
|
event. oggplay_prepare_for_close is called before sending the
|
|
|
|
shutdown event to tell liboggplay to shutdown.
|
|
|
|
|
|
|
|
This call results in oggplay internally not calling any
|
|
|
|
read/write/seek/tell methods, and returns a value that results in
|
|
|
|
stopping the decoder thread.
|
|
|
|
|
|
|
|
oggplay_close is called in the destructor which results in the media
|
|
|
|
stream being closed. This is how the nsMediaStream contract that no
|
|
|
|
read/seeking must occur during or after Close is called is enforced.
|
|
|
|
|
|
|
|
This object keeps pointers to the nsOggDecoder and nsChannelReader
|
|
|
|
objects. Since the lifetime of nsOggDecodeStateMachine is
|
|
|
|
controlled by nsOggDecoder it will never have a stale reference to
|
|
|
|
these objects. The reader is destroyed by the call to oggplay_close
|
|
|
|
which is done in the destructor so again this will never be a stale
|
|
|
|
reference.
|
|
|
|
|
|
|
|
All internal state is synchronised via the decoder monitor. NotifyAll
|
|
|
|
on the monitor is called when the state of the state machine is changed
|
|
|
|
by the main thread. The following changes to state cause a notify:
|
|
|
|
|
|
|
|
mState and data related to that state changed (mSeekTime, etc)
|
|
|
|
Ogg Metadata Loaded
|
|
|
|
First Frame Loaded
|
|
|
|
Frame decoded
|
|
|
|
|
|
|
|
See nsOggDecoder.h for more details.
|
|
|
|
*/
|
|
|
|
class nsOggDecodeStateMachine : public nsRunnable
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
// Object to hold the decoded data from a frame
|
|
|
|
class FrameData {
|
|
|
|
public:
|
|
|
|
FrameData() :
|
2009-03-16 20:45:00 -07:00
|
|
|
mVideoHeader(nsnull),
|
2008-10-19 00:39:21 -07:00
|
|
|
mVideoWidth(0),
|
|
|
|
mVideoHeight(0),
|
2009-03-16 20:45:00 -07:00
|
|
|
mUVWidth(0),
|
|
|
|
mUVHeight(0),
|
2008-10-19 00:39:21 -07:00
|
|
|
mDecodedFrameTime(0.0),
|
|
|
|
mTime(0.0)
|
|
|
|
{
|
|
|
|
MOZ_COUNT_CTOR(FrameData);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
~FrameData()
|
|
|
|
{
|
|
|
|
MOZ_COUNT_DTOR(FrameData);
|
2009-03-16 20:45:00 -07:00
|
|
|
|
|
|
|
if (mVideoHeader) {
|
|
|
|
oggplay_callback_info_unlock_item(mVideoHeader);
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-12-15 15:06:22 -08:00
|
|
|
// Write the audio data from the frame to the Audio stream.
|
|
|
|
void Write(nsAudioStream* aStream)
|
|
|
|
{
|
|
|
|
PRUint32 length = mAudioData.Length();
|
|
|
|
if (length == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
aStream->Write(mAudioData.Elements(), length);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2009-03-16 20:45:00 -07:00
|
|
|
void SetVideoHeader(OggPlayDataHeader* aVideoHeader)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(!mVideoHeader, "Frame already owns a video header");
|
|
|
|
mVideoHeader = aVideoHeader;
|
|
|
|
oggplay_callback_info_lock_item(mVideoHeader);
|
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
// The position in the stream where this frame ended, in bytes
|
|
|
|
PRInt64 mEndStreamPosition;
|
2009-03-16 20:45:00 -07:00
|
|
|
OggPlayDataHeader* mVideoHeader;
|
2008-10-19 00:39:21 -07:00
|
|
|
nsTArray<float> mAudioData;
|
|
|
|
int mVideoWidth;
|
|
|
|
int mVideoHeight;
|
2009-03-16 20:45:00 -07:00
|
|
|
int mUVWidth;
|
|
|
|
int mUVHeight;
|
2008-10-19 00:39:21 -07:00
|
|
|
float mDecodedFrameTime;
|
|
|
|
float mTime;
|
|
|
|
OggPlayStreamInfo mState;
|
|
|
|
};
|
|
|
|
|
|
|
|
// A queue of decoded video frames.
|
|
|
|
class FrameQueue
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
public:
|
|
|
|
FrameQueue() :
|
|
|
|
mHead(0),
|
|
|
|
mTail(0),
|
|
|
|
mEmpty(PR_TRUE)
|
|
|
|
{
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void Push(FrameData* frame)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(!IsFull(), "FrameQueue is full");
|
|
|
|
mQueue[mTail] = frame;
|
|
|
|
mTail = (mTail+1) % OGGPLAY_BUFFER_SIZE;
|
|
|
|
mEmpty = PR_FALSE;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
FrameData* Peek()
|
|
|
|
{
|
|
|
|
NS_ASSERTION(!mEmpty, "FrameQueue is empty");
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
return mQueue[mHead];
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
FrameData* Pop()
|
|
|
|
{
|
|
|
|
NS_ASSERTION(!mEmpty, "FrameQueue is empty");
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
FrameData* result = mQueue[mHead];
|
|
|
|
mHead = (mHead + 1) % OGGPLAY_BUFFER_SIZE;
|
|
|
|
mEmpty = mHead == mTail;
|
|
|
|
return result;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
PRBool IsEmpty() const
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
return mEmpty;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
PRBool IsFull() const
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
return !mEmpty && mHead == mTail;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
private:
|
|
|
|
FrameData* mQueue[OGGPLAY_BUFFER_SIZE];
|
|
|
|
PRInt32 mHead;
|
|
|
|
PRInt32 mTail;
|
|
|
|
PRPackedBool mEmpty;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Enumeration for the valid states
|
|
|
|
enum State {
|
|
|
|
DECODER_STATE_DECODING_METADATA,
|
|
|
|
DECODER_STATE_DECODING_FIRSTFRAME,
|
|
|
|
DECODER_STATE_DECODING,
|
|
|
|
DECODER_STATE_SEEKING,
|
|
|
|
DECODER_STATE_BUFFERING,
|
|
|
|
DECODER_STATE_COMPLETED,
|
|
|
|
DECODER_STATE_SHUTDOWN
|
|
|
|
};
|
|
|
|
|
2008-11-18 00:19:32 -08:00
|
|
|
nsOggDecodeStateMachine(nsOggDecoder* aDecoder);
|
2008-10-19 00:39:21 -07:00
|
|
|
~nsOggDecodeStateMachine();
|
|
|
|
|
|
|
|
// Cause state transitions. These methods obtain the decoder monitor
|
|
|
|
// to synchronise the change of state, and to notify other threads
|
|
|
|
// that the state has changed.
|
|
|
|
void Shutdown();
|
|
|
|
void Decode();
|
|
|
|
void Seek(float aTime);
|
|
|
|
|
|
|
|
NS_IMETHOD Run();
|
|
|
|
|
|
|
|
PRBool HasAudio()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "HasAudio() called during invalid state");
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "HasAudio() called without acquiring decoder monitor");
|
|
|
|
return mAudioTrack != -1;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Decode one frame of data, returning the OggPlay error code. Must
|
|
|
|
// be called only when the current state > DECODING_METADATA. The decode
|
|
|
|
// monitor MUST NOT be locked during this call since it can take a long
|
|
|
|
// time. liboggplay internally handles locking.
|
|
|
|
// Any return value apart from those below is mean decoding cannot continue.
|
|
|
|
// E_OGGPLAY_CONTINUE = One frame decoded and put in buffer list
|
|
|
|
// E_OGGPLAY_USER_INTERRUPT = One frame decoded, buffer list is now full
|
|
|
|
// E_OGGPLAY_TIMEOUT = No frames decoded, timed out
|
|
|
|
OggPlayErrorCode DecodeFrame();
|
|
|
|
|
|
|
|
// Returns the next decoded frame of data. The caller is responsible
|
|
|
|
// for freeing the memory returned. This function must be called
|
|
|
|
// only when the current state > DECODING_METADATA. The decode
|
|
|
|
// monitor lock does not need to be locked during this call since
|
|
|
|
// liboggplay internally handles locking.
|
|
|
|
FrameData* NextFrame();
|
|
|
|
|
|
|
|
// Play a frame of decoded video. The decode monitor is obtained
|
|
|
|
// internally by this method for synchronisation.
|
|
|
|
void PlayFrame();
|
|
|
|
|
|
|
|
// Play the video data from the given frame. The decode monitor
|
|
|
|
// must be locked when calling this method.
|
|
|
|
void PlayVideo(FrameData* aFrame);
|
|
|
|
|
|
|
|
// Play the audio data from the given frame. The decode monitor must
|
2008-12-15 15:06:22 -08:00
|
|
|
// be locked when calling this method.
|
|
|
|
void PlayAudio(FrameData* aFrame);
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
// Called from the main thread to get the current frame time. The decoder
|
|
|
|
// monitor must be obtained before calling this.
|
|
|
|
float GetCurrentTime();
|
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
// Called from the main thread to get the duration. The decoder monitor
|
|
|
|
// must be obtained before calling this. It is in units of milliseconds.
|
|
|
|
PRInt64 GetDuration();
|
|
|
|
|
2009-02-07 02:10:34 -08:00
|
|
|
// Called from the main thread to set the duration of the media resource
|
|
|
|
// if it is able to be obtained via HTTP headers. The decoder monitor
|
|
|
|
// must be obtained before calling this.
|
|
|
|
void SetDuration(PRInt64 aDuration);
|
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
// Called from the main thread to set whether the media resource can
|
|
|
|
// be seeked. The decoder monitor must be obtained before calling this.
|
|
|
|
void SetSeekable(PRBool aSeekable);
|
|
|
|
|
2009-02-25 02:59:00 -08:00
|
|
|
// Set the audio volume. The decoder monitor must be obtained before
|
|
|
|
// calling this.
|
2008-10-19 00:39:21 -07:00
|
|
|
void SetVolume(float aVolume);
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-23 01:02:18 -07:00
|
|
|
// Clear the flag indicating that a playback position change event
|
|
|
|
// is currently queued. This is called from the main thread and must
|
|
|
|
// be called with the decode monitor held.
|
|
|
|
void ClearPositionChangeFlag();
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
// Must be called with the decode monitor held. Can be called by main
|
|
|
|
// thread.
|
|
|
|
PRBool HaveNextFrameData() const {
|
2009-02-10 17:43:45 -08:00
|
|
|
return !mDecodedFrames.IsEmpty() &&
|
|
|
|
(mState == DECODER_STATE_DECODING ||
|
|
|
|
mState == DECODER_STATE_COMPLETED);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Must be called with the decode monitor held. Can be called by main
|
|
|
|
// thread.
|
|
|
|
PRBool IsBuffering() const {
|
|
|
|
return mState == nsOggDecodeStateMachine::DECODER_STATE_BUFFERING;
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
protected:
|
|
|
|
// Convert the OggPlay frame information into a format used by Gecko
|
|
|
|
// (RGB for video, float for sound, etc).The decoder monitor must be
|
|
|
|
// acquired in the scope of calls to these functions. They must be
|
|
|
|
// called only when the current state > DECODING_METADATA.
|
2009-03-16 20:45:00 -07:00
|
|
|
void HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayDataHeader* aVideoHeader);
|
2008-10-19 00:39:21 -07:00
|
|
|
void HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize);
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// These methods can only be called on the decoding thread.
|
2009-02-19 18:49:00 -08:00
|
|
|
void LoadOggHeaders(nsChannelReader* aReader);
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Initializes and opens the audio stream. Called from the decode
|
|
|
|
// thread only. Must be called with the decode monitor held.
|
|
|
|
void OpenAudioStream();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Closes and releases resources used by the audio stream. Called
|
|
|
|
// from the decode thread only. Must be called with the decode
|
|
|
|
// monitor held.
|
|
|
|
void CloseAudioStream();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Start playback of audio, either by opening or resuming the audio
|
|
|
|
// stream. Must be called with the decode monitor held.
|
|
|
|
void StartAudio();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Stop playback of audio, either by closing or pausing the audio
|
|
|
|
// stream. Must be called with the decode monitor held.
|
|
|
|
void StopAudio();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Start playback of media. Must be called with the decode monitor held.
|
|
|
|
void StartPlayback();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Stop playback of media. Must be called with the decode monitor held.
|
|
|
|
void StopPlayback();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-23 01:02:18 -07:00
|
|
|
// Update the playback position. This can result in a timeupdate event
|
|
|
|
// and an invalidate of the frame being dispatched asynchronously if
|
|
|
|
// there is no such event currently queued.
|
|
|
|
// Only called on the decoder thread. Must be called with
|
|
|
|
// the decode monitor held.
|
|
|
|
void UpdatePlaybackPosition(float aTime);
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
private:
|
|
|
|
// *****
|
|
|
|
// The follow fields are only accessed by the decoder thread
|
|
|
|
// *****
|
|
|
|
|
|
|
|
// The decoder object that created this state machine. The decoder
|
|
|
|
// always outlives us since it controls our lifetime.
|
|
|
|
nsOggDecoder* mDecoder;
|
|
|
|
|
|
|
|
// The OggPlay handle. Synchronisation of calls to oggplay functions
|
|
|
|
// are handled by liboggplay. We control the lifetime of this
|
|
|
|
// object, destroying it in our destructor.
|
|
|
|
OggPlay* mPlayer;
|
|
|
|
|
|
|
|
// Frame data containing decoded video/audio for the frame the
|
2009-02-05 00:02:21 -08:00
|
|
|
// current frame and the previous frame. Always accessed with monitor
|
|
|
|
// held. Written only via the decoder thread, but can be tested on
|
|
|
|
// main thread via HaveNextFrameData.
|
2008-10-19 00:39:21 -07:00
|
|
|
FrameQueue mDecodedFrames;
|
|
|
|
|
|
|
|
// The time that playback started from the system clock. This is used
|
|
|
|
// for synchronising frames. It is reset after a seek as the mTime member
|
|
|
|
// of FrameData is reset to start at 0 from the first frame after a seek.
|
|
|
|
// Accessed only via the decoder thread.
|
|
|
|
PRIntervalTime mPlayStartTime;
|
|
|
|
|
|
|
|
// The time that playback was most recently paused, either via
|
|
|
|
// buffering or pause. This is used to compute mPauseDuration for
|
|
|
|
// a/v sync adjustments. Accessed only via the decoder thread.
|
|
|
|
PRIntervalTime mPauseStartTime;
|
|
|
|
|
|
|
|
// The total time that has been spent in completed pauses (via
|
|
|
|
// 'pause' or buffering). This is used to adjust for these
|
|
|
|
// pauses when computing a/v synchronisation. Accessed only via the
|
|
|
|
// decoder thread.
|
|
|
|
PRIntervalTime mPauseDuration;
|
|
|
|
|
|
|
|
// PR_TRUE if the media is playing and the decoder has started
|
|
|
|
// the sound and adjusted the sync time for pauses. PR_FALSE
|
|
|
|
// if the media is paused and the decoder has stopped the sound
|
|
|
|
// and adjusted the sync time for pauses. Accessed only via the
|
|
|
|
// decoder thread.
|
|
|
|
PRPackedBool mPlaying;
|
|
|
|
|
|
|
|
// Number of seconds of data video/audio data held in a frame.
|
|
|
|
// Accessed only via the decoder thread.
|
|
|
|
float mCallbackPeriod;
|
|
|
|
|
|
|
|
// Video data. These are initially set when the metadata is loaded.
|
|
|
|
// They are only accessed from the decoder thread.
|
|
|
|
PRInt32 mVideoTrack;
|
|
|
|
float mFramerate;
|
|
|
|
|
|
|
|
// Audio data. These are initially set when the metadata is loaded.
|
|
|
|
// They are only accessed from the decoder thread.
|
|
|
|
PRInt32 mAudioRate;
|
|
|
|
PRInt32 mAudioChannels;
|
|
|
|
PRInt32 mAudioTrack;
|
|
|
|
|
|
|
|
// Time that buffering started. Used for buffering timeout and only
|
|
|
|
// accessed in the decoder thread.
|
|
|
|
PRIntervalTime mBufferingStart;
|
|
|
|
|
2009-02-10 17:44:39 -08:00
|
|
|
// Download position where we should stop buffering. Only
|
|
|
|
// accessed in the decoder thread.
|
|
|
|
PRInt64 mBufferingEndOffset;
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
// The time value of the last decoded video frame. Used for
|
|
|
|
// computing the sleep period between frames for a/v sync.
|
|
|
|
// Read/Write from the decode thread only.
|
|
|
|
float mLastFrameTime;
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
// The decoder position of the end of the last decoded video frame.
|
|
|
|
// Read/Write from the decode thread only.
|
|
|
|
PRInt64 mLastFramePosition;
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// *****
|
|
|
|
// The follow fields are accessed by the decoder thread or
|
|
|
|
// the main thread.
|
|
|
|
// *****
|
|
|
|
|
|
|
|
// The decoder monitor must be obtained before modifying this state.
|
|
|
|
// NotifyAll on the monitor must be called when the state is changed by
|
|
|
|
// the main thread so the decoder thread can wake up.
|
|
|
|
State mState;
|
|
|
|
|
|
|
|
// Position to seek to when the seek state transition occurs. The
|
|
|
|
// decoder monitor lock must be obtained before reading or writing
|
|
|
|
// this value.
|
|
|
|
float mSeekTime;
|
|
|
|
|
|
|
|
// The audio stream resource. Used on the decode thread and the
|
2009-02-25 02:59:00 -08:00
|
|
|
// main thread (Via the SetVolume call). Synchronisation via
|
2008-10-19 00:39:21 -07:00
|
|
|
// mDecoder monitor.
|
|
|
|
nsAutoPtr<nsAudioStream> mAudioStream;
|
|
|
|
|
|
|
|
// The time of the current frame in seconds. This is referenced from
|
|
|
|
// 0.0 which is the initial start of the stream. Set by the decode
|
|
|
|
// thread, and read-only from the main thread to get the current
|
|
|
|
// time value. Synchronised via decoder monitor.
|
|
|
|
float mCurrentFrameTime;
|
|
|
|
|
|
|
|
// Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
|
|
|
|
// from the decode and main threads. Synchronised via decoder
|
|
|
|
// monitor.
|
|
|
|
float mVolume;
|
2008-10-23 01:02:18 -07:00
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
// Duration of the media resource. It is accessed from the decoder and main
|
|
|
|
// threads. Synchronised via decoder monitor. It is in units of
|
|
|
|
// milliseconds.
|
|
|
|
PRInt64 mDuration;
|
|
|
|
|
|
|
|
// PR_TRUE if the media resource can be seeked. Accessed from the decoder
|
|
|
|
// and main threads. Synchronised via decoder monitor.
|
|
|
|
PRPackedBool mSeekable;
|
|
|
|
|
2008-10-23 01:02:18 -07:00
|
|
|
// PR_TRUE if an event to notify about a change in the playback
|
|
|
|
// position has been queued, but not yet run. It is set to PR_FALSE when
|
|
|
|
// the event is run. This allows coalescing of these events as they can be
|
|
|
|
// produced many times per second. Synchronised via decoder monitor.
|
|
|
|
PRPackedBool mPositionChangeQueued;
|
2008-10-19 00:39:21 -07:00
|
|
|
};
|
|
|
|
|
2008-11-18 00:19:32 -08:00
|
|
|
nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder) :
|
2008-10-19 00:39:21 -07:00
|
|
|
mDecoder(aDecoder),
|
|
|
|
mPlayer(0),
|
|
|
|
mPlayStartTime(0),
|
|
|
|
mPauseStartTime(0),
|
|
|
|
mPauseDuration(0),
|
|
|
|
mPlaying(PR_FALSE),
|
|
|
|
mCallbackPeriod(1.0),
|
|
|
|
mVideoTrack(-1),
|
|
|
|
mFramerate(0.0),
|
2008-07-29 23:50:14 -07:00
|
|
|
mAudioRate(0),
|
|
|
|
mAudioChannels(0),
|
|
|
|
mAudioTrack(-1),
|
2008-10-19 00:39:21 -07:00
|
|
|
mBufferingStart(0),
|
2009-02-10 17:44:39 -08:00
|
|
|
mBufferingEndOffset(0),
|
2008-10-19 00:39:21 -07:00
|
|
|
mLastFrameTime(0),
|
2009-02-05 00:02:21 -08:00
|
|
|
mLastFramePosition(-1),
|
2008-10-19 00:39:21 -07:00
|
|
|
mState(DECODER_STATE_DECODING_METADATA),
|
|
|
|
mSeekTime(0.0),
|
|
|
|
mCurrentFrameTime(0.0),
|
2008-10-23 01:02:18 -07:00
|
|
|
mVolume(1.0),
|
2008-11-09 17:38:02 -08:00
|
|
|
mDuration(-1),
|
|
|
|
mSeekable(PR_TRUE),
|
2008-10-23 01:02:18 -07:00
|
|
|
mPositionChangeQueued(PR_FALSE)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
while (!mDecodedFrames.IsEmpty()) {
|
|
|
|
delete mDecodedFrames.Pop();
|
|
|
|
}
|
|
|
|
oggplay_close(mPlayer);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
OggPlayErrorCode nsOggDecodeStateMachine::DecodeFrame()
|
|
|
|
{
|
|
|
|
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "DecodeFrame() called during invalid state");
|
|
|
|
return oggplay_step_decoding(mPlayer);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "NextFrame() called during invalid state");
|
|
|
|
OggPlayCallbackInfo** info = oggplay_buffer_retrieve_next(mPlayer);
|
|
|
|
if (!info)
|
|
|
|
return nsnull;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
FrameData* frame = new FrameData();
|
|
|
|
if (!frame) {
|
|
|
|
return nsnull;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
frame->mTime = mLastFrameTime;
|
2009-02-05 00:02:21 -08:00
|
|
|
frame->mEndStreamPosition = mDecoder->mDecoderPosition;
|
2008-10-19 00:39:21 -07:00
|
|
|
mLastFrameTime += mCallbackPeriod;
|
2009-02-05 00:02:21 -08:00
|
|
|
|
|
|
|
if (mLastFramePosition >= 0) {
|
|
|
|
NS_ASSERTION(frame->mEndStreamPosition >= mLastFramePosition,
|
|
|
|
"Playback positions must not decrease without an intervening reset");
|
|
|
|
mDecoder->mPlaybackStatistics.Start(frame->mTime*PR_TicksPerSecond());
|
|
|
|
mDecoder->mPlaybackStatistics.AddBytes(frame->mEndStreamPosition - mLastFramePosition);
|
|
|
|
mDecoder->mPlaybackStatistics.Stop(mLastFrameTime*PR_TicksPerSecond());
|
2009-03-31 17:52:56 -07:00
|
|
|
mDecoder->UpdatePlaybackRate();
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
mLastFramePosition = frame->mEndStreamPosition;
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
int num_tracks = oggplay_get_num_tracks(mPlayer);
|
|
|
|
float audioTime = 0.0;
|
|
|
|
float videoTime = 0.0;
|
|
|
|
|
|
|
|
if (mVideoTrack != -1 &&
|
|
|
|
num_tracks > mVideoTrack &&
|
|
|
|
oggplay_callback_info_get_type(info[mVideoTrack]) == OGGPLAY_YUV_VIDEO) {
|
|
|
|
OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mVideoTrack]);
|
|
|
|
videoTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
|
2009-03-16 20:45:00 -07:00
|
|
|
HandleVideoData(frame, mVideoTrack, headers[0]);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
if (mAudioTrack != -1 &&
|
|
|
|
num_tracks > mAudioTrack &&
|
|
|
|
oggplay_callback_info_get_type(info[mAudioTrack]) == OGGPLAY_FLOATS_AUDIO) {
|
|
|
|
OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mAudioTrack]);
|
|
|
|
audioTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
|
|
|
|
int required = oggplay_callback_info_get_required(info[mAudioTrack]);
|
|
|
|
for (int j = 0; j < required; ++j) {
|
|
|
|
int size = oggplay_callback_info_get_record_size(headers[j]);
|
|
|
|
OggPlayAudioData* audio_data = oggplay_callback_info_get_audio_data(headers[j]);
|
|
|
|
HandleAudioData(frame, audio_data, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pick one stream to act as the reference track to indicate if the
|
|
|
|
// stream has ended, seeked, etc.
|
|
|
|
if (mVideoTrack >= 0 )
|
|
|
|
frame->mState = oggplay_callback_info_get_stream_info(info[mVideoTrack]);
|
|
|
|
else if (mAudioTrack >= 0)
|
|
|
|
frame->mState = oggplay_callback_info_get_stream_info(info[mAudioTrack]);
|
|
|
|
else
|
|
|
|
frame->mState = OGGPLAY_STREAM_UNINITIALISED;
|
|
|
|
|
|
|
|
frame->mDecodedFrameTime = mVideoTrack == -1 ? audioTime : videoTime;
|
|
|
|
|
|
|
|
oggplay_buffer_release(mPlayer, info);
|
|
|
|
return frame;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2009-03-16 20:45:00 -07:00
|
|
|
void nsOggDecodeStateMachine::HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayDataHeader* aVideoHeader) {
|
|
|
|
if (!aVideoHeader)
|
2008-10-19 00:39:21 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
int y_width;
|
|
|
|
int y_height;
|
|
|
|
oggplay_get_video_y_size(mPlayer, aTrackNum, &y_width, &y_height);
|
|
|
|
int uv_width;
|
|
|
|
int uv_height;
|
|
|
|
oggplay_get_video_uv_size(mPlayer, aTrackNum, &uv_width, &uv_height);
|
|
|
|
|
|
|
|
if (y_width >= MAX_VIDEO_WIDTH || y_height >= MAX_VIDEO_HEIGHT) {
|
|
|
|
return;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
aFrame->mVideoWidth = y_width;
|
|
|
|
aFrame->mVideoHeight = y_height;
|
2009-03-16 20:45:00 -07:00
|
|
|
aFrame->mUVWidth = uv_width;
|
|
|
|
aFrame->mUVHeight = uv_height;
|
|
|
|
aFrame->SetVideoHeader(aVideoHeader);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize) {
|
|
|
|
// 'aSize' is number of samples. Multiply by number of channels to
|
|
|
|
// get the actual number of floats being sent.
|
|
|
|
int size = aSize * mAudioChannels;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
aFrame->mAudioData.AppendElements(reinterpret_cast<float*>(aAudioData), size);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::PlayFrame() {
|
|
|
|
// Play a frame of video and/or audio data.
|
|
|
|
// If we are playing we open the audio stream if needed
|
|
|
|
// If there are decoded frames in the queue a single frame
|
|
|
|
// is popped off and played if it is time for that frame
|
|
|
|
// to display.
|
|
|
|
// If it is not time yet to display the frame, we either
|
|
|
|
// continue decoding frames, or wait until it is time for
|
|
|
|
// the frame to display if the queue is full.
|
|
|
|
//
|
|
|
|
// If the decode state is not PLAYING then we just exit
|
|
|
|
// so we can continue decoding frames. If the queue is
|
|
|
|
// full we wait for a state change.
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
|
|
|
|
if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
|
|
|
|
if (!mPlaying) {
|
|
|
|
StartPlayback();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
if (!mDecodedFrames.IsEmpty()) {
|
|
|
|
FrameData* frame = mDecodedFrames.Peek();
|
|
|
|
if (frame->mState == OGGPLAY_STREAM_JUST_SEEKED) {
|
|
|
|
// After returning from a seek all mTime members of
|
|
|
|
// FrameData start again from a time position of 0.
|
|
|
|
// Reset the play start time.
|
|
|
|
mPlayStartTime = PR_IntervalNow();
|
|
|
|
mPauseDuration = 0;
|
2009-03-03 02:23:54 -08:00
|
|
|
frame->mState = OGGPLAY_STREAM_INITIALISED;
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0);
|
|
|
|
if (time >= frame->mTime) {
|
|
|
|
// Audio for the current frame is played, but video for the next frame
|
|
|
|
// is displayed, to account for lag from the time the audio is written
|
|
|
|
// to when it is played. This will go away when we move to a/v sync
|
|
|
|
// using the audio hardware clock.
|
|
|
|
PlayAudio(frame);
|
2008-12-15 15:06:22 -08:00
|
|
|
mDecodedFrames.Pop();
|
|
|
|
PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
|
2009-02-05 00:02:21 -08:00
|
|
|
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
|
2008-10-23 01:02:18 -07:00
|
|
|
UpdatePlaybackPosition(frame->mDecodedFrameTime);
|
2008-10-19 00:39:21 -07:00
|
|
|
delete frame;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
else {
|
|
|
|
// If the queue of decoded frame is full then we wait for the
|
|
|
|
// approximate time until the next frame.
|
|
|
|
if (mDecodedFrames.IsFull()) {
|
|
|
|
mon.Wait(PR_MillisecondsToInterval(PRInt64((frame->mTime - time)*1000)));
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (mPlaying) {
|
|
|
|
StopPlayback();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
if (mDecodedFrames.IsFull() && mState == DECODER_STATE_DECODING) {
|
|
|
|
mon.Wait();
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN) {
|
|
|
|
return;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::PlayVideo(FrameData* aFrame)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayVideo() called without acquiring decoder monitor");
|
2009-03-16 20:45:00 -07:00
|
|
|
if (aFrame && aFrame->mVideoHeader) {
|
|
|
|
OggPlayVideoData* videoData = oggplay_callback_info_get_video_data(aFrame->mVideoHeader);
|
|
|
|
|
|
|
|
OggPlayYUVChannels yuv;
|
|
|
|
yuv.ptry = videoData->y;
|
|
|
|
yuv.ptru = videoData->u;
|
|
|
|
yuv.ptrv = videoData->v;
|
|
|
|
yuv.uv_width = aFrame->mUVWidth;
|
|
|
|
yuv.uv_height = aFrame->mUVHeight;
|
|
|
|
yuv.y_width = aFrame->mVideoWidth;
|
|
|
|
yuv.y_height = aFrame->mVideoHeight;
|
|
|
|
|
|
|
|
size_t size = aFrame->mVideoWidth * aFrame->mVideoHeight * 4;
|
|
|
|
nsAutoArrayPtr<unsigned char> buffer(new unsigned char[size]);
|
|
|
|
if (!buffer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
OggPlayRGBChannels rgb;
|
|
|
|
rgb.ptro = buffer;
|
|
|
|
rgb.rgb_width = aFrame->mVideoWidth;
|
|
|
|
rgb.rgb_height = aFrame->mVideoHeight;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2009-03-16 20:45:00 -07:00
|
|
|
#ifdef IS_BIG_ENDIAN
|
|
|
|
oggplay_yuv2argb(&yuv, &rgb);
|
|
|
|
#else
|
|
|
|
oggplay_yuv2bgr(&yuv, &rgb);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mDecoder->SetRGBData(aFrame->mVideoWidth, aFrame->mVideoHeight, mFramerate, buffer.forget());
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-12-15 15:06:22 -08:00
|
|
|
void nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame)
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayAudio() called without acquiring decoder monitor");
|
2008-12-15 15:06:22 -08:00
|
|
|
if (!mAudioStream)
|
|
|
|
return;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-12-15 15:06:22 -08:00
|
|
|
aFrame->Write(mAudioStream);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::OpenAudioStream()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "OpenAudioStream() called without acquiring decoder monitor");
|
|
|
|
mAudioStream = new nsAudioStream();
|
|
|
|
if (!mAudioStream) {
|
|
|
|
LOG(PR_LOG_ERROR, ("Could not create audio stream"));
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
else {
|
2009-01-29 23:27:43 -08:00
|
|
|
mAudioStream->Init(mAudioChannels, mAudioRate, nsAudioStream::FORMAT_FLOAT32);
|
2008-10-19 00:39:21 -07:00
|
|
|
mAudioStream->SetVolume(mVolume);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::CloseAudioStream()
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "CloseAudioStream() called without acquiring decoder monitor");
|
|
|
|
if (mAudioStream) {
|
|
|
|
mAudioStream->Shutdown();
|
|
|
|
mAudioStream = nsnull;
|
2008-08-03 18:31:33 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-08-03 18:31:33 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::StartAudio()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartAudio() called without acquiring decoder monitor");
|
|
|
|
if (HasAudio()) {
|
|
|
|
OpenAudioStream();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::StopAudio()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopAudio() called without acquiring decoder monitor");
|
|
|
|
if (HasAudio()) {
|
|
|
|
CloseAudioStream();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::StartPlayback()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartPlayback() called without acquiring decoder monitor");
|
|
|
|
StartAudio();
|
|
|
|
mPlaying = PR_TRUE;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// If this is the very first play, then set the initial start time
|
|
|
|
if (mPlayStartTime == 0) {
|
|
|
|
mPlayStartTime = PR_IntervalNow();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-08-03 18:31:33 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// If we have been paused previously, then compute duration spent paused
|
|
|
|
if (mPauseStartTime != 0) {
|
|
|
|
mPauseDuration += PR_IntervalNow() - mPauseStartTime;
|
2008-08-03 18:31:33 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::StopPlayback()
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopPlayback() called without acquiring decoder monitor");
|
|
|
|
StopAudio();
|
|
|
|
mPlaying = PR_FALSE;
|
|
|
|
mPauseStartTime = PR_IntervalNow();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-23 01:02:18 -07:00
|
|
|
void nsOggDecodeStateMachine::UpdatePlaybackPosition(float aTime)
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "UpdatePlaybackPosition() called without acquiring decoder monitor");
|
|
|
|
mCurrentFrameTime = aTime;
|
|
|
|
if (!mPositionChangeQueued) {
|
|
|
|
mPositionChangeQueued = PR_TRUE;
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackPositionChanged);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecodeStateMachine::ClearPositionChangeFlag()
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "ClearPositionChangeFlag() called without acquiring decoder monitor");
|
|
|
|
mPositionChangeQueued = PR_FALSE;
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::SetVolume(float volume)
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetVolume() called without acquiring decoder monitor");
|
|
|
|
if (mAudioStream) {
|
|
|
|
mAudioStream->SetVolume(volume);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
mVolume = volume;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
float nsOggDecodeStateMachine::GetCurrentTime()
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetCurrentTime() called without acquiring decoder monitor");
|
|
|
|
return mCurrentFrameTime;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
PRInt64 nsOggDecodeStateMachine::GetDuration()
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetDuration() called without acquiring decoder monitor");
|
|
|
|
return mDuration;
|
|
|
|
}
|
|
|
|
|
2009-02-07 02:10:34 -08:00
|
|
|
void nsOggDecodeStateMachine::SetDuration(PRInt64 aDuration)
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetDuration() called without acquiring decoder monitor");
|
|
|
|
mDuration = aDuration;
|
|
|
|
}
|
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable)
|
|
|
|
{
|
|
|
|
// NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetSeekable() called without acquiring decoder monitor");
|
|
|
|
mSeekable = aSeekable;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecodeStateMachine::Shutdown()
|
|
|
|
{
|
|
|
|
// oggplay_prepare_for_close cannot be undone. Once called, the
|
|
|
|
// mPlayer object cannot decode any more frames. Once we've entered
|
|
|
|
// the shutdown state here there's no going back.
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
if (mPlayer) {
|
|
|
|
oggplay_prepare_for_close(mPlayer);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state to SHUTDOWN"));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_SHUTDOWN;
|
|
|
|
mon.NotifyAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecodeStateMachine::Decode()
|
|
|
|
{
|
|
|
|
// When asked to decode, switch to decoding only if
|
|
|
|
// we are currently buffering.
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
if (mState == DECODER_STATE_BUFFERING) {
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from BUFFERING to DECODING"));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_DECODING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecodeStateMachine::Seek(float aTime)
|
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
mSeekTime = aTime;
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state to SEEKING (to %f)", aTime));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_SEEKING;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsresult nsOggDecodeStateMachine::Run()
|
|
|
|
{
|
2008-11-18 00:19:32 -08:00
|
|
|
nsChannelReader* reader = mDecoder->GetReader();
|
|
|
|
NS_ENSURE_TRUE(reader, NS_ERROR_NULL_POINTER);
|
2008-10-19 00:39:21 -07:00
|
|
|
while (PR_TRUE) {
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
switch(mState) {
|
|
|
|
case DECODER_STATE_SHUTDOWN:
|
2009-03-24 19:19:58 -07:00
|
|
|
if (mPlaying) {
|
|
|
|
StopPlayback();
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
case DECODER_STATE_DECODING_METADATA:
|
|
|
|
mon.Exit();
|
2009-02-19 18:49:00 -08:00
|
|
|
LoadOggHeaders(reader);
|
2008-10-19 00:39:21 -07:00
|
|
|
mon.Enter();
|
|
|
|
|
|
|
|
if (mState == DECODER_STATE_DECODING_METADATA) {
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from DECODING_METADATA to DECODING_FIRSTFRAME"));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_DECODING_FIRSTFRAME;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECODER_STATE_DECODING_FIRSTFRAME:
|
|
|
|
{
|
|
|
|
OggPlayErrorCode r;
|
|
|
|
do {
|
|
|
|
mon.Exit();
|
|
|
|
r = DecodeFrame();
|
|
|
|
mon.Enter();
|
|
|
|
} while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
|
|
|
|
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
mLastFrameTime = 0;
|
|
|
|
FrameData* frame = NextFrame();
|
|
|
|
if (frame) {
|
|
|
|
mDecodedFrames.Push(frame);
|
2009-02-05 00:02:21 -08:00
|
|
|
mDecoder->mPlaybackPosition = frame->mEndStreamPosition;
|
2008-10-23 01:02:18 -07:00
|
|
|
UpdatePlaybackPosition(frame->mDecodedFrameTime);
|
2008-10-19 00:39:21 -07:00
|
|
|
PlayVideo(frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, FirstFrameLoaded);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
|
|
|
|
|
|
if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from DECODING_FIRSTFRAME to DECODING"));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_DECODING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECODER_STATE_DECODING:
|
|
|
|
{
|
2009-02-05 00:02:21 -08:00
|
|
|
PRBool bufferExhausted = PR_FALSE;
|
2009-02-05 00:02:21 -08:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
if (!mDecodedFrames.IsFull()) {
|
2009-03-31 17:52:56 -07:00
|
|
|
PRInt64 initialDownloadPosition =
|
|
|
|
mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
|
2009-02-05 00:02:21 -08:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
mon.Exit();
|
|
|
|
OggPlayErrorCode r = DecodeFrame();
|
|
|
|
mon.Enter();
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
// Check whether decoding that frame required us to read data
|
|
|
|
// that wasn't available at the start of the frame. That means
|
|
|
|
// we should probably start buffering.
|
|
|
|
bufferExhausted =
|
|
|
|
mDecoder->mDecoderPosition > initialDownloadPosition;
|
|
|
|
|
|
|
|
if (mState != DECODER_STATE_DECODING)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Get the decoded frame and store it in our queue of decoded frames
|
|
|
|
FrameData* frame = NextFrame();
|
|
|
|
if (frame) {
|
|
|
|
mDecodedFrames.Push(frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r != E_OGGPLAY_CONTINUE &&
|
|
|
|
r != E_OGGPLAY_USER_INTERRUPT &&
|
|
|
|
r != E_OGGPLAY_TIMEOUT) {
|
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from DECODING to COMPLETED"));
|
|
|
|
mState = DECODER_STATE_COMPLETED;
|
2009-02-05 02:51:24 -08:00
|
|
|
}
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Show at least the first frame if we're not playing
|
|
|
|
// so we have a poster frame on initial load and after seek.
|
|
|
|
if (!mPlaying && !mDecodedFrames.IsEmpty()) {
|
|
|
|
PlayVideo(mDecodedFrames.Peek());
|
|
|
|
}
|
2009-02-05 02:51:24 -08:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
if (bufferExhausted && mState == DECODER_STATE_DECODING &&
|
|
|
|
mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING &&
|
2009-03-31 17:52:56 -07:00
|
|
|
!mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
|
|
|
|
!mDecoder->mReader->Stream()->IsSuspendedByCache()) {
|
2009-02-05 00:02:21 -08:00
|
|
|
// There is at most one frame in the queue and there's
|
|
|
|
// more data to load. Let's buffer to make sure we can play a
|
|
|
|
// decent amount of video in the future.
|
|
|
|
if (mPlaying) {
|
|
|
|
StopPlayback();
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
|
2009-02-10 17:43:45 -08:00
|
|
|
// We need to tell the element that buffering has started.
|
|
|
|
// We can't just directly send an asynchronous runnable that
|
|
|
|
// eventually fires the "waiting" event. The problem is that
|
|
|
|
// there might be pending main-thread events, such as "data
|
|
|
|
// received" notifications, that mean we're not actually still
|
|
|
|
// buffering by the time this runnable executes. So instead
|
|
|
|
// we just trigger UpdateReadyStateForData; when it runs, it
|
|
|
|
// will check the current state and decide whether to tell
|
|
|
|
// the element we're buffering or not.
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, UpdateReadyStateForData);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
mBufferingStart = PR_IntervalNow();
|
2009-03-31 17:52:56 -07:00
|
|
|
PRPackedBool reliable;
|
|
|
|
double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
|
|
|
|
mBufferingEndOffset = mDecoder->mDecoderPosition +
|
2009-02-10 17:44:39 -08:00
|
|
|
BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
|
2009-02-05 00:02:21 -08:00
|
|
|
mState = DECODER_STATE_BUFFERING;
|
2009-02-10 17:44:39 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
|
2009-02-05 00:02:21 -08:00
|
|
|
} else {
|
2008-10-19 00:39:21 -07:00
|
|
|
PlayFrame();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECODER_STATE_SEEKING:
|
|
|
|
{
|
|
|
|
// During the seek, don't have a lock on the decoder state,
|
|
|
|
// otherwise long seek operations can block the main thread.
|
|
|
|
// The events dispatched to the main thread are SYNC calls.
|
|
|
|
// These calls are made outside of the decode monitor lock so
|
|
|
|
// it is safe for the main thread to makes calls that acquire
|
|
|
|
// the lock since it won't deadlock. We check the state when
|
|
|
|
// acquiring the lock again in case shutdown has occurred
|
|
|
|
// during the time when we didn't have the lock.
|
|
|
|
float seekTime = mSeekTime;
|
2009-01-15 12:26:51 -08:00
|
|
|
mDecoder->StopProgressUpdates();
|
2008-10-19 00:39:21 -07:00
|
|
|
mon.Exit();
|
|
|
|
nsCOMPtr<nsIRunnable> startEvent =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
|
|
|
|
NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Entering oggplay_seek(%f)", seekTime));
|
2008-10-19 00:39:21 -07:00
|
|
|
oggplay_seek(mPlayer, ogg_int64_t(seekTime * 1000));
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Leaving oggplay_seek"));
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2008-12-10 06:30:41 -08:00
|
|
|
// Reactivate all tracks. Liboggplay deactivates tracks when it
|
|
|
|
// reads to the end of stream, but they must be reactivated in order
|
|
|
|
// to start reading from them again.
|
|
|
|
for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
|
|
|
|
if (oggplay_set_track_active(mPlayer, i) < 0) {
|
|
|
|
LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
mon.Enter();
|
2009-01-15 12:26:51 -08:00
|
|
|
mDecoder->StartProgressUpdates();
|
2009-03-31 17:52:56 -07:00
|
|
|
mLastFramePosition = mDecoder->mPlaybackPosition;
|
2008-10-19 00:39:21 -07:00
|
|
|
if (mState == DECODER_STATE_SHUTDOWN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Remove all frames decoded prior to seek from the queue
|
|
|
|
while (!mDecodedFrames.IsEmpty()) {
|
|
|
|
delete mDecodedFrames.Pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
OggPlayErrorCode r;
|
|
|
|
do {
|
|
|
|
mon.Exit();
|
|
|
|
r = DecodeFrame();
|
|
|
|
mon.Enter();
|
|
|
|
} while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
|
|
|
|
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
mLastFrameTime = 0;
|
|
|
|
FrameData* frame = NextFrame();
|
2008-12-10 06:30:41 -08:00
|
|
|
NS_ASSERTION(frame != nsnull, "No frame after seek!");
|
2008-10-19 00:39:21 -07:00
|
|
|
if (frame) {
|
|
|
|
mDecodedFrames.Push(frame);
|
2008-10-23 01:02:18 -07:00
|
|
|
UpdatePlaybackPosition(frame->mDecodedFrameTime);
|
2008-10-19 00:39:21 -07:00
|
|
|
PlayVideo(frame);
|
|
|
|
}
|
|
|
|
mon.Exit();
|
|
|
|
nsCOMPtr<nsIRunnable> stopEvent =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
|
|
|
|
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
|
|
|
|
mon.Enter();
|
|
|
|
|
|
|
|
if (mState == DECODER_STATE_SEEKING && mSeekTime == seekTime) {
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from SEEKING (to %f) to DECODING", seekTime));
|
2008-10-19 00:39:21 -07:00
|
|
|
mState = DECODER_STATE_DECODING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECODER_STATE_BUFFERING:
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
|
|
|
PRIntervalTime now = PR_IntervalNow();
|
|
|
|
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < BUFFERING_WAIT*1000) &&
|
2009-03-31 17:52:56 -07:00
|
|
|
mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
|
|
|
|
!mDecoder->mReader->Stream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
|
|
|
|
!mDecoder->mReader->Stream()->IsSuspendedByCache()) {
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG,
|
|
|
|
("In buffering: buffering data until %d bytes available or %d milliseconds",
|
2009-03-31 17:52:56 -07:00
|
|
|
PRUint32(mBufferingEndOffset - mDecoder->mReader->Stream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
|
2009-02-05 00:02:21 -08:00
|
|
|
BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(now - mBufferingStart))));
|
|
|
|
mon.Wait(PR_MillisecondsToInterval(1000));
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN)
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
LOG(PR_LOG_DEBUG, ("Changed state from BUFFERING to DECODING"));
|
|
|
|
mState = DECODER_STATE_DECODING;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
if (mState != DECODER_STATE_BUFFERING) {
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
2009-02-10 17:43:45 -08:00
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, UpdateReadyStateForData);
|
2009-02-05 00:02:21 -08:00
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
|
|
if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
|
|
|
|
if (!mPlaying) {
|
|
|
|
StartPlayback();
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
break;
|
|
|
|
}
|
2009-02-05 02:51:24 -08:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
case DECODER_STATE_COMPLETED:
|
|
|
|
{
|
2008-12-08 16:49:24 -08:00
|
|
|
while (mState == DECODER_STATE_COMPLETED &&
|
2008-10-19 00:39:21 -07:00
|
|
|
!mDecodedFrames.IsEmpty()) {
|
|
|
|
PlayFrame();
|
2008-10-23 01:02:18 -07:00
|
|
|
if (mState != DECODER_STATE_SHUTDOWN) {
|
|
|
|
// Wait for the time of one frame so we don't tight loop
|
|
|
|
// and we need to release the monitor so timeupdate and
|
|
|
|
// invalidate's on the main thread can occur.
|
|
|
|
mon.Wait(PR_MillisecondsToInterval(PRInt64(mCallbackPeriod*1000)));
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-12-08 16:49:24 -08:00
|
|
|
if (mState != DECODER_STATE_COMPLETED)
|
2008-10-19 00:39:21 -07:00
|
|
|
continue;
|
|
|
|
|
2008-12-15 15:06:22 -08:00
|
|
|
if (mAudioStream) {
|
|
|
|
mon.Exit();
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("Begin nsAudioStream::Drain"));
|
2008-12-15 15:06:22 -08:00
|
|
|
mAudioStream->Drain();
|
2009-02-05 00:02:21 -08:00
|
|
|
LOG(PR_LOG_DEBUG, ("End nsAudioStream::Drain"));
|
2008-12-15 15:06:22 -08:00
|
|
|
mon.Enter();
|
|
|
|
if (mState != DECODER_STATE_COMPLETED)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
|
|
do {
|
|
|
|
mon.Wait();
|
2008-12-13 19:15:18 -08:00
|
|
|
} while (mState == DECODER_STATE_COMPLETED);
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
return NS_OK;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2009-02-19 18:49:00 -08:00
|
|
|
void nsOggDecodeStateMachine::LoadOggHeaders(nsChannelReader* aReader)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
|
|
|
LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
|
2009-02-19 18:49:00 -08:00
|
|
|
mPlayer = oggplay_open_with_reader(aReader);
|
2008-07-29 23:50:14 -07:00
|
|
|
if (mPlayer) {
|
|
|
|
LOG(PR_LOG_DEBUG, ("There are %d tracks", oggplay_get_num_tracks(mPlayer)));
|
|
|
|
|
|
|
|
for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
|
|
|
|
LOG(PR_LOG_DEBUG, ("Tracks %d: %s", i, oggplay_get_track_typename(mPlayer, i)));
|
|
|
|
if (mVideoTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_THEORA) {
|
|
|
|
oggplay_set_callback_num_frames(mPlayer, i, 1);
|
|
|
|
mVideoTrack = i;
|
2009-01-28 01:33:37 -08:00
|
|
|
|
2008-07-29 23:50:14 -07:00
|
|
|
int fpsd, fpsn;
|
|
|
|
oggplay_get_video_fps(mPlayer, i, &fpsd, &fpsn);
|
2008-10-19 00:39:21 -07:00
|
|
|
mFramerate = fpsd == 0 ? 0.0 : float(fpsn)/float(fpsd);
|
|
|
|
mCallbackPeriod = 1.0 / mFramerate;
|
2008-07-29 23:50:14 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Frame rate: %f", mFramerate));
|
2009-01-28 01:33:37 -08:00
|
|
|
|
|
|
|
int y_width;
|
|
|
|
int y_height;
|
|
|
|
oggplay_get_video_y_size(mPlayer, i, &y_width, &y_height);
|
2009-03-16 20:45:00 -07:00
|
|
|
mDecoder->SetRGBData(y_width, y_height, mFramerate, nsnull);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
else if (mAudioTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_VORBIS) {
|
|
|
|
mAudioTrack = i;
|
|
|
|
oggplay_set_offset(mPlayer, i, OGGPLAY_AUDIO_OFFSET);
|
|
|
|
oggplay_get_audio_samplerate(mPlayer, i, &mAudioRate);
|
|
|
|
oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
|
|
|
|
LOG(PR_LOG_DEBUG, ("samplerate: %d, channels: %d", mAudioRate, mAudioChannels));
|
|
|
|
}
|
2008-11-09 17:38:02 -08:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
if (oggplay_set_track_active(mPlayer, i) < 0) {
|
2008-07-29 23:50:14 -07:00
|
|
|
LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-11-09 17:38:02 -08:00
|
|
|
|
2008-07-29 23:50:14 -07:00
|
|
|
if (mVideoTrack == -1) {
|
|
|
|
oggplay_set_callback_num_frames(mPlayer, mAudioTrack, OGGPLAY_FRAMES_PER_CALLBACK);
|
2008-10-19 00:39:21 -07:00
|
|
|
mCallbackPeriod = 1.0 / (float(mAudioRate) / OGGPLAY_FRAMES_PER_CALLBACK);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Callback Period: %f", mCallbackPeriod));
|
2008-07-29 23:50:14 -07:00
|
|
|
|
|
|
|
oggplay_use_buffer(mPlayer, OGGPLAY_BUFFER_SIZE);
|
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
// Get the duration from the Ogg file. We only do this if the
|
|
|
|
// content length of the resource is known as we need to seek
|
|
|
|
// to the end of the file to get the last time field. We also
|
2009-02-07 02:10:34 -08:00
|
|
|
// only do this if the resource is seekable and if we haven't
|
|
|
|
// already obtained the duration via an HTTP header.
|
2008-11-09 17:38:02 -08:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mDecoder->GetMonitor());
|
|
|
|
if (mState != DECODER_STATE_SHUTDOWN &&
|
2009-03-31 17:52:56 -07:00
|
|
|
aReader->Stream()->GetLength() >= 0 &&
|
2009-02-07 02:10:34 -08:00
|
|
|
mSeekable &&
|
|
|
|
mDuration == -1) {
|
2009-01-15 12:26:51 -08:00
|
|
|
mDecoder->StopProgressUpdates();
|
2008-11-09 17:38:02 -08:00
|
|
|
// Don't hold the monitor during the duration
|
|
|
|
// call as it can issue seek requests
|
|
|
|
// and blocks until these are completed.
|
|
|
|
mon.Exit();
|
|
|
|
PRInt64 d = oggplay_get_duration(mPlayer);
|
2009-03-03 02:23:54 -08:00
|
|
|
oggplay_seek(mPlayer, 0);
|
2008-11-09 17:38:02 -08:00
|
|
|
mon.Enter();
|
|
|
|
mDuration = d;
|
2009-01-15 12:26:51 -08:00
|
|
|
mDecoder->StartProgressUpdates();
|
2009-03-31 17:52:56 -07:00
|
|
|
mDecoder->UpdatePlaybackRate();
|
2008-11-09 17:38:02 -08:00
|
|
|
}
|
|
|
|
if (mState == DECODER_STATE_SHUTDOWN)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-07-29 23:50:14 -07:00
|
|
|
// Inform the element that we've loaded the Ogg metadata
|
|
|
|
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded);
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder, nsIObserver)
|
|
|
|
|
|
|
|
void nsOggDecoder::Pause()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
if (mPlayState == PLAY_STATE_SEEKING) {
|
|
|
|
mNextState = PLAY_STATE_PAUSED;
|
|
|
|
return;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
ChangeState(PLAY_STATE_PAUSED);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecoder::SetVolume(float volume)
|
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
mInitialVolume = volume;
|
|
|
|
|
|
|
|
if (mDecodeStateMachine) {
|
|
|
|
mDecodeStateMachine->SetVolume(volume);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
float nsOggDecoder::GetDuration()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-11-09 17:38:02 -08:00
|
|
|
if (mDuration >= 0) {
|
|
|
|
return static_cast<float>(mDuration) / 1000.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::numeric_limits<float>::quiet_NaN();
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsOggDecoder::nsOggDecoder() :
|
|
|
|
nsMediaDecoder(),
|
2009-02-05 00:02:21 -08:00
|
|
|
mDecoderPosition(0),
|
|
|
|
mPlaybackPosition(0),
|
2008-10-23 01:02:18 -07:00
|
|
|
mCurrentTime(0.0),
|
2008-10-19 00:39:21 -07:00
|
|
|
mInitialVolume(0.0),
|
|
|
|
mRequestedSeekTime(-1.0),
|
2009-02-22 16:51:06 -08:00
|
|
|
mDuration(-1),
|
2008-10-19 00:39:21 -07:00
|
|
|
mNotifyOnShutdown(PR_FALSE),
|
2008-11-09 17:38:02 -08:00
|
|
|
mSeekable(PR_TRUE),
|
2009-03-31 17:52:56 -07:00
|
|
|
mReader(nsnull),
|
|
|
|
mMonitor(nsnull),
|
2008-10-19 00:39:21 -07:00
|
|
|
mPlayState(PLAY_STATE_PAUSED),
|
2009-01-15 12:26:51 -08:00
|
|
|
mNextState(PLAY_STATE_PAUSED),
|
|
|
|
mResourceLoaded(PR_FALSE),
|
|
|
|
mIgnoreProgressData(PR_FALSE)
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
MOZ_COUNT_CTOR(nsOggDecoder);
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2009-01-15 23:57:37 -08:00
|
|
|
PRBool nsOggDecoder::Init(nsHTMLMediaElement* aElement)
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
mMonitor = nsAutoMonitor::NewMonitor("media.decoder");
|
2009-01-15 23:57:37 -08:00
|
|
|
return mMonitor && nsMediaDecoder::Init(aElement);
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
void nsOggDecoder::Shutdown()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
mShuttingDown = PR_TRUE;
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
ChangeState(PLAY_STATE_SHUTDOWN);
|
|
|
|
nsMediaDecoder::Shutdown();
|
|
|
|
|
2008-12-12 00:41:49 -08:00
|
|
|
Stop();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsOggDecoder::~nsOggDecoder()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
MOZ_COUNT_DTOR(nsOggDecoder);
|
|
|
|
nsAutoMonitor::DestroyMonitor(mMonitor);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-29 22:20:08 -07:00
|
|
|
nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
|
|
|
|
nsIStreamListener** aStreamListener)
|
2008-09-06 16:47:28 -07:00
|
|
|
{
|
2008-11-18 00:19:32 -08:00
|
|
|
// Reset Stop guard flag flag, else shutdown won't occur properly when
|
|
|
|
// reusing decoder.
|
2008-12-08 16:43:56 -08:00
|
|
|
mStopping = PR_FALSE;
|
2008-11-18 00:19:32 -08:00
|
|
|
|
2009-01-15 12:26:51 -08:00
|
|
|
// Reset progress member variables
|
2009-02-05 00:02:21 -08:00
|
|
|
mDecoderPosition = 0;
|
|
|
|
mPlaybackPosition = 0;
|
2009-01-15 12:26:51 -08:00
|
|
|
mResourceLoaded = PR_FALSE;
|
|
|
|
|
2008-12-12 00:41:49 -08:00
|
|
|
NS_ASSERTION(!mReader, "Didn't shutdown properly!");
|
|
|
|
NS_ASSERTION(!mDecodeStateMachine, "Didn't shutdown properly!");
|
|
|
|
NS_ASSERTION(!mDecodeThread, "Didn't shutdown properly!");
|
|
|
|
|
2008-10-29 22:20:08 -07:00
|
|
|
if (aStreamListener) {
|
|
|
|
*aStreamListener = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aURI) {
|
|
|
|
NS_ASSERTION(!aStreamListener, "No listener should be requested here");
|
|
|
|
mURI = aURI;
|
|
|
|
} else {
|
|
|
|
NS_ASSERTION(aChannel, "Either a URI or a channel is required");
|
|
|
|
NS_ASSERTION(aStreamListener, "A listener should be requested here");
|
|
|
|
|
|
|
|
// If the channel was redirected, we want the post-redirect URI;
|
|
|
|
// but if the URI scheme was expanded, say from chrome: to jar:file:,
|
|
|
|
// we want the original URI.
|
|
|
|
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI));
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
}
|
2008-09-06 16:47:28 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
RegisterShutdownObserver();
|
|
|
|
|
|
|
|
mReader = new nsChannelReader();
|
|
|
|
NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
|
2008-10-19 00:39:21 -07:00
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
|
2008-11-18 00:19:32 -08:00
|
|
|
mDecodeStateMachine = new nsOggDecodeStateMachine(this);
|
2008-11-09 17:38:02 -08:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
mDecodeStateMachine->SetSeekable(mSeekable);
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
ChangeState(PLAY_STATE_LOADING);
|
|
|
|
|
|
|
|
return mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
|
2008-09-06 16:47:28 -07:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsresult nsOggDecoder::Play()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
if (mPlayState == PLAY_STATE_SEEKING) {
|
|
|
|
mNextState = PLAY_STATE_PLAYING;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2009-03-31 18:07:10 -07:00
|
|
|
if (mPlayState == PLAY_STATE_ENDED)
|
|
|
|
return Seek(0);
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
ChangeState(PLAY_STATE_PLAYING);
|
|
|
|
|
|
|
|
return NS_OK;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
nsresult nsOggDecoder::Seek(float aTime)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-10-19 00:39:21 -07:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
|
|
|
|
if (aTime < 0.0)
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
|
|
|
mRequestedSeekTime = aTime;
|
|
|
|
|
|
|
|
// If we are already in the seeking state, then setting mRequestedSeekTime
|
|
|
|
// above will result in the new seek occurring when the current seek
|
|
|
|
// completes.
|
|
|
|
if (mPlayState != PLAY_STATE_SEEKING) {
|
2009-03-31 18:07:10 -07:00
|
|
|
if (mPlayState == PLAY_STATE_ENDED) {
|
|
|
|
mNextState = PLAY_STATE_PLAYING;
|
|
|
|
} else {
|
|
|
|
mNextState = mPlayState;
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
ChangeState(PLAY_STATE_SEEKING);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult nsOggDecoder::PlaybackRateChanged()
|
|
|
|
{
|
|
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-12-12 00:41:49 -08:00
|
|
|
// Postpones destruction of nsOggDecoder's objects, so they can be safely
|
|
|
|
// performed later, when events can't interfere.
|
|
|
|
class nsDestroyStateMachine : public nsRunnable {
|
|
|
|
public:
|
|
|
|
nsDestroyStateMachine(nsOggDecoder *aDecoder,
|
|
|
|
nsOggDecodeStateMachine *aMachine,
|
|
|
|
nsChannelReader *aReader,
|
|
|
|
nsIThread *aThread)
|
|
|
|
: mDecoder(aDecoder),
|
|
|
|
mDecodeStateMachine(aMachine),
|
|
|
|
mReader(aReader),
|
|
|
|
mDecodeThread(aThread)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHOD Run() {
|
|
|
|
NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
|
|
|
|
// The decode thread must die before the state machine can die.
|
|
|
|
// The state machine must die before the reader.
|
|
|
|
// The state machine must die before the decoder.
|
|
|
|
if (mDecodeThread)
|
|
|
|
mDecodeThread->Shutdown();
|
|
|
|
mDecodeThread = nsnull;
|
|
|
|
mDecodeStateMachine = nsnull;
|
|
|
|
mReader = nsnull;
|
|
|
|
mDecoder = nsnull;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
nsRefPtr<nsOggDecoder> mDecoder;
|
|
|
|
nsCOMPtr<nsOggDecodeStateMachine> mDecodeStateMachine;
|
|
|
|
nsAutoPtr<nsChannelReader> mReader;
|
|
|
|
nsCOMPtr<nsIThread> mDecodeThread;
|
|
|
|
};
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecoder::Stop()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-11-18 00:19:32 -08:00
|
|
|
NS_ASSERTION(NS_IsMainThread(),
|
|
|
|
"nsOggDecoder::Stop called on non-main thread");
|
|
|
|
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mStopping)
|
2008-11-18 00:19:32 -08:00
|
|
|
return;
|
2008-12-08 16:43:56 -08:00
|
|
|
|
|
|
|
mStopping = PR_TRUE;
|
2008-11-18 00:19:32 -08:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
ChangeState(PLAY_STATE_ENDED);
|
|
|
|
|
|
|
|
StopProgress();
|
|
|
|
|
|
|
|
// Force any outstanding seek and byterange requests to complete
|
|
|
|
// to prevent shutdown from deadlocking.
|
2009-03-31 17:52:56 -07:00
|
|
|
mReader->Stream()->Close();
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
// Shutdown must be on called the mDecodeStateMachine before deleting.
|
|
|
|
// This is required to ensure that the state machine isn't running
|
|
|
|
// in the thread and using internal objects when it is deleted.
|
|
|
|
if (mDecodeStateMachine) {
|
|
|
|
mDecodeStateMachine->Shutdown();
|
|
|
|
}
|
|
|
|
|
2008-12-12 00:41:49 -08:00
|
|
|
// mDecodeThread holds a ref to mDecodeStateMachine, so we can't destroy
|
|
|
|
// mDecodeStateMachine until mDecodeThread is destroyed. We can't destroy
|
|
|
|
// mReader until mDecodeStateMachine is destroyed because mDecodeStateMachine
|
|
|
|
// uses mReader in its destructor. In addition, it's unsafe to Shutdown() the
|
|
|
|
// decode thread here, as nsIThread::Shutdown() may run events, such as JS
|
|
|
|
// event handlers, which could kick off a new Load().
|
|
|
|
// mDecodeStateMachine::Run() may also be holding a reference to the decoder
|
|
|
|
// in an event runner object on its stack, so the decoder must outlive the
|
|
|
|
// state machine, else we may destroy the decoder on a non-main thread,
|
|
|
|
// and its monitor doesn't like that. So we need to create a new event which
|
|
|
|
// holds references the decoder, reader, thread, and state machine, and
|
|
|
|
// releases them safely later on the main thread when events can't interfere.
|
|
|
|
// See bug 468721.
|
|
|
|
nsCOMPtr<nsIRunnable> event = new nsDestroyStateMachine(this,
|
|
|
|
mDecodeStateMachine,
|
|
|
|
mReader.forget(),
|
|
|
|
mDecodeThread);
|
|
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-12-12 00:41:49 -08:00
|
|
|
// Null data fields. They can be reinitialized in future Load()s safely now.
|
|
|
|
mDecodeThread = nsnull;
|
2008-10-19 00:39:21 -07:00
|
|
|
mDecodeStateMachine = nsnull;
|
|
|
|
UnregisterShutdownObserver();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
float nsOggDecoder::GetCurrentTime()
|
|
|
|
{
|
2008-10-23 01:02:18 -07:00
|
|
|
return mCurrentTime;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
void nsOggDecoder::GetCurrentURI(nsIURI** aURI)
|
|
|
|
{
|
|
|
|
NS_IF_ADDREF(*aURI = mURI);
|
|
|
|
}
|
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
already_AddRefed<nsIPrincipal> nsOggDecoder::GetCurrentPrincipal()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2009-03-31 17:52:56 -07:00
|
|
|
if (!mReader)
|
2008-10-19 00:39:21 -07:00
|
|
|
return nsnull;
|
2009-03-31 17:52:56 -07:00
|
|
|
return mReader->Stream()->GetCurrentPrincipal();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::MetadataLoaded()
|
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mShuttingDown)
|
|
|
|
return;
|
|
|
|
|
2008-12-10 06:23:23 -08:00
|
|
|
// Only inform the element of MetadataLoaded if not doing a load() in order
|
|
|
|
// to fulfill a seek, otherwise we'll get multiple metadataloaded events.
|
|
|
|
PRBool notifyElement = PR_TRUE;
|
2008-11-09 17:38:02 -08:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
mDuration = mDecodeStateMachine ? mDecodeStateMachine->GetDuration() : -1;
|
2008-12-10 06:23:23 -08:00
|
|
|
notifyElement = mNextState != PLAY_STATE_SEEKING;
|
2008-11-09 17:38:02 -08:00
|
|
|
}
|
|
|
|
|
2008-12-10 06:23:23 -08:00
|
|
|
if (mElement && notifyElement) {
|
2009-01-28 01:33:37 -08:00
|
|
|
// Make sure the element and the frame (if any) are told about
|
|
|
|
// our new size.
|
|
|
|
Invalidate();
|
2008-07-29 23:50:14 -07:00
|
|
|
mElement->MetadataLoaded();
|
|
|
|
}
|
2009-01-15 12:26:51 -08:00
|
|
|
|
|
|
|
if (!mResourceLoaded) {
|
|
|
|
StartProgress();
|
|
|
|
}
|
|
|
|
else if (mElement)
|
|
|
|
{
|
|
|
|
// Resource was loaded during metadata loading, when progress
|
|
|
|
// events are being ignored. Fire the final progress event.
|
|
|
|
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::FirstFrameLoaded()
|
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mShuttingDown)
|
|
|
|
return;
|
2008-12-10 06:23:23 -08:00
|
|
|
|
|
|
|
// Only inform the element of FirstFrameLoaded if not doing a load() in order
|
|
|
|
// to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
|
|
|
|
PRBool notifyElement = PR_TRUE;
|
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
notifyElement = mNextState != PLAY_STATE_SEEKING;
|
|
|
|
}
|
2008-12-08 16:43:56 -08:00
|
|
|
|
2008-12-10 06:23:23 -08:00
|
|
|
if (mElement && notifyElement) {
|
2008-07-29 23:50:14 -07:00
|
|
|
mElement->FirstFrameLoaded();
|
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
// The element can run javascript via events
|
|
|
|
// before reaching here, so only change the
|
|
|
|
// state if we're still set to the original
|
|
|
|
// loading state.
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
if (mPlayState == PLAY_STATE_LOADING) {
|
|
|
|
if (mRequestedSeekTime >= 0.0) {
|
|
|
|
ChangeState(PLAY_STATE_SEEKING);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ChangeState(mNextState);
|
|
|
|
}
|
|
|
|
}
|
2009-01-15 12:26:51 -08:00
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
if (!mResourceLoaded && mReader &&
|
|
|
|
mReader->Stream()->IsDataCachedToEndOfStream(mDecoderPosition)) {
|
2009-01-15 12:26:51 -08:00
|
|
|
ResourceLoaded();
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::ResourceLoaded()
|
|
|
|
{
|
2009-01-15 12:26:51 -08:00
|
|
|
// Don't handle ResourceLoaded if we are shutting down, or if
|
|
|
|
// we need to ignore progress data due to seeking (in the case
|
|
|
|
// that the seek results in reaching end of file, we get a bogus call
|
|
|
|
// to ResourceLoaded).
|
2009-01-06 19:33:42 -08:00
|
|
|
if (mShuttingDown)
|
2008-12-08 16:43:56 -08:00
|
|
|
return;
|
|
|
|
|
2009-01-15 12:26:51 -08:00
|
|
|
{
|
|
|
|
// If we are seeking or loading then the resource loaded notification we get
|
|
|
|
// should be ignored, since it represents the end of the seek request.
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
2009-02-05 00:02:21 -08:00
|
|
|
if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
|
2009-01-15 12:26:51 -08:00
|
|
|
return;
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
Progress(PR_FALSE);
|
2009-01-15 12:26:51 -08:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
mResourceLoaded = PR_TRUE;
|
|
|
|
StopProgress();
|
|
|
|
}
|
2009-02-05 02:51:24 -08:00
|
|
|
|
2009-01-15 12:26:51 -08:00
|
|
|
// Ensure the final progress event gets fired
|
2008-07-29 23:50:14 -07:00
|
|
|
if (mElement) {
|
2009-01-15 12:26:51 -08:00
|
|
|
mElement->DispatchAsyncProgressEvent(NS_LITERAL_STRING("progress"));
|
2008-07-29 23:50:14 -07:00
|
|
|
mElement->ResourceLoaded();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-06 12:53:20 -08:00
|
|
|
void nsOggDecoder::NetworkError()
|
|
|
|
{
|
2009-02-05 00:02:21 -08:00
|
|
|
if (mStopping || mShuttingDown)
|
2008-12-08 16:43:56 -08:00
|
|
|
return;
|
|
|
|
|
2008-11-06 12:53:20 -08:00
|
|
|
if (mElement)
|
|
|
|
mElement->NetworkError();
|
|
|
|
Stop();
|
|
|
|
}
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
PRBool nsOggDecoder::IsSeeking() const
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-12-14 10:02:54 -08:00
|
|
|
return mPlayState == PLAY_STATE_SEEKING || mNextState == PLAY_STATE_SEEKING;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool nsOggDecoder::IsEnded() const
|
|
|
|
{
|
|
|
|
return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN;
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::PlaybackEnded()
|
|
|
|
{
|
2008-12-13 19:15:18 -08:00
|
|
|
if (mShuttingDown || mPlayState == nsOggDecoder::PLAY_STATE_SEEKING)
|
2008-12-08 16:43:56 -08:00
|
|
|
return;
|
|
|
|
|
2009-03-31 18:07:10 -07:00
|
|
|
ChangeState(PLAY_STATE_ENDED);
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
if (mElement) {
|
|
|
|
mElement->PlaybackEnded();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
|
|
|
|
const char *aTopic,
|
|
|
|
const PRUnichar *someData)
|
|
|
|
{
|
|
|
|
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
|
|
|
Shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
nsMediaDecoder::Statistics
|
|
|
|
nsOggDecoder::GetStatistics()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2009-02-05 00:02:21 -08:00
|
|
|
Statistics result;
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
2009-03-31 17:52:56 -07:00
|
|
|
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;
|
2009-03-31 20:19:00 -07:00
|
|
|
} else {
|
2009-03-31 17:52:56 -07:00
|
|
|
result.mDownloadRate = 0;
|
|
|
|
result.mDownloadRateReliable = PR_TRUE;
|
|
|
|
result.mPlaybackRate = 0;
|
|
|
|
result.mPlaybackRateReliable = PR_TRUE;
|
|
|
|
result.mDecoderPosition = 0;
|
|
|
|
result.mPlaybackPosition = 0;
|
|
|
|
result.mDownloadPosition = 0;
|
|
|
|
result.mTotalBytes = 0;
|
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
return result;
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
double nsOggDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
2009-03-31 17:52:56 -07:00
|
|
|
PRInt64 length = mReader ? mReader->Stream()->GetLength() : -1;
|
|
|
|
if (mDuration >= 0 && length >= 0) {
|
|
|
|
*aReliable = PR_TRUE;
|
|
|
|
return double(length)*1000.0/mDuration;
|
|
|
|
}
|
|
|
|
return mPlaybackStatistics.GetRateAtLastStop(aReliable);
|
|
|
|
}
|
2009-02-05 00:02:21 -08:00
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
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);
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
2009-03-31 17:52:56 -07:00
|
|
|
mReader->Stream()->SetPlaybackRate(rate);
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
void nsOggDecoder::NotifySuspendedStatusChanged()
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
|
|
|
NS_ASSERTION(NS_IsMainThread(),
|
2009-03-31 17:52:56 -07:00
|
|
|
"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();
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2009-03-31 17:52:56 -07:00
|
|
|
void nsOggDecoder::NotifyBytesDownloaded()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2009-03-31 17:52:56 -07:00
|
|
|
NS_ASSERTION(NS_IsMainThread(),
|
|
|
|
"nsOggDecoder::NotifyBytesDownloaded called on non-main thread");
|
|
|
|
UpdateReadyStateForData();
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
void nsOggDecoder::NotifyDownloadEnded(nsresult aStatus)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2009-02-05 00:02:21 -08:00
|
|
|
if (aStatus == NS_BINDING_ABORTED)
|
2008-12-08 16:43:56 -08:00
|
|
|
return;
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
2009-03-31 17:52:56 -07:00
|
|
|
UpdatePlaybackRate();
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
2009-02-05 00:02:21 -08:00
|
|
|
|
|
|
|
if (NS_SUCCEEDED(aStatus)) {
|
|
|
|
ResourceLoaded();
|
|
|
|
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
|
|
|
|
NetworkError();
|
|
|
|
}
|
|
|
|
UpdateReadyStateForData();
|
2009-02-05 00:02:21 -08:00
|
|
|
}
|
|
|
|
|
2009-02-05 00:02:21 -08:00
|
|
|
void nsOggDecoder::NotifyBytesConsumed(PRInt64 aBytes)
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
2009-02-05 00:02:21 -08:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
if (!mIgnoreProgressData) {
|
|
|
|
mDecoderPosition += aBytes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::UpdateReadyStateForData()
|
|
|
|
{
|
|
|
|
if (!mElement || mShuttingDown || !mDecodeStateMachine)
|
2008-12-08 16:43:56 -08:00
|
|
|
return;
|
|
|
|
|
2009-02-10 17:43:45 -08:00
|
|
|
nsHTMLMediaElement::NextFrameStatus frameStatus;
|
2009-02-05 00:02:21 -08:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
2009-02-10 17:43:45 -08:00
|
|
|
if (mDecodeStateMachine->HaveNextFrameData()) {
|
|
|
|
frameStatus = nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
|
|
|
|
} else if (mDecodeStateMachine->IsBuffering()) {
|
|
|
|
frameStatus = nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
|
|
|
|
} else {
|
|
|
|
frameStatus = nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
|
|
|
|
}
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2009-02-10 17:43:45 -08:00
|
|
|
mElement->UpdateReadyStateForData(frameStatus);
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
void nsOggDecoder::SeekingStopped()
|
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mShuttingDown)
|
|
|
|
return;
|
|
|
|
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
|
|
|
|
// An additional seek was requested while the current seek was
|
|
|
|
// in operation.
|
|
|
|
if (mRequestedSeekTime >= 0.0)
|
|
|
|
ChangeState(PLAY_STATE_SEEKING);
|
|
|
|
else
|
|
|
|
ChangeState(mNextState);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mElement) {
|
|
|
|
mElement->SeekCompleted();
|
2009-02-05 00:02:21 -08:00
|
|
|
UpdateReadyStateForData();
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::SeekingStarted()
|
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mShuttingDown)
|
|
|
|
return;
|
2008-10-19 00:39:21 -07:00
|
|
|
|
|
|
|
if (mElement) {
|
|
|
|
mElement->SeekStarted();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::RegisterShutdownObserver()
|
|
|
|
{
|
|
|
|
if (!mNotifyOnShutdown) {
|
|
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
|
|
do_GetService("@mozilla.org/observer-service;1");
|
|
|
|
if (observerService) {
|
|
|
|
mNotifyOnShutdown =
|
|
|
|
NS_SUCCEEDED(observerService->AddObserver(this,
|
|
|
|
NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
|
|
|
PR_FALSE));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
NS_WARNING("Could not get an observer service. Video decoding events may not shutdown cleanly.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::UnregisterShutdownObserver()
|
|
|
|
{
|
|
|
|
if (mNotifyOnShutdown) {
|
|
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
|
|
do_GetService("@mozilla.org/observer-service;1");
|
|
|
|
if (observerService) {
|
|
|
|
mNotifyOnShutdown = PR_FALSE;
|
|
|
|
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::ChangeState(PlayState aState)
|
|
|
|
{
|
2008-11-18 00:19:32 -08:00
|
|
|
NS_ASSERTION(NS_IsMainThread(),
|
|
|
|
"nsOggDecoder::ChangeState called on non-main thread");
|
2008-10-19 00:39:21 -07:00
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
|
|
|
|
if (mNextState == aState) {
|
|
|
|
mNextState = PLAY_STATE_PAUSED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPlayState == PLAY_STATE_SHUTDOWN) {
|
|
|
|
mon.NotifyAll();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPlayState = aState;
|
|
|
|
switch (aState) {
|
|
|
|
case PLAY_STATE_PAUSED:
|
|
|
|
/* No action needed */
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_PLAYING:
|
|
|
|
mDecodeStateMachine->Decode();
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_SEEKING:
|
|
|
|
mDecodeStateMachine->Seek(mRequestedSeekTime);
|
|
|
|
mRequestedSeekTime = -1.0;
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_LOADING:
|
|
|
|
/* No action needed */
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_START:
|
|
|
|
/* No action needed */
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_ENDED:
|
|
|
|
/* No action needed */
|
|
|
|
break;
|
|
|
|
case PLAY_STATE_SHUTDOWN:
|
|
|
|
/* No action needed */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mon.NotifyAll();
|
|
|
|
}
|
2008-10-23 01:02:18 -07:00
|
|
|
|
|
|
|
void nsOggDecoder::PlaybackPositionChanged()
|
|
|
|
{
|
2008-12-08 16:43:56 -08:00
|
|
|
if (mShuttingDown)
|
|
|
|
return;
|
|
|
|
|
2008-10-23 01:02:18 -07:00
|
|
|
float lastTime = mCurrentTime;
|
|
|
|
|
|
|
|
// Control the scope of the monitor so it is not
|
|
|
|
// held while the timeupdate and the invalidate is run.
|
|
|
|
{
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
|
|
|
|
if (mDecodeStateMachine) {
|
|
|
|
mCurrentTime = mDecodeStateMachine->GetCurrentTime();
|
|
|
|
mDecodeStateMachine->ClearPositionChangeFlag();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalidate the frame so any video data is displayed.
|
|
|
|
// Do this before the timeupdate event so that if that
|
|
|
|
// event runs JavaScript that queries the media size, the
|
|
|
|
// frame has reflowed and the size updated beforehand.
|
|
|
|
Invalidate();
|
|
|
|
|
|
|
|
if (mElement && lastTime != mCurrentTime) {
|
|
|
|
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
|
|
|
}
|
|
|
|
}
|
2008-11-09 17:38:02 -08:00
|
|
|
|
2009-02-07 02:10:34 -08:00
|
|
|
void nsOggDecoder::SetDuration(PRInt64 aDuration)
|
|
|
|
{
|
|
|
|
mDuration = aDuration;
|
|
|
|
if (mDecodeStateMachine) {
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
mDecodeStateMachine->SetDuration(mDuration);
|
2009-03-03 02:23:54 -08:00
|
|
|
|
|
|
|
if (mReader) {
|
|
|
|
mReader->SetDuration(mDuration);
|
|
|
|
}
|
2009-03-31 17:52:56 -07:00
|
|
|
UpdatePlaybackRate();
|
2009-02-07 02:10:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-09 17:38:02 -08:00
|
|
|
void nsOggDecoder::SetSeekable(PRBool aSeekable)
|
|
|
|
{
|
|
|
|
mSeekable = aSeekable;
|
|
|
|
if (mDecodeStateMachine) {
|
|
|
|
nsAutoMonitor mon(mMonitor);
|
|
|
|
mDecodeStateMachine->SetSeekable(aSeekable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool nsOggDecoder::GetSeekable()
|
|
|
|
{
|
|
|
|
return mSeekable;
|
|
|
|
}
|
2009-01-06 19:33:42 -08:00
|
|
|
|
2009-01-21 15:54:40 -08:00
|
|
|
void nsOggDecoder::Suspend()
|
|
|
|
{
|
|
|
|
if (mReader) {
|
2009-03-31 17:52:56 -07:00
|
|
|
mReader->Stream()->Suspend();
|
2009-01-21 15:54:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::Resume()
|
|
|
|
{
|
|
|
|
if (mReader) {
|
2009-03-31 17:52:56 -07:00
|
|
|
mReader->Stream()->Resume();
|
2009-01-21 15:54:40 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-15 12:26:51 -08:00
|
|
|
void nsOggDecoder::StopProgressUpdates()
|
|
|
|
{
|
|
|
|
mIgnoreProgressData = PR_TRUE;
|
2009-03-31 17:52:56 -07:00
|
|
|
if (mReader) {
|
|
|
|
mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_METADATA);
|
|
|
|
}
|
2009-01-15 12:26:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void nsOggDecoder::StartProgressUpdates()
|
|
|
|
{
|
|
|
|
mIgnoreProgressData = PR_FALSE;
|
2009-03-31 17:52:56 -07:00
|
|
|
if (mReader) {
|
|
|
|
mReader->Stream()->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
|
|
|
|
mDecoderPosition = mPlaybackPosition = mReader->Stream()->Tell();
|
|
|
|
}
|
2009-01-15 12:26:51 -08:00
|
|
|
}
|