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>
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 ***** */
41 #include "nsAudioStream.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
;
53 extern PRLogModuleInfo
* gBuiltinDecoderLog
;
54 #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
56 #define LOG(type, msg)
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
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
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
) :
98 mState(DECODER_STATE_DECODING_METADATA
),
99 mAudioMonitor("media.audiostream"),
102 mBufferingEndOffset(0),
107 mCurrentFrameTime(0),
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();
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) ||
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;
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
184 MonitorAutoEnter
mon(mDecoder
->GetMonitor());
185 while (!mStopDecodeThreads
&&
187 mState
!= DECODER_STATE_SHUTDOWN
)
191 if (mState
== DECODER_STATE_SHUTDOWN
|| mStopDecodeThreads
)
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
) {
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
;
210 videoQueueSize
< LOW_VIDEO_FRAMES
)
212 skipToNextKeyframe
= PR_TRUE
;
215 // Determine how much audio data is decoded ahead of the current playback
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
) {
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());
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.
284 if (mState
== DECODER_STATE_SHUTDOWN
|| mStopDecodeThreads
) {
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.
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;
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
&&
337 mState
== DECODER_STATE_BUFFERING
||
338 (mReader
->mAudioQueue
.GetSize() == 0 &&
339 !mReader
->mAudioQueue
.AtEndOfStream())))
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())
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
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
);
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
384 if (!mAudioStream
->IsPaused()) {
385 mAudioStream
->Write(sound
->mAudioData
,
386 sound
->AudioDataLength(),
388 audioEndTime
= sound
->mTime
+ sound
->mDuration
;
389 mDecoder
->UpdatePlaybackOffset(sound
->mOffset
);
391 mReader
->mAudioQueue
.PushFront(sound
);
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.
420 if (mReader
->mAudioQueue
.AtEndOfStream() &&
421 mState
!= DECODER_STATE_SHUTDOWN
&&
424 // Last sample pushed to audio hardware, wait for the audio to finish,
425 // before the audio thread terminates.
426 MonitorAutoEnter
audioMon(mAudioMonitor
);
428 mAudioStream
->Drain();
430 LOG(PR_LOG_DEBUG
, ("%p Reached audio stream end.", mDecoder
));
433 MonitorAutoEnter
mon(mDecoder
->GetMonitor());
434 mAudioCompleted
= PR_TRUE
;
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.
460 mPlayDuration
+= TimeStamp::Now() - mPlayStartTime
;
461 mPlayStartTime
= TimeStamp();
464 MonitorAutoExit
exitMon(mDecoder
->GetMonitor());
465 MonitorAutoEnter
audioMon(mAudioMonitor
);
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
));
485 MonitorAutoExit
exitMon(mDecoder
->GetMonitor());
486 MonitorAutoEnter
audioMon(mAudioMonitor
);
488 // We have an audiostream, so it must have been paused the last time
489 // StopPlayback() was called.
490 mAudioStream
->Resume();
492 // No audiostream, create one.
493 const nsVideoInfo
& info
= mReader
->GetInfo();
494 mAudioStream
= new nsAudioStream();
495 mAudioStream
->Init(info
.mAudioChannels
,
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!");
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
);
555 mAudioStream
->SetVolume(volume
);
559 MonitorAutoEnter
mon(mDecoder
->GetMonitor());
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)
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
;
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;
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.
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();
677 MonitorAutoExit
exitMon(mDecoder
->GetMonitor());
678 mDecodeThread
->Shutdown();
680 mDecodeThread
= nsnull
;
684 MonitorAutoExit
exitMon(mDecoder
->GetMonitor());
685 mAudioThread
->Shutdown();
687 mAudioThread
= nsnull
;
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
));
701 mState
= DECODER_STATE_SHUTDOWN
;
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
));
711 mState
= DECODER_STATE_SHUTDOWN
;
714 nsCOMPtr
<nsIRunnable
> event
=
715 NS_NewRunnableMethod(this, &nsBuiltinDecoderStateMachine::AudioLoop
);
716 mAudioThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
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
);
729 MonitorAutoEnter
mon(mDecoder
->GetMonitor());
731 case DECODER_STATE_SHUTDOWN
:
733 StopPlayback(AUDIO_SHUTDOWN
);
736 NS_ASSERTION(mState
== DECODER_STATE_SHUTDOWN
,
737 "How did we escape from the shutdown state???");
740 case DECODER_STATE_DECODING_METADATA
:
743 if (mState
== DECODER_STATE_SHUTDOWN
) {
747 VideoData
* videoData
= FindStartTime();
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())) {
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
)
770 // Inform the element that we've loaded the metadata and the
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
;
782 if (mDecoder
->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING
) {
790 case DECODER_STATE_DECODING
:
792 if (NS_FAILED(StartDecodeThreads())) {
798 if (mState
!= DECODER_STATE_DECODING
)
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.
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.
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"));
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();
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
);
863 // SeekingStarted will do a UpdateReadyStateForData which will
864 // inform the element and its users that we have no frames
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
) {
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
;
883 mPlayDuration
= TimeDuration::FromMilliseconds(audio
->mTime
);
886 nsAutoPtr
<VideoData
> video(mReader
->mVideoQueue
.PeekFront());
888 RenderVideoFrame(video
);
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
)
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
;
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
);
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()) {
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()));
946 if (mState
== DECODER_STATE_SHUTDOWN
)
949 LOG(PR_LOG_DEBUG
, ("%p Changed state from BUFFERING to DECODING", mDecoder
));
950 LOG(PR_LOG_DEBUG
, ("%p Buffered for %lf seconds",
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();
961 if (mDecoder
->GetState() == nsBuiltinDecoder::PLAY_STATE_PLAYING
) {
971 case DECODER_STATE_COMPLETED
:
973 if (NS_FAILED(StartDecodeThreads())) {
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.
982 } while (mState
== DECODER_STATE_COMPLETED
&&
983 (mReader
->mVideoQueue
.GetSize() > 0 ||
984 (HasAudio() && !mAudioCompleted
)));
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
)
996 LOG(PR_LOG_DEBUG
, ("Shutting down the state machine thread"));
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();
1022 void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData
* aData
)
1024 NS_ASSERTION(IsCurrentThread(mDecoder
->mStateMachineThread
), "Should be on state machine thread.");
1026 if (aData
->mDuplicate
) {
1030 nsRefPtr
<Image
> image
= aData
->mImage
;
1032 const nsVideoInfo
& info
= mReader
->GetInfo();
1033 mDecoder
->SetVideoData(gfxIntSize(info
.mPicture
.width
, info
.mPicture
.height
), info
.mPixelAspectRatio
, image
);
1038 nsBuiltinDecoderStateMachine::GetAudioClock()
1040 NS_ASSERTION(IsCurrentThread(mDecoder
->mStateMachineThread
), "Should be on state machine thread.");
1041 if (!mAudioStream
|| !HasAudio())
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
) {
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
);
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
);
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
;
1094 mReader
->mVideoQueue
.PopFront();
1095 mDecoder
->UpdatePlaybackOffset(data
->mOffset
);
1096 if (mReader
->mVideoQueue
.GetSize() == 0)
1098 data
= mReader
->mVideoQueue
.PeekFront();
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
1109 RenderVideoFrame(videoData
);
1111 mDecoder
->GetMonitor().NotifyAll();
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.
1136 Wait(mReader
->GetInfo().mCallbackPeriod
);
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
);
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;
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;
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
1188 mEndTime
= mStartTime
+ mEndTime
;
1191 LOG(PR_LOG_DEBUG
, ("%p Media start time is %lldms", mDecoder
, mStartTime
));
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");
1207 PRInt64 endTime
= 0;
1209 MonitorAutoExit
exitMon(mDecoder
->GetMonitor());
1210 endTime
= mReader
->FindEndTime(length
);
1212 if (endTime
!= -1) {
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
);
1227 case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE
:
1228 event
= NS_NewRunnableMethod(mDecoder
, &nsBuiltinDecoder::NextFrameAvailable
);
1230 case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE
:
1231 event
= NS_NewRunnableMethod(mDecoder
, &nsBuiltinDecoder::NextFrameUnavailable
);
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
);
1265 LOG(PR_LOG_DEBUG
, ("%p Callback Period: %u", mDecoder
, info
.mCallbackPeriod
));