summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/base/pipeline.cc2
-rw-r--r--media/base/pipeline_status.h1
-rw-r--r--media/filters/chunk_demuxer.cc61
-rw-r--r--media/filters/chunk_demuxer.h3
-rw-r--r--media/filters/chunk_demuxer_unittest.cc165
-rw-r--r--media/filters/source_buffer_stream.cc60
-rw-r--r--media/filters/source_buffer_stream.h12
-rw-r--r--media/filters/source_buffer_stream_unittest.cc95
-rw-r--r--webkit/media/webmediaplayer_impl.cc7
9 files changed, 294 insertions, 112 deletions
diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc
index b585355..658b414 100644
--- a/media/base/pipeline.cc
+++ b/media/base/pipeline.cc
@@ -785,7 +785,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
// kStarting (for each filter)
// kStarted
SetState(kPausing);
- seek_timestamp_ = time;
+ seek_timestamp_ = std::max(time, demuxer_->GetStartTime());
seek_cb_ = seek_cb;
// Kick off seeking!
diff --git a/media/base/pipeline_status.h b/media/base/pipeline_status.h
index 8f1a677..cfce0b4 100644
--- a/media/base/pipeline_status.h
+++ b/media/base/pipeline_status.h
@@ -12,6 +12,7 @@
namespace media {
// Status states for pipeline. All codes except PIPELINE_OK indicate errors.
+// TODO(vrk/scherkus): Trim the unused status codes. (crbug.com/126070)
enum PipelineStatus {
PIPELINE_OK,
PIPELINE_ERROR_URL_NOT_FOUND,
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index ead7956..06a8d629 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -5,6 +5,7 @@
#include "media/filters/chunk_demuxer.h"
#include "base/bind.h"
+#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/string_util.h"
@@ -167,6 +168,9 @@ class ChunkDemuxerStream : public DemuxerStream {
// Append() belong to a media segment that starts at |start_timestamp|.
void OnNewMediaSegment(TimeDelta start_timestamp);
+ // Notifies the stream that it begins at |start_time|.
+ void SetStartTime(TimeDelta start_time);
+
// Called when mid-stream config updates occur.
// Returns true if the new config is accepted.
// Returns false if the new config should trigger an error.
@@ -265,6 +269,11 @@ void ChunkDemuxerStream::OnNewMediaSegment(TimeDelta start_timestamp) {
stream_->OnNewMediaSegment(start_timestamp);
}
+void ChunkDemuxerStream::SetStartTime(TimeDelta start_time) {
+ base::AutoLock auto_lock(lock_);
+ stream_->SetStartTime(start_time);
+}
+
bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
if (buffers.empty())
return false;
@@ -459,7 +468,8 @@ void ChunkDemuxerStream::CreateReadDoneClosures_Locked(ClosureQueue* closures) {
ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client)
: state_(WAITING_FOR_INIT),
host_(NULL),
- client_(client) {
+ client_(client),
+ start_time_(kNoTimestamp()) {
DCHECK(client);
}
@@ -486,6 +496,7 @@ void ChunkDemuxer::Stop(const base::Closure& callback) {
void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) {
DVLOG(1) << "Seek(" << time.InSecondsF() << ")";
+ DCHECK(time >= start_time_);
PipelineStatus status = PIPELINE_ERROR_INVALID_STATE;
{
@@ -534,10 +545,9 @@ scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream(
}
TimeDelta ChunkDemuxer::GetStartTime() const {
- DVLOG(1) << "GetStartTime()";
- // TODO(acolwell) : Fix this so it uses the time on the first packet.
- // (crbug.com/132815)
- return TimeDelta();
+ DCHECK(start_time_ != kNoTimestamp());
+ DVLOG(1) << "GetStartTime(): " << start_time_.InSecondsF();
+ return start_time_;
}
void ChunkDemuxer::StartWaitingForSeek() {
@@ -693,9 +703,9 @@ bool ChunkDemuxer::AppendData(const std::string& id,
switch (state_) {
case INITIALIZING:
+ case WAITING_FOR_START_TIME:
DCHECK_GT(stream_parser_map_.count(id), 0u);
if (!stream_parser_map_[id]->Parse(data, length)) {
- DCHECK_EQ(state_, INITIALIZING);
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return true;
}
@@ -880,7 +890,7 @@ bool ChunkDemuxer::CanEndOfStream_Locked() const {
}
void ChunkDemuxer::OnStreamParserInitDone(bool success, TimeDelta duration) {
- DVLOG(1) << "OnSourceBufferInitDone(" << success << ", "
+ DVLOG(1) << "OnStreamParserInitDone(" << success << ", "
<< duration.InSecondsF() << ")";
lock_.AssertAcquired();
DCHECK_EQ(state_, INITIALIZING);
@@ -899,10 +909,7 @@ void ChunkDemuxer::OnStreamParserInitDone(bool success, TimeDelta duration) {
host_->SetDuration(duration_);
- ChangeState_Locked(INITIALIZED);
- PipelineStatusCB cb;
- std::swap(cb, init_cb_);
- cb.Run(PIPELINE_OK);
+ ChangeState_Locked(WAITING_FOR_START_TIME);
}
bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video,
@@ -950,6 +957,7 @@ bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video,
}
bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) {
+ lock_.AssertAcquired();
DCHECK_NE(state_, SHUTDOWN);
if (!audio_)
@@ -959,6 +967,7 @@ bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) {
}
bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) {
+ lock_.AssertAcquired();
DCHECK_NE(state_, SHUTDOWN);
if (!video_)
@@ -975,13 +984,37 @@ bool ChunkDemuxer::OnNeedKey(scoped_array<uint8> init_data,
void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
TimeDelta start_timestamp) {
- // TODO(vrk): There should be a special case for the first appends where all
- // streams (for both demuxed and muxed case) begin at the earliest stream
- // timestamp. (crbug.com/132815)
+ DVLOG(2) << "OnNewMediaSegment(" << source_id << ", "
+ << start_timestamp.InSecondsF() << ")";
+ lock_.AssertAcquired();
+
+ if (start_time_ == kNoTimestamp()) {
+ DCHECK(state_ == INITIALIZING || state_ == WAITING_FOR_START_TIME);
+ // Use the first reported media segment start time as the |start_time_|
+ // for the demuxer.
+ start_time_ = start_timestamp;
+ }
+
if (audio_ && source_id == source_id_audio_)
audio_->OnNewMediaSegment(start_timestamp);
if (video_ && source_id == source_id_video_)
video_->OnNewMediaSegment(start_timestamp);
+
+ if (state_ != WAITING_FOR_START_TIME)
+ return;
+
+ if (audio_) {
+ audio_->SetStartTime(start_time_);
+ audio_->Seek(start_time_);
+ }
+ if (video_) {
+ video_->SetStartTime(start_time_);
+ video_->Seek(start_time_);
+ }
+
+ // The demuxer is now initialized after the |start_timestamp_| was set.
+ ChangeState_Locked(INITIALIZED);
+ base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
}
} // namespace media
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index ecd2d77..ced6d97 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -87,6 +87,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
enum State {
WAITING_FOR_INIT,
INITIALIZING,
+ WAITING_FOR_START_TIME,
INITIALIZED,
ENDED,
PARSE_ERROR,
@@ -143,6 +144,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
std::string source_id_audio_;
std::string source_id_video_;
+ base::TimeDelta start_time_;
+
DISALLOW_COPY_AND_ASSIGN(ChunkDemuxer);
};
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index cde3417..85e98f6 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -273,14 +273,28 @@ class ChunkDemuxerTest : public testing::Test {
return AppendData(source_id, info_tracks.get(), info_tracks_size);
}
+ bool AppendGarbage() {
+ // Fill up an array with gibberish.
+ int garbage_cluster_size = 10;
+ scoped_array<uint8> garbage_cluster(new uint8[garbage_cluster_size]);
+ for (int i = 0; i < garbage_cluster_size; ++i)
+ garbage_cluster[i] = i;
+ return AppendData(garbage_cluster.get(), garbage_cluster_size);
+ }
+
void InitDoneCalled(PipelineStatus expected_status,
PipelineStatus status) {
EXPECT_EQ(status, expected_status);
}
+ bool AppendEmptyCluster(int timecode) {
+ scoped_ptr<Cluster> empty_cluster = GenerateEmptyCluster(timecode);
+ return AppendData(empty_cluster->data(), empty_cluster->size());
+ }
+
PipelineStatusCB CreateInitDoneCB(const base::TimeDelta& expected_duration,
PipelineStatus expected_status) {
- if (expected_status == PIPELINE_OK)
+ if (expected_duration != kNoTimestamp())
EXPECT_CALL(host_, SetDuration(expected_duration));
return CreateInitDoneCB(expected_status);
}
@@ -296,9 +310,13 @@ class ChunkDemuxerTest : public testing::Test {
PipelineStatus expected_status =
(has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN;
+ base::TimeDelta expected_duration = kNoTimestamp();
+ if (expected_status == PIPELINE_OK)
+ expected_duration = kDefaultDuration();
+
EXPECT_CALL(*client_, DemuxerOpened(_));
demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), expected_status));
+ &host_, CreateInitDoneCB(expected_duration, expected_status));
if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk)
return false;
@@ -322,6 +340,18 @@ class ChunkDemuxerTest : public testing::Test {
return success;
}
+ bool InitDemuxer_ExpectInitFailure() {
+ EXPECT_CALL(*client_, DemuxerOpened(_));
+ demuxer_->Initialize(
+ &host_, CreateInitDoneCB(
+ kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN));
+
+ if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk)
+ return false;
+
+ return AppendInitSegment(true, true, false);
+ }
+
void ShutdownDemuxer() {
if (demuxer_) {
EXPECT_CALL(*client_, DemuxerClosed());
@@ -451,8 +481,9 @@ class ChunkDemuxerTest : public testing::Test {
}
}
- void GenerateExpectedReads(int timecode, int block_count,
- DemuxerStream* stream, int block_duration) {
+ void GenerateSingleStreamExpectedReads(
+ int timecode, int block_count, DemuxerStream* stream,
+ int block_duration) {
CHECK_GT(block_count, 0);
int stream_timecode = timecode;
@@ -624,30 +655,29 @@ TEST_F(ChunkDemuxerTest, TestInit) {
}
}
-// Makes sure that Seek() reports an error if Shutdown()
+// Makes sure that pipeline reports an error if Shutdown()
// is called before the first cluster is passed to the demuxer.
-TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) {
- ASSERT_TRUE(InitDemuxer(true, true, false));
-
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_ERROR_ABORT));
+TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstCluster) {
+ ASSERT_TRUE(InitDemuxer_ExpectInitFailure());
}
// Test that Seek() completes successfully when the first cluster
// arrives.
TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
InSequence s;
EXPECT_CALL(*this, Checkpoint(1));
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ demuxer_->Seek(base::TimeDelta::FromMilliseconds(46),
NewExpectedStatusCB(PIPELINE_OK));
EXPECT_CALL(*this, Checkpoint(2));
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
+ scoped_ptr<Cluster> cluster(kDefaultSecondCluster());
Checkpoint(1);
@@ -656,6 +686,24 @@ TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) {
Checkpoint(2);
}
+// Test that parsing errors are handled for clusters appended before init
+// completes.
+TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterBeforeInitCompletes) {
+ ASSERT_TRUE(InitDemuxer_ExpectInitFailure());
+
+ ASSERT_TRUE(AppendGarbage());
+}
+
+// Test that parsing errors are handled for clusters appended after init.
+TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterAfterInit) {
+ ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
+
+ EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
+ ASSERT_TRUE(AppendGarbage());
+}
+
// Test the case where a Seek() is requested while the parser
// is in the middle of cluster. This is to verify that the parser
// does not reset itself on a seek.
@@ -715,6 +763,9 @@ TEST_F(ChunkDemuxerTest, TestAppendDataBeforeInit) {
TEST_F(ChunkDemuxerTest, TestRead) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
+
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
@@ -725,24 +776,20 @@ TEST_F(ChunkDemuxerTest, TestRead) {
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done));
-
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done));
- scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
-
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
}
TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_ptr<Cluster> cluster_a(GenerateCluster(10, 4));
-
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
// Cluster B starts before cluster_a and has data
@@ -760,6 +807,8 @@ TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
@@ -783,6 +832,8 @@ TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) {
TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
@@ -807,6 +858,8 @@ TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) {
TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
+ ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
@@ -929,6 +982,9 @@ class EndOfStreamHelper {
TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
+ ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
+
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
@@ -950,10 +1006,6 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
end_of_stream_helper_1.RequestReads();
end_of_stream_helper_2.RequestReads();
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
-
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
EXPECT_TRUE(audio_read_done_1);
EXPECT_TRUE(video_read_done_1);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
@@ -970,6 +1022,9 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
+ ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
+
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
@@ -991,10 +1046,6 @@ TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) {
end_of_stream_helper_1.RequestReads();
- scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
-
- ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
-
EXPECT_TRUE(audio_read_done_1);
EXPECT_TRUE(video_read_done_1);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
@@ -1130,6 +1181,7 @@ TEST_F(ChunkDemuxerTest, TestWebMFile_AltRefFrames) {
// Verify that we output buffers before the entire cluster has been parsed.
TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) {
ASSERT_TRUE(InitDemuxer(true, true, false));
+ ASSERT_TRUE(AppendEmptyCluster(0));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 6));
scoped_refptr<DemuxerStream> audio =
@@ -1192,13 +1244,11 @@ TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) {
EXPECT_TRUE(video_read_done);
}
-
TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) {
- EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
-
EXPECT_CALL(*client_, DemuxerOpened(_));
demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
+ &host_, CreateInitDoneCB(
+ kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN));
ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
@@ -1211,7 +1261,7 @@ TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) {
TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) {
EXPECT_CALL(*client_, DemuxerOpened(_));
demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(),
+ &host_, CreateInitDoneCB(kNoTimestamp(),
DEMUXER_ERROR_COULD_NOT_OPEN));
std::vector<std::string> codecs(1);
@@ -1225,7 +1275,7 @@ TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) {
TEST_F(ChunkDemuxerTest, TestAVHeadersWithVideoOnlyType) {
EXPECT_CALL(*client_, DemuxerOpened(_));
demuxer_->Initialize(
- &host_, CreateInitDoneCB(kDefaultDuration(),
+ &host_, CreateInitDoneCB(kNoTimestamp(),
DEMUXER_ERROR_COULD_NOT_OPEN));
std::vector<std::string> codecs(1);
@@ -1274,9 +1324,9 @@ TEST_F(ChunkDemuxerTest, TestAddSeparateSourcesForAudioAndVideo) {
// Append audio and video data into separate source ids.
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
- GenerateExpectedReads(0, 4, audio, kAudioBlockDuration);
+ GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
- GenerateExpectedReads(0, 4, video, kVideoBlockDuration);
+ GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
}
TEST_F(ChunkDemuxerTest, TestAddIdFailures) {
@@ -1317,7 +1367,7 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) {
// Read() from audio should return normal buffers.
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
- GenerateExpectedReads(0, 4, audio, kAudioBlockDuration);
+ GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
// Remove the audio id.
demuxer_->RemoveId(audio_id);
@@ -1331,7 +1381,7 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) {
// Read() from video should still return normal buffers.
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
- GenerateExpectedReads(0, 4, video, kVideoBlockDuration);
+ GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
}
// Test that Seek() successfully seeks to all source IDs.
@@ -1571,14 +1621,12 @@ TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) {
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
- demuxer_->Seek(base::TimeDelta::FromSeconds(0),
- NewExpectedStatusCB(PIPELINE_OK));
-
// Create a cluster where the video timecode begins 25ms after the audio.
- scoped_ptr<Cluster> start_cluster(
- GenerateCluster(0, 25, 8));
-
+ scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 25, 8));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
+
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 25, 8, audio, video);
// Seek to 5 seconds.
@@ -1593,12 +1641,39 @@ TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) {
GenerateExpectedReads(5025, 5000, 8, audio, video);
}
+TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesSeparateSources) {
+ std::string audio_id = "audio1";
+ std::string video_id = "video1";
+ ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
+
+ scoped_refptr<DemuxerStream> audio =
+ demuxer_->GetStream(DemuxerStream::AUDIO);
+ scoped_refptr<DemuxerStream> video =
+ demuxer_->GetStream(DemuxerStream::VIDEO);
+
+ // Generate two streams where the video stream starts 5ms after the audio
+ // stream and append them.
+ scoped_ptr<Cluster> cluster_v(
+ GenerateSingleStreamCluster(30, 4 * kVideoBlockDuration + 30,
+ kVideoTrackNum, kVideoBlockDuration));
+ scoped_ptr<Cluster> cluster_a(
+ GenerateSingleStreamCluster(25, 4 * kAudioBlockDuration + 25,
+ kAudioTrackNum, kAudioBlockDuration));
+ ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
+ ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
+
+ // Both streams should be able to fulfill a seek to 25.
+ demuxer_->Seek(base::TimeDelta::FromMilliseconds(25),
+ NewExpectedStatusCB(PIPELINE_OK));
+ GenerateSingleStreamExpectedReads(25, 4, audio, kAudioBlockDuration);
+ GenerateSingleStreamExpectedReads(30, 4, video, kVideoBlockDuration);
+}
+
TEST_F(ChunkDemuxerTest, TestClusterWithNoBuffers) {
ASSERT_TRUE(InitDemuxer(true, true, false));
- // Create an empty cluster beginning at 0.
- scoped_ptr<Cluster> empty_cluster(GenerateEmptyCluster(0));
- ASSERT_TRUE(AppendData(empty_cluster->data(), empty_cluster->size()));
+ // Generate and append an empty cluster beginning at 0.
+ ASSERT_TRUE(AppendEmptyCluster(0));
// Sanity check that data can be appended after this cluster correctly.
scoped_ptr<Cluster> media_data(GenerateCluster(0, 2));
diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc
index c80504f..b72aeb5 100644
--- a/media/filters/source_buffer_stream.cc
+++ b/media/filters/source_buffer_stream.cc
@@ -59,6 +59,9 @@ class SourceBufferRange {
// |timestamp|.
void SeekAheadPast(base::TimeDelta timestamp);
+ // Seeks to the beginning of the range.
+ void SeekToStart();
+
// Finds the next keyframe from |buffers_| after |timestamp|, and creates and
// returns a new SourceBufferRange with the buffers from that keyframe onward.
// The buffers in the new SourceBufferRange are moved out of this range. If
@@ -226,7 +229,8 @@ static int kDefaultBufferDurationInMs = 125;
namespace media {
SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
- : seek_pending_(false),
+ : stream_start_time_(kNoTimestamp()),
+ seek_pending_(false),
seek_buffer_timestamp_(kNoTimestamp()),
selected_range_(NULL),
end_of_stream_(false),
@@ -239,7 +243,8 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
}
SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config)
- : seek_pending_(false),
+ : stream_start_time_(kNoTimestamp()),
+ seek_pending_(false),
seek_buffer_timestamp_(kNoTimestamp()),
selected_range_(NULL),
end_of_stream_(false),
@@ -269,6 +274,15 @@ void SourceBufferStream::OnNewMediaSegment(
last_buffer_timestamp_ = kNoTimestamp();
}
+void SourceBufferStream::SetStartTime(base::TimeDelta stream_start_time) {
+ DCHECK(stream_start_time_ == kNoTimestamp());
+ DCHECK(stream_start_time != kNoTimestamp());
+ stream_start_time_ = stream_start_time;
+
+ DCHECK(ranges_.empty() ||
+ ranges_.front()->GetStartTimestamp() >= stream_start_time_);
+}
+
bool SourceBufferStream::Append(
const SourceBufferStream::BufferQueue& buffers) {
DCHECK(!buffers.empty());
@@ -286,10 +300,15 @@ bool SourceBufferStream::Append(
return false;
}
+ if (stream_start_time_ != kNoTimestamp() &&
+ media_segment_start_time_ < stream_start_time_) {
+ DVLOG(1) << "Cannot append a media segment before the start of stream.";
+ return false;
+ }
+
UpdateMaxInterbufferDistance(buffers);
- // Save a snapshot of the |selected_range_| state before range modifications
- // are made.
+ // Save a snapshot of stream state before range modifications are made.
base::TimeDelta next_buffer_timestamp = GetNextBufferTimestamp();
base::TimeDelta end_buffer_timestamp = GetEndBufferTimestamp();
@@ -322,14 +341,6 @@ bool SourceBufferStream::Append(
range_for_new_buffers, &deleted_next_buffer, &deleted_buffers);
MergeWithAdjacentRangeIfNecessary(range_for_new_buffers);
- // If these were the first buffers appended to the stream, seek to the
- // beginning of the range.
- // TODO(vrk): This should be done by ChunkDemuxer. (crbug.com/132815)
- if (!seek_pending_ && !selected_range_) {
- SetSelectedRange(*range_for_new_buffers);
- selected_range_->Seek(buffers.front()->GetDecodeTimestamp());
- }
-
// Seek to try to fulfill a previous call to Seek().
if (seek_pending_) {
DCHECK(!selected_range_);
@@ -360,8 +371,14 @@ bool SourceBufferStream::Append(
return true;
}
+bool SourceBufferStream::IsBeforeFirstRange(base::TimeDelta timestamp) const {
+ if (ranges_.empty())
+ return false;
+ return timestamp < ranges_.front()->GetStartTimestamp();
+}
+
bool SourceBufferStream::IsMonotonicallyIncreasing(
- const BufferQueue& buffers) {
+ const BufferQueue& buffers) const {
DCHECK(!buffers.empty());
base::TimeDelta prev_timestamp = last_buffer_timestamp_;
for (BufferQueue::const_iterator itr = buffers.begin();
@@ -605,14 +622,24 @@ void SourceBufferStream::MergeWithAdjacentRangeIfNecessary(
}
void SourceBufferStream::Seek(base::TimeDelta timestamp) {
+ DCHECK(stream_start_time_ != kNoTimestamp());
+ DCHECK(timestamp >= stream_start_time_);
SetSelectedRange(NULL);
track_buffer_.clear();
+ if (IsBeforeFirstRange(timestamp)) {
+ SetSelectedRange(ranges_.front());
+ ranges_.front()->SeekToStart();
+ seek_pending_ = false;
+ end_of_stream_ = false;
+ return;
+ }
+
seek_buffer_timestamp_ = timestamp;
seek_pending_ = true;
RangeList::iterator itr = ranges_.end();
- for (itr = ranges_.begin(); itr != ranges_.end(); itr++) {
+ for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) {
if ((*itr)->CanSeekTo(timestamp))
break;
}
@@ -802,6 +829,11 @@ void SourceBufferRange::SeekAhead(base::TimeDelta timestamp,
DCHECK_LT(next_buffer_index_, static_cast<int>(buffers_.size()));
}
+void SourceBufferRange::SeekToStart() {
+ DCHECK(!buffers_.empty());
+ next_buffer_index_ = 0;
+}
+
SourceBufferRange* SourceBufferRange::SplitRange(base::TimeDelta timestamp) {
// Find the first keyframe after |timestamp|, not including |timestamp|.
KeyframeMap::iterator new_beginning_keyframe =
diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h
index e1b56e5..a0151ea 100644
--- a/media/filters/source_buffer_stream.h
+++ b/media/filters/source_buffer_stream.h
@@ -37,6 +37,9 @@ class MEDIA_EXPORT SourceBufferStream {
// starting at |media_segment_start_time|.
void OnNewMediaSegment(base::TimeDelta media_segment_start_time);
+ // Sets the start time of the stream.
+ void SetStartTime(base::TimeDelta stream_start_time);
+
// Add the |buffers| to the SourceBufferStream. Buffers within the queue are
// expected to be in order, but multiple calls to Append() may add buffers out
// of order or overlapping. Assumes all buffers within |buffers| are in
@@ -159,9 +162,13 @@ class MEDIA_EXPORT SourceBufferStream {
// for the previous |selected_range_|.
void SetSelectedRange(SourceBufferRange* range);
+ // Returns true if |timestamp| occurs before the start timestamp of the first
+ // range in |ranges_|, false otherwise or if |ranges_| is empty.
+ bool IsBeforeFirstRange(base::TimeDelta timestamp) const;
+
// Returns true if the timestamps of |buffers| are monotonically increasing
// since the previous append to the media segment, false otherwise.
- bool IsMonotonicallyIncreasing(const BufferQueue& buffers);
+ bool IsMonotonicallyIncreasing(const BufferQueue& buffers) const;
// Returns true if |selected_range_| is the only range in |ranges_| that
// HasNextBufferPosition().
@@ -176,6 +183,9 @@ class MEDIA_EXPORT SourceBufferStream {
AudioDecoderConfig audio_config_;
VideoDecoderConfig video_config_;
+ // The starting time of the stream.
+ base::TimeDelta stream_start_time_;
+
// True if more data needs to be appended before the Seek() can complete,
// false if no Seek() has been requested or the Seek() is completed.
bool seek_pending_;
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index dfecbf3..95a81ff 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -21,6 +21,9 @@ class SourceBufferStreamTest : public testing::Test {
SourceBufferStreamTest() {
stream_.reset(new SourceBufferStream(config_));
SetStreamInfo(kDefaultFramesPerSecond, kDefaultKeyframesPerSecond);
+
+ // Set start time to the beginning of the stream.
+ stream_->SetStartTime(base::TimeDelta());
}
void SetStreamInfo(int frames_per_second, int keyframes_per_second) {
@@ -1155,6 +1158,7 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) {
// Append 5 buffers at position (5 + |bump|) through 9, where the media
// segment begins at position 5.
+ Seek(5);
NewSegmentAppend_OffsetFirstBuffer(5, 5, bump);
scoped_refptr<StreamParserBuffer> buffer;
@@ -1180,6 +1184,17 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) {
CheckExpectedBuffers(16, 19);
}
+TEST_F(SourceBufferStreamTest, Seek_BeforeStartOfSegment) {
+ // Append 10 buffers at positions 5 through 14.
+ NewSegmentAppend(5, 10);
+
+ // Seek to a time before the first buffer in the range.
+ Seek(0);
+
+ // Should return buffers from the beginning of the range.
+ CheckExpectedBuffers(5, 14);
+}
+
TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap) {
// Append 5 buffers at positions 0 through 4.
NewSegmentAppend(0, 4);
@@ -1200,18 +1215,21 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap) {
}
TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap_Pending) {
- // Append 5 buffers at positions 10 through 14 and seek to beginning of the
+ // Append 2 buffers at positions 0 through 1.
+ NewSegmentAppend(0, 2);
+
+ // Append 5 buffers at positions 15 through 19 and seek to beginning of the
// range.
- NewSegmentAppend(10, 5);
- Seek(10);
+ NewSegmentAppend(15, 5);
+ Seek(15);
- // Now seek to the beginning of the stream.
- Seek(0);
+ // Now seek position 5.
+ Seek(5);
// Completely overlap the old seek point.
- NewSegmentAppend(5, 15);
+ NewSegmentAppend(10, 15);
- // The seek at time 0 should still be pending.
+ // The seek at position 5 should still be pending.
CheckNoNextBuffer();
}
@@ -1235,18 +1253,21 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap) {
}
TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap_Pending) {
- // Append 15 buffers at positions 5 through 19 and seek to position 15.
- NewSegmentAppend(5, 15);
- Seek(15);
+ // Append 2 buffers at positions 0 through 1.
+ NewSegmentAppend(0, 2);
- // Now seek to the beginning of the stream.
- Seek(0);
+ // Append 15 buffers at positions 10 through 24 and seek to position 20.
+ NewSegmentAppend(10, 15);
+ Seek(20);
- // Overlap the middle of the range such that there are now two ranges.
- NewSegmentAppend(10, 3);
- CheckExpectedRanges("{ [5,12) [15,19) }");
+ // Now seek to position 5.
+ Seek(5);
- // The seek at time 0 should still be pending.
+ // Overlap the middle of the range such that it is now split into two ranges.
+ NewSegmentAppend(15, 3);
+ CheckExpectedRanges("{ [0,1) [10,17) [20,24) }");
+
+ // The seek at position 5 should still be pending.
CheckNoNextBuffer();
}
@@ -1269,15 +1290,18 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap) {
}
TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap_Pending) {
- // Append 15 buffers at positions 5 through 19 and seek to position 15.
- NewSegmentAppend(5, 15);
- Seek(15);
+ // Append 2 buffers at positions 0 through 1.
+ NewSegmentAppend(0, 2);
- // Now seek to the beginning of the stream.
- Seek(0);
+ // Append 15 buffers at positions 10 through 24 and seek to position 20.
+ NewSegmentAppend(10, 15);
+ Seek(20);
+
+ // Now seek to position 5.
+ Seek(5);
// Start overlap the old seek point.
- NewSegmentAppend(10, 10);
+ NewSegmentAppend(15, 10);
// The seek at time 0 should still be pending.
CheckNoNextBuffer();
@@ -1302,15 +1326,18 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap) {
}
TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap_Pending) {
- // Append 15 buffers at positions 10 through 24 and seek to start of range.
- NewSegmentAppend(10, 15);
- Seek(10);
+ // Append 2 buffers at positions 0 through 1.
+ NewSegmentAppend(0, 2);
- // Now seek to the beginning of the stream.
- Seek(0);
+ // Append 15 buffers at positions 15 through 29 and seek to start of range.
+ NewSegmentAppend(15, 15);
+ Seek(15);
+
+ // Now seek to position 5
+ Seek(5);
// End overlap the old seek point.
- NewSegmentAppend(5, 10);
+ NewSegmentAppend(10, 10);
// The seek at time 0 should still be pending.
CheckNoNextBuffer();
@@ -1361,6 +1388,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenAppend) {
TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenStartOverlap) {
// Append 10 buffers at positions 0 through 9 and exhaust the buffers.
NewSegmentAppend(0, 10, &kDataA);
+ Seek(0);
CheckExpectedBuffers(0, 9, &kDataA);
// Next buffer is at position 10, so should not be able to fulfill request.
@@ -1389,6 +1417,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenStartOverlap) {
TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenCompleteOverlap) {
// Append 5 buffers at positions 10 through 14 and exhaust the buffers.
NewSegmentAppend(10, 5, &kDataA);
+ Seek(10);
CheckExpectedBuffers(10, 14, &kDataA);
// Next buffer is at position 15, so should not be able to fulfill request.
@@ -1416,6 +1445,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenCompleteOverlap) {
TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenEndOverlap) {
// Append 5 buffers at positions 10 through 14 and exhaust the buffers.
NewSegmentAppend(10, 5, &kDataA);
+ Seek(10);
CheckExpectedBuffers(10, 14, &kDataA);
CheckExpectedRanges("{ [10,14) }");
@@ -1467,17 +1497,10 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_Overlap_Selected_Complete) {
CheckExpectedBuffers(10, 14, &kDataB);
}
-TEST_F(SourceBufferStreamTest, GetNextBuffer_NoSeek) {
- // Append 5 buffers at position 5.
- NewSegmentAppend(5, 5);
-
- // Should receive buffers from the start without needing to seek.
- CheckExpectedBuffers(5, 5);
-}
-
TEST_F(SourceBufferStreamTest, PresentationTimestampIndependence) {
// Append 20 buffers at position 0.
NewSegmentAppend(0, 20);
+ Seek(0);
int last_keyframe_idx = -1;
base::TimeDelta last_keyframe_presentation_timestamp;
diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc
index 41bcf11..0281177 100644
--- a/webkit/media/webmediaplayer_impl.cc
+++ b/webkit/media/webmediaplayer_impl.cc
@@ -776,7 +776,9 @@ void WebMediaPlayerImpl::Repaint() {
void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (status != media::PIPELINE_OK) {
- OnPipelineError(status);
+ // Any error that occurs before the pipeline can initialize should be
+ // considered a format error.
+ SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
// Repaint to trigger UI update.
Repaint();
return;
@@ -840,6 +842,9 @@ void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
SetNetworkState(WebMediaPlayer::NetworkStateNetworkError);
break;
+ // TODO(vrk): Because OnPipelineInitialize() directly reports the
+ // NetworkStateFormatError instead of calling OnPipelineError(), I believe
+ // this block can be deleted. Should look into it! (crbug.com/126070)
case media::PIPELINE_ERROR_INITIALIZATION_FAILED:
case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING:
case media::PIPELINE_ERROR_COULD_NOT_RENDER: