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: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
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.
21 : *
22 : * Contributor(s):
23 : * Chris Double <chris.double@double.co.nz>
24 : * Chris Pearce <chris@pearce.org.nz>
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include <limits>
41 : #include "nsNetUtil.h"
42 : #include "nsAudioStream.h"
43 : #include "nsHTMLVideoElement.h"
44 : #include "nsIObserver.h"
45 : #include "nsIObserverService.h"
46 : #include "nsTArray.h"
47 : #include "VideoUtils.h"
48 : #include "nsBuiltinDecoder.h"
49 : #include "nsBuiltinDecoderStateMachine.h"
50 : #include "nsTimeRanges.h"
51 : #include "nsContentUtils.h"
52 :
53 : using namespace mozilla;
54 :
55 : #ifdef PR_LOGGING
56 : PRLogModuleInfo* gBuiltinDecoderLog;
57 : #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
58 : #else
59 : #define LOG(type, msg)
60 : #endif
61 :
62 0 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsBuiltinDecoder, nsIObserver)
63 :
64 0 : void nsBuiltinDecoder::Pause()
65 : {
66 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
67 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
68 0 : if (mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) {
69 0 : mNextState = PLAY_STATE_PAUSED;
70 : return;
71 : }
72 :
73 0 : ChangeState(PLAY_STATE_PAUSED);
74 : }
75 :
76 0 : void nsBuiltinDecoder::SetVolume(double aVolume)
77 : {
78 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
79 0 : mInitialVolume = aVolume;
80 0 : if (mDecoderStateMachine) {
81 0 : mDecoderStateMachine->SetVolume(aVolume);
82 : }
83 0 : }
84 :
85 0 : double nsBuiltinDecoder::GetDuration()
86 : {
87 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
88 0 : if (mInfiniteStream) {
89 0 : return std::numeric_limits<double>::infinity();
90 : }
91 0 : if (mDuration >= 0) {
92 0 : return static_cast<double>(mDuration) / static_cast<double>(USECS_PER_S);
93 : }
94 0 : return std::numeric_limits<double>::quiet_NaN();
95 : }
96 :
97 0 : void nsBuiltinDecoder::SetInfinite(bool aInfinite)
98 : {
99 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
100 0 : mInfiniteStream = aInfinite;
101 0 : }
102 :
103 0 : bool nsBuiltinDecoder::IsInfinite()
104 : {
105 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
106 0 : return mInfiniteStream;
107 : }
108 :
109 0 : nsBuiltinDecoder::nsBuiltinDecoder() :
110 : mDecoderPosition(0),
111 : mPlaybackPosition(0),
112 : mCurrentTime(0.0),
113 : mInitialVolume(0.0),
114 : mRequestedSeekTime(-1.0),
115 : mDuration(-1),
116 : mSeekable(true),
117 : mReentrantMonitor("media.decoder"),
118 : mPlayState(PLAY_STATE_PAUSED),
119 : mNextState(PLAY_STATE_PAUSED),
120 : mResourceLoaded(false),
121 : mIgnoreProgressData(false),
122 0 : mInfiniteStream(false)
123 : {
124 0 : MOZ_COUNT_CTOR(nsBuiltinDecoder);
125 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
126 : #ifdef PR_LOGGING
127 0 : if (!gBuiltinDecoderLog) {
128 0 : gBuiltinDecoderLog = PR_NewLogModule("nsBuiltinDecoder");
129 : }
130 : #endif
131 0 : }
132 :
133 0 : bool nsBuiltinDecoder::Init(nsHTMLMediaElement* aElement)
134 : {
135 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
136 0 : if (!nsMediaDecoder::Init(aElement))
137 0 : return false;
138 :
139 0 : nsContentUtils::RegisterShutdownObserver(this);
140 0 : return true;
141 : }
142 :
143 0 : void nsBuiltinDecoder::Shutdown()
144 : {
145 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
146 :
147 0 : if (mShuttingDown)
148 0 : return;
149 :
150 0 : mShuttingDown = true;
151 :
152 : // This changes the decoder state to SHUTDOWN and does other things
153 : // necessary to unblock the state machine thread if it's blocked, so
154 : // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
155 0 : if (mDecoderStateMachine) {
156 0 : mDecoderStateMachine->Shutdown();
157 : }
158 :
159 : // Force any outstanding seek and byterange requests to complete
160 : // to prevent shutdown from deadlocking.
161 0 : if (mResource) {
162 0 : mResource->Close();
163 : }
164 :
165 0 : ChangeState(PLAY_STATE_SHUTDOWN);
166 0 : nsMediaDecoder::Shutdown();
167 :
168 0 : nsContentUtils::UnregisterShutdownObserver(this);
169 : }
170 :
171 0 : nsBuiltinDecoder::~nsBuiltinDecoder()
172 : {
173 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
174 0 : UnpinForSeek();
175 0 : MOZ_COUNT_DTOR(nsBuiltinDecoder);
176 0 : }
177 :
178 0 : nsresult nsBuiltinDecoder::Load(MediaResource* aResource,
179 : nsIStreamListener** aStreamListener,
180 : nsMediaDecoder* aCloneDonor)
181 : {
182 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
183 0 : if (aStreamListener) {
184 0 : *aStreamListener = nsnull;
185 : }
186 :
187 : {
188 : // Hold the lock while we do this to set proper lock ordering
189 : // expectations for dynamic deadlock detectors: decoder lock(s)
190 : // should be grabbed before the cache lock
191 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
192 :
193 0 : nsresult rv = aResource->Open(aStreamListener);
194 0 : if (NS_FAILED(rv)) {
195 0 : LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this));
196 0 : delete aResource;
197 0 : return rv;
198 : }
199 :
200 0 : mResource = aResource;
201 : }
202 :
203 0 : mDecoderStateMachine = CreateStateMachine();
204 0 : if (!mDecoderStateMachine) {
205 0 : LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this));
206 0 : return NS_ERROR_FAILURE;
207 : }
208 :
209 0 : nsBuiltinDecoder* cloneDonor = static_cast<nsBuiltinDecoder*>(aCloneDonor);
210 0 : if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ?
211 : cloneDonor->mDecoderStateMachine : nsnull))) {
212 0 : LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this));
213 0 : return NS_ERROR_FAILURE;
214 : }
215 : {
216 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
217 0 : mDecoderStateMachine->SetSeekable(mSeekable);
218 0 : mDecoderStateMachine->SetDuration(mDuration);
219 0 : mDecoderStateMachine->SetVolume(mInitialVolume);
220 :
221 0 : if (mFrameBufferLength > 0) {
222 : // The valid mFrameBufferLength value was specified earlier
223 0 : mDecoderStateMachine->SetFrameBufferLength(mFrameBufferLength);
224 : }
225 : }
226 :
227 0 : ChangeState(PLAY_STATE_LOADING);
228 :
229 0 : return ScheduleStateMachineThread();
230 : }
231 :
232 0 : nsresult nsBuiltinDecoder::RequestFrameBufferLength(PRUint32 aLength)
233 : {
234 0 : nsresult res = nsMediaDecoder::RequestFrameBufferLength(aLength);
235 0 : NS_ENSURE_SUCCESS(res,res);
236 :
237 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
238 0 : if (mDecoderStateMachine) {
239 0 : mDecoderStateMachine->SetFrameBufferLength(aLength);
240 : }
241 0 : return res;
242 : }
243 :
244 0 : nsresult nsBuiltinDecoder::ScheduleStateMachineThread()
245 : {
246 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
247 0 : NS_ASSERTION(mDecoderStateMachine,
248 : "Must have state machine to start state machine thread");
249 0 : NS_ENSURE_STATE(mDecoderStateMachine);
250 :
251 0 : if (mShuttingDown)
252 0 : return NS_OK;
253 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
254 : nsBuiltinDecoderStateMachine* m =
255 0 : static_cast<nsBuiltinDecoderStateMachine*>(mDecoderStateMachine.get());
256 0 : return m->ScheduleStateMachine();
257 : }
258 :
259 0 : nsresult nsBuiltinDecoder::Play()
260 : {
261 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
262 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
263 0 : NS_ASSERTION(mDecoderStateMachine != nsnull, "Should have state machine.");
264 0 : nsresult res = ScheduleStateMachineThread();
265 0 : NS_ENSURE_SUCCESS(res,res);
266 0 : if (mPlayState == PLAY_STATE_SEEKING) {
267 0 : mNextState = PLAY_STATE_PLAYING;
268 0 : return NS_OK;
269 : }
270 0 : if (mPlayState == PLAY_STATE_ENDED)
271 0 : return Seek(0);
272 :
273 0 : ChangeState(PLAY_STATE_PLAYING);
274 0 : return NS_OK;
275 : }
276 :
277 : /**
278 : * Returns true if aValue is inside a range of aRanges, and put the range
279 : * index in aIntervalIndex if it is not null.
280 : * If aValue is not inside a range, false is returned, and aIntervalIndex, if
281 : * not null, is set to the index of the range which ends immediately before aValue
282 : * (and can be -1 if aValue is before aRanges.Start(0)).
283 : */
284 0 : static bool IsInRanges(nsTimeRanges& aRanges, double aValue, PRInt32& aIntervalIndex) {
285 : PRUint32 length;
286 0 : aRanges.GetLength(&length);
287 0 : for (PRUint32 i = 0; i < length; i++) {
288 : double start, end;
289 0 : aRanges.Start(i, &start);
290 0 : if (start > aValue) {
291 0 : aIntervalIndex = i - 1;
292 0 : return false;
293 : }
294 0 : aRanges.End(i, &end);
295 0 : if (aValue <= end) {
296 0 : aIntervalIndex = i;
297 0 : return true;
298 : }
299 : }
300 0 : aIntervalIndex = length - 1;
301 0 : return false;
302 : }
303 :
304 0 : nsresult nsBuiltinDecoder::Seek(double aTime)
305 : {
306 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
307 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
308 :
309 0 : NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value.");
310 :
311 0 : nsTimeRanges seekable;
312 : nsresult res;
313 0 : PRUint32 length = 0;
314 0 : res = GetSeekable(&seekable);
315 0 : NS_ENSURE_SUCCESS(res, NS_OK);
316 :
317 0 : seekable.GetLength(&length);
318 0 : if (!length) {
319 0 : return NS_OK;
320 : }
321 :
322 : // If the position we want to seek to is not in a seekable range, we seek
323 : // to the closest position in the seekable ranges instead. If two positions
324 : // are equally close, we seek to the closest position from the currentTime.
325 : // See seeking spec, point 7 :
326 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
327 0 : PRInt32 range = 0;
328 0 : if (!IsInRanges(seekable, aTime, range)) {
329 0 : if (range != -1) {
330 : // |range + 1| can't be negative, because the only possible negative value
331 : // for |range| is -1.
332 0 : if (PRUint32(range + 1) < length) {
333 : double leftBound, rightBound;
334 0 : res = seekable.End(range, &leftBound);
335 0 : NS_ENSURE_SUCCESS(res, NS_OK);
336 0 : res = seekable.Start(range + 1, &rightBound);
337 0 : NS_ENSURE_SUCCESS(res, NS_OK);
338 0 : double distanceLeft = NS_ABS(leftBound - aTime);
339 0 : double distanceRight = NS_ABS(rightBound - aTime);
340 0 : if (distanceLeft == distanceRight) {
341 0 : distanceLeft = NS_ABS(leftBound - mCurrentTime);
342 0 : distanceRight = NS_ABS(rightBound - mCurrentTime);
343 : }
344 0 : aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
345 : } else {
346 : // Seek target is after the end last range in seekable data.
347 : // Clamp the seek target to the end of the last seekable range.
348 0 : res = seekable.End(length - 1, &aTime);
349 0 : NS_ENSURE_SUCCESS(res, NS_OK);
350 : }
351 : } else {
352 : // aTime is before the first range in |seekable|, the closest point we can
353 : // seek to is the start of the first range.
354 0 : seekable.Start(0, &aTime);
355 : }
356 : }
357 :
358 0 : mRequestedSeekTime = aTime;
359 0 : mCurrentTime = aTime;
360 :
361 : // If we are already in the seeking state, then setting mRequestedSeekTime
362 : // above will result in the new seek occurring when the current seek
363 : // completes.
364 0 : if (mPlayState != PLAY_STATE_SEEKING) {
365 0 : bool paused = false;
366 0 : if (mElement) {
367 0 : mElement->GetPaused(&paused);
368 : }
369 0 : mNextState = paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING;
370 0 : PinForSeek();
371 0 : ChangeState(PLAY_STATE_SEEKING);
372 : }
373 :
374 0 : return ScheduleStateMachineThread();
375 : }
376 :
377 0 : nsresult nsBuiltinDecoder::PlaybackRateChanged()
378 : {
379 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
380 0 : return NS_ERROR_NOT_IMPLEMENTED;
381 : }
382 :
383 0 : double nsBuiltinDecoder::GetCurrentTime()
384 : {
385 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
386 0 : return mCurrentTime;
387 : }
388 :
389 0 : already_AddRefed<nsIPrincipal> nsBuiltinDecoder::GetCurrentPrincipal()
390 : {
391 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
392 0 : return mResource ? mResource->GetCurrentPrincipal() : nsnull;
393 : }
394 :
395 0 : void nsBuiltinDecoder::AudioAvailable(float* aFrameBuffer,
396 : PRUint32 aFrameBufferLength,
397 : float aTime)
398 : {
399 : // Auto manage the frame buffer's memory. If we return due to an error
400 : // here, this ensures we free the memory. Otherwise, we pass off ownership
401 : // to HTMLMediaElement::NotifyAudioAvailable().
402 0 : nsAutoArrayPtr<float> frameBuffer(aFrameBuffer);
403 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
404 0 : if (mShuttingDown || !mElement) {
405 : return;
406 : }
407 0 : mElement->NotifyAudioAvailable(frameBuffer.forget(), aFrameBufferLength, aTime);
408 : }
409 :
410 0 : void nsBuiltinDecoder::MetadataLoaded(PRUint32 aChannels,
411 : PRUint32 aRate)
412 : {
413 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
414 0 : if (mShuttingDown) {
415 0 : return;
416 : }
417 :
418 : // Only inform the element of MetadataLoaded if not doing a load() in order
419 : // to fulfill a seek, otherwise we'll get multiple metadataloaded events.
420 0 : bool notifyElement = true;
421 : {
422 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
423 0 : mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
424 : // Duration has changed so we should recompute playback rate
425 0 : UpdatePlaybackRate();
426 :
427 0 : notifyElement = mNextState != PLAY_STATE_SEEKING;
428 : }
429 :
430 0 : if (mDuration == -1) {
431 0 : SetInfinite(true);
432 : }
433 :
434 0 : if (mElement && notifyElement) {
435 : // Make sure the element and the frame (if any) are told about
436 : // our new size.
437 0 : Invalidate();
438 0 : mElement->MetadataLoaded(aChannels, aRate);
439 : }
440 :
441 0 : if (!mResourceLoaded) {
442 0 : StartProgress();
443 0 : } else if (mElement) {
444 : // Resource was loaded during metadata loading, when progress
445 : // events are being ignored. Fire the final progress event.
446 0 : mElement->DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
447 : }
448 :
449 : // Only inform the element of FirstFrameLoaded if not doing a load() in order
450 : // to fulfill a seek, otherwise we'll get multiple loadedfirstframe events.
451 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
452 0 : bool resourceIsLoaded = !mResourceLoaded && mResource &&
453 0 : mResource->IsDataCachedToEndOfResource(mDecoderPosition);
454 0 : if (mElement && notifyElement) {
455 0 : mElement->FirstFrameLoaded(resourceIsLoaded);
456 : }
457 :
458 : // This can run cache callbacks.
459 0 : mResource->EnsureCacheUpToDate();
460 :
461 : // The element can run javascript via events
462 : // before reaching here, so only change the
463 : // state if we're still set to the original
464 : // loading state.
465 0 : if (mPlayState == PLAY_STATE_LOADING) {
466 0 : if (mRequestedSeekTime >= 0.0) {
467 0 : ChangeState(PLAY_STATE_SEEKING);
468 : }
469 : else {
470 0 : ChangeState(mNextState);
471 : }
472 : }
473 :
474 0 : if (resourceIsLoaded) {
475 0 : ResourceLoaded();
476 : }
477 :
478 : // Run NotifySuspendedStatusChanged now to give us a chance to notice
479 : // that autoplay should run.
480 0 : NotifySuspendedStatusChanged();
481 : }
482 :
483 0 : void nsBuiltinDecoder::ResourceLoaded()
484 : {
485 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
486 :
487 : // Don't handle ResourceLoaded if we are shutting down, or if
488 : // we need to ignore progress data due to seeking (in the case
489 : // that the seek results in reaching end of file, we get a bogus call
490 : // to ResourceLoaded).
491 0 : if (mShuttingDown)
492 0 : return;
493 :
494 : {
495 : // If we are seeking or loading then the resource loaded notification we get
496 : // should be ignored, since it represents the end of the seek request.
497 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
498 0 : if (mIgnoreProgressData || mResourceLoaded || mPlayState == PLAY_STATE_LOADING)
499 : return;
500 :
501 0 : Progress(false);
502 :
503 0 : mResourceLoaded = true;
504 0 : StopProgress();
505 : }
506 :
507 : // Ensure the final progress event gets fired
508 0 : if (mElement) {
509 0 : mElement->ResourceLoaded();
510 : }
511 : }
512 :
513 0 : void nsBuiltinDecoder::NetworkError()
514 : {
515 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
516 0 : if (mShuttingDown)
517 0 : return;
518 :
519 0 : if (mElement)
520 0 : mElement->NetworkError();
521 :
522 0 : Shutdown();
523 : }
524 :
525 0 : void nsBuiltinDecoder::DecodeError()
526 : {
527 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
528 0 : if (mShuttingDown)
529 0 : return;
530 :
531 0 : if (mElement)
532 0 : mElement->DecodeError();
533 :
534 0 : Shutdown();
535 : }
536 :
537 0 : bool nsBuiltinDecoder::IsSeeking() const
538 : {
539 0 : return mPlayState == PLAY_STATE_SEEKING || mNextState == PLAY_STATE_SEEKING;
540 : }
541 :
542 0 : bool nsBuiltinDecoder::IsEnded() const
543 : {
544 0 : return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN;
545 : }
546 :
547 0 : void nsBuiltinDecoder::PlaybackEnded()
548 : {
549 0 : if (mShuttingDown || mPlayState == nsBuiltinDecoder::PLAY_STATE_SEEKING)
550 0 : return;
551 :
552 0 : PlaybackPositionChanged();
553 0 : ChangeState(PLAY_STATE_ENDED);
554 :
555 0 : if (mElement) {
556 0 : UpdateReadyStateForData();
557 0 : mElement->PlaybackEnded();
558 : }
559 :
560 : // This must be called after |mElement->PlaybackEnded()| call above, in order
561 : // to fire the required durationchange.
562 0 : if (IsInfinite()) {
563 0 : SetInfinite(false);
564 : }
565 : }
566 :
567 0 : NS_IMETHODIMP nsBuiltinDecoder::Observe(nsISupports *aSubjet,
568 : const char *aTopic,
569 : const PRUnichar *someData)
570 : {
571 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
572 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
573 0 : Shutdown();
574 : }
575 :
576 0 : return NS_OK;
577 : }
578 :
579 : nsMediaDecoder::Statistics
580 0 : nsBuiltinDecoder::GetStatistics()
581 : {
582 0 : NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
583 : "Should be on main or state machine thread.");
584 : Statistics result;
585 :
586 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
587 0 : if (mResource) {
588 : result.mDownloadRate =
589 0 : mResource->GetDownloadRate(&result.mDownloadRateReliable);
590 : result.mDownloadPosition =
591 0 : mResource->GetCachedDataEnd(mDecoderPosition);
592 0 : result.mTotalBytes = mResource->GetLength();
593 0 : result.mPlaybackRate = ComputePlaybackRate(&result.mPlaybackRateReliable);
594 0 : result.mDecoderPosition = mDecoderPosition;
595 0 : result.mPlaybackPosition = mPlaybackPosition;
596 : }
597 : else {
598 0 : result.mDownloadRate = 0;
599 0 : result.mDownloadRateReliable = true;
600 0 : result.mPlaybackRate = 0;
601 0 : result.mPlaybackRateReliable = true;
602 0 : result.mDecoderPosition = 0;
603 0 : result.mPlaybackPosition = 0;
604 0 : result.mDownloadPosition = 0;
605 0 : result.mTotalBytes = 0;
606 : }
607 :
608 : return result;
609 : }
610 :
611 0 : double nsBuiltinDecoder::ComputePlaybackRate(bool* aReliable)
612 : {
613 0 : GetReentrantMonitor().AssertCurrentThreadIn();
614 0 : NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
615 : "Should be on main or state machine thread.");
616 :
617 0 : PRInt64 length = mResource ? mResource->GetLength() : -1;
618 0 : if (mDuration >= 0 && length >= 0) {
619 0 : *aReliable = true;
620 0 : return length * static_cast<double>(USECS_PER_S) / mDuration;
621 : }
622 0 : return mPlaybackStatistics.GetRateAtLastStop(aReliable);
623 : }
624 :
625 0 : void nsBuiltinDecoder::UpdatePlaybackRate()
626 : {
627 0 : NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
628 : "Should be on main or state machine thread.");
629 0 : GetReentrantMonitor().AssertCurrentThreadIn();
630 0 : if (!mResource)
631 0 : return;
632 : bool reliable;
633 0 : PRUint32 rate = PRUint32(ComputePlaybackRate(&reliable));
634 0 : if (reliable) {
635 : // Avoid passing a zero rate
636 0 : rate = NS_MAX(rate, 1u);
637 : }
638 : else {
639 : // Set a minimum rate of 10,000 bytes per second ... sometimes we just
640 : // don't have good data
641 0 : rate = NS_MAX(rate, 10000u);
642 : }
643 0 : mResource->SetPlaybackRate(rate);
644 : }
645 :
646 0 : void nsBuiltinDecoder::NotifySuspendedStatusChanged()
647 : {
648 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
649 0 : if (!mResource)
650 0 : return;
651 : MediaResource* activeStream;
652 0 : bool suspended = mResource->IsSuspendedByCache(&activeStream);
653 :
654 0 : if (suspended && mElement) {
655 : // if this is an autoplay element, we need to kick off its autoplaying
656 : // now so we consume data and hopefully free up cache space
657 0 : mElement->NotifyAutoplayDataReady();
658 : }
659 : }
660 :
661 0 : void nsBuiltinDecoder::NotifyBytesDownloaded()
662 : {
663 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
664 0 : UpdateReadyStateForData();
665 0 : Progress(false);
666 0 : }
667 :
668 0 : void nsBuiltinDecoder::NotifyDownloadEnded(nsresult aStatus)
669 : {
670 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
671 :
672 0 : if (aStatus == NS_BINDING_ABORTED) {
673 : // Download has been cancelled by user.
674 0 : if (mElement) {
675 0 : mElement->LoadAborted();
676 : }
677 0 : return;
678 : }
679 :
680 : {
681 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
682 0 : UpdatePlaybackRate();
683 : }
684 :
685 0 : if (NS_SUCCEEDED(aStatus)) {
686 0 : ResourceLoaded();
687 : }
688 0 : else if (aStatus != NS_BASE_STREAM_CLOSED) {
689 0 : NetworkError();
690 : }
691 0 : UpdateReadyStateForData();
692 : }
693 :
694 0 : void nsBuiltinDecoder::NotifyBytesConsumed(PRInt64 aBytes)
695 : {
696 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
697 0 : NS_ASSERTION(OnStateMachineThread() || mDecoderStateMachine->OnDecodeThread(),
698 : "Should be on play state machine or decode thread.");
699 0 : if (!mIgnoreProgressData) {
700 0 : mDecoderPosition += aBytes;
701 0 : mPlaybackStatistics.AddBytes(aBytes);
702 : }
703 0 : }
704 :
705 0 : void nsBuiltinDecoder::NextFrameUnavailableBuffering()
706 : {
707 0 : NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
708 0 : if (!mElement || mShuttingDown || !mDecoderStateMachine)
709 0 : return;
710 :
711 0 : mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING);
712 : }
713 :
714 0 : void nsBuiltinDecoder::NextFrameAvailable()
715 : {
716 0 : NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
717 0 : if (!mElement || mShuttingDown || !mDecoderStateMachine)
718 0 : return;
719 :
720 0 : mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_AVAILABLE);
721 : }
722 :
723 0 : void nsBuiltinDecoder::NextFrameUnavailable()
724 : {
725 0 : NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
726 0 : if (!mElement || mShuttingDown || !mDecoderStateMachine)
727 0 : return;
728 0 : mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE);
729 : }
730 :
731 0 : void nsBuiltinDecoder::UpdateReadyStateForData()
732 : {
733 0 : NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread");
734 0 : if (!mElement || mShuttingDown || !mDecoderStateMachine)
735 0 : return;
736 : nsHTMLMediaElement::NextFrameStatus frameStatus =
737 0 : mDecoderStateMachine->GetNextFrameStatus();
738 0 : mElement->UpdateReadyStateForData(frameStatus);
739 : }
740 :
741 0 : void nsBuiltinDecoder::SeekingStopped()
742 : {
743 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
744 :
745 0 : if (mShuttingDown)
746 0 : return;
747 :
748 0 : bool seekWasAborted = false;
749 : {
750 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
751 :
752 : // An additional seek was requested while the current seek was
753 : // in operation.
754 0 : if (mRequestedSeekTime >= 0.0) {
755 0 : ChangeState(PLAY_STATE_SEEKING);
756 0 : seekWasAborted = true;
757 : } else {
758 0 : UnpinForSeek();
759 0 : ChangeState(mNextState);
760 : }
761 : }
762 :
763 0 : if (mElement) {
764 0 : UpdateReadyStateForData();
765 0 : if (!seekWasAborted) {
766 0 : mElement->SeekCompleted();
767 : }
768 : }
769 : }
770 :
771 : // This is called when seeking stopped *and* we're at the end of the
772 : // media.
773 0 : void nsBuiltinDecoder::SeekingStoppedAtEnd()
774 : {
775 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
776 :
777 0 : if (mShuttingDown)
778 0 : return;
779 :
780 0 : bool fireEnded = false;
781 0 : bool seekWasAborted = false;
782 : {
783 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
784 :
785 : // An additional seek was requested while the current seek was
786 : // in operation.
787 0 : if (mRequestedSeekTime >= 0.0) {
788 0 : ChangeState(PLAY_STATE_SEEKING);
789 0 : seekWasAborted = true;
790 : } else {
791 0 : UnpinForSeek();
792 0 : fireEnded = true;
793 0 : ChangeState(PLAY_STATE_ENDED);
794 : }
795 : }
796 :
797 0 : if (mElement) {
798 0 : UpdateReadyStateForData();
799 0 : if (!seekWasAborted) {
800 0 : mElement->SeekCompleted();
801 0 : if (fireEnded) {
802 0 : mElement->PlaybackEnded();
803 : }
804 : }
805 : }
806 : }
807 :
808 0 : void nsBuiltinDecoder::SeekingStarted()
809 : {
810 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
811 0 : if (mShuttingDown)
812 0 : return;
813 :
814 0 : if (mElement) {
815 0 : UpdateReadyStateForData();
816 0 : mElement->SeekStarted();
817 : }
818 : }
819 :
820 0 : void nsBuiltinDecoder::ChangeState(PlayState aState)
821 : {
822 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
823 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
824 :
825 0 : if (mNextState == aState) {
826 0 : mNextState = PLAY_STATE_PAUSED;
827 : }
828 :
829 0 : if (mPlayState == PLAY_STATE_SHUTDOWN) {
830 0 : mReentrantMonitor.NotifyAll();
831 : return;
832 : }
833 :
834 0 : mPlayState = aState;
835 0 : if (mDecoderStateMachine) {
836 0 : switch (aState) {
837 : case PLAY_STATE_PLAYING:
838 0 : mDecoderStateMachine->Play();
839 0 : break;
840 : case PLAY_STATE_SEEKING:
841 0 : mDecoderStateMachine->Seek(mRequestedSeekTime);
842 0 : mRequestedSeekTime = -1.0;
843 0 : break;
844 : default:
845 : /* No action needed */
846 0 : break;
847 : }
848 : }
849 0 : mReentrantMonitor.NotifyAll();
850 : }
851 :
852 0 : void nsBuiltinDecoder::PlaybackPositionChanged()
853 : {
854 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
855 0 : if (mShuttingDown)
856 0 : return;
857 :
858 0 : double lastTime = mCurrentTime;
859 :
860 : // Control the scope of the monitor so it is not
861 : // held while the timeupdate and the invalidate is run.
862 : {
863 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
864 0 : if (mDecoderStateMachine) {
865 0 : if (!IsSeeking()) {
866 : // Only update the current playback position if we're not seeking.
867 : // If we are seeking, the update could have been scheduled on the
868 : // state machine thread while we were playing but after the seek
869 : // algorithm set the current playback position on the main thread,
870 : // and we don't want to override the seek algorithm and change the
871 : // current time after the seek has started but before it has
872 : // completed.
873 0 : mCurrentTime = mDecoderStateMachine->GetCurrentTime();
874 : }
875 0 : mDecoderStateMachine->ClearPositionChangeFlag();
876 : }
877 : }
878 :
879 : // Invalidate the frame so any video data is displayed.
880 : // Do this before the timeupdate event so that if that
881 : // event runs JavaScript that queries the media size, the
882 : // frame has reflowed and the size updated beforehand.
883 0 : Invalidate();
884 :
885 0 : if (mElement && lastTime != mCurrentTime) {
886 0 : FireTimeUpdate();
887 : }
888 : }
889 :
890 0 : void nsBuiltinDecoder::DurationChanged()
891 : {
892 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
893 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
894 0 : PRInt64 oldDuration = mDuration;
895 0 : mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
896 : // Duration has changed so we should recompute playback rate
897 0 : UpdatePlaybackRate();
898 :
899 0 : if (mElement && oldDuration != mDuration && !IsInfinite()) {
900 0 : LOG(PR_LOG_DEBUG, ("%p duration changed to %lld", this, mDuration));
901 0 : mElement->DispatchEvent(NS_LITERAL_STRING("durationchange"));
902 : }
903 0 : }
904 :
905 0 : void nsBuiltinDecoder::SetDuration(double aDuration)
906 : {
907 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
908 0 : mDuration = static_cast<PRInt64>(NS_round(aDuration * static_cast<double>(USECS_PER_S)));
909 :
910 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
911 0 : if (mDecoderStateMachine) {
912 0 : mDecoderStateMachine->SetDuration(mDuration);
913 : }
914 :
915 : // Duration has changed so we should recompute playback rate
916 0 : UpdatePlaybackRate();
917 0 : }
918 :
919 0 : void nsBuiltinDecoder::SetSeekable(bool aSeekable)
920 : {
921 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
922 0 : mSeekable = aSeekable;
923 0 : if (mDecoderStateMachine) {
924 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
925 0 : mDecoderStateMachine->SetSeekable(aSeekable);
926 : }
927 0 : }
928 :
929 0 : bool nsBuiltinDecoder::IsSeekable()
930 : {
931 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
932 0 : return mSeekable;
933 : }
934 :
935 0 : nsresult nsBuiltinDecoder::GetSeekable(nsTimeRanges* aSeekable)
936 : {
937 : //TODO : change 0.0 to GetInitialTime() when available
938 0 : double initialTime = 0.0;
939 :
940 0 : if (IsSeekable()) {
941 0 : double end = IsInfinite() ? std::numeric_limits<double>::infinity()
942 0 : : initialTime + GetDuration();
943 0 : aSeekable->Add(initialTime, end);
944 0 : return NS_OK;
945 : }
946 :
947 0 : return GetBuffered(aSeekable);
948 : }
949 :
950 0 : void nsBuiltinDecoder::SetEndTime(double aTime)
951 : {
952 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
953 0 : if (mDecoderStateMachine) {
954 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
955 0 : mDecoderStateMachine->SetFragmentEndTime(static_cast<PRInt64>(aTime * USECS_PER_S));
956 : }
957 0 : }
958 :
959 0 : void nsBuiltinDecoder::Suspend()
960 : {
961 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
962 0 : if (mResource) {
963 0 : mResource->Suspend(true);
964 : }
965 0 : }
966 :
967 0 : void nsBuiltinDecoder::Resume(bool aForceBuffering)
968 : {
969 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
970 0 : if (mResource) {
971 0 : mResource->Resume();
972 : }
973 0 : if (aForceBuffering) {
974 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
975 0 : if (mDecoderStateMachine) {
976 0 : mDecoderStateMachine->StartBuffering();
977 : }
978 : }
979 0 : }
980 :
981 0 : void nsBuiltinDecoder::StopProgressUpdates()
982 : {
983 0 : NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
984 : "Should be on state machine or decode thread.");
985 0 : GetReentrantMonitor().AssertCurrentThreadIn();
986 0 : mIgnoreProgressData = true;
987 0 : if (mResource) {
988 0 : mResource->SetReadMode(nsMediaCacheStream::MODE_METADATA);
989 : }
990 0 : }
991 :
992 0 : void nsBuiltinDecoder::StartProgressUpdates()
993 : {
994 0 : NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
995 : "Should be on state machine or decode thread.");
996 0 : GetReentrantMonitor().AssertCurrentThreadIn();
997 0 : mIgnoreProgressData = false;
998 0 : if (mResource) {
999 0 : mResource->SetReadMode(nsMediaCacheStream::MODE_PLAYBACK);
1000 0 : mDecoderPosition = mPlaybackPosition = mResource->Tell();
1001 : }
1002 0 : }
1003 :
1004 0 : void nsBuiltinDecoder::MoveLoadsToBackground()
1005 : {
1006 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
1007 0 : if (mResource) {
1008 0 : mResource->MoveLoadsToBackground();
1009 : }
1010 0 : }
1011 :
1012 0 : void nsBuiltinDecoder::UpdatePlaybackOffset(PRInt64 aOffset)
1013 : {
1014 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
1015 0 : mPlaybackPosition = NS_MAX(aOffset, mPlaybackPosition);
1016 0 : }
1017 :
1018 0 : bool nsBuiltinDecoder::OnStateMachineThread() const
1019 : {
1020 0 : return IsCurrentThread(nsBuiltinDecoderStateMachine::GetStateMachineThread());
1021 : }
1022 :
1023 0 : void nsBuiltinDecoder::NotifyAudioAvailableListener()
1024 : {
1025 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
1026 0 : if (mDecoderStateMachine) {
1027 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
1028 0 : mDecoderStateMachine->NotifyAudioAvailableListener();
1029 : }
1030 0 : }
|