Skip to content

Commit

Permalink
Add WebMediaPlayer::timelineOffset() support to WebMediaPlayerImpl.
Browse files Browse the repository at this point in the history
These changes add support for the
WebMediaPlayer::timelineOffset() method so that
HTMLMediaElement::getStartDate() can report this
information to JavaScript. FFmpegDemuxer & ChunkDemuxer
have been updated to provide "timeline offset" information
for WebM.

BUG=312699
TESTS=PipelineIntegrationTest.BasicPlayback, PipelineIntegrationTest.BasicPlaybackLive, PipelineIntegrationTest.BasicPlayback_MediaSource, PipelineIntegrationTest, BasicPlayback_MediaSource_Live

Review URL: https://codereview.chromium.org/236023003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@264145 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
acolwell@chromium.org committed Apr 16, 2014
1 parent b1248eb commit db66d00
Show file tree
Hide file tree
Showing 29 changed files with 297 additions and 16 deletions.
9 changes: 9 additions & 0 deletions content/renderer/media/webmediaplayer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,15 @@ double WebMediaPlayerImpl::duration() const {
return GetPipelineDuration();
}

double WebMediaPlayerImpl::timelineOffset() const {
DCHECK(main_loop_->BelongsToCurrentThread());

if (pipeline_metadata_.timeline_offset.is_null())
return std::numeric_limits<double>::quiet_NaN();

return pipeline_metadata_.timeline_offset.ToJsTime();
}

double WebMediaPlayerImpl::currentTime() const {
DCHECK(main_loop_->BelongsToCurrentThread());
return (paused_ ? paused_time_ : pipeline_.GetMediaTime()).InSecondsF();
Expand Down
1 change: 1 addition & 0 deletions content/renderer/media/webmediaplayer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class WebMediaPlayerImpl
virtual bool paused() const;
virtual bool seeking() const;
virtual double duration() const;
virtual double timelineOffset() const;
virtual double currentTime() const;

// Internal states of loading and network.
Expand Down
5 changes: 5 additions & 0 deletions media/base/demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ class MEDIA_EXPORT Demuxer {
// Returns the starting time for the media file.
virtual base::TimeDelta GetStartTime() const = 0;

// Returns Time represented by presentation timestamp 0.
// If the timstamps are not associated with a Time, then
// a null Time is returned.
virtual base::Time GetTimelineOffset() const = 0;

private:
DISALLOW_COPY_AND_ASSIGN(Demuxer);
};
Expand Down
1 change: 1 addition & 0 deletions media/base/mock_filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class MockDemuxer : public Demuxer {
MOCK_METHOD0(OnAudioRendererDisabled, void());
MOCK_METHOD1(GetStream, DemuxerStream*(DemuxerStream::Type));
MOCK_CONST_METHOD0(GetStartTime, base::TimeDelta());
MOCK_CONST_METHOD0(GetTimelineOffset, base::Time());

private:
DISALLOW_COPY_AND_ASSIGN(MockDemuxer);
Expand Down
1 change: 1 addition & 0 deletions media/base/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ void Pipeline::StateTransitionTask(PipelineStatus status) {
PipelineMetadata metadata;
metadata.has_audio = audio_renderer_;
metadata.has_video = video_renderer_;
metadata.timeline_offset = demuxer_->GetTimelineOffset();
DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
if (stream)
metadata.natural_size = stream->video_decoder_config().natural_size();
Expand Down
1 change: 1 addition & 0 deletions media/base/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct PipelineMetadata {
bool has_audio;
bool has_video;
gfx::Size natural_size;
base::Time timeline_offset;
};

typedef base::Callback<void(PipelineMetadata)> PipelineMetadataCB;
Expand Down
5 changes: 4 additions & 1 deletion media/base/pipeline_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class PipelineTest : public ::testing::Test {

EXPECT_CALL(*demuxer_, GetStartTime())
.WillRepeatedly(Return(base::TimeDelta()));

EXPECT_CALL(*demuxer_, GetTimelineOffset())
.WillRepeatedly(Return(base::Time()));
}

virtual ~PipelineTest() {
Expand Down Expand Up @@ -659,7 +662,7 @@ TEST_F(PipelineTest, AudioStreamShorterThanVideo) {
streams.push_back(audio_stream());
streams.push_back(video_stream());

// Replace the clock so we can simulate wallclock time advancing w/o using
// Replace the clock so we can simulate wall clock time advancing w/o using
// Sleep().
pipeline_->SetClockForTesting(new Clock(&test_tick_clock_));

Expand Down
9 changes: 7 additions & 2 deletions media/base/stream_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ class MEDIA_EXPORT StreamParser {
// occurred.
// Second parameter - Indicates the stream duration. Only contains a valid
// value if the first parameter is true.
// Third parameters - Indicates that timestampOffset should be updated based
// Third parameter - Indicates the Time associated with
// presentation timestamp 0 if such a mapping exists in
// the bytestream. If no mapping exists this parameter
// contains null Time object. Only contains a valid
// value if the first parameter is true.
// Fourth parameters - Indicates that timestampOffset should be updated based
// on the earliest end timestamp (audio or video) provided
// during each NewBuffersCB.
typedef base::Callback<void(bool, base::TimeDelta, bool)> InitCB;
typedef base::Callback<void(bool, base::TimeDelta, base::Time, bool)> InitCB;

// Indicates when new stream configurations have been parsed.
// First parameter - The new audio configuration. If the config is not valid
Expand Down
34 changes: 34 additions & 0 deletions media/ffmpeg/ffmpeg_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
Expand Down Expand Up @@ -550,4 +551,37 @@ PixelFormat VideoFormatToPixelFormat(VideoFrame::Format video_format) {
return PIX_FMT_NONE;
}

bool FFmpegUTCDateToTime(const char* date_utc,
base::Time* out) {
DCHECK(date_utc);
DCHECK(out);

std::vector<std::string> fields;
std::vector<std::string> date_fields;
std::vector<std::string> time_fields;
base::Time::Exploded exploded;
exploded.millisecond = 0;

// TODO(acolwell): Update this parsing code when FFmpeg returns sub-second
// information.
if ((Tokenize(date_utc, " ", &fields) == 2) &&
(Tokenize(fields[0], "-", &date_fields) == 3) &&
(Tokenize(fields[1], ":", &time_fields) == 3) &&
base::StringToInt(date_fields[0], &exploded.year) &&
base::StringToInt(date_fields[1], &exploded.month) &&
base::StringToInt(date_fields[2], &exploded.day_of_month) &&
base::StringToInt(time_fields[0], &exploded.hour) &&
base::StringToInt(time_fields[1], &exploded.minute) &&
base::StringToInt(time_fields[2], &exploded.second)) {
base::Time parsed_time = base::Time::FromUTCExploded(exploded);
if (parsed_time.is_null())
return false;

*out = parsed_time;
return true;
}

return false;
}

} // namespace media
5 changes: 5 additions & 0 deletions media/ffmpeg/ffmpeg_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ MEDIA_EXPORT VideoFrame::Format PixelFormatToVideoFormat(
// Converts video formats to its corresponding FFmpeg's pixel formats.
PixelFormat VideoFormatToPixelFormat(VideoFrame::Format video_format);

// Convert FFmpeg UTC representation (YYYY-MM-DD HH:MM:SS) to base::Time.
// Returns true and sets |*out| if |date_utc| contains a valid
// date string. Otherwise returns fals and timeline_offset is unmodified.
MEDIA_EXPORT bool FFmpegUTCDateToTime(const char* date_utc, base::Time* out);

} // namespace media

#endif // MEDIA_FFMPEG_FFMPEG_COMMON_H_
54 changes: 54 additions & 0 deletions media/ffmpeg/ffmpeg_common_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,58 @@ TEST_F(FFmpegCommonTest, VerifyFormatSizes) {
}
}

TEST_F(FFmpegCommonTest, UTCDateToTime_Valid) {
base::Time result;
EXPECT_TRUE(FFmpegUTCDateToTime("2012-11-10 12:34:56", &result));

base::Time::Exploded exploded;
result.UTCExplode(&exploded);
EXPECT_TRUE(exploded.HasValidValues());
EXPECT_EQ(2012, exploded.year);
EXPECT_EQ(11, exploded.month);
EXPECT_EQ(6, exploded.day_of_week);
EXPECT_EQ(10, exploded.day_of_month);
EXPECT_EQ(12, exploded.hour);
EXPECT_EQ(34, exploded.minute);
EXPECT_EQ(56, exploded.second);
EXPECT_EQ(0, exploded.millisecond);
}

TEST_F(FFmpegCommonTest, UTCDateToTime_Invalid) {
const char* invalid_date_strings[] = {
"",
"2012-11-10",
"12:34:56",
"-- ::",
"2012-11-10 12:34:",
"2012-11-10 12::56",
"2012-11-10 :34:56",
"2012-11- 12:34:56",
"2012--10 12:34:56",
"-11-10 12:34:56",
"2012-11 12:34:56",
"2012-11-10-12 12:34:56",
"2012-11-10 12:34",
"2012-11-10 12:34:56:78",
"ABCD-11-10 12:34:56",
"2012-EF-10 12:34:56",
"2012-11-GH 12:34:56",
"2012-11-10 IJ:34:56",
"2012-11-10 12:JL:56",
"2012-11-10 12:34:MN",
"2012-11-10 12:34:56.123",
"2012-11-1012:34:56",
"2012-11-10 12:34:56 UTC",
};

for (size_t i = 0; i < arraysize(invalid_date_strings); ++i) {
const char* date_string = invalid_date_strings[i];
base::Time result;
EXPECT_FALSE(FFmpegUTCDateToTime(date_string, &result))
<< "date_string '" << date_string << "'";
EXPECT_TRUE(result.is_null());
}
}


} // namespace media
30 changes: 27 additions & 3 deletions media/filters/chunk_demuxer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ class SourceState {
// occurred.
// Second parameter - Indicates the stream duration. Only contains a valid
// value if the first parameter is true.
typedef base::Callback<void(bool, TimeDelta)> InitCB;
// Third parameter - Indicates the source Time associated with
// presentation timestamp 0. A null Time is returned if
// no mapping to Time exists. Only contains a
// valid value if the first parameter is true.
typedef base::Callback<void(bool, TimeDelta, base::Time)> InitCB;

SourceState(
scoped_ptr<StreamParser> stream_parser,
Expand Down Expand Up @@ -190,6 +194,7 @@ class SourceState {

void OnSourceInitDone(bool success,
TimeDelta duration,
base::Time timeline_offset,
bool auto_update_timestamp_offset);

CreateDemuxerStreamCB create_demuxer_stream_cb_;
Expand Down Expand Up @@ -697,9 +702,11 @@ bool SourceState::OnNewBuffers(

void SourceState::OnSourceInitDone(bool success,
TimeDelta duration,
base::Time timeline_offset,
bool auto_update_timestamp_offset) {
auto_update_timestamp_offset_ = auto_update_timestamp_offset;
base::ResetAndReturn(&init_cb_).Run(success, duration);
base::ResetAndReturn(&init_cb_).Run(
success, duration, timeline_offset);
}

ChunkDemuxerStream::ChunkDemuxerStream(Type type, bool splice_frames_enabled)
Expand Down Expand Up @@ -1069,6 +1076,10 @@ TimeDelta ChunkDemuxer::GetStartTime() const {
return TimeDelta();
}

base::Time ChunkDemuxer::GetTimelineOffset() const {
return timeline_offset_;
}

void ChunkDemuxer::StartWaitingForSeek(TimeDelta seek_time) {
DVLOG(1) << "StartWaitingForSeek()";
base::AutoLock auto_lock(lock_);
Expand Down Expand Up @@ -1486,7 +1497,8 @@ bool ChunkDemuxer::IsSeekWaitingForData_Locked() const {
return false;
}

void ChunkDemuxer::OnSourceInitDone(bool success, TimeDelta duration) {
void ChunkDemuxer::OnSourceInitDone(bool success, TimeDelta duration,
base::Time timeline_offset) {
DVLOG(1) << "OnSourceInitDone(" << success << ", "
<< duration.InSecondsF() << ")";
lock_.AssertAcquired();
Expand All @@ -1499,6 +1511,18 @@ void ChunkDemuxer::OnSourceInitDone(bool success, TimeDelta duration) {
if (duration != TimeDelta() && duration_ == kNoTimestamp())
UpdateDuration(duration);

if (!timeline_offset.is_null()) {
if (!timeline_offset_.is_null() &&
timeline_offset != timeline_offset_) {
MEDIA_LOG(log_cb_)
<< "Timeline offset is not the same across all SourceBuffers.";
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return;
}

timeline_offset_ = timeline_offset;
}

// Wait until all streams have initialized.
if ((!source_id_audio_.empty() && !audio_) ||
(!source_id_video_.empty() && !video_))
Expand Down
6 changes: 5 additions & 1 deletion media/filters/chunk_demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
virtual void OnAudioRendererDisabled() OVERRIDE;
virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE;
virtual base::TimeDelta GetStartTime() const OVERRIDE;
virtual base::Time GetTimelineOffset() const OVERRIDE;

// Methods used by an external object to control this demuxer.
//
Expand Down Expand Up @@ -286,7 +287,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
bool CanEndOfStream_Locked() const;

// SourceState callbacks.
void OnSourceInitDone(bool success, base::TimeDelta duration);
void OnSourceInitDone(bool success, base::TimeDelta duration,
base::Time timeline_offset);

// Creates a DemuxerStream for the specified |type|.
// Returns a new ChunkDemuxerStream instance if a stream of this type
Expand Down Expand Up @@ -363,6 +365,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
// the actual duration instead of a user specified value.
double user_specified_duration_;

base::Time timeline_offset_;

typedef std::map<std::string, SourceState*> SourceStateMap;
SourceStateMap source_state_map_;

Expand Down
22 changes: 22 additions & 0 deletions media/filters/ffmpeg_demuxer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@

namespace media {

static base::Time ExtractTimelineOffset(AVFormatContext* format_context) {
if (strstr(format_context->iformat->name, "webm") ||
strstr(format_context->iformat->name, "matroska")) {
const AVDictionaryEntry* entry =
av_dict_get(format_context->metadata, "creation_time", NULL, 0);

base::Time timeline_offset;
if (entry != NULL && entry->value != NULL &&
FFmpegUTCDateToTime(entry->value, &timeline_offset)) {
return timeline_offset;
}
}

return base::Time();
}

//
// FFmpegDemuxerStream
//
Expand Down Expand Up @@ -486,6 +502,10 @@ base::TimeDelta FFmpegDemuxer::GetStartTime() const {
return start_time_;
}

base::Time FFmpegDemuxer::GetTimelineOffset() const {
return timeline_offset_;
}

void FFmpegDemuxer::AddTextStreams() {
DCHECK(task_runner_->BelongsToCurrentThread());

Expand Down Expand Up @@ -674,6 +694,8 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
if (strcmp(format_context->iformat->name, "avi") == 0)
format_context->flags |= AVFMT_FLAG_GENPTS;

timeline_offset_ = ExtractTimelineOffset(format_context);

// Good to go: set the duration and bitrate and notify we're done
// initializing.
host_->SetDuration(max_duration);
Expand Down
5 changes: 5 additions & 0 deletions media/filters/ffmpeg_demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
virtual void OnAudioRendererDisabled() OVERRIDE;
virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE;
virtual base::TimeDelta GetStartTime() const OVERRIDE;
virtual base::Time GetTimelineOffset() const OVERRIDE;

// Calls |need_key_cb_| with the initialization data encountered in the file.
void FireNeedKey(const std::string& init_data_type,
Expand Down Expand Up @@ -246,6 +247,10 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
// is 0.
base::TimeDelta start_time_;

// The Time associated with timestamp 0. Set to a null
// time if the file doesn't have an association to Time.
base::Time timeline_offset_;

// Whether audio has been disabled for this demuxer (in which case this class
// drops packets destined for AUDIO demuxer streams on the floor).
bool audio_disabled_;
Expand Down
Loading

0 comments on commit db66d00

Please sign in to comment.