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
3 // found in the LICENSE file.
5 #include "webkit/media/buffered_data_source.h"
8 #include "media/base/media_log.h"
9 #include "net/base/net_errors.h"
11 using WebKit::WebFrame
;
13 namespace webkit_media
{
15 // BufferedDataSource has an intermediate buffer, this value governs the initial
16 // size of that buffer. It is set to 32KB because this is a typical read size
18 static const int kInitialReadBufferSize
= 32768;
20 // Number of cache misses we allow for a single Read() before signalling an
22 static const int kNumCacheMissRetries
= 3;
24 BufferedDataSource::BufferedDataSource(
25 MessageLoop
* render_loop
,
27 media::MediaLog
* media_log
)
28 : total_bytes_(kPositionNotSpecified
),
33 is_downloading_data_(false),
37 intermediate_read_buffer_(new uint8
[kInitialReadBufferSize
]),
38 intermediate_read_buffer_size_(kInitialReadBufferSize
),
39 render_loop_(render_loop
),
40 stop_signal_received_(false),
41 stopped_on_render_loop_(false),
42 media_is_paused_(true),
43 media_has_played_(false),
44 preload_(media::AUTO
),
45 using_range_request_(true),
46 cache_miss_retries_left_(kNumCacheMissRetries
),
49 media_log_(media_log
) {
52 BufferedDataSource::~BufferedDataSource() {}
54 // A factory method to create BufferedResourceLoader using the read parameters.
55 // This method can be overrided to inject mock BufferedResourceLoader object
56 // for testing purpose.
57 BufferedResourceLoader
* BufferedDataSource::CreateResourceLoader(
58 int64 first_byte_position
, int64 last_byte_position
) {
59 DCHECK(MessageLoop::current() == render_loop_
);
61 return new BufferedResourceLoader(url_
,
64 ChooseDeferStrategy(),
70 void BufferedDataSource::set_host(media::DataSourceHost
* host
) {
71 DataSource::set_host(host
);
74 base::AutoLock
auto_lock(lock_
);
75 UpdateHostState_Locked();
79 void BufferedDataSource::Initialize(
81 const media::PipelineStatusCB
& initialize_cb
) {
82 DCHECK(MessageLoop::current() == render_loop_
);
83 DCHECK(!initialize_cb
.is_null());
84 DCHECK(!loader_
.get());
87 // This data source doesn't support data:// protocol so reject it.
88 if (url_
.SchemeIs(kDataScheme
)) {
89 initialize_cb
.Run(media::DATASOURCE_ERROR_URL_NOT_SUPPORTED
);
93 initialize_cb_
= initialize_cb
;
95 if (url_
.SchemeIs(kHttpScheme
) || url_
.SchemeIs(kHttpsScheme
)) {
96 // Do an unbounded range request starting at the beginning. If the server
97 // responds with 200 instead of 206 we'll fall back into a streaming mode.
98 loader_
.reset(CreateResourceLoader(0, kPositionNotSpecified
));
100 base::Bind(&BufferedDataSource::HttpInitialStartCallback
, this),
101 base::Bind(&BufferedDataSource::NetworkEventCallback
, this),
106 // For all other protocols, assume they support range request. We fetch
107 // the full range of the resource to obtain the instance size because
108 // we won't be served HTTP headers.
109 loader_
.reset(CreateResourceLoader(kPositionNotSpecified
,
110 kPositionNotSpecified
));
112 base::Bind(&BufferedDataSource::NonHttpInitialStartCallback
, this),
113 base::Bind(&BufferedDataSource::NetworkEventCallback
, this),
117 /////////////////////////////////////////////////////////////////////////////
118 // media::Filter implementation.
119 void BufferedDataSource::Stop(const base::Closure
& closure
) {
121 base::AutoLock
auto_lock(lock_
);
122 stop_signal_received_
= true;
124 if (!closure
.is_null())
127 render_loop_
->PostTask(FROM_HERE
,
128 base::Bind(&BufferedDataSource::CleanupTask
, this));
131 void BufferedDataSource::SetPlaybackRate(float playback_rate
) {
132 render_loop_
->PostTask(FROM_HERE
, base::Bind(
133 &BufferedDataSource::SetPlaybackRateTask
, this, playback_rate
));
136 void BufferedDataSource::SetPreload(media::Preload preload
) {
137 render_loop_
->PostTask(FROM_HERE
, base::Bind(
138 &BufferedDataSource::SetPreloadTask
, this, preload
));
141 void BufferedDataSource::SetBitrate(int bitrate
) {
142 render_loop_
->PostTask(FROM_HERE
, base::Bind(
143 &BufferedDataSource::SetBitrateTask
, this, bitrate
));
146 /////////////////////////////////////////////////////////////////////////////
147 // media::DataSource implementation.
148 void BufferedDataSource::Read(
149 int64 position
, size_t size
, uint8
* data
,
150 const media::DataSource::ReadCallback
& read_cb
) {
151 DVLOG(1) << "Read: " << position
<< " offset, " << size
<< " bytes";
152 DCHECK(!read_cb
.is_null());
155 base::AutoLock
auto_lock(lock_
);
156 DCHECK(read_cb_
.is_null());
158 if (stop_signal_received_
|| stopped_on_render_loop_
) {
159 read_cb
.Run(kReadError
);
166 render_loop_
->PostTask(FROM_HERE
, base::Bind(
167 &BufferedDataSource::ReadTask
, this,
168 position
, static_cast<int>(size
), data
));
171 bool BufferedDataSource::GetSize(int64
* size_out
) {
172 if (total_bytes_
!= kPositionNotSpecified
) {
173 *size_out
= total_bytes_
;
180 bool BufferedDataSource::IsStreaming() {
184 bool BufferedDataSource::HasSingleOrigin() {
185 DCHECK(MessageLoop::current() == render_loop_
);
186 return loader_
.get() ? loader_
->HasSingleOrigin() : true;
189 void BufferedDataSource::Abort() {
190 DCHECK(MessageLoop::current() == render_loop_
);
196 /////////////////////////////////////////////////////////////////////////////
197 // Render thread tasks.
198 void BufferedDataSource::ReadTask(
202 DCHECK(MessageLoop::current() == render_loop_
);
204 base::AutoLock
auto_lock(lock_
);
205 if (stopped_on_render_loop_
)
208 DCHECK(!read_cb_
.is_null());
211 // Saves the read parameters.
212 read_position_
= position
;
213 read_size_
= read_size
;
214 read_buffer_
= buffer
;
215 cache_miss_retries_left_
= kNumCacheMissRetries
;
217 // Call to read internal to perform the actual read.
221 void BufferedDataSource::CleanupTask() {
222 DCHECK(MessageLoop::current() == render_loop_
);
225 base::AutoLock
auto_lock(lock_
);
226 initialize_cb_
.Reset();
227 if (stopped_on_render_loop_
)
230 // Signal that stop task has finished execution.
231 // NOTE: it's vital that this be set under lock, as that's how Read() tests
232 // before registering a new |read_cb_| (which is cleared below).
233 stopped_on_render_loop_
= true;
235 if (!read_cb_
.is_null())
236 DoneRead_Locked(net::ERR_FAILED
);
239 // We just need to stop the loader, so it stops activity.
243 // Reset the parameters of the current read request.
249 void BufferedDataSource::RestartLoadingTask() {
250 DCHECK(MessageLoop::current() == render_loop_
);
251 if (stopped_on_render_loop_
)
255 // If there's no outstanding read then return early.
256 base::AutoLock
auto_lock(lock_
);
257 if (read_cb_
.is_null())
261 loader_
.reset(CreateResourceLoader(read_position_
, kPositionNotSpecified
));
263 base::Bind(&BufferedDataSource::PartialReadStartCallback
, this),
264 base::Bind(&BufferedDataSource::NetworkEventCallback
, this),
268 void BufferedDataSource::SetPlaybackRateTask(float playback_rate
) {
269 DCHECK(MessageLoop::current() == render_loop_
);
270 DCHECK(loader_
.get());
272 playback_rate_
= playback_rate
;
273 loader_
->SetPlaybackRate(playback_rate
);
275 bool previously_paused
= media_is_paused_
;
276 media_is_paused_
= (playback_rate
== 0.0);
278 if (!media_has_played_
&& previously_paused
&& !media_is_paused_
)
279 media_has_played_
= true;
281 BufferedResourceLoader::DeferStrategy strategy
= ChooseDeferStrategy();
282 loader_
->UpdateDeferStrategy(strategy
);
285 void BufferedDataSource::SetPreloadTask(media::Preload preload
) {
286 DCHECK(MessageLoop::current() == render_loop_
);
290 void BufferedDataSource::SetBitrateTask(int bitrate
) {
291 DCHECK(MessageLoop::current() == render_loop_
);
292 DCHECK(loader_
.get());
295 loader_
->SetBitrate(bitrate
);
298 BufferedResourceLoader::DeferStrategy
299 BufferedDataSource::ChooseDeferStrategy() {
300 DCHECK(MessageLoop::current() == render_loop_
);
301 // If the page indicated preload=metadata, then load exactly what is needed
302 // needed for starting playback.
303 if (!media_has_played_
&& preload_
== media::METADATA
)
304 return BufferedResourceLoader::kReadThenDefer
;
306 // If the playback has started (at which point the preload value is ignored)
307 // and we're paused, then try to load as much as possible.
308 if (media_has_played_
&& media_is_paused_
)
309 return BufferedResourceLoader::kNeverDefer
;
311 // If media is currently playing or the page indicated preload=auto,
312 // use threshold strategy to enable/disable deferring when the buffer
314 return BufferedResourceLoader::kThresholdDefer
;
317 // This method is the place where actual read happens, |loader_| must be valid
318 // prior to make this method call.
319 void BufferedDataSource::ReadInternal() {
320 DCHECK(MessageLoop::current() == render_loop_
);
321 DCHECK(loader_
.get());
323 // First we prepare the intermediate read buffer for BufferedResourceLoader
325 if (read_size_
> intermediate_read_buffer_size_
) {
326 intermediate_read_buffer_
.reset(new uint8
[read_size_
]);
329 // Perform the actual read with BufferedResourceLoader.
330 loader_
->Read(read_position_
, read_size_
, intermediate_read_buffer_
.get(),
331 base::Bind(&BufferedDataSource::ReadCallback
, this));
334 // Method to report the results of the current read request. Also reset all
335 // the read parameters.
336 void BufferedDataSource::DoneRead_Locked(int error
) {
337 DVLOG(1) << "DoneRead: " << error
<< " bytes";
339 DCHECK(MessageLoop::current() == render_loop_
);
340 DCHECK(!read_cb_
.is_null());
341 lock_
.AssertAcquired();
344 read_cb_
.Run(static_cast<size_t>(error
));
346 read_cb_
.Run(kReadError
);
355 void BufferedDataSource::DoneInitialization_Locked(
356 media::PipelineStatus status
) {
357 DCHECK(MessageLoop::current() == render_loop_
);
358 DCHECK(!initialize_cb_
.is_null());
359 lock_
.AssertAcquired();
361 initialize_cb_
.Run(status
);
362 initialize_cb_
.Reset();
365 /////////////////////////////////////////////////////////////////////////////
366 // BufferedResourceLoader callback methods.
367 void BufferedDataSource::HttpInitialStartCallback(int error
) {
368 DCHECK(MessageLoop::current() == render_loop_
);
369 DCHECK(loader_
.get());
371 int64 instance_size
= loader_
->instance_size();
372 bool success
= error
== net::OK
;
374 bool initialize_cb_is_null
= false;
376 base::AutoLock
auto_lock(lock_
);
377 initialize_cb_is_null
= initialize_cb_
.is_null();
379 if (initialize_cb_is_null
) {
385 // TODO(hclam): Needs more thinking about supporting servers without range
386 // request or their partial response is not complete.
387 total_bytes_
= instance_size
;
388 streaming_
= (instance_size
== kPositionNotSpecified
) ||
389 !loader_
->range_supported();
391 // TODO(hclam): In case of failure, we can retry several times.
395 if (error
== net::ERR_INVALID_RESPONSE
&& using_range_request_
) {
396 // Assuming that the Range header was causing the problem. Retry without
398 using_range_request_
= false;
399 loader_
.reset(CreateResourceLoader(kPositionNotSpecified
,
400 kPositionNotSpecified
));
402 base::Bind(&BufferedDataSource::HttpInitialStartCallback
, this),
403 base::Bind(&BufferedDataSource::NetworkEventCallback
, this),
408 // Reference to prevent destruction while inside the |initialize_cb_|
409 // call. This is a temporary fix to prevent crashes caused by holding the
410 // lock and running the destructor.
411 // TODO: Review locking in this class and figure out a way to run the callback
413 scoped_refptr
<BufferedDataSource
> destruction_guard(this);
415 // We need to prevent calling to filter host and running the callback if
416 // we have received the stop signal. We need to lock down the whole callback
417 // method to prevent bad things from happening. The reason behind this is
418 // that we cannot guarantee tasks on render thread have completely stopped
419 // when we receive the Stop() method call. The only way to solve this is to
420 // let tasks on render thread to run but make sure they don't call outside
421 // this object when Stop() method is ever called. Locking this method is
422 // safe because |lock_| is only acquired in tasks on render thread.
423 base::AutoLock
auto_lock(lock_
);
424 if (stop_signal_received_
)
428 DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK
);
432 UpdateHostState_Locked();
433 DoneInitialization_Locked(media::PIPELINE_OK
);
437 void BufferedDataSource::NonHttpInitialStartCallback(int error
) {
438 DCHECK(MessageLoop::current() == render_loop_
);
439 DCHECK(loader_
.get());
441 bool initialize_cb_is_null
= false;
443 base::AutoLock
auto_lock(lock_
);
444 initialize_cb_is_null
= initialize_cb_
.is_null();
446 if (initialize_cb_is_null
) {
451 int64 instance_size
= loader_
->instance_size();
452 bool success
= error
== net::OK
&& instance_size
!= kPositionNotSpecified
;
455 total_bytes_
= instance_size
;
456 buffered_bytes_
= total_bytes_
;
461 // Reference to prevent destruction while inside the |initialize_cb_|
462 // call. This is a temporary fix to prevent crashes caused by holding the
463 // lock and running the destructor.
464 // TODO: Review locking in this class and figure out a way to run the callback
466 scoped_refptr
<BufferedDataSource
> destruction_guard(this);
468 // We need to prevent calling to filter host and running the callback if
469 // we have received the stop signal. We need to lock down the whole callback
470 // method to prevent bad things from happening. The reason behind this is
471 // that we cannot guarantee tasks on render thread have completely stopped
472 // when we receive the Stop() method call. The only way to solve this is to
473 // let tasks on render thread to run but make sure they don't call outside
474 // this object when Stop() method is ever called. Locking this method is
475 // safe because |lock_| is only acquired in tasks on render thread.
476 base::AutoLock
auto_lock(lock_
);
477 if (stop_signal_received_
|| initialize_cb_
.is_null())
481 DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK
);
485 UpdateHostState_Locked();
486 DoneInitialization_Locked(media::PIPELINE_OK
);
490 void BufferedDataSource::PartialReadStartCallback(int error
) {
491 DCHECK(MessageLoop::current() == render_loop_
);
492 DCHECK(loader_
.get());
494 if (error
== net::OK
) {
495 // Once the request has started successfully, we can proceed with
501 // Stop the resource loader since we have received an error.
504 // We need to prevent calling to filter host and running the callback if
505 // we have received the stop signal. We need to lock down the whole callback
506 // method to prevent bad things from happening. The reason behind this is
507 // that we cannot guarantee tasks on render thread have completely stopped
508 // when we receive the Stop() method call. So only way to solve this is to
509 // let tasks on render thread to run but make sure they don't call outside
510 // this object when Stop() method is ever called. Locking this method is
511 // safe because |lock_| is only acquired in tasks on render thread.
512 base::AutoLock
auto_lock(lock_
);
513 if (stop_signal_received_
)
515 DoneRead_Locked(net::ERR_INVALID_RESPONSE
);
518 void BufferedDataSource::ReadCallback(int error
) {
519 DCHECK(MessageLoop::current() == render_loop_
);
522 DCHECK(loader_
.get());
524 // Stop the resource load if it failed.
527 if (error
== net::ERR_CACHE_MISS
&& cache_miss_retries_left_
> 0) {
528 cache_miss_retries_left_
--;
529 render_loop_
->PostTask(FROM_HERE
,
530 base::Bind(&BufferedDataSource::RestartLoadingTask
, this));
535 // We need to prevent calling to filter host and running the callback if
536 // we have received the stop signal. We need to lock down the whole callback
537 // method to prevent bad things from happening. The reason behind this is
538 // that we cannot guarantee tasks on render thread have completely stopped
539 // when we receive the Stop() method call. So only way to solve this is to
540 // let tasks on render thread to run but make sure they don't call outside
541 // this object when Stop() method is ever called. Locking this method is safe
542 // because |lock_| is only acquired in tasks on render thread.
543 base::AutoLock
auto_lock(lock_
);
544 if (stop_signal_received_
)
548 // If a position error code is received, read was successful. So copy
549 // from intermediate read buffer to the target read buffer.
550 memcpy(read_buffer_
, intermediate_read_buffer_
.get(), error
);
551 } else if (error
== 0 && total_bytes_
== kPositionNotSpecified
) {
552 // We've reached the end of the file and we didn't know the total size
553 // before. Update the total size so Read()s past the end of the file will
554 // fail like they would if we had known the file size at the beginning.
555 total_bytes_
= loader_
->instance_size();
557 if (host() && total_bytes_
!= kPositionNotSpecified
) {
558 host()->SetTotalBytes(total_bytes_
);
559 host()->SetBufferedBytes(total_bytes_
);
562 DoneRead_Locked(error
);
565 void BufferedDataSource::NetworkEventCallback() {
566 DCHECK(MessageLoop::current() == render_loop_
);
567 DCHECK(loader_
.get());
569 // In case of non-HTTP request we don't need to report network events,
570 // so return immediately.
571 if (!url_
.SchemeIs(kHttpScheme
) && !url_
.SchemeIs(kHttpsScheme
))
574 bool is_downloading_data
= loader_
->is_downloading_data();
575 int64 buffered_position
= loader_
->GetBufferedPosition();
577 // If we get an unspecified value, return immediately.
578 if (buffered_position
== kPositionNotSpecified
)
581 // We need to prevent calling to filter host and running the callback if
582 // we have received the stop signal. We need to lock down the whole callback
583 // method to prevent bad things from happening. The reason behind this is
584 // that we cannot guarantee tasks on render thread have completely stopped
585 // when we receive the Stop() method call. So only way to solve this is to
586 // let tasks on render thread to run but make sure they don't call outside
587 // this object when Stop() method is ever called. Locking this method is safe
588 // because |lock_| is only acquired in tasks on render thread.
589 base::AutoLock
auto_lock(lock_
);
590 if (stop_signal_received_
)
593 if (is_downloading_data
!= is_downloading_data_
) {
594 is_downloading_data_
= is_downloading_data
;
596 host()->SetNetworkActivity(is_downloading_data
);
599 buffered_bytes_
= buffered_position
+ 1;
601 host()->SetBufferedBytes(buffered_bytes_
);
604 void BufferedDataSource::UpdateHostState_Locked() {
605 // Called from various threads, under lock.
606 lock_
.AssertAcquired();
611 if (total_bytes_
!= kPositionNotSpecified
)
612 host()->SetTotalBytes(total_bytes_
);
613 host()->SetBufferedBytes(buffered_bytes_
);
616 } // namespace webkit_media