[JAEGER] Cache atom in PIC directly, instead of index, for simplicity.
[mozilla-central.git] / content / media / nsBuiltinDecoderStateMachine.cpp
blob679adbb94fba4d0a90deadbe50f6417fbaaa1c4b
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>
24 * Chris Pearce <chris@pearce.org.nz>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include <limits>
41 #include "nsAudioStream.h"
42 #include "nsTArray.h"
43 #include "nsBuiltinDecoder.h"
44 #include "nsBuiltinDecoderReader.h"
45 #include "nsBuiltinDecoderStateMachine.h"
46 #include "mozilla/mozalloc.h"
47 #include "VideoUtils.h"
49 using namespace mozilla;
50 using namespace mozilla::layers;
52 #ifdef PR_LOGGING
53 extern PRLogModuleInfo* gBuiltinDecoderLog;
54 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
55 #else
56 #define LOG(type, msg)
57 #endif
59 // Wait this number of seconds when buffering, then leave and play
60 // as best as we can if the required amount of data hasn't been
61 // retrieved.
62 #define BUFFERING_WAIT 30
64 // The amount of data to retrieve during buffering is computed based
65 // on the download rate. BUFFERING_MIN_RATE is the minimum download
66 // rate to be used in that calculation to help avoid constant buffering
67 // attempts at a time when the average download rate has not stabilised.
68 #define BUFFERING_MIN_RATE 50000
69 #define BUFFERING_RATE(x) ((x)< BUFFERING_MIN_RATE ? BUFFERING_MIN_RATE : (x))
71 // If audio queue has less than this many ms of decoded audio, we won't risk
72 // trying to decode the video, we'll skip decoding video up to the next
73 // keyframe.
75 // Also if the decode catches up with the end of the downloaded data,
76 // we'll only go into BUFFERING state if we've got audio and have queued
77 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
78 // less than LOW_VIDEO_FRAMES frames.
79 static const PRUint32 LOW_AUDIO_MS = 100;
81 // If more than this many ms of decoded audio is queued, we'll hold off
82 // decoding more audio.
83 const unsigned AMPLE_AUDIO_MS = 2000;
85 // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
86 // we're not "pumping video", we'll skip the video up to the next keyframe
87 // which is at or after the current playback position.
89 // Also if the decode catches up with the end of the downloaded data,
90 // we'll only go into BUFFERING state if we've got audio and have queued
91 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
92 // less than LOW_VIDEO_FRAMES frames.
93 static const PRUint32 LOW_VIDEO_FRAMES = 1;
95 nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder,
96 nsBuiltinDecoderReader* aReader) :
97 mDecoder(aDecoder),
98 mState(DECODER_STATE_DECODING_METADATA),
99 mAudioMonitor("media.audiostream"),
100 mCbCrSize(0),
101 mPlayDuration(0),
102 mBufferingEndOffset(0),
103 mStartTime(-1),
104 mEndTime(-1),
105 mSeekTime(0),
106 mReader(aReader),
107 mCurrentFrameTime(0),
108 mAudioStartTime(-1),
109 mAudioEndTime(-1),
110 mVideoFrameTime(-1),
111 mVolume(1.0),
112 mSeekable(PR_TRUE),
113 mPositionChangeQueued(PR_FALSE),
114 mAudioCompleted(PR_FALSE),
115 mBufferExhausted(PR_FALSE),
116 mGotDurationFromHeader(PR_FALSE),
117 mStopDecodeThreads(PR_TRUE)
119 MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
122 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
124 MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
127 PRBool nsBuiltinDecoderStateMachine::HasFutureAudio() const {
128 mDecoder->GetMonitor().AssertCurrentThreadIn();
129 return HasAudio() &&
130 !mAudioCompleted &&
131 (mReader->mAudioQueue.GetSize() > 0 ||
132 mAudioEndTime - mCurrentFrameTime + mStartTime > LOW_AUDIO_MS);
135 PRBool nsBuiltinDecoderStateMachine::HaveNextFrameData() const {
136 return ((!HasAudio() || mReader->mAudioQueue.AtEndOfStream()) &&
137 mReader->mVideoQueue.GetSize() > 0) ||
138 HasFutureAudio();
141 void nsBuiltinDecoderStateMachine::DecodeLoop()
143 NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
144 PRBool videoPlaying = PR_FALSE;
145 PRBool audioPlaying = PR_FALSE;
147 MonitorAutoEnter mon(mDecoder->GetMonitor());
148 videoPlaying = HasVideo();
149 audioPlaying = HasAudio();
152 // We want to "pump" the decode until we've got a few frames/samples decoded
153 // before we consider whether decode is falling behind.
154 PRBool audioPump = PR_TRUE;
155 PRBool videoPump = PR_TRUE;
157 // If the video decode is falling behind the audio, we'll start dropping the
158 // inter-frames up until the next keyframe which is at or before the current
159 // playback position. skipToNextKeyframe is PR_TRUE if we're currently
160 // skipping up to the next keyframe.
161 PRBool skipToNextKeyframe = PR_FALSE;
163 // Once we've decoded more than videoPumpThreshold video frames, we'll
164 // no longer be considered to be "pumping video".
165 const unsigned videoPumpThreshold = 5;
167 // If we've got more than videoWaitThreshold decoded video frames waiting in
168 // the video queue, we will not decode any more video frames until they've
169 // been consumed by the play state machine thread.
170 const unsigned videoWaitThreshold = 10;
172 // After the audio decode fills with more than audioPumpThresholdMs ms
173 // of decoded audio, we'll start to check whether the audio or video decode
174 // is falling behind.
175 const unsigned audioPumpThresholdMs = 250;
177 // Main decode loop.
178 while (videoPlaying || audioPlaying) {
179 PRBool audioWait = !audioPlaying;
180 PRBool videoWait = !videoPlaying;
182 // Wait for more data to download if we've exhausted all our
183 // buffered data.
184 MonitorAutoEnter mon(mDecoder->GetMonitor());
185 while (!mStopDecodeThreads &&
186 mBufferExhausted &&
187 mState != DECODER_STATE_SHUTDOWN)
189 mon.Wait();
191 if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads)
192 break;
195 PRUint32 videoQueueSize = mReader->mVideoQueue.GetSize();
196 // Don't decode any more frames if we've filled our buffers.
197 // Limits memory consumption.
198 if (videoQueueSize > videoWaitThreshold) {
199 videoWait = PR_TRUE;
202 // We don't want to consider skipping to the next keyframe if we've
203 // only just started up the decode loop, so wait until we've decoded
204 // some frames before allowing the keyframe skip.
205 if (videoPump && videoQueueSize >= videoPumpThreshold) {
206 videoPump = PR_FALSE;
208 if (!videoPump &&
209 videoPlaying &&
210 videoQueueSize < LOW_VIDEO_FRAMES)
212 skipToNextKeyframe = PR_TRUE;
215 // Determine how much audio data is decoded ahead of the current playback
216 // position.
217 int audioQueueSize = mReader->mAudioQueue.GetSize();
218 PRInt64 initialDownloadPosition = 0;
219 PRInt64 currentTime = 0;
220 PRInt64 audioDecoded = 0;
222 MonitorAutoEnter mon(mDecoder->GetMonitor());
223 currentTime = mCurrentFrameTime + mStartTime;
224 audioDecoded = mReader->mAudioQueue.Duration() +
225 mAudioEndTime - currentTime;
226 initialDownloadPosition =
227 mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
230 // Don't decode any audio if the audio decode is way ahead.
231 if (audioDecoded > AMPLE_AUDIO_MS) {
232 audioWait = PR_TRUE;
234 if (audioPump && audioDecoded > audioPumpThresholdMs) {
235 audioPump = PR_FALSE;
237 if (!audioPump && audioPlaying && audioDecoded < LOW_AUDIO_MS) {
238 skipToNextKeyframe = PR_TRUE;
241 if (videoPlaying && !videoWait) {
242 videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
244 MonitorAutoEnter mon(mDecoder->GetMonitor());
245 if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
246 mBufferExhausted = PR_TRUE;
251 MonitorAutoEnter mon(mDecoder->GetMonitor());
252 initialDownloadPosition =
253 mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition);
254 mDecoder->GetMonitor().NotifyAll();
257 if (audioPlaying && !audioWait) {
258 audioPlaying = mReader->DecodeAudioData();
260 MonitorAutoEnter mon(mDecoder->GetMonitor());
261 if (mDecoder->mDecoderPosition >= initialDownloadPosition) {
262 mBufferExhausted = PR_TRUE;
268 MonitorAutoEnter mon(mDecoder->GetMonitor());
270 if (!IsPlaying() &&
271 (!audioWait || !videoWait) &&
272 (videoQueueSize < 2 || audioQueueSize < 2))
274 // Transitioning from 0 to 1 frames or from 1 to 2 frames could
275 // affect HaveNextFrameData and hence what UpdateReadyStateForData does.
276 // This could change us from HAVE_CURRENT_DATA to HAVE_FUTURE_DATA
277 // (or even HAVE_ENOUGH_DATA), so we'd better trigger an
278 // update to the ready state. We only need to do this if we're
279 // not playing; if we're playing the playback code will post an update
280 // whenever it advances a frame.
281 UpdateReadyState();
284 if (mState == DECODER_STATE_SHUTDOWN || mStopDecodeThreads) {
285 break;
287 if ((!HasAudio() || (audioWait && audioPlaying)) &&
288 (!HasVideo() || (videoWait && videoPlaying)))
290 // All active bitstreams' decode is well ahead of the playback
291 // position, we may as well wait have for the playback to catch up.
292 mon.Wait();
298 MonitorAutoEnter mon(mDecoder->GetMonitor());
299 if (!mStopDecodeThreads &&
300 mState != DECODER_STATE_SHUTDOWN &&
301 mState != DECODER_STATE_SEEKING)
303 mState = DECODER_STATE_COMPLETED;
304 mDecoder->GetMonitor().NotifyAll();
307 LOG(PR_LOG_DEBUG, ("Shutting down DecodeLoop this=%p", this));
310 PRBool nsBuiltinDecoderStateMachine::IsPlaying()
312 mDecoder->GetMonitor().AssertCurrentThreadIn();
314 return !mPlayStartTime.IsNull();
317 void nsBuiltinDecoderStateMachine::AudioLoop()
319 NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
320 LOG(PR_LOG_DEBUG, ("Begun audio thread/loop"));
322 MonitorAutoEnter mon(mDecoder->GetMonitor());
323 mAudioCompleted = PR_FALSE;
325 PRInt64 audioStartTime = -1;
326 while (1) {
328 // Wait while we're not playing, and we're not shutting down, or we're
329 // playing and we've got no audio to play.
331 MonitorAutoEnter mon(mDecoder->GetMonitor());
332 NS_ASSERTION(mState != DECODER_STATE_DECODING_METADATA,
333 "Should have meta data before audio started playing.");
334 while (mState != DECODER_STATE_SHUTDOWN &&
335 !mStopDecodeThreads &&
336 (!IsPlaying() ||
337 mState == DECODER_STATE_BUFFERING ||
338 (mReader->mAudioQueue.GetSize() == 0 &&
339 !mReader->mAudioQueue.AtEndOfStream())))
341 mon.Wait();
344 // If we're shutting down, break out and exit the audio thread.
345 if (mState == DECODER_STATE_SHUTDOWN ||
346 mStopDecodeThreads ||
347 mReader->mAudioQueue.AtEndOfStream())
349 break;
353 NS_ASSERTION(mReader->mAudioQueue.GetSize() > 0,
354 "Should have data to play");
355 nsAutoPtr<SoundData> sound(mReader->mAudioQueue.PopFront());
357 MonitorAutoEnter mon(mDecoder->GetMonitor());
358 NS_WARN_IF_FALSE(IsPlaying(), "Should be playing");
359 // Awaken the decode loop if it's waiting for space to free up in the
360 // audio queue.
361 mDecoder->GetMonitor().NotifyAll();
364 if (audioStartTime == -1) {
365 // Remember the presentation time of the first audio sample we play.
366 // We add this to the position/played duration of the audio stream to
367 // determine the audio clock time. Used for A/V sync.
368 MonitorAutoEnter mon(mDecoder->GetMonitor());
369 mAudioStartTime = audioStartTime = sound->mTime;
370 LOG(PR_LOG_DEBUG, ("First audio sample has timestamp %lldms", mAudioStartTime));
373 PRInt64 audioEndTime = -1;
375 MonitorAutoEnter audioMon(mAudioMonitor);
376 if (mAudioStream) {
377 // The state machine could have paused since we've released the decoder
378 // monitor and acquired the audio monitor. Rather than acquire both
379 // monitors, the audio stream also maintains whether its paused or not.
380 // This prevents us from doing a blocking write while holding the audio
381 // monitor while paused; we would block, and the state machine won't be
382 // able to acquire the audio monitor in order to resume or destroy the
383 // audio stream.
384 if (!mAudioStream->IsPaused()) {
385 mAudioStream->Write(sound->mAudioData,
386 sound->AudioDataLength(),
387 PR_TRUE);
388 audioEndTime = sound->mTime + sound->mDuration;
389 mDecoder->UpdatePlaybackOffset(sound->mOffset);
390 } else {
391 mReader->mAudioQueue.PushFront(sound);
392 sound.forget();
396 sound = nsnull;
399 MonitorAutoEnter mon(mDecoder->GetMonitor());
400 if (audioEndTime != -1) {
401 mAudioEndTime = audioEndTime;
403 PRInt64 audioAhead = mAudioEndTime - mCurrentFrameTime - mStartTime;
404 if (audioAhead > AMPLE_AUDIO_MS) {
405 // We've pushed enough audio onto the hardware that we've queued up a
406 // significant amount ahead of the playback position. The decode
407 // thread will be going to sleep, so we won't get any new samples
408 // anyway, so sleep until we need to push to the hardware again.
409 Wait(AMPLE_AUDIO_MS / 2);
410 // Kick the decode thread; since above we only do a NotifyAll when
411 // we pop an audio chunk of the queue, the decoder won't wake up if
412 // we've got no more decoded chunks to push to the hardware. We can
413 // hit this condition if the last sample in the stream doesn't have
414 // it's EOS flag set, and the decode thread sleeps just after decoding
415 // that packet, but before realising there's no more packets.
416 mon.NotifyAll();
420 if (mReader->mAudioQueue.AtEndOfStream() &&
421 mState != DECODER_STATE_SHUTDOWN &&
422 !mStopDecodeThreads)
424 // Last sample pushed to audio hardware, wait for the audio to finish,
425 // before the audio thread terminates.
426 MonitorAutoEnter audioMon(mAudioMonitor);
427 if (mAudioStream) {
428 mAudioStream->Drain();
430 LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
433 MonitorAutoEnter mon(mDecoder->GetMonitor());
434 mAudioCompleted = PR_TRUE;
435 UpdateReadyState();
436 // Kick the decode and state machine threads; they may be sleeping waiting
437 // for this to finish.
438 mDecoder->GetMonitor().NotifyAll();
440 LOG(PR_LOG_DEBUG, ("Audio stream finished playing, audio thread exit"));
443 nsresult nsBuiltinDecoderStateMachine::Init()
445 return mReader->Init();
448 void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
450 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
451 "Should be on state machine thread.");
452 mDecoder->GetMonitor().AssertCurrentThreadIn();
454 // Reset mPlayStartTime before we pause/shutdown the nsAudioStream. This is
455 // so that if the audio loop is about to write audio, it will have the chance
456 // to check to see if we're paused and not write the audio. If not, the
457 // audio thread can block in the write, and we deadlock trying to acquire
458 // the audio monitor upon resume playback.
459 if (IsPlaying()) {
460 mPlayDuration += TimeStamp::Now() - mPlayStartTime;
461 mPlayStartTime = TimeStamp();
463 if (HasAudio()) {
464 MonitorAutoExit exitMon(mDecoder->GetMonitor());
465 MonitorAutoEnter audioMon(mAudioMonitor);
466 if (mAudioStream) {
467 if (aMode == AUDIO_PAUSE) {
468 mAudioStream->Pause();
469 } else if (aMode == AUDIO_SHUTDOWN) {
470 mAudioStream->Shutdown();
471 mAudioStream = nsnull;
477 void nsBuiltinDecoderStateMachine::StartPlayback()
479 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
480 "Should be on state machine thread.");
481 NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
482 mDecoder->GetMonitor().AssertCurrentThreadIn();
483 LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
484 if (HasAudio()) {
485 MonitorAutoExit exitMon(mDecoder->GetMonitor());
486 MonitorAutoEnter audioMon(mAudioMonitor);
487 if (mAudioStream) {
488 // We have an audiostream, so it must have been paused the last time
489 // StopPlayback() was called.
490 mAudioStream->Resume();
491 } else {
492 // No audiostream, create one.
493 const nsVideoInfo& info = mReader->GetInfo();
494 mAudioStream = new nsAudioStream();
495 mAudioStream->Init(info.mAudioChannels,
496 info.mAudioRate,
497 nsAudioStream::FORMAT_FLOAT32);
498 mAudioStream->SetVolume(mVolume);
501 mPlayStartTime = TimeStamp::Now();
502 mDecoder->GetMonitor().NotifyAll();
505 void nsBuiltinDecoderStateMachine::UpdatePlaybackPosition(PRInt64 aTime)
507 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
508 "Should be on state machine thread.");
509 mDecoder->GetMonitor().AssertCurrentThreadIn();
511 NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
512 mCurrentFrameTime = aTime - mStartTime;
513 NS_ASSERTION(mCurrentFrameTime >= 0, "CurrentTime should be positive!");
514 if (aTime > mEndTime) {
515 NS_ASSERTION(mCurrentFrameTime > GetDuration(),
516 "CurrentTime must be after duration if aTime > endTime!");
517 mEndTime = aTime;
518 nsCOMPtr<nsIRunnable> event =
519 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DurationChanged);
520 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
522 if (!mPositionChangeQueued) {
523 mPositionChangeQueued = PR_TRUE;
524 nsCOMPtr<nsIRunnable> event =
525 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackPositionChanged);
526 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
530 void nsBuiltinDecoderStateMachine::ClearPositionChangeFlag()
532 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
533 mDecoder->GetMonitor().AssertCurrentThreadIn();
535 mPositionChangeQueued = PR_FALSE;
538 nsHTMLMediaElement::NextFrameStatus nsBuiltinDecoderStateMachine::GetNextFrameStatus()
540 MonitorAutoEnter mon(mDecoder->GetMonitor());
541 if (IsBuffering() || IsSeeking()) {
542 return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
543 } else if (HaveNextFrameData()) {
544 return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
546 return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
549 void nsBuiltinDecoderStateMachine::SetVolume(float volume)
551 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
553 MonitorAutoEnter audioMon(mAudioMonitor);
554 if (mAudioStream) {
555 mAudioStream->SetVolume(volume);
559 MonitorAutoEnter mon(mDecoder->GetMonitor());
560 mVolume = volume;
564 float nsBuiltinDecoderStateMachine::GetCurrentTime()
566 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
567 mDecoder->GetMonitor().AssertCurrentThreadIn();
569 return (float)mCurrentFrameTime / 1000.0;
572 PRInt64 nsBuiltinDecoderStateMachine::GetDuration()
574 mDecoder->GetMonitor().AssertCurrentThreadIn();
576 if (mEndTime == -1 || mStartTime == -1)
577 return -1;
578 return mEndTime - mStartTime;
581 void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
583 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
584 mDecoder->GetMonitor().AssertCurrentThreadIn();
586 if (mStartTime != -1) {
587 mEndTime = mStartTime + aDuration;
588 } else {
589 mStartTime = 0;
590 mEndTime = aDuration;
594 void nsBuiltinDecoderStateMachine::SetSeekable(PRBool aSeekable)
596 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
597 mDecoder->GetMonitor().AssertCurrentThreadIn();
599 mSeekable = aSeekable;
602 void nsBuiltinDecoderStateMachine::Shutdown()
604 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
606 // Once we've entered the shutdown state here there's no going back.
607 MonitorAutoEnter mon(mDecoder->GetMonitor());
609 // Change state before issuing shutdown request to threads so those
610 // threads can start exiting cleanly during the Shutdown call.
611 LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder));
612 mState = DECODER_STATE_SHUTDOWN;
613 mDecoder->GetMonitor().NotifyAll();
616 void nsBuiltinDecoderStateMachine::Decode()
618 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
619 // When asked to decode, switch to decoding only if
620 // we are currently buffering.
621 MonitorAutoEnter mon(mDecoder->GetMonitor());
622 if (mState == DECODER_STATE_BUFFERING) {
623 LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
624 mState = DECODER_STATE_DECODING;
625 mDecoder->GetMonitor().NotifyAll();
629 void nsBuiltinDecoderStateMachine::ResetPlayback()
631 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
632 "Should be on state machine thread.");
633 mVideoFrameTime = -1;
634 mAudioStartTime = -1;
635 mAudioEndTime = -1;
636 mAudioCompleted = PR_FALSE;
639 void nsBuiltinDecoderStateMachine::Seek(float aTime)
641 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
642 MonitorAutoEnter mon(mDecoder->GetMonitor());
643 // nsBuiltinDecoder::mPlayState should be SEEKING while we seek, and
644 // in that case nsBuiltinDecoder shouldn't be calling us.
645 NS_ASSERTION(mState != DECODER_STATE_SEEKING,
646 "We shouldn't already be seeking");
647 NS_ASSERTION(mState >= DECODER_STATE_DECODING,
648 "We should have loaded metadata");
649 double t = aTime * 1000.0;
650 if (t > PR_INT64_MAX) {
651 // Prevent integer overflow.
652 return;
655 mSeekTime = static_cast<PRInt64>(t) + mStartTime;
656 NS_ASSERTION(mSeekTime >= mStartTime && mSeekTime <= mEndTime,
657 "Can only seek in range [0,duration]");
659 // Bound the seek time to be inside the media range.
660 NS_ASSERTION(mStartTime != -1, "Should know start time by now");
661 NS_ASSERTION(mEndTime != -1, "Should know end time by now");
662 mSeekTime = NS_MIN(mSeekTime, mEndTime);
663 mSeekTime = NS_MAX(mStartTime, mSeekTime);
664 LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
665 mState = DECODER_STATE_SEEKING;
668 void nsBuiltinDecoderStateMachine::StopDecodeThreads()
670 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
671 "Should be on state machine thread.");
672 mDecoder->GetMonitor().AssertCurrentThreadIn();
673 mStopDecodeThreads = PR_TRUE;
674 mDecoder->GetMonitor().NotifyAll();
675 if (mDecodeThread) {
677 MonitorAutoExit exitMon(mDecoder->GetMonitor());
678 mDecodeThread->Shutdown();
680 mDecodeThread = nsnull;
682 if (mAudioThread) {
684 MonitorAutoExit exitMon(mDecoder->GetMonitor());
685 mAudioThread->Shutdown();
687 mAudioThread = nsnull;
691 nsresult
692 nsBuiltinDecoderStateMachine::StartDecodeThreads()
694 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
695 "Should be on state machine thread.");
696 mDecoder->GetMonitor().AssertCurrentThreadIn();
697 mStopDecodeThreads = PR_FALSE;
698 if (!mDecodeThread && mState < DECODER_STATE_COMPLETED) {
699 nsresult rv = NS_NewThread(getter_AddRefs(mDecodeThread));
700 if (NS_FAILED(rv)) {
701 mState = DECODER_STATE_SHUTDOWN;
702 return rv;
704 nsCOMPtr<nsIRunnable> event =
705 NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::DecodeLoop);
706 mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
708 if (HasAudio() && !mAudioThread) {
709 nsresult rv = NS_NewThread(getter_AddRefs(mAudioThread));
710 if (NS_FAILED(rv)) {
711 mState = DECODER_STATE_SHUTDOWN;
712 return rv;
714 nsCOMPtr<nsIRunnable> event =
715 NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::AudioLoop);
716 mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
718 return NS_OK;
721 nsresult nsBuiltinDecoderStateMachine::Run()
723 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
724 "Should be on state machine thread.");
725 nsMediaStream* stream = mDecoder->GetCurrentStream();
726 NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
728 while (PR_TRUE) {
729 MonitorAutoEnter mon(mDecoder->GetMonitor());
730 switch (mState) {
731 case DECODER_STATE_SHUTDOWN:
732 if (IsPlaying()) {
733 StopPlayback(AUDIO_SHUTDOWN);
735 StopDecodeThreads();
736 NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
737 "How did we escape from the shutdown state???");
738 return NS_OK;
740 case DECODER_STATE_DECODING_METADATA:
742 LoadMetadata();
743 if (mState == DECODER_STATE_SHUTDOWN) {
744 continue;
747 VideoData* videoData = FindStartTime();
748 if (videoData) {
749 MonitorAutoExit exitMon(mDecoder->GetMonitor());
750 RenderVideoFrame(videoData);
753 // Start the decode threads, so that we can pre buffer the streams.
754 // and calculate the start time in order to determine the duration.
755 if (NS_FAILED(StartDecodeThreads())) {
756 continue;
759 NS_ASSERTION(mStartTime != -1, "Must have start time");
760 NS_ASSERTION((!HasVideo() && !HasAudio()) ||
761 !mSeekable || mEndTime != -1,
762 "Active seekable media should have end time");
763 NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
764 LOG(PR_LOG_DEBUG, ("%p Media goes from %lldms to %lldms (duration %lldms) seekable=%d",
765 mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
767 if (mState == DECODER_STATE_SHUTDOWN)
768 continue;
770 // Inform the element that we've loaded the metadata and the
771 // first frame.
772 nsCOMPtr<nsIRunnable> metadataLoadedEvent =
773 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::MetadataLoaded);
774 NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
776 if (mState == DECODER_STATE_DECODING_METADATA) {
777 LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
778 mState = DECODER_STATE_DECODING;
781 // Start playback.
782 if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
783 if (!IsPlaying()) {
784 StartPlayback();
788 break;
790 case DECODER_STATE_DECODING:
792 if (NS_FAILED(StartDecodeThreads())) {
793 continue;
796 AdvanceFrame();
798 if (mState != DECODER_STATE_DECODING)
799 continue;
801 if (mBufferExhausted &&
802 mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING &&
803 !mDecoder->GetCurrentStream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
804 !mDecoder->GetCurrentStream()->IsSuspendedByCache() &&
805 ((HasAudio() && mReader->mAudioQueue.Duration() < LOW_AUDIO_MS) ||
806 (HasVideo() && (PRUint32)mReader->mVideoQueue.GetSize() < LOW_VIDEO_FRAMES)))
808 // There is at most one frame in the queue and there's
809 // more data to load. Let's buffer to make sure we can play a
810 // decent amount of video in the future.
811 if (IsPlaying()) {
812 StopPlayback(AUDIO_PAUSE);
813 mDecoder->GetMonitor().NotifyAll();
816 // We need to tell the element that buffering has started.
817 // We can't just directly send an asynchronous runnable that
818 // eventually fires the "waiting" event. The problem is that
819 // there might be pending main-thread events, such as "data
820 // received" notifications, that mean we're not actually still
821 // buffering by the time this runnable executes. So instead
822 // we just trigger UpdateReadyStateForData; when it runs, it
823 // will check the current state and decide whether to tell
824 // the element we're buffering or not.
825 UpdateReadyState();
827 mBufferingStart = TimeStamp::Now();
828 PRPackedBool reliable;
829 double playbackRate = mDecoder->ComputePlaybackRate(&reliable);
830 mBufferingEndOffset = mDecoder->mDecoderPosition +
831 BUFFERING_RATE(playbackRate) * BUFFERING_WAIT;
832 mState = DECODER_STATE_BUFFERING;
833 LOG(PR_LOG_DEBUG, ("Changed state from DECODING to BUFFERING"));
834 } else {
835 if (mBufferExhausted) {
836 // This will wake up the decode thread and force it to try to
837 // decode video and audio. This guarantees we make progress.
838 mBufferExhausted = PR_FALSE;
839 mDecoder->GetMonitor().NotifyAll();
844 break;
846 case DECODER_STATE_SEEKING:
848 // During the seek, don't have a lock on the decoder state,
849 // otherwise long seek operations can block the main thread.
850 // The events dispatched to the main thread are SYNC calls.
851 // These calls are made outside of the decode monitor lock so
852 // it is safe for the main thread to makes calls that acquire
853 // the lock since it won't deadlock. We check the state when
854 // acquiring the lock again in case shutdown has occurred
855 // during the time when we didn't have the lock.
856 PRInt64 seekTime = mSeekTime;
857 mDecoder->StopProgressUpdates();
859 StopPlayback(AUDIO_SHUTDOWN);
860 StopDecodeThreads();
861 ResetPlayback();
863 // SeekingStarted will do a UpdateReadyStateForData which will
864 // inform the element and its users that we have no frames
865 // to display
867 MonitorAutoExit exitMon(mDecoder->GetMonitor());
868 nsCOMPtr<nsIRunnable> startEvent =
869 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStarted);
870 NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
872 if (mCurrentFrameTime != mSeekTime - mStartTime) {
873 nsresult res;
875 MonitorAutoExit exitMon(mDecoder->GetMonitor());
876 // Now perform the seek. We must not hold the state machine monitor
877 // while we seek, since the seek decodes.
878 res = mReader->Seek(seekTime, mStartTime, mEndTime);
880 if (NS_SUCCEEDED(res)){
881 SoundData* audio = HasAudio() ? mReader->mAudioQueue.PeekFront() : nsnull;
882 if (audio) {
883 mPlayDuration = TimeDuration::FromMilliseconds(audio->mTime);
885 if (HasVideo()) {
886 nsAutoPtr<VideoData> video(mReader->mVideoQueue.PeekFront());
887 if (video) {
888 RenderVideoFrame(video);
889 if (!audio) {
890 NS_ASSERTION(video->mTime <= seekTime &&
891 seekTime <= video->mTime + mReader->GetInfo().mCallbackPeriod,
892 "Seek target should lie inside the first frame after seek");
893 mPlayDuration = TimeDuration::FromMilliseconds(seekTime);
896 mReader->mVideoQueue.PopFront();
898 UpdatePlaybackPosition(seekTime);
901 mDecoder->StartProgressUpdates();
902 if (mState == DECODER_STATE_SHUTDOWN)
903 continue;
905 // Try to decode another frame to detect if we're at the end...
906 LOG(PR_LOG_DEBUG, ("Seek completed, mCurrentFrameTime=%lld\n", mCurrentFrameTime));
908 // Change state to DECODING or COMPLETED now. SeekingStopped will
909 // call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
910 // if we need to seek again.
912 nsCOMPtr<nsIRunnable> stopEvent;
913 if (mCurrentFrameTime == mEndTime) {
914 LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lldms) to COMPLETED",
915 mDecoder, seekTime));
916 stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
917 mState = DECODER_STATE_COMPLETED;
918 } else {
919 LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lldms) to DECODING",
920 mDecoder, seekTime));
921 stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
922 mState = DECODER_STATE_DECODING;
924 mBufferExhausted = PR_FALSE;
925 mDecoder->GetMonitor().NotifyAll();
928 MonitorAutoExit exitMon(mDecoder->GetMonitor());
929 NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
932 break;
934 case DECODER_STATE_BUFFERING:
936 TimeStamp now = TimeStamp::Now();
937 if (now - mBufferingStart < TimeDuration::FromSeconds(BUFFERING_WAIT) &&
938 mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition) < mBufferingEndOffset &&
939 !mDecoder->GetCurrentStream()->IsDataCachedToEndOfStream(mDecoder->mDecoderPosition) &&
940 !mDecoder->GetCurrentStream()->IsSuspendedByCache()) {
941 LOG(PR_LOG_DEBUG,
942 ("In buffering: buffering data until %d bytes available or %f seconds",
943 PRUint32(mBufferingEndOffset - mDecoder->GetCurrentStream()->GetCachedDataEnd(mDecoder->mDecoderPosition)),
944 BUFFERING_WAIT - (now - mBufferingStart).ToSeconds()));
945 Wait(1000);
946 if (mState == DECODER_STATE_SHUTDOWN)
947 continue;
948 } else {
949 LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
950 LOG(PR_LOG_DEBUG, ("%p Buffered for %lf seconds",
951 mDecoder,
952 (TimeStamp::Now() - mBufferingStart).ToSeconds()));
953 mState = DECODER_STATE_DECODING;
956 if (mState != DECODER_STATE_BUFFERING) {
957 mBufferExhausted = PR_FALSE;
958 // Notify to allow blocked decoder thread to continue
959 mDecoder->GetMonitor().NotifyAll();
960 UpdateReadyState();
961 if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
962 if (!IsPlaying()) {
963 StartPlayback();
968 break;
971 case DECODER_STATE_COMPLETED:
973 if (NS_FAILED(StartDecodeThreads())) {
974 continue;
977 // Play the remaining media. We want to run AdvanceFrame() at least
978 // once to ensure the current playback position is advanced to the
979 // end of the media, and so that we update the readyState.
980 do {
981 AdvanceFrame();
982 } while (mState == DECODER_STATE_COMPLETED &&
983 (mReader->mVideoQueue.GetSize() > 0 ||
984 (HasAudio() && !mAudioCompleted)));
986 if (mAudioStream) {
987 // Close the audop stream so that next time audio is used a new stream
988 // is created. The StopPlayback call also resets the IsPlaying() state
989 // so audio is restarted correctly.
990 StopPlayback(AUDIO_SHUTDOWN);
993 if (mState != DECODER_STATE_COMPLETED)
994 continue;
996 LOG(PR_LOG_DEBUG, ("Shutting down the state machine thread"));
997 StopDecodeThreads();
999 if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
1000 PRInt64 videoTime = HasVideo() ? (mVideoFrameTime + mReader->GetInfo().mCallbackPeriod) : 0;
1001 PRInt64 clockTime = NS_MAX(mEndTime, NS_MAX(videoTime, GetAudioClock()));
1002 UpdatePlaybackPosition(clockTime);
1004 MonitorAutoExit exitMon(mDecoder->GetMonitor());
1005 nsCOMPtr<nsIRunnable> event =
1006 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackEnded);
1007 NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
1011 while (mState == DECODER_STATE_COMPLETED) {
1012 mDecoder->GetMonitor().Wait();
1015 break;
1019 return NS_OK;
1022 void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData* aData)
1024 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
1026 if (aData->mDuplicate) {
1027 return;
1030 nsRefPtr<Image> image = aData->mImage;
1031 if (image) {
1032 const nsVideoInfo& info = mReader->GetInfo();
1033 mDecoder->SetVideoData(gfxIntSize(info.mPicture.width, info.mPicture.height), info.mPixelAspectRatio, image);
1037 PRInt64
1038 nsBuiltinDecoderStateMachine::GetAudioClock()
1040 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
1041 if (!mAudioStream || !HasAudio())
1042 return -1;
1043 PRInt64 t = mAudioStream->GetPosition();
1044 return (t == -1) ? -1 : t + mAudioStartTime;
1047 void nsBuiltinDecoderStateMachine::AdvanceFrame()
1049 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
1050 mDecoder->GetMonitor().AssertCurrentThreadIn();
1052 // When it's time to display a frame, decode the frame and display it.
1053 if (mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING) {
1054 if (!IsPlaying()) {
1055 StartPlayback();
1056 mDecoder->GetMonitor().NotifyAll();
1059 if (HasAudio() && mAudioStartTime == -1 && !mAudioCompleted) {
1060 // We've got audio (so we should sync off the audio clock), but we've
1061 // not played a sample on the audio thread, so we can't get a time
1062 // from the audio clock. Just wait and then return, to give the audio
1063 // clock time to tick.
1064 Wait(mReader->GetInfo().mCallbackPeriod);
1065 return;
1068 // Determine the clock time. If we've got audio, and we've not reached
1069 // the end of the audio, use the audio clock. However if we've finished
1070 // audio, or don't have audio, use the system clock.
1071 PRInt64 clock_time = -1;
1072 PRInt64 audio_time = GetAudioClock();
1073 if (HasAudio() && !mAudioCompleted && audio_time != -1) {
1074 clock_time = audio_time;
1075 // Resync against the audio clock, while we're trusting the
1076 // audio clock. This ensures no "drift", particularly on Linux.
1077 mPlayStartTime = TimeStamp::Now() - TimeDuration::FromMilliseconds(clock_time);
1078 } else {
1079 // Sound is disabled on this system. Sync to the system clock.
1080 TimeDuration t = TimeStamp::Now() - mPlayStartTime + mPlayDuration;
1081 clock_time = (PRInt64)(1000 * t.ToSeconds());
1082 // Ensure the clock can never go backwards.
1083 NS_ASSERTION(mCurrentFrameTime <= clock_time, "Clock should go forwards");
1084 clock_time = NS_MAX(mCurrentFrameTime, clock_time) + mStartTime;
1087 NS_ASSERTION(clock_time >= mStartTime, "Should have positive clock time.");
1088 nsAutoPtr<VideoData> videoData;
1089 if (mReader->mVideoQueue.GetSize() > 0) {
1090 VideoData* data = mReader->mVideoQueue.PeekFront();
1091 while (clock_time >= data->mTime) {
1092 mVideoFrameTime = data->mTime;
1093 videoData = data;
1094 mReader->mVideoQueue.PopFront();
1095 mDecoder->UpdatePlaybackOffset(data->mOffset);
1096 if (mReader->mVideoQueue.GetSize() == 0)
1097 break;
1098 data = mReader->mVideoQueue.PeekFront();
1102 if (videoData) {
1103 // Decode one frame and display it
1104 NS_ASSERTION(videoData->mTime >= mStartTime, "Should have positive frame time");
1106 MonitorAutoExit exitMon(mDecoder->GetMonitor());
1107 // If we have video, we want to increment the clock in steps of the frame
1108 // duration.
1109 RenderVideoFrame(videoData);
1111 mDecoder->GetMonitor().NotifyAll();
1112 videoData = nsnull;
1115 // Cap the current time to the larger of the audio and video end time.
1116 // This ensures that if we're running off the system clock, we don't
1117 // advance the clock to after the media end time.
1118 if (mVideoFrameTime != -1 || mAudioEndTime != -1) {
1119 // These will be non -1 if we've displayed a video frame, or played an audio sample.
1120 clock_time = NS_MIN(clock_time, NS_MAX(mVideoFrameTime, mAudioEndTime));
1121 if (clock_time - mStartTime > mCurrentFrameTime) {
1122 // Only update the playback position if the clock time is greater
1123 // than the previous playback position. The audio clock can
1124 // sometimes report a time less than its previously reported in
1125 // some situations, and we need to gracefully handle that.
1126 UpdatePlaybackPosition(clock_time);
1130 // If the number of audio/video samples queued has changed, either by
1131 // this function popping and playing a video sample, or by the audio
1132 // thread popping and playing an audio sample, we may need to update our
1133 // ready state. Post an update to do so.
1134 UpdateReadyState();
1136 Wait(mReader->GetInfo().mCallbackPeriod);
1137 } else {
1138 if (IsPlaying()) {
1139 StopPlayback(AUDIO_PAUSE);
1140 mDecoder->GetMonitor().NotifyAll();
1143 if (mState == DECODER_STATE_DECODING ||
1144 mState == DECODER_STATE_COMPLETED) {
1145 mDecoder->GetMonitor().Wait();
1150 void nsBuiltinDecoderStateMachine::Wait(PRUint32 aMs) {
1151 mDecoder->GetMonitor().AssertCurrentThreadIn();
1152 TimeStamp end = TimeStamp::Now() + TimeDuration::FromMilliseconds(aMs);
1153 TimeStamp now;
1154 while ((now = TimeStamp::Now()) < end &&
1155 mState != DECODER_STATE_SHUTDOWN &&
1156 mState != DECODER_STATE_SEEKING)
1158 TimeDuration d = end - now;
1159 PRInt64 ms = d.ToSeconds() * 1000;
1160 if (ms == 0) {
1161 break;
1163 NS_ASSERTION(ms <= aMs && ms > 0,
1164 "nsBuiltinDecoderStateMachine::Wait interval very wrong!");
1165 mDecoder->GetMonitor().Wait(PR_MillisecondsToInterval(ms));
1169 VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
1171 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
1172 mDecoder->GetMonitor().AssertCurrentThreadIn();
1173 PRInt64 startTime = 0;
1174 mStartTime = 0;
1175 VideoData* v = nsnull;
1177 MonitorAutoExit exitMon(mDecoder->GetMonitor());
1178 v = mReader->FindStartTime(mReader->GetInfo().mDataOffset, startTime);
1180 if (startTime != 0) {
1181 mStartTime = startTime;
1182 if (mGotDurationFromHeader) {
1183 NS_ASSERTION(mEndTime != -1,
1184 "We should have mEndTime as supplied duration here");
1185 // We were specified a duration from a Content-Duration HTTP header.
1186 // Adjust mEndTime so that mEndTime-mStartTime matches the specified
1187 // duration.
1188 mEndTime = mStartTime + mEndTime;
1191 LOG(PR_LOG_DEBUG, ("%p Media start time is %lldms", mDecoder, mStartTime));
1192 return v;
1195 void nsBuiltinDecoderStateMachine::FindEndTime()
1197 NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
1198 mDecoder->GetMonitor().AssertCurrentThreadIn();
1200 nsMediaStream* stream = mDecoder->GetCurrentStream();
1202 // Seek to the end of file to find the length and duration.
1203 PRInt64 length = stream->GetLength();
1204 NS_ASSERTION(length > 0, "Must have a content length to get end time");
1206 mEndTime = 0;
1207 PRInt64 endTime = 0;
1209 MonitorAutoExit exitMon(mDecoder->GetMonitor());
1210 endTime = mReader->FindEndTime(length);
1212 if (endTime != -1) {
1213 mEndTime = endTime;
1216 LOG(PR_LOG_DEBUG, ("%p Media end time is %lldms", mDecoder, mEndTime));
1219 void nsBuiltinDecoderStateMachine::UpdateReadyState() {
1220 mDecoder->GetMonitor().AssertCurrentThreadIn();
1222 nsCOMPtr<nsIRunnable> event;
1223 switch (GetNextFrameStatus()) {
1224 case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING:
1225 event = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::NextFrameUnavailableBuffering);
1226 break;
1227 case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE:
1228 event = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::NextFrameAvailable);
1229 break;
1230 case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE:
1231 event = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::NextFrameUnavailable);
1232 break;
1233 default:
1234 PR_NOT_REACHED("unhandled frame state");
1237 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1240 void nsBuiltinDecoderStateMachine::LoadMetadata()
1242 NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
1243 "Should be on state machine thread.");
1244 mDecoder->GetMonitor().AssertCurrentThreadIn();
1246 LOG(PR_LOG_DEBUG, ("Loading Media Headers"));
1248 nsMediaStream* stream = mDecoder->mStream;
1251 MonitorAutoExit exitMon(mDecoder->GetMonitor());
1252 mReader->ReadMetadata();
1254 mDecoder->StartProgressUpdates();
1255 const nsVideoInfo& info = mReader->GetInfo();
1257 if (!info.mHasVideo && !info.mHasAudio) {
1258 mState = DECODER_STATE_SHUTDOWN;
1259 nsCOMPtr<nsIRunnable> event =
1260 NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
1261 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1262 return;
1265 LOG(PR_LOG_DEBUG, ("%p Callback Period: %u", mDecoder, info.mCallbackPeriod));