Bug 778077 - Implement HTMLMediaElement.fastSeek(time). r=cajbir

Implement HTMLMediaElement.fastSeek(), basically by changing all the
MediaDecoderReader::Seek() overrides to not call
MediaDecoderReader::DecodeToTarget(), and have MediaDecoderReader::DecodeSeek()
call DecodeToTarget() if we're doing an accurate (non-fast) seek.

Update gizmo.mp4 to have a keyframe every second, instead of only 1 keyframe at
the start of stream. This makes the unit test I added more useful for mp4...

I pushed most of the seek target clamping logic in MediaDecoder up into
HTMLMediaElement, so that we're clamping in fewer places. Note
MediaDecoderStateMachine::Seek() still sanity checks the seek target.

We have to update the currentTime/MediaDecoder playback position after a seek
completes now, rather than assuming the seek always got it exactly right.

Removed those pesky assertions about seek target lying in the first frame after
seek, since actually sometimes the media doesn't have samples for all streams
after a seek (either due to the media being encoded like that, or because of a
bug in the platform's decoder, not entirely sure).

Green: https://tbpl.mozilla.org/?tree=Try&rev=b028258565e2
This commit is contained in:
Chris Pearce
2014-03-28 15:50:28 +13:00
parent 8aecd20a8d
commit 73b32c2026
26 changed files with 439 additions and 178 deletions

View File

@@ -166,7 +166,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mPlayDuration(0),
mStartTime(-1),
mEndTime(-1),
mSeekTime(0),
mFragmentEndTime(-1),
mReader(aReader),
mCurrentFrameTime(0),
@@ -1415,7 +1414,7 @@ void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer,
}
}
void MediaDecoderStateMachine::Seek(double aTime)
void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -1431,26 +1430,22 @@ void MediaDecoderStateMachine::Seek(double aTime)
"We shouldn't already be seeking");
NS_ASSERTION(mState >= DECODER_STATE_DECODING,
"We should have loaded metadata");
double t = aTime * static_cast<double>(USECS_PER_S);
if (t > INT64_MAX) {
// Prevent integer overflow.
return;
}
mSeekTime = static_cast<int64_t>(t) + mStartTime;
NS_ASSERTION(mSeekTime >= mStartTime && mSeekTime <= mEndTime,
"Can only seek in range [0,duration]");
// Bound the seek time to be inside the media range.
NS_ASSERTION(mStartTime != -1, "Should know start time by now");
NS_ASSERTION(mEndTime != -1, "Should know end time by now");
mSeekTime = std::min(mSeekTime, mEndTime);
mSeekTime = std::max(mStartTime, mSeekTime);
mBasePosition = mSeekTime - mStartTime;
DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime));
int64_t seekTime = aTarget.mTime + mStartTime;
seekTime = std::min(seekTime, mEndTime);
seekTime = std::max(mStartTime, seekTime);
NS_ASSERTION(seekTime >= mStartTime && seekTime <= mEndTime,
"Can only seek in range [0,duration]");
mSeekTarget = SeekTarget(seekTime, aTarget.mType);
mBasePosition = seekTime - mStartTime;
DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %ld)", mDecoder.get(), mSeekTarget.mTime));
mState = DECODER_STATE_SEEKING;
if (mDecoder->GetDecodedStream()) {
mDecoder->RecreateDecodedStream(mSeekTime - mStartTime);
mDecoder->RecreateDecodedStream(seekTime - mStartTime);
}
ScheduleStateMachine();
}
@@ -1941,11 +1936,11 @@ void MediaDecoderStateMachine::DecodeSeek()
// the lock since it won't deadlock. We check the state when
// acquiring the lock again in case shutdown has occurred
// during the time when we didn't have the lock.
int64_t seekTime = mSeekTime;
int64_t seekTime = mSeekTarget.mTime;
mDecoder->StopProgressUpdates();
bool currentTimeChanged = false;
int64_t mediaTime = GetMediaTime();
const int64_t mediaTime = GetMediaTime();
if (mediaTime != seekTime) {
currentTimeChanged = true;
// Stop playback now to ensure that while we're outside the monitor
@@ -1965,6 +1960,7 @@ void MediaDecoderStateMachine::DecodeSeek()
NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
}
int64_t newCurrentTime = seekTime;
if (currentTimeChanged) {
// The seek target is different than the current playback position,
// we'll need to seek the playback position, so shutdown our decode
@@ -1980,23 +1976,33 @@ void MediaDecoderStateMachine::DecodeSeek()
mStartTime,
mEndTime,
mediaTime);
if (NS_SUCCEEDED(res) && mSeekTarget.mType == SeekTarget::Accurate) {
res = mReader->DecodeToTarget(seekTime);
}
}
if (NS_SUCCEEDED(res)) {
AudioData* audio = HasAudio() ? mReader->AudioQueue().PeekFront() : nullptr;
MOZ_ASSERT(!audio ||
(audio->mTime <= seekTime &&
seekTime <= audio->mTime + audio->mDuration) ||
mReader->AudioQueue().IsFinished(),
"Seek target should lie inside the first audio block after seek");
int64_t startTime = (audio && audio->mTime < seekTime) ? audio->mTime : seekTime;
mAudioStartTime = startTime;
mPlayDuration = startTime - mStartTime;
int64_t nextSampleStartTime = 0;
VideoData* video = nullptr;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
video = mReader->FindStartTime(nextSampleStartTime);
}
// Setup timestamp state.
if (seekTime == mEndTime) {
newCurrentTime = mAudioStartTime = seekTime;
} else if (HasAudio()) {
AudioData* audio = mReader->AudioQueue().PeekFront();
newCurrentTime = mAudioStartTime = audio ? audio->mTime : seekTime;
} else {
newCurrentTime = video ? video->mTime : seekTime;
}
mPlayDuration = newCurrentTime - mStartTime;
if (HasVideo()) {
VideoData* video = mReader->VideoQueue().PeekFront();
if (video) {
MOZ_ASSERT((video->mTime <= seekTime && seekTime <= video->GetEndTime()) ||
mReader->VideoQueue().IsFinished(),
"Seek target should lie inside the first frame after seek, unless it's the last frame.");
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
RenderVideoFrame(video, TimeStamp::Now());
@@ -2017,10 +2023,6 @@ void MediaDecoderStateMachine::DecodeSeek()
return;
}
// Try to decode another frame to detect if we're at the end...
DECODER_LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
mDecoder.get(), mCurrentFrameTime));
// Change state to DECODING or COMPLETED now. SeekingStopped will
// call MediaDecoderStateMachine::Seek to reset our state to SEEKING
// if we need to seek again.
@@ -2046,6 +2048,18 @@ void MediaDecoderStateMachine::DecodeSeek()
stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
StartDecoding();
}
if (newCurrentTime != mediaTime) {
UpdatePlaybackPositionInternal(newCurrentTime);
if (mDecoder->GetDecodedStream()) {
SetSyncPointForMediaStream();
}
}
// Try to decode another frame to detect if we're at the end...
DECODER_LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
mDecoder.get(), mCurrentFrameTime));
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
@@ -2844,7 +2858,7 @@ void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
if (!HasAudio()) {
// mBasePosition is a position in the video stream, not an absolute time.
if (mState == DECODER_STATE_SEEKING) {
mBasePosition = mSeekTime - mStartTime;
mBasePosition = mSeekTarget.mTime - mStartTime;
} else {
mBasePosition = GetVideoStreamPosition();
}