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/message_loop/message_loop_proxy.h" 10 #include "media/base/media_log.h" 11 #include "net/base/net_errors.h" 12 13 using blink::WebFrame; 14 15 namespace { 16 17 // BufferedDataSource has an intermediate buffer, this value governs the initial 18 // size of that buffer. It is set to 32KB because this is a typical read size 19 // of FFmpeg. 20 const int kInitialReadBufferSize = 32768; 21 22 // Number of cache misses we allow for a single Read() before signaling an 23 // error. 24 const int kNumCacheMissRetries = 3; 25 26 } // namespace 27 28 namespace content { 29 30 class BufferedDataSource::ReadOperation { 31 public: 32 ReadOperation(int64 position, int size, uint8* data, 33 const media::DataSource::ReadCB& callback); 34 ~ReadOperation(); 35 36 // Runs |callback_| with the given |result|, deleting the operation 37 // afterwards. 38 static void Run(scoped_ptr<ReadOperation> read_op, int result); 39 40 // State for the number of times this read operation has been retried. 41 int retries() { return retries_; } 42 void IncrementRetries() { ++retries_; } 43 44 int64 position() { return position_; } 45 int size() { return size_; } 46 uint8* data() { return data_; } 47 48 private: 49 int retries_; 50 51 const int64 position_; 52 const int size_; 53 uint8* data_; 54 media::DataSource::ReadCB callback_; 55 56 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation); 57 }; 58 59 BufferedDataSource::ReadOperation::ReadOperation( 60 int64 position, int size, uint8* data, 61 const media::DataSource::ReadCB& callback) 62 : retries_(0), 63 position_(position), 64 size_(size), 65 data_(data), 66 callback_(callback) { 67 DCHECK(!callback_.is_null()); 68 } 69 70 BufferedDataSource::ReadOperation::~ReadOperation() { 71 DCHECK(callback_.is_null()); 72 } 73 74 // static 75 void BufferedDataSource::ReadOperation::Run( 76 scoped_ptr<ReadOperation> read_op, int result) { 77 base::ResetAndReturn(&read_op->callback_).Run(result); 78 } 79 80 BufferedDataSource::BufferedDataSource( 81 const scoped_refptr<base::MessageLoopProxy>& render_loop, 82 WebFrame* frame, 83 media::MediaLog* media_log, 84 const DownloadingCB& downloading_cb) 85 : cors_mode_(BufferedResourceLoader::kUnspecified), 86 total_bytes_(kPositionNotSpecified), 87 assume_fully_buffered_(false), 88 streaming_(false), 89 frame_(frame), 90 intermediate_read_buffer_(new uint8[kInitialReadBufferSize]), 91 intermediate_read_buffer_size_(kInitialReadBufferSize), 92 render_loop_(render_loop), 93 stop_signal_received_(false), 94 media_has_played_(false), 95 preload_(AUTO), 96 bitrate_(0), 97 playback_rate_(0.0), 98 media_log_(media_log), 99 downloading_cb_(downloading_cb), 100 weak_factory_(this) { 101 DCHECK(!downloading_cb_.is_null()); 102 weak_this_ = weak_factory_.GetWeakPtr(); 103 } 104 105 BufferedDataSource::~BufferedDataSource() {} 106 107 // A factory method to create BufferedResourceLoader using the read parameters. 108 // This method can be overridden to inject mock BufferedResourceLoader object 109 // for testing purpose. 110 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( 111 int64 first_byte_position, int64 last_byte_position) { 112 DCHECK(render_loop_->BelongsToCurrentThread()); 113 114 BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ? 115 BufferedResourceLoader::kReadThenDefer : 116 BufferedResourceLoader::kCapacityDefer; 117 118 return new BufferedResourceLoader(url_, 119 cors_mode_, 120 first_byte_position, 121 last_byte_position, 122 strategy, 123 bitrate_, 124 playback_rate_, 125 media_log_.get()); 126 } 127 128 void BufferedDataSource::set_host(media::DataSourceHost* host) { 129 DataSource::set_host(host); 130 131 if (loader_) { 132 base::AutoLock auto_lock(lock_); 133 UpdateHostState_Locked(); 134 } 135 } 136 137 void BufferedDataSource::Initialize( 138 const GURL& url, 139 BufferedResourceLoader::CORSMode cors_mode, 140 const InitializeCB& init_cb) { 141 DCHECK(render_loop_->BelongsToCurrentThread()); 142 DCHECK(!init_cb.is_null()); 143 DCHECK(!loader_.get()); 144 url_ = url; 145 cors_mode_ = cors_mode; 146 147 init_cb_ = init_cb; 148 149 if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { 150 // Do an unbounded range request starting at the beginning. If the server 151 // responds with 200 instead of 206 we'll fall back into a streaming mode. 152 loader_.reset(CreateResourceLoader(0, kPositionNotSpecified)); 153 } else { 154 // For all other protocols, assume they support range request. We fetch 155 // the full range of the resource to obtain the instance size because 156 // we won't be served HTTP headers. 157 loader_.reset(CreateResourceLoader(kPositionNotSpecified, 158 kPositionNotSpecified)); 159 assume_fully_buffered_ = true; 160 } 161 162 loader_->Start( 163 base::Bind(&BufferedDataSource::StartCallback, weak_this_), 164 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this_), 165 base::Bind(&BufferedDataSource::ProgressCallback, weak_this_), 166 frame_); 167 } 168 169 void BufferedDataSource::SetPreload(Preload preload) { 170 DCHECK(render_loop_->BelongsToCurrentThread()); 171 preload_ = preload; 172 } 173 174 bool BufferedDataSource::HasSingleOrigin() { 175 DCHECK(render_loop_->BelongsToCurrentThread()); 176 DCHECK(init_cb_.is_null() && loader_.get()) 177 << "Initialize() must complete before calling HasSingleOrigin()"; 178 return loader_->HasSingleOrigin(); 179 } 180 181 bool BufferedDataSource::DidPassCORSAccessCheck() const { 182 return loader_.get() && loader_->DidPassCORSAccessCheck(); 183 } 184 185 void BufferedDataSource::Abort() { 186 DCHECK(render_loop_->BelongsToCurrentThread()); 187 { 188 base::AutoLock auto_lock(lock_); 189 StopInternal_Locked(); 190 } 191 StopLoader(); 192 frame_ = NULL; 193 } 194 195 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) { 196 DCHECK(render_loop_->BelongsToCurrentThread()); 197 DCHECK(loader_.get()); 198 199 if (playback_rate < 0.0f) 200 return; 201 202 playback_rate_ = playback_rate; 203 loader_->SetPlaybackRate(playback_rate); 204 } 205 206 void BufferedDataSource::MediaIsPlaying() { 207 DCHECK(render_loop_->BelongsToCurrentThread()); 208 media_has_played_ = true; 209 UpdateDeferStrategy(false); 210 } 211 212 void BufferedDataSource::MediaIsPaused() { 213 DCHECK(render_loop_->BelongsToCurrentThread()); 214 UpdateDeferStrategy(true); 215 } 216 217 ///////////////////////////////////////////////////////////////////////////// 218 // media::DataSource implementation. 219 void BufferedDataSource::Stop(const base::Closure& closure) { 220 { 221 base::AutoLock auto_lock(lock_); 222 StopInternal_Locked(); 223 } 224 closure.Run(); 225 226 render_loop_->PostTask(FROM_HERE, 227 base::Bind(&BufferedDataSource::StopLoader, weak_this_)); 228 } 229 230 void BufferedDataSource::SetBitrate(int bitrate) { 231 render_loop_->PostTask(FROM_HERE, base::Bind( 232 &BufferedDataSource::SetBitrateTask, weak_this_, bitrate)); 233 } 234 235 void BufferedDataSource::Read( 236 int64 position, int size, uint8* data, 237 const media::DataSource::ReadCB& read_cb) { 238 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes"; 239 DCHECK(!read_cb.is_null()); 240 241 { 242 base::AutoLock auto_lock(lock_); 243 DCHECK(!read_op_); 244 245 if (stop_signal_received_) { 246 read_cb.Run(kReadError); 247 return; 248 } 249 250 read_op_.reset(new ReadOperation(position, size, data, read_cb)); 251 } 252 253 render_loop_->PostTask(FROM_HERE, base::Bind( 254 &BufferedDataSource::ReadTask, weak_this_)); 255 } 256 257 bool BufferedDataSource::GetSize(int64* size_out) { 258 if (total_bytes_ != kPositionNotSpecified) { 259 *size_out = total_bytes_; 260 return true; 261 } 262 *size_out = 0; 263 return false; 264 } 265 266 bool BufferedDataSource::IsStreaming() { 267 return streaming_; 268 } 269 270 ///////////////////////////////////////////////////////////////////////////// 271 // Render thread tasks. 272 void BufferedDataSource::ReadTask() { 273 DCHECK(render_loop_->BelongsToCurrentThread()); 274 ReadInternal(); 275 } 276 277 void BufferedDataSource::StopInternal_Locked() { 278 lock_.AssertAcquired(); 279 if (stop_signal_received_) 280 return; 281 282 stop_signal_received_ = true; 283 284 // Initialize() isn't part of the DataSource interface so don't call it in 285 // response to Stop(). 286 init_cb_.Reset(); 287 288 if (read_op_) 289 ReadOperation::Run(read_op_.Pass(), kReadError); 290 } 291 292 void BufferedDataSource::StopLoader() { 293 DCHECK(render_loop_->BelongsToCurrentThread()); 294 295 if (loader_) 296 loader_->Stop(); 297 } 298 299 void BufferedDataSource::SetBitrateTask(int bitrate) { 300 DCHECK(render_loop_->BelongsToCurrentThread()); 301 DCHECK(loader_.get()); 302 303 bitrate_ = bitrate; 304 loader_->SetBitrate(bitrate); 305 } 306 307 // This method is the place where actual read happens, |loader_| must be valid 308 // prior to make this method call. 309 void BufferedDataSource::ReadInternal() { 310 DCHECK(render_loop_->BelongsToCurrentThread()); 311 int64 position = 0; 312 int size = 0; 313 { 314 base::AutoLock auto_lock(lock_); 315 if (stop_signal_received_) 316 return; 317 318 position = read_op_->position(); 319 size = read_op_->size(); 320 } 321 322 // First we prepare the intermediate read buffer for BufferedResourceLoader 323 // to write to. 324 if (size > intermediate_read_buffer_size_) { 325 intermediate_read_buffer_.reset(new uint8[size]); 326 } 327 328 // Perform the actual read with BufferedResourceLoader. 329 loader_->Read( 330 position, size, intermediate_read_buffer_.get(), 331 base::Bind(&BufferedDataSource::ReadCallback, weak_this_)); 332 } 333 334 335 ///////////////////////////////////////////////////////////////////////////// 336 // BufferedResourceLoader callback methods. 337 void BufferedDataSource::StartCallback( 338 BufferedResourceLoader::Status status) { 339 DCHECK(render_loop_->BelongsToCurrentThread()); 340 DCHECK(loader_.get()); 341 342 bool init_cb_is_null = false; 343 { 344 base::AutoLock auto_lock(lock_); 345 init_cb_is_null = init_cb_.is_null(); 346 } 347 if (init_cb_is_null) { 348 loader_->Stop(); 349 return; 350 } 351 352 // All responses must be successful. Resources that are assumed to be fully 353 // buffered must have a known content length. 354 bool success = status == BufferedResourceLoader::kOk && 355 (!assume_fully_buffered_ || 356 loader_->instance_size() != kPositionNotSpecified); 357 358 if (success) { 359 total_bytes_ = loader_->instance_size(); 360 streaming_ = !assume_fully_buffered_ && 361 (total_bytes_ == kPositionNotSpecified || !loader_->range_supported()); 362 363 media_log_->SetDoubleProperty("total_bytes", 364 static_cast<double>(total_bytes_)); 365 media_log_->SetBooleanProperty("streaming", streaming_); 366 } else { 367 loader_->Stop(); 368 } 369 370 // TODO(scherkus): we shouldn't have to lock to signal host(), see 371 // http://crbug.com/113712 for details. 372 base::AutoLock auto_lock(lock_); 373 if (stop_signal_received_) 374 return; 375 376 if (success) { 377 UpdateHostState_Locked(); 378 media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin()); 379 media_log_->SetBooleanProperty("passed_cors_access_check", 380 loader_->DidPassCORSAccessCheck()); 381 media_log_->SetBooleanProperty("range_header_supported", 382 loader_->range_supported()); 383 } 384 385 base::ResetAndReturn(&init_cb_).Run(success); 386 } 387 388 void BufferedDataSource::PartialReadStartCallback( 389 BufferedResourceLoader::Status status) { 390 DCHECK(render_loop_->BelongsToCurrentThread()); 391 DCHECK(loader_.get()); 392 393 if (status == BufferedResourceLoader::kOk) { 394 // Once the request has started successfully, we can proceed with 395 // reading from it. 396 ReadInternal(); 397 return; 398 } 399 400 // Stop the resource loader since we have received an error. 401 loader_->Stop(); 402 403 // TODO(scherkus): we shouldn't have to lock to signal host(), see 404 // http://crbug.com/113712 for details. 405 base::AutoLock auto_lock(lock_); 406 if (stop_signal_received_) 407 return; 408 ReadOperation::Run(read_op_.Pass(), kReadError); 409 } 410 411 void BufferedDataSource::ReadCallback( 412 BufferedResourceLoader::Status status, 413 int bytes_read) { 414 DCHECK(render_loop_->BelongsToCurrentThread()); 415 416 // TODO(scherkus): we shouldn't have to lock to signal host(), see 417 // http://crbug.com/113712 for details. 418 base::AutoLock auto_lock(lock_); 419 if (stop_signal_received_) 420 return; 421 422 if (status != BufferedResourceLoader::kOk) { 423 // Stop the resource load if it failed. 424 loader_->Stop(); 425 426 if (status == BufferedResourceLoader::kCacheMiss && 427 read_op_->retries() < kNumCacheMissRetries) { 428 read_op_->IncrementRetries(); 429 430 // Recreate a loader starting from where we last left off until the 431 // end of the resource. 432 loader_.reset(CreateResourceLoader( 433 read_op_->position(), kPositionNotSpecified)); 434 loader_->Start( 435 base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this_), 436 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, 437 weak_this_), 438 base::Bind(&BufferedDataSource::ProgressCallback, weak_this_), 439 frame_); 440 return; 441 } 442 443 ReadOperation::Run(read_op_.Pass(), kReadError); 444 return; 445 } 446 447 if (bytes_read > 0) { 448 memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read); 449 } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) { 450 // We've reached the end of the file and we didn't know the total size 451 // before. Update the total size so Read()s past the end of the file will 452 // fail like they would if we had known the file size at the beginning. 453 total_bytes_ = loader_->instance_size(); 454 455 if (host() && total_bytes_ != kPositionNotSpecified) { 456 host()->SetTotalBytes(total_bytes_); 457 host()->AddBufferedByteRange(loader_->first_byte_position(), 458 total_bytes_); 459 } 460 } 461 ReadOperation::Run(read_op_.Pass(), bytes_read); 462 } 463 464 void BufferedDataSource::LoadingStateChangedCallback( 465 BufferedResourceLoader::LoadingState state) { 466 DCHECK(render_loop_->BelongsToCurrentThread()); 467 468 if (assume_fully_buffered_) 469 return; 470 471 bool is_downloading_data; 472 switch (state) { 473 case BufferedResourceLoader::kLoading: 474 is_downloading_data = true; 475 break; 476 case BufferedResourceLoader::kLoadingDeferred: 477 case BufferedResourceLoader::kLoadingFinished: 478 is_downloading_data = false; 479 break; 480 481 // TODO(scherkus): we don't signal network activity changes when loads 482 // fail to preserve existing behaviour when deferring is toggled, however 483 // we should consider changing DownloadingCB to also propagate loading 484 // state. For example there isn't any signal today to notify the client that 485 // loading has failed (we only get errors on subsequent reads). 486 case BufferedResourceLoader::kLoadingFailed: 487 return; 488 } 489 490 downloading_cb_.Run(is_downloading_data); 491 } 492 493 void BufferedDataSource::ProgressCallback(int64 position) { 494 DCHECK(render_loop_->BelongsToCurrentThread()); 495 496 if (assume_fully_buffered_) 497 return; 498 499 // TODO(scherkus): we shouldn't have to lock to signal host(), see 500 // http://crbug.com/113712 for details. 501 base::AutoLock auto_lock(lock_); 502 if (stop_signal_received_) 503 return; 504 505 ReportOrQueueBufferedBytes(loader_->first_byte_position(), position); 506 } 507 508 void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) { 509 if (host()) 510 host()->AddBufferedByteRange(start, end); 511 else 512 queued_buffered_byte_ranges_.Add(start, end); 513 } 514 515 void BufferedDataSource::UpdateHostState_Locked() { 516 lock_.AssertAcquired(); 517 518 if (!host()) 519 return; 520 521 for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) { 522 host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i), 523 queued_buffered_byte_ranges_.end(i)); 524 } 525 queued_buffered_byte_ranges_.clear(); 526 527 if (total_bytes_ == kPositionNotSpecified) 528 return; 529 530 host()->SetTotalBytes(total_bytes_); 531 532 if (assume_fully_buffered_) 533 host()->AddBufferedByteRange(0, total_bytes_); 534 } 535 536 void BufferedDataSource::UpdateDeferStrategy(bool paused) { 537 // 200 responses end up not being reused to satisfy future range requests, 538 // and we don't want to get too far ahead of the read-head (and thus require 539 // a restart), so keep to the thresholds. 540 if (!loader_->range_supported()) { 541 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); 542 return; 543 } 544 545 // If the playback has started (at which point the preload value is ignored) 546 // and we're paused, then try to load as much as possible (the loader will 547 // fall back to kCapacityDefer if it knows the current response won't be 548 // useful from the cache in the future). 549 if (media_has_played_ && paused) { 550 loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer); 551 return; 552 } 553 554 // If media is currently playing or the page indicated preload=auto, 555 // use threshold strategy to enable/disable deferring when the buffer 556 // is full/depleted. 557 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); 558 } 559 560 } // namespace content 561