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, |
| 58 int size, |
| 59 uint8* data, |
| 60 const DataSource::ReadCB& callback); |
| 61 ~ReadOperation(); |
| 62 |
| 63 // Runs |callback_| with the given |result|, deleting the operation |
| 64 // afterwards. |
| 65 static void Run(scoped_ptr<ReadOperation> read_op, int result); |
| 66 |
| 67 int64 position() { return position_; } |
| 68 int size() { return size_; } |
| 69 uint8* data() { return data_; } |
| 70 |
| 71 private: |
| 72 const int64 position_; |
| 73 const int size_; |
| 74 uint8* data_; |
| 75 DataSource::ReadCB callback_; |
| 76 |
| 77 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation); |
| 78 }; |
| 79 |
| 80 MultibufferDataSource::ReadOperation::ReadOperation( |
| 81 int64 position, |
| 82 int size, |
| 83 uint8* data, |
| 84 const DataSource::ReadCB& callback) |
| 85 : position_(position), size_(size), data_(data), callback_(callback) { |
| 86 DCHECK(!callback_.is_null()); |
| 87 } |
| 88 |
| 89 MultibufferDataSource::ReadOperation::~ReadOperation() { |
| 90 DCHECK(callback_.is_null()); |
| 91 } |
| 92 |
| 93 // static |
| 94 void MultibufferDataSource::ReadOperation::Run( |
| 95 scoped_ptr<ReadOperation> read_op, |
| 96 int result) { |
| 97 base::ResetAndReturn(&read_op->callback_).Run(result); |
| 98 } |
| 99 |
| 100 MultibufferDataSource::MultibufferDataSource( |
| 101 const GURL& url, |
| 102 UrlData::CORSMode cors_mode, |
| 103 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| 104 linked_ptr<ResourceMultiBuffer> multibuffer, |
| 105 WebFrame* frame, |
| 106 MediaLog* media_log, |
| 107 BufferedDataSourceHost* host, |
| 108 const DownloadingCB& downloading_cb) |
| 109 : cors_mode_(cors_mode), |
| 110 total_bytes_(kPositionNotSpecified), |
| 111 streaming_(false), |
| 112 loading_(false), |
| 113 render_task_runner_(task_runner), |
| 114 multibuffer_(multibuffer), |
| 115 frame_(frame), |
| 116 stop_signal_received_(false), |
| 117 media_has_played_(false), |
| 118 single_origin_(true), |
| 119 cancel_on_defer_(false), |
| 120 preload_(AUTO), |
| 121 bitrate_(0), |
| 122 playback_rate_(0.0), |
| 123 media_log_(media_log), |
| 124 host_(host), |
| 125 downloading_cb_(downloading_cb), |
| 126 weak_factory_(this) { |
| 127 weak_ptr_ = weak_factory_.GetWeakPtr(); |
| 128 DCHECK(host_); |
| 129 DCHECK(!downloading_cb_.is_null()); |
| 130 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 131 url_data_ = multibuffer_->url_index()->GetByUrl(url, cors_mode_); |
| 132 url_data_->Use(); |
| 133 DCHECK(url_data_); |
| 134 } |
| 135 |
| 136 MultibufferDataSource::~MultibufferDataSource() { |
| 137 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 138 } |
| 139 |
| 140 bool MultibufferDataSource::media_has_played() const { |
| 141 return media_has_played_; |
| 142 } |
| 143 |
| 144 bool MultibufferDataSource::assume_fully_buffered() { |
| 145 return !url_data_->url().SchemeIsHTTPOrHTTPS(); |
| 146 } |
| 147 |
| 148 void MultibufferDataSource::CreateResourceLoader(int64 first_byte_position, |
| 149 int64 last_byte_position) { |
| 150 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 151 |
| 152 base::WeakPtr<MultibufferDataSource> weak_this = weak_factory_.GetWeakPtr(); |
| 153 reader_.reset(new MultiBufferReader( |
| 154 multibuffer_.get(), |
| 155 destination_url_data_ ? destination_url_data_ : url_data_, |
| 156 first_byte_position, 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(!reader_.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 (reader_->Available()) { |
| 174 render_task_runner_->PostTask( |
| 175 FROM_HERE, |
| 176 base::Bind(&MultibufferDataSource::StartCallback, weak_this)); |
| 177 } else { |
| 178 reader_->Wait(1, |
| 179 base::Bind(&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() && reader_.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 (!reader_) |
| 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(reader_.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( |
| 261 FROM_HERE, base::Bind(&MultibufferDataSource::SetBitrateTask, |
| 262 weak_factory_.GetWeakPtr(), bitrate)); |
| 263 } |
| 264 |
| 265 void MultibufferDataSource::OnBufferingHaveEnough() { |
| 266 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 267 if (reader_ && preload_ == METADATA && !media_has_played_ && !IsStreaming()) { |
| 268 cancel_on_defer_ = true; |
| 269 if (!loading_) |
| 270 reader_.reset(nullptr); |
| 271 } |
| 272 } |
| 273 |
| 274 void MultibufferDataSource::Read(int64 position, |
| 275 int size, |
| 276 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 (!reader_) { |
| 328 CreateResourceLoader(read_op_->position(), kPositionNotSpecified); |
| 329 } else { |
| 330 reader_->Seek(read_op_->position()); |
| 331 } |
| 332 |
| 333 int64_t available = reader_->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 = |
| 341 static_cast<int>(std::min<int64_t>(available, read_op_->size())); |
| 342 bytes_read = reader_->TryRead(read_op_->data(), bytes_read); |
| 343 ReadOperation::Run(read_op_.Pass(), bytes_read); |
| 344 } else { |
| 345 reader_->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 reader_.reset(nullptr); |
| 369 UpdateLoadingState(); |
| 370 } |
| 371 |
| 372 void MultibufferDataSource::SetBitrateTask(int bitrate) { |
| 373 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 374 DCHECK(reader_.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(reader_); |
| 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 reader_.reset(); |
| 393 return; |
| 394 } |
| 395 |
| 396 destination_url_data_ = reader_->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 = reader_->Available() > 0 && destination_url_data_ && |
| 401 (!assume_fully_buffered() || |
| 402 destination_url_data_->length() != kPositionNotSpecified); |
| 403 |
| 404 if (success) { |
| 405 total_bytes_ = destination_url_data_->length(); |
| 406 streaming_ = |
| 407 !assume_fully_buffered() && (total_bytes_ == kPositionNotSpecified || |
| 408 !destination_url_data_->range_supported()); |
| 409 |
| 410 media_log_->SetDoubleProperty("total_bytes", |
| 411 static_cast<double>(total_bytes_)); |
| 412 media_log_->SetBooleanProperty("streaming", streaming_); |
| 413 } else { |
| 414 reader_.reset(nullptr); |
| 415 } |
| 416 |
| 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_) |
| 421 return; |
| 422 |
| 423 if (success) { |
| 424 if (total_bytes_ != kPositionNotSpecified) { |
| 425 host_->SetTotalBytes(total_bytes_); |
| 426 if (assume_fully_buffered()) |
| 427 host_->AddBufferedByteRange(0, total_bytes_); |
| 428 } |
| 429 |
| 430 // Progress callback might be called after the start callback, |
| 431 // make sure that we update single_origin_ now. |
| 432 UpdateSingleOrigin(); |
| 433 |
| 434 media_log_->SetBooleanProperty("single_origin", single_origin_); |
| 435 media_log_->SetBooleanProperty("passed_cors_access_check", |
| 436 DidPassCORSAccessCheck()); |
| 437 media_log_->SetBooleanProperty("range_header_supported", |
| 438 destination_url_data_->range_supported()); |
| 439 } |
| 440 |
| 441 UpdateLoadingState(); |
| 442 render_task_runner_->PostTask( |
| 443 FROM_HERE, base::Bind(base::ResetAndReturn(&init_cb_), success)); |
| 444 } |
| 445 |
| 446 void MultibufferDataSource::UpdateSingleOrigin() { |
| 447 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 448 if (reader_ && destination_url_data_) { |
| 449 scoped_refptr<UrlData> new_url_data = reader_->GetUrlData(); |
| 450 if (new_url_data && new_url_data != destination_url_data_) { |
| 451 // A redirect has happened. |
| 452 // Check if origin has changed. |
| 453 if (destination_url_data_->url().GetOrigin() != |
| 454 new_url_data->url().GetOrigin()) { |
| 455 single_origin_ = false; |
| 456 } |
| 457 } |
| 458 } |
| 459 } |
| 460 |
| 461 void MultibufferDataSource::ProgressCallback(int64 begin, int64 end) { |
| 462 DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| 463 |
| 464 UpdateSingleOrigin(); |
| 465 if (assume_fully_buffered()) |
| 466 return; |
| 467 |
| 468 if (end > begin) { |
| 469 // TODO(scherkus): we shouldn't have to lock to signal host(), see |
| 470 // http://crbug.com/113712 for details. |
| 471 base::AutoLock auto_lock(lock_); |
| 472 if (stop_signal_received_) |
| 473 return; |
| 474 |
| 475 host_->AddBufferedByteRange(begin, end); |
| 476 } |
| 477 |
| 478 UpdateLoadingState(); |
| 479 } |
| 480 |
| 481 void MultibufferDataSource::UpdateLoadingState() { |
| 482 // Update loading state. |
| 483 if ((!!reader_ && reader_->IsLoading()) != loading_) { |
| 484 loading_ = !loading_; |
| 485 |
| 486 if (!loading_ && cancel_on_defer_) { |
| 487 reader_.reset(nullptr); |
| 488 } |
| 489 |
| 490 // Callback could kill us, be sure to call it last. |
| 491 downloading_cb_.Run(loading_); |
| 492 } |
| 493 } |
| 494 |
| 495 void MultibufferDataSource::UpdateBufferSizes() { |
| 496 if (!reader_) |
| 497 return; |
| 498 |
| 499 if (!assume_fully_buffered()) { |
| 500 // If the playback has started and we're paused, then try to load as much as |
| 501 // possible, assuming that the file is cacheable. (If not, why bother?) |
| 502 if (media_has_played_ && paused_ && destination_url_data_ && |
| 503 destination_url_data_->range_supported() && |
| 504 destination_url_data_->cacheable()) { |
| 505 reader_->SetPreload(1LL << 40, 1LL << 40); // 1 Tb |
| 506 return; |
| 507 } |
| 508 } |
| 509 |
| 510 // Use a default bit rate if unknown and clamp to prevent overflow. |
| 511 int64 bitrate = clamp<int64>(bitrate_, 0, kMaxBitrate); |
| 512 if (bitrate == 0) |
| 513 bitrate = kDefaultBitrate; |
| 514 |
| 515 // Only scale the buffer window for playback rates greater than 1.0 in |
| 516 // magnitude and clamp to prevent overflow. |
| 517 bool backward_playback = false; |
| 518 double playback_rate = playback_rate_; |
| 519 if (playback_rate < 0.0) { |
| 520 backward_playback = true; |
| 521 playback_rate *= -1.0; |
| 522 } |
| 523 |
| 524 playback_rate = std::max(playback_rate, 1.0); |
| 525 playback_rate = std::min(playback_rate, kMaxPlaybackRate); |
| 526 |
| 527 int64 bytes_per_second = (bitrate / 8.0) * playback_rate; |
| 528 |
| 529 int64 preload = clamp(kTargetSecondsBufferedAhead * bytes_per_second, |
| 530 kMinBufferPreload, kMaxBufferPreload); |
| 531 int64 back_buffer = clamp(kTargetSecondsBufferedBehind * bytes_per_second, |
| 532 kMinBufferPreload, kMaxBufferPreload); |
| 533 if (backward_playback) |
| 534 std::swap(preload, back_buffer); |
| 535 |
| 536 int64 pin_forwards = kMaxBufferSize - back_buffer; |
| 537 DCHECK_LE(preload_ + kPreloadHighExtra, pin_forwards); |
| 538 reader_->SetMaxBuffer(back_buffer, pin_forwards); |
| 539 |
| 540 if (preload_ == METADATA) { |
| 541 reader_->SetPreload(0, 0); |
| 542 } else { |
| 543 reader_->SetPreload(preload + kPreloadHighExtra, preload); |
| 544 } |
| 545 } |
| 546 |
| 547 } // namespace media |
OLD | NEW |