| Index: media/formats/webm/webm_cluster_parser.cc | 
| diff --git a/media/formats/webm/webm_cluster_parser.cc b/media/formats/webm/webm_cluster_parser.cc | 
| index a762e2c40b34411594a5a30f0438e36f96d31a57..6e2424b101efc6a78fae978553bb96c21254085a 100644 | 
| --- a/media/formats/webm/webm_cluster_parser.cc | 
| +++ b/media/formats/webm/webm_cluster_parser.cc | 
| @@ -15,10 +15,21 @@ | 
| #include "media/formats/webm/webm_crypto_helpers.h" | 
| #include "media/formats/webm/webm_webvtt_parser.h" | 
|  | 
| +// Arbitrarily-chosen numbers to estimate the duration of a buffer if none is | 
| +// set and there is not enough information to get a better estimate. | 
| +// TODO(wolenetz/acolwell): Parse audio codebook to determine missing audio | 
| +// frame durations. See http://crbug.com/351166. | 
| +static int kDefaultAudioBufferDurationInMs = 23;  // Common 1k samples @44.1kHz | 
| +static int kDefaultVideoBufferDurationInMs = 42;  // Low 24fps to reduce stalls | 
| + | 
| namespace media { | 
|  | 
| WebMClusterParser::WebMClusterParser( | 
| -    int64 timecode_scale, int audio_track_num, int video_track_num, | 
| +    int64 timecode_scale, | 
| +    int audio_track_num, | 
| +    base::TimeDelta audio_default_duration, | 
| +    int video_track_num, | 
| +    base::TimeDelta video_default_duration, | 
| const WebMTracksParser::TextTracks& text_tracks, | 
| const std::set<int64>& ignored_tracks, | 
| const std::string& audio_encryption_key_id, | 
| @@ -38,13 +49,14 @@ WebMClusterParser::WebMClusterParser( | 
| cluster_timecode_(-1), | 
| cluster_start_time_(kNoTimestamp()), | 
| cluster_ended_(false), | 
| -      audio_(audio_track_num, false), | 
| -      video_(video_track_num, true), | 
| +      audio_(audio_track_num, false, audio_default_duration), | 
| +      video_(video_track_num, true, video_default_duration), | 
| log_cb_(log_cb) { | 
| for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin(); | 
| it != text_tracks.end(); | 
| ++it) { | 
| -    text_track_map_.insert(std::make_pair(it->first, Track(it->first, false))); | 
| +    text_track_map_.insert(std::make_pair( | 
| +        it->first, Track(it->first, false, kNoTimestamp()))); | 
| } | 
| } | 
|  | 
| @@ -62,8 +74,8 @@ void WebMClusterParser::Reset() { | 
| } | 
|  | 
| int WebMClusterParser::Parse(const uint8* buf, int size) { | 
| -  audio_.Reset(); | 
| -  video_.Reset(); | 
| +  audio_.ClearBuffersButKeepLastIfMissingDuration(); | 
| +  video_.ClearBuffersButKeepLastIfMissingDuration(); | 
| ResetTextTracks(); | 
|  | 
| int result = parser_.Parse(buf, size); | 
| @@ -99,6 +111,18 @@ int WebMClusterParser::Parse(const uint8* buf, int size) { | 
| return result; | 
| } | 
|  | 
| +const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() { | 
| +  if (cluster_ended_) | 
| +    audio_.ApplyDurationDefaultOrEstimateIfNeeded(); | 
| +  return audio_.buffers(); | 
| +} | 
| + | 
| +const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() { | 
| +  if (cluster_ended_) | 
| +    video_.ApplyDurationDefaultOrEstimateIfNeeded(); | 
| +  return video_.buffers(); | 
| +} | 
| + | 
| const WebMClusterParser::TextBufferQueueMap& | 
| WebMClusterParser::GetTextBuffers() { | 
| // Translate our |text_track_map_| into |text_buffers_map_|, inserting rows in | 
| @@ -107,6 +131,9 @@ WebMClusterParser::GetTextBuffers() { | 
| for (TextTrackMap::const_iterator itr = text_track_map_.begin(); | 
| itr != text_track_map_.end(); | 
| ++itr) { | 
| +    // Per OnBlock(), all text buffers should already have valid durations, so | 
| +    // there is no need to call | 
| +    // itr->second.ApplyDurationDefaultOrEstimateIfNeeded() here. | 
| const BufferQueue& text_buffers = itr->second.buffers(); | 
| if (!text_buffers.empty()) | 
| text_buffers_map_.insert(std::make_pair(itr->first, text_buffers)); | 
| @@ -390,9 +417,14 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, int track_num, | 
| return track->AddBuffer(buffer); | 
| } | 
|  | 
| -WebMClusterParser::Track::Track(int track_num, bool is_video) | 
| +WebMClusterParser::Track::Track(int track_num, bool is_video, | 
| +                                base::TimeDelta default_duration) | 
| : track_num_(track_num), | 
| -      is_video_(is_video) { | 
| +      is_video_(is_video), | 
| +      default_duration_(default_duration), | 
| +      estimated_next_frame_duration_(kNoTimestamp()) { | 
| +  DCHECK(default_duration_ == kNoTimestamp() || | 
| +         default_duration_ > base::TimeDelta()); | 
| } | 
|  | 
| WebMClusterParser::Track::~Track() {} | 
| @@ -405,14 +437,66 @@ bool WebMClusterParser::Track::AddBuffer( | 
| << " kf " << buffer->IsKeyframe() | 
| << " size " << buffer->data_size(); | 
|  | 
| -  buffers_.push_back(buffer); | 
| -  return true; | 
| +  if (last_added_buffer_missing_duration_) { | 
| +    base::TimeDelta derived_duration = | 
| +        buffer->timestamp() - last_added_buffer_missing_duration_->timestamp(); | 
| +    last_added_buffer_missing_duration_->set_duration(derived_duration); | 
| + | 
| +    DVLOG(2) << "AddBuffer() : applied derived duration to held-back buffer : " | 
| +             << " ts " | 
| +             << last_added_buffer_missing_duration_->timestamp().InSecondsF() | 
| +             << " dur " | 
| +             << last_added_buffer_missing_duration_->duration().InSecondsF() | 
| +             << " kf " << last_added_buffer_missing_duration_->IsKeyframe() | 
| +             << " size " << last_added_buffer_missing_duration_->data_size(); | 
| +    scoped_refptr<StreamParserBuffer> updated_buffer = | 
| +        last_added_buffer_missing_duration_; | 
| +    last_added_buffer_missing_duration_ = NULL; | 
| +    if (!QueueBuffer(updated_buffer)) | 
| +      return false; | 
| +  } | 
| + | 
| +  if (buffer->duration() == kNoTimestamp()) { | 
| +    last_added_buffer_missing_duration_ = buffer; | 
| +    DVLOG(2) << "AddBuffer() : holding back buffer that is missing duration"; | 
| +    return true; | 
| +  } | 
| + | 
| +  return QueueBuffer(buffer); | 
| } | 
|  | 
| -void WebMClusterParser::Track::Reset() { | 
| +void WebMClusterParser::Track::ApplyDurationDefaultOrEstimateIfNeeded() { | 
| +  if (!last_added_buffer_missing_duration_) | 
| +    return; | 
| + | 
| +  last_added_buffer_missing_duration_->set_duration( | 
| +      GetDurationDefaultOrEstimate()); | 
| + | 
| +  DVLOG(2) << "ApplyDurationDefaultOrEstimateIfNeeded() : new dur : " | 
| +           << " ts " | 
| +           << last_added_buffer_missing_duration_->timestamp().InSecondsF() | 
| +           << " dur " | 
| +           << last_added_buffer_missing_duration_->duration().InSecondsF() | 
| +           << " kf " << last_added_buffer_missing_duration_->IsKeyframe() | 
| +           << " size " << last_added_buffer_missing_duration_->data_size(); | 
| + | 
| +  // Don't use the applied duration as a future estimation (don't use | 
| +  // QueueBuffer() here.) | 
| +  buffers_.push_back(last_added_buffer_missing_duration_); | 
| +  last_added_buffer_missing_duration_ = NULL; | 
| +} | 
| + | 
| +void WebMClusterParser::Track::ClearBuffersButKeepLastIfMissingDuration() { | 
| +  // Note that |estimated_next_frame_duration_| is not reset, so it can be | 
| +  // reused on subsequent buffers added to this instance. | 
| buffers_.clear(); | 
| } | 
|  | 
| +void WebMClusterParser::Track::Reset() { | 
| +  ClearBuffersButKeepLastIfMissingDuration(); | 
| +  last_added_buffer_missing_duration_ = NULL; | 
| +} | 
| + | 
| bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const { | 
| // For now, assume that all blocks are keyframes for datatypes other than | 
| // video. This is a valid assumption for Vorbis, WebVTT, & Opus. | 
| @@ -436,6 +520,45 @@ bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const { | 
| return true; | 
| } | 
|  | 
| +bool WebMClusterParser::Track::QueueBuffer( | 
| +    const scoped_refptr<StreamParserBuffer>& buffer) { | 
| +  DCHECK(!last_added_buffer_missing_duration_); | 
| +  base::TimeDelta duration = buffer->duration(); | 
| +  if (duration < base::TimeDelta() || duration == kNoTimestamp()) { | 
| +    DVLOG(2) << "QueueBuffer() : Invalid buffer duration: " | 
| +             << duration.InSecondsF(); | 
| +    return false; | 
| +  } | 
| + | 
| +  estimated_next_frame_duration_ = std::max(duration, | 
| +                                            estimated_next_frame_duration_); | 
| +  buffers_.push_back(buffer); | 
| +  return true; | 
| +} | 
| + | 
| +base::TimeDelta WebMClusterParser::Track::GetDurationDefaultOrEstimate() { | 
| +  base::TimeDelta duration = default_duration_; | 
| +  if (duration != kNoTimestamp()) { | 
| +    DVLOG(3) << __FUNCTION__ << " : using TrackEntry DefaultDuration"; | 
| +  } else if (estimated_next_frame_duration_ != kNoTimestamp()) { | 
| +    DVLOG(3) << __FUNCTION__ << " : using estimated duration"; | 
| +    duration = estimated_next_frame_duration_; | 
| +  } else { | 
| +    DVLOG(3) << __FUNCTION__ << " : using hardcoded default duration"; | 
| +    if (is_video_) { | 
| +      duration = base::TimeDelta::FromMilliseconds( | 
| +          kDefaultVideoBufferDurationInMs); | 
| +    } else { | 
| +      duration = base::TimeDelta::FromMilliseconds( | 
| +          kDefaultAudioBufferDurationInMs); | 
| +    } | 
| +  } | 
| + | 
| +  DCHECK(duration > base::TimeDelta()); | 
| +  DCHECK(duration != kNoTimestamp()); | 
| +  return duration; | 
| +} | 
| + | 
| void WebMClusterParser::ResetTextTracks() { | 
| text_buffers_map_.clear(); | 
| for (TextTrackMap::iterator it = text_track_map_.begin(); | 
|  |