Bug 654787 - part6: Correct the playback position while looping; r=jwwang
MozReview-Commit-ID: 4h2zgtbVBVq
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1934,16 +1934,18 @@ public:
// StopPlayback in order to reset the IsPlaying() state so audio
// is restarted correctly.
mMaster->StopPlayback();
if (!mSentPlaybackEndedEvent) {
auto clockTime =
std::max(mMaster->AudioEndTime(), mMaster->VideoEndTime());
+ // Correct the time over the end once looping was turned on.
+ Reader()->AdjustByLooping(clockTime);
if (mMaster->mDuration.Ref()->IsInfinite()) {
// We have a finite duration when playback reaches the end.
mMaster->mDuration = Some(clockTime);
}
mMaster->UpdatePlaybackPosition(clockTime);
// Ensure readyState is updated before firing the 'ended' event.
mMaster->mOnNextFrameStatus.Notify(
@@ -3467,29 +3469,35 @@ MediaDecoderStateMachine::UpdatePlayback
return;
}
// Cap the current time to the larger of the audio and video end time.
// This ensures that if we're running off the system clock, we don't
// advance the clock to after the media end time.
if (VideoEndTime() > TimeUnit::Zero() || AudioEndTime() > TimeUnit::Zero()) {
- const auto clockTime = GetClock();
+ auto clockTime = GetClock();
+
+ // Once looping was turned on, the time is probably larger than the duration
+ // of the media track, so the time over the end should be corrected.
+ mReader->AdjustByLooping(clockTime);
+ bool loopback = clockTime < GetMediaTime();
+
// Skip frames up to the frame at the playback position, and figure out
// the time remaining until it's time to display the next frame and drop
// the current frame.
NS_ASSERTION(clockTime >= TimeUnit::Zero(), "Should have positive clock time.");
// These will be non -1 if we've displayed a video frame, or played an audio
// frame.
auto maxEndTime = std::max(VideoEndTime(), AudioEndTime());
auto t = std::min(clockTime, maxEndTime);
// FIXME: Bug 1091422 - chained ogg files hit this assertion.
//MOZ_ASSERT(t >= GetMediaTime());
- if (t > GetMediaTime()) {
+ if (loopback || t > GetMediaTime()) {
UpdatePlaybackPosition(t);
}
}
// Note we have to update playback position before releasing the monitor.
// Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
// the monitor and get a staled value from GetCurrentTimeUs() which hits the
// assertion in GetClock().
--- a/dom/media/ReaderProxy.cpp
+++ b/dom/media/ReaderProxy.cpp
@@ -76,16 +76,21 @@ ReaderProxy::OnAudioDataRequestFailed(co
return AudioDataPromise::CreateAndReject(aError, __func__);
}
// The data time in the audio queue is assumed to be increased linearly,
// so we need to add the last ending time as the offset to correct the
// audio data time in the next round when seamless looping is enabled.
mLoopingOffset = mLastAudioEndTime;
+ // Save the duration of the audio track if it hasn't been set.
+ if (!mAudioDuration.IsValid()) {
+ mAudioDuration = mLastAudioEndTime;
+ }
+
// For seamless looping, the demuxer is sought to the beginning and then
// keep requesting decoded data in advance, upon receiving EOS.
// The MDSM will not be aware of the EOS and keep receiving decoded data
// as usual while looping is on.
RefPtr<ReaderProxy> self = this;
RefPtr<MediaFormatReader> reader = mReader;
ResetDecode(TrackInfo::kAudioTrack);
return SeekInternal(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
@@ -155,16 +160,17 @@ ReaderProxy::RequestVideoData(const medi
RefPtr<ReaderProxy::SeekPromise>
ReaderProxy::Seek(const SeekTarget& aTarget)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mSeamlessLoopingBlocked = true;
// Reset the members for seamless looping if the seek is triggered outside.
mLoopingOffset = media::TimeUnit::Zero();
mLastAudioEndTime = media::TimeUnit::Zero();
+ mAudioDuration = media::TimeUnit::Invalid();
return SeekInternal(aTarget);
}
RefPtr<ReaderProxy::SeekPromise>
ReaderProxy::SeekInternal(const SeekTarget& aTarget)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
SeekTarget adjustedTarget = aTarget;
@@ -281,9 +287,20 @@ ReaderProxy::SetCanonicalDuration(
void
ReaderProxy::SetSeamlessLoopingEnabled(bool aEnabled)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mSeamlessLoopingEnabled = aEnabled;
}
+void
+ReaderProxy::AdjustByLooping(media::TimeUnit& aTime)
+{
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ MOZ_ASSERT(!mShutdown);
+ MOZ_ASSERT(!mSeamlessLoopingEnabled || !mSeamlessLoopingBlocked);
+ if (mAudioDuration.IsValid() && mAudioDuration.IsPositive()) {
+ aTime = aTime % mAudioDuration.ToMicroseconds();
+ }
+}
+
} // namespace mozilla
--- a/dom/media/ReaderProxy.h
+++ b/dom/media/ReaderProxy.h
@@ -81,16 +81,18 @@ public:
void SetVideoBlankDecode(bool aIsBlankDecode);
void SetCanonicalDuration(
AbstractCanonical<media::NullableTimeUnit>* aCanonical);
void SetSeamlessLoopingEnabled(bool aEnabled);
+ void AdjustByLooping(media::TimeUnit& aTime);
+
private:
~ReaderProxy();
RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
void UpdateDuration();
RefPtr<SeekPromise> SeekInternal(const SeekTarget& aTarget);
RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestCompleted(
@@ -109,16 +111,18 @@ private:
// Duration, mirrored from the state machine task queue.
Mirror<media::NullableTimeUnit> mDuration;
// The total duration of audio looping in previous rounds.
media::TimeUnit mLoopingOffset = media::TimeUnit::Zero();
// To keep tracking the latest time of decoded audio data.
media::TimeUnit mLastAudioEndTime = media::TimeUnit::Zero();
+ // The duration of the audio track.
+ media::TimeUnit mAudioDuration = media::TimeUnit::Invalid();
// To prevent seamless looping while seeking.
bool mSeamlessLoopingBlocked;
// Indicates whether we should loop the media.
bool mSeamlessLoopingEnabled;
};
} // namespace mozilla
--- a/dom/media/TimeUnits.h
+++ b/dom/media/TimeUnits.h
@@ -172,16 +172,20 @@ public:
TimeUnit MultDouble(double aVal) const
{
return TimeUnit::FromSeconds(ToSeconds() * aVal);
}
friend TimeUnit operator/(const TimeUnit& aUnit, int aVal)
{
return TimeUnit(aUnit.mValue / aVal);
}
+ friend TimeUnit operator%(const TimeUnit& aUnit, int aVal)
+ {
+ return TimeUnit(aUnit.mValue % aVal);
+ }
bool IsValid() const { return mValue.isValid(); }
constexpr TimeUnit()
: mValue(CheckedInt64(0))
{
}