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 we allow for a single Read() before signaling an
25 const int kNumCacheMissRetries
= 3;
31 class BufferedDataSource::ReadOperation
{
33 ReadOperation(int64 position
, int size
, uint8
* data
,
34 const DataSource::ReadCB
& callback
);
37 // Runs |callback_| with the given |result|, deleting the operation
39 static void Run(scoped_ptr
<ReadOperation
> read_op
, int result
);
41 // State for the number of times this read operation has been retried.
42 int retries() { return retries_
; }
43 void IncrementRetries() { ++retries_
; }
45 int64
position() { return position_
; }
46 int size() { return size_
; }
47 uint8
* data() { return data_
; }
52 const int64 position_
;
55 DataSource::ReadCB callback_
;
57 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation
);
60 BufferedDataSource::ReadOperation::ReadOperation(
61 int64 position
, int size
, uint8
* data
,
62 const DataSource::ReadCB
& callback
)
68 DCHECK(!callback_
.is_null());
71 BufferedDataSource::ReadOperation::~ReadOperation() {
72 DCHECK(callback_
.is_null());
76 void BufferedDataSource::ReadOperation::Run(
77 scoped_ptr
<ReadOperation
> read_op
, int result
) {
78 base::ResetAndReturn(&read_op
->callback_
).Run(result
);
81 BufferedDataSource::BufferedDataSource(
83 BufferedResourceLoader::CORSMode cors_mode
,
84 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
87 BufferedDataSourceHost
* host
,
88 const DownloadingCB
& downloading_cb
)
90 cors_mode_(cors_mode
),
91 total_bytes_(kPositionNotSpecified
),
94 intermediate_read_buffer_(kInitialReadBufferSize
),
95 render_task_runner_(task_runner
),
96 stop_signal_received_(false),
97 media_has_played_(false),
101 media_log_(media_log
),
103 downloading_cb_(downloading_cb
),
104 weak_factory_(this) {
106 DCHECK(!downloading_cb_
.is_null());
109 BufferedDataSource::~BufferedDataSource() {}
111 // A factory method to create BufferedResourceLoader using the read parameters.
112 // This method can be overridden to inject mock BufferedResourceLoader object
113 // for testing purpose.
114 BufferedResourceLoader
* BufferedDataSource::CreateResourceLoader(
115 int64 first_byte_position
, int64 last_byte_position
) {
116 DCHECK(render_task_runner_
->BelongsToCurrentThread());
118 BufferedResourceLoader::DeferStrategy strategy
= preload_
== METADATA
?
119 BufferedResourceLoader::kReadThenDefer
:
120 BufferedResourceLoader::kCapacityDefer
;
122 return new BufferedResourceLoader(url_
,
132 void BufferedDataSource::Initialize(const InitializeCB
& init_cb
) {
133 DCHECK(render_task_runner_
->BelongsToCurrentThread());
134 DCHECK(!init_cb
.is_null());
135 DCHECK(!loader_
.get());
139 if (url_
.SchemeIsHTTPOrHTTPS()) {
140 // Do an unbounded range request starting at the beginning. If the server
141 // responds with 200 instead of 206 we'll fall back into a streaming mode.
142 loader_
.reset(CreateResourceLoader(0, kPositionNotSpecified
));
144 // For all other protocols, assume they support range request. We fetch
145 // the full range of the resource to obtain the instance size because
146 // we won't be served HTTP headers.
147 loader_
.reset(CreateResourceLoader(kPositionNotSpecified
,
148 kPositionNotSpecified
));
151 base::WeakPtr
<BufferedDataSource
> weak_this
= weak_factory_
.GetWeakPtr();
153 base::Bind(&BufferedDataSource::StartCallback
, weak_this
),
154 base::Bind(&BufferedDataSource::LoadingStateChangedCallback
, weak_this
),
155 base::Bind(&BufferedDataSource::ProgressCallback
, weak_this
),
159 void BufferedDataSource::SetPreload(Preload preload
) {
160 DCHECK(render_task_runner_
->BelongsToCurrentThread());
164 bool BufferedDataSource::HasSingleOrigin() {
165 DCHECK(render_task_runner_
->BelongsToCurrentThread());
166 DCHECK(init_cb_
.is_null() && loader_
.get())
167 << "Initialize() must complete before calling HasSingleOrigin()";
168 return loader_
->HasSingleOrigin();
171 bool BufferedDataSource::DidPassCORSAccessCheck() const {
172 return loader_
.get() && loader_
->DidPassCORSAccessCheck();
175 void BufferedDataSource::Abort() {
176 DCHECK(render_task_runner_
->BelongsToCurrentThread());
178 base::AutoLock
auto_lock(lock_
);
179 StopInternal_Locked();
185 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate
) {
186 DCHECK(render_task_runner_
->BelongsToCurrentThread());
187 DCHECK(loader_
.get());
189 if (playback_rate
< 0.0f
)
192 playback_rate_
= playback_rate
;
193 loader_
->SetPlaybackRate(playback_rate
);
196 void BufferedDataSource::MediaIsPlaying() {
197 DCHECK(render_task_runner_
->BelongsToCurrentThread());
198 media_has_played_
= true;
199 UpdateDeferStrategy(false);
202 void BufferedDataSource::MediaIsPaused() {
203 DCHECK(render_task_runner_
->BelongsToCurrentThread());
204 UpdateDeferStrategy(true);
207 /////////////////////////////////////////////////////////////////////////////
208 // DataSource implementation.
209 void BufferedDataSource::Stop() {
211 base::AutoLock
auto_lock(lock_
);
212 StopInternal_Locked();
215 render_task_runner_
->PostTask(
217 base::Bind(&BufferedDataSource::StopLoader
, weak_factory_
.GetWeakPtr()));
220 void BufferedDataSource::SetBitrate(int bitrate
) {
221 render_task_runner_
->PostTask(FROM_HERE
,
222 base::Bind(&BufferedDataSource::SetBitrateTask
,
223 weak_factory_
.GetWeakPtr(),
227 void BufferedDataSource::Read(
228 int64 position
, int size
, uint8
* data
,
229 const DataSource::ReadCB
& read_cb
) {
230 DVLOG(1) << "Read: " << position
<< " offset, " << size
<< " bytes";
231 DCHECK(!read_cb
.is_null());
234 base::AutoLock
auto_lock(lock_
);
237 if (stop_signal_received_
) {
238 read_cb
.Run(kReadError
);
242 read_op_
.reset(new ReadOperation(position
, size
, data
, read_cb
));
245 render_task_runner_
->PostTask(
247 base::Bind(&BufferedDataSource::ReadTask
, weak_factory_
.GetWeakPtr()));
250 bool BufferedDataSource::GetSize(int64
* size_out
) {
251 if (total_bytes_
!= kPositionNotSpecified
) {
252 *size_out
= total_bytes_
;
259 bool BufferedDataSource::IsStreaming() {
263 /////////////////////////////////////////////////////////////////////////////
264 // Render thread tasks.
265 void BufferedDataSource::ReadTask() {
266 DCHECK(render_task_runner_
->BelongsToCurrentThread());
270 void BufferedDataSource::StopInternal_Locked() {
271 lock_
.AssertAcquired();
272 if (stop_signal_received_
)
275 stop_signal_received_
= true;
277 // Initialize() isn't part of the DataSource interface so don't call it in
278 // response to Stop().
282 ReadOperation::Run(read_op_
.Pass(), kReadError
);
285 void BufferedDataSource::StopLoader() {
286 DCHECK(render_task_runner_
->BelongsToCurrentThread());
292 void BufferedDataSource::SetBitrateTask(int bitrate
) {
293 DCHECK(render_task_runner_
->BelongsToCurrentThread());
294 DCHECK(loader_
.get());
297 loader_
->SetBitrate(bitrate
);
300 // This method is the place where actual read happens, |loader_| must be valid
301 // prior to make this method call.
302 void BufferedDataSource::ReadInternal() {
303 DCHECK(render_task_runner_
->BelongsToCurrentThread());
307 base::AutoLock
auto_lock(lock_
);
308 if (stop_signal_received_
)
311 position
= read_op_
->position();
312 size
= read_op_
->size();
315 // First we prepare the intermediate read buffer for BufferedResourceLoader
317 if (static_cast<int>(intermediate_read_buffer_
.size()) < size
)
318 intermediate_read_buffer_
.resize(size
);
320 // Perform the actual read with BufferedResourceLoader.
321 DCHECK(!intermediate_read_buffer_
.empty());
322 loader_
->Read(position
,
324 &intermediate_read_buffer_
[0],
325 base::Bind(&BufferedDataSource::ReadCallback
,
326 weak_factory_
.GetWeakPtr()));
330 /////////////////////////////////////////////////////////////////////////////
331 // BufferedResourceLoader callback methods.
332 void BufferedDataSource::StartCallback(
333 BufferedResourceLoader::Status status
) {
334 DCHECK(render_task_runner_
->BelongsToCurrentThread());
335 DCHECK(loader_
.get());
337 bool init_cb_is_null
= false;
339 base::AutoLock
auto_lock(lock_
);
340 init_cb_is_null
= init_cb_
.is_null();
342 if (init_cb_is_null
) {
347 // All responses must be successful. Resources that are assumed to be fully
348 // buffered must have a known content length.
349 bool success
= status
== BufferedResourceLoader::kOk
&&
350 (!assume_fully_buffered() ||
351 loader_
->instance_size() != kPositionNotSpecified
);
354 total_bytes_
= loader_
->instance_size();
356 !assume_fully_buffered() &&
357 (total_bytes_
== kPositionNotSpecified
|| !loader_
->range_supported());
359 media_log_
->SetDoubleProperty("total_bytes",
360 static_cast<double>(total_bytes_
));
361 media_log_
->SetBooleanProperty("streaming", streaming_
);
366 // TODO(scherkus): we shouldn't have to lock to signal host(), see
367 // http://crbug.com/113712 for details.
368 base::AutoLock
auto_lock(lock_
);
369 if (stop_signal_received_
)
373 if (total_bytes_
!= kPositionNotSpecified
) {
374 host_
->SetTotalBytes(total_bytes_
);
375 if (assume_fully_buffered())
376 host_
->AddBufferedByteRange(0, total_bytes_
);
379 media_log_
->SetBooleanProperty("single_origin", loader_
->HasSingleOrigin());
380 media_log_
->SetBooleanProperty("passed_cors_access_check",
381 loader_
->DidPassCORSAccessCheck());
382 media_log_
->SetBooleanProperty("range_header_supported",
383 loader_
->range_supported());
386 base::ResetAndReturn(&init_cb_
).Run(success
);
389 void BufferedDataSource::PartialReadStartCallback(
390 BufferedResourceLoader::Status status
) {
391 DCHECK(render_task_runner_
->BelongsToCurrentThread());
392 DCHECK(loader_
.get());
394 if (status
== BufferedResourceLoader::kOk
) {
395 // Once the request has started successfully, we can proceed with
401 // Stop the resource loader since we have received an error.
404 // TODO(scherkus): we shouldn't have to lock to signal host(), see
405 // http://crbug.com/113712 for details.
406 base::AutoLock
auto_lock(lock_
);
407 if (stop_signal_received_
)
409 ReadOperation::Run(read_op_
.Pass(), kReadError
);
412 void BufferedDataSource::ReadCallback(
413 BufferedResourceLoader::Status status
,
415 DCHECK(render_task_runner_
->BelongsToCurrentThread());
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_
)
423 if (status
!= BufferedResourceLoader::kOk
) {
424 // Stop the resource load if it failed.
427 if (status
== BufferedResourceLoader::kCacheMiss
&&
428 read_op_
->retries() < kNumCacheMissRetries
) {
429 read_op_
->IncrementRetries();
431 // Recreate a loader starting from where we last left off until the
432 // end of the resource.
433 loader_
.reset(CreateResourceLoader(
434 read_op_
->position(), kPositionNotSpecified
));
436 base::WeakPtr
<BufferedDataSource
> weak_this
= weak_factory_
.GetWeakPtr();
438 base::Bind(&BufferedDataSource::PartialReadStartCallback
, weak_this
),
439 base::Bind(&BufferedDataSource::LoadingStateChangedCallback
,
441 base::Bind(&BufferedDataSource::ProgressCallback
, weak_this
),
446 ReadOperation::Run(read_op_
.Pass(), kReadError
);
450 if (bytes_read
> 0) {
451 DCHECK(!intermediate_read_buffer_
.empty());
452 memcpy(read_op_
->data(), &intermediate_read_buffer_
[0], bytes_read
);
453 } else if (bytes_read
== 0 && total_bytes_
== kPositionNotSpecified
) {
454 // We've reached the end of the file and we didn't know the total size
455 // before. Update the total size so Read()s past the end of the file will
456 // fail like they would if we had known the file size at the beginning.
457 total_bytes_
= loader_
->instance_size();
459 if (total_bytes_
!= kPositionNotSpecified
) {
460 host_
->SetTotalBytes(total_bytes_
);
461 host_
->AddBufferedByteRange(loader_
->first_byte_position(),
465 ReadOperation::Run(read_op_
.Pass(), bytes_read
);
468 void BufferedDataSource::LoadingStateChangedCallback(
469 BufferedResourceLoader::LoadingState state
) {
470 DCHECK(render_task_runner_
->BelongsToCurrentThread());
472 if (assume_fully_buffered())
475 bool is_downloading_data
;
477 case BufferedResourceLoader::kLoading
:
478 is_downloading_data
= true;
480 case BufferedResourceLoader::kLoadingDeferred
:
481 case BufferedResourceLoader::kLoadingFinished
:
482 is_downloading_data
= false;
485 // TODO(scherkus): we don't signal network activity changes when loads
486 // fail to preserve existing behaviour when deferring is toggled, however
487 // we should consider changing DownloadingCB to also propagate loading
488 // state. For example there isn't any signal today to notify the client that
489 // loading has failed (we only get errors on subsequent reads).
490 case BufferedResourceLoader::kLoadingFailed
:
494 downloading_cb_
.Run(is_downloading_data
);
497 void BufferedDataSource::ProgressCallback(int64 position
) {
498 DCHECK(render_task_runner_
->BelongsToCurrentThread());
500 if (assume_fully_buffered())
503 // TODO(scherkus): we shouldn't have to lock to signal host(), see
504 // http://crbug.com/113712 for details.
505 base::AutoLock
auto_lock(lock_
);
506 if (stop_signal_received_
)
509 host_
->AddBufferedByteRange(loader_
->first_byte_position(), position
);
512 void BufferedDataSource::UpdateDeferStrategy(bool paused
) {
513 // No need to aggressively buffer when we are assuming the resource is fully
515 if (assume_fully_buffered()) {
516 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer
);
520 // If the playback has started (at which point the preload value is ignored)
521 // and we're paused, then try to load as much as possible (the loader will
522 // fall back to kCapacityDefer if it knows the current response won't be
523 // useful from the cache in the future).
524 if (media_has_played_
&& paused
&& loader_
->range_supported()) {
525 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer
);
529 // If media is currently playing or the page indicated preload=auto or the
530 // the server does not support the byte range request or we do not want to go
531 // too far ahead of the read head, use threshold strategy to enable/disable
532 // deferring when the buffer is full/depleted.
533 loader_
->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer
);