mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
563 lines
21 KiB
C++
563 lines
21 KiB
C++
/* -*- 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 ***** */
|
|
/*
|
|
Each video element has one thread. This thread, called the Decode thread,
|
|
owns the resources for downloading and reading the video file. It goes through the
|
|
file, decoding the theora and vorbis data. It uses Oggplay to do the decoding.
|
|
It indirectly uses an nsMediaStream to do the file reading and seeking via Oggplay.
|
|
All file reads and seeks must occur on this thread only. It handles the sending
|
|
of the audio data to the sound device and the presentation of the video data
|
|
at the correct frame rate.
|
|
|
|
When the decode thread is created an event is dispatched to it. The event
|
|
runs for the lifetime of the playback of the resource. The decode thread
|
|
synchronises with the main thread via a single monitor held by the
|
|
nsOggDecoder object.
|
|
|
|
The event contains a Run method which consists of an infinite loop
|
|
that checks the state that the state machine is in and processes
|
|
operations on that state.
|
|
|
|
The nsOggDecodeStateMachine class is the event that gets dispatched to
|
|
the decode thread. It has the following states:
|
|
|
|
DECODING_METADATA
|
|
The Ogg headers are being loaded, and things like framerate, etc are
|
|
being decoded.
|
|
DECODING_FIRSTFRAME
|
|
The first frame of audio/video data is being decoded.
|
|
DECODING
|
|
Video/Audio frames are being decoded.
|
|
SEEKING
|
|
A seek operation is in progress.
|
|
BUFFERING
|
|
Decoding is paused while data is buffered for smooth playback.
|
|
COMPLETED
|
|
The resource has completed decoding.
|
|
SHUTDOWN
|
|
The decoder object is about to be destroyed.
|
|
|
|
The following result in state transitions.
|
|
|
|
Shutdown()
|
|
Clean up any resources the nsOggDecodeStateMachine owns.
|
|
Decode()
|
|
Start decoding video frames.
|
|
Buffer
|
|
This is not user initiated. It occurs when the
|
|
available data in the stream drops below a certain point.
|
|
Complete
|
|
This is not user initiated. It occurs when the
|
|
stream is completely decoded.
|
|
Seek(float)
|
|
Seek to the time position given in the resource.
|
|
|
|
A state transition diagram:
|
|
|
|
DECODING_METADATA
|
|
| | Shutdown()
|
|
v -->-------------------->--------------------------|
|
|
| |
|
|
DECODING_FIRSTFRAME v
|
|
| | Shutdown() |
|
|
v >-------------------->--------------------------|
|
|
| |------------->----->------------------------| v
|
|
DECODING | | | | |
|
|
^ v Seek(t) | | | |
|
|
| Decode() | v | | |
|
|
^-----------<----SEEKING | v Complete v v
|
|
| | | | | |
|
|
| | | COMPLETED SHUTDOWN-<-|
|
|
^ ^ | |Shutdown() |
|
|
| | | >-------->-----^
|
|
| Decode() |Seek(t) |Buffer() |
|
|
-----------<--------<-------BUFFERING |
|
|
| ^
|
|
v Shutdown() |
|
|
| |
|
|
------------>-----|
|
|
|
|
The Main thread controls the decode state machine by setting the value
|
|
of a mPlayState variable and notifying on the monitor
|
|
based on the high level player actions required (Seek, Pause, Play, etc).
|
|
|
|
The player states are the states requested by the client through the
|
|
DOM API. They represent the desired state of the player, while the
|
|
decoder's state represents the actual state of the decoder.
|
|
|
|
The high level state of the player is maintained via a PlayState value.
|
|
It can have the following states:
|
|
|
|
START
|
|
The decoder has been initialized but has no resource loaded.
|
|
PAUSED
|
|
A request via the API has been received to pause playback.
|
|
LOADING
|
|
A request via the API has been received to load a resource.
|
|
PLAYING
|
|
A request via the API has been received to start playback.
|
|
SEEKING
|
|
A request via the API has been received to start seeking.
|
|
COMPLETED
|
|
Playback has completed.
|
|
SHUTDOWN
|
|
The decoder is about to be destroyed.
|
|
|
|
State transition occurs when the Media Element calls the Play, Seek,
|
|
etc methods on the nsOggDecoder object. When the transition occurs
|
|
nsOggDecoder then calls the methods on the decoder state machine
|
|
object to cause it to behave appropriate to the play state.
|
|
|
|
The following represents the states that the player can be in, and the
|
|
valid states the decode thread can be in at that time:
|
|
|
|
player LOADING decoder DECODING_METADATA, DECODING_FIRSTFRAME
|
|
player PLAYING decoder DECODING, BUFFERING, SEEKING, COMPLETED
|
|
player PAUSED decoder DECODING, BUFFERING, SEEKING, COMPLETED
|
|
player SEEKING decoder SEEKING
|
|
player COMPLETED decoder SHUTDOWN
|
|
player SHUTDOWN decoder SHUTDOWN
|
|
|
|
The general sequence of events with these objects is:
|
|
|
|
1) The video element calls Load on nsMediaDecoder. This creates the
|
|
decode thread and starts the channel for downloading the file. It
|
|
instantiates and starts the Decode state machine. The high level
|
|
LOADING state is entered, which results in the decode state machine
|
|
to start decoding metadata. These are the headers that give the
|
|
video size, framerate, etc. It returns immediately to the calling
|
|
video element.
|
|
|
|
2) When the Ogg metadata has been loaded by the decode thread it will
|
|
call a method on the video element object to inform it that this
|
|
step is done, so it can do the things required by the video
|
|
specification at this stage. The decoder then continues to decode
|
|
the first frame of data.
|
|
|
|
3) When the first frame of Ogg data has been successfully decoded it
|
|
calls a method on the video element object to inform it that this
|
|
step has been done, once again so it can do the required things by
|
|
the video specification at this stage.
|
|
|
|
This results in the high level state changing to PLAYING or PAUSED
|
|
depending on any user action that may have occurred.
|
|
|
|
The decode thread, while in the DECODING state, plays audio and
|
|
video, if the correct frame time comes around and the decoder
|
|
play state is PLAYING.
|
|
|
|
a/v synchronisation is done by a combination of liboggplay and the
|
|
Decoder state machine. liboggplay ensures that a decoded frame of data
|
|
has both the audio samples and the YUV data for that period of time.
|
|
|
|
When a frame is decoded by the decode state machine it converts the
|
|
YUV encoded video to RGB and copies the sound data to an internal
|
|
FrameData object. This is stored in a queue of available decoded frames.
|
|
Included in the FrameData object is the time that that frame should
|
|
be displayed.
|
|
|
|
The display state machine keeps track of the time since the last frame it
|
|
played. After decoding a frame it checks if it is time to display the next
|
|
item in the decoded frame queue. If so, it pops the item off the queue
|
|
and displays it.
|
|
|
|
Ideally a/v sync would take into account the actual audio clock of the
|
|
audio hardware for the sync rather than using the system clock.
|
|
Unfortunately getting valid time data out of the audio hardware has proven
|
|
to be unreliable across platforms (and even distributions in Linux) depending
|
|
on audio hardware, audio backend etc. The current approach works fine in practice
|
|
and is a compromise until this issue can be sorted. The plan is to eventually
|
|
move to synchronising using the audio hardware.
|
|
|
|
To prevent audio skipping and framerate dropping it is very important to
|
|
make sure no blocking occurs during the decoding process and minimise
|
|
expensive time operations at the time a frame is to be displayed. This is
|
|
managed by immediately converting video data to RGB on decode (an expensive
|
|
operation to do at frame display time) and checking if the sound device will
|
|
not block before writing sound data to it.
|
|
|
|
Shutdown needs to ensure that the event posted to the decode
|
|
thread is completed. The decode thread can potentially block internally
|
|
inside liboggplay when reading, seeking, or its internal buffers containing
|
|
decoded data are full. When blocked in this manner a call from the main thread
|
|
to Shutdown() will hang.
|
|
|
|
This is fixed with a protocol to ensure that the decode event cleanly
|
|
completes. The nsMediaStream that the nsChannelReader uses has a
|
|
Cancel() method. Calling this before Shutdown() will close any
|
|
internal streams or listeners resulting in blocked i/o completing with
|
|
an error, and all future i/o on the stream having an error.
|
|
|
|
This causes the decode thread to exit and Shutdown() can occur.
|
|
|
|
If the decode thread is seeking then the same Cancel() operation
|
|
causes an error to be returned from the seek call to liboggplay which
|
|
exits out of the seek operation, and stops the seek state running on the
|
|
decode thread.
|
|
|
|
If the decode thread is blocked due to internal decode buffers being
|
|
full, it is unblocked during the shutdown process by calling
|
|
oggplay_prepare_for_close.
|
|
|
|
In practice the OggPlay internal buffer should never fill as we retrieve and
|
|
process the frame immediately on decoding.
|
|
|
|
The Shutdown method on nsOggDecoder can spin the event loop as it waits
|
|
for threads to complete. Spinning the event loop is a bad thing to happen
|
|
during certain times like destruction of the media element. To work around
|
|
this the Shutdown method does nothing by queue an event to the main thread
|
|
to perform the actual Shutdown. This way the shutdown can occur at a safe
|
|
time.
|
|
|
|
This means the owning object of a nsOggDecoder object *MUST* call Shutdown
|
|
when destroying the nsOggDecoder object.
|
|
*/
|
|
#if !defined(nsOggDecoder_h_)
|
|
#define nsOggDecoder_h_
|
|
|
|
#include "nsISupports.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIThread.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsChannelReader.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsSize.h"
|
|
#include "prlog.h"
|
|
#include "prmon.h"
|
|
#include "gfxContext.h"
|
|
#include "gfxRect.h"
|
|
#include "oggplay/oggplay.h"
|
|
#include "nsMediaDecoder.h"
|
|
|
|
class nsAudioStream;
|
|
class nsOggDecodeStateMachine;
|
|
|
|
class nsOggDecoder : public nsMediaDecoder
|
|
{
|
|
friend class nsOggDecodeStateMachine;
|
|
|
|
// ISupports
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// nsIObserver
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
public:
|
|
// Enumeration for the valid play states (see mPlayState)
|
|
enum PlayState {
|
|
PLAY_STATE_START,
|
|
PLAY_STATE_LOADING,
|
|
PLAY_STATE_PAUSED,
|
|
PLAY_STATE_PLAYING,
|
|
PLAY_STATE_SEEKING,
|
|
PLAY_STATE_ENDED,
|
|
PLAY_STATE_SHUTDOWN
|
|
};
|
|
|
|
nsOggDecoder();
|
|
~nsOggDecoder();
|
|
virtual PRBool Init(nsHTMLMediaElement* aElement);
|
|
|
|
// This method must be called by the owning object before that
|
|
// object disposes of this decoder object.
|
|
virtual void Shutdown();
|
|
|
|
virtual float GetCurrentTime();
|
|
|
|
virtual nsresult Load(nsIURI* aURI,
|
|
nsIChannel* aChannel,
|
|
nsIStreamListener **aListener);
|
|
|
|
// Start playback of a video. 'Load' must have previously been
|
|
// called.
|
|
virtual nsresult Play();
|
|
|
|
// Stop playback of a video, and stop download of video stream.
|
|
virtual void Stop();
|
|
|
|
// Seek to the time position in (seconds) from the start of the video.
|
|
virtual nsresult Seek(float time);
|
|
|
|
virtual nsresult PlaybackRateChanged();
|
|
|
|
virtual void Pause();
|
|
virtual void SetVolume(float volume);
|
|
virtual float GetDuration();
|
|
|
|
virtual void GetCurrentURI(nsIURI** aURI);
|
|
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
|
|
|
|
virtual void NotifySuspendedStatusChanged();
|
|
virtual void NotifyBytesDownloaded();
|
|
virtual void NotifyDownloadEnded(nsresult aStatus);
|
|
// Called by nsChannelReader on the decoder thread
|
|
void NotifyBytesConsumed(PRInt64 aBytes);
|
|
|
|
// Called when the video file has completed downloading.
|
|
// Call on the main thread only.
|
|
void ResourceLoaded();
|
|
|
|
// Called if the media file encounters a network error.
|
|
// Call on the main thread only.
|
|
virtual void NetworkError();
|
|
|
|
// Call from any thread safely. Return PR_TRUE if we are currently
|
|
// seeking in the media resource.
|
|
virtual PRBool IsSeeking() const;
|
|
|
|
// Return PR_TRUE if the decoder has reached the end of playback.
|
|
// Call on the main thread only.
|
|
virtual PRBool IsEnded() const;
|
|
|
|
// Set the duration of the media resource in units of milliseconds.
|
|
// This is called via a channel listener if it can pick up the duration
|
|
// from a content header. Must be called from the main thread only.
|
|
virtual void SetDuration(PRInt64 aDuration);
|
|
|
|
// Set a flag indicating whether seeking is supported
|
|
virtual void SetSeekable(PRBool aSeekable);
|
|
|
|
// Return PR_TRUE if seeking is supported.
|
|
virtual PRBool GetSeekable();
|
|
|
|
// Returns the channel reader.
|
|
nsChannelReader* GetReader() { return mReader; }
|
|
|
|
virtual Statistics GetStatistics();
|
|
|
|
// Suspend any media downloads that are in progress. Called by the
|
|
// media element when it is sent to the bfcache. Call on the main
|
|
// thread only.
|
|
virtual void Suspend();
|
|
|
|
// Resume any media downloads that have been suspended. Called by the
|
|
// media element when it is restored from the bfcache. Call on the
|
|
// main thread only.
|
|
virtual void Resume();
|
|
|
|
protected:
|
|
|
|
// Returns the monitor for other threads to synchronise access to
|
|
// state.
|
|
PRMonitor* GetMonitor()
|
|
{
|
|
return mMonitor;
|
|
}
|
|
|
|
// Return the current state. Can be called on any thread. If called from
|
|
// a non-main thread, the decoder monitor must be held.
|
|
PlayState GetState()
|
|
{
|
|
return mPlayState;
|
|
}
|
|
|
|
// Stop updating the bytes downloaded for progress notifications. Called
|
|
// when seeking to prevent wild changes to the progress notification.
|
|
// Must be called with the decoder monitor held.
|
|
void StopProgressUpdates();
|
|
|
|
// Allow updating the bytes downloaded for progress notifications. Must
|
|
// be called with the decoder monitor held.
|
|
void StartProgressUpdates();
|
|
|
|
// Something has changed that could affect the computed playback rate,
|
|
// so recompute it. The monitor must be held.
|
|
void UpdatePlaybackRate();
|
|
|
|
// The actual playback rate computation. The monitor must be held.
|
|
double ComputePlaybackRate(PRPackedBool* aReliable);
|
|
|
|
/******
|
|
* The following methods must only be called on the main
|
|
* thread.
|
|
******/
|
|
|
|
// Change to a new play state. This updates the mState variable and
|
|
// notifies any thread blocking on this object's monitor of the
|
|
// change. Call on the main thread only.
|
|
void ChangeState(PlayState aState);
|
|
|
|
// Called when the metadata from the Ogg file has been read.
|
|
// Call on the main thread only.
|
|
void MetadataLoaded();
|
|
|
|
// Called when the first frame has been loaded.
|
|
// Call on the main thread only.
|
|
void FirstFrameLoaded();
|
|
|
|
// Called when the video has completed playing.
|
|
// Call on the main thread only.
|
|
void PlaybackEnded();
|
|
|
|
// Seeking has stopped. Inform the element on the main
|
|
// thread.
|
|
void SeekingStopped();
|
|
|
|
// Seeking has started. Inform the element on the main
|
|
// thread.
|
|
void SeekingStarted();
|
|
|
|
// Called when the backend has changed the current playback
|
|
// position. It dispatches a timeupdate event and invalidates the frame.
|
|
// This must be called on the main thread only.
|
|
void PlaybackPositionChanged();
|
|
|
|
// Calls mElement->UpdateReadyStateForData, telling it whether we have
|
|
// data for the next frame and if we're buffering. Main thread only.
|
|
void UpdateReadyStateForData();
|
|
|
|
// Find the end of the cached data starting at the current decoder
|
|
// position.
|
|
PRInt64 GetDownloadPosition();
|
|
|
|
private:
|
|
// Register/Unregister with Shutdown Observer.
|
|
// Call on main thread only.
|
|
void RegisterShutdownObserver();
|
|
void UnregisterShutdownObserver();
|
|
|
|
/******
|
|
* The following members should be accessed with the decoder lock held.
|
|
******/
|
|
|
|
// 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.
|
|
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.
|
|
PRInt64 mPlaybackPosition;
|
|
// 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;
|
|
|
|
// The URI of the current resource
|
|
nsCOMPtr<nsIURI> mURI;
|
|
|
|
// Thread to handle decoding of Ogg data.
|
|
nsCOMPtr<nsIThread> mDecodeThread;
|
|
|
|
// The current playback position of the media resource in units of
|
|
// seconds. This is updated approximately at the framerate of the
|
|
// video (if it is a video) or the callback period of the audio.
|
|
// It is read and written from the main thread only.
|
|
float mCurrentTime;
|
|
|
|
// Volume that playback should start at. 0.0 = muted. 1.0 = full
|
|
// volume. Readable/Writeable from the main thread. Read from the
|
|
// audio thread when it is first started to get the initial volume
|
|
// level.
|
|
float mInitialVolume;
|
|
|
|
// Position to seek to when the seek notification is received by the
|
|
// decoding thread. Written by the main thread and read via the
|
|
// decoding thread. Synchronised using mPlayStateMonitor. If the
|
|
// value is negative then no seek has been requested. When a seek is
|
|
// started this is reset to negative.
|
|
float mRequestedSeekTime;
|
|
|
|
// Duration of the media resource. Set to -1 if unknown.
|
|
// Set when the Ogg metadata is loaded. Accessed on the main thread
|
|
// only.
|
|
PRInt64 mDuration;
|
|
|
|
// True if we are registered with the observer service for shutdown.
|
|
PRPackedBool mNotifyOnShutdown;
|
|
|
|
// True if the media resource is seekable (server supports byte range
|
|
// requests).
|
|
PRPackedBool mSeekable;
|
|
|
|
/******
|
|
* The following member variables can be accessed from any thread.
|
|
******/
|
|
|
|
// The state machine object for handling the decoding via
|
|
// oggplay. It is safe to call methods of this object from other
|
|
// threads. Its internal data is synchronised on a monitor. The
|
|
// lifetime of this object is after mPlayState is LOADING and before
|
|
// mPlayState is SHUTDOWN. It is safe to access it during this
|
|
// period.
|
|
nsCOMPtr<nsOggDecodeStateMachine> mDecodeStateMachine;
|
|
|
|
// OggPlay object used to read data from a channel. Created on main
|
|
// thread. Passed to liboggplay and the locking for multithreaded
|
|
// access is handled by that library. Some methods are called from
|
|
// the decoder thread, and the state machine for that thread keeps
|
|
// a pointer to this reader. This is safe as the only methods called
|
|
// are threadsafe (via the threadsafe nsMediaStream).
|
|
nsAutoPtr<nsChannelReader> mReader;
|
|
|
|
// Monitor for detecting when the video play state changes. A call
|
|
// to Wait on this monitor will block the thread until the next
|
|
// state change.
|
|
PRMonitor* mMonitor;
|
|
|
|
// Set to one of the valid play states. It is protected by the
|
|
// monitor mMonitor. This monitor must be acquired when reading or
|
|
// writing the state. Any change to the state on the main thread
|
|
// must call NotifyAll on the monitor so the decode thread can wake up.
|
|
PlayState mPlayState;
|
|
|
|
// The state to change to after a seek or load operation. It must only
|
|
// be changed from the main thread. The decoder monitor must be acquired
|
|
// when writing to the state, or when reading from a non-main thread.
|
|
// Any change to the state must call NotifyAll on the monitor.
|
|
PlayState mNextState;
|
|
|
|
// True when we have fully loaded the resource and reported that
|
|
// to the element (i.e. reached NETWORK_LOADED state).
|
|
// Accessed on the main thread only.
|
|
PRPackedBool mResourceLoaded;
|
|
|
|
// True when seeking or otherwise moving the play position around in
|
|
// such a manner that progress event data is inaccurate. This is set
|
|
// during seek and duration operations to prevent the progress indicator
|
|
// from jumping around. Read/Write from any thread. Must have decode monitor
|
|
// locked before accessing.
|
|
PRPackedBool mIgnoreProgressData;
|
|
};
|
|
|
|
#endif
|