Bug 1269408: P1. Retry InternalSeek if previous attempt failed once more data is available. r?gerald
MozReview-Commit-ID: Jro2PRtGX7c
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -515,17 +515,20 @@ MediaFormatReader::RequestVideoData(bool
}
if (mShutdown) {
NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
return MediaDataPromise::CreateAndReject(CANCELED, __func__);
}
media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
- if (ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
+ // Ensure we have no pending seek going as ShouldSkip could return out of date
+ // information.
+ if (!mVideo.HasInternalSeekPending() &&
+ ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
// Cancel any pending demux request.
mVideo.mDemuxRequest.DisconnectIfExists();
// I think it's still possible for an output to have been sent from the decoder
// and is currently sitting in our event queue waiting to be processed. The following
// flush won't clear it, and when we return to the event loop it'll be added to our
// output queue and be used.
// This code will count that as dropped, which was the intent, but not quite true.
@@ -742,16 +745,17 @@ MediaFormatReader::NeedInput(DecoderData
// decoded sample. To account for H.264 streams which may require a longer
// run of input than we input, decoders fire an "input exhausted" callback,
// which overrides our "few more samples" threshold.
return
!aDecoder.mDraining &&
!aDecoder.mError &&
aDecoder.mDecodingRequested &&
!aDecoder.mDemuxRequest.Exists() &&
+ !aDecoder.HasInternalSeekPending() &&
aDecoder.mOutput.Length() <= aDecoder.mDecodeAhead &&
(aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
aDecoder.mTimeThreshold.isSome() ||
aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput <= aDecoder.mDecodeAhead);
}
void
MediaFormatReader::ScheduleUpdate(TrackType aTrack)
@@ -779,18 +783,32 @@ MediaFormatReader::UpdateReceivedNewData
if (!decoder.mReceivedNewData) {
return false;
}
// Update our cached TimeRange.
decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
- if (decoder.mDrainComplete || decoder.mDraining ||
- decoder.mDemuxRequest.Exists()) {
+ // We do not want to clear mWaitingForData while there are pending
+ // demuxing or seeking operations that could affect the value of this flag.
+ // This is in order to ensure that we will retry once they complete as we may
+ // now have new data that could potentially allow those operations to
+ // successfully complete if tried again.
+ if (decoder.mSeekRequest.Exists()) {
+ // Nothing more to do until this operation complete.
+ return true;
+ }
+ if (decoder.mDemuxRequest.Exists()) {
+ // We may have pending operations to process, so we want to continue
+ // after UpdateReceivedNewData returns.
+ return false;
+ }
+
+ if (decoder.mDrainComplete || decoder.mDraining) {
// We do not want to clear mWaitingForData or mDemuxEOS while
// a drain is in progress in order to properly complete the operation.
return false;
}
bool hasLastEnd;
media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
if (hasLastEnd) {
@@ -805,20 +823,27 @@ MediaFormatReader::UpdateReceivedNewData
if (decoder.mTimeThreshold) {
decoder.mTimeThreshold.ref().mWaiting = false;
}
decoder.mWaitingForData = false;
if (decoder.mError) {
return false;
}
- if (decoder.HasWaitingPromise()) {
- MOZ_ASSERT(!decoder.HasPromise());
- LOG("We have new data. Resolving WaitingPromise");
- decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
+
+ if (decoder.HasInternalSeekPending() || decoder.HasWaitingPromise()) {
+ if (decoder.HasInternalSeekPending()) {
+ LOG("Attempting Internal Seek");
+ InternalSeek(aTrack, decoder.mTimeThreshold.ref());
+ }
+ if (decoder.HasWaitingPromise()) {
+ MOZ_ASSERT(!decoder.HasPromise());
+ LOG("We have new data. Resolving WaitingPromise");
+ decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
+ }
return true;
}
if (!mSeekPromise.IsEmpty()) {
MOZ_ASSERT(!decoder.HasPromise());
if (mVideo.mSeekRequest.Exists() || mAudio.mSeekRequest.Exists()) {
// Already waiting for a seek to complete. Nothing more to do.
return true;
}
@@ -969,45 +994,55 @@ MediaFormatReader::HandleDemuxedSamples(
// We have serviced the decoder's request for more data.
decoder.mInputExhausted = false;
}
void
MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget)
{
MOZ_ASSERT(OnTaskQueue());
+ LOG("%s internal seek to %f",
+ TrackTypeToStr(aTrack), aTarget.mTime.ToSeconds());
+
auto& decoder = GetDecoderData(aTrack);
decoder.mTimeThreshold = Some(aTarget);
RefPtr<MediaFormatReader> self = this;
decoder.ResetDemuxer();
+ decoder.mTimeThreshold = Some(aTarget);
+ RefPtr<MediaFormatReader> self = this;
decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().mTime)
->Then(OwnerThread(), __func__,
[self, aTrack] (media::TimeUnit aTime) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mSeekRequest.Complete();
+ MOZ_ASSERT(decoder.mTimeThreshold,
+ "Seek promise must be disconnected when timethreshold is reset");
+ decoder.mTimeThreshold.ref().mHasSeeked = true;
self->NotifyDecodingRequested(aTrack);
},
[self, aTrack] (DemuxerFailureReason aResult) {
auto& decoder = self->GetDecoderData(aTrack);
decoder.mSeekRequest.Complete();
switch (aResult) {
case DemuxerFailureReason::WAITING_FOR_DATA:
self->NotifyWaitingForData(aTrack);
break;
case DemuxerFailureReason::END_OF_STREAM:
+ decoder.mTimeThreshold.reset();
self->NotifyEndOfStream(aTrack);
break;
case DemuxerFailureReason::CANCELED:
case DemuxerFailureReason::SHUTDOWN:
+ decoder.mTimeThreshold.reset();
break;
default:
+ decoder.mTimeThreshold.reset();
self->NotifyError(aTrack);
break;
}
- decoder.mTimeThreshold.reset();
}));
}
void
MediaFormatReader::DrainDecoder(TrackType aTrack)
{
MOZ_ASSERT(OnTaskQueue());
@@ -1049,16 +1084,26 @@ MediaFormatReader::Update(TrackType aTra
return;
}
if (UpdateReceivedNewData(aTrack)) {
LOGV("Nothing more to do");
return;
}
+ if (decoder.mSeekRequest.Exists()) {
+ LOGV("Seeking hasn't completed, nothing more to do");
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!decoder.HasInternalSeekPending() ||
+ (!decoder.mOutput.Length() &&
+ !decoder.mQueuedSamples.Length()),
+ "No frames can be demuxed or decoded while an internal seek is pending");
+
// Record number of frames decoded and parsed. Automatically update the
// stats counters using the AutoNotifyDecoded stack-based class.
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
// Drop any frames found prior our internal seek target.
while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
RefPtr<MediaData>& output = decoder.mOutput[0];
InternalSeekTarget target = decoder.mTimeThreshold.ref();
@@ -1120,17 +1165,17 @@ MediaFormatReader::Update(TrackType aTra
if (!decoder.mReceivedNewData) {
LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
decoder.RejectPromise(WAITING_FOR_DATA, __func__);
}
}
// Now that draining has completed, we check if we have received
// new data again as the result may now be different from the earlier
// run.
- if (UpdateReceivedNewData(aTrack)) {
+ if (UpdateReceivedNewData(aTrack) || decoder.mSeekRequest.Exists()) {
LOGV("Nothing more to do");
return;
}
}
}
if (decoder.mNeedDraining) {
DrainDecoder(aTrack);
@@ -1241,18 +1286,16 @@ MediaFormatReader::WaitForData(MediaData
}
nsresult
MediaFormatReader::ResetDecode()
{
MOZ_ASSERT(OnTaskQueue());
LOGV("");
- mAudio.mSeekRequest.DisconnectIfExists();
- mVideo.mSeekRequest.DisconnectIfExists();
mSeekPromise.RejectIfExists(NS_OK, __func__);
mSkipRequest.DisconnectIfExists();
// Do the same for any data wait promises.
mAudio.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::CANCELED), __func__);
mVideo.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::CANCELED), __func__);
// Reset miscellaneous seeking state.
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -132,22 +132,25 @@ private:
bool DecodeDemuxedSamples(TrackType aTrack,
MediaRawData* aSample);
struct InternalSeekTarget {
InternalSeekTarget(const media::TimeUnit& aTime, bool aDropTarget)
: mTime(aTime)
, mDropTarget(aDropTarget)
, mWaiting(false)
+ , mHasSeeked(false)
{}
media::TimeUnit mTime;
bool mDropTarget;
bool mWaiting;
+ bool mHasSeeked;
};
+
// Perform an internal seek to aTime. If aDropTarget is true then
// the first sample past the target will be dropped.
void InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget);
// Drain the current decoder.
void DrainDecoder(TrackType aTrack);
void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
void NotifyInputExhausted(TrackType aTrack);
@@ -286,16 +289,18 @@ private:
return !mWaitingPromise.IsEmpty();
}
// MediaDataDecoder handler's variables.
// Decoder initialization promise holder.
MozPromiseRequestHolder<MediaDataDecoder::InitPromise> mInitPromise;
// False when decoder is created. True when decoder Init() promise is resolved.
bool mDecoderInitialized;
+ // Set when decoding can proceed. It is reset when a decoding promise is
+ // rejected or prior a seek operation.
bool mDecodingRequested;
bool mOutputRequested;
bool mInputExhausted;
bool mError;
bool mNeedDraining;
bool mDraining;
bool mDrainComplete;
// If set, all decoded samples prior mTimeThreshold will be dropped.
@@ -322,16 +327,17 @@ private:
virtual bool HasPromise() = 0;
virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
const char* aMethodName) = 0;
void ResetDemuxer()
{
// Clear demuxer related data.
mDemuxRequest.DisconnectIfExists();
+ mSeekRequest.DisconnectIfExists();
mTrackDemuxer->Reset();
}
void ResetState()
{
MOZ_ASSERT(mOwner->OnTaskQueue());
mDemuxEOS = false;
mWaitingForData = false;
@@ -348,16 +354,21 @@ private:
mLastSampleTime.reset();
mOutput.Clear();
mNumSamplesInput = 0;
mNumSamplesOutput = 0;
mSizeOfQueue = 0;
mNextStreamSourceID.reset();
}
+ bool HasInternalSeekPending() const
+ {
+ return mTimeThreshold && !mTimeThreshold.ref().mHasSeeked;
+ }
+
// Used by the MDSM for logging purposes.
Atomic<size_t> mSizeOfQueue;
// Used by the MDSM to determine if video decoding is hardware accelerated.
// This value is updated after a frame is successfully decoded.
Atomic<bool> mIsHardwareAccelerated;
// Sample format monitoring.
uint32_t mLastStreamSourceID;
Maybe<uint32_t> mNextStreamSourceID;