Bug 1123452 - Make Mp4Reader enter dormant state when it is idle and its document is hidden. r=cpearce
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -31,16 +31,19 @@
#ifdef MOZ_WMF
#include "WMFDecoder.h"
#endif
using namespace mozilla::layers;
using namespace mozilla::dom;
+// Default timeout msecs until try to enter dormant state by heuristic.
+static const int DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS = 60000;
+
namespace mozilla {
// Number of estimated seconds worth of data we need to have buffered
// ahead of the current playback position before we allow the media decoder
// to report that it can play through the entire media without the decode
// catching up with the download. Having this margin make the
// MediaDecoder::CanPlayThrough() calculation more stable in the case of
// fluctuating bitrates.
@@ -118,37 +121,65 @@ NS_IMPL_ISUPPORTS(MediaMemoryTracker, ns
NS_IMPL_ISUPPORTS(MediaDecoder, nsIObserver)
void MediaDecoder::NotifyOwnerActivityChanged()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
- if (!mDecoderStateMachine ||
- !mDecoderStateMachine->IsDormantNeeded() ||
- mPlayState == PLAY_STATE_SHUTDOWN) {
- return;
- }
-
if (!mOwner) {
NS_WARNING("MediaDecoder without a decoder owner, can't update dormant");
return;
}
+ UpdateDormantState(false /* aDormantTimeout */, false /* aActivity */);
+ // Start dormant timer if necessary
+ StartDormantTimer();
+}
+
+void MediaDecoder::UpdateDormantState(bool aDormantTimeout, bool aActivity)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ GetReentrantMonitor().AssertCurrentThreadIn();
+
+ if (!mDecoderStateMachine ||
+ !mOwner->GetVideoFrameContainer() ||
+ !mDecoderStateMachine->IsDormantNeeded() ||
+ mPlayState == PLAY_STATE_SHUTDOWN) {
+ return;
+ }
+
bool prevDormant = mIsDormant;
mIsDormant = false;
- if (!mOwner->IsActive() && mOwner->GetVideoFrameContainer()) {
+ if (!mOwner->IsActive()) {
mIsDormant = true;
}
#ifdef MOZ_WIDGET_GONK
- if (mOwner->IsHidden() && mOwner->GetVideoFrameContainer()) {
+ if (mOwner->IsHidden()) {
mIsDormant = true;
}
#endif
+ // Try to enable dormant by idle heuristic, when the owner is hidden.
+ bool prevHeuristicDormant = mIsHeuristicDormant;
+ mIsHeuristicDormant = false;
+ if (mIsHeuristicDormantSupported && mOwner->IsHidden()) {
+ if (aDormantTimeout && !aActivity &&
+ (mPlayState == PLAY_STATE_PAUSED || mPlayState == PLAY_STATE_ENDED)) {
+ // Enable heuristic dormant
+ mIsHeuristicDormant = true;
+ } else if(prevHeuristicDormant && !aActivity) {
+ // Continue heuristic dormant
+ mIsHeuristicDormant = true;
+ }
+
+ if (mIsHeuristicDormant) {
+ mIsDormant = true;
+ }
+ }
if (prevDormant == mIsDormant) {
// No update to dormant state
return;
}
if (mIsDormant) {
// enter dormant state
@@ -162,16 +193,57 @@ void MediaDecoder::NotifyOwnerActivityCh
ChangeState(PLAY_STATE_LOADING);
} else {
// exit dormant state
// trigger to state machine.
mDecoderStateMachine->SetDormant(false);
}
}
+void MediaDecoder::DormantTimerExpired(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_ASSERT(aClosure);
+ MediaDecoder* decoder = static_cast<MediaDecoder*>(aClosure);
+ ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
+ decoder->UpdateDormantState(true /* aDormantTimeout */,
+ false /* aActivity */);
+}
+
+void MediaDecoder::StartDormantTimer()
+{
+ if (!mIsHeuristicDormantSupported) {
+ return;
+ }
+
+ if (mIsHeuristicDormant ||
+ mShuttingDown ||
+ !mOwner ||
+ !mOwner->IsHidden() ||
+ (mPlayState != PLAY_STATE_PAUSED &&
+ mPlayState != PLAY_STATE_ENDED))
+ {
+ return;
+ }
+
+ if (!mDormantTimer) {
+ mDormantTimer = do_CreateInstance("@mozilla.org/timer;1");
+ }
+ mDormantTimer->InitWithFuncCallback(&MediaDecoder::DormantTimerExpired,
+ this,
+ mHeuristicDormantTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void MediaDecoder::CancelDormantTimer()
+{
+ if (mDormantTimer) {
+ mDormantTimer->Cancel();
+ }
+}
+
void MediaDecoder::Pause()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mPlayState == PLAY_STATE_LOADING ||
mPlayState == PLAY_STATE_SEEKING ||
mPlayState == PLAY_STATE_ENDED) {
mNextState = PLAY_STATE_PAUSED;
@@ -467,17 +539,23 @@ MediaDecoder::MediaDecoder() :
mInfiniteStream(false),
mOwner(nullptr),
mPlaybackStatistics(new MediaChannelStatistics()),
mPinnedForSeek(false),
mShuttingDown(false),
mPausedForPlaybackRateNull(false),
mMinimizePreroll(false),
mMediaTracksConstructed(false),
- mIsDormant(false)
+ mIsDormant(false),
+ mIsHeuristicDormantSupported(
+ Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)),
+ mHeuristicDormantTimeout(
+ Preferences::GetInt("media.decoder.heuristic.dormant.timeout",
+ DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS)),
+ mIsHeuristicDormant(false)
{
MOZ_COUNT_CTOR(MediaDecoder);
MOZ_ASSERT(NS_IsMainThread());
MediaMemoryTracker::AddMediaDecoder(this);
#ifdef PR_LOGGING
if (!gMediaDecoderLog) {
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
}
@@ -516,16 +594,18 @@ void MediaDecoder::Shutdown()
}
// Force any outstanding seek and byterange requests to complete
// to prevent shutdown from deadlocking.
if (mResource) {
mResource->Close();
}
+ CancelDormantTimer();
+
ChangeState(PLAY_STATE_SHUTDOWN);
mOwner = nullptr;
MediaShutdownManager::Instance().Unregister(this);
}
MediaDecoder::~MediaDecoder()
@@ -617,16 +697,18 @@ nsresult MediaDecoder::ScheduleStateMach
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
return mDecoderStateMachine->ScheduleStateMachine();
}
nsresult MediaDecoder::Play()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+ UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
+
NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
if (mPausedForPlaybackRateNull) {
return NS_OK;
}
nsresult res = ScheduleStateMachineThread();
NS_ENSURE_SUCCESS(res,res);
if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) {
mNextState = PLAY_STATE_PLAYING;
@@ -639,16 +721,17 @@ nsresult MediaDecoder::Play()
ChangeState(PLAY_STATE_PLAYING);
return NS_OK;
}
nsresult MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+ UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */);
NS_ABORT_IF_FALSE(aTime >= 0.0, "Cannot seek to a negative value.");
int64_t timeUsecs = 0;
nsresult rv = SecondsToUsecs(aTime, timeUsecs);
NS_ENSURE_SUCCESS(rv, rv);
mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType);
@@ -1187,16 +1270,20 @@ void MediaDecoder::ChangeState(PlayState
if (mPlayState == PLAY_STATE_PLAYING) {
ConstructMediaTracks();
} else if (mPlayState == PLAY_STATE_ENDED) {
RemoveMediaTracks();
}
ApplyStateToStateMachine(mPlayState);
+ CancelDormantTimer();
+ // Start dormant timer if necessary
+ StartDormantTimer();
+
GetReentrantMonitor().NotifyAll();
}
void MediaDecoder::ApplyStateToStateMachine(PlayState aState)
{
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -183,16 +183,17 @@ destroying the MediaDecoder object.
*/
#if !defined(MediaDecoder_h_)
#define MediaDecoder_h_
#include "nsISupports.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsAutoPtr.h"
+#include "nsITimer.h"
#include "MediaResource.h"
#include "mozilla/dom/AudioChannelBinding.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/ReentrantMonitor.h"
#include "MediaStreamGraph.h"
#include "AbstractMediaDecoder.h"
#include "necko-config.h"
#ifdef MOZ_EME
@@ -362,16 +363,18 @@ public:
// Notify activity of the decoder owner is changed.
// Based on the activity, dormant state is updated.
// Dormant state is a state to free all scarce media resources
// (like hw video codec), did not decoding and stay dormant.
// It is used to share scarece media resources in system.
virtual void NotifyOwnerActivityChanged();
+ void UpdateDormantState(bool aDormantTimeout, bool aActivity);
+
// Pause video playback.
virtual void Pause();
// Adjust the speed of the playback, optionally with pitch correction,
virtual void SetVolume(double aVolume);
// Sets whether audio is being captured. If it is, we won't play any
// of our audio.
virtual void SetAudioCaptured(bool aCaptured);
@@ -1017,16 +1020,24 @@ public:
{
GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded);
}
protected:
virtual ~MediaDecoder();
void SetStateMachineParameters();
+ static void DormantTimerExpired(nsITimer *aTimer, void *aClosure);
+
+ // Start a timer for heuristic dormant.
+ void StartDormantTimer();
+
+ // Cancel a timer for heuristic dormant.
+ void CancelDormantTimer();
+
/******
* The following members should be accessed with the decoder lock held.
******/
// Current decoding position in the stream. This is where the decoder
// is up to consuming the stream. This is not adjusted during decoder
// seek operations, but it's updated at the end when we start playing
// back again.
@@ -1214,13 +1225,25 @@ protected:
bool mMediaTracksConstructed;
// Stores media info, including info of audio tracks and video tracks, should
// only be accessed from main thread.
nsAutoPtr<MediaInfo> mInfo;
// True if MediaDecoder is in dormant state.
bool mIsDormant;
+
+ // True if heuristic dormant is supported.
+ const bool mIsHeuristicDormantSupported;
+
+ // Timeout ms of heuristic dormant timer.
+ const int mHeuristicDormantTimeout;
+
+ // True if MediaDecoder is in dormant by heuristic.
+ bool mIsHeuristicDormant;
+
+ // Timer to schedule updating dormant state.
+ nsCOMPtr<nsITimer> mDormantTimer;
};
} // namespace mozilla
#endif