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 "media/blink/multibuffer_data_source.h" | |
6 | |
7 #include "base/bind.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 "media/blink/multibuffer_reader.h" | |
13 #include "net/base/net_errors.h" | |
14 | |
15 using blink::WebFrame; | |
16 | |
17 namespace { | |
18 | |
19 // Minimum preload buffer. | |
20 const int64 kMinBufferPreload = 2 << 20; // 2 Mb | |
21 // Maxmimum preload buffer. | |
22 const int64 kMaxBufferPreload = 20 << 20; // 20 Mb | |
23 | |
24 // Preload this much extra, then stop preloading until we fall below the | |
25 // kTargetSecondsBufferedAhead. | |
26 const int64 kPreloadHighExtra = 1 << 20; // 1 Mb | |
27 | |
28 // Total size of the pinned region in the cache. | |
29 const int64 kMaxBufferSize = 25 << 20; // 25 Mb | |
30 | |
31 // If bitrate is not known, use this. | |
32 const int64 kDefaultBitrate = 200 * 8 << 10; // 200 Kbps. | |
33 | |
34 // Maximum bitrate for buffer calculations. | |
35 const int64 kMaxBitrate = 20 * 8 << 20; // 20 Mbps. | |
36 | |
37 // Maximum playback rate for buffer calculations. | |
38 const double kMaxPlaybackRate = 25.0; | |
39 | |
40 // Preload this many seconds of data by default. | |
41 const int64 kTargetSecondsBufferedAhead = 10; | |
42 | |
43 // Keep this many seconds of data for going back by default. | |
44 const int64 kTargetSecondsBufferedBehind = 2; | |
45 | |
46 } // namespace | |
47 | |
48 namespace media { | |
49 | |
50 template<typename T> | |
51 T clamp(T value, T min, T max) { | |
52 return std::max(std::min(value, max), min); | |
53 } | |
54 | |
55 class MultibufferDataSource::ReadOperation { | |
56 public: | |
57 ReadOperation(int64 position, int size, uint8* data, | |
58 const DataSource::ReadCB& callback); | |
59 ~ReadOperation(); | |
60 | |
61 // Runs |callback_| with the given |result|, deleting the operation | |
62 // afterwards. | |
63 static void Run(scoped_ptr<ReadOperation> read_op, int result); | |
64 | |
65 int64 position() { return position_; } | |
66 int size() { return size_; } | |
67 uint8* data() { return data_; } | |
68 | |
69 private: | |
70 const int64 position_; | |
71 const int size_; | |
72 uint8* data_; | |
73 DataSource::ReadCB callback_; | |
74 | |
75 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation); | |
76 }; | |
77 | |
78 MultibufferDataSource::ReadOperation::ReadOperation( | |
79 int64 position, int size, uint8* data, | |
80 const DataSource::ReadCB& callback) | |
81 : position_(position), | |
82 size_(size), | |
83 data_(data), | |
84 callback_(callback) { | |
85 DCHECK(!callback_.is_null()); | |
86 } | |
87 | |
88 MultibufferDataSource::ReadOperation::~ReadOperation() { | |
89 DCHECK(callback_.is_null()); | |
90 } | |
91 | |
92 // static | |
93 void MultibufferDataSource::ReadOperation::Run( | |
94 scoped_ptr<ReadOperation> read_op, int result) { | |
95 base::ResetAndReturn(&read_op->callback_).Run(result); | |
96 } | |
97 | |
98 MultibufferDataSource::MultibufferDataSource( | |
99 const GURL& url, | |
100 UrlData::CORSMode cors_mode, | |
101 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
102 ResourceMultiBuffer* multibuffer, | |
103 WebFrame* frame, | |
104 MediaLog* media_log, | |
105 BufferedDataSourceHost* host, | |
106 const DownloadingCB& downloading_cb) | |
107 : cors_mode_(cors_mode), | |
108 total_bytes_(kPositionNotSpecified), | |
109 streaming_(false), | |
110 loading_(false), | |
111 render_task_runner_(task_runner), | |
112 multibuffer_(multibuffer), | |
113 frame_(frame), | |
114 stop_signal_received_(false), | |
115 media_has_played_(false), | |
116 single_origin_(true), | |
117 cancel_on_defer_(false), | |
118 preload_(AUTO), | |
119 bitrate_(0), | |
120 playback_rate_(0.0), | |
121 media_log_(media_log), | |
122 host_(host), | |
123 downloading_cb_(downloading_cb), | |
124 weak_factory_(this) { | |
125 weak_ptr_ = weak_factory_.GetWeakPtr(); | |
126 DCHECK(host_); | |
127 DCHECK(!downloading_cb_.is_null()); | |
128 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
129 url_data_ = multibuffer_->url_index()->GetByUrl(url, cors_mode_); | |
130 url_data_->Use(); | |
131 DCHECK(url_data_); | |
132 } | |
133 | |
134 MultibufferDataSource::~MultibufferDataSource() { | |
135 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
136 } | |
137 | |
138 bool MultibufferDataSource::media_has_played() const { | |
139 return media_has_played_; | |
140 } | |
141 | |
142 bool MultibufferDataSource::assume_fully_buffered() { | |
143 return !url_data_->url().SchemeIsHTTPOrHTTPS(); | |
144 } | |
145 | |
146 // A factory method to create BufferedResourceLoader using the read parameters. | |
liberato (no reviews please)
2015/10/16 21:50:36
comment is out of date. also, it doesn't create a
hubbe
2015/10/16 23:47:05
Removed the comment.
| |
147 void MultibufferDataSource::CreateResourceLoader( | |
148 int64 first_byte_position, int64 last_byte_position) { | |
149 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
150 | |
151 base::WeakPtr<MultibufferDataSource> weak_this = weak_factory_.GetWeakPtr(); | |
152 loader_.reset(new MultiBufferReader( | |
153 multibuffer_, | |
154 destination_url_data_ ? destination_url_data_ : url_data_, | |
155 first_byte_position, | |
156 last_byte_position, | |
157 base::Bind(&MultibufferDataSource::ProgressCallback, weak_this))); | |
158 UpdateBufferSizes(); | |
159 } | |
160 | |
161 void MultibufferDataSource::Initialize(const InitializeCB& init_cb) { | |
162 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
163 DCHECK(!init_cb.is_null()); | |
164 DCHECK(!loader_.get()); | |
165 | |
166 init_cb_ = init_cb; | |
167 | |
168 CreateResourceLoader(0, kPositionNotSpecified); | |
169 | |
170 base::WeakPtr<MultibufferDataSource> weak_this = weak_factory_.GetWeakPtr(); | |
171 | |
172 // We're not allowed to call Wait() if data is already available. | |
173 if (loader_->Available()) { | |
174 render_task_runner_->PostTask( | |
175 FROM_HERE, | |
176 base::Bind(&MultibufferDataSource::StartCallback, weak_this)); | |
177 } else { | |
178 loader_->Wait(1, base::Bind( | |
179 &MultibufferDataSource::StartCallback, weak_this)); | |
180 } | |
181 UpdateLoadingState(); | |
182 } | |
183 | |
184 void MultibufferDataSource::SetPreload(Preload preload) { | |
185 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
186 preload_ = preload; | |
187 UpdateBufferSizes(); | |
188 } | |
189 | |
190 bool MultibufferDataSource::HasSingleOrigin() { | |
191 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
192 DCHECK(init_cb_.is_null() && loader_.get()) | |
193 << "Initialize() must complete before calling HasSingleOrigin()"; | |
194 return single_origin_; | |
195 } | |
196 | |
197 bool MultibufferDataSource::DidPassCORSAccessCheck() const { | |
198 if (cors_mode_ == UrlData::kUnspecified) | |
199 return false; | |
200 // If init_cb is set, we initialization is not finished yet. | |
201 if (!init_cb_.is_null()) | |
202 return false; | |
203 // Loader will be false if there was a failure. | |
204 if (!loader_) | |
205 return false; | |
206 return true; | |
207 } | |
208 | |
209 void MultibufferDataSource::Abort() { | |
210 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
211 { | |
212 base::AutoLock auto_lock(lock_); | |
213 StopInternal_Locked(); | |
214 } | |
215 StopLoader(); | |
216 frame_ = NULL; | |
217 } | |
218 | |
219 void MultibufferDataSource::MediaPlaybackRateChanged(double playback_rate) { | |
220 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
221 DCHECK(loader_.get()); | |
222 | |
223 if (playback_rate < 0.0) | |
224 return; | |
225 | |
226 playback_rate_ = playback_rate; | |
227 cancel_on_defer_ = false; | |
228 UpdateBufferSizes(); | |
229 } | |
230 | |
231 void MultibufferDataSource::MediaIsPlaying() { | |
232 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
233 media_has_played_ = true; | |
234 cancel_on_defer_ = false; | |
235 paused_ = false; | |
236 preload_ = AUTO; | |
237 UpdateBufferSizes(); | |
238 } | |
239 | |
240 void MultibufferDataSource::MediaIsPaused() { | |
241 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
242 paused_ = true; | |
243 UpdateBufferSizes(); | |
244 } | |
245 | |
246 ///////////////////////////////////////////////////////////////////////////// | |
247 // DataSource implementation. | |
248 void MultibufferDataSource::Stop() { | |
249 { | |
250 base::AutoLock auto_lock(lock_); | |
251 StopInternal_Locked(); | |
252 } | |
253 | |
254 render_task_runner_->PostTask(FROM_HERE, | |
255 base::Bind(&MultibufferDataSource::StopLoader, | |
256 weak_factory_.GetWeakPtr())); | |
257 } | |
258 | |
259 void MultibufferDataSource::SetBitrate(int bitrate) { | |
260 render_task_runner_->PostTask(FROM_HERE, | |
261 base::Bind(&MultibufferDataSource::SetBitrateTask, | |
262 weak_factory_.GetWeakPtr(), | |
263 bitrate)); | |
264 } | |
265 | |
266 void MultibufferDataSource::OnBufferingHaveEnough() { | |
267 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
268 if (loader_ && preload_ == METADATA && !media_has_played_ && !IsStreaming()) { | |
269 cancel_on_defer_ = true; | |
270 if (!loading_) | |
271 loader_.reset(nullptr); | |
272 } | |
273 } | |
274 | |
275 void MultibufferDataSource::Read( | |
276 int64 position, int size, uint8* data, | |
277 const DataSource::ReadCB& read_cb) { | |
278 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes"; | |
279 // Reading is not allowed until after initialization. | |
280 DCHECK(init_cb_.is_null()); | |
281 DCHECK(!read_cb.is_null()); | |
282 | |
283 { | |
284 base::AutoLock auto_lock(lock_); | |
285 DCHECK(!read_op_); | |
286 | |
287 if (stop_signal_received_) { | |
288 read_cb.Run(kReadError); | |
289 return; | |
290 } | |
291 | |
292 read_op_.reset(new ReadOperation(position, size, data, read_cb)); | |
293 } | |
294 | |
295 render_task_runner_->PostTask( | |
296 FROM_HERE, | |
297 base::Bind(&MultibufferDataSource::ReadTask, weak_factory_.GetWeakPtr())); | |
298 } | |
299 | |
300 bool MultibufferDataSource::GetSize(int64* size_out) { | |
301 if (destination_url_data_) { | |
302 *size_out = destination_url_data_->length(); | |
303 if (*size_out != kPositionNotSpecified) { | |
304 return true; | |
305 } | |
306 } | |
307 *size_out = 0; | |
308 return false; | |
309 } | |
310 | |
311 bool MultibufferDataSource::IsStreaming() { | |
312 return streaming_; | |
313 } | |
314 | |
315 ///////////////////////////////////////////////////////////////////////////// | |
316 // This method is the place where actual read happens, | |
317 void MultibufferDataSource::ReadTask() { | |
318 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
319 | |
320 base::AutoLock auto_lock(lock_); | |
321 int bytes_read = 0; | |
322 if (stop_signal_received_) | |
323 return; | |
324 DCHECK(read_op_); | |
325 DCHECK(read_op_->size()); | |
326 | |
327 if (!loader_) { | |
328 CreateResourceLoader(read_op_->position(), kPositionNotSpecified); | |
329 } | |
liberato (no reviews please)
2015/10/16 21:50:36
else?
hubbe
2015/10/16 23:47:05
Sure, why not?
| |
330 | |
331 loader_->Seek(read_op_->position()); | |
332 | |
333 int64_t available = loader_->Available(); | |
334 if (available < 0) { | |
335 // A failure has occured. | |
336 ReadOperation::Run(read_op_.Pass(), kReadError); | |
337 return; | |
338 } | |
339 if (available) { | |
340 bytes_read = static_cast<int>( | |
341 std::min<int64_t>(available, read_op_->size())); | |
342 bytes_read = loader_->TryRead(read_op_->data(), bytes_read); | |
343 ReadOperation::Run(read_op_.Pass(), bytes_read); | |
344 } else { | |
345 loader_->Wait(1, base::Bind(&MultibufferDataSource::ReadTask, | |
346 weak_factory_.GetWeakPtr())); | |
347 UpdateLoadingState(); | |
348 } | |
349 } | |
350 | |
351 void MultibufferDataSource::StopInternal_Locked() { | |
352 lock_.AssertAcquired(); | |
353 if (stop_signal_received_) | |
354 return; | |
355 | |
356 stop_signal_received_ = true; | |
357 | |
358 // Initialize() isn't part of the DataSource interface so don't call it in | |
359 // response to Stop(). | |
360 init_cb_.Reset(); | |
361 | |
362 if (read_op_) | |
363 ReadOperation::Run(read_op_.Pass(), kReadError); | |
364 } | |
365 | |
366 void MultibufferDataSource::StopLoader() { | |
367 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
368 loader_.reset(nullptr); | |
369 UpdateLoadingState(); | |
370 } | |
371 | |
372 void MultibufferDataSource::SetBitrateTask(int bitrate) { | |
373 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
374 DCHECK(loader_.get()); | |
375 | |
376 bitrate_ = bitrate; | |
377 UpdateBufferSizes(); | |
378 } | |
379 | |
380 ///////////////////////////////////////////////////////////////////////////// | |
381 // BufferedResourceLoader callback methods. | |
382 void MultibufferDataSource::StartCallback() { | |
383 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
384 DCHECK(loader_); | |
385 | |
386 bool init_cb_is_null = false; | |
387 { | |
388 base::AutoLock auto_lock(lock_); | |
389 init_cb_is_null = init_cb_.is_null(); | |
390 } | |
391 if (init_cb_is_null) { | |
392 loader_.reset(); | |
393 return; | |
394 } | |
395 | |
396 destination_url_data_ = loader_->GetUrlData(); | |
397 | |
398 // All responses must be successful. Resources that are assumed to be fully | |
399 // buffered must have a known content length. | |
400 bool success = | |
401 loader_->Available() > 0 && | |
402 destination_url_data_ && | |
403 (!assume_fully_buffered() || | |
404 destination_url_data_->length() != kPositionNotSpecified); | |
405 | |
406 if (success) { | |
407 total_bytes_ = destination_url_data_->length(); | |
408 streaming_ = | |
409 !assume_fully_buffered() && | |
410 (total_bytes_ == kPositionNotSpecified || | |
411 !destination_url_data_->range_supported()); | |
412 | |
413 media_log_->SetDoubleProperty("total_bytes", | |
414 static_cast<double>(total_bytes_)); | |
415 media_log_->SetBooleanProperty("streaming", streaming_); | |
416 } else { | |
417 loader_.reset(nullptr); | |
418 } | |
419 | |
420 // TODO(scherkus): we shouldn't have to lock to signal host(), see | |
421 // http://crbug.com/113712 for details. | |
422 base::AutoLock auto_lock(lock_); | |
423 if (stop_signal_received_) | |
424 return; | |
425 | |
426 if (success) { | |
427 if (total_bytes_ != kPositionNotSpecified) { | |
428 host_->SetTotalBytes(total_bytes_); | |
429 if (assume_fully_buffered()) | |
430 host_->AddBufferedByteRange(0, total_bytes_); | |
431 } | |
432 | |
433 // Progress callback might be called after the start callback, | |
434 // make sure that we update has_single_origin_ now. | |
liberato (no reviews please)
2015/10/16 21:50:36
single_origin_
hubbe
2015/10/16 23:47:05
Done.
| |
435 UpdateSingleOrigin(); | |
436 | |
437 media_log_->SetBooleanProperty("single_origin", single_origin_); | |
438 media_log_->SetBooleanProperty("passed_cors_access_check", | |
439 DidPassCORSAccessCheck()); | |
440 media_log_->SetBooleanProperty("range_header_supported", | |
441 destination_url_data_->range_supported()); | |
442 } | |
443 | |
444 UpdateLoadingState(); | |
445 render_task_runner_->PostTask( | |
446 FROM_HERE, base::Bind(base::ResetAndReturn(&init_cb_), success)); | |
447 } | |
448 | |
449 void MultibufferDataSource::UpdateSingleOrigin() { | |
450 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
451 if (loader_ && destination_url_data_) { | |
452 scoped_refptr<UrlData> new_url_data = loader_->GetUrlData(); | |
453 if (new_url_data && new_url_data != destination_url_data_) { | |
454 // A redirect has happened. | |
455 // Check if origin has changed. | |
456 if (destination_url_data_->url().GetOrigin() != | |
457 new_url_data->url().GetOrigin()) { | |
458 single_origin_ = false; | |
459 } | |
460 } | |
461 } | |
462 } | |
463 | |
464 void MultibufferDataSource::ProgressCallback(int64 begin, int64 end) { | |
465 DCHECK(render_task_runner_->BelongsToCurrentThread()); | |
466 | |
467 UpdateSingleOrigin(); | |
468 if (assume_fully_buffered()) | |
469 return; | |
470 | |
471 if (end > begin) { | |
472 // TODO(scherkus): we shouldn't have to lock to signal host(), see | |
473 // http://crbug.com/113712 for details. | |
474 base::AutoLock auto_lock(lock_); | |
475 if (stop_signal_received_) | |
476 return; | |
477 | |
478 host_->AddBufferedByteRange(begin, end); | |
479 } | |
480 | |
481 UpdateLoadingState(); | |
482 } | |
483 | |
484 void MultibufferDataSource::UpdateLoadingState() { | |
485 // Update loading state. | |
486 if ((!!loader_ && loader_->IsLoading()) != loading_) { | |
liberato (no reviews please)
2015/10/16 21:50:36
extra points to use s/!=/^ :)
hubbe
2015/10/16 23:47:05
Acknowledged.
| |
487 loading_ = !loading_; | |
488 | |
489 if (!loading_ && cancel_on_defer_) { | |
490 loader_.reset(nullptr); | |
491 } | |
492 | |
493 // Callback could kill us, be sure to call it last. | |
494 downloading_cb_.Run(loading_); | |
495 } | |
496 } | |
497 | |
498 void MultibufferDataSource::UpdateBufferSizes() { | |
499 if (!loader_) | |
500 return; | |
501 | |
502 if (!assume_fully_buffered()) { | |
503 // If the playback has started and we're paused, then try to load as much as | |
504 // possible, assuming that the file is cacheable. (If not, why bother?) | |
505 if (media_has_played_ && paused_ && | |
506 destination_url_data_ && | |
507 destination_url_data_->range_supported() && | |
508 destination_url_data_->cacheable()) { | |
509 loader_->SetPreload(1LL << 40, 1LL << 40); // 1 Tb | |
510 return; | |
511 } | |
512 } | |
513 | |
514 // Use a default bit rate if unknown and clamp to prevent overflow. | |
515 int64 bitrate = clamp<int64>(bitrate_, 0, kMaxBitrate); | |
516 if (bitrate == 0) | |
517 bitrate = kDefaultBitrate; | |
518 | |
519 // Only scale the buffer window for playback rates greater than 1.0 in | |
520 // magnitude and clamp to prevent overflow. | |
521 bool backward_playback = false; | |
522 double playback_rate = playback_rate_; | |
523 if (playback_rate < 0.0) { | |
524 backward_playback = true; | |
525 playback_rate *= -1.0; | |
526 } | |
527 | |
528 playback_rate = std::max(playback_rate, 1.0); | |
529 playback_rate = std::min(playback_rate, kMaxPlaybackRate); | |
530 | |
531 int64 bytes_per_second = (bitrate / 8.0) * playback_rate; | |
532 | |
533 int64 preload = clamp(kTargetSecondsBufferedAhead * bytes_per_second, | |
534 kMinBufferPreload, | |
535 kMaxBufferPreload); | |
536 int64 back_buffer = clamp(kTargetSecondsBufferedBehind * bytes_per_second, | |
537 kMinBufferPreload, | |
538 kMaxBufferPreload); | |
539 if (backward_playback) | |
540 std::swap(preload, back_buffer); | |
541 | |
542 int64 pin_forwards = kMaxBufferSize - back_buffer; | |
543 DCHECK_LE(preload_ + kPreloadHighExtra, pin_forwards); | |
544 loader_->SetMaxBuffer(back_buffer, pin_forwards); | |
545 | |
546 if (preload_ == METADATA) { | |
547 loader_->SetPreload(0, 0); | |
548 } else { | |
549 loader_->SetPreload(preload + kPreloadHighExtra, preload); | |
550 } | |
551 } | |
552 | |
553 } // namespace media | |
OLD | NEW |