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 ***** */
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"
52 #include "nsNetUtil.h"
53 #include "nsOggDecoder.h"
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
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
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
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)
121 See nsOggDecoder.h for more details.
123 class nsOggDecodeStateMachine
: public nsRunnable
126 // Object to hold the decoded data from a frame
132 mDecodedFrameTime(0.0),
135 MOZ_COUNT_CTOR(FrameData
);
140 MOZ_COUNT_DTOR(FrameData
);
144 nsAutoArrayPtr
<unsigned char> mVideoData
;
145 nsTArray
<float> mAudioData
;
148 float mDecodedFrameTime
;
150 OggPlayStreamInfo mState
;
153 // A queue of decoded video frames.
164 void Push(FrameData
* frame
)
166 NS_ASSERTION(!IsFull(), "FrameQueue is full");
167 mQueue
[mTail
] = frame
;
168 mTail
= (mTail
+1) % OGGPLAY_BUFFER_SIZE
;
174 NS_ASSERTION(!mEmpty
, "FrameQueue is empty");
176 return mQueue
[mHead
];
181 NS_ASSERTION(!mEmpty
, "FrameQueue is empty");
183 FrameData
* result
= mQueue
[mHead
];
184 mHead
= (mHead
+ 1) % OGGPLAY_BUFFER_SIZE
;
185 mEmpty
= mHead
== mTail
;
196 return !mEmpty
&& mHead
== mTail
;
200 FrameData
* mQueue
[OGGPLAY_BUFFER_SIZE
];
206 // Enumeration for the valid states
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.
225 void Seek(float aTime
);
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.
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.
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();
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
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.
304 // Stop playback of audio, either by closing or pausing the audio
305 // stream. Must be called with the decode monitor held.
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.
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
);
323 // The follow fields are only accessed by the decoder thread
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.
335 // Frame data containing decoded video/audio for the frame the
336 // current frame and the previous frame. Accessed only via the
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
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
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.
373 // Audio data. These are initially set when the metadata is loaded.
374 // They are only accessed from the decoder thread.
376 PRInt32 mAudioChannels
;
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
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
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
;
400 // The follow fields are accessed by the decoder thread or
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.
409 // Position to seek to when the seek state transition occurs. The
410 // decoder monitor lock must be obtained before reading or writing
414 // The audio stream resource. Used on the decode thread and the
415 // main thread (Via the Get/SetVolume calls). Synchronisation via
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
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
) :
444 mCallbackPeriod(1.0),
454 mState(DECODER_STATE_DECODING_METADATA
),
456 mCurrentFrameTime(0.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
);
485 FrameData
* frame
= new FrameData();
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
]);
524 frame
->mState
= OGGPLAY_STREAM_UNINITIALISED
;
526 frame
->mDecodedFrameTime
= mVideoTrack
== -1 ? audioTime
: videoTime
;
528 oggplay_buffer_release(mPlayer
, info
);
532 void nsOggDecodeStateMachine::HandleVideoData(FrameData
* aFrame
, int aTrackNum
, OggPlayVideoData
* aVideoData
) {
538 oggplay_get_video_y_size(mPlayer
, aTrackNum
, &y_width
, &y_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
) {
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
) {
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
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
) {
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();
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());
619 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
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
) {
639 if (mDecodedFrames
.IsFull() && mState
== DECODER_STATE_DECODING
) {
641 if (mState
== DECODER_STATE_SHUTDOWN
) {
648 void nsOggDecodeStateMachine::PlayVideo(FrameData
* aFrame
)
650 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayVideo() called without acquiring decoder monitor");
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())
667 mAudioStream
->Write(aFrame
->mAudioData
.Elements(), aFrame
->mAudioData
.Length());
673 void nsOggDecodeStateMachine::OpenAudioStream()
675 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "OpenAudioStream() called without acquiring decoder monitor");
676 mAudioStream
= new nsAudioStream();
678 LOG(PR_LOG_ERROR
, ("Could not create audio stream"));
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");
690 mAudioStream
->Shutdown();
691 mAudioStream
= nsnull
;
695 void nsOggDecodeStateMachine::StartAudio()
697 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartAudio() called without acquiring decoder monitor");
703 void nsOggDecodeStateMachine::StopAudio()
705 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StopAudio() called without acquiring decoder monitor");
711 void nsOggDecodeStateMachine::StartPlayback()
713 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "StartPlayback() called without acquiring decoder monitor");
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");
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");
760 void nsOggDecodeStateMachine::SetVolume(float volume
)
762 // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "SetVolume() called without acquiring decoder monitor");
764 mAudioStream
->SetVolume(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());
784 oggplay_prepare_for_close(mPlayer
);
786 mState
= DECODER_STATE_SHUTDOWN
;
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());
804 mState
= DECODER_STATE_SEEKING
;
807 nsresult
nsOggDecodeStateMachine::Run()
810 nsAutoMonitor
mon(mDecoder
->GetMonitor());
812 case DECODER_STATE_SHUTDOWN
:
815 case DECODER_STATE_DECODING_METADATA
:
820 if (mState
== DECODER_STATE_DECODING_METADATA
) {
821 mState
= DECODER_STATE_DECODING_FIRSTFRAME
;
825 case DECODER_STATE_DECODING_FIRSTFRAME
:
832 } while (mState
!= DECODER_STATE_SHUTDOWN
&& r
== E_OGGPLAY_TIMEOUT
);
834 if (mState
== DECODER_STATE_SHUTDOWN
)
838 FrameData
* frame
= NextFrame();
840 mDecodedFrames
.Push(frame
);
841 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
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
;
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
) {
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
);
875 if (!mDecodedFrames
.IsFull()) {
877 OggPlayErrorCode r
= DecodeFrame();
880 if (mState
== DECODER_STATE_SHUTDOWN
)
883 // Get the decoded frame and store it in our queue of decoded frames
884 FrameData
* frame
= NextFrame();
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());
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
;
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));
926 if (mState
== DECODER_STATE_SHUTDOWN
)
929 // Remove all frames decoded prior to seek from the queue
930 while (!mDecodedFrames
.IsEmpty()) {
931 delete mDecodedFrames
.Pop();
939 } while (mState
!= DECODER_STATE_SHUTDOWN
&& r
== E_OGGPLAY_TIMEOUT
);
941 if (mState
== DECODER_STATE_SHUTDOWN
)
945 FrameData
* frame
= NextFrame();
947 mDecodedFrames
.Push(frame
);
948 UpdatePlaybackPosition(frame
->mDecodedFrameTime
);
952 nsCOMPtr
<nsIRunnable
> stopEvent
=
953 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, SeekingStopped
);
954 NS_DispatchToMainThread(stopEvent
, NS_DISPATCH_SYNC
);
957 if (mState
== DECODER_STATE_SEEKING
&& mSeekTime
== seekTime
) {
958 mState
= DECODER_STATE_DECODING
;
963 case DECODER_STATE_BUFFERING
:
964 if ((PR_IntervalToMilliseconds(PR_IntervalNow() - mBufferingStart
) < BUFFERING_WAIT
*1000) &&
965 mReader
->DownloadRate() >= 0 &&
966 mReader
->Available() < mBufferingBytes
) {
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
)
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
) {
992 case DECODER_STATE_COMPLETED
:
994 while (mState
!= DECODER_STATE_SHUTDOWN
&&
995 !mDecodedFrames
.IsEmpty()) {
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
)
1008 nsCOMPtr
<nsIRunnable
> event
=
1009 NS_NEW_RUNNABLE_METHOD(nsOggDecoder
, mDecoder
, PlaybackEnded
);
1010 NS_DispatchToMainThread(event
, NS_DISPATCH_NORMAL
);
1013 } while (mState
!= DECODER_STATE_SHUTDOWN
);
1022 void nsOggDecodeStateMachine::LoadOggHeaders()
1024 LOG(PR_LOG_DEBUG
, ("Loading Ogg Headers"));
1025 mPlayer
= oggplay_open_with_reader(mReader
);
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);
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
) {
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
;
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
1106 nsOggDecoder::nsOggDecoder() :
1108 mBytesDownloaded(0),
1110 mInitialVolume(0.0),
1111 mRequestedSeekTime(-1.0),
1113 mNotifyOnShutdown(PR_FALSE
),
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
1138 nsOggDecoderShutdown(nsOggDecoder
* aDecoder
) :
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
;
1177 NS_ASSERTION(!aStreamListener
, "No listener should be requested here");
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
);
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
;
1218 ChangeState(PLAY_STATE_PLAYING
);
1223 nsresult
nsOggDecoder::Seek(float aTime
)
1225 nsAutoMonitor
mon(mMonitor
);
1228 return NS_ERROR_FAILURE
;
1230 if (mPlayState
== PLAY_STATE_LOADING
&& aTime
== 0.0) {
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
1239 if (mPlayState
!= PLAY_STATE_SEEKING
) {
1240 mNextState
= mPlayState
;
1241 ChangeState(PLAY_STATE_SEEKING
);
1247 nsresult
nsOggDecoder::PlaybackRateChanged()
1249 return NS_ERROR_NOT_IMPLEMENTED
;
1252 void nsOggDecoder::Stop()
1254 ChangeState(PLAY_STATE_ENDED
);
1258 // Force any outstanding seek and byterange requests to complete
1259 // to prevent shutdown from deadlocking.
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()
1300 return mReader
->GetCurrentPrincipal();
1303 void nsOggDecoder::MetadataLoaded()
1306 mElement
->MetadataLoaded();
1310 void nsOggDecoder::FirstFrameLoaded()
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
1320 nsAutoMonitor
mon(mMonitor
);
1321 if (mPlayState
== PLAY_STATE_LOADING
) {
1322 if (mRequestedSeekTime
>= 0.0) {
1323 ChangeState(PLAY_STATE_SEEKING
);
1326 ChangeState(mNextState
);
1331 void nsOggDecoder::ResourceLoaded()
1334 mElement
->ResourceLoaded();
1339 void nsOggDecoder::NetworkError()
1342 mElement
->NetworkError();
1346 PRBool
nsOggDecoder::IsSeeking() const
1348 return mPlayState
== PLAY_STATE_SEEKING
;
1351 void nsOggDecoder::PlaybackEnded()
1355 mElement
->PlaybackEnded();
1359 NS_IMETHODIMP
nsOggDecoder::Observe(nsISupports
*aSubjet
,
1361 const PRUnichar
*someData
)
1363 if (strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
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()
1393 mElement
->ChangeReadyState(nsIDOMHTMLMediaElement::CAN_SHOW_CURRENT_FRAME
);
1397 void nsOggDecoder::BufferingStarted()
1400 mElement
->ChangeReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE
);
1404 void nsOggDecoder::SeekingStopped()
1407 nsAutoMonitor
mon(mMonitor
);
1408 if (mPlayState
== PLAY_STATE_SHUTDOWN
)
1411 // An additional seek was requested while the current seek was
1413 if (mRequestedSeekTime
>= 0.0)
1414 ChangeState(PLAY_STATE_SEEKING
);
1416 ChangeState(mNextState
);
1420 mElement
->SeekCompleted();
1424 void nsOggDecoder::SeekingStarted()
1427 nsAutoMonitor
mon(mMonitor
);
1428 if (mPlayState
== PLAY_STATE_SHUTDOWN
)
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
) {
1444 NS_SUCCEEDED(observerService
->AddObserver(this,
1445 NS_XPCOM_SHUTDOWN_OBSERVER_ID
,
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
) {
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
);
1493 mPlayState
= aState
;
1495 case PLAY_STATE_PAUSED
:
1496 /* No action needed */
1498 case PLAY_STATE_PLAYING
:
1499 mDecodeStateMachine
->Decode();
1501 case PLAY_STATE_SEEKING
:
1502 mDecodeStateMachine
->Seek(mRequestedSeekTime
);
1503 mRequestedSeekTime
= -1.0;
1505 case PLAY_STATE_LOADING
:
1506 /* No action needed */
1508 case PLAY_STATE_START
:
1509 /* No action needed */
1511 case PLAY_STATE_ENDED
:
1512 /* No action needed */
1514 case PLAY_STATE_SHUTDOWN
:
1515 /* No action needed */
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
)
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.
1546 if (mElement
&& lastTime
!= mCurrentTime
) {
1547 mElement
->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));