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 ***** */
42 #include "nsIDocument.h"
43 #include "nsThreadUtils.h"
44 #include "nsIDOMHTMLMediaElement.h"
45 #include "nsNetUtil.h"
46 #include "nsAudioStream.h"
47 #include "nsChannelReader.h"
48 #include "nsHTMLVideoElement.h"
49 #include "nsIObserver.h"
50 #include "nsIObserverService.h"
51 #include "nsAutoLock.h"
53 #include "nsNetUtil.h"
54 #include "nsOggDecoder.h"
57 The maximum height and width of the video. Used for
58 sanitizing the memory allocation of the RGB buffer
60 #define MAX_VIDEO_WIDTH 2000
61 #define MAX_VIDEO_HEIGHT 2000
63 // The number of entries in oggplay buffer list. This value
64 // is the one used by the oggplay examples.
65 #define OGGPLAY_BUFFER_SIZE 20
67 // The number of frames to read before audio callback is called.
68 // This value is the one used by the oggplay examples.
69 #define OGGPLAY_FRAMES_PER_CALLBACK 2048
71 // Offset into Ogg buffer containing audio information. This value
72 // is the one used by the oggplay examples.
73 #define OGGPLAY_AUDIO_OFFSET 250L
75 // Wait this number of seconds when buffering, then leave and play
76 // as best as we can if the required amount of data hasn't been
78 #define BUFFERING_WAIT 15
80 // The amount of data to retrieve during buffering is computed based
81 // on the download rate. BUFFERING_MIN_RATE is the minimum download
82 // rate to be used in that calculation to help avoid constant buffering
83 // attempts at a time when the average download rate has not stabilised.
84 #define BUFFERING_MIN_RATE 50000
85 #define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
87 // The number of seconds of buffer data before buffering happens
88 // based on current playback rate.
89 #define BUFFERING_SECONDS_LOW_WATER_MARK 1
92 All reading (including seeks) from the nsMediaStream are done on the
93 decoding thread. The decoder thread is informed before closing that
94 the stream is about to close via the Shutdown
95 event. oggplay_prepare_for_close is called before sending the
96 shutdown event to tell liboggplay to shutdown.
98 This call results in oggplay internally not calling any
99 read/write/seek/tell methods, and returns a value that results in
100 stopping the decoder thread.
102 oggplay_close is called in the destructor which results in the media
103 stream being closed. This is how the nsMediaStream contract that no
104 read/seeking must occur during or after Close is called is enforced.
106 This object keeps pointers to the nsOggDecoder and nsChannelReader
107 objects. Since the lifetime of nsOggDecodeStateMachine is
108 controlled by nsOggDecoder it will never have a stale reference to
109 these objects. The reader is destroyed by the call to oggplay_close
110 which is done in the destructor so again this will never be a stale
113 All internal state is synchronised via the decoder monitor. NotifyAll
114 on the monitor is called when the state of the state machine is changed
115 by the main thread. The following changes to state cause a notify:
117 mState and data related to that state changed (mSeekTime, etc)
122 See nsOggDecoder.h for more details.
124 class nsOggDecodeStateMachine
: public nsRunnable
127 // Object to hold the decoded data from a frame
133 mDecodedFrameTime(0.0),
136 MOZ_COUNT_CTOR(FrameData
);
141 MOZ_COUNT_DTOR(FrameData
);
144 // Write the audio data from the frame to the Audio stream.
145 void Write(nsAudioStream
* aStream
)
147 PRUint32 length
= mAudioData
.Length();
151 aStream
->Write(mAudioData
.Elements(), length
);
154 nsAutoArrayPtr
<unsigned char> mVideoData
;
155 nsTArray
<float> mAudioData
;
158 float mDecodedFrameTime
;
160 OggPlayStreamInfo mState
;
163 // A queue of decoded video frames.
174 void Push(FrameData
* frame
)
176 NS_ASSERTION(!IsFull(), "FrameQueue is full");
177 mQueue
[mTail
] = frame
;
178 mTail
= (mTail
+1) % OGGPLAY_BUFFER_SIZE
;
184 NS_ASSERTION(!mEmpty
, "FrameQueue is empty");
186 return mQueue
[mHead
];
191 NS_ASSERTION(!mEmpty
, "FrameQueue is empty");
193 FrameData
* result
= mQueue
[mHead
];
194 mHead
= (mHead
+ 1) % OGGPLAY_BUFFER_SIZE
;
195 mEmpty
= mHead
== mTail
;
206 return !mEmpty
&& mHead
== mTail
;
210 FrameData
* mQueue
[OGGPLAY_BUFFER_SIZE
];
216 // Enumeration for the valid states
218 DECODER_STATE_DECODING_METADATA
,
219 DECODER_STATE_DECODING_FIRSTFRAME
,
220 DECODER_STATE_DECODING
,
221 DECODER_STATE_SEEKING
,
222 DECODER_STATE_BUFFERING
,
223 DECODER_STATE_COMPLETED
,
224 DECODER_STATE_SHUTDOWN
227 nsOggDecodeStateMachine(nsOggDecoder
* aDecoder
);
228 ~nsOggDecodeStateMachine();
230 // Cause state transitions. These methods obtain the decoder monitor
231 // to synchronise the change of state, and to notify other threads
232 // that the state has changed.
235 void Seek(float aTime
);
241 NS_ASSERTION(mState
> DECODER_STATE_DECODING_METADATA
, "HasAudio() called during invalid state");
242 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "HasAudio() called without acquiring decoder monitor");
243 return mAudioTrack
!= -1;
246 // Decode one frame of data, returning the OggPlay error code. Must
247 // be called only when the current state > DECODING_METADATA. The decode
248 // monitor MUST NOT be locked during this call since it can take a long
249 // time. liboggplay internally handles locking.
250 // Any return value apart from those below is mean decoding cannot continue.
251 // E_OGGPLAY_CONTINUE = One frame decoded and put in buffer list
252 // E_OGGPLAY_USER_INTERRUPT = One frame decoded, buffer list is now full
253 // E_OGGPLAY_TIMEOUT = No frames decoded, timed out
254 OggPlayErrorCode
DecodeFrame();
256 // Returns the next decoded frame of data. The caller is responsible
257 // for freeing the memory returned. This function must be called
258 // only when the current state > DECODING_METADATA. The decode
259 // monitor lock does not need to be locked during this call since
260 // liboggplay internally handles locking.
261 FrameData
* NextFrame();
263 // Play a frame of decoded video. The decode monitor is obtained
264 // internally by this method for synchronisation.
267 // Play the video data from the given frame. The decode monitor
268 // must be locked when calling this method.
269 void PlayVideo(FrameData
* aFrame
);
271 // Play the audio data from the given frame. The decode monitor must
272 // be locked when calling this method.
273 void PlayAudio(FrameData
* aFrame
);
275 // Called from the main thread to get the current frame time. The decoder
276 // monitor must be obtained before calling this.
277 float GetCurrentTime();
279 // Called from the main thread to get the duration. The decoder monitor
280 // must be obtained before calling this. It is in units of milliseconds.
281 PRInt64
GetDuration();
283 // Called from the main thread to set the content length of the media
284 // resource. The decoder monitor must be obtained before calling this.
285 void SetContentLength(PRInt64 aLength
);
287 // Called from the main thread to set whether the media resource can
288 // be seeked. The decoder monitor must be obtained before calling this.
289 void SetSeekable(PRBool aSeekable
);
291 // Get and set the audio volume. The decoder monitor must be
292 // obtained before calling this.
294 void SetVolume(float aVolume
);
296 // Clear the flag indicating that a playback position change event
297 // is currently queued. This is called from the main thread and must
298 // be called with the decode monitor held.
299 void ClearPositionChangeFlag();
302 // Convert the OggPlay frame information into a format used by Gecko
303 // (RGB for video, float for sound, etc).The decoder monitor must be
304 // acquired in the scope of calls to these functions. They must be
305 // called only when the current state > DECODING_METADATA.
306 void HandleVideoData(FrameData
* aFrame
, int aTrackNum
, OggPlayVideoData
* aVideoData
);
307 void HandleAudioData(FrameData
* aFrame
, OggPlayAudioData
* aAudioData
, int aSize
);
309 // These methods can only be called on the decoding thread.
310 void LoadOggHeaders();
312 // Initializes and opens the audio stream. Called from the decode
313 // thread only. Must be called with the decode monitor held.
314 void OpenAudioStream();
316 // Closes and releases resources used by the audio stream. Called
317 // from the decode thread only. Must be called with the decode
319 void CloseAudioStream();
321 // Start playback of audio, either by opening or resuming the audio
322 // stream. Must be called with the decode monitor held.
325 // Stop playback of audio, either by closing or pausing the audio
326 // stream. Must be called with the decode monitor held.
329 // Start playback of media. Must be called with the decode monitor held.
330 void StartPlayback();
332 // Stop playback of media. Must be called with the decode monitor held.
335 // Update the playback position. This can result in a timeupdate event
336 // and an invalidate of the frame being dispatched asynchronously if
337 // there is no such event currently queued.
338 // Only called on the decoder thread. Must be called with
339 // the decode monitor held.
340 void UpdatePlaybackPosition(float aTime
);
344 // The follow fields are only accessed by the decoder thread
347 // The decoder object that created this state machine. The decoder
348 // always outlives us since it controls our lifetime.
349 nsOggDecoder
* mDecoder
;
351 // The OggPlay handle. Synchronisation of calls to oggplay functions
352 // are handled by liboggplay. We control the lifetime of this
353 // object, destroying it in our destructor.
356 // Frame data containing decoded video/audio for the frame the
357 // current frame and the previous frame. Accessed only via the
359 FrameQueue mDecodedFrames
;
361 // The time that playback started from the system clock. This is used
362 // for synchronising frames. It is reset after a seek as the mTime member
363 // of FrameData is reset to start at 0 from the first frame after a seek.
364 // Accessed only via the decoder thread.
365 PRIntervalTime mPlayStartTime
;
367 // The time that playback was most recently paused, either via
368 // buffering or pause. This is used to compute mPauseDuration for
369 // a/v sync adjustments. Accessed only via the decoder thread.
370 PRIntervalTime mPauseStartTime
;
372 // The total time that has been spent in completed pauses (via
373 // 'pause' or buffering). This is used to adjust for these
374 // pauses when computing a/v synchronisation. Accessed only via the
376 PRIntervalTime mPauseDuration
;
378 // PR_TRUE if the media is playing and the decoder has started
379 // the sound and adjusted the sync time for pauses. PR_FALSE
380 // if the media is paused and the decoder has stopped the sound
381 // and adjusted the sync time for pauses. Accessed only via the
383 PRPackedBool mPlaying
;
385 // Number of seconds of data video/audio data held in a frame.
386 // Accessed only via the decoder thread.
387 float mCallbackPeriod
;
389 // Video data. These are initially set when the metadata is loaded.
390 // They are only accessed from the decoder thread.
394 // Audio data. These are initially set when the metadata is loaded.
395 // They are only accessed from the decoder thread.
397 PRInt32 mAudioChannels
;
400 // Time that buffering started. Used for buffering timeout and only
401 // accessed in the decoder thread.
402 PRIntervalTime mBufferingStart
;
404 // Number of bytes to buffer when buffering. Only accessed in the
406 PRUint32 mBufferingBytes
;
408 // The time value of the last decoded video frame. Used for
409 // computing the sleep period between frames for a/v sync.
410 // Read/Write from the decode thread only.
411 float mLastFrameTime
;
414 // The follow fields are accessed by the decoder thread or
418 // The decoder monitor must be obtained before modifying this state.
419 // NotifyAll on the monitor must be called when the state is changed by
420 // the main thread so the decoder thread can wake up.
423 // Position to seek to when the seek state transition occurs. The
424 // decoder monitor lock must be obtained before reading or writing
428 // The audio stream resource. Used on the decode thread and the
429 // main thread (Via the Get/SetVolume calls). Synchronisation via
431 nsAutoPtr
<nsAudioStream
> mAudioStream
;
433 // The time of the current frame in seconds. This is referenced from
434 // 0.0 which is the initial start of the stream. Set by the decode
435 // thread, and read-only from the main thread to get the current
436 // time value. Synchronised via decoder monitor.
437 float mCurrentFrameTime
;
439 // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
440 // from the decode and main threads. Synchronised via decoder
444 // Duration of the media resource. It is accessed from the decoder and main
445 // threads. Synchronised via decoder monitor. It is in units of
449 // Content Length of the media resource if known. If it is -1 then the
450 // size is unknown. Accessed from the decoder and main threads. Synchronised
451 // via decoder monitor.
452 PRInt64 mContentLength
;
454 // PR_TRUE if the media resource can be seeked. Accessed from the decoder
455 // and main threads. Synchronised via decoder monitor.
456 PRPackedBool mSeekable
;
458 // PR_TRUE if an event to notify about a change in the playback
459 // position has been queued, but not yet run. It is set to PR_FALSE when
460 // the event is run. This allows coalescing of these events as they can be
461 // produced many times per second. Synchronised via decoder monitor.
462 PRPackedBool mPositionChangeQueued
;
465 nsOggDecodeStateMachine::nsOggDecodeStateMachine(nsOggDecoder
* aDecoder
) :
472 mCallbackPeriod(1.0),
481 mState(DECODER_STATE_DECODING_METADATA
),
483 mCurrentFrameTime(0.0),
488 mPositionChangeQueued(PR_FALSE
)
492 nsOggDecodeStateMachine::~nsOggDecodeStateMachine()
494 while (!mDecodedFrames
.IsEmpty()) {
495 delete mDecodedFrames
.Pop();
497 oggplay_close(mPlayer
);
500 OggPlayErrorCode
nsOggDecodeStateMachine::DecodeFrame()
502 NS_ASSERTION(mState
> DECODER_STATE_DECODING_METADATA
, "DecodeFrame() called during invalid state");
503 return oggplay_step_decoding(mPlayer
);
506 nsOggDecodeStateMachine::FrameData
* nsOggDecodeStateMachine::NextFrame()
508 NS_ASSERTION(mState
> DECODER_STATE_DECODING_METADATA
, "NextFrame() called during invalid state");
509 OggPlayCallbackInfo
** info
= oggplay_buffer_retrieve_next(mPlayer
);
513 FrameData
* frame
= new FrameData();
518 frame
->mTime
= mLastFrameTime
;
519 mLastFrameTime
+= mCallbackPeriod
;
520 int num_tracks
= oggplay_get_num_tracks(mPlayer
);
521 float audioTime
= 0.0;
522 float videoTime
= 0.0;
524 if (mVideoTrack
!= -1 &&
525 num_tracks
> mVideoTrack
&&
526 oggplay_callback_info_get_type(info
[mVideoTrack
]) == OGGPLAY_YUV_VIDEO
) {
527 OggPlayDataHeader
** headers
= oggplay_callback_info_get_headers(info
[mVideoTrack
]);
528 videoTime
= ((float)oggplay_callback_info_get_presentation_time(headers
[0]))/1000.0;
529 HandleVideoData(frame
, mVideoTrack
, oggplay_callback_info_get_video_data(headers
[0]));
532 if (mAudioTrack
!= -1 &&
533 num_tracks
> mAudioTrack
&&
534 oggplay_callback_info_get_type(info
[mAudioTrack
]) == OGGPLAY_FLOATS_AUDIO
) {
535 OggPlayDataHeader
** headers
= oggplay_callback_info_get_headers(info
[mAudioTrack
]);
536 audioTime
= ((float)oggplay_callback_info_get_presentation_time(headers
[0]))/1000.0;
537 int required
= oggplay_callback_info_get_required(info
[mAudioTrack
]);
538 for (int j
= 0; j
< required
; ++j
) {
539 int size
= oggplay_callback_info_get_record_size(headers
[j
]);
540 OggPlayAudioData
* audio_data
= oggplay_callback_info_get_audio_data(headers
[j
]);
541 HandleAudioData(frame
, audio_data
, size
);
545 // Pick one stream to act as the reference track to indicate if the
546 // stream has ended, seeked, etc.
547 if (mVideoTrack
>= 0 )
548 frame
->mState
= oggplay_callback_info_get_stream_info(info
[mVideoTrack
]);
549 else if (mAudioTrack
>= 0)
550 frame
->mState
= oggplay_callback_info_get_stream_info(info
[mAudioTrack
]);
552 frame
->mState
= OGGPLAY_STREAM_UNINITIALISED
;
554 frame
->mDecodedFrameTime
= mVideoTrack
== -1 ? audioTime
: videoTime
;
556 oggplay_buffer_release(mPlayer
, info
);
560 void nsOggDecodeStateMachine::HandleVideoData(FrameData
* aFrame
, int aTrackNum
, OggPlayVideoData
* aVideoData
) {
566 oggplay_get_video_y_size(mPlayer
, aTrackNum
, &y_width
, &y_height
);
569 oggplay_get_video_uv_size(mPlayer
, aTrackNum
, &uv_width
, &uv_height
);
571 if (y_width
>= MAX_VIDEO_WIDTH
|| y_height
>= MAX_VIDEO_HEIGHT
) {
575 aFrame
->mVideoWidth
= y_width
;
576 aFrame
->mVideoHeight
= y_height
;
577 aFrame
->mVideoData
= new unsigned char[y_width
* y_height
* 4];
578 if (!aFrame
->mVideoData
) {
582 OggPlayYUVChannels yuv
;
583 OggPlayRGBChannels rgb
;
585 yuv
.ptry
= aVideoData
->y
;
586 yuv
.ptru
= aVideoData
->u
;
587 yuv
.ptrv
= aVideoData
->v
;
588 yuv
.uv_width
= uv_width
;
589 yuv
.uv_height
= uv_height
;
590 yuv
.y_width
= y_width
;
591 yuv
.y_height
= y_height
;
593 rgb
.ptro
= aFrame
->mVideoData
;
594 rgb
.rgb_width
= aFrame
->mVideoWidth
;
595 rgb
.rgb_height
= aFrame
->mVideoHeight
;
597 oggplay_yuv2bgr(&yuv
, &rgb
);
600 void nsOggDecodeStateMachine::HandleAudioData(FrameData
* aFrame
, OggPlayAudioData
* aAudioData
, int aSize
) {
601 // 'aSize' is number of samples. Multiply by number of channels to
602 // get the actual number of floats being sent.
603 int size
= aSize
* mAudioChannels
;
605 aFrame
->mAudioData
.AppendElements(reinterpret_cast<float*>(aAudioData
), size
);
608 void nsOggDecodeStateMachine::PlayFrame() {
609 // Play a frame of video and/or audio data.
610 // If we are playing we open the audio stream if needed
611 // If there are decoded frames in the queue a single frame
612 // is popped off and played if it is time for that frame
614 // If it is not time yet to display the frame, we either
615 // continue decoding frames, or wait until it is time for
616 // the frame to display if the queue is full.
618 // If the decode state is not PLAYING then we just exit
619 // so we can continue decoding frames. If the queue is
620 // full we wait for a state change.
621 nsAutoMonitor
mon(mDecoder
->GetMonitor());
623 if (mDecoder
->GetState() == nsOggDecoder::PLAY_STATE_PLAYING
) {
628 if (!mDecodedFrames
.IsEmpty()) {
629 FrameData
* frame
= mDecodedFrames
.Peek();
630 if (frame
->mState
== OGGPLAY_STREAM_JUST_SEEKED
) {
631 // After returning from a seek all mTime members of
632 // FrameData start again from a time position of 0.
633 // Reset the play start time.
634 mPlayStartTime
= PR_IntervalNow();
638 double time
= (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime
-mPauseDuration
)/1000.0);
639 if (time
>= frame
->mTime
) {
640 // Audio for the current frame is played, but video for the next frame
641 // is displayed, to account for lag from the time the audio is written
642 // to when it is played. This will go away when we move to a/v sync
643 // using the audio hardware clock.
645 mDecodedFrames
.Pop();
646 PlayVideo(mDecodedFrames
.IsEmpty() ? frame
: mDecodedFrames
.Peek());
647 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
651 // If the queue of decoded frame is full then we wait for the
652 // approximate time until the next frame.
653 if (mDecodedFrames
.IsFull()) {
654 mon
.Wait(PR_MillisecondsToInterval(PRInt64((frame
->mTime
- time
)*1000)));
655 if (mState
== DECODER_STATE_SHUTDOWN
) {
667 if (mDecodedFrames
.IsFull() && mState
== DECODER_STATE_DECODING
) {
669 if (mState
== DECODER_STATE_SHUTDOWN
) {
676 void nsOggDecodeStateMachine::PlayVideo(FrameData
* aFrame
)
678 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayVideo() called without acquiring decoder monitor");
680 if (aFrame
->mVideoData
) {
681 nsAutoLock
lock(mDecoder
->mVideoUpdateLock
);
683 mDecoder
->SetRGBData(aFrame
->mVideoWidth
, aFrame
->mVideoHeight
, mFramerate
, aFrame
->mVideoData
);
688 void nsOggDecodeStateMachine::PlayAudio(FrameData
* aFrame
)
690 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayAudio() called without acquiring decoder monitor");
694 aFrame
->Write(mAudioStream
);
697 void nsOggDecodeStateMachine::OpenAudioStream()
699 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "OpenAudioStream() called without acquiring decoder monitor");
700 mAudioStream
= new nsAudioStream();
702 LOG(PR_LOG_ERROR
, ("Could not create audio stream"));
705 mAudioStream
->Init(mAudioChannels
, mAudioRate
, nsAudioStream::FORMAT_FLOAT32_LE
);
706 mAudioStream
->SetVolume(mVolume
);
710 void nsOggDecodeStateMachine::CloseAudioStream()
712 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "CloseAudioStream() called without acquiring decoder monitor");
714 mAudioStream
->Shutdown();
715 mAudioStream
= nsnull
;
719 void nsOggDecodeStateMachine::StartAudio()
721 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartAudio() called without acquiring decoder monitor");
727 void nsOggDecodeStateMachine::StopAudio()
729 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopAudio() called without acquiring decoder monitor");
735 void nsOggDecodeStateMachine::StartPlayback()
737 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartPlayback() called without acquiring decoder monitor");
741 // If this is the very first play, then set the initial start time
742 if (mPlayStartTime
== 0) {
743 mPlayStartTime
= PR_IntervalNow();
746 // If we have been paused previously, then compute duration spent paused
747 if (mPauseStartTime
!= 0) {
748 mPauseDuration
+= PR_IntervalNow() - mPauseStartTime
;
752 void nsOggDecodeStateMachine::StopPlayback()
754 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopPlayback() called without acquiring decoder monitor");
757 mPauseStartTime
= PR_IntervalNow();
760 void nsOggDecodeStateMachine::UpdatePlaybackPosition(float aTime
)
762 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "UpdatePlaybackPosition() called without acquiring decoder monitor");
763 mCurrentFrameTime
= aTime
;
764 if (!mPositionChangeQueued
) {
765 mPositionChangeQueued
= PR_TRUE
;
766 nsCOMPtr
<nsIRunnable
> event
=
767 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, PlaybackPositionChanged
);
768 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
772 void nsOggDecodeStateMachine::ClearPositionChangeFlag()
774 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "ClearPositionChangeFlag() called without acquiring decoder monitor");
775 mPositionChangeQueued
= PR_FALSE
;
778 float nsOggDecodeStateMachine::GetVolume()
780 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetVolume() called without acquiring decoder monitor");
784 void nsOggDecodeStateMachine::SetVolume(float volume
)
786 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetVolume() called without acquiring decoder monitor");
788 mAudioStream
->SetVolume(volume
);
794 float nsOggDecodeStateMachine::GetCurrentTime()
796 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetCurrentTime() called without acquiring decoder monitor");
797 return mCurrentFrameTime
;
800 PRInt64
nsOggDecodeStateMachine::GetDuration()
802 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "GetDuration() called without acquiring decoder monitor");
806 void nsOggDecodeStateMachine::SetContentLength(PRInt64 aLength
)
808 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetContentLength() called without acquiring decoder monitor");
809 mContentLength
= aLength
;
812 void nsOggDecodeStateMachine::SetSeekable(PRBool aSeekable
)
814 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetSeekable() called without acquiring decoder monitor");
815 mSeekable
= aSeekable
;
818 void nsOggDecodeStateMachine::Shutdown()
820 // oggplay_prepare_for_close cannot be undone. Once called, the
821 // mPlayer object cannot decode any more frames. Once we've entered
822 // the shutdown state here there's no going back.
823 nsAutoMonitor
mon(mDecoder
->GetMonitor());
825 oggplay_prepare_for_close(mPlayer
);
827 mState
= DECODER_STATE_SHUTDOWN
;
831 void nsOggDecodeStateMachine::Decode()
833 // When asked to decode, switch to decoding only if
834 // we are currently buffering.
835 nsAutoMonitor
mon(mDecoder
->GetMonitor());
836 if (mState
== DECODER_STATE_BUFFERING
) {
837 mState
= DECODER_STATE_DECODING
;
841 void nsOggDecodeStateMachine::Seek(float aTime
)
843 nsAutoMonitor
mon(mDecoder
->GetMonitor());
845 mState
= DECODER_STATE_SEEKING
;
848 nsresult
nsOggDecodeStateMachine::Run()
850 nsChannelReader
* reader
= mDecoder
->GetReader();
851 NS_ENSURE_TRUE(reader
, NS_ERROR_NULL_POINTER
);
853 nsAutoMonitor
mon(mDecoder
->GetMonitor());
855 case DECODER_STATE_SHUTDOWN
:
858 case DECODER_STATE_DECODING_METADATA
:
863 if (mState
== DECODER_STATE_DECODING_METADATA
) {
864 mState
= DECODER_STATE_DECODING_FIRSTFRAME
;
868 case DECODER_STATE_DECODING_FIRSTFRAME
:
875 } while (mState
!= DECODER_STATE_SHUTDOWN
&& r
== E_OGGPLAY_TIMEOUT
);
877 if (mState
== DECODER_STATE_SHUTDOWN
)
881 FrameData
* frame
= NextFrame();
883 mDecodedFrames
.Push(frame
);
884 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
888 nsCOMPtr
<nsIRunnable
> event
=
889 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, FirstFrameLoaded
);
890 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
892 if (mState
== DECODER_STATE_DECODING_FIRSTFRAME
) {
893 mState
= DECODER_STATE_DECODING
;
898 case DECODER_STATE_DECODING
:
900 // Before decoding check if we should buffer more data
901 if (reader
->DownloadRate() >= 0 &&
902 reader
->Available() < reader
->PlaybackRate() * BUFFERING_SECONDS_LOW_WATER_MARK
) {
903 if (mDecoder
->GetState() == nsOggDecoder::PLAY_STATE_PLAYING
) {
909 mBufferingStart
= PR_IntervalNow();
910 mBufferingBytes
= PRUint32(BUFFERING_RATE(reader
->PlaybackRate()) * BUFFERING_WAIT
);
911 mState
= DECODER_STATE_BUFFERING
;
913 nsCOMPtr
<nsIRunnable
> event
=
914 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, BufferingStarted
);
915 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
918 if (!mDecodedFrames
.IsFull()) {
920 OggPlayErrorCode r
= DecodeFrame();
923 if (mState
!= DECODER_STATE_DECODING
)
926 // Get the decoded frame and store it in our queue of decoded frames
927 FrameData
* frame
= NextFrame();
929 mDecodedFrames
.Push(frame
);
932 if (r
!= E_OGGPLAY_CONTINUE
&&
933 r
!= E_OGGPLAY_USER_INTERRUPT
&&
934 r
!= E_OGGPLAY_TIMEOUT
) {
935 mState
= DECODER_STATE_COMPLETED
;
939 // Show at least the first frame if we're not playing
940 // so we have a poster frame on initial load and after seek.
941 if (!mPlaying
&& !mDecodedFrames
.IsEmpty()) {
942 PlayVideo(mDecodedFrames
.Peek());
950 case DECODER_STATE_SEEKING
:
952 // During the seek, don't have a lock on the decoder state,
953 // otherwise long seek operations can block the main thread.
954 // The events dispatched to the main thread are SYNC calls.
955 // These calls are made outside of the decode monitor lock so
956 // it is safe for the main thread to makes calls that acquire
957 // the lock since it won't deadlock. We check the state when
958 // acquiring the lock again in case shutdown has occurred
959 // during the time when we didn't have the lock.
960 float seekTime
= mSeekTime
;
962 nsCOMPtr
<nsIRunnable
> startEvent
=
963 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, SeekingStarted
);
964 NS_DispatchToMainThread(startEvent
, NS_DISPATCH_SYNC
);
966 oggplay_seek(mPlayer
, ogg_int64_t(seekTime
* 1000));
968 // Reactivate all tracks. Liboggplay deactivates tracks when it
969 // reads to the end of stream, but they must be reactivated in order
970 // to start reading from them again.
971 for (int i
= 0; i
< oggplay_get_num_tracks(mPlayer
); ++i
) {
972 if (oggplay_set_track_active(mPlayer
, i
) < 0) {
973 LOG(PR_LOG_ERROR
, ("Could not set track %d active", i
));
978 if (mState
== DECODER_STATE_SHUTDOWN
)
981 // Remove all frames decoded prior to seek from the queue
982 while (!mDecodedFrames
.IsEmpty()) {
983 delete mDecodedFrames
.Pop();
991 } while (mState
!= DECODER_STATE_SHUTDOWN
&& r
== E_OGGPLAY_TIMEOUT
);
993 if (mState
== DECODER_STATE_SHUTDOWN
)
997 FrameData
* frame
= NextFrame();
998 NS_ASSERTION(frame
!= nsnull
, "No frame after seek!");
1000 mDecodedFrames
.Push(frame
);
1001 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
1005 nsCOMPtr
<nsIRunnable
> stopEvent
=
1006 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, SeekingStopped
);
1007 NS_DispatchToMainThread(stopEvent
, NS_DISPATCH_SYNC
);
1010 if (mState
== DECODER_STATE_SEEKING
&& mSeekTime
== seekTime
) {
1011 mState
= DECODER_STATE_DECODING
;
1016 case DECODER_STATE_BUFFERING
:
1017 if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart
) < BUFFERING_WAIT
*1000) &&
1018 reader
->DownloadRate() >= 0 &&
1019 reader
->Available() < mBufferingBytes
) {
1021 ("Buffering data until %d bytes available or %d milliseconds",
1022 mBufferingBytes
- reader
->Available(),
1023 BUFFERING_WAIT
*1000 - (PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart
))));
1024 mon
.Wait(PR_MillisecondsToInterval(1000));
1025 if (mState
== DECODER_STATE_SHUTDOWN
)
1029 mState
= DECODER_STATE_DECODING
;
1032 if (mState
!= DECODER_STATE_BUFFERING
) {
1033 nsCOMPtr
<nsIRunnable
> event
=
1034 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, BufferingStopped
);
1035 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
1036 if (mDecoder
->GetState() == nsOggDecoder::PLAY_STATE_PLAYING
) {
1045 case DECODER_STATE_COMPLETED
:
1047 while (mState
== DECODER_STATE_COMPLETED
&&
1048 !mDecodedFrames
.IsEmpty()) {
1050 if (mState
!= DECODER_STATE_SHUTDOWN
) {
1051 // Wait for the time of one frame so we don't tight loop
1052 // and we need to release the monitor so timeupdate and
1053 // invalidate's on the main thread can occur.
1054 mon
.Wait(PR_MillisecondsToInterval(PRInt64(mCallbackPeriod
*1000)));
1058 if (mState
!= DECODER_STATE_COMPLETED
)
1063 mAudioStream
->Drain();
1065 if (mState
!= DECODER_STATE_COMPLETED
)
1069 nsCOMPtr
<nsIRunnable
> event
=
1070 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, PlaybackEnded
);
1071 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
1074 } while (mState
== DECODER_STATE_COMPLETED
);
1083 void nsOggDecodeStateMachine::LoadOggHeaders()
1085 LOG(PR_LOG_DEBUG
, ("Loading Ogg Headers"));
1086 mPlayer
= oggplay_open_with_reader(mDecoder
->GetReader());
1088 LOG(PR_LOG_DEBUG
, ("There are %d tracks", oggplay_get_num_tracks(mPlayer
)));
1090 for (int i
= 0; i
< oggplay_get_num_tracks(mPlayer
); ++i
) {
1091 LOG(PR_LOG_DEBUG
, ("Tracks %d: %s", i
, oggplay_get_track_typename(mPlayer
, i
)));
1092 if (mVideoTrack
== -1 && oggplay_get_track_type(mPlayer
, i
) == OGGZ_CONTENT_THEORA
) {
1093 oggplay_set_callback_num_frames(mPlayer
, i
, 1);
1096 oggplay_get_video_fps(mPlayer
, i
, &fpsd
, &fpsn
);
1097 mFramerate
= fpsd
== 0 ? 0.0 : float(fpsn
)/float(fpsd
);
1098 mCallbackPeriod
= 1.0 / mFramerate
;
1099 LOG(PR_LOG_DEBUG
, ("Frame rate: %f", mFramerate
));
1101 else if (mAudioTrack
== -1 && oggplay_get_track_type(mPlayer
, i
) == OGGZ_CONTENT_VORBIS
) {
1103 oggplay_set_offset(mPlayer
, i
, OGGPLAY_AUDIO_OFFSET
);
1104 oggplay_get_audio_samplerate(mPlayer
, i
, &mAudioRate
);
1105 oggplay_get_audio_channels(mPlayer
, i
, &mAudioChannels
);
1106 LOG(PR_LOG_DEBUG
, ("samplerate: %d, channels: %d", mAudioRate
, mAudioChannels
));
1109 if (oggplay_set_track_active(mPlayer
, i
) < 0) {
1110 LOG(PR_LOG_ERROR
, ("Could not set track %d active", i
));
1114 if (mVideoTrack
== -1) {
1115 oggplay_set_callback_num_frames(mPlayer
, mAudioTrack
, OGGPLAY_FRAMES_PER_CALLBACK
);
1116 mCallbackPeriod
= 1.0 / (float(mAudioRate
) / OGGPLAY_FRAMES_PER_CALLBACK
);
1118 LOG(PR_LOG_DEBUG
, ("Callback Period: %f", mCallbackPeriod
));
1120 oggplay_use_buffer(mPlayer
, OGGPLAY_BUFFER_SIZE
);
1122 // Get the duration from the Ogg file. We only do this if the
1123 // content length of the resource is known as we need to seek
1124 // to the end of the file to get the last time field. We also
1125 // only do this if the resource is seekable.
1127 nsAutoMonitor
mon(mDecoder
->GetMonitor());
1128 if (mState
!= DECODER_STATE_SHUTDOWN
&&
1129 mContentLength
>= 0 &&
1131 // Don't hold the monitor during the duration
1132 // call as it can issue seek requests
1133 // and blocks until these are completed.
1135 PRInt64 d
= oggplay_get_duration(mPlayer
);
1139 if (mState
== DECODER_STATE_SHUTDOWN
)
1143 // Inform the element that we've loaded the Ogg metadata
1144 nsCOMPtr
<nsIRunnable
> metadataLoadedEvent
=
1145 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, MetadataLoaded
);
1147 NS_DispatchToMainThread(metadataLoadedEvent
, NS_DISPATCH_NORMAL
);
1151 NS_IMPL_THREADSAFE_ISUPPORTS1(nsOggDecoder
, nsIObserver
)
1153 void nsOggDecoder::Pause()
1155 nsAutoMonitor
mon(mMonitor
);
1156 if (mPlayState
== PLAY_STATE_SEEKING
) {
1157 mNextState
= PLAY_STATE_PAUSED
;
1161 ChangeState(PLAY_STATE_PAUSED
);
1164 float nsOggDecoder::GetVolume()
1166 nsAutoMonitor
mon(mMonitor
);
1167 return mDecodeStateMachine
? mDecodeStateMachine
->GetVolume() : mInitialVolume
;
1170 void nsOggDecoder::SetVolume(float volume
)
1172 nsAutoMonitor
mon(mMonitor
);
1173 mInitialVolume
= volume
;
1175 if (mDecodeStateMachine
) {
1176 mDecodeStateMachine
->SetVolume(volume
);
1180 float nsOggDecoder::GetDuration()
1182 if (mDuration
>= 0) {
1183 return static_cast<float>(mDuration
) / 1000.0;
1186 return std::numeric_limits
<float>::quiet_NaN();
1189 nsOggDecoder::nsOggDecoder() :
1191 mBytesDownloaded(0),
1193 mInitialVolume(0.0),
1194 mRequestedSeekTime(-1.0),
1196 mNotifyOnShutdown(PR_FALSE
),
1200 mPlayState(PLAY_STATE_PAUSED
),
1201 mNextState(PLAY_STATE_PAUSED
)
1203 MOZ_COUNT_CTOR(nsOggDecoder
);
1206 PRBool
nsOggDecoder::Init()
1208 mMonitor
= nsAutoMonitor::NewMonitor("media.decoder");
1209 return mMonitor
&& nsMediaDecoder::Init();
1212 void nsOggDecoder::Shutdown()
1214 mShuttingDown
= PR_TRUE
;
1216 ChangeState(PLAY_STATE_SHUTDOWN
);
1217 nsMediaDecoder::Shutdown();
1222 nsOggDecoder::~nsOggDecoder()
1224 MOZ_COUNT_DTOR(nsOggDecoder
);
1225 nsAutoMonitor::DestroyMonitor(mMonitor
);
1228 nsresult
nsOggDecoder::Load(nsIURI
* aURI
, nsIChannel
* aChannel
,
1229 nsIStreamListener
** aStreamListener
)
1231 // Reset Stop guard flag flag, else shutdown won't occur properly when
1233 mStopping
= PR_FALSE
;
1235 NS_ASSERTION(!mReader
, "Didn't shutdown properly!");
1236 NS_ASSERTION(!mDecodeStateMachine
, "Didn't shutdown properly!");
1237 NS_ASSERTION(!mDecodeThread
, "Didn't shutdown properly!");
1239 if (aStreamListener
) {
1240 *aStreamListener
= nsnull
;
1244 NS_ASSERTION(!aStreamListener
, "No listener should be requested here");
1247 NS_ASSERTION(aChannel
, "Either a URI or a channel is required");
1248 NS_ASSERTION(aStreamListener
, "A listener should be requested here");
1250 // If the channel was redirected, we want the post-redirect URI;
1251 // but if the URI scheme was expanded, say from chrome: to jar:file:,
1252 // we want the original URI.
1253 nsresult rv
= NS_GetFinalChannelURI(aChannel
, getter_AddRefs(mURI
));
1254 NS_ENSURE_SUCCESS(rv
, rv
);
1259 RegisterShutdownObserver();
1261 mReader
= new nsChannelReader();
1262 NS_ENSURE_TRUE(mReader
, NS_ERROR_OUT_OF_MEMORY
);
1264 nsresult rv
= mReader
->Init(this, mURI
, aChannel
, aStreamListener
);
1265 NS_ENSURE_SUCCESS(rv
, rv
);
1267 rv
= NS_NewThread(getter_AddRefs(mDecodeThread
));
1268 NS_ENSURE_SUCCESS(rv
, rv
);
1270 mDecodeStateMachine
= new nsOggDecodeStateMachine(this);
1272 nsAutoMonitor
mon(mMonitor
);
1273 mDecodeStateMachine
->SetContentLength(mContentLength
);
1274 mDecodeStateMachine
->SetSeekable(mSeekable
);
1277 ChangeState(PLAY_STATE_LOADING
);
1279 return mDecodeThread
->Dispatch(mDecodeStateMachine
, NS_DISPATCH_NORMAL
);
1282 nsresult
nsOggDecoder::Play()
1284 nsAutoMonitor
mon(mMonitor
);
1285 if (mPlayState
== PLAY_STATE_SEEKING
) {
1286 mNextState
= PLAY_STATE_PLAYING
;
1290 ChangeState(PLAY_STATE_PLAYING
);
1295 nsresult
nsOggDecoder::Seek(float aTime
)
1297 nsAutoMonitor
mon(mMonitor
);
1300 return NS_ERROR_FAILURE
;
1302 mRequestedSeekTime
= aTime
;
1304 // If we are already in the seeking state, then setting mRequestedSeekTime
1305 // above will result in the new seek occurring when the current seek
1307 if (mPlayState
!= PLAY_STATE_SEEKING
) {
1308 mNextState
= mPlayState
;
1309 ChangeState(PLAY_STATE_SEEKING
);
1315 nsresult
nsOggDecoder::PlaybackRateChanged()
1317 return NS_ERROR_NOT_IMPLEMENTED
;
1320 // Postpones destruction of nsOggDecoder's objects, so they can be safely
1321 // performed later, when events can't interfere.
1322 class nsDestroyStateMachine
: public nsRunnable
{
1324 nsDestroyStateMachine(nsOggDecoder
*aDecoder
,
1325 nsOggDecodeStateMachine
*aMachine
,
1326 nsChannelReader
*aReader
,
1328 : mDecoder(aDecoder
),
1329 mDecodeStateMachine(aMachine
),
1331 mDecodeThread(aThread
)
1336 NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
1337 // The decode thread must die before the state machine can die.
1338 // The state machine must die before the reader.
1339 // The state machine must die before the decoder.
1341 mDecodeThread
->Shutdown();
1342 mDecodeThread
= nsnull
;
1343 mDecodeStateMachine
= nsnull
;
1350 nsRefPtr
<nsOggDecoder
> mDecoder
;
1351 nsCOMPtr
<nsOggDecodeStateMachine
> mDecodeStateMachine
;
1352 nsAutoPtr
<nsChannelReader
> mReader
;
1353 nsCOMPtr
<nsIThread
> mDecodeThread
;
1356 void nsOggDecoder::Stop()
1358 NS_ASSERTION(NS_IsMainThread(),
1359 "nsOggDecoder::Stop called on non-main thread");
1364 mStopping
= PR_TRUE
;
1366 ChangeState(PLAY_STATE_ENDED
);
1370 // Force any outstanding seek and byterange requests to complete
1371 // to prevent shutdown from deadlocking.
1376 // Shutdown must be on called the mDecodeStateMachine before deleting.
1377 // This is required to ensure that the state machine isn't running
1378 // in the thread and using internal objects when it is deleted.
1379 if (mDecodeStateMachine
) {
1380 mDecodeStateMachine
->Shutdown();
1383 // mDecodeThread holds a ref to mDecodeStateMachine, so we can't destroy
1384 // mDecodeStateMachine until mDecodeThread is destroyed. We can't destroy
1385 // mReader until mDecodeStateMachine is destroyed because mDecodeStateMachine
1386 // uses mReader in its destructor. In addition, it's unsafe to Shutdown() the
1387 // decode thread here, as nsIThread::Shutdown() may run events, such as JS
1388 // event handlers, which could kick off a new Load().
1389 // mDecodeStateMachine::Run() may also be holding a reference to the decoder
1390 // in an event runner object on its stack, so the decoder must outlive the
1391 // state machine, else we may destroy the decoder on a non-main thread,
1392 // and its monitor doesn't like that. So we need to create a new event which
1393 // holds references the decoder, reader, thread, and state machine, and
1394 // releases them safely later on the main thread when events can't interfere.
1396 nsCOMPtr
<nsIRunnable
> event
= new nsDestroyStateMachine(this,
1397 mDecodeStateMachine
,
1400 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
1402 // Null data fields. They can be reinitialized in future Load()s safely now.
1403 mDecodeThread
= nsnull
;
1404 mDecodeStateMachine
= nsnull
;
1405 UnregisterShutdownObserver();
1408 float nsOggDecoder::GetCurrentTime()
1410 return mCurrentTime
;
1413 void nsOggDecoder::GetCurrentURI(nsIURI
** aURI
)
1415 NS_IF_ADDREF(*aURI
= mURI
);
1418 nsIPrincipal
* nsOggDecoder::GetCurrentPrincipal()
1424 return mReader
->GetCurrentPrincipal();
1427 void nsOggDecoder::MetadataLoaded()
1432 // Only inform the element of MetadataLoaded if not doing a load() in order
1433 // to fulfill a seek, otherwise we'll get multiple metadataloaded events.
1434 PRBool notifyElement
= PR_TRUE
;
1436 nsAutoMonitor
mon(mMonitor
);
1437 mDuration
= mDecodeStateMachine
? mDecodeStateMachine
->GetDuration() : -1;
1438 notifyElement
= mNextState
!= PLAY_STATE_SEEKING
;
1441 if (mElement
&& notifyElement
) {
1442 mElement
->MetadataLoaded();
1446 void nsOggDecoder::FirstFrameLoaded()
1451 // Only inform the element of FirstFrameLoaded if not doing a load() in order
1452 // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
1453 PRBool notifyElement
= PR_TRUE
;
1455 nsAutoMonitor
mon(mMonitor
);
1456 notifyElement
= mNextState
!= PLAY_STATE_SEEKING
;
1459 if (mElement
&& notifyElement
) {
1460 mElement
->FirstFrameLoaded();
1463 // The element can run javascript via events
1464 // before reaching here, so only change the
1465 // state if we're still set to the original
1467 nsAutoMonitor
mon(mMonitor
);
1468 if (mPlayState
== PLAY_STATE_LOADING
) {
1469 if (mRequestedSeekTime
>= 0.0) {
1470 ChangeState(PLAY_STATE_SEEKING
);
1473 ChangeState(mNextState
);
1478 void nsOggDecoder::ResourceLoaded()
1484 mElement
->ResourceLoaded();
1489 void nsOggDecoder::NetworkError()
1495 mElement
->NetworkError();
1499 PRBool
nsOggDecoder::IsSeeking() const
1501 return mPlayState
== PLAY_STATE_SEEKING
|| mNextState
== PLAY_STATE_SEEKING
;
1504 PRBool
nsOggDecoder::IsEnded() const
1506 return mPlayState
== PLAY_STATE_ENDED
|| mPlayState
== PLAY_STATE_SHUTDOWN
;
1509 void nsOggDecoder::PlaybackEnded()
1511 if (mShuttingDown
|| mPlayState
== nsOggDecoder::PLAY_STATE_SEEKING
)
1516 mElement
->PlaybackEnded();
1520 NS_IMETHODIMP
nsOggDecoder::Observe(nsISupports
*aSubjet
,
1522 const PRUnichar
*someData
)
1524 if (strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
1531 PRUint64
nsOggDecoder::GetBytesLoaded()
1533 return mBytesDownloaded
;
1536 PRInt64
nsOggDecoder::GetTotalBytes()
1538 return mContentLength
;
1541 void nsOggDecoder::SetTotalBytes(PRInt64 aBytes
)
1543 mContentLength
= aBytes
;
1544 if (mDecodeStateMachine
) {
1545 nsAutoMonitor
mon(mMonitor
);
1546 mDecodeStateMachine
->SetContentLength(aBytes
);
1550 void nsOggDecoder::UpdateBytesDownloaded(PRUint64 aBytes
)
1552 mBytesDownloaded
= aBytes
;
1555 void nsOggDecoder::BufferingStopped()
1561 mElement
->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA
);
1565 void nsOggDecoder::BufferingStarted()
1571 mElement
->ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA
);
1575 void nsOggDecoder::SeekingStopped()
1581 nsAutoMonitor
mon(mMonitor
);
1583 // An additional seek was requested while the current seek was
1585 if (mRequestedSeekTime
>= 0.0)
1586 ChangeState(PLAY_STATE_SEEKING
);
1588 ChangeState(mNextState
);
1592 mElement
->SeekCompleted();
1596 void nsOggDecoder::SeekingStarted()
1602 mElement
->SeekStarted();
1606 void nsOggDecoder::RegisterShutdownObserver()
1608 if (!mNotifyOnShutdown
) {
1609 nsCOMPtr
<nsIObserverService
> observerService
=
1610 do_GetService("@mozilla.org/observer-service;1");
1611 if (observerService
) {
1613 NS_SUCCEEDED(observerService
->AddObserver(this,
1614 NS_XPCOM_SHUTDOWN_OBSERVER_ID
,
1618 NS_WARNING("Could not get an observer service. Video decoding events may not shutdown cleanly.");
1623 void nsOggDecoder::UnregisterShutdownObserver()
1625 if (mNotifyOnShutdown
) {
1626 nsCOMPtr
<nsIObserverService
> observerService
=
1627 do_GetService("@mozilla.org/observer-service;1");
1628 if (observerService
) {
1629 mNotifyOnShutdown
= PR_FALSE
;
1630 observerService
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
1635 void nsOggDecoder::ChangeState(PlayState aState
)
1637 NS_ASSERTION(NS_IsMainThread(),
1638 "nsOggDecoder::ChangeState called on non-main thread");
1639 nsAutoMonitor
mon(mMonitor
);
1641 if (mNextState
== aState
) {
1642 mNextState
= PLAY_STATE_PAUSED
;
1645 if (mPlayState
== PLAY_STATE_SHUTDOWN
) {
1650 if (mPlayState
== PLAY_STATE_ENDED
&&
1651 aState
!= PLAY_STATE_SHUTDOWN
) {
1652 // If we've completed playback then the decode and display threads
1653 // have been shutdown. To honor the state change request we need
1654 // to reload the resource and restart the threads.
1655 // Like seeking, this will require opening a new channel, which means
1656 // we may not actually get the same resource --- a server may send
1657 // us something different.
1658 mNextState
= aState
;
1659 mPlayState
= PLAY_STATE_LOADING
;
1660 Load(mURI
, nsnull
, nsnull
);
1664 mPlayState
= aState
;
1666 case PLAY_STATE_PAUSED
:
1667 /* No action needed */
1669 case PLAY_STATE_PLAYING
:
1670 mDecodeStateMachine
->Decode();
1672 case PLAY_STATE_SEEKING
:
1673 mDecodeStateMachine
->Seek(mRequestedSeekTime
);
1674 mRequestedSeekTime
= -1.0;
1676 case PLAY_STATE_LOADING
:
1677 /* No action needed */
1679 case PLAY_STATE_START
:
1680 /* No action needed */
1682 case PLAY_STATE_ENDED
:
1683 /* No action needed */
1685 case PLAY_STATE_SHUTDOWN
:
1686 /* No action needed */
1692 void nsOggDecoder::PlaybackPositionChanged()
1697 float lastTime
= mCurrentTime
;
1699 // Control the scope of the monitor so it is not
1700 // held while the timeupdate and the invalidate is run.
1702 nsAutoMonitor
mon(mMonitor
);
1704 if (mDecodeStateMachine
) {
1705 mCurrentTime
= mDecodeStateMachine
->GetCurrentTime();
1706 mDecodeStateMachine
->ClearPositionChangeFlag();
1710 // Invalidate the frame so any video data is displayed.
1711 // Do this before the timeupdate event so that if that
1712 // event runs JavaScript that queries the media size, the
1713 // frame has reflowed and the size updated beforehand.
1716 if (mElement
&& lastTime
!= mCurrentTime
) {
1717 mElement
->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
1721 void nsOggDecoder::SetSeekable(PRBool aSeekable
)
1723 mSeekable
= aSeekable
;
1724 if (mDecodeStateMachine
) {
1725 nsAutoMonitor
mon(mMonitor
);
1726 mDecodeStateMachine
->SetSeekable(aSeekable
);
1730 PRBool
nsOggDecoder::GetSeekable()