b=449315 Support WAV format in <audio> element. r+sr=roc
[wine-gecko.git] / content / media / video / src / nsOggDecoder.cpp
blob0a69f4a2fe8bee45e0af5685c6454fe8cca87382
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
14 * License.
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.
22 * Contributor(s):
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 ***** */
38 #include "prlog.h"
39 #include "prmem.h"
40 #include "nsIFrame.h"
41 #include "nsIDocument.h"
42 #include "nsThreadUtils.h"
43 #include "nsIDOMHTMLMediaElement.h"
44 #include "nsNetUtil.h"
45 #include "nsAudioStream.h"
46 #include "nsChannelReader.h"
47 #include "nsHTMLVideoElement.h"
48 #include "nsIObserver.h"
49 #include "nsIObserverService.h"
50 #include "nsAutoLock.h"
51 #include "nsTArray.h"
52 #include "nsNetUtil.h"
53 #include "nsOggDecoder.h"
55 /*
56 The maximum height and width of the video. Used for
57 sanitizing the memory allocation of the RGB buffer
59 #define MAX_VIDEO_WIDTH 2000
60 #define MAX_VIDEO_HEIGHT 2000
62 // The number of entries in oggplay buffer list. This value
63 // is the one used by the oggplay examples.
64 #define OGGPLAY_BUFFER_SIZE 20
66 // The number of frames to read before audio callback is called.
67 // This value is the one used by the oggplay examples.
68 #define OGGPLAY_FRAMES_PER_CALLBACK 2048
70 // Offset into Ogg buffer containing audio information. This value
71 // is the one used by the oggplay examples.
72 #define OGGPLAY_AUDIO_OFFSET 250L
74 // Wait this number of seconds when buffering, then leave and play
75 // as best as we can if the required amount of data hasn't been
76 // retrieved.
77 #define BUFFERING_WAIT 15
79 // The amount of data to retrieve during buffering is computed based
80 // on the download rate. BUFFERING_MIN_RATE is the minimum download
81 // rate to be used in that calculation to help avoid constant buffering
82 // attempts at a time when the average download rate has not stabilised.
83 #define BUFFERING_MIN_RATE 50000
84 #define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
86 // The number of seconds of buffer data before buffering happens
87 // based on current playback rate.
88 #define BUFFERING_SECONDS_LOW_WATER_MARK 1
90 /*
91 All reading (including seeks) from the nsMediaStream are done on the
92 decoding thread. The decoder thread is informed before closing that
93 the stream is about to close via the Shutdown
94 event. oggplay_prepare_for_close is called before sending the
95 shutdown event to tell liboggplay to shutdown.
97 This call results in oggplay internally not calling any
98 read/write/seek/tell methods, and returns a value that results in
99 stopping the decoder thread.
101 oggplay_close is called in the destructor which results in the media
102 stream being closed. This is how the nsMediaStream contract that no
103 read/seeking must occur during or after Close is called is enforced.
105 This object keeps pointers to the nsOggDecoder and nsChannelReader
106 objects. Since the lifetime of nsOggDecodeStateMachine is
107 controlled by nsOggDecoder it will never have a stale reference to
108 these objects. The reader is destroyed by the call to oggplay_close
109 which is done in the destructor so again this will never be a stale
110 reference.
112 All internal state is synchronised via the decoder monitor. NotifyAll
113 on the monitor is called when the state of the state machine is changed
114 by the main thread. The following changes to state cause a notify:
116 mState and data related to that state changed (mSeekTime, etc)
117 Ogg Metadata Loaded
118 First Frame Loaded
119 Frame decoded
121 See nsOggDecoder.h for more details.
123 class nsOggDecodeStateMachine : public nsRunnable
125 public:
126 // Object to hold the decoded data from a frame
127 class FrameData {
128 public:
129 FrameData() :
130 mVideoWidth(0),
131 mVideoHeight(0),
132 mDecodedFrameTime(0.0),
133 mTime(0.0)
135 MOZ_COUNT_CTOR(FrameData);
138 ~FrameData()
140 MOZ_COUNT_DTOR(FrameData);
144 nsAutoArrayPtr<unsigned char> mVideoData;
145 nsTArray<float> mAudioData;
146 int mVideoWidth;
147 int mVideoHeight;
148 float mDecodedFrameTime;
149 float mTime;
150 OggPlayStreamInfo mState;
153 // A queue of decoded video frames.
154 class FrameQueue
156 public:
157 FrameQueue() :
158 mHead(0),
159 mTail(0),
160 mEmpty(PR_TRUE)
164 void Push(FrameData* frame)
166 NS_ASSERTION(!IsFull(), "FrameQueue is full");
167 mQueue[mTail] = frame;
168 mTail = (mTail+1) % OGGPLAY_BUFFER_SIZE;
169 mEmpty = PR_FALSE;
172 FrameData* Peek()
174 NS_ASSERTION(!mEmpty, "FrameQueue is empty");
176 return mQueue[mHead];
179 FrameData* Pop()
181 NS_ASSERTION(!mEmpty, "FrameQueue is empty");
183 FrameData* result = mQueue[mHead];
184 mHead = (mHead + 1) % OGGPLAY_BUFFER_SIZE;
185 mEmpty = mHead == mTail;
186 return result;
189 PRBool IsEmpty()
191 return mEmpty;
194 PRBool IsFull()
196 return !mEmpty && mHead == mTail;
199 private:
200 FrameData* mQueue[OGGPLAY_BUFFER_SIZE];
201 PRInt32 mHead;
202 PRInt32 mTail;
203 PRPackedBool mEmpty;
206 // Enumeration for the valid states
207 enum State {
208 DECODER_STATE_DECODING_METADATA,
209 DECODER_STATE_DECODING_FIRSTFRAME,
210 DECODER_STATE_DECODING,
211 DECODER_STATE_SEEKING,
212 DECODER_STATE_BUFFERING,
213 DECODER_STATE_COMPLETED,
214 DECODER_STATE_SHUTDOWN
217 nsOggDecodeStateMachine(nsOggDecoder* aDecoder, nsChannelReader* aReader);
218 ~nsOggDecodeStateMachine();
220 // Cause state transitions. These methods obtain the decoder monitor
221 // to synchronise the change of state, and to notify other threads
222 // that the state has changed.
223 void Shutdown();
224 void Decode();
225 void Seek(float aTime);
227 NS_IMETHOD Run();
229 PRBool HasAudio()
231 NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "HasAudio() called during invalid state");
232 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "HasAudio() called without acquiring decoder monitor");
233 return mAudioTrack != -1;
236 // Decode one frame of data, returning the OggPlay error code. Must
237 // be called only when the current state > DECODING_METADATA. The decode
238 // monitor MUST NOT be locked during this call since it can take a long
239 // time. liboggplay internally handles locking.
240 // Any return value apart from those below is mean decoding cannot continue.
241 // E_OGGPLAY_CONTINUE = One frame decoded and put in buffer list
242 // E_OGGPLAY_USER_INTERRUPT = One frame decoded, buffer list is now full
243 // E_OGGPLAY_TIMEOUT = No frames decoded, timed out
244 OggPlayErrorCode DecodeFrame();
246 // Returns the next decoded frame of data. The caller is responsible
247 // for freeing the memory returned. This function must be called
248 // only when the current state > DECODING_METADATA. The decode
249 // monitor lock does not need to be locked during this call since
250 // liboggplay internally handles locking.
251 FrameData* NextFrame();
253 // Play a frame of decoded video. The decode monitor is obtained
254 // internally by this method for synchronisation.
255 void PlayFrame();
257 // Play the video data from the given frame. The decode monitor
258 // must be locked when calling this method.
259 void PlayVideo(FrameData* aFrame);
261 // Play the audio data from the given frame. The decode monitor must
262 // be locked when calling this method. Returns PR_FALSE if unable to
263 // write to the audio device without blocking.
264 PRBool PlayAudio(FrameData* aFrame);
266 // Called from the main thread to get the current frame time. The decoder
267 // monitor must be obtained before calling this.
268 float GetCurrentTime();
270 // Get and set the audio volume. The decoder monitor must be
271 // obtained before calling this.
272 float GetVolume();
273 void SetVolume(float aVolume);
275 // Clear the flag indicating that a playback position change event
276 // is currently queued. This is called from the main thread and must
277 // be called with the decode monitor held.
278 void ClearPositionChangeFlag();
280 protected:
281 // Convert the OggPlay frame information into a format used by Gecko
282 // (RGB for video, float for sound, etc).The decoder monitor must be
283 // acquired in the scope of calls to these functions. They must be
284 // called only when the current state > DECODING_METADATA.
285 void HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayVideoData* aVideoData);
286 void HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize);
288 // These methods can only be called on the decoding thread.
289 void LoadOggHeaders();
291 // Initializes and opens the audio stream. Called from the decode
292 // thread only. Must be called with the decode monitor held.
293 void OpenAudioStream();
295 // Closes and releases resources used by the audio stream. Called
296 // from the decode thread only. Must be called with the decode
297 // monitor held.
298 void CloseAudioStream();
300 // Start playback of audio, either by opening or resuming the audio
301 // stream. Must be called with the decode monitor held.
302 void StartAudio();
304 // Stop playback of audio, either by closing or pausing the audio
305 // stream. Must be called with the decode monitor held.
306 void StopAudio();
308 // Start playback of media. Must be called with the decode monitor held.
309 void StartPlayback();
311 // Stop playback of media. Must be called with the decode monitor held.
312 void StopPlayback();
314 // Update the playback position. This can result in a timeupdate event
315 // and an invalidate of the frame being dispatched asynchronously if
316 // there is no such event currently queued.
317 // Only called on the decoder thread. Must be called with
318 // the decode monitor held.
319 void UpdatePlaybackPosition(float aTime);
321 private:
322 // *****
323 // The follow fields are only accessed by the decoder thread
324 // *****
326 // The decoder object that created this state machine. The decoder
327 // always outlives us since it controls our lifetime.
328 nsOggDecoder* mDecoder;
330 // The OggPlay handle. Synchronisation of calls to oggplay functions
331 // are handled by liboggplay. We control the lifetime of this
332 // object, destroying it in our destructor.
333 OggPlay* mPlayer;
335 // Frame data containing decoded video/audio for the frame the
336 // current frame and the previous frame. Accessed only via the
337 // decoder thread.
338 FrameQueue mDecodedFrames;
340 // The time that playback started from the system clock. This is used
341 // for synchronising frames. It is reset after a seek as the mTime member
342 // of FrameData is reset to start at 0 from the first frame after a seek.
343 // Accessed only via the decoder thread.
344 PRIntervalTime mPlayStartTime;
346 // The time that playback was most recently paused, either via
347 // buffering or pause. This is used to compute mPauseDuration for
348 // a/v sync adjustments. Accessed only via the decoder thread.
349 PRIntervalTime mPauseStartTime;
351 // The total time that has been spent in completed pauses (via
352 // 'pause' or buffering). This is used to adjust for these
353 // pauses when computing a/v synchronisation. Accessed only via the
354 // decoder thread.
355 PRIntervalTime mPauseDuration;
357 // PR_TRUE if the media is playing and the decoder has started
358 // the sound and adjusted the sync time for pauses. PR_FALSE
359 // if the media is paused and the decoder has stopped the sound
360 // and adjusted the sync time for pauses. Accessed only via the
361 // decoder thread.
362 PRPackedBool mPlaying;
364 // Number of seconds of data video/audio data held in a frame.
365 // Accessed only via the decoder thread.
366 float mCallbackPeriod;
368 // Video data. These are initially set when the metadata is loaded.
369 // They are only accessed from the decoder thread.
370 PRInt32 mVideoTrack;
371 float mFramerate;
373 // Audio data. These are initially set when the metadata is loaded.
374 // They are only accessed from the decoder thread.
375 PRInt32 mAudioRate;
376 PRInt32 mAudioChannels;
377 PRInt32 mAudioTrack;
379 // Channel Reader. Originally created by the mDecoder object, it is
380 // destroyed when we close the mPlayer handle in the
381 // destructor. Used to obtain download and playback rate information
382 // for buffering. Synchronisation for those methods are handled by
383 // nsMediaStream.
384 nsChannelReader* mReader;
386 // Time that buffering started. Used for buffering timeout and only
387 // accessed in the decoder thread.
388 PRIntervalTime mBufferingStart;
390 // Number of bytes to buffer when buffering. Only accessed in the
391 // decoder thread.
392 PRUint32 mBufferingBytes;
394 // The time value of the last decoded video frame. Used for
395 // computing the sleep period between frames for a/v sync.
396 // Read/Write from the decode thread only.
397 float mLastFrameTime;
399 // *****
400 // The follow fields are accessed by the decoder thread or
401 // the main thread.
402 // *****
404 // The decoder monitor must be obtained before modifying this state.
405 // NotifyAll on the monitor must be called when the state is changed by
406 // the main thread so the decoder thread can wake up.
407 State mState;
409 // Position to seek to when the seek state transition occurs. The
410 // decoder monitor lock must be obtained before reading or writing
411 // this value.
412 float mSeekTime;
414 // The audio stream resource. Used on the decode thread and the
415 // main thread (Via the Get/SetVolume calls). Synchronisation via
416 // mDecoder monitor.
417 nsAutoPtr<nsAudioStream> mAudioStream;
419 // The time of the current frame in seconds. This is referenced from
420 // 0.0 which is the initial start of the stream. Set by the decode
421 // thread, and read-only from the main thread to get the current
422 // time value. Synchronised via decoder monitor.
423 float mCurrentFrameTime;
425 // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
426 // from the decode and main threads. Synchronised via decoder
427 // monitor.
428 float mVolume;
430 // PR_TRUE if an event to notify about a change in the playback
431 // position has been queued, but not yet run. It is set to PR_FALSE when
432 // the event is run. This allows coalescing of these events as they can be
433 // produced many times per second. Synchronised via decoder monitor.
434 PRPackedBool mPositionChangeQueued;
437 nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder* aDecoder, nsChannelReader* aReader) :
438 mDecoder(aDecoder),
439 mPlayer(0),
440 mPlayStartTime(0),
441 mPauseStartTime(0),
442 mPauseDuration(0),
443 mPlaying(PR_FALSE),
444 mCallbackPeriod(1.0),
445 mVideoTrack(-1),
446 mFramerate(0.0),
447 mAudioRate(0),
448 mAudioChannels(0),
449 mAudioTrack(-1),
450 mReader(aReader),
451 mBufferingStart(0),
452 mBufferingBytes(0),
453 mLastFrameTime(0),
454 mState(DECODER_STATE_DECODING_METADATA),
455 mSeekTime(0.0),
456 mCurrentFrameTime(0.0),
457 mVolume(1.0),
458 mPositionChangeQueued(PR_FALSE)
462 nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
464 while (!mDecodedFrames.IsEmpty()) {
465 delete mDecodedFrames.Pop();
468 oggplay_close(mPlayer);
472 OggPlayErrorCode nsOggDecodeStateMachine::DecodeFrame()
474 NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "DecodeFrame() called during invalid state");
475 return oggplay_step_decoding(mPlayer);
478 nsOggDecodeStateMachine::FrameData* nsOggDecodeStateMachine::NextFrame()
480 NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "NextFrame() called during invalid state");
481 OggPlayCallbackInfo** info = oggplay_buffer_retrieve_next(mPlayer);
482 if (!info)
483 return nsnull;
485 FrameData* frame = new FrameData();
486 if (!frame) {
487 return nsnull;
490 frame->mTime = mLastFrameTime;
491 mLastFrameTime += mCallbackPeriod;
492 int num_tracks = oggplay_get_num_tracks(mPlayer);
493 float audioTime = 0.0;
494 float videoTime = 0.0;
496 if (mVideoTrack != -1 &&
497 num_tracks > mVideoTrack &&
498 oggplay_callback_info_get_type(info[mVideoTrack]) == OGGPLAY_YUV_VIDEO) {
499 OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mVideoTrack]);
500 videoTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
501 HandleVideoData(frame, mVideoTrack, oggplay_callback_info_get_video_data(headers[0]));
504 if (mAudioTrack != -1 &&
505 num_tracks > mAudioTrack &&
506 oggplay_callback_info_get_type(info[mAudioTrack]) == OGGPLAY_FLOATS_AUDIO) {
507 OggPlayDataHeader** headers = oggplay_callback_info_get_headers(info[mAudioTrack]);
508 audioTime = ((float)oggplay_callback_info_get_presentation_time(headers[0]))/1000.0;
509 int required = oggplay_callback_info_get_required(info[mAudioTrack]);
510 for (int j = 0; j < required; ++j) {
511 int size = oggplay_callback_info_get_record_size(headers[j]);
512 OggPlayAudioData* audio_data = oggplay_callback_info_get_audio_data(headers[j]);
513 HandleAudioData(frame, audio_data, size);
517 // Pick one stream to act as the reference track to indicate if the
518 // stream has ended, seeked, etc.
519 if (mVideoTrack >= 0 )
520 frame->mState = oggplay_callback_info_get_stream_info(info[mVideoTrack]);
521 else if (mAudioTrack >= 0)
522 frame->mState = oggplay_callback_info_get_stream_info(info[mAudioTrack]);
523 else
524 frame->mState = OGGPLAY_STREAM_UNINITIALISED;
526 frame->mDecodedFrameTime = mVideoTrack == -1 ? audioTime : videoTime;
528 oggplay_buffer_release(mPlayer, info);
529 return frame;
532 void nsOggDecodeStateMachine::HandleVideoData(FrameData* aFrame, int aTrackNum, OggPlayVideoData* aVideoData) {
533 if (!aVideoData)
534 return;
536 int y_width;
537 int y_height;
538 oggplay_get_video_y_size(mPlayer, aTrackNum, &y_width, &y_height);
539 int uv_width;
540 int uv_height;
541 oggplay_get_video_uv_size(mPlayer, aTrackNum, &uv_width, &uv_height);
543 if (y_width >= MAX_VIDEO_WIDTH || y_height >= MAX_VIDEO_HEIGHT) {
544 return;
547 aFrame->mVideoWidth = y_width;
548 aFrame->mVideoHeight = y_height;
549 aFrame->mVideoData = new unsigned char[y_width * y_height * 4];
550 if (!aFrame->mVideoData) {
551 return;
554 OggPlayYUVChannels yuv;
555 OggPlayRGBChannels rgb;
557 yuv.ptry = aVideoData->y;
558 yuv.ptru = aVideoData->u;
559 yuv.ptrv = aVideoData->v;
560 yuv.uv_width = uv_width;
561 yuv.uv_height = uv_height;
562 yuv.y_width = y_width;
563 yuv.y_height = y_height;
565 rgb.ptro = aFrame->mVideoData;
566 rgb.rgb_width = aFrame->mVideoWidth;
567 rgb.rgb_height = aFrame->mVideoHeight;
569 oggplay_yuv2bgr(&yuv, &rgb);
572 void nsOggDecodeStateMachine::HandleAudioData(FrameData* aFrame, OggPlayAudioData* aAudioData, int aSize) {
573 // 'aSize' is number of samples. Multiply by number of channels to
574 // get the actual number of floats being sent.
575 int size = aSize * mAudioChannels;
577 aFrame->mAudioData.AppendElements(reinterpret_cast<float*>(aAudioData), size);
580 void nsOggDecodeStateMachine::PlayFrame() {
581 // Play a frame of video and/or audio data.
582 // If we are playing we open the audio stream if needed
583 // If there are decoded frames in the queue a single frame
584 // is popped off and played if it is time for that frame
585 // to display.
586 // If it is not time yet to display the frame, we either
587 // continue decoding frames, or wait until it is time for
588 // the frame to display if the queue is full.
590 // If the decode state is not PLAYING then we just exit
591 // so we can continue decoding frames. If the queue is
592 // full we wait for a state change.
593 nsAutoMonitor mon(mDecoder->GetMonitor());
595 if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
596 if (!mPlaying) {
597 StartPlayback();
600 if (!mDecodedFrames.IsEmpty()) {
601 FrameData* frame = mDecodedFrames.Peek();
602 if (frame->mState == OGGPLAY_STREAM_JUST_SEEKED) {
603 // After returning from a seek all mTime members of
604 // FrameData start again from a time position of 0.
605 // Reset the play start time.
606 mPlayStartTime = PR_IntervalNow();
607 mPauseDuration = 0;
610 double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0);
611 if (time >= frame->mTime) {
612 mDecodedFrames.Pop();
613 // Audio for the current frame is played, but video for the next frame
614 // is displayed, to account for lag from the time the audio is written
615 // to when it is played. This will go away when we move to a/v sync
616 // using the audio hardware clock.
617 PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
618 PlayAudio(frame);
619 UpdatePlaybackPosition(frame->mDecodedFrameTime);
620 delete frame;
622 else {
623 // If the queue of decoded frame is full then we wait for the
624 // approximate time until the next frame.
625 if (mDecodedFrames.IsFull()) {
626 mon.Wait(PR_MillisecondsToInterval(PRInt64((frame->mTime - time)*1000)));
627 if (mState == DECODER_STATE_SHUTDOWN) {
628 return;
634 else {
635 if (mPlaying) {
636 StopPlayback();
639 if (mDecodedFrames.IsFull() && mState == DECODER_STATE_DECODING) {
640 mon.Wait();
641 if (mState == DECODER_STATE_SHUTDOWN) {
642 return;
648 void nsOggDecodeStateMachine::PlayVideo(FrameData* aFrame)
650 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayVideo() called without acquiring decoder monitor");
651 if (aFrame) {
652 if (aFrame->mVideoData) {
653 nsAutoLock lock(mDecoder->mVideoUpdateLock);
655 mDecoder->SetRGBData(aFrame->mVideoWidth, aFrame->mVideoHeight, mFramerate, aFrame->mVideoData);
660 PRBool nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame)
662 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayAudio() called without acquiring decoder monitor");
663 if (mAudioStream && aFrame && !aFrame->mAudioData.IsEmpty()) {
664 if (PRUint32(mAudioStream->Available()) < aFrame->mAudioData.Length())
665 return PR_FALSE;
667 mAudioStream->Write(aFrame->mAudioData.Elements(), aFrame->mAudioData.Length());
670 return PR_TRUE;
673 void nsOggDecodeStateMachine::OpenAudioStream()
675 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "OpenAudioStream() called without acquiring decoder monitor");
676 mAudioStream = new nsAudioStream();
677 if (!mAudioStream) {
678 LOG(PR_LOG_ERROR, ("Could not create audio stream"));
680 else {
681 mAudioStream->Init(mAudioChannels, mAudioRate);
682 mAudioStream->SetVolume(mVolume);
686 void nsOggDecodeStateMachine::CloseAudioStream()
688 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "CloseAudioStream() called without acquiring decoder monitor");
689 if (mAudioStream) {
690 mAudioStream->Shutdown();
691 mAudioStream = nsnull;
695 void nsOggDecodeStateMachine::StartAudio()
697 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartAudio() called without acquiring decoder monitor");
698 if (HasAudio()) {
699 OpenAudioStream();
703 void nsOggDecodeStateMachine::StopAudio()
705 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopAudio() called without acquiring decoder monitor");
706 if (HasAudio()) {
707 CloseAudioStream();
711 void nsOggDecodeStateMachine::StartPlayback()
713 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartPlayback() called without acquiring decoder monitor");
714 StartAudio();
715 mPlaying = PR_TRUE;
717 // If this is the very first play, then set the initial start time
718 if (mPlayStartTime == 0) {
719 mPlayStartTime = PR_IntervalNow();
722 // If we have been paused previously, then compute duration spent paused
723 if (mPauseStartTime != 0) {
724 mPauseDuration += PR_IntervalNow() - mPauseStartTime;
728 void nsOggDecodeStateMachine::StopPlayback()
730 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopPlayback() called without acquiring decoder monitor");
731 StopAudio();
732 mPlaying = PR_FALSE;
733 mPauseStartTime = PR_IntervalNow();
736 void nsOggDecodeStateMachine::UpdatePlaybackPosition(float aTime)
738 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "UpdatePlaybackPosition() called without acquiring decoder monitor");
739 mCurrentFrameTime = aTime;
740 if (!mPositionChangeQueued) {
741 mPositionChangeQueued = PR_TRUE;
742 nsCOMPtr<nsIRunnable> event =
743 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackPositionChanged);
744 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
748 void nsOggDecodeStateMachine::ClearPositionChangeFlag()
750 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "ClearPositionChangeFlag() called without acquiring decoder monitor");
751 mPositionChangeQueued = PR_FALSE;
754 float nsOggDecodeStateMachine::GetVolume()
756 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetVolume() called without acquiring decoder monitor");
757 return mVolume;
760 void nsOggDecodeStateMachine::SetVolume(float volume)
762 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetVolume() called without acquiring decoder monitor");
763 if (mAudioStream) {
764 mAudioStream->SetVolume(volume);
767 mVolume = volume;
770 float nsOggDecodeStateMachine::GetCurrentTime()
772 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetCurrentTime() called without acquiring decoder monitor");
773 return mCurrentFrameTime;
777 void nsOggDecodeStateMachine::Shutdown()
779 // oggplay_prepare_for_close cannot be undone. Once called, the
780 // mPlayer object cannot decode any more frames. Once we've entered
781 // the shutdown state here there's no going back.
782 nsAutoMonitor mon(mDecoder->GetMonitor());
783 if (mPlayer) {
784 oggplay_prepare_for_close(mPlayer);
786 mState = DECODER_STATE_SHUTDOWN;
787 mon.NotifyAll();
790 void nsOggDecodeStateMachine::Decode()
792 // When asked to decode, switch to decoding only if
793 // we are currently buffering.
794 nsAutoMonitor mon(mDecoder->GetMonitor());
795 if (mState == DECODER_STATE_BUFFERING) {
796 mState = DECODER_STATE_DECODING;
800 void nsOggDecodeStateMachine::Seek(float aTime)
802 nsAutoMonitor mon(mDecoder->GetMonitor());
803 mSeekTime = aTime;
804 mState = DECODER_STATE_SEEKING;
807 nsresult nsOggDecodeStateMachine::Run()
809 while (PR_TRUE) {
810 nsAutoMonitor mon(mDecoder->GetMonitor());
811 switch(mState) {
812 case DECODER_STATE_SHUTDOWN:
813 return NS_OK;
815 case DECODER_STATE_DECODING_METADATA:
816 mon.Exit();
817 LoadOggHeaders();
818 mon.Enter();
820 if (mState == DECODER_STATE_DECODING_METADATA) {
821 mState = DECODER_STATE_DECODING_FIRSTFRAME;
823 break;
825 case DECODER_STATE_DECODING_FIRSTFRAME:
827 OggPlayErrorCode r;
828 do {
829 mon.Exit();
830 r = DecodeFrame();
831 mon.Enter();
832 } while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
834 if (mState == DECODER_STATE_SHUTDOWN)
835 continue;
837 mLastFrameTime = 0;
838 FrameData* frame = NextFrame();
839 if (frame) {
840 mDecodedFrames.Push(frame);
841 UpdatePlaybackPosition(frame->mDecodedFrameTime);
842 PlayVideo(frame);
845 nsCOMPtr<nsIRunnable> event =
846 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, FirstFrameLoaded);
847 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
849 if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
850 mState = DECODER_STATE_DECODING;
853 break;
855 case DECODER_STATE_DECODING:
857 // Before decoding check if we should buffer more data
858 if (mReader->DownloadRate() >= 0 &&
859 mReader->Available() < mReader->PlaybackRate() * BUFFERING_SECONDS_LOW_WATER_MARK) {
860 if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
861 if (mPlaying) {
862 StopPlayback();
866 mBufferingStart = PR_IntervalNow();
867 mBufferingBytes = PRUint32(BUFFERING_RATE(mReader->PlaybackRate()) * BUFFERING_WAIT);
868 mState = DECODER_STATE_BUFFERING;
870 nsCOMPtr<nsIRunnable> event =
871 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStarted);
872 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
874 else {
875 if (!mDecodedFrames.IsFull()) {
876 mon.Exit();
877 OggPlayErrorCode r = DecodeFrame();
878 mon.Enter();
880 if (mState == DECODER_STATE_SHUTDOWN)
881 continue;
883 // Get the decoded frame and store it in our queue of decoded frames
884 FrameData* frame = NextFrame();
885 if (frame) {
886 mDecodedFrames.Push(frame);
889 if (r != E_OGGPLAY_CONTINUE &&
890 r != E_OGGPLAY_USER_INTERRUPT &&
891 r != E_OGGPLAY_TIMEOUT) {
892 mState = DECODER_STATE_COMPLETED;
896 // Show at least the first frame if we're not playing
897 // so we have a poster frame on initial load and after seek.
898 if (!mPlaying && !mDecodedFrames.IsEmpty()) {
899 PlayVideo(mDecodedFrames.Peek());
902 PlayFrame();
905 break;
907 case DECODER_STATE_SEEKING:
909 // During the seek, don't have a lock on the decoder state,
910 // otherwise long seek operations can block the main thread.
911 // The events dispatched to the main thread are SYNC calls.
912 // These calls are made outside of the decode monitor lock so
913 // it is safe for the main thread to makes calls that acquire
914 // the lock since it won't deadlock. We check the state when
915 // acquiring the lock again in case shutdown has occurred
916 // during the time when we didn't have the lock.
917 float seekTime = mSeekTime;
918 mon.Exit();
919 nsCOMPtr<nsIRunnable> startEvent =
920 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStarted);
921 NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
923 oggplay_seek(mPlayer, ogg_int64_t(seekTime * 1000));
925 mon.Enter();
926 if (mState == DECODER_STATE_SHUTDOWN)
927 continue;
929 // Remove all frames decoded prior to seek from the queue
930 while (!mDecodedFrames.IsEmpty()) {
931 delete mDecodedFrames.Pop();
934 OggPlayErrorCode r;
935 do {
936 mon.Exit();
937 r = DecodeFrame();
938 mon.Enter();
939 } while (mState != DECODER_STATE_SHUTDOWN && r == E_OGGPLAY_TIMEOUT);
941 if (mState == DECODER_STATE_SHUTDOWN)
942 continue;
944 mLastFrameTime = 0;
945 FrameData* frame = NextFrame();
946 if (frame) {
947 mDecodedFrames.Push(frame);
948 UpdatePlaybackPosition(frame->mDecodedFrameTime);
949 PlayVideo(frame);
951 mon.Exit();
952 nsCOMPtr<nsIRunnable> stopEvent =
953 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, SeekingStopped);
954 NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
955 mon.Enter();
957 if (mState == DECODER_STATE_SEEKING && mSeekTime == seekTime) {
958 mState = DECODER_STATE_DECODING;
961 break;
963 case DECODER_STATE_BUFFERING:
964 if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart) < BUFFERING_WAIT*1000) &&
965 mReader->DownloadRate() >= 0 &&
966 mReader->Available() < mBufferingBytes) {
967 LOG(PR_LOG_DEBUG,
968 ("Buffering data until %d bytes available or %d milliseconds",
969 mBufferingBytes - mReader->Available(),
970 BUFFERING_WAIT*1000 - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart))));
971 mon.Wait(PR_MillisecondsToInterval(1000));
972 if (mState == DECODER_STATE_SHUTDOWN)
973 continue;
975 else {
976 mState = DECODER_STATE_DECODING;
979 if (mState != DECODER_STATE_BUFFERING) {
980 nsCOMPtr<nsIRunnable> event =
981 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, BufferingStopped);
982 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
983 if (mDecoder->GetState() == nsOggDecoder::PLAY_STATE_PLAYING) {
984 if (!mPlaying) {
985 StartPlayback();
990 break;
992 case DECODER_STATE_COMPLETED:
994 while (mState != DECODER_STATE_SHUTDOWN &&
995 !mDecodedFrames.IsEmpty()) {
996 PlayFrame();
997 if (mState != DECODER_STATE_SHUTDOWN) {
998 // Wait for the time of one frame so we don't tight loop
999 // and we need to release the monitor so timeupdate and
1000 // invalidate's on the main thread can occur.
1001 mon.Wait(PR_MillisecondsToInterval(PRInt64(mCallbackPeriod*1000)));
1005 if (mState == DECODER_STATE_SHUTDOWN)
1006 continue;
1008 nsCOMPtr<nsIRunnable> event =
1009 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
1010 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1011 do {
1012 mon.Wait();
1013 } while (mState != DECODER_STATE_SHUTDOWN);
1015 break;
1019 return NS_OK;
1022 void nsOggDecodeStateMachine::LoadOggHeaders()
1024 LOG(PR_LOG_DEBUG, ("Loading Ogg Headers"));
1025 mPlayer = oggplay_open_with_reader(mReader);
1026 if (mPlayer) {
1027 LOG(PR_LOG_DEBUG, ("There are %d tracks", oggplay_get_num_tracks(mPlayer)));
1029 for (int i = 0; i < oggplay_get_num_tracks(mPlayer); ++i) {
1030 LOG(PR_LOG_DEBUG, ("Tracks %d: %s", i, oggplay_get_track_typename(mPlayer, i)));
1031 if (mVideoTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_THEORA) {
1032 oggplay_set_callback_num_frames(mPlayer, i, 1);
1033 mVideoTrack = i;
1034 int fpsd, fpsn;
1035 oggplay_get_video_fps(mPlayer, i, &fpsd, &fpsn);
1036 mFramerate = fpsd == 0 ? 0.0 : float(fpsn)/float(fpsd);
1037 mCallbackPeriod = 1.0 / mFramerate;
1038 LOG(PR_LOG_DEBUG, ("Frame rate: %f", mFramerate));
1040 else if (mAudioTrack == -1 && oggplay_get_track_type(mPlayer, i) == OGGZ_CONTENT_VORBIS) {
1041 mAudioTrack = i;
1042 oggplay_set_offset(mPlayer, i, OGGPLAY_AUDIO_OFFSET);
1043 oggplay_get_audio_samplerate(mPlayer, i, &mAudioRate);
1044 oggplay_get_audio_channels(mPlayer, i, &mAudioChannels);
1045 LOG(PR_LOG_DEBUG, ("samplerate: %d, channels: %d", mAudioRate, mAudioChannels));
1048 if (oggplay_set_track_active(mPlayer, i) < 0) {
1049 LOG(PR_LOG_ERROR, ("Could not set track %d active", i));
1053 if (mVideoTrack == -1) {
1054 oggplay_set_callback_num_frames(mPlayer, mAudioTrack, OGGPLAY_FRAMES_PER_CALLBACK);
1055 mCallbackPeriod = 1.0 / (float(mAudioRate) / OGGPLAY_FRAMES_PER_CALLBACK);
1057 LOG(PR_LOG_DEBUG, ("Callback Period: %f", mCallbackPeriod));
1059 oggplay_use_buffer(mPlayer, OGGPLAY_BUFFER_SIZE);
1061 // Inform the element that we've loaded the Ogg metadata
1062 nsCOMPtr<nsIRunnable> metadataLoadedEvent =
1063 NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, MetadataLoaded);
1065 NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
1069 NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder, nsIObserver)
1071 void nsOggDecoder::Pause()
1073 nsAutoMonitor mon(mMonitor);
1074 if (mPlayState == PLAY_STATE_SEEKING) {
1075 mNextState = PLAY_STATE_PAUSED;
1076 return;
1079 ChangeState(PLAY_STATE_PAUSED);
1082 float nsOggDecoder::GetVolume()
1084 nsAutoMonitor mon(mMonitor);
1085 return mDecodeStateMachine ? mDecodeStateMachine->GetVolume() : mInitialVolume;
1088 void nsOggDecoder::SetVolume(float volume)
1090 nsAutoMonitor mon(mMonitor);
1091 mInitialVolume = volume;
1093 if (mDecodeStateMachine) {
1094 mDecodeStateMachine->SetVolume(volume);
1098 float nsOggDecoder::GetDuration()
1100 // Currently not implemented. Video Spec says to return
1101 // NaN if unknown.
1102 // TODO: return NaN
1103 return 0.0;
1106 nsOggDecoder::nsOggDecoder() :
1107 nsMediaDecoder(),
1108 mBytesDownloaded(0),
1109 mCurrentTime(0.0),
1110 mInitialVolume(0.0),
1111 mRequestedSeekTime(-1.0),
1112 mContentLength(0),
1113 mNotifyOnShutdown(PR_FALSE),
1114 mReader(0),
1115 mMonitor(0),
1116 mPlayState(PLAY_STATE_PAUSED),
1117 mNextState(PLAY_STATE_PAUSED)
1119 MOZ_COUNT_CTOR(nsOggDecoder);
1122 PRBool nsOggDecoder::Init()
1124 mMonitor = nsAutoMonitor::NewMonitor("media.decoder");
1125 return mMonitor && nsMediaDecoder::Init();
1128 // An event that gets posted to the main thread, when the media
1129 // element is being destroyed, to destroy the decoder. Since the
1130 // decoder shutdown can block and post events this cannot be done
1131 // inside destructor calls. So this event is posted asynchronously to
1132 // the main thread to perform the shutdown. It keeps a strong
1133 // reference to the decoder to ensure it does not get deleted when the
1134 // element is deleted.
1135 class nsOggDecoderShutdown : public nsRunnable
1137 public:
1138 nsOggDecoderShutdown(nsOggDecoder* aDecoder) :
1139 mDecoder(aDecoder)
1143 NS_IMETHOD Run()
1145 mDecoder->Stop();
1146 return NS_OK;
1149 private:
1150 nsRefPtr<nsOggDecoder> mDecoder;
1154 void nsOggDecoder::Shutdown()
1156 ChangeState(PLAY_STATE_SHUTDOWN);
1157 nsMediaDecoder::Shutdown();
1159 nsCOMPtr<nsIRunnable> event = new nsOggDecoderShutdown(this);
1160 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1163 nsOggDecoder::~nsOggDecoder()
1165 MOZ_COUNT_DTOR(nsOggDecoder);
1166 nsAutoMonitor::DestroyMonitor(mMonitor);
1169 nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
1170 nsIStreamListener** aStreamListener)
1172 if (aStreamListener) {
1173 *aStreamListener = nsnull;
1176 if (aURI) {
1177 NS_ASSERTION(!aStreamListener, "No listener should be requested here");
1178 mURI = aURI;
1179 } else {
1180 NS_ASSERTION(aChannel, "Either a URI or a channel is required");
1181 NS_ASSERTION(aStreamListener, "A listener should be requested here");
1183 // If the channel was redirected, we want the post-redirect URI;
1184 // but if the URI scheme was expanded, say from chrome: to jar:file:,
1185 // we want the original URI.
1186 nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI));
1187 NS_ENSURE_SUCCESS(rv, rv);
1190 StartProgress();
1192 RegisterShutdownObserver();
1194 mReader = new nsChannelReader();
1195 NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
1197 nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
1198 NS_ENSURE_SUCCESS(rv, rv);
1200 rv = NS_NewThread(getter_AddRefs(mDecodeThread));
1201 NS_ENSURE_SUCCESS(rv, rv);
1203 mDecodeStateMachine = new nsOggDecodeStateMachine(this, mReader);
1205 ChangeState(PLAY_STATE_LOADING);
1207 return mDecodeThread->Dispatch(mDecodeStateMachine, NS_DISPATCH_NORMAL);
1210 nsresult nsOggDecoder::Play()
1212 nsAutoMonitor mon(mMonitor);
1213 if (mPlayState == PLAY_STATE_SEEKING) {
1214 mNextState = PLAY_STATE_PLAYING;
1215 return NS_OK;
1218 ChangeState(PLAY_STATE_PLAYING);
1220 return NS_OK;
1223 nsresult nsOggDecoder::Seek(float aTime)
1225 nsAutoMonitor mon(mMonitor);
1227 if (aTime < 0.0)
1228 return NS_ERROR_FAILURE;
1230 if (mPlayState == PLAY_STATE_LOADING && aTime == 0.0) {
1231 return NS_OK;
1234 mRequestedSeekTime = aTime;
1236 // If we are already in the seeking state, then setting mRequestedSeekTime
1237 // above will result in the new seek occurring when the current seek
1238 // completes.
1239 if (mPlayState != PLAY_STATE_SEEKING) {
1240 mNextState = mPlayState;
1241 ChangeState(PLAY_STATE_SEEKING);
1244 return NS_OK;
1247 nsresult nsOggDecoder::PlaybackRateChanged()
1249 return NS_ERROR_NOT_IMPLEMENTED;
1252 void nsOggDecoder::Stop()
1254 ChangeState(PLAY_STATE_ENDED);
1256 StopProgress();
1258 // Force any outstanding seek and byterange requests to complete
1259 // to prevent shutdown from deadlocking.
1260 if (mReader) {
1261 mReader->Cancel();
1262 mReader = nsnull;
1265 // Shutdown must be on called the mDecodeStateMachine before deleting.
1266 // This is required to ensure that the state machine isn't running
1267 // in the thread and using internal objects when it is deleted.
1268 if (mDecodeStateMachine) {
1269 mDecodeStateMachine->Shutdown();
1272 // The state machines must be Shutdown() before the thread is
1273 // Shutdown. The Shutdown() on the state machine unblocks any
1274 // blocking calls preventing the thread Shutdown from deadlocking.
1275 if (mDecodeThread) {
1276 mDecodeThread->Shutdown();
1277 mDecodeThread = nsnull;
1280 mDecodeStateMachine = nsnull;
1281 UnregisterShutdownObserver();
1284 float nsOggDecoder::GetCurrentTime()
1286 return mCurrentTime;
1289 void nsOggDecoder::GetCurrentURI(nsIURI** aURI)
1291 NS_IF_ADDREF(*aURI = mURI);
1294 nsIPrincipal* nsOggDecoder::GetCurrentPrincipal()
1296 if (!mReader) {
1297 return nsnull;
1300 return mReader->GetCurrentPrincipal();
1303 void nsOggDecoder::MetadataLoaded()
1305 if (mElement) {
1306 mElement->MetadataLoaded();
1310 void nsOggDecoder::FirstFrameLoaded()
1312 if (mElement) {
1313 mElement->FirstFrameLoaded();
1316 // The element can run javascript via events
1317 // before reaching here, so only change the
1318 // state if we're still set to the original
1319 // loading state.
1320 nsAutoMonitor mon(mMonitor);
1321 if (mPlayState == PLAY_STATE_LOADING) {
1322 if (mRequestedSeekTime >= 0.0) {
1323 ChangeState(PLAY_STATE_SEEKING);
1325 else {
1326 ChangeState(mNextState);
1331 void nsOggDecoder::ResourceLoaded()
1333 if (mElement) {
1334 mElement->ResourceLoaded();
1336 StopProgress();
1339 void nsOggDecoder::NetworkError()
1341 if (mElement)
1342 mElement->NetworkError();
1343 Stop();
1346 PRBool nsOggDecoder::IsSeeking() const
1348 return mPlayState == PLAY_STATE_SEEKING;
1351 void nsOggDecoder::PlaybackEnded()
1353 Stop();
1354 if (mElement) {
1355 mElement->PlaybackEnded();
1359 NS_IMETHODIMP nsOggDecoder::Observe(nsISupports *aSubjet,
1360 const char *aTopic,
1361 const PRUnichar *someData)
1363 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
1364 Shutdown();
1367 return NS_OK;
1370 PRUint64 nsOggDecoder::GetBytesLoaded()
1372 return mBytesDownloaded;
1375 PRInt64 nsOggDecoder::GetTotalBytes()
1377 return mContentLength;
1380 void nsOggDecoder::SetTotalBytes(PRInt64 aBytes)
1382 mContentLength = aBytes;
1385 void nsOggDecoder::UpdateBytesDownloaded(PRUint64 aBytes)
1387 mBytesDownloaded = aBytes;
1390 void nsOggDecoder::BufferingStopped()
1392 if (mElement) {
1393 mElement->ChangeReadyState(nsIDOMHTMLMediaElement::CAN_SHOW_CURRENT_FRAME);
1397 void nsOggDecoder::BufferingStarted()
1399 if (mElement) {
1400 mElement->ChangeReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE);
1404 void nsOggDecoder::SeekingStopped()
1407 nsAutoMonitor mon(mMonitor);
1408 if (mPlayState == PLAY_STATE_SHUTDOWN)
1409 return;
1411 // An additional seek was requested while the current seek was
1412 // in operation.
1413 if (mRequestedSeekTime >= 0.0)
1414 ChangeState(PLAY_STATE_SEEKING);
1415 else
1416 ChangeState(mNextState);
1419 if (mElement) {
1420 mElement->SeekCompleted();
1424 void nsOggDecoder::SeekingStarted()
1427 nsAutoMonitor mon(mMonitor);
1428 if (mPlayState == PLAY_STATE_SHUTDOWN)
1429 return;
1432 if (mElement) {
1433 mElement->SeekStarted();
1437 void nsOggDecoder::RegisterShutdownObserver()
1439 if (!mNotifyOnShutdown) {
1440 nsCOMPtr<nsIObserverService> observerService =
1441 do_GetService("@mozilla.org/observer-service;1");
1442 if (observerService) {
1443 mNotifyOnShutdown =
1444 NS_SUCCEEDED(observerService->AddObserver(this,
1445 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
1446 PR_FALSE));
1448 else {
1449 NS_WARNING("Could not get an observer service. Video decoding events may not shutdown cleanly.");
1454 void nsOggDecoder::UnregisterShutdownObserver()
1456 if (mNotifyOnShutdown) {
1457 nsCOMPtr<nsIObserverService> observerService =
1458 do_GetService("@mozilla.org/observer-service;1");
1459 if (observerService) {
1460 mNotifyOnShutdown = PR_FALSE;
1461 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1466 void nsOggDecoder::ChangeState(PlayState aState)
1468 nsAutoMonitor mon(mMonitor);
1470 if (mNextState == aState) {
1471 mNextState = PLAY_STATE_PAUSED;
1474 if (mPlayState == PLAY_STATE_SHUTDOWN) {
1475 mon.NotifyAll();
1476 return;
1479 if (mPlayState == PLAY_STATE_ENDED &&
1480 aState != PLAY_STATE_SHUTDOWN) {
1481 // If we've completed playback then the decode and display threads
1482 // have been shutdown. To honor the state change request we need
1483 // to reload the resource and restart the threads.
1484 // Like seeking, this will require opening a new channel, which means
1485 // we may not actually get the same resource --- a server may send
1486 // us something different.
1487 mNextState = aState;
1488 mPlayState = PLAY_STATE_LOADING;
1489 Load(mURI, nsnull, nsnull);
1490 return;
1493 mPlayState = aState;
1494 switch (aState) {
1495 case PLAY_STATE_PAUSED:
1496 /* No action needed */
1497 break;
1498 case PLAY_STATE_PLAYING:
1499 mDecodeStateMachine->Decode();
1500 break;
1501 case PLAY_STATE_SEEKING:
1502 mDecodeStateMachine->Seek(mRequestedSeekTime);
1503 mRequestedSeekTime = -1.0;
1504 break;
1505 case PLAY_STATE_LOADING:
1506 /* No action needed */
1507 break;
1508 case PLAY_STATE_START:
1509 /* No action needed */
1510 break;
1511 case PLAY_STATE_ENDED:
1512 /* No action needed */
1513 break;
1514 case PLAY_STATE_SHUTDOWN:
1515 /* No action needed */
1516 break;
1518 mon.NotifyAll();
1521 void nsOggDecoder::PlaybackPositionChanged()
1523 float lastTime = mCurrentTime;
1525 // Control the scope of the monitor so it is not
1526 // held while the timeupdate and the invalidate is run.
1528 nsAutoMonitor mon(mMonitor);
1530 // If we are shutting down, don't dispatch the event
1531 if (mPlayState == PLAY_STATE_SHUTDOWN)
1532 return;
1534 if (mDecodeStateMachine) {
1535 mCurrentTime = mDecodeStateMachine->GetCurrentTime();
1536 mDecodeStateMachine->ClearPositionChangeFlag();
1540 // Invalidate the frame so any video data is displayed.
1541 // Do this before the timeupdate event so that if that
1542 // event runs JavaScript that queries the media size, the
1543 // frame has reflowed and the size updated beforehand.
1544 Invalidate();
1546 if (mElement && lastTime != mCurrentTime) {
1547 mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));