| 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/android/media_source_delegate.h" | |
| 6 | |
| 7 #include <limits> | |
| 8 #include <string> | |
| 9 #include <utility> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/callback_helpers.h" | |
| 13 #include "base/strings/string_number_conversions.h" | |
| 14 #include "base/threading/thread_task_runner_handle.h" | |
| 15 #include "content/renderer/media/android/renderer_demuxer_android.h" | |
| 16 #include "media/base/android/demuxer_stream_player_params.h" | |
| 17 #include "media/base/bind_to_current_loop.h" | |
| 18 #include "media/base/demuxer_stream.h" | |
| 19 #include "media/base/media_log.h" | |
| 20 #include "media/base/timestamp_constants.h" | |
| 21 #include "media/blink/webmediaplayer_util.h" | |
| 22 #include "media/blink/webmediasource_impl.h" | |
| 23 #include "media/filters/chunk_demuxer.h" | |
| 24 #include "media/filters/decrypting_demuxer_stream.h" | |
| 25 #include "third_party/WebKit/public/platform/WebString.h" | |
| 26 #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" | |
| 27 | |
| 28 using media::DemuxerStream; | |
| 29 using media::DemuxerConfigs; | |
| 30 using media::DemuxerData; | |
| 31 using blink::WebMediaPlayer; | |
| 32 using blink::WebString; | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 // The size of the access unit to transfer in an IPC in case of MediaSource. | |
| 37 // 4: approximately 64ms of content in 60 fps movies. | |
| 38 const size_t kAccessUnitSizeForMediaSource = 4; | |
| 39 | |
| 40 const uint8_t kVorbisPadding[] = {0xff, 0xff, 0xff, 0xff}; | |
| 41 | |
| 42 } // namespace | |
| 43 | |
| 44 namespace content { | |
| 45 | |
| 46 MediaSourceDelegate::MediaSourceDelegate( | |
| 47 RendererDemuxerAndroid* demuxer_client, | |
| 48 int demuxer_client_id, | |
| 49 const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, | |
| 50 const scoped_refptr<media::MediaLog> media_log) | |
| 51 : demuxer_client_(demuxer_client), | |
| 52 demuxer_client_id_(demuxer_client_id), | |
| 53 media_log_(media_log), | |
| 54 is_demuxer_ready_(false), | |
| 55 audio_stream_(NULL), | |
| 56 video_stream_(NULL), | |
| 57 seeking_(false), | |
| 58 is_video_encrypted_(false), | |
| 59 doing_browser_seek_(false), | |
| 60 browser_seek_time_(media::kNoTimestamp), | |
| 61 expecting_regular_seek_(false), | |
| 62 access_unit_size_(0), | |
| 63 main_task_runner_(base::ThreadTaskRunnerHandle::Get()), | |
| 64 media_task_runner_(media_task_runner), | |
| 65 main_weak_factory_(this), | |
| 66 media_weak_factory_(this) { | |
| 67 main_weak_this_ = main_weak_factory_.GetWeakPtr(); | |
| 68 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 69 } | |
| 70 | |
| 71 MediaSourceDelegate::~MediaSourceDelegate() { | |
| 72 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 73 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 74 DCHECK(!chunk_demuxer_); | |
| 75 DCHECK(!demuxer_client_); | |
| 76 DCHECK(!audio_decrypting_demuxer_stream_); | |
| 77 DCHECK(!video_decrypting_demuxer_stream_); | |
| 78 DCHECK(!audio_stream_); | |
| 79 DCHECK(!video_stream_); | |
| 80 } | |
| 81 | |
| 82 void MediaSourceDelegate::Stop(const base::Closure& stop_cb) { | |
| 83 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 84 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 85 | |
| 86 if (!chunk_demuxer_) { | |
| 87 DCHECK(!demuxer_client_); | |
| 88 return; | |
| 89 } | |
| 90 | |
| 91 duration_change_cb_.Reset(); | |
| 92 update_network_state_cb_.Reset(); | |
| 93 media_source_opened_cb_.Reset(); | |
| 94 | |
| 95 main_weak_factory_.InvalidateWeakPtrs(); | |
| 96 DCHECK(!main_weak_factory_.HasWeakPtrs()); | |
| 97 | |
| 98 chunk_demuxer_->Shutdown(); | |
| 99 | |
| 100 // Continue to stop objects on the media thread. | |
| 101 media_task_runner_->PostTask( | |
| 102 FROM_HERE, | |
| 103 base::Bind( | |
| 104 &MediaSourceDelegate::StopDemuxer, base::Unretained(this), stop_cb)); | |
| 105 } | |
| 106 | |
| 107 bool MediaSourceDelegate::IsVideoEncrypted() { | |
| 108 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 109 base::AutoLock auto_lock(is_video_encrypted_lock_); | |
| 110 return is_video_encrypted_; | |
| 111 } | |
| 112 | |
| 113 base::Time MediaSourceDelegate::GetTimelineOffset() const { | |
| 114 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 115 if (!chunk_demuxer_) | |
| 116 return base::Time(); | |
| 117 | |
| 118 return chunk_demuxer_->GetTimelineOffset(); | |
| 119 } | |
| 120 | |
| 121 void MediaSourceDelegate::StopDemuxer(const base::Closure& stop_cb) { | |
| 122 DVLOG(2) << __FUNCTION__; | |
| 123 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 124 DCHECK(chunk_demuxer_); | |
| 125 | |
| 126 demuxer_client_->RemoveDelegate(demuxer_client_id_); | |
| 127 demuxer_client_ = NULL; | |
| 128 | |
| 129 audio_stream_ = NULL; | |
| 130 video_stream_ = NULL; | |
| 131 // TODO(xhwang): Figure out if we need to Reset the DDSs after Seeking or | |
| 132 // before destroying them. | |
| 133 audio_decrypting_demuxer_stream_.reset(); | |
| 134 video_decrypting_demuxer_stream_.reset(); | |
| 135 | |
| 136 media_weak_factory_.InvalidateWeakPtrs(); | |
| 137 DCHECK(!media_weak_factory_.HasWeakPtrs()); | |
| 138 | |
| 139 chunk_demuxer_->Stop(); | |
| 140 chunk_demuxer_.reset(); | |
| 141 | |
| 142 // |this| may be destroyed at this point in time as a result of running | |
| 143 // |stop_cb|. | |
| 144 stop_cb.Run(); | |
| 145 } | |
| 146 | |
| 147 void MediaSourceDelegate::InitializeMediaSource( | |
| 148 const MediaSourceOpenedCB& media_source_opened_cb, | |
| 149 const media::Demuxer::EncryptedMediaInitDataCB& | |
| 150 encrypted_media_init_data_cb, | |
| 151 const SetCdmReadyCB& set_cdm_ready_cb, | |
| 152 const UpdateNetworkStateCB& update_network_state_cb, | |
| 153 const DurationChangeCB& duration_change_cb, | |
| 154 const base::Closure& waiting_for_decryption_key_cb) { | |
| 155 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 156 DCHECK(!media_source_opened_cb.is_null()); | |
| 157 DCHECK(!encrypted_media_init_data_cb.is_null()); | |
| 158 DCHECK(!set_cdm_ready_cb.is_null()); | |
| 159 DCHECK(!update_network_state_cb.is_null()); | |
| 160 DCHECK(!duration_change_cb.is_null()); | |
| 161 DCHECK(!waiting_for_decryption_key_cb.is_null()); | |
| 162 | |
| 163 media_source_opened_cb_ = media_source_opened_cb; | |
| 164 encrypted_media_init_data_cb_ = encrypted_media_init_data_cb; | |
| 165 set_cdm_ready_cb_ = media::BindToCurrentLoop(set_cdm_ready_cb); | |
| 166 update_network_state_cb_ = media::BindToCurrentLoop(update_network_state_cb); | |
| 167 duration_change_cb_ = duration_change_cb; | |
| 168 waiting_for_decryption_key_cb_ = | |
| 169 media::BindToCurrentLoop(waiting_for_decryption_key_cb); | |
| 170 access_unit_size_ = kAccessUnitSizeForMediaSource; | |
| 171 | |
| 172 chunk_demuxer_.reset(new media::ChunkDemuxer( | |
| 173 media::BindToCurrentLoop( | |
| 174 base::Bind(&MediaSourceDelegate::OnDemuxerOpened, main_weak_this_)), | |
| 175 media::BindToCurrentLoop(base::Bind( | |
| 176 &MediaSourceDelegate::OnEncryptedMediaInitData, main_weak_this_)), | |
| 177 media_log_, false)); | |
| 178 | |
| 179 // |this| will be retained until StopDemuxer() is posted, so Unretained() is | |
| 180 // safe here. | |
| 181 media_task_runner_->PostTask(FROM_HERE, | |
| 182 base::Bind(&MediaSourceDelegate::InitializeDemuxer, | |
| 183 base::Unretained(this))); | |
| 184 } | |
| 185 | |
| 186 void MediaSourceDelegate::InitializeDemuxer() { | |
| 187 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 188 demuxer_client_->AddDelegate(demuxer_client_id_, this); | |
| 189 chunk_demuxer_->Initialize(this, | |
| 190 base::Bind(&MediaSourceDelegate::OnDemuxerInitDone, | |
| 191 media_weak_factory_.GetWeakPtr()), | |
| 192 false); | |
| 193 } | |
| 194 | |
| 195 blink::WebTimeRanges MediaSourceDelegate::Buffered() const { | |
| 196 return media::ConvertToWebTimeRanges(buffered_time_ranges_); | |
| 197 } | |
| 198 | |
| 199 size_t MediaSourceDelegate::DecodedFrameCount() const { | |
| 200 return statistics_.video_frames_decoded; | |
| 201 } | |
| 202 | |
| 203 size_t MediaSourceDelegate::DroppedFrameCount() const { | |
| 204 return statistics_.video_frames_dropped; | |
| 205 } | |
| 206 | |
| 207 size_t MediaSourceDelegate::AudioDecodedByteCount() const { | |
| 208 return statistics_.audio_bytes_decoded; | |
| 209 } | |
| 210 | |
| 211 size_t MediaSourceDelegate::VideoDecodedByteCount() const { | |
| 212 return statistics_.video_bytes_decoded; | |
| 213 } | |
| 214 | |
| 215 void MediaSourceDelegate::CancelPendingSeek(const base::TimeDelta& seek_time) { | |
| 216 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 217 DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ") : " | |
| 218 << demuxer_client_id_; | |
| 219 | |
| 220 if (!chunk_demuxer_) | |
| 221 return; | |
| 222 | |
| 223 { | |
| 224 // Remember to trivially finish any newly arriving browser seek requests | |
| 225 // that may arrive prior to the next regular seek request. | |
| 226 base::AutoLock auto_lock(seeking_lock_); | |
| 227 expecting_regular_seek_ = true; | |
| 228 } | |
| 229 | |
| 230 // Cancel any previously expected or in-progress regular or browser seek. | |
| 231 // It is possible that we have just finished the seek, but caller does | |
| 232 // not know this yet. It is still safe to cancel in this case because the | |
| 233 // caller will always call StartWaitingForSeek() when it is notified of | |
| 234 // the finished seek. | |
| 235 chunk_demuxer_->CancelPendingSeek(seek_time); | |
| 236 } | |
| 237 | |
| 238 void MediaSourceDelegate::StartWaitingForSeek( | |
| 239 const base::TimeDelta& seek_time) { | |
| 240 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 241 DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ") : " | |
| 242 << demuxer_client_id_; | |
| 243 | |
| 244 if (!chunk_demuxer_) | |
| 245 return; | |
| 246 | |
| 247 bool cancel_browser_seek = false; | |
| 248 { | |
| 249 // Remember to trivially finish any newly arriving browser seek requests | |
| 250 // that may arrive prior to the next regular seek request. | |
| 251 base::AutoLock auto_lock(seeking_lock_); | |
| 252 expecting_regular_seek_ = true; | |
| 253 | |
| 254 // Remember to cancel any in-progress browser seek. | |
| 255 if (seeking_) { | |
| 256 DCHECK(doing_browser_seek_); | |
| 257 cancel_browser_seek = true; | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 if (cancel_browser_seek) | |
| 262 chunk_demuxer_->CancelPendingSeek(seek_time); | |
| 263 chunk_demuxer_->StartWaitingForSeek(seek_time); | |
| 264 } | |
| 265 | |
| 266 void MediaSourceDelegate::Seek( | |
| 267 const base::TimeDelta& seek_time, bool is_browser_seek) { | |
| 268 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 269 DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ", " | |
| 270 << (is_browser_seek ? "browser seek" : "regular seek") << ") : " | |
| 271 << demuxer_client_id_; | |
| 272 | |
| 273 base::TimeDelta internal_seek_time = seek_time; | |
| 274 { | |
| 275 base::AutoLock auto_lock(seeking_lock_); | |
| 276 DCHECK(!seeking_); | |
| 277 seeking_ = true; | |
| 278 doing_browser_seek_ = is_browser_seek; | |
| 279 | |
| 280 if (doing_browser_seek_ && (!chunk_demuxer_ || expecting_regular_seek_)) { | |
| 281 // Trivially finish the browser seek without actually doing it. Reads will | |
| 282 // continue to be |kAborted| until the next regular seek is done. Browser | |
| 283 // seeking is not supported unless using a ChunkDemuxer; browser seeks are | |
| 284 // trivially finished if |chunk_demuxer_| is NULL. | |
| 285 seeking_ = false; | |
| 286 doing_browser_seek_ = false; | |
| 287 demuxer_client_->DemuxerSeekDone(demuxer_client_id_, seek_time); | |
| 288 return; | |
| 289 } | |
| 290 | |
| 291 if (doing_browser_seek_) { | |
| 292 internal_seek_time = FindBufferedBrowserSeekTime_Locked(seek_time); | |
| 293 browser_seek_time_ = internal_seek_time; | |
| 294 } else { | |
| 295 expecting_regular_seek_ = false; | |
| 296 browser_seek_time_ = media::kNoTimestamp; | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 // Prepare |chunk_demuxer_| for browser seek. | |
| 301 if (is_browser_seek) { | |
| 302 chunk_demuxer_->CancelPendingSeek(internal_seek_time); | |
| 303 chunk_demuxer_->StartWaitingForSeek(internal_seek_time); | |
| 304 } | |
| 305 | |
| 306 SeekInternal(internal_seek_time); | |
| 307 } | |
| 308 | |
| 309 void MediaSourceDelegate::SeekInternal(const base::TimeDelta& seek_time) { | |
| 310 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 311 DCHECK(IsSeeking()); | |
| 312 chunk_demuxer_->Seek(seek_time, base::Bind( | |
| 313 &MediaSourceDelegate::OnDemuxerSeekDone, | |
| 314 media_weak_factory_.GetWeakPtr())); | |
| 315 } | |
| 316 | |
| 317 void MediaSourceDelegate::OnBufferedTimeRangesChanged( | |
| 318 const media::Ranges<base::TimeDelta>& ranges) { | |
| 319 buffered_time_ranges_ = ranges; | |
| 320 } | |
| 321 | |
| 322 void MediaSourceDelegate::SetDuration(base::TimeDelta duration) { | |
| 323 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 324 DVLOG(1) << __FUNCTION__ << "(" << duration.InSecondsF() << ") : " | |
| 325 << demuxer_client_id_; | |
| 326 | |
| 327 // Force duration change notification to be async to avoid reentrancy into | |
| 328 // ChunkDemxuer. | |
| 329 main_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 330 &MediaSourceDelegate::OnDurationChanged, main_weak_this_, duration)); | |
| 331 } | |
| 332 | |
| 333 void MediaSourceDelegate::OnDurationChanged(const base::TimeDelta& duration) { | |
| 334 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 335 if (demuxer_client_) | |
| 336 demuxer_client_->DurationChanged(demuxer_client_id_, duration); | |
| 337 if (!duration_change_cb_.is_null()) | |
| 338 duration_change_cb_.Run(duration); | |
| 339 } | |
| 340 | |
| 341 void MediaSourceDelegate::OnReadFromDemuxer(media::DemuxerStream::Type type) { | |
| 342 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 343 DVLOG(1) << __FUNCTION__ << "(" << type << ") : " << demuxer_client_id_; | |
| 344 if (IsSeeking()) | |
| 345 return; // Drop the request during seeking. | |
| 346 | |
| 347 DCHECK(type == DemuxerStream::AUDIO || type == DemuxerStream::VIDEO); | |
| 348 // The access unit size should have been initialized properly at this stage. | |
| 349 DCHECK_GT(access_unit_size_, 0u); | |
| 350 std::unique_ptr<DemuxerData> data(new DemuxerData()); | |
| 351 data->type = type; | |
| 352 data->access_units.resize(access_unit_size_); | |
| 353 ReadFromDemuxerStream(type, std::move(data), 0); | |
| 354 } | |
| 355 | |
| 356 void MediaSourceDelegate::ReadFromDemuxerStream( | |
| 357 media::DemuxerStream::Type type, | |
| 358 std::unique_ptr<DemuxerData> data, | |
| 359 size_t index) { | |
| 360 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 361 // DemuxerStream::Read() always returns the read callback asynchronously. | |
| 362 DemuxerStream* stream = | |
| 363 (type == DemuxerStream::AUDIO) ? audio_stream_ : video_stream_; | |
| 364 stream->Read(base::Bind( | |
| 365 &MediaSourceDelegate::OnBufferReady, | |
| 366 media_weak_factory_.GetWeakPtr(), type, base::Passed(&data), index)); | |
| 367 } | |
| 368 | |
| 369 void MediaSourceDelegate::OnBufferReady( | |
| 370 media::DemuxerStream::Type type, | |
| 371 std::unique_ptr<DemuxerData> data, | |
| 372 size_t index, | |
| 373 DemuxerStream::Status status, | |
| 374 const scoped_refptr<media::DecoderBuffer>& buffer) { | |
| 375 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 376 DVLOG(1) << __FUNCTION__ << "(" << index << ", " << status << ", " | |
| 377 << ((!buffer.get() || buffer->end_of_stream()) | |
| 378 ? -1 | |
| 379 : buffer->timestamp().InMilliseconds()) | |
| 380 << ") : " << demuxer_client_id_; | |
| 381 DCHECK(chunk_demuxer_); | |
| 382 | |
| 383 // No new OnReadFromDemuxer() will be called during seeking. So this callback | |
| 384 // must be from previous OnReadFromDemuxer() call and should be ignored. | |
| 385 if (IsSeeking()) { | |
| 386 DVLOG(1) << __FUNCTION__ << ": Ignore previous read during seeking."; | |
| 387 return; | |
| 388 } | |
| 389 | |
| 390 bool is_audio = (type == DemuxerStream::AUDIO); | |
| 391 if (status != DemuxerStream::kAborted && | |
| 392 index >= data->access_units.size()) { | |
| 393 LOG(ERROR) << "The internal state inconsistency onBufferReady: " | |
| 394 << (is_audio ? "Audio" : "Video") << ", index " << index | |
| 395 << ", size " << data->access_units.size() | |
| 396 << ", status " << static_cast<int>(status); | |
| 397 NOTREACHED(); | |
| 398 return; | |
| 399 } | |
| 400 | |
| 401 switch (status) { | |
| 402 case DemuxerStream::kAborted: | |
| 403 DVLOG(1) << __FUNCTION__ << " : Aborted"; | |
| 404 data->access_units[index].status = status; | |
| 405 data->access_units.resize(index + 1); | |
| 406 break; | |
| 407 | |
| 408 case DemuxerStream::kConfigChanged: | |
| 409 CHECK((is_audio && audio_stream_) || (!is_audio && video_stream_)); | |
| 410 data->demuxer_configs.resize(1); | |
| 411 CHECK(GetDemuxerConfigFromStream(&data->demuxer_configs[0], is_audio)); | |
| 412 if (!is_audio) { | |
| 413 gfx::Size size = data->demuxer_configs[0].video_size; | |
| 414 DVLOG(1) << "Video config is changed: " << size.width() << "x" | |
| 415 << size.height(); | |
| 416 } | |
| 417 data->access_units[index].status = status; | |
| 418 data->access_units.resize(index + 1); | |
| 419 break; | |
| 420 | |
| 421 case DemuxerStream::kOk: | |
| 422 data->access_units[index].status = status; | |
| 423 if (buffer->end_of_stream()) { | |
| 424 data->access_units[index].is_end_of_stream = true; | |
| 425 data->access_units.resize(index + 1); | |
| 426 break; | |
| 427 } | |
| 428 data->access_units[index].is_key_frame = buffer->is_key_frame(); | |
| 429 // TODO(ycheo): We assume that the inputed stream will be decoded | |
| 430 // right away. | |
| 431 // Need to implement this properly using MediaPlayer.OnInfoListener. | |
| 432 if (is_audio) { | |
| 433 statistics_.audio_bytes_decoded += buffer->data_size(); | |
| 434 } else { | |
| 435 statistics_.video_bytes_decoded += buffer->data_size(); | |
| 436 statistics_.video_frames_decoded++; | |
| 437 } | |
| 438 data->access_units[index].timestamp = buffer->timestamp(); | |
| 439 | |
| 440 data->access_units[index].data.assign( | |
| 441 buffer->data(), buffer->data() + buffer->data_size()); | |
| 442 // Vorbis needs 4 extra bytes padding on Android. Check | |
| 443 // NuMediaExtractor.cpp in Android source code. | |
| 444 if (is_audio && media::kCodecVorbis == | |
| 445 audio_stream_->audio_decoder_config().codec()) { | |
| 446 data->access_units[index].data.insert( | |
| 447 data->access_units[index].data.end(), kVorbisPadding, | |
| 448 kVorbisPadding + 4); | |
| 449 } | |
| 450 if (buffer->decrypt_config()) { | |
| 451 data->access_units[index].key_id = std::vector<char>( | |
| 452 buffer->decrypt_config()->key_id().begin(), | |
| 453 buffer->decrypt_config()->key_id().end()); | |
| 454 data->access_units[index].iv = std::vector<char>( | |
| 455 buffer->decrypt_config()->iv().begin(), | |
| 456 buffer->decrypt_config()->iv().end()); | |
| 457 data->access_units[index].subsamples = | |
| 458 buffer->decrypt_config()->subsamples(); | |
| 459 } | |
| 460 if (++index < data->access_units.size()) { | |
| 461 ReadFromDemuxerStream(type, std::move(data), index); | |
| 462 return; | |
| 463 } | |
| 464 break; | |
| 465 | |
| 466 default: | |
| 467 NOTREACHED(); | |
| 468 } | |
| 469 | |
| 470 if (!IsSeeking() && demuxer_client_) | |
| 471 demuxer_client_->ReadFromDemuxerAck(demuxer_client_id_, *data); | |
| 472 } | |
| 473 | |
| 474 void MediaSourceDelegate::OnDemuxerError(media::PipelineStatus status) { | |
| 475 DVLOG(1) << __FUNCTION__ << "(" << status << ") : " << demuxer_client_id_; | |
| 476 // |update_network_state_cb_| is bound to the main thread. | |
| 477 if (status != media::PIPELINE_OK && !update_network_state_cb_.is_null()) | |
| 478 update_network_state_cb_.Run(PipelineErrorToNetworkState(status)); | |
| 479 } | |
| 480 | |
| 481 void MediaSourceDelegate::AddTextStream( | |
| 482 media::DemuxerStream* /* text_stream */ , | |
| 483 const media::TextTrackConfig& /* config */ ) { | |
| 484 // TODO(matthewjheaney): add text stream (http://crbug/322115). | |
| 485 NOTIMPLEMENTED(); | |
| 486 } | |
| 487 | |
| 488 void MediaSourceDelegate::RemoveTextStream( | |
| 489 media::DemuxerStream* /* text_stream */ ) { | |
| 490 // TODO(matthewjheaney): remove text stream (http://crbug/322115). | |
| 491 NOTIMPLEMENTED(); | |
| 492 } | |
| 493 | |
| 494 void MediaSourceDelegate::OnDemuxerInitDone(media::PipelineStatus status) { | |
| 495 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 496 DVLOG(1) << __FUNCTION__ << "(" << status << ") : " << demuxer_client_id_; | |
| 497 DCHECK(chunk_demuxer_); | |
| 498 | |
| 499 if (status != media::PIPELINE_OK) { | |
| 500 OnDemuxerError(status); | |
| 501 return; | |
| 502 } | |
| 503 | |
| 504 audio_stream_ = chunk_demuxer_->GetStream(DemuxerStream::AUDIO); | |
| 505 video_stream_ = chunk_demuxer_->GetStream(DemuxerStream::VIDEO); | |
| 506 DCHECK(audio_stream_ || video_stream_); | |
| 507 | |
| 508 if (HasEncryptedStream()) { | |
| 509 set_cdm_ready_cb_.Run(BindToCurrentLoop(base::Bind( | |
| 510 &MediaSourceDelegate::SetCdm, media_weak_factory_.GetWeakPtr()))); | |
| 511 return; | |
| 512 } | |
| 513 | |
| 514 // Notify demuxer ready when both streams are not encrypted. | |
| 515 NotifyDemuxerReady(false); | |
| 516 } | |
| 517 | |
| 518 bool MediaSourceDelegate::HasEncryptedStream() { | |
| 519 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 520 DCHECK(audio_stream_ || video_stream_); | |
| 521 | |
| 522 return (audio_stream_ && | |
| 523 audio_stream_->audio_decoder_config().is_encrypted()) || | |
| 524 (video_stream_ && | |
| 525 video_stream_->video_decoder_config().is_encrypted()); | |
| 526 } | |
| 527 | |
| 528 void MediaSourceDelegate::SetCdm(media::CdmContext* cdm_context, | |
| 529 const media::CdmAttachedCB& cdm_attached_cb) { | |
| 530 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 531 DCHECK(cdm_context); | |
| 532 DCHECK(!cdm_attached_cb.is_null()); | |
| 533 DCHECK(!is_demuxer_ready_); | |
| 534 DCHECK(HasEncryptedStream()); | |
| 535 | |
| 536 cdm_context_ = cdm_context; | |
| 537 pending_cdm_attached_cb_ = cdm_attached_cb; | |
| 538 | |
| 539 if (audio_stream_ && audio_stream_->audio_decoder_config().is_encrypted()) { | |
| 540 InitAudioDecryptingDemuxerStream(); | |
| 541 // InitVideoDecryptingDemuxerStream() will be called in | |
| 542 // OnAudioDecryptingDemuxerStreamInitDone(). | |
| 543 return; | |
| 544 } | |
| 545 | |
| 546 if (video_stream_ && video_stream_->video_decoder_config().is_encrypted()) { | |
| 547 InitVideoDecryptingDemuxerStream(); | |
| 548 return; | |
| 549 } | |
| 550 | |
| 551 NOTREACHED() << "No encrytped stream."; | |
| 552 } | |
| 553 | |
| 554 void MediaSourceDelegate::InitAudioDecryptingDemuxerStream() { | |
| 555 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 556 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 557 DCHECK(cdm_context_); | |
| 558 audio_decrypting_demuxer_stream_.reset(new media::DecryptingDemuxerStream( | |
| 559 media_task_runner_, media_log_, waiting_for_decryption_key_cb_)); | |
| 560 audio_decrypting_demuxer_stream_->Initialize( | |
| 561 audio_stream_, cdm_context_, | |
| 562 base::Bind(&MediaSourceDelegate::OnAudioDecryptingDemuxerStreamInitDone, | |
| 563 media_weak_factory_.GetWeakPtr())); | |
| 564 } | |
| 565 | |
| 566 void MediaSourceDelegate::InitVideoDecryptingDemuxerStream() { | |
| 567 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 568 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 569 DCHECK(cdm_context_); | |
| 570 | |
| 571 video_decrypting_demuxer_stream_.reset(new media::DecryptingDemuxerStream( | |
| 572 media_task_runner_, media_log_, waiting_for_decryption_key_cb_)); | |
| 573 video_decrypting_demuxer_stream_->Initialize( | |
| 574 video_stream_, cdm_context_, | |
| 575 base::Bind(&MediaSourceDelegate::OnVideoDecryptingDemuxerStreamInitDone, | |
| 576 media_weak_factory_.GetWeakPtr())); | |
| 577 } | |
| 578 | |
| 579 void MediaSourceDelegate::OnAudioDecryptingDemuxerStreamInitDone( | |
| 580 media::PipelineStatus status) { | |
| 581 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 582 DVLOG(1) << __FUNCTION__ << "(" << status << ") : " << demuxer_client_id_; | |
| 583 DCHECK(chunk_demuxer_); | |
| 584 | |
| 585 if (status != media::PIPELINE_OK) { | |
| 586 audio_decrypting_demuxer_stream_.reset(); | |
| 587 // Different CDMs are supported differently. For CDMs that support a | |
| 588 // Decryptor, we'll try to use DecryptingDemuxerStream in the render side. | |
| 589 // Otherwise, we'll try to use the CDMs in the browser side. Therefore, if | |
| 590 // DecryptingDemuxerStream initialization failed, it's still possible that | |
| 591 // we can handle the audio with a CDM in the browser. Declare demuxer ready | |
| 592 // now to try that path. Note there's no need to try DecryptingDemuxerStream | |
| 593 // for video here since it is impossible to handle audio in the browser and | |
| 594 // handle video in the render process. | |
| 595 NotifyDemuxerReady(false); | |
| 596 return; | |
| 597 } | |
| 598 | |
| 599 audio_stream_ = audio_decrypting_demuxer_stream_.get(); | |
| 600 | |
| 601 if (video_stream_ && video_stream_->video_decoder_config().is_encrypted()) { | |
| 602 InitVideoDecryptingDemuxerStream(); | |
| 603 return; | |
| 604 } | |
| 605 | |
| 606 // Try to notify demuxer ready when audio DDS initialization finished and | |
| 607 // video is not encrypted. | |
| 608 NotifyDemuxerReady(true); | |
| 609 } | |
| 610 | |
| 611 void MediaSourceDelegate::OnVideoDecryptingDemuxerStreamInitDone( | |
| 612 media::PipelineStatus status) { | |
| 613 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 614 DVLOG(1) << __FUNCTION__ << "(" << status << ") : " << demuxer_client_id_; | |
| 615 DCHECK(chunk_demuxer_); | |
| 616 | |
| 617 bool success = status == media::PIPELINE_OK; | |
| 618 | |
| 619 if (!success) | |
| 620 video_decrypting_demuxer_stream_.reset(); | |
| 621 else | |
| 622 video_stream_ = video_decrypting_demuxer_stream_.get(); | |
| 623 | |
| 624 // Try to notify demuxer ready when video DDS initialization finished. | |
| 625 NotifyDemuxerReady(success); | |
| 626 } | |
| 627 | |
| 628 void MediaSourceDelegate::OnDemuxerSeekDone(media::PipelineStatus status) { | |
| 629 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 630 DVLOG(1) << __FUNCTION__ << "(" << status << ") : " << demuxer_client_id_; | |
| 631 DCHECK(IsSeeking()); | |
| 632 | |
| 633 if (status != media::PIPELINE_OK) { | |
| 634 OnDemuxerError(status); | |
| 635 return; | |
| 636 } | |
| 637 | |
| 638 ResetAudioDecryptingDemuxerStream(); | |
| 639 } | |
| 640 | |
| 641 void MediaSourceDelegate::ResetAudioDecryptingDemuxerStream() { | |
| 642 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 643 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 644 if (audio_decrypting_demuxer_stream_) { | |
| 645 audio_decrypting_demuxer_stream_->Reset( | |
| 646 base::Bind(&MediaSourceDelegate::ResetVideoDecryptingDemuxerStream, | |
| 647 media_weak_factory_.GetWeakPtr())); | |
| 648 return; | |
| 649 } | |
| 650 | |
| 651 ResetVideoDecryptingDemuxerStream(); | |
| 652 } | |
| 653 | |
| 654 void MediaSourceDelegate::ResetVideoDecryptingDemuxerStream() { | |
| 655 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 656 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 657 if (video_decrypting_demuxer_stream_) { | |
| 658 video_decrypting_demuxer_stream_->Reset(base::Bind( | |
| 659 &MediaSourceDelegate::FinishResettingDecryptingDemuxerStreams, | |
| 660 media_weak_factory_.GetWeakPtr())); | |
| 661 return; | |
| 662 } | |
| 663 | |
| 664 FinishResettingDecryptingDemuxerStreams(); | |
| 665 } | |
| 666 | |
| 667 void MediaSourceDelegate::FinishResettingDecryptingDemuxerStreams() { | |
| 668 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 669 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_; | |
| 670 | |
| 671 base::AutoLock auto_lock(seeking_lock_); | |
| 672 DCHECK(seeking_); | |
| 673 seeking_ = false; | |
| 674 doing_browser_seek_ = false; | |
| 675 demuxer_client_->DemuxerSeekDone(demuxer_client_id_, browser_seek_time_); | |
| 676 } | |
| 677 | |
| 678 void MediaSourceDelegate::NotifyDemuxerReady(bool is_cdm_attached) { | |
| 679 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 680 DVLOG(1) << __FUNCTION__ << " : " << demuxer_client_id_ | |
| 681 << ", is_cdm_attached: " << is_cdm_attached; | |
| 682 DCHECK(!is_demuxer_ready_); | |
| 683 | |
| 684 is_demuxer_ready_ = true; | |
| 685 | |
| 686 if (!pending_cdm_attached_cb_.is_null()) | |
| 687 base::ResetAndReturn(&pending_cdm_attached_cb_).Run(is_cdm_attached); | |
| 688 | |
| 689 std::unique_ptr<DemuxerConfigs> configs(new DemuxerConfigs()); | |
| 690 GetDemuxerConfigFromStream(configs.get(), true); | |
| 691 GetDemuxerConfigFromStream(configs.get(), false); | |
| 692 configs->duration = GetDuration(); | |
| 693 | |
| 694 if (demuxer_client_) | |
| 695 demuxer_client_->DemuxerReady(demuxer_client_id_, *configs); | |
| 696 | |
| 697 base::AutoLock auto_lock(is_video_encrypted_lock_); | |
| 698 is_video_encrypted_ = configs->is_video_encrypted; | |
| 699 } | |
| 700 | |
| 701 base::TimeDelta MediaSourceDelegate::GetDuration() const { | |
| 702 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 703 if (!chunk_demuxer_) | |
| 704 return media::kNoTimestamp; | |
| 705 | |
| 706 double duration = chunk_demuxer_->GetDuration(); | |
| 707 if (duration == std::numeric_limits<double>::infinity()) | |
| 708 return media::kInfiniteDuration; | |
| 709 | |
| 710 return base::TimeDelta::FromSecondsD(duration); | |
| 711 } | |
| 712 | |
| 713 void MediaSourceDelegate::OnDemuxerOpened() { | |
| 714 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 715 if (media_source_opened_cb_.is_null()) | |
| 716 return; | |
| 717 | |
| 718 media_source_opened_cb_.Run( | |
| 719 new media::WebMediaSourceImpl(chunk_demuxer_.get(), media_log_)); | |
| 720 } | |
| 721 | |
| 722 void MediaSourceDelegate::OnEncryptedMediaInitData( | |
| 723 media::EmeInitDataType init_data_type, | |
| 724 const std::vector<uint8_t>& init_data) { | |
| 725 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 726 if (encrypted_media_init_data_cb_.is_null()) | |
| 727 return; | |
| 728 | |
| 729 encrypted_media_init_data_cb_.Run(init_data_type, init_data); | |
| 730 } | |
| 731 | |
| 732 bool MediaSourceDelegate::IsSeeking() const { | |
| 733 base::AutoLock auto_lock(seeking_lock_); | |
| 734 return seeking_; | |
| 735 } | |
| 736 | |
| 737 base::TimeDelta MediaSourceDelegate::FindBufferedBrowserSeekTime_Locked( | |
| 738 const base::TimeDelta& seek_time) const { | |
| 739 seeking_lock_.AssertAcquired(); | |
| 740 DCHECK(seeking_); | |
| 741 DCHECK(doing_browser_seek_); | |
| 742 DCHECK(chunk_demuxer_) << "Browser seek requested, but no chunk demuxer"; | |
| 743 | |
| 744 media::Ranges<base::TimeDelta> buffered = | |
| 745 chunk_demuxer_->GetBufferedRanges(); | |
| 746 | |
| 747 for (size_t i = 0; i < buffered.size(); ++i) { | |
| 748 base::TimeDelta range_start = buffered.start(i); | |
| 749 base::TimeDelta range_end = buffered.end(i); | |
| 750 if (range_start <= seek_time) { | |
| 751 if (range_end >= seek_time) | |
| 752 return seek_time; | |
| 753 continue; | |
| 754 } | |
| 755 | |
| 756 // If the start of the next buffered range after |seek_time| is too far | |
| 757 // into the future, do not jump forward. | |
| 758 if ((range_start - seek_time) > base::TimeDelta::FromMilliseconds(100)) | |
| 759 break; | |
| 760 | |
| 761 // TODO(wolenetz): Remove possibility that this browser seek jumps | |
| 762 // into future when the requested range is unbuffered but there is some | |
| 763 // other buffered range after it. See http://crbug.com/304234. | |
| 764 return range_start; | |
| 765 } | |
| 766 | |
| 767 // We found no range containing |seek_time| or beginning shortly after | |
| 768 // |seek_time|. While possible that such data at and beyond the player's | |
| 769 // current time have been garbage collected or removed by the web app, this is | |
| 770 // unlikely. This may cause unexpected playback stall due to seek pending an | |
| 771 // append for a GOP prior to the last GOP demuxed. | |
| 772 // TODO(wolenetz): Remove the possibility for this seek to cause unexpected | |
| 773 // player stall by replaying cached data since last keyframe in browser player | |
| 774 // rather than issuing browser seek. See http://crbug.com/304234. | |
| 775 return seek_time; | |
| 776 } | |
| 777 | |
| 778 bool MediaSourceDelegate::GetDemuxerConfigFromStream( | |
| 779 media::DemuxerConfigs* configs, bool is_audio) { | |
| 780 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
| 781 if (!is_demuxer_ready_) | |
| 782 return false; | |
| 783 if (is_audio && audio_stream_) { | |
| 784 media::AudioDecoderConfig config = audio_stream_->audio_decoder_config(); | |
| 785 configs->audio_codec = config.codec(); | |
| 786 configs->audio_channels = | |
| 787 media::ChannelLayoutToChannelCount(config.channel_layout()); | |
| 788 configs->audio_sampling_rate = config.samples_per_second(); | |
| 789 configs->is_audio_encrypted = config.is_encrypted(); | |
| 790 configs->audio_extra_data = config.extra_data(); | |
| 791 configs->audio_codec_delay_ns = static_cast<int64_t>( | |
| 792 config.codec_delay() * | |
| 793 (static_cast<double>(base::Time::kNanosecondsPerSecond) / | |
| 794 config.samples_per_second())); | |
| 795 configs->audio_seek_preroll_ns = | |
| 796 config.seek_preroll().InMicroseconds() * | |
| 797 base::Time::kNanosecondsPerMicrosecond; | |
| 798 return true; | |
| 799 } | |
| 800 if (!is_audio && video_stream_) { | |
| 801 media::VideoDecoderConfig config = video_stream_->video_decoder_config(); | |
| 802 configs->video_codec = config.codec(); | |
| 803 configs->video_size = config.natural_size(); | |
| 804 configs->is_video_encrypted = config.is_encrypted(); | |
| 805 configs->video_extra_data = config.extra_data(); | |
| 806 return true; | |
| 807 } | |
| 808 return false; | |
| 809 } | |
| 810 | |
| 811 } // namespace content | |
| OLD | NEW |