Commit 0e5c161c authored by vrk@chromium.org's avatar vrk@chromium.org

Cancel pending seek in ChunkDemuxer when a new seek occurs

This makes it so playback doesn't stall if one seeks into an unbuffered
region then seeks back into a buffered region.

BUG=132935


Review URL: https://chromiumcodereview.appspot.com/10835044

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150489 0039d316-1c4b-4281-b951-d872f2087c98
parent a4f3d05f
...@@ -173,6 +173,7 @@ class ChunkDemuxerStream : public DemuxerStream { ...@@ -173,6 +173,7 @@ class ChunkDemuxerStream : public DemuxerStream {
void StartWaitingForSeek(); void StartWaitingForSeek();
void Seek(TimeDelta time); void Seek(TimeDelta time);
void CancelPendingSeek();
bool IsSeekPending() const; bool IsSeekPending() const;
// Add buffers to this stream. Buffers are stored in SourceBufferStreams, // Add buffers to this stream. Buffers are stored in SourceBufferStreams,
...@@ -214,6 +215,7 @@ class ChunkDemuxerStream : public DemuxerStream { ...@@ -214,6 +215,7 @@ class ChunkDemuxerStream : public DemuxerStream {
enum State { enum State {
RETURNING_DATA_FOR_READS, RETURNING_DATA_FOR_READS,
WAITING_FOR_SEEK, WAITING_FOR_SEEK,
CANCELED,
SHUTDOWN, SHUTDOWN,
}; };
...@@ -280,12 +282,29 @@ void ChunkDemuxerStream::Seek(TimeDelta time) { ...@@ -280,12 +282,29 @@ void ChunkDemuxerStream::Seek(TimeDelta time) {
DCHECK(read_cbs_.empty()); DCHECK(read_cbs_.empty());
// Ignore seek requests when canceled.
if (state_ == CANCELED)
return;
stream_->Seek(time); stream_->Seek(time);
if (state_ == WAITING_FOR_SEEK) if (state_ == WAITING_FOR_SEEK)
ChangeState_Locked(RETURNING_DATA_FOR_READS); ChangeState_Locked(RETURNING_DATA_FOR_READS);
} }
void ChunkDemuxerStream::CancelPendingSeek() {
DVLOG(1) << "CancelPendingSeek()";
ReadCBQueue read_cbs;
{
base::AutoLock auto_lock(lock_);
ChangeState_Locked(CANCELED);
std::swap(read_cbs_, read_cbs);
}
for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
it->Run(kAborted, NULL);
}
bool ChunkDemuxerStream::IsSeekPending() const { bool ChunkDemuxerStream::IsSeekPending() const {
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
return stream_->IsSeekPending(); return stream_->IsSeekPending();
...@@ -490,6 +509,7 @@ bool ChunkDemuxerStream::GetNextBuffer_Locked( ...@@ -490,6 +509,7 @@ bool ChunkDemuxerStream::GetNextBuffer_Locked(
return true; return true;
} }
break; break;
case CANCELED:
case WAITING_FOR_SEEK: case WAITING_FOR_SEEK:
// Null buffers should be returned in this state since we are waiting // Null buffers should be returned in this state since we are waiting
// for a seek. Any buffers in the SourceBuffer should NOT be returned // for a seek. Any buffers in the SourceBuffer should NOT be returned
...@@ -540,6 +560,7 @@ void ChunkDemuxer::Stop(const base::Closure& callback) { ...@@ -540,6 +560,7 @@ void ChunkDemuxer::Stop(const base::Closure& callback) {
void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) { void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) {
DVLOG(1) << "Seek(" << time.InSecondsF() << ")"; DVLOG(1) << "Seek(" << time.InSecondsF() << ")";
DCHECK(time >= start_time_); DCHECK(time >= start_time_);
DCHECK(seek_cb_.is_null());
PipelineStatus status = PIPELINE_ERROR_INVALID_STATE; PipelineStatus status = PIPELINE_ERROR_INVALID_STATE;
{ {
...@@ -605,6 +626,24 @@ void ChunkDemuxer::StartWaitingForSeek() { ...@@ -605,6 +626,24 @@ void ChunkDemuxer::StartWaitingForSeek() {
ChangeState_Locked(INITIALIZED); ChangeState_Locked(INITIALIZED);
} }
void ChunkDemuxer::CancelPendingSeek() {
PipelineStatusCB cb;
{
base::AutoLock auto_lock(lock_);
if (IsSeekPending_Locked() && !seek_cb_.is_null()) {
std::swap(cb, seek_cb_);
}
if (audio_)
audio_->CancelPendingSeek();
if (video_)
video_->CancelPendingSeek();
}
if (!cb.is_null())
cb.Run(PIPELINE_OK);
}
ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id, ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
const std::string& type, const std::string& type,
std::vector<std::string>& codecs) { std::vector<std::string>& codecs) {
......
...@@ -47,6 +47,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { ...@@ -47,6 +47,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
// Methods used by an external object to control this demuxer. // Methods used by an external object to control this demuxer.
void StartWaitingForSeek(); void StartWaitingForSeek();
void CancelPendingSeek();
// Registers a new |id| to use for AppendData() calls. |type| indicates // Registers a new |id| to use for AppendData() calls. |type| indicates
// the MIME type for the data that we intend to append for this ID. // the MIME type for the data that we intend to append for this ID.
......
...@@ -86,6 +86,14 @@ static void OnReadDone(const base::TimeDelta& expected_time, ...@@ -86,6 +86,14 @@ static void OnReadDone(const base::TimeDelta& expected_time,
*called = true; *called = true;
} }
static void OnReadDone_AbortExpected(
bool* called, DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
EXPECT_EQ(status, DemuxerStream::kAborted);
EXPECT_EQ(NULL, buffer.get());
*called = true;
}
static void OnReadDone_EOSExpected(bool* called, static void OnReadDone_EOSExpected(bool* called,
DemuxerStream::Status status, DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) { const scoped_refptr<DecoderBuffer>& buffer) {
...@@ -1489,6 +1497,79 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) { ...@@ -1489,6 +1497,79 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) {
GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration); GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
} }
TEST_F(ChunkDemuxerTest, TestSeekCanceled) {
ASSERT_TRUE(InitDemuxer(true, true, false));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Append cluster at the beginning of the stream.
scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
// Seek to an unbuffered region.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(50),
NewExpectedStatusCB(PIPELINE_OK));
// Attempt to read in unbuffered area; should not fulfill the read.
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
EXPECT_FALSE(audio_read_done);
EXPECT_FALSE(video_read_done);
// Now cancel the pending seek, which should flush the reads with empty
// buffers.
demuxer_->CancelPendingSeek();
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
// A seek back to the buffered region should succeed.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 4, audio, video);
}
TEST_F(ChunkDemuxerTest, TestSeekCanceledWhileWaitingForSeek) {
ASSERT_TRUE(InitDemuxer(true, true, false));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Append cluster at the beginning of the stream.
scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
// Start waiting for a seek.
demuxer_->StartWaitingForSeek();
// Now cancel the upcoming seek to an unbuffered region.
demuxer_->CancelPendingSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(50),
NewExpectedStatusCB(PIPELINE_OK));
// Read requests should be fulfilled with empty buffers.
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
// A seek back to the buffered region should succeed.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 4, audio, video);
}
// Test that Seek() successfully seeks to all source IDs. // Test that Seek() successfully seeks to all source IDs.
TEST_F(ChunkDemuxerTest, TestSeekAudioAndVideoSources) { TEST_F(ChunkDemuxerTest, TestSeekAudioAndVideoSources) {
std::string audio_id = "audio1"; std::string audio_id = "audio1";
......
...@@ -313,6 +313,7 @@ void WebMediaPlayerImpl::seek(float seconds) { ...@@ -313,6 +313,7 @@ void WebMediaPlayerImpl::seek(float seconds) {
if (seeking_) { if (seeking_) {
pending_seek_ = true; pending_seek_ = true;
pending_seek_seconds_ = seconds; pending_seek_seconds_ = seconds;
proxy_->DemuxerCancelPendingSeek();
return; return;
} }
......
...@@ -188,6 +188,11 @@ void WebMediaPlayerProxy::DemuxerStartWaitingForSeek() { ...@@ -188,6 +188,11 @@ void WebMediaPlayerProxy::DemuxerStartWaitingForSeek() {
chunk_demuxer_->StartWaitingForSeek(); chunk_demuxer_->StartWaitingForSeek();
} }
void WebMediaPlayerProxy::DemuxerCancelPendingSeek() {
if (chunk_demuxer_.get())
chunk_demuxer_->CancelPendingSeek();
}
media::ChunkDemuxer::Status WebMediaPlayerProxy::DemuxerAddId( media::ChunkDemuxer::Status WebMediaPlayerProxy::DemuxerAddId(
const std::string& id, const std::string& id,
const std::string& type, const std::string& type,
......
...@@ -88,6 +88,7 @@ class WebMediaPlayerProxy ...@@ -88,6 +88,7 @@ class WebMediaPlayerProxy
// Methods for Demuxer communication. // Methods for Demuxer communication.
void DemuxerStartWaitingForSeek(); void DemuxerStartWaitingForSeek();
void DemuxerCancelPendingSeek();
media::ChunkDemuxer::Status DemuxerAddId(const std::string& id, media::ChunkDemuxer::Status DemuxerAddId(const std::string& id,
const std::string& type, const std::string& type,
std::vector<std::string>& codecs); std::vector<std::string>& codecs);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment