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