OLD | NEW |
| (Empty) |
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/single_thread_task_runner.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. | |
42 int retries() { return retries_; } | |
43 void IncrementRetries() { ++retries_; } | |
44 | |
45 int64 position() { return position_; } | |
46 int size() { return size_; } | |
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 | |
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 | |
71 BufferedDataSource::ReadOperation::~ReadOperation() { | |
72 DCHECK(callback_.is_null()); | |
73 } | |
74 | |
75 // static | |
76 void BufferedDataSource::ReadOperation::Run( | |
77 scoped_ptr<ReadOperation> read_op, int result) { | |
78 base::ResetAndReturn(&read_op->callback_).Run(result); | |
79 } | |
80 | |
81 BufferedDataSource::BufferedDataSource( | |
82 const GURL& url, | |
83 BufferedResourceLoader::CORSMode cors_mode, | |
84 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
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_task_runner_(task_runner), | |
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 | |
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. | |
115 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( | |
116 int64 first_byte_position, int64 last_byte_position) { | |
117 DCHECK(render_task_runner_->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 | |
133 void BufferedDataSource::Initialize(const InitializeCB& init_cb) { | |
134 DCHECK(render_task_runner_->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 | |
160 void BufferedDataSource::SetPreload(Preload preload) { | |
161 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
162 preload_ = preload; | |
163 } | |
164 | |
165 bool BufferedDataSource::HasSingleOrigin() { | |
166 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
167 DCHECK(init_cb_.is_null() && loader_.get()) | |
168 << "Initialize() must complete before calling HasSingleOrigin()"; | |
169 return loader_->HasSingleOrigin(); | |
170 } | |
171 | |
172 bool BufferedDataSource::DidPassCORSAccessCheck() const { | |
173 return loader_.get() && loader_->DidPassCORSAccessCheck(); | |
174 } | |
175 | |
176 void BufferedDataSource::Abort() { | |
177 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
178 { | |
179 base::AutoLock auto_lock(lock_); | |
180 StopInternal_Locked(); | |
181 } | |
182 StopLoader(); | |
183 frame_ = NULL; | |
184 } | |
185 | |
186 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) { | |
187 DCHECK(render_task_runner_->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 | |
197 void BufferedDataSource::MediaIsPlaying() { | |
198 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
199 media_has_played_ = true; | |
200 UpdateDeferStrategy(false); | |
201 } | |
202 | |
203 void BufferedDataSource::MediaIsPaused() { | |
204 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
205 UpdateDeferStrategy(true); | |
206 } | |
207 | |
208 ///////////////////////////////////////////////////////////////////////////// | |
209 // media::DataSource implementation. | |
210 void BufferedDataSource::Stop() { | |
211 { | |
212 base::AutoLock auto_lock(lock_); | |
213 StopInternal_Locked(); | |
214 } | |
215 | |
216 render_task_runner_->PostTask( | |
217 FROM_HERE, | |
218 base::Bind(&BufferedDataSource::StopLoader, weak_factory_.GetWeakPtr())); | |
219 } | |
220 | |
221 void BufferedDataSource::SetBitrate(int bitrate) { | |
222 render_task_runner_->PostTask(FROM_HERE, | |
223 base::Bind(&BufferedDataSource::SetBitrateTask, | |
224 weak_factory_.GetWeakPtr(), | |
225 bitrate)); | |
226 } | |
227 | |
228 void BufferedDataSource::Read( | |
229 int64 position, int size, uint8* data, | |
230 const media::DataSource::ReadCB& read_cb) { | |
231 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes"; | |
232 DCHECK(!read_cb.is_null()); | |
233 | |
234 { | |
235 base::AutoLock auto_lock(lock_); | |
236 DCHECK(!read_op_); | |
237 | |
238 if (stop_signal_received_) { | |
239 read_cb.Run(kReadError); | |
240 return; | |
241 } | |
242 | |
243 read_op_.reset(new ReadOperation(position, size, data, read_cb)); | |
244 } | |
245 | |
246 render_task_runner_->PostTask( | |
247 FROM_HERE, | |
248 base::Bind(&BufferedDataSource::ReadTask, weak_factory_.GetWeakPtr())); | |
249 } | |
250 | |
251 bool BufferedDataSource::GetSize(int64* size_out) { | |
252 if (total_bytes_ != kPositionNotSpecified) { | |
253 *size_out = total_bytes_; | |
254 return true; | |
255 } | |
256 *size_out = 0; | |
257 return false; | |
258 } | |
259 | |
260 bool BufferedDataSource::IsStreaming() { | |
261 return streaming_; | |
262 } | |
263 | |
264 ///////////////////////////////////////////////////////////////////////////// | |
265 // Render thread tasks. | |
266 void BufferedDataSource::ReadTask() { | |
267 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
268 ReadInternal(); | |
269 } | |
270 | |
271 void BufferedDataSource::StopInternal_Locked() { | |
272 lock_.AssertAcquired(); | |
273 if (stop_signal_received_) | |
274 return; | |
275 | |
276 stop_signal_received_ = true; | |
277 | |
278 // Initialize() isn't part of the DataSource interface so don't call it in | |
279 // response to Stop(). | |
280 init_cb_.Reset(); | |
281 | |
282 if (read_op_) | |
283 ReadOperation::Run(read_op_.Pass(), kReadError); | |
284 } | |
285 | |
286 void BufferedDataSource::StopLoader() { | |
287 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
288 | |
289 if (loader_) | |
290 loader_->Stop(); | |
291 } | |
292 | |
293 void BufferedDataSource::SetBitrateTask(int bitrate) { | |
294 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
295 DCHECK(loader_.get()); | |
296 | |
297 bitrate_ = bitrate; | |
298 loader_->SetBitrate(bitrate); | |
299 } | |
300 | |
301 // This method is the place where actual read happens, |loader_| must be valid | |
302 // prior to make this method call. | |
303 void BufferedDataSource::ReadInternal() { | |
304 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
305 int64 position = 0; | |
306 int size = 0; | |
307 { | |
308 base::AutoLock auto_lock(lock_); | |
309 if (stop_signal_received_) | |
310 return; | |
311 | |
312 position = read_op_->position(); | |
313 size = read_op_->size(); | |
314 } | |
315 | |
316 // First we prepare the intermediate read buffer for BufferedResourceLoader | |
317 // to write to. | |
318 if (size > intermediate_read_buffer_size_) { | |
319 intermediate_read_buffer_.reset(new uint8[size]); | |
320 } | |
321 | |
322 // Perform the actual read with BufferedResourceLoader. | |
323 loader_->Read(position, | |
324 size, | |
325 intermediate_read_buffer_.get(), | |
326 base::Bind(&BufferedDataSource::ReadCallback, | |
327 weak_factory_.GetWeakPtr())); | |
328 } | |
329 | |
330 | |
331 ///////////////////////////////////////////////////////////////////////////// | |
332 // BufferedResourceLoader callback methods. | |
333 void BufferedDataSource::StartCallback( | |
334 BufferedResourceLoader::Status status) { | |
335 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
336 DCHECK(loader_.get()); | |
337 | |
338 bool init_cb_is_null = false; | |
339 { | |
340 base::AutoLock auto_lock(lock_); | |
341 init_cb_is_null = init_cb_.is_null(); | |
342 } | |
343 if (init_cb_is_null) { | |
344 loader_->Stop(); | |
345 return; | |
346 } | |
347 | |
348 // All responses must be successful. Resources that are assumed to be fully | |
349 // buffered must have a known content length. | |
350 bool success = status == BufferedResourceLoader::kOk && | |
351 (!assume_fully_buffered() || | |
352 loader_->instance_size() != kPositionNotSpecified); | |
353 | |
354 if (success) { | |
355 total_bytes_ = loader_->instance_size(); | |
356 streaming_ = | |
357 !assume_fully_buffered() && | |
358 (total_bytes_ == kPositionNotSpecified || !loader_->range_supported()); | |
359 | |
360 media_log_->SetDoubleProperty("total_bytes", | |
361 static_cast<double>(total_bytes_)); | |
362 media_log_->SetBooleanProperty("streaming", streaming_); | |
363 } else { | |
364 loader_->Stop(); | |
365 } | |
366 | |
367 // TODO(scherkus): we shouldn't have to lock to signal host(), see | |
368 // http://crbug.com/113712 for details. | |
369 base::AutoLock auto_lock(lock_); | |
370 if (stop_signal_received_) | |
371 return; | |
372 | |
373 if (success) { | |
374 if (total_bytes_ != kPositionNotSpecified) { | |
375 host_->SetTotalBytes(total_bytes_); | |
376 if (assume_fully_buffered()) | |
377 host_->AddBufferedByteRange(0, total_bytes_); | |
378 } | |
379 | |
380 media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin()); | |
381 media_log_->SetBooleanProperty("passed_cors_access_check", | |
382 loader_->DidPassCORSAccessCheck()); | |
383 media_log_->SetBooleanProperty("range_header_supported", | |
384 loader_->range_supported()); | |
385 } | |
386 | |
387 base::ResetAndReturn(&init_cb_).Run(success); | |
388 } | |
389 | |
390 void BufferedDataSource::PartialReadStartCallback( | |
391 BufferedResourceLoader::Status status) { | |
392 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
393 DCHECK(loader_.get()); | |
394 | |
395 if (status == BufferedResourceLoader::kOk) { | |
396 // Once the request has started successfully, we can proceed with | |
397 // reading from it. | |
398 ReadInternal(); | |
399 return; | |
400 } | |
401 | |
402 // Stop the resource loader since we have received an error. | |
403 loader_->Stop(); | |
404 | |
405 // TODO(scherkus): we shouldn't have to lock to signal host(), see | |
406 // http://crbug.com/113712 for details. | |
407 base::AutoLock auto_lock(lock_); | |
408 if (stop_signal_received_) | |
409 return; | |
410 ReadOperation::Run(read_op_.Pass(), kReadError); | |
411 } | |
412 | |
413 void BufferedDataSource::ReadCallback( | |
414 BufferedResourceLoader::Status status, | |
415 int bytes_read) { | |
416 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
417 | |
418 // TODO(scherkus): we shouldn't have to lock to signal host(), see | |
419 // http://crbug.com/113712 for details. | |
420 base::AutoLock auto_lock(lock_); | |
421 if (stop_signal_received_) | |
422 return; | |
423 | |
424 if (status != BufferedResourceLoader::kOk) { | |
425 // Stop the resource load if it failed. | |
426 loader_->Stop(); | |
427 | |
428 if (status == BufferedResourceLoader::kCacheMiss && | |
429 read_op_->retries() < kNumCacheMissRetries) { | |
430 read_op_->IncrementRetries(); | |
431 | |
432 // Recreate a loader starting from where we last left off until the | |
433 // end of the resource. | |
434 loader_.reset(CreateResourceLoader( | |
435 read_op_->position(), kPositionNotSpecified)); | |
436 | |
437 base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr(); | |
438 loader_->Start( | |
439 base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this), | |
440 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, | |
441 weak_this), | |
442 base::Bind(&BufferedDataSource::ProgressCallback, weak_this), | |
443 frame_); | |
444 return; | |
445 } | |
446 | |
447 ReadOperation::Run(read_op_.Pass(), kReadError); | |
448 return; | |
449 } | |
450 | |
451 if (bytes_read > 0) { | |
452 memcpy(read_op_->data(), intermediate_read_buffer_.get(), 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(); | |
458 | |
459 if (total_bytes_ != kPositionNotSpecified) { | |
460 host_->SetTotalBytes(total_bytes_); | |
461 host_->AddBufferedByteRange(loader_->first_byte_position(), | |
462 total_bytes_); | |
463 } | |
464 } | |
465 ReadOperation::Run(read_op_.Pass(), bytes_read); | |
466 } | |
467 | |
468 void BufferedDataSource::LoadingStateChangedCallback( | |
469 BufferedResourceLoader::LoadingState state) { | |
470 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
471 | |
472 if (assume_fully_buffered()) | |
473 return; | |
474 | |
475 bool is_downloading_data; | |
476 switch (state) { | |
477 case BufferedResourceLoader::kLoading: | |
478 is_downloading_data = true; | |
479 break; | |
480 case BufferedResourceLoader::kLoadingDeferred: | |
481 case BufferedResourceLoader::kLoadingFinished: | |
482 is_downloading_data = false; | |
483 break; | |
484 | |
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: | |
491 return; | |
492 } | |
493 | |
494 downloading_cb_.Run(is_downloading_data); | |
495 } | |
496 | |
497 void BufferedDataSource::ProgressCallback(int64 position) { | |
498 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
499 | |
500 if (assume_fully_buffered()) | |
501 return; | |
502 | |
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_) | |
507 return; | |
508 | |
509 host_->AddBufferedByteRange(loader_->first_byte_position(), position); | |
510 } | |
511 | |
512 void BufferedDataSource::UpdateDeferStrategy(bool paused) { | |
513 // No need to aggressively buffer when we are assuming the resource is fully | |
514 // buffered. | |
515 if (assume_fully_buffered()) { | |
516 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); | |
517 return; | |
518 } | |
519 | |
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); | |
526 return; | |
527 } | |
528 | |
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); | |
534 } | |
535 | |
536 } // namespace content | |
OLD | NEW |