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.
5 #include "media/blink/buffered_data_source.h"
8 #include "base/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/single_thread_task_runner.h"
11 #include "media/base/media_log.h"
12 #include "net/base/net_errors.h"
14 using blink::WebFrame
;
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
21 const int kInitialReadBufferSize
= 32768;
23 // Number of cache misses or read failures we allow for a single Read() before
24 // signaling an error.
25 const int kLoaderRetries
= 3;
27 // The number of milliseconds to wait before retrying a failed load.
28 const int kLoaderFailedRetryDelayMs
= 250;
34 class BufferedDataSource::ReadOperation
{
36 ReadOperation(int64 position
, int size
, uint8
* data
,
37 const DataSource::ReadCB
& callback
);
40 // Runs |callback_| with the given |result|, deleting the operation
42 static void Run(scoped_ptr
<ReadOperation
> read_op
, int result
);
44 // State for the number of times this read operation has been retried.
45 int retries() { return retries_
; }
46 void IncrementRetries() { ++retries_
; }
48 int64
position() { return position_
; }
49 int size() { return size_
; }
50 uint8
* data() { return data_
; }
55 const int64 position_
;
58 DataSource::ReadCB callback_
;
60 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation
);
63 BufferedDataSource::ReadOperation::ReadOperation(
64 int64 position
, int size
, uint8
* data
,
65 const DataSource::ReadCB
& callback
)
71 DCHECK(!callback_
.is_null());
74 BufferedDataSource::ReadOperation::~ReadOperation() {
75 DCHECK(callback_
.is_null());
79 void BufferedDataSource::ReadOperation::Run(
80 scoped_ptr
<ReadOperation
> read_op
, int result
) {
81 base::ResetAndReturn(&read_op
->callback_
).Run(result
);
84 BufferedDataSource::BufferedDataSource(
86 BufferedResourceLoader::CORSMode cors_mode
,
87 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
90 BufferedDataSourceHost
* host
,
91 const DownloadingCB
& downloading_cb
)
93 cors_mode_(cors_mode
),
94 total_bytes_(kPositionNotSpecified
),
97 intermediate_read_buffer_(kInitialReadBufferSize
),
98 render_task_runner_(task_runner
),
99 stop_signal_received_(false),
100 media_has_played_(false),
104 media_log_(media_log
),
106 downloading_cb_(downloading_cb
),
107 weak_factory_(this) {
108 weak_ptr_
= weak_factory_
.GetWeakPtr();
110 DCHECK(!downloading_cb_
.is_null());
111 DCHECK(render_task_runner_
->BelongsToCurrentThread());
114 BufferedDataSource::~BufferedDataSource() {
115 DCHECK(render_task_runner_
->BelongsToCurrentThread());
118 // A factory method to create BufferedResourceLoader using the read parameters.
119 // This method can be overridden to inject mock BufferedResourceLoader object
120 // for testing purpose.
121 BufferedResourceLoader
* BufferedDataSource::CreateResourceLoader(
122 int64 first_byte_position
, int64 last_byte_position
) {
123 DCHECK(render_task_runner_
->BelongsToCurrentThread());
125 BufferedResourceLoader::DeferStrategy strategy
= preload_
== METADATA
?
126 BufferedResourceLoader::kReadThenDefer
:
127 BufferedResourceLoader::kCapacityDefer
;
129 return new BufferedResourceLoader(url_
,
139 void BufferedDataSource::Initialize(const InitializeCB
& init_cb
) {
140 DCHECK(render_task_runner_
->BelongsToCurrentThread());
141 DCHECK(!init_cb
.is_null());
142 DCHECK(!loader_
.get());
146 if (url_
.SchemeIsHTTPOrHTTPS()) {
147 // Do an unbounded range request starting at the beginning. If the server
148 // responds with 200 instead of 206 we'll fall back into a streaming mode.
149 loader_
.reset(CreateResourceLoader(0, kPositionNotSpecified
));
151 // For all other protocols, assume they support range request. We fetch
152 // the full range of the resource to obtain the instance size because
153 // we won't be served HTTP headers.
154 loader_
.reset(CreateResourceLoader(kPositionNotSpecified
,
155 kPositionNotSpecified
));
158 base::WeakPtr
<BufferedDataSource
> weak_this
= weak_factory_
.GetWeakPtr();
160 base::Bind(&BufferedDataSource::StartCallback
, weak_this
),
161 base::Bind(&BufferedDataSource::LoadingStateChangedCallback
, weak_this
),
162 base::Bind(&BufferedDataSource::ProgressCallback
, weak_this
),
166 void BufferedDataSource::SetPreload(Preload preload
) {
167 DCHECK(render_task_runner_
->BelongsToCurrentThread());
171 bool BufferedDataSource::HasSingleOrigin() {
172 DCHECK(render_task_runner_
->BelongsToCurrentThread());
173 DCHECK(init_cb_
.is_null() && loader_
.get())
174 << "Initialize() must complete before calling HasSingleOrigin()";
175 return loader_
->HasSingleOrigin();
178 bool BufferedDataSource::DidPassCORSAccessCheck() const {
179 return loader_
.get() && loader_
->DidPassCORSAccessCheck();
182 void BufferedDataSource::Abort() {
183 DCHECK(render_task_runner_
->BelongsToCurrentThread());
185 base::AutoLock
auto_lock(lock_
);
186 StopInternal_Locked();
192 void BufferedDataSource::MediaPlaybackRateChanged(double playback_rate
) {
193 DCHECK(render_task_runner_
->BelongsToCurrentThread());
194 DCHECK(loader_
.get());
196 if (playback_rate
< 0.0)
199 playback_rate_
= playback_rate
;
200 loader_
->SetPlaybackRate(playback_rate
);
203 void BufferedDataSource::MediaIsPlaying() {
204 DCHECK(render_task_runner_
->BelongsToCurrentThread());
205 media_has_played_
= true;
206 UpdateDeferStrategy(false);
209 void BufferedDataSource::MediaIsPaused() {
210 DCHECK(render_task_runner_
->BelongsToCurrentThread());
211 UpdateDeferStrategy(true);
214 /////////////////////////////////////////////////////////////////////////////
215 // DataSource implementation.
216 void BufferedDataSource::Stop() {
218 base::AutoLock
auto_lock(lock_
);
219 StopInternal_Locked();
222 render_task_runner_
->PostTask(
224 base::Bind(&BufferedDataSource::StopLoader
, weak_factory_
.GetWeakPtr()));
227 void BufferedDataSource::SetBitrate(int bitrate
) {
228 render_task_runner_
->PostTask(FROM_HERE
,
229 base::Bind(&BufferedDataSource::SetBitrateTask
,
230 weak_factory_
.GetWeakPtr(),
234 void BufferedDataSource::OnBufferingHaveEnough() {
235 DCHECK(render_task_runner_
->BelongsToCurrentThread());
236 if (loader_
&& preload_
== METADATA
&& !media_has_played_
&& !IsStreaming())
237 loader_
->CancelUponDeferral();
240 void BufferedDataSource::Read(
241 int64 position
, int size
, uint8
* data
,
242 const DataSource::ReadCB
& read_cb
) {
243 DVLOG(1) << "Read: " << position
<< " offset, " << size
<< " bytes";
244 DCHECK(!read_cb
.is_null());
247 base::AutoLock
auto_lock(lock_
);
250 if (stop_signal_received_
) {
251 read_cb
.Run(kReadError
);
255 read_op_
.reset(new ReadOperation(position
, size
, data
, read_cb
));
258 render_task_runner_
->PostTask(
260 base::Bind(&BufferedDataSource::ReadTask
, weak_factory_
.GetWeakPtr()));
263 bool BufferedDataSource::GetSize(int64
* size_out
) {
264 if (total_bytes_
!= kPositionNotSpecified
) {
265 *size_out
= total_bytes_
;
272 bool BufferedDataSource::IsStreaming() {
276 /////////////////////////////////////////////////////////////////////////////
277 // Render thread tasks.
278 void BufferedDataSource::ReadTask() {
279 DCHECK(render_task_runner_
->BelongsToCurrentThread());
283 void BufferedDataSource::StopInternal_Locked() {
284 lock_
.AssertAcquired();
285 if (stop_signal_received_
)
288 stop_signal_received_
= true;
290 // Initialize() isn't part of the DataSource interface so don't call it in
291 // response to Stop().
295 ReadOperation::Run(read_op_
.Pass(), kReadError
);
298 void BufferedDataSource::StopLoader() {
299 DCHECK(render_task_runner_
->BelongsToCurrentThread());
305 void BufferedDataSource::SetBitrateTask(int bitrate
) {
306 DCHECK(render_task_runner_
->BelongsToCurrentThread());
307 DCHECK(loader_
.get());
310 loader_
->SetBitrate(bitrate
);
313 // This method is the place where actual read happens, |loader_| must be valid
314 // prior to make this method call.
315 void BufferedDataSource::ReadInternal() {
316 DCHECK(render_task_runner_
->BelongsToCurrentThread());
320 base::AutoLock
auto_lock(lock_
);
321 if (stop_signal_received_
)
324 position
= read_op_
->position();
325 size
= read_op_
->size();
328 // First we prepare the intermediate read buffer for BufferedResourceLoader
330 if (static_cast<int>(intermediate_read_buffer_
.size()) < size
)
331 intermediate_read_buffer_
.resize(size
);
333 // Perform the actual read with BufferedResourceLoader.
334 DCHECK(!intermediate_read_buffer_
.empty());
335 loader_
->Read(position
,
337 &intermediate_read_buffer_
[0],
338 base::Bind(&BufferedDataSource::ReadCallback
,
339 weak_factory_
.GetWeakPtr()));
343 /////////////////////////////////////////////////////////////////////////////
344 // BufferedResourceLoader callback methods.
345 void BufferedDataSource::StartCallback(
346 BufferedResourceLoader::Status status
) {
347 DCHECK(render_task_runner_
->BelongsToCurrentThread());
348 DCHECK(loader_
.get());
350 bool init_cb_is_null
= false;
352 base::AutoLock
auto_lock(lock_
);
353 init_cb_is_null
= init_cb_
.is_null();
355 if (init_cb_is_null
) {
360 // All responses must be successful. Resources that are assumed to be fully
361 // buffered must have a known content length.
362 bool success
= status
== BufferedResourceLoader::kOk
&&
363 (!assume_fully_buffered() ||
364 loader_
->instance_size() != kPositionNotSpecified
);
367 total_bytes_
= loader_
->instance_size();
369 !assume_fully_buffered() &&
370 (total_bytes_
== kPositionNotSpecified
|| !loader_
->range_supported());
372 media_log_
->SetDoubleProperty("total_bytes",
373 static_cast<double>(total_bytes_
));
374 media_log_
->SetBooleanProperty("streaming", streaming_
);
379 // TODO(scherkus): we shouldn't have to lock to signal host(), see
380 // http://crbug.com/113712 for details.
381 base::AutoLock
auto_lock(lock_
);
382 if (stop_signal_received_
)
386 if (total_bytes_
!= kPositionNotSpecified
) {
387 host_
->SetTotalBytes(total_bytes_
);
388 if (assume_fully_buffered())
389 host_
->AddBufferedByteRange(0, total_bytes_
);
392 media_log_
->SetBooleanProperty("single_origin", loader_
->HasSingleOrigin());
393 media_log_
->SetBooleanProperty("passed_cors_access_check",
394 loader_
->DidPassCORSAccessCheck());
395 media_log_
->SetBooleanProperty("range_header_supported",
396 loader_
->range_supported());
399 base::ResetAndReturn(&init_cb_
).Run(success
);
402 void BufferedDataSource::PartialReadStartCallback(
403 BufferedResourceLoader::Status status
) {
404 DCHECK(render_task_runner_
->BelongsToCurrentThread());
405 DCHECK(loader_
.get());
407 if (status
== BufferedResourceLoader::kOk
) {
408 // Once the request has started successfully, we can proceed with
414 // Stop the resource loader since we have received an error.
417 // TODO(scherkus): we shouldn't have to lock to signal host(), see
418 // http://crbug.com/113712 for details.
419 base::AutoLock
auto_lock(lock_
);
420 if (stop_signal_received_
)
422 ReadOperation::Run(read_op_
.Pass(), kReadError
);
425 void BufferedDataSource::ReadCallback(
426 BufferedResourceLoader::Status status
,
428 DCHECK(render_task_runner_
->BelongsToCurrentThread());
430 // TODO(scherkus): we shouldn't have to lock to signal host(), see
431 // http://crbug.com/113712 for details.
432 base::AutoLock
auto_lock(lock_
);
433 if (stop_signal_received_
)
436 if (status
!= BufferedResourceLoader::kOk
) {
437 // Stop the resource load if it failed.
440 if (read_op_
->retries() < kLoaderRetries
) {
441 // Allow some resiliency against sporadic network failures or intentional
442 // cancellations due to a system suspend / resume. Here we treat failed
443 // reads as a cache miss so long as we haven't exceeded max retries.
444 if (status
== BufferedResourceLoader::kFailed
) {
445 render_task_runner_
->PostDelayedTask(
446 FROM_HERE
, base::Bind(&BufferedDataSource::ReadCallback
,
447 weak_factory_
.GetWeakPtr(),
448 BufferedResourceLoader::kCacheMiss
, 0),
449 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs
));
453 read_op_
->IncrementRetries();
455 // Recreate a loader starting from where we last left off until the
456 // end of the resource.
457 loader_
.reset(CreateResourceLoader(
458 read_op_
->position(), kPositionNotSpecified
));
460 base::WeakPtr
<BufferedDataSource
> weak_this
= weak_factory_
.GetWeakPtr();
462 base::Bind(&BufferedDataSource::PartialReadStartCallback
, weak_this
),
463 base::Bind(&BufferedDataSource::LoadingStateChangedCallback
,
465 base::Bind(&BufferedDataSource::ProgressCallback
, weak_this
),
470 ReadOperation::Run(read_op_
.Pass(), kReadError
);
474 if (bytes_read
> 0) {
475 DCHECK(!intermediate_read_buffer_
.empty());
476 memcpy(read_op_
->data(), &intermediate_read_buffer_
[0], bytes_read
);
477 } else if (bytes_read
== 0 && total_bytes_
== kPositionNotSpecified
) {
478 // We've reached the end of the file and we didn't know the total size
479 // before. Update the total size so Read()s past the end of the file will
480 // fail like they would if we had known the file size at the beginning.
481 total_bytes_
= loader_
->instance_size();
483 if (total_bytes_
!= kPositionNotSpecified
) {
484 host_
->SetTotalBytes(total_bytes_
);
485 host_
->AddBufferedByteRange(loader_
->first_byte_position(),
489 ReadOperation::Run(read_op_
.Pass(), bytes_read
);
492 void BufferedDataSource::LoadingStateChangedCallback(
493 BufferedResourceLoader::LoadingState state
) {
494 DCHECK(render_task_runner_
->BelongsToCurrentThread());
496 if (assume_fully_buffered())
499 bool is_downloading_data
;
501 case BufferedResourceLoader::kLoading
:
502 is_downloading_data
= true;
504 case BufferedResourceLoader::kLoadingDeferred
:
505 case BufferedResourceLoader::kLoadingFinished
:
506 is_downloading_data
= false;
509 // TODO(scherkus): we don't signal network activity changes when loads
510 // fail to preserve existing behaviour when deferring is toggled, however
511 // we should consider changing DownloadingCB to also propagate loading
512 // state. For example there isn't any signal today to notify the client that
513 // loading has failed (we only get errors on subsequent reads).
514 case BufferedResourceLoader::kLoadingFailed
:
518 downloading_cb_
.Run(is_downloading_data
);
521 void BufferedDataSource::ProgressCallback(int64 position
) {
522 DCHECK(render_task_runner_
->BelongsToCurrentThread());
524 if (assume_fully_buffered())
527 // TODO(scherkus): we shouldn't have to lock to signal host(), see
528 // http://crbug.com/113712 for details.
529 base::AutoLock
auto_lock(lock_
);
530 if (stop_signal_received_
)
533 host_
->AddBufferedByteRange(loader_
->first_byte_position(), position
);
536 void BufferedDataSource::UpdateDeferStrategy(bool paused
) {
537 // No need to aggressively buffer when we are assuming the resource is fully
539 if (assume_fully_buffered()) {
540 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer
);
544 // If the playback has started (at which point the preload value is ignored)
545 // and we're paused, then try to load as much as possible (the loader will
546 // fall back to kCapacityDefer if it knows the current response won't be
547 // useful from the cache in the future).
548 if (media_has_played_
&& paused
&& loader_
->range_supported()) {
549 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer
);
553 // If media is currently playing or the page indicated preload=auto or the
554 // the server does not support the byte range request or we do not want to go
555 // too far ahead of the read head, use threshold strategy to enable/disable
556 // deferring when the buffer is full/depleted.
557 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer
);