Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/filters/ffmpeg_demuxer.h" | 5 #include "media/filters/ffmpeg_demuxer.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <string> | 8 #include <string> |
| 9 | 9 |
| 10 #include "base/base64.h" | 10 #include "base/base64.h" |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 57 case AVMEDIA_TYPE_AUDIO: | 57 case AVMEDIA_TYPE_AUDIO: |
| 58 type_ = AUDIO; | 58 type_ = AUDIO; |
| 59 AVStreamToAudioDecoderConfig(stream, &audio_config_, true); | 59 AVStreamToAudioDecoderConfig(stream, &audio_config_, true); |
| 60 is_encrypted = audio_config_.is_encrypted(); | 60 is_encrypted = audio_config_.is_encrypted(); |
| 61 break; | 61 break; |
| 62 case AVMEDIA_TYPE_VIDEO: | 62 case AVMEDIA_TYPE_VIDEO: |
| 63 type_ = VIDEO; | 63 type_ = VIDEO; |
| 64 AVStreamToVideoDecoderConfig(stream, &video_config_, true); | 64 AVStreamToVideoDecoderConfig(stream, &video_config_, true); |
| 65 is_encrypted = video_config_.is_encrypted(); | 65 is_encrypted = video_config_.is_encrypted(); |
| 66 break; | 66 break; |
| 67 case AVMEDIA_TYPE_SUBTITLE: | |
| 68 type_ = TEXT; | |
| 69 break; | |
| 67 default: | 70 default: |
| 68 NOTREACHED(); | 71 NOTREACHED(); |
| 69 break; | 72 break; |
| 70 } | 73 } |
| 71 | 74 |
| 72 // Calculate the duration. | 75 // Calculate the duration. |
| 73 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); | 76 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); |
| 74 | 77 |
| 75 if (stream_->codec->codec_id == AV_CODEC_ID_H264) { | 78 if (stream_->codec->codec_id == AV_CODEC_ID_H264) { |
| 76 bitstream_converter_.reset( | 79 bitstream_converter_.reset( |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 103 NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; | 106 NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; |
| 104 return; | 107 return; |
| 105 } | 108 } |
| 106 | 109 |
| 107 // Convert the packet if there is a bitstream filter. | 110 // Convert the packet if there is a bitstream filter. |
| 108 if (packet->data && bitstream_converter_enabled_ && | 111 if (packet->data && bitstream_converter_enabled_ && |
| 109 !bitstream_converter_->ConvertPacket(packet.get())) { | 112 !bitstream_converter_->ConvertPacket(packet.get())) { |
| 110 LOG(ERROR) << "Format conversion failed."; | 113 LOG(ERROR) << "Format conversion failed."; |
| 111 } | 114 } |
| 112 | 115 |
| 113 // Get side data if any. For now, the only type of side_data is VP8 Alpha. We | 116 scoped_refptr<DecoderBuffer> buffer; |
| 114 // keep this generic so that other side_data types in the future can be | 117 |
| 115 // handled the same way as well. | 118 // Get side data if any. For now, the only types of side_data are VP8 Alpha, |
| 119 // and WebVTT id and settings. We keep this generic so that other side_data | |
| 120 // types in the future can be handled the same way as well. | |
| 116 av_packet_split_side_data(packet.get()); | 121 av_packet_split_side_data(packet.get()); |
| 117 int side_data_size = 0; | 122 if (type() == DemuxerStream::TEXT) { |
| 118 uint8* side_data = av_packet_get_side_data( | 123 int id_size = 0; |
| 119 packet.get(), | 124 uint8* id_data = av_packet_get_side_data( |
| 120 AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, | 125 packet.get(), |
| 121 &side_data_size); | 126 AV_PKT_DATA_WEBVTT_IDENTIFIER, |
| 127 &id_size); | |
| 122 | 128 |
| 123 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will | 129 int settings_size = 0; |
| 124 // reference inner memory of FFmpeg. As such we should transfer the packet | 130 uint8* settings_data = av_packet_get_side_data( |
| 125 // into memory we control. | 131 packet.get(), |
| 126 scoped_refptr<DecoderBuffer> buffer; | 132 AV_PKT_DATA_WEBVTT_SETTINGS, |
| 127 if (side_data_size > 0) { | 133 &settings_size); |
| 134 | |
| 135 // The DecoderBuffer only supports a single side data item. In the case of | |
| 136 // a WebVTT cue, we can have potentially two side data items. In order to | |
| 137 // avoid disrupting DecoderBuffer any more than we need to, we copy both | |
| 138 // side data items onto a single one, and separate them with a marker | |
| 139 // (the byte value 0xFF is not part of the representation of any UTF8 | |
| 140 // character). | |
| 141 std::basic_string<uint8> side_data; | |
| 142 side_data.append(id_data, id_size); | |
| 143 side_data.append(1, 0xFF); | |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: Consider just using the null terminator and s
Matthew Heaney (Chromium)
2013/09/13 19:51:54
I appended a NUL to each sub-string, instead of us
| |
| 144 side_data.append(settings_data, settings_size); | |
| 145 | |
| 128 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, | 146 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, |
| 129 side_data, side_data_size); | 147 side_data.data(), side_data.length()); |
| 130 } else { | 148 } else { |
| 131 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size); | 149 int side_data_size = 0; |
| 132 } | 150 uint8* side_data = av_packet_get_side_data( |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
Why is this change needed for non-texttrack data?
Matthew Heaney (Chromium)
2013/09/13 19:51:54
I'm not quite following here. What change are you
acolwell GONE FROM CHROMIUM
2013/09/13 20:57:30
IIUC This is the non-text track code path. The ori
Matthew Heaney (Chromium)
2013/09/20 23:53:54
I checked master again -- this bit of code was alr
| |
| 151 packet.get(), | |
| 152 AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, | |
| 153 &side_data_size); | |
| 133 | 154 |
| 134 if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) || | 155 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will |
| 135 (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) { | 156 // reference inner memory of FFmpeg. As such we should transfer the packet |
| 136 scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig( | 157 // into memory we control. |
| 137 packet->data, packet->size, | 158 if (side_data_size > 0) { |
| 138 reinterpret_cast<const uint8*>(encryption_key_id_.data()), | 159 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size, |
| 139 encryption_key_id_.size())); | 160 side_data, side_data_size); |
| 140 if (!config) | 161 } else { |
| 141 LOG(ERROR) << "Creation of DecryptConfig failed."; | 162 buffer = DecoderBuffer::CopyFrom(packet.get()->data, packet.get()->size); |
| 142 buffer->set_decrypt_config(config.Pass()); | 163 } |
| 164 | |
| 165 if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) || | |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: I don't think you need to move this into the
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
| 166 (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) { | |
| 167 scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig( | |
| 168 packet->data, packet->size, | |
| 169 reinterpret_cast<const uint8*>(encryption_key_id_.data()), | |
| 170 encryption_key_id_.size())); | |
| 171 if (!config) | |
| 172 LOG(ERROR) << "Creation of DecryptConfig failed."; | |
| 173 buffer->set_decrypt_config(config.Pass()); | |
| 174 } | |
| 143 } | 175 } |
| 144 | 176 |
| 145 buffer->set_timestamp(ConvertStreamTimestamp( | 177 buffer->set_timestamp(ConvertStreamTimestamp( |
| 146 stream_->time_base, packet->pts)); | 178 stream_->time_base, packet->pts)); |
| 147 buffer->set_duration(ConvertStreamTimestamp( | 179 buffer->set_duration(ConvertStreamTimestamp( |
| 148 stream_->time_base, packet->duration)); | 180 stream_->time_base, packet->duration)); |
| 149 if (buffer->timestamp() != kNoTimestamp() && | 181 if (buffer->timestamp() != kNoTimestamp() && |
| 150 last_packet_timestamp_ != kNoTimestamp() && | 182 last_packet_timestamp_ != kNoTimestamp() && |
| 151 last_packet_timestamp_ < buffer->timestamp()) { | 183 last_packet_timestamp_ < buffer->timestamp()) { |
| 152 buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp()); | 184 buffered_ranges_.Add(last_packet_timestamp_, buffer->timestamp()); |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 281 return ConvertFromTimeBase(time_base, timestamp); | 313 return ConvertFromTimeBase(time_base, timestamp); |
| 282 } | 314 } |
| 283 | 315 |
| 284 // | 316 // |
| 285 // FFmpegDemuxer | 317 // FFmpegDemuxer |
| 286 // | 318 // |
| 287 FFmpegDemuxer::FFmpegDemuxer( | 319 FFmpegDemuxer::FFmpegDemuxer( |
| 288 const scoped_refptr<base::MessageLoopProxy>& message_loop, | 320 const scoped_refptr<base::MessageLoopProxy>& message_loop, |
| 289 DataSource* data_source, | 321 DataSource* data_source, |
| 290 const NeedKeyCB& need_key_cb, | 322 const NeedKeyCB& need_key_cb, |
| 323 const FFmpegAddTextTrackCB& add_text_track_cb, | |
| 291 const scoped_refptr<MediaLog>& media_log) | 324 const scoped_refptr<MediaLog>& media_log) |
| 292 : host_(NULL), | 325 : host_(NULL), |
| 293 message_loop_(message_loop), | 326 message_loop_(message_loop), |
| 294 weak_factory_(this), | 327 weak_factory_(this), |
| 295 blocking_thread_("FFmpegDemuxer"), | 328 blocking_thread_("FFmpegDemuxer"), |
| 296 pending_read_(false), | 329 pending_read_(false), |
| 297 pending_seek_(false), | 330 pending_seek_(false), |
| 298 data_source_(data_source), | 331 data_source_(data_source), |
| 299 media_log_(media_log), | 332 media_log_(media_log), |
| 300 bitrate_(0), | 333 bitrate_(0), |
| 301 start_time_(kNoTimestamp()), | 334 start_time_(kNoTimestamp()), |
| 302 audio_disabled_(false), | 335 audio_disabled_(false), |
| 303 duration_known_(false), | 336 duration_known_(false), |
| 304 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( | 337 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( |
| 305 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))), | 338 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))), |
| 306 need_key_cb_(need_key_cb) { | 339 need_key_cb_(need_key_cb), |
| 340 add_text_track_cb_(add_text_track_cb) { | |
| 307 DCHECK(message_loop_.get()); | 341 DCHECK(message_loop_.get()); |
| 308 DCHECK(data_source_); | 342 DCHECK(data_source_); |
| 309 } | 343 } |
| 310 | 344 |
| 311 FFmpegDemuxer::~FFmpegDemuxer() {} | 345 FFmpegDemuxer::~FFmpegDemuxer() {} |
| 312 | 346 |
| 313 void FFmpegDemuxer::Stop(const base::Closure& callback) { | 347 void FFmpegDemuxer::Stop(const base::Closure& callback) { |
| 314 DCHECK(message_loop_->BelongsToCurrentThread()); | 348 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 315 url_protocol_.Abort(); | 349 url_protocol_.Abort(); |
| 316 data_source_->Stop(BindToCurrentLoop(base::Bind( | 350 data_source_->Stop(BindToCurrentLoop(base::Bind( |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 386 FROM_HERE, | 420 FROM_HERE, |
| 387 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), | 421 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), |
| 388 base::Bind(&FFmpegDemuxer::OnOpenContextDone, weak_this_, status_cb)); | 422 base::Bind(&FFmpegDemuxer::OnOpenContextDone, weak_this_, status_cb)); |
| 389 } | 423 } |
| 390 | 424 |
| 391 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) { | 425 DemuxerStream* FFmpegDemuxer::GetStream(DemuxerStream::Type type) { |
| 392 DCHECK(message_loop_->BelongsToCurrentThread()); | 426 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 393 return GetFFmpegStream(type); | 427 return GetFFmpegStream(type); |
| 394 } | 428 } |
| 395 | 429 |
| 430 int FFmpegDemuxer::GetStreamCount() const { | |
| 431 return streams_.size(); | |
| 432 } | |
| 433 | |
| 434 DemuxerStream* FFmpegDemuxer::GetStreamByIndex(int idx) { | |
| 435 if (idx < 0 || StreamVector::size_type(idx) >= streams_.size()) { | |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: These should be DCHECKs.
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
| 436 return NULL; | |
| 437 } else { | |
| 438 return streams_[idx]; | |
| 439 } | |
| 440 } | |
| 441 | |
| 396 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( | 442 FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( |
| 397 DemuxerStream::Type type) const { | 443 DemuxerStream::Type type) const { |
| 398 StreamVector::const_iterator iter; | 444 StreamVector::const_iterator iter; |
| 399 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 445 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 400 if (*iter && (*iter)->type() == type) { | 446 if (*iter && (*iter)->type() == type) { |
| 401 return *iter; | 447 return *iter; |
| 402 } | 448 } |
| 403 } | 449 } |
| 404 return NULL; | 450 return NULL; |
| 405 } | 451 } |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 487 // partial playback. At least one audio or video stream must be playable. | 533 // partial playback. At least one audio or video stream must be playable. |
| 488 AVFormatContext* format_context = glue_->format_context(); | 534 AVFormatContext* format_context = glue_->format_context(); |
| 489 streams_.resize(format_context->nb_streams); | 535 streams_.resize(format_context->nb_streams); |
| 490 | 536 |
| 491 AVStream* audio_stream = NULL; | 537 AVStream* audio_stream = NULL; |
| 492 AudioDecoderConfig audio_config; | 538 AudioDecoderConfig audio_config; |
| 493 | 539 |
| 494 AVStream* video_stream = NULL; | 540 AVStream* video_stream = NULL; |
| 495 VideoDecoderConfig video_config; | 541 VideoDecoderConfig video_config; |
| 496 | 542 |
| 543 std::vector<AVStream*> text_streams(format_context->nb_streams); | |
| 544 | |
| 497 base::TimeDelta max_duration; | 545 base::TimeDelta max_duration; |
| 498 for (size_t i = 0; i < format_context->nb_streams; ++i) { | 546 for (size_t i = 0; i < format_context->nb_streams; ++i) { |
| 499 AVStream* stream = format_context->streams[i]; | 547 AVStream* stream = format_context->streams[i]; |
| 500 AVCodecContext* codec_context = stream->codec; | 548 AVCodecContext* codec_context = stream->codec; |
| 501 AVMediaType codec_type = codec_context->codec_type; | 549 AVMediaType codec_type = codec_context->codec_type; |
| 502 | 550 |
| 503 if (codec_type == AVMEDIA_TYPE_AUDIO) { | 551 if (codec_type == AVMEDIA_TYPE_AUDIO) { |
| 504 if (audio_stream) | 552 if (audio_stream) |
| 505 continue; | 553 continue; |
| 506 | 554 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 520 // Log the codec detected, whether it is supported or not. | 568 // Log the codec detected, whether it is supported or not. |
| 521 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec", | 569 UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec", |
| 522 codec_context->codec_id); | 570 codec_context->codec_id); |
| 523 // Ensure the codec is supported. IsValidConfig() also checks that the | 571 // Ensure the codec is supported. IsValidConfig() also checks that the |
| 524 // frame size and visible size are valid. | 572 // frame size and visible size are valid. |
| 525 AVStreamToVideoDecoderConfig(stream, &video_config, false); | 573 AVStreamToVideoDecoderConfig(stream, &video_config, false); |
| 526 | 574 |
| 527 if (!video_config.IsValidConfig()) | 575 if (!video_config.IsValidConfig()) |
| 528 continue; | 576 continue; |
| 529 video_stream = stream; | 577 video_stream = stream; |
| 578 } else if (codec_type == AVMEDIA_TYPE_SUBTITLE) { | |
| 579 if (codec_context->codec_id != AV_CODEC_ID_WEBVTT || | |
| 580 add_text_track_cb_.is_null()) { | |
| 581 continue; | |
| 582 } | |
| 583 | |
| 584 text_streams[i] = stream; | |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: Any harm in doing the work below, right here?
Matthew Heaney (Chromium)
2013/09/13 19:51:54
We iterate through all the streams first, in order
acolwell GONE FROM CHROMIUM
2013/09/13 20:57:30
I don't think this should be a problem. Reads on t
Matthew Heaney (Chromium)
2013/09/20 23:53:54
I ended up moving that loop into its own subprogra
| |
| 530 } else { | 585 } else { |
| 531 continue; | 586 continue; |
| 532 } | 587 } |
| 533 | 588 |
| 534 streams_[i] = new FFmpegDemuxerStream(this, stream); | 589 streams_[i] = new FFmpegDemuxerStream(this, stream); |
| 535 max_duration = std::max(max_duration, streams_[i]->duration()); | 590 max_duration = std::max(max_duration, streams_[i]->duration()); |
| 536 | 591 |
| 537 if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) { | 592 if (stream->first_dts != static_cast<int64_t>(AV_NOPTS_VALUE)) { |
| 538 const base::TimeDelta first_dts = ConvertFromTimeBase( | 593 const base::TimeDelta first_dts = ConvertFromTimeBase( |
| 539 stream->time_base, stream->first_dts); | 594 stream->time_base, stream->first_dts); |
| 540 if (start_time_ == kNoTimestamp() || first_dts < start_time_) | 595 if (start_time_ == kNoTimestamp() || first_dts < start_time_) |
| 541 start_time_ = first_dts; | 596 start_time_ = first_dts; |
| 542 } | 597 } |
| 543 } | 598 } |
| 544 | 599 |
| 545 if (!audio_stream && !video_stream) { | 600 if (!audio_stream && !video_stream) { |
| 546 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); | 601 status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS); |
| 547 return; | 602 return; |
| 548 } | 603 } |
| 549 | 604 |
| 605 for (size_t idx = 0; idx < text_streams.size(); ++idx) { | |
| 606 AVStream* text_stream = text_streams[idx]; | |
| 607 if (text_stream == NULL) | |
| 608 continue; | |
| 609 | |
| 610 TextKind kind; | |
| 611 | |
| 612 if (text_stream->disposition & AV_DISPOSITION_CAPTIONS) { | |
| 613 kind = kTextCaptions; | |
| 614 } else if (text_stream->disposition & AV_DISPOSITION_DESCRIPTIONS) { | |
| 615 kind = kTextDescriptions; | |
| 616 } else if (text_stream->disposition & AV_DISPOSITION_METADATA) { | |
| 617 kind = kTextMetadata; | |
| 618 } else { | |
| 619 kind = kTextSubtitles; | |
| 620 } | |
| 621 | |
| 622 AVDictionaryEntry* text_title = | |
| 623 av_dict_get(text_stream->metadata, "title", NULL, 0); | |
| 624 | |
| 625 std::string title; | |
| 626 | |
| 627 if (text_title != NULL && text_title->value != NULL) { | |
|
acolwell GONE FROM CHROMIUM
2013/09/12 00:15:15
nit: move av_dict_get() & NULL checks to a helper
Matthew Heaney (Chromium)
2013/09/13 19:51:54
Done.
| |
| 628 title = text_title->value; | |
| 629 } | |
| 630 | |
| 631 AVDictionaryEntry* text_language = | |
| 632 av_dict_get(text_stream->metadata, "language", NULL, 0); | |
| 633 | |
| 634 std::string language; | |
| 635 | |
| 636 if (text_language != NULL && text_language->value != NULL) { | |
| 637 language = text_language->value; | |
| 638 } | |
| 639 | |
| 640 add_text_track_cb_.Run(kind, title, language, idx); | |
| 641 } | |
| 642 | |
| 550 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) { | 643 if (format_context->duration != static_cast<int64_t>(AV_NOPTS_VALUE)) { |
| 551 // If there is a duration value in the container use that to find the | 644 // If there is a duration value in the container use that to find the |
| 552 // maximum between it and the duration from A/V streams. | 645 // maximum between it and the duration from A/V streams. |
| 553 const AVRational av_time_base = {1, AV_TIME_BASE}; | 646 const AVRational av_time_base = {1, AV_TIME_BASE}; |
| 554 max_duration = | 647 max_duration = |
| 555 std::max(max_duration, | 648 std::max(max_duration, |
| 556 ConvertFromTimeBase(av_time_base, format_context->duration)); | 649 ConvertFromTimeBase(av_time_base, format_context->duration)); |
| 557 } else { | 650 } else { |
| 558 // The duration is unknown, in which case this is likely a live stream. | 651 // The duration is unknown, in which case this is likely a live stream. |
| 559 max_duration = kInfiniteDuration(); | 652 max_duration = kInfiniteDuration(); |
| (...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 838 } | 931 } |
| 839 for (size_t i = 0; i < buffered.size(); ++i) | 932 for (size_t i = 0; i < buffered.size(); ++i) |
| 840 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); | 933 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); |
| 841 } | 934 } |
| 842 | 935 |
| 843 void FFmpegDemuxer::OnDataSourceError() { | 936 void FFmpegDemuxer::OnDataSourceError() { |
| 844 host_->OnDemuxerError(PIPELINE_ERROR_READ); | 937 host_->OnDemuxerError(PIPELINE_ERROR_READ); |
| 845 } | 938 } |
| 846 | 939 |
| 847 } // namespace media | 940 } // namespace media |
| OLD | NEW |