1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/renderer/media/buffered_data_source.h"
6
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/message_loop/message_loop_proxy.h"
10 #include "media/base/media_log.h"
11 #include "net/base/net_errors.h"
12
13 using blink::WebFrame;
14
15 namespace {
16
17 // BufferedDataSource has an intermediate buffer, this value governs the initial
18 // size of that buffer. It is set to 32KB because this is a typical read size
19 // of FFmpeg.
20 const int kInitialReadBufferSize = 32768;
21
22 // Number of cache misses we allow for a single Read() before signaling an
23 // error.
24 const int kNumCacheMissRetries = 3;
25
26 } // namespace
27
28 namespace content {
29
30 class BufferedDataSource::ReadOperation {
31 public:
32 ReadOperation(int64 position, int size, uint8* data,
33 const media::DataSource::ReadCB& callback);
34 ~ReadOperation();
35
36 // Runs |callback_| with the given |result|, deleting the operation
37 // afterwards.
38 static void Run(scoped_ptr<ReadOperation> read_op, int result);
39
40 // State for the number of times this read operation has been retried.
retries()41 int retries() { return retries_; }
IncrementRetries()42 void IncrementRetries() { ++retries_; }
43
position()44 int64 position() { return position_; }
size()45 int size() { return size_; }
data()46 uint8* data() { return data_; }
47
48 private:
49 int retries_;
50
51 const int64 position_;
52 const int size_;
53 uint8* data_;
54 media::DataSource::ReadCB callback_;
55
56 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation);
57 };
58
ReadOperation(int64 position,int size,uint8 * data,const media::DataSource::ReadCB & callback)59 BufferedDataSource::ReadOperation::ReadOperation(
60 int64 position, int size, uint8* data,
61 const media::DataSource::ReadCB& callback)
62 : retries_(0),
63 position_(position),
64 size_(size),
65 data_(data),
66 callback_(callback) {
67 DCHECK(!callback_.is_null());
68 }
69
~ReadOperation()70 BufferedDataSource::ReadOperation::~ReadOperation() {
71 DCHECK(callback_.is_null());
72 }
73
74 // static
Run(scoped_ptr<ReadOperation> read_op,int result)75 void BufferedDataSource::ReadOperation::Run(
76 scoped_ptr<ReadOperation> read_op, int result) {
77 base::ResetAndReturn(&read_op->callback_).Run(result);
78 }
79
BufferedDataSource(const scoped_refptr<base::MessageLoopProxy> & render_loop,WebFrame * frame,media::MediaLog * media_log,const DownloadingCB & downloading_cb)80 BufferedDataSource::BufferedDataSource(
81 const scoped_refptr<base::MessageLoopProxy>& render_loop,
82 WebFrame* frame,
83 media::MediaLog* media_log,
84 const DownloadingCB& downloading_cb)
85 : cors_mode_(BufferedResourceLoader::kUnspecified),
86 total_bytes_(kPositionNotSpecified),
87 assume_fully_buffered_(false),
88 streaming_(false),
89 frame_(frame),
90 intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
91 intermediate_read_buffer_size_(kInitialReadBufferSize),
92 render_loop_(render_loop),
93 stop_signal_received_(false),
94 media_has_played_(false),
95 preload_(AUTO),
96 bitrate_(0),
97 playback_rate_(0.0),
98 media_log_(media_log),
99 downloading_cb_(downloading_cb),
100 weak_factory_(this) {
101 DCHECK(!downloading_cb_.is_null());
102 weak_this_ = weak_factory_.GetWeakPtr();
103 }
104
~BufferedDataSource()105 BufferedDataSource::~BufferedDataSource() {}
106
107 // A factory method to create BufferedResourceLoader using the read parameters.
108 // This method can be overridden to inject mock BufferedResourceLoader object
109 // for testing purpose.
CreateResourceLoader(int64 first_byte_position,int64 last_byte_position)110 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
111 int64 first_byte_position, int64 last_byte_position) {
112 DCHECK(render_loop_->BelongsToCurrentThread());
113
114 BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
115 BufferedResourceLoader::kReadThenDefer :
116 BufferedResourceLoader::kCapacityDefer;
117
118 return new BufferedResourceLoader(url_,
119 cors_mode_,
120 first_byte_position,
121 last_byte_position,
122 strategy,
123 bitrate_,
124 playback_rate_,
125 media_log_.get());
126 }
127
set_host(media::DataSourceHost * host)128 void BufferedDataSource::set_host(media::DataSourceHost* host) {
129 DataSource::set_host(host);
130
131 if (loader_) {
132 base::AutoLock auto_lock(lock_);
133 UpdateHostState_Locked();
134 }
135 }
136
Initialize(const GURL & url,BufferedResourceLoader::CORSMode cors_mode,const InitializeCB & init_cb)137 void BufferedDataSource::Initialize(
138 const GURL& url,
139 BufferedResourceLoader::CORSMode cors_mode,
140 const InitializeCB& init_cb) {
141 DCHECK(render_loop_->BelongsToCurrentThread());
142 DCHECK(!init_cb.is_null());
143 DCHECK(!loader_.get());
144 url_ = url;
145 cors_mode_ = cors_mode;
146
147 init_cb_ = init_cb;
148
149 if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
150 // Do an unbounded range request starting at the beginning. If the server
151 // responds with 200 instead of 206 we'll fall back into a streaming mode.
152 loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
153 } else {
154 // For all other protocols, assume they support range request. We fetch
155 // the full range of the resource to obtain the instance size because
156 // we won't be served HTTP headers.
157 loader_.reset(CreateResourceLoader(kPositionNotSpecified,
158 kPositionNotSpecified));
159 assume_fully_buffered_ = true;
160 }
161
162 loader_->Start(
163 base::Bind(&BufferedDataSource::StartCallback, weak_this_),
164 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this_),
165 base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
166 frame_);
167 }
168
SetPreload(Preload preload)169 void BufferedDataSource::SetPreload(Preload preload) {
170 DCHECK(render_loop_->BelongsToCurrentThread());
171 preload_ = preload;
172 }
173
HasSingleOrigin()174 bool BufferedDataSource::HasSingleOrigin() {
175 DCHECK(render_loop_->BelongsToCurrentThread());
176 DCHECK(init_cb_.is_null() && loader_.get())
177 << "Initialize() must complete before calling HasSingleOrigin()";
178 return loader_->HasSingleOrigin();
179 }
180
DidPassCORSAccessCheck() const181 bool BufferedDataSource::DidPassCORSAccessCheck() const {
182 return loader_.get() && loader_->DidPassCORSAccessCheck();
183 }
184
Abort()185 void BufferedDataSource::Abort() {
186 DCHECK(render_loop_->BelongsToCurrentThread());
187 {
188 base::AutoLock auto_lock(lock_);
189 StopInternal_Locked();
190 }
191 StopLoader();
192 frame_ = NULL;
193 }
194
MediaPlaybackRateChanged(float playback_rate)195 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) {
196 DCHECK(render_loop_->BelongsToCurrentThread());
197 DCHECK(loader_.get());
198
199 if (playback_rate < 0.0f)
200 return;
201
202 playback_rate_ = playback_rate;
203 loader_->SetPlaybackRate(playback_rate);
204 }
205
MediaIsPlaying()206 void BufferedDataSource::MediaIsPlaying() {
207 DCHECK(render_loop_->BelongsToCurrentThread());
208 media_has_played_ = true;
209 UpdateDeferStrategy(false);
210 }
211
MediaIsPaused()212 void BufferedDataSource::MediaIsPaused() {
213 DCHECK(render_loop_->BelongsToCurrentThread());
214 UpdateDeferStrategy(true);
215 }
216
217 /////////////////////////////////////////////////////////////////////////////
218 // media::DataSource implementation.
Stop(const base::Closure & closure)219 void BufferedDataSource::Stop(const base::Closure& closure) {
220 {
221 base::AutoLock auto_lock(lock_);
222 StopInternal_Locked();
223 }
224 closure.Run();
225
226 render_loop_->PostTask(FROM_HERE,
227 base::Bind(&BufferedDataSource::StopLoader, weak_this_));
228 }
229
SetBitrate(int bitrate)230 void BufferedDataSource::SetBitrate(int bitrate) {
231 render_loop_->PostTask(FROM_HERE, base::Bind(
232 &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
233 }
234
Read(int64 position,int size,uint8 * data,const media::DataSource::ReadCB & read_cb)235 void BufferedDataSource::Read(
236 int64 position, int size, uint8* data,
237 const media::DataSource::ReadCB& read_cb) {
238 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
239 DCHECK(!read_cb.is_null());
240
241 {
242 base::AutoLock auto_lock(lock_);
243 DCHECK(!read_op_);
244
245 if (stop_signal_received_) {
246 read_cb.Run(kReadError);
247 return;
248 }
249
250 read_op_.reset(new ReadOperation(position, size, data, read_cb));
251 }
252
253 render_loop_->PostTask(FROM_HERE, base::Bind(
254 &BufferedDataSource::ReadTask, weak_this_));
255 }
256
GetSize(int64 * size_out)257 bool BufferedDataSource::GetSize(int64* size_out) {
258 if (total_bytes_ != kPositionNotSpecified) {
259 *size_out = total_bytes_;
260 return true;
261 }
262 *size_out = 0;
263 return false;
264 }
265
IsStreaming()266 bool BufferedDataSource::IsStreaming() {
267 return streaming_;
268 }
269
270 /////////////////////////////////////////////////////////////////////////////
271 // Render thread tasks.
ReadTask()272 void BufferedDataSource::ReadTask() {
273 DCHECK(render_loop_->BelongsToCurrentThread());
274 ReadInternal();
275 }
276
StopInternal_Locked()277 void BufferedDataSource::StopInternal_Locked() {
278 lock_.AssertAcquired();
279 if (stop_signal_received_)
280 return;
281
282 stop_signal_received_ = true;
283
284 // Initialize() isn't part of the DataSource interface so don't call it in
285 // response to Stop().
286 init_cb_.Reset();
287
288 if (read_op_)
289 ReadOperation::Run(read_op_.Pass(), kReadError);
290 }
291
StopLoader()292 void BufferedDataSource::StopLoader() {
293 DCHECK(render_loop_->BelongsToCurrentThread());
294
295 if (loader_)
296 loader_->Stop();
297 }
298
SetBitrateTask(int bitrate)299 void BufferedDataSource::SetBitrateTask(int bitrate) {
300 DCHECK(render_loop_->BelongsToCurrentThread());
301 DCHECK(loader_.get());
302
303 bitrate_ = bitrate;
304 loader_->SetBitrate(bitrate);
305 }
306
307 // This method is the place where actual read happens, |loader_| must be valid
308 // prior to make this method call.
ReadInternal()309 void BufferedDataSource::ReadInternal() {
310 DCHECK(render_loop_->BelongsToCurrentThread());
311 int64 position = 0;
312 int size = 0;
313 {
314 base::AutoLock auto_lock(lock_);
315 if (stop_signal_received_)
316 return;
317
318 position = read_op_->position();
319 size = read_op_->size();
320 }
321
322 // First we prepare the intermediate read buffer for BufferedResourceLoader
323 // to write to.
324 if (size > intermediate_read_buffer_size_) {
325 intermediate_read_buffer_.reset(new uint8[size]);
326 }
327
328 // Perform the actual read with BufferedResourceLoader.
329 loader_->Read(
330 position, size, intermediate_read_buffer_.get(),
331 base::Bind(&BufferedDataSource::ReadCallback, weak_this_));
332 }
333
334
335 /////////////////////////////////////////////////////////////////////////////
336 // BufferedResourceLoader callback methods.
StartCallback(BufferedResourceLoader::Status status)337 void BufferedDataSource::StartCallback(
338 BufferedResourceLoader::Status status) {
339 DCHECK(render_loop_->BelongsToCurrentThread());
340 DCHECK(loader_.get());
341
342 bool init_cb_is_null = false;
343 {
344 base::AutoLock auto_lock(lock_);
345 init_cb_is_null = init_cb_.is_null();
346 }
347 if (init_cb_is_null) {
348 loader_->Stop();
349 return;
350 }
351
352 // All responses must be successful. Resources that are assumed to be fully
353 // buffered must have a known content length.
354 bool success = status == BufferedResourceLoader::kOk &&
355 (!assume_fully_buffered_ ||
356 loader_->instance_size() != kPositionNotSpecified);
357
358 if (success) {
359 total_bytes_ = loader_->instance_size();
360 streaming_ = !assume_fully_buffered_ &&
361 (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
362
363 media_log_->SetDoubleProperty("total_bytes",
364 static_cast<double>(total_bytes_));
365 media_log_->SetBooleanProperty("streaming", streaming_);
366 } else {
367 loader_->Stop();
368 }
369
370 // TODO(scherkus): we shouldn't have to lock to signal host(), see
371 // http://crbug.com/113712 for details.
372 base::AutoLock auto_lock(lock_);
373 if (stop_signal_received_)
374 return;
375
376 if (success) {
377 UpdateHostState_Locked();
378 media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin());
379 media_log_->SetBooleanProperty("passed_cors_access_check",
380 loader_->DidPassCORSAccessCheck());
381 media_log_->SetBooleanProperty("range_header_supported",
382 loader_->range_supported());
383 }
384
385 base::ResetAndReturn(&init_cb_).Run(success);
386 }
387
PartialReadStartCallback(BufferedResourceLoader::Status status)388 void BufferedDataSource::PartialReadStartCallback(
389 BufferedResourceLoader::Status status) {
390 DCHECK(render_loop_->BelongsToCurrentThread());
391 DCHECK(loader_.get());
392
393 if (status == BufferedResourceLoader::kOk) {
394 // Once the request has started successfully, we can proceed with
395 // reading from it.
396 ReadInternal();
397 return;
398 }
399
400 // Stop the resource loader since we have received an error.
401 loader_->Stop();
402
403 // TODO(scherkus): we shouldn't have to lock to signal host(), see
404 // http://crbug.com/113712 for details.
405 base::AutoLock auto_lock(lock_);
406 if (stop_signal_received_)
407 return;
408 ReadOperation::Run(read_op_.Pass(), kReadError);
409 }
410
ReadCallback(BufferedResourceLoader::Status status,int bytes_read)411 void BufferedDataSource::ReadCallback(
412 BufferedResourceLoader::Status status,
413 int bytes_read) {
414 DCHECK(render_loop_->BelongsToCurrentThread());
415
416 // TODO(scherkus): we shouldn't have to lock to signal host(), see
417 // http://crbug.com/113712 for details.
418 base::AutoLock auto_lock(lock_);
419 if (stop_signal_received_)
420 return;
421
422 if (status != BufferedResourceLoader::kOk) {
423 // Stop the resource load if it failed.
424 loader_->Stop();
425
426 if (status == BufferedResourceLoader::kCacheMiss &&
427 read_op_->retries() < kNumCacheMissRetries) {
428 read_op_->IncrementRetries();
429
430 // Recreate a loader starting from where we last left off until the
431 // end of the resource.
432 loader_.reset(CreateResourceLoader(
433 read_op_->position(), kPositionNotSpecified));
434 loader_->Start(
435 base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this_),
436 base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
437 weak_this_),
438 base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
439 frame_);
440 return;
441 }
442
443 ReadOperation::Run(read_op_.Pass(), kReadError);
444 return;
445 }
446
447 if (bytes_read > 0) {
448 memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
449 } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
450 // We've reached the end of the file and we didn't know the total size
451 // before. Update the total size so Read()s past the end of the file will
452 // fail like they would if we had known the file size at the beginning.
453 total_bytes_ = loader_->instance_size();
454
455 if (host() && total_bytes_ != kPositionNotSpecified) {
456 host()->SetTotalBytes(total_bytes_);
457 host()->AddBufferedByteRange(loader_->first_byte_position(),
458 total_bytes_);
459 }
460 }
461 ReadOperation::Run(read_op_.Pass(), bytes_read);
462 }
463
LoadingStateChangedCallback(BufferedResourceLoader::LoadingState state)464 void BufferedDataSource::LoadingStateChangedCallback(
465 BufferedResourceLoader::LoadingState state) {
466 DCHECK(render_loop_->BelongsToCurrentThread());
467
468 if (assume_fully_buffered_)
469 return;
470
471 bool is_downloading_data;
472 switch (state) {
473 case BufferedResourceLoader::kLoading:
474 is_downloading_data = true;
475 break;
476 case BufferedResourceLoader::kLoadingDeferred:
477 case BufferedResourceLoader::kLoadingFinished:
478 is_downloading_data = false;
479 break;
480
481 // TODO(scherkus): we don't signal network activity changes when loads
482 // fail to preserve existing behaviour when deferring is toggled, however
483 // we should consider changing DownloadingCB to also propagate loading
484 // state. For example there isn't any signal today to notify the client that
485 // loading has failed (we only get errors on subsequent reads).
486 case BufferedResourceLoader::kLoadingFailed:
487 return;
488 }
489
490 downloading_cb_.Run(is_downloading_data);
491 }
492
ProgressCallback(int64 position)493 void BufferedDataSource::ProgressCallback(int64 position) {
494 DCHECK(render_loop_->BelongsToCurrentThread());
495
496 if (assume_fully_buffered_)
497 return;
498
499 // TODO(scherkus): we shouldn't have to lock to signal host(), see
500 // http://crbug.com/113712 for details.
501 base::AutoLock auto_lock(lock_);
502 if (stop_signal_received_)
503 return;
504
505 ReportOrQueueBufferedBytes(loader_->first_byte_position(), position);
506 }
507
ReportOrQueueBufferedBytes(int64 start,int64 end)508 void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) {
509 if (host())
510 host()->AddBufferedByteRange(start, end);
511 else
512 queued_buffered_byte_ranges_.Add(start, end);
513 }
514
UpdateHostState_Locked()515 void BufferedDataSource::UpdateHostState_Locked() {
516 lock_.AssertAcquired();
517
518 if (!host())
519 return;
520
521 for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) {
522 host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i),
523 queued_buffered_byte_ranges_.end(i));
524 }
525 queued_buffered_byte_ranges_.clear();
526
527 if (total_bytes_ == kPositionNotSpecified)
528 return;
529
530 host()->SetTotalBytes(total_bytes_);
531
532 if (assume_fully_buffered_)
533 host()->AddBufferedByteRange(0, total_bytes_);
534 }
535
UpdateDeferStrategy(bool paused)536 void BufferedDataSource::UpdateDeferStrategy(bool paused) {
537 // 200 responses end up not being reused to satisfy future range requests,
538 // and we don't want to get too far ahead of the read-head (and thus require
539 // a restart), so keep to the thresholds.
540 if (!loader_->range_supported()) {
541 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
542 return;
543 }
544
545 // If the playback has started (at which point the preload value is ignored)
546 // and we're paused, then try to load as much as possible (the loader will
547 // fall back to kCapacityDefer if it knows the current response won't be
548 // useful from the cache in the future).
549 if (media_has_played_ && paused) {
550 loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
551 return;
552 }
553
554 // If media is currently playing or the page indicated preload=auto,
555 // use threshold strategy to enable/disable deferring when the buffer
556 // is full/depleted.
557 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
558 }
559
560 } // namespace content
561