Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(181)

Side by Side Diff: media/filters/ffmpeg_demuxer.cc

Issue 10829470: Support for parsing encrypted WebM streams by src. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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/bind.h" 11 #include "base/bind.h"
11 #include "base/callback.h" 12 #include "base/callback.h"
12 #include "base/command_line.h" 13 #include "base/command_line.h"
13 #include "base/memory/scoped_ptr.h" 14 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop.h" 15 #include "base/message_loop.h"
15 #include "base/stl_util.h" 16 #include "base/stl_util.h"
16 #include "base/string_util.h" 17 #include "base/string_util.h"
18 #include "base/sys_byteorder.h"
17 #include "base/time.h" 19 #include "base/time.h"
18 #include "media/base/audio_decoder_config.h" 20 #include "media/base/audio_decoder_config.h"
19 #include "media/base/decoder_buffer.h" 21 #include "media/base/decoder_buffer.h"
20 #include "media/base/limits.h" 22 #include "media/base/limits.h"
21 #include "media/base/media_switches.h" 23 #include "media/base/media_switches.h"
22 #include "media/base/video_decoder_config.h" 24 #include "media/base/video_decoder_config.h"
23 #include "media/ffmpeg/ffmpeg_common.h" 25 #include "media/ffmpeg/ffmpeg_common.h"
24 #include "media/filters/ffmpeg_glue.h" 26 #include "media/filters/ffmpeg_glue.h"
25 #include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h" 27 #include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
28 #include "media/webm/webm_constants.h"
26 29
27 namespace media { 30 namespace media {
28 31
32 // Generates a 16 byte CTR counter block. The CTR counter block format is a
ddorwin 2012/08/22 23:20:29 We seem to do this a lot. Can we put it in media/b
fgalligan1 2012/08/23 02:39:11 Sure is there a convention where it should go?
xhwang 2012/08/23 19:04:53 media/crypto/decryptor_helpers.* ?
ddorwin 2012/08/24 00:20:30 and/or webm_helpers.* depending on what it is.
fgalligan1 2012/08/24 20:01:26 Done.
33 // CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
34 // Always returns a valid pointer to a buffer of kDecryptionKeySize bytes.
35 static scoped_array<uint8> GenerateCounterBlock(uint64 iv) {
36 scoped_array<uint8> counter_block_data(
37 new uint8[DecryptConfig::kDecryptionKeySize]);
38
39 // Set the IV.
40 memcpy(counter_block_data.get(), &iv, sizeof(iv));
41
42 // Set block counter to all 0's.
43 memset(counter_block_data.get() + sizeof(iv),
44 0,
45 DecryptConfig::kDecryptionKeySize - sizeof(iv));
46
47 return counter_block_data.Pass();
48 }
49
29 // 50 //
30 // FFmpegDemuxerStream 51 // FFmpegDemuxerStream
31 // 52 //
32 FFmpegDemuxerStream::FFmpegDemuxerStream( 53 FFmpegDemuxerStream::FFmpegDemuxerStream(
33 FFmpegDemuxer* demuxer, 54 FFmpegDemuxer* demuxer,
34 AVStream* stream) 55 AVStream* stream)
35 : demuxer_(demuxer), 56 : demuxer_(demuxer),
36 stream_(stream), 57 stream_(stream),
37 type_(UNKNOWN), 58 type_(UNKNOWN),
38 stopped_(false), 59 stopped_(false),
39 last_packet_timestamp_(kNoTimestamp()) { 60 last_packet_timestamp_(kNoTimestamp()),
61 need_decryption_key_(false) {
40 DCHECK(demuxer_); 62 DCHECK(demuxer_);
41 63
42 // Determine our media format. 64 // Determine our media format.
43 switch (stream->codec->codec_type) { 65 switch (stream->codec->codec_type) {
44 case AVMEDIA_TYPE_AUDIO: 66 case AVMEDIA_TYPE_AUDIO:
45 type_ = AUDIO; 67 type_ = AUDIO;
46 AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_); 68 AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_);
47 break; 69 break;
48 case AVMEDIA_TYPE_VIDEO: 70 case AVMEDIA_TYPE_VIDEO:
49 type_ = VIDEO; 71 type_ = VIDEO;
50 AVStreamToVideoDecoderConfig(stream, &video_config_); 72 AVStreamToVideoDecoderConfig(stream, &video_config_);
51 break; 73 break;
52 default: 74 default:
53 NOTREACHED(); 75 NOTREACHED();
54 break; 76 break;
55 } 77 }
56 78
57 // Calculate the duration. 79 // Calculate the duration.
58 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration); 80 duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration);
81
82 AVDictionaryEntry *key = av_dict_get(stream->metadata, "enc_key_id", NULL, 0);
83 if (key) {
84 DCHECK(key->value);
85 base::StringPiece base64_key_id(key->value);
86 base::Base64Decode(base64_key_id, &enc_key_id_);
87 CHECK(!enc_key_id_.empty());
ddorwin 2012/08/22 23:20:29 DCHECK? Do we prevent this in FFmpeg?
fgalligan1 2012/08/23 02:39:11 In the current code in FFmpeg we will only ads an
ddorwin 2012/08/24 00:20:30 I was just wondering whether DCHECK was sufficient
fgalligan1 2012/08/24 20:01:26 I had it as a DCHECK because at one point in time
88 need_decryption_key_ = true;
ddorwin 2012/08/22 23:20:29 The lifetime of this object is the entire media st
fgalligan1 2012/08/23 02:39:11 I don't think FFmpegDemuxerStream supports more th
ddorwin 2012/08/24 00:20:30 We should find out how acolwell's changes to handl
89 demuxer_->NeedKey(enc_key_id_);
90 }
91
92 // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. ***
93 //enc_key_id_ = "DEBUG_KEY_REMOVE";
ddorwin 2012/08/22 23:20:29 Could you av_dict_set() at 81 instead and thus exe
fgalligan1 2012/08/23 02:39:11 Done.
94 //need_decryption_key_ = true;
95 //demuxer_->NeedKey(enc_key_id_);
96 // *** DEBUG Until FFmpeg metadata "enck_key_id" is added for windows. ***
59 } 97 }
ddorwin 2012/08/22 23:20:29 I think the use of enc_key_id_ for both reporting
fgalligan1 2012/08/23 02:39:11 PTAL and let me if what I did is what you were ask
60 98
61 bool FFmpegDemuxerStream::HasPendingReads() { 99 bool FFmpegDemuxerStream::HasPendingReads() {
62 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread()); 100 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread());
63 base::AutoLock auto_lock(lock_); 101 base::AutoLock auto_lock(lock_);
64 DCHECK(!stopped_ || read_queue_.empty()) 102 DCHECK(!stopped_ || read_queue_.empty())
65 << "Read queue should have been emptied if demuxing stream is stopped"; 103 << "Read queue should have been emptied if demuxing stream is stopped";
66 return !read_queue_.empty(); 104 return !read_queue_.empty();
67 } 105 }
68 106
69 void FFmpegDemuxerStream::EnqueuePacket( 107 void FFmpegDemuxerStream::EnqueuePacket(
70 scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet) { 108 scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet) {
71 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread()); 109 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread());
72 110
73 base::AutoLock auto_lock(lock_); 111 base::AutoLock auto_lock(lock_);
74 if (stopped_) { 112 if (stopped_) {
75 NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; 113 NOTREACHED() << "Attempted to enqueue packet on a stopped stream";
76 return; 114 return;
77 } 115 }
78 116
79 scoped_refptr<DecoderBuffer> buffer; 117 scoped_refptr<DecoderBuffer> buffer;
80 if (!packet.get()) { 118 if (!packet.get()) {
81 buffer = DecoderBuffer::CreateEOSBuffer(); 119 buffer = DecoderBuffer::CreateEOSBuffer();
82 } else { 120 } else {
83 // Convert the packet if there is a bitstream filter. 121 // Convert the packet if there is a bitstream filter.
84 if (packet->data && bitstream_converter_.get() && 122 if (packet->data && bitstream_converter_.get() &&
85 !bitstream_converter_->ConvertPacket(packet.get())) { 123 !bitstream_converter_->ConvertPacket(packet.get())) {
86 LOG(ERROR) << "Format converstion failed."; 124 LOG(ERROR) << "Format converstion failed.";
87 } 125 }
88 126
127 // Every encrypted Block has an HMAC and IV prepended to it. Current
ddorwin 2012/08/22 23:20:29 We seem to do this a lot. Can we put it in media/b
fgalligan1 2012/08/23 02:39:11 You want me to remove the comment?
ddorwin 2012/08/24 00:20:30 No. Move the code like line 32.
fgalligan1 2012/08/24 20:01:26 Done.
128 // encrypted WebM request for comments specification is here
129 // http://wiki.webmproject.org/encryption/webm-encryption-rfc
130 // If encrypted skip past the HMAC. Encrypted buffers must include the IV
ddorwin 2012/08/22 23:20:29 If encrypted, skip past the HMAC to get the buffer
fgalligan1 2012/08/23 02:39:11 Done.
131 // and the encrypted frame because the decryptor will verify this data
132 // before decryption. The HMAC and IV will be copied into DecryptConfig.
133 int offset = (enc_key_id_.empty()) ? 0 : kWebMHmacSize;
134
89 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will 135 // If a packet is returned by FFmpeg's av_parser_parse2() the packet will
90 // reference inner memory of FFmpeg. As such we should transfer the packet 136 // reference inner memory of FFmpeg. As such we should transfer the packet
91 // into memory we control. 137 // into memory we control.
92 buffer = DecoderBuffer::CopyFrom(packet->data, packet->size); 138 buffer = DecoderBuffer::CopyFrom(packet->data + offset,
139 packet->size - offset);
140 if (!enc_key_id_.empty()) {
ddorwin 2012/08/22 23:20:29 We now have WebM code in the middle of the generic
fgalligan1 2012/08/23 02:39:11 Added a TODO, which can be done before the CL is s
141 uint64 network_iv;
142 memcpy(&network_iv, packet->data + kWebMHmacSize, sizeof(network_iv));
143 const uint64 iv = base::NetToHost64(network_iv);
144
145 scoped_array<uint8> counter_block(GenerateCounterBlock(iv));
146 buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
147 enc_key_id_,
148 std::string(reinterpret_cast<const char*>(counter_block.get()),
149 DecryptConfig::kDecryptionKeySize),
150 std::string(reinterpret_cast<const char*>(packet->data),
151 kWebMHmacSize),
152 sizeof(iv),
153 std::vector<SubsampleEntry>())));
154 }
155
93 buffer->SetTimestamp(ConvertStreamTimestamp( 156 buffer->SetTimestamp(ConvertStreamTimestamp(
94 stream_->time_base, packet->pts)); 157 stream_->time_base, packet->pts));
95 buffer->SetDuration(ConvertStreamTimestamp( 158 buffer->SetDuration(ConvertStreamTimestamp(
96 stream_->time_base, packet->duration)); 159 stream_->time_base, packet->duration));
97 if (buffer->GetTimestamp() != kNoTimestamp() && 160 if (buffer->GetTimestamp() != kNoTimestamp() &&
98 last_packet_timestamp_ != kNoTimestamp() && 161 last_packet_timestamp_ != kNoTimestamp() &&
99 last_packet_timestamp_ < buffer->GetTimestamp()) { 162 last_packet_timestamp_ < buffer->GetTimestamp()) {
100 buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp()); 163 buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp());
101 demuxer_->message_loop()->PostTask(FROM_HERE, base::Bind( 164 demuxer_->message_loop()->PostTask(FROM_HERE, base::Bind(
102 &FFmpegDemuxer::NotifyBufferingChanged, demuxer_)); 165 &FFmpegDemuxer::NotifyBufferingChanged, demuxer_));
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 read_cb.Run(DemuxerStream::kOk, 241 read_cb.Run(DemuxerStream::kOk,
179 scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer())); 242 scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer()));
180 return; 243 return;
181 } 244 }
182 245
183 // Enqueue the callback and attempt to satisfy it immediately. 246 // Enqueue the callback and attempt to satisfy it immediately.
184 read_queue_.push_back(read_cb); 247 read_queue_.push_back(read_cb);
185 FulfillPendingRead(); 248 FulfillPendingRead();
186 249
187 // Check if there are still pending reads, demux some more. 250 // Check if there are still pending reads, demux some more.
188 if (!read_queue_.empty()) { 251 if (!read_queue_.empty() && !need_decryption_key_) {
ddorwin 2012/08/22 23:20:29 Why not post if need a decryption key? It's up to
fgalligan1 2012/08/23 02:39:11 I thought this was a pretty clean break point in w
ddorwin 2012/08/24 00:20:30 As xhwang mentioned, I think this is already handl
fgalligan1 2012/08/24 20:01:26 Done. Works with xhwang changes.
189 demuxer_->PostDemuxTask(); 252 demuxer_->PostDemuxTask();
190 } 253 }
191 } 254 }
192 255
193 void FFmpegDemuxerStream::FulfillPendingRead() { 256 void FFmpegDemuxerStream::FulfillPendingRead() {
194 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread()); 257 DCHECK(demuxer_->message_loop()->BelongsToCurrentThread());
195 lock_.AssertAcquired(); 258 lock_.AssertAcquired();
196 if (buffer_queue_.empty() || read_queue_.empty()) { 259 if (buffer_queue_.empty() || read_queue_.empty()) {
197 return; 260 return;
198 } 261 }
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
231 base::AutoLock auto_lock(lock_); 294 base::AutoLock auto_lock(lock_);
232 DCHECK(stopped_); 295 DCHECK(stopped_);
233 DCHECK(read_queue_.empty()); 296 DCHECK(read_queue_.empty());
234 DCHECK(buffer_queue_.empty()); 297 DCHECK(buffer_queue_.empty());
235 } 298 }
236 299
237 base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const { 300 base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const {
238 return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts); 301 return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts);
239 } 302 }
240 303
304 void FFmpegDemuxerStream::KeyAdded() {
305 need_decryption_key_ = false;
306 }
307
241 Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const { 308 Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
242 base::AutoLock auto_lock(lock_); 309 base::AutoLock auto_lock(lock_);
243 return buffered_ranges_; 310 return buffered_ranges_;
244 } 311 }
245 312
246 // static 313 // static
247 base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp( 314 base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
248 const AVRational& time_base, int64 timestamp) { 315 const AVRational& time_base, int64 timestamp) {
249 if (timestamp == static_cast<int64>(AV_NOPTS_VALUE)) 316 if (timestamp == static_cast<int64>(AV_NOPTS_VALUE))
250 return kNoTimestamp(); 317 return kNoTimestamp();
251 318
252 return ConvertFromTimeBase(time_base, timestamp); 319 return ConvertFromTimeBase(time_base, timestamp);
253 } 320 }
254 321
255 // 322 //
256 // FFmpegDemuxer 323 // FFmpegDemuxer
257 // 324 //
258 FFmpegDemuxer::FFmpegDemuxer( 325 FFmpegDemuxer::FFmpegDemuxer(
259 const scoped_refptr<base::MessageLoopProxy>& message_loop, 326 const scoped_refptr<base::MessageLoopProxy>& message_loop,
260 const scoped_refptr<DataSource>& data_source) 327 const scoped_refptr<DataSource>& data_source,
328 const FFmpegNeedKeyCB& need_key_cb)
261 : host_(NULL), 329 : host_(NULL),
262 message_loop_(message_loop), 330 message_loop_(message_loop),
263 format_context_(NULL), 331 format_context_(NULL),
264 data_source_(data_source), 332 data_source_(data_source),
265 read_event_(false, false), 333 read_event_(false, false),
266 read_has_failed_(false), 334 read_has_failed_(false),
267 last_read_bytes_(0), 335 last_read_bytes_(0),
268 read_position_(0), 336 read_position_(0),
269 bitrate_(0), 337 bitrate_(0),
270 start_time_(kNoTimestamp()), 338 start_time_(kNoTimestamp()),
271 audio_disabled_(false), 339 audio_disabled_(false),
272 duration_known_(false) { 340 duration_known_(false),
341 need_key_cb_(need_key_cb) {
273 DCHECK(message_loop_); 342 DCHECK(message_loop_);
274 DCHECK(data_source_); 343 DCHECK(data_source_);
275 } 344 }
276 345
277 FFmpegDemuxer::~FFmpegDemuxer() { 346 FFmpegDemuxer::~FFmpegDemuxer() {
278 // In this destructor, we clean up resources held by FFmpeg. It is ugly to 347 // In this destructor, we clean up resources held by FFmpeg. It is ugly to
279 // close the codec contexts here because the corresponding codecs are opened 348 // close the codec contexts here because the corresponding codecs are opened
280 // in the decoder filters. By reaching this point, all filters should have 349 // in the decoder filters. By reaching this point, all filters should have
281 // stopped, so this is the only safe place to do the global clean up. 350 // stopped, so this is the only safe place to do the global clean up.
282 // TODO(hclam): close the codecs in the corresponding decoders. 351 // TODO(hclam): close the codecs in the corresponding decoders.
(...skipping 26 matching lines...) Expand all
309 void FFmpegDemuxer::SetPlaybackRate(float playback_rate) { 378 void FFmpegDemuxer::SetPlaybackRate(float playback_rate) {
310 DCHECK(data_source_.get()); 379 DCHECK(data_source_.get());
311 data_source_->SetPlaybackRate(playback_rate); 380 data_source_->SetPlaybackRate(playback_rate);
312 } 381 }
313 382
314 void FFmpegDemuxer::OnAudioRendererDisabled() { 383 void FFmpegDemuxer::OnAudioRendererDisabled() {
315 message_loop_->PostTask(FROM_HERE, base::Bind( 384 message_loop_->PostTask(FROM_HERE, base::Bind(
316 &FFmpegDemuxer::DisableAudioStreamTask, this)); 385 &FFmpegDemuxer::DisableAudioStreamTask, this));
317 } 386 }
318 387
388 void FFmpegDemuxer::KeyAdded() {
389 message_loop_->PostTask(FROM_HERE,
390 base::Bind(&FFmpegDemuxer::KeyAddedTask, this));
391 }
392
393 void FFmpegDemuxer::NeedKey(const std::string& key_id) {
394 int key_id_size = key_id.size();
395 scoped_array<uint8> key_id_local(new uint8[key_id_size]);
396 memcpy(key_id_local.get(), key_id.data(), key_id_size);
397 need_key_cb_.Run(key_id_local.Pass(), key_id_size);
398 }
399
319 void FFmpegDemuxer::Initialize(DemuxerHost* host, 400 void FFmpegDemuxer::Initialize(DemuxerHost* host,
320 const PipelineStatusCB& status_cb) { 401 const PipelineStatusCB& status_cb) {
321 message_loop_->PostTask(FROM_HERE, base::Bind( 402 message_loop_->PostTask(FROM_HERE, base::Bind(
322 &FFmpegDemuxer::InitializeTask, this, host, status_cb)); 403 &FFmpegDemuxer::InitializeTask, this, host, status_cb));
323 } 404 }
324 405
325 scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream( 406 scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(
326 DemuxerStream::Type type) { 407 DemuxerStream::Type type) {
327 return GetFFmpegStream(type); 408 return GetFFmpegStream(type);
328 } 409 }
(...skipping 344 matching lines...) Expand 10 before | Expand all | Expand 10 after
673 DCHECK(message_loop_->BelongsToCurrentThread()); 754 DCHECK(message_loop_->BelongsToCurrentThread());
674 audio_disabled_ = true; 755 audio_disabled_ = true;
675 StreamVector::iterator iter; 756 StreamVector::iterator iter;
676 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 757 for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
677 if (*iter && (*iter)->type() == DemuxerStream::AUDIO) { 758 if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
678 (*iter)->Stop(); 759 (*iter)->Stop();
679 } 760 }
680 } 761 }
681 } 762 }
682 763
764 void FFmpegDemuxer::KeyAddedTask() {
765 DCHECK(message_loop_->BelongsToCurrentThread());
766
767 StreamVector::iterator iter;
768 for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
769 if (*iter && (*iter)->type() == DemuxerStream::VIDEO) {
770 (*iter)->KeyAdded();
771 }
772 }
773
774 PostDemuxTask();
775 }
776
683 bool FFmpegDemuxer::StreamsHavePendingReads() { 777 bool FFmpegDemuxer::StreamsHavePendingReads() {
684 DCHECK(message_loop_->BelongsToCurrentThread()); 778 DCHECK(message_loop_->BelongsToCurrentThread());
685 StreamVector::iterator iter; 779 StreamVector::iterator iter;
686 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { 780 for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
687 if (*iter && (*iter)->HasPendingReads()) { 781 if (*iter && (*iter)->HasPendingReads()) {
688 return true; 782 return true;
689 } 783 }
690 } 784 }
691 return false; 785 return false;
692 } 786 }
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
727 } else if (audio) { 821 } else if (audio) {
728 buffered = audio->GetBufferedRanges(); 822 buffered = audio->GetBufferedRanges();
729 } else if (video) { 823 } else if (video) {
730 buffered = video->GetBufferedRanges(); 824 buffered = video->GetBufferedRanges();
731 } 825 }
732 for (size_t i = 0; i < buffered.size(); ++i) 826 for (size_t i = 0; i < buffered.size(); ++i)
733 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); 827 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i));
734 } 828 }
735 829
736 } // namespace media 830 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698