1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: ML 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is Mozilla code.
18 * The Initial Developer of the Original Code is the Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2007
20 * the Initial Developer. All Rights Reserved.
23 * Chris Double <chris.double@double.co.nz>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 Each video element has one thread. This thread, called the Decode thread,
40 owns the resources for downloading and reading the video file. It goes through the
41 file, decoding the theora and vorbis data. It uses Oggplay to do the decoding.
42 It indirectly uses an nsMediaStream to do the file reading and seeking via Oggplay.
43 All file reads and seeks must occur on this thread only. It handles the sending
44 of the audio data to the sound device and the presentation of the video data
45 at the correct frame rate.
47 When the decode thread is created an event is dispatched to it. The event
48 runs for the lifetime of the playback of the resource. The decode thread
49 synchronises with the main thread via a single monitor held by the
52 The event contains a Run method which consists of an infinite loop
53 that checks the state that the state machine is in and processes
54 operations on that state.
56 The nsOggDecodeStateMachine class is the event that gets dispatched to
57 the decode thread. It has the following states:
60 The Ogg headers are being loaded, and things like framerate, etc are
63 The first frame of audio/video data is being decoded.
65 Video/Audio frames are being decoded.
67 A seek operation is in progress.
69 Decoding is paused while data is buffered for smooth playback.
71 The resource has completed decoding.
73 The decoder object is about to be destroyed.
75 The following result in state transitions.
78 Clean up any resources the nsOggDecodeStateMachine owns.
80 Start decoding video frames.
82 This is not user initiated. It occurs when the
83 available data in the stream drops below a certain point.
85 This is not user initiated. It occurs when the
86 stream is completely decoded.
88 Seek to the time position given in the resource.
90 A state transition diagram:
94 v -->-------------------->--------------------------|
98 v >-------------------->--------------------------|
99 | |------------->----->------------------------| v
103 ^-----------<----SEEKING | v Complete v v
105 | | | COMPLETED SHUTDOWN-<-|
107 | | | >-------->-----^
108 | Decode() |Seek(t) |Buffer() |
109 -----------<--------<-------BUFFERING |
115 The Main thread controls the decode state machine by setting the value
116 of a mPlayState variable and notifying on the monitor
117 based on the high level player actions required (Seek, Pause, Play, etc).
119 The player states are the states requested by the client through the
120 DOM API. They represent the desired state of the player, while the
121 decoder's state represents the actual state of the decoder.
123 The high level state of the player is maintained via a PlayState value.
124 It can have the following states:
127 The decoder has been initialized but has no resource loaded.
129 A request via the API has been received to pause playback.
131 A request via the API has been received to load a resource.
133 A request via the API has been received to start playback.
135 A request via the API has been received to start seeking.
137 Playback has completed.
139 The decoder is about to be destroyed.
141 State transition occurs when the Media Element calls the Play, Seek,
142 etc methods on the nsOggDecoder object. When the transition occurs
143 nsOggDecoder then calls the methods on the decoder state machine
144 object to cause it to behave appropriate to the play state.
146 The following represents the states that the player can be in, and the
147 valid states the decode thread can be in at that time:
149 player LOADING decoder DECODING_METADATA, DECODING_FIRSTFRAME
150 player PLAYING decoder DECODING, BUFFERING, SEEKING, COMPLETED
151 player PAUSED decoder DECODING, BUFFERING, SEEKING, COMPLETED
152 player SEEKING decoder SEEKING
153 player COMPLETED decoder SHUTDOWN
154 player SHUTDOWN decoder SHUTDOWN
156 The general sequence of events with these objects is:
158 1) The video element calls Load on nsMediaDecoder. This creates the
159 decode thread and starts the channel for downloading the file. It
160 instantiates and starts the Decode state machine. The high level
161 LOADING state is entered, which results in the decode state machine
162 to start decoding metadata. These are the headers that give the
163 video size, framerate, etc. It returns immediately to the calling
166 2) When the Ogg metadata has been loaded by the decode thread it will
167 call a method on the video element object to inform it that this
168 step is done, so it can do the things required by the video
169 specification at this stage. The decoder then continues to decode
170 the first frame of data.
172 3) When the first frame of Ogg data has been successfully decoded it
173 calls a method on the video element object to inform it that this
174 step has been done, once again so it can do the required things by
175 the video specification at this stage.
177 This results in the high level state changing to PLAYING or PAUSED
178 depending on any user action that may have occurred.
180 The decode thread, while in the DECODING state, plays audio and
181 video, if the correct frame time comes around and the decoder
182 play state is PLAYING.
184 a/v synchronisation is done by a combination of liboggplay and the
185 Decoder state machine. liboggplay ensures that a decoded frame of data
186 has both the audio samples and the YUV data for that period of time.
188 When a frame is decoded by the decode state machine it converts the
189 YUV encoded video to RGB and copies the sound data to an internal
190 FrameData object. This is stored in a queue of available decoded frames.
191 Included in the FrameData object is the time that that frame should
194 The display state machine keeps track of the time since the last frame it
195 played. After decoding a frame it checks if it is time to display the next
196 item in the decoded frame queue. If so, it pops the item off the queue
199 Ideally a/v sync would take into account the actual audio clock of the
200 audio hardware for the sync rather than using the system clock.
201 Unfortunately getting valid time data out of the audio hardware has proven
202 to be unreliable across platforms (and even distributions in Linux) depending
203 on audio hardware, audio backend etc. The current approach works fine in practice
204 and is a compromise until this issue can be sorted. The plan is to eventually
205 move to synchronising using the audio hardware.
207 To prevent audio skipping and framerate dropping it is very important to
208 make sure no blocking occurs during the decoding process and minimise
209 expensive time operations at the time a frame is to be displayed. This is
210 managed by immediately converting video data to RGB on decode (an expensive
211 operation to do at frame display time) and checking if the sound device will
212 not block before writing sound data to it.
214 Shutdown needs to ensure that the event posted to the decode
215 thread is completed. The decode thread can potentially block internally
216 inside liboggplay when reading, seeking, or its internal buffers containing
217 decoded data are full. When blocked in this manner a call from the main thread
218 to Shutdown() will hang.
220 This is fixed with a protocol to ensure that the decode event cleanly
221 completes. The nsMediaStream that the nsChannelReader uses has a
222 Cancel() method. Calling this before Shutdown() will close any
223 internal streams or listeners resulting in blocked i/o completing with
224 an error, and all future i/o on the stream having an error.
226 This causes the decode thread to exit and Shutdown() can occur.
228 If the decode thread is seeking then the same Cancel() operation
229 causes an error to be returned from the seek call to liboggplay which
230 exits out of the seek operation, and stops the seek state running on the
233 If the decode thread is blocked due to internal decode buffers being
234 full, it is unblocked during the shutdown process by calling
235 oggplay_prepare_for_close.
237 In practice the OggPlay internal buffer should never fill as we retrieve and
238 process the frame immediately on decoding.
240 The Shutdown method on nsOggDecoder can spin the event loop as it waits
241 for threads to complete. Spinning the event loop is a bad thing to happen
242 during certain times like destruction of the media element. To work around
243 this the Shutdown method does nothing by queue an event to the main thread
244 to perform the actual Shutdown. This way the shutdown can occur at a safe
247 This means the owning object of a nsOggDecoder object *MUST* call Shutdown
248 when destroying the nsOggDecoder object.
250 #if !defined(nsOggDecoder_h_)
251 #define nsOggDecoder_h_
253 #include "nsISupports.h"
254 #include "nsCOMPtr.h"
255 #include "nsIThread.h"
256 #include "nsIChannel.h"
257 #include "nsChannelReader.h"
258 #include "nsIObserver.h"
259 #include "nsIFrame.h"
260 #include "nsAutoPtr.h"
264 #include "gfxContext.h"
266 #include "oggplay/oggplay.h"
267 #include "nsMediaDecoder.h"
270 class nsOggDecodeStateMachine
;
272 class nsOggDecoder
: public nsMediaDecoder
274 friend class nsOggDecodeStateMachine
;
283 // Enumeration for the valid play states (see mPlayState)
298 // This method must be called by the owning object before that
299 // object disposes of this decoder object.
300 virtual void Shutdown();
302 virtual float GetCurrentTime();
304 virtual nsresult
Load(nsIURI
* aURI
,
305 nsIChannel
* aChannel
,
306 nsIStreamListener
**aListener
);
308 // Start playback of a video. 'Load' must have previously been
310 virtual nsresult
Play();
312 // Stop playback of a video, and stop download of video stream.
315 // Seek to the time position in (seconds) from the start of the video.
316 virtual nsresult
Seek(float time
);
318 virtual nsresult
PlaybackRateChanged();
320 virtual void Pause();
321 virtual float GetVolume();
322 virtual void SetVolume(float volume
);
323 virtual float GetDuration();
325 virtual void GetCurrentURI(nsIURI
** aURI
);
326 virtual nsIPrincipal
* GetCurrentPrincipal();
328 virtual void UpdateBytesDownloaded(PRUint64 aBytes
);
330 // Called when the video file has completed downloading.
331 // Call on the main thread only.
332 void ResourceLoaded();
334 // Called if the media file encounters a network error.
335 // Call on the main thread only.
336 virtual void NetworkError();
338 // Call from any thread safely. Return PR_TRUE if we are currently
339 // seeking in the media resource.
340 virtual PRBool
IsSeeking() const;
342 // Return PR_TRUE if the decoder has reached the end of playback.
343 // Call on the main thread only.
344 virtual PRBool
IsEnded() const;
346 // Get the size of the media file in bytes. Called on the main thread only.
347 virtual void SetTotalBytes(PRInt64 aBytes
);
349 // Set a flag indicating whether seeking is supported
350 virtual void SetSeekable(PRBool aSeekable
);
352 // Return PR_TRUE if seeking is supported.
353 virtual PRBool
GetSeekable();
355 // Returns the channel reader.
356 nsChannelReader
* GetReader() { return mReader
; }
360 // Returns the monitor for other threads to synchronise access to
362 PRMonitor
* GetMonitor()
367 // Return the current state. Can be called on any thread. If called from
368 // a non-main thread, the decoder monitor must be held.
375 * The following methods must only be called on the main
379 // Change to a new play state. This updates the mState variable and
380 // notifies any thread blocking on this object's monitor of the
381 // change. Call on the main thread only.
382 void ChangeState(PlayState aState
);
384 // Called when the metadata from the Ogg file has been read.
385 // Call on the main thread only.
386 void MetadataLoaded();
388 // Called when the first frame has been loaded.
389 // Call on the main thread only.
390 void FirstFrameLoaded();
392 // Called when the video has completed playing.
393 // Call on the main thread only.
394 void PlaybackEnded();
396 // Return the current number of bytes loaded from the video file.
397 // This is used for progress events.
398 virtual PRUint64
GetBytesLoaded();
400 // Return the size of the video file in bytes.
401 // This is used for progress events.
402 virtual PRInt64
GetTotalBytes();
404 // Buffering of data has stopped. Inform the element on the main
406 void BufferingStopped();
408 // Buffering of data has started. Inform the element on the main
410 void BufferingStarted();
412 // Seeking has stopped. Inform the element on the main
414 void SeekingStopped();
416 // Seeking has started. Inform the element on the main
418 void SeekingStarted();
420 // Called when the backend has changed the current playback
421 // position. It dispatches a timeupdate event and invalidates the frame.
422 // This must be called on the main thread only.
423 void PlaybackPositionChanged();
426 // Register/Unregister with Shutdown Observer.
427 // Call on main thread only.
428 void RegisterShutdownObserver();
429 void UnregisterShutdownObserver();
432 * The following members should be accessed on the main thread only
434 // Total number of bytes downloaded so far.
435 PRUint64 mBytesDownloaded
;
437 // The URI of the current resource
438 nsCOMPtr
<nsIURI
> mURI
;
440 // Thread to handle decoding of Ogg data.
441 nsCOMPtr
<nsIThread
> mDecodeThread
;
443 // The current playback position of the media resource in units of
444 // seconds. This is updated approximately at the framerate of the
445 // video (if it is a video) or the callback period of the audio.
446 // It is read and written from the main thread only.
449 // Volume that playback should start at. 0.0 = muted. 1.0 = full
450 // volume. Readable/Writeable from the main thread. Read from the
451 // audio thread when it is first started to get the initial volume
453 float mInitialVolume
;
455 // Position to seek to when the seek notification is received by the
456 // decoding thread. Written by the main thread and read via the
457 // decoding thread. Synchronised using mPlayStateMonitor. If the
458 // value is negative then no seek has been requested. When a seek is
459 // started this is reset to negative.
460 float mRequestedSeekTime
;
462 // Size of the media file in bytes. Set on the first non-byte range
463 // HTTP request from nsChannelToPipe Listener. Accessed on the
465 PRInt64 mContentLength
;
467 // Duration of the media resource. Set to -1 if unknown.
468 // Set when the Ogg metadata is loaded. Accessed on the main thread
472 // True if we are registered with the observer service for shutdown.
473 PRPackedBool mNotifyOnShutdown
;
475 // True if the media resource is seekable (server supports byte range
477 PRPackedBool mSeekable
;
480 * The following member variables can be accessed from any thread.
483 // The state machine object for handling the decoding via
484 // oggplay. It is safe to call methods of this object from other
485 // threads. Its internal data is synchronised on a monitor. The
486 // lifetime of this object is after mPlayState is LOADING and before
487 // mPlayState is SHUTDOWN. It is safe to access it during this
489 nsCOMPtr
<nsOggDecodeStateMachine
> mDecodeStateMachine
;
491 // OggPlay object used to read data from a channel. Created on main
492 // thread. Passed to liboggplay and the locking for multithreaded
493 // access is handled by that library. Some methods are called from
494 // the decoder thread, and the state machine for that thread keeps
495 // a pointer to this reader. This is safe as the only methods called
496 // are threadsafe (via the threadsafe nsMediaStream).
497 nsAutoPtr
<nsChannelReader
> mReader
;
499 // Monitor for detecting when the video play state changes. A call
500 // to Wait on this monitor will block the thread until the next
504 // Set to one of the valid play states. It is protected by the
505 // monitor mMonitor. This monitor must be acquired when reading or
506 // writing the state. Any change to the state on the main thread
507 // must call NotifyAll on the monitor so the decode thread can wake up.
508 PlayState mPlayState
;
510 // The state to change to after a seek or load operation. It must only
511 // be changed from the main thread. The decoder monitor must be acquired
512 // when writing to the state, or when reading from a non-main thread.
513 // Any change to the state must call NotifyAll on the monitor.
514 PlayState mNextState
;