ChannelMediaDecoder.cpp (23188B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ChannelMediaDecoder.h" 8 9 #include "BaseMediaResource.h" 10 #include "ChannelMediaResource.h" 11 #include "DecoderTraits.h" 12 #include "ExternalEngineStateMachine.h" 13 #include "MediaDecoderStateMachine.h" 14 #include "MediaFormatReader.h" 15 #include "MediaShutdownManager.h" 16 #include "VideoUtils.h" 17 #include "base/process_util.h" 18 #include "mozilla/Preferences.h" 19 #include "mozilla/StaticPrefs_media.h" 20 21 namespace mozilla { 22 23 using TimeUnit = media::TimeUnit; 24 25 extern LazyLogModule gMediaDecoderLog; 26 #define LOG(x, ...) \ 27 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__) 28 #define LOGD(x, ...) \ 29 MOZ_LOG_FMT(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__) 30 31 ChannelMediaDecoder::ResourceCallback::ResourceCallback( 32 AbstractThread* aMainThread) 33 : mAbstractMainThread(aMainThread) { 34 MOZ_ASSERT(aMainThread); 35 DecoderDoctorLogger::LogConstructionAndBase( 36 "ChannelMediaDecoder::ResourceCallback", this, 37 static_cast<const MediaResourceCallback*>(this)); 38 } 39 40 ChannelMediaDecoder::ResourceCallback::~ResourceCallback() { 41 DecoderDoctorLogger::LogDestruction("ChannelMediaDecoder::ResourceCallback", 42 this); 43 } 44 45 void ChannelMediaDecoder::ResourceCallback::Connect( 46 ChannelMediaDecoder* aDecoder) { 47 MOZ_ASSERT(NS_IsMainThread()); 48 mDecoder = aDecoder; 49 DecoderDoctorLogger::LinkParentAndChild( 50 "ChannelMediaDecoder::ResourceCallback", this, "decoder", mDecoder); 51 mTimer = NS_NewTimer(mAbstractMainThread->AsEventTarget()); 52 } 53 54 void ChannelMediaDecoder::ResourceCallback::Disconnect() { 55 MOZ_ASSERT(NS_IsMainThread()); 56 if (mDecoder) { 57 DecoderDoctorLogger::UnlinkParentAndChild( 58 "ChannelMediaDecoder::ResourceCallback", this, mDecoder); 59 mDecoder = nullptr; 60 mTimer->Cancel(); 61 mTimer = nullptr; 62 } 63 } 64 65 AbstractThread* ChannelMediaDecoder::ResourceCallback::AbstractMainThread() 66 const { 67 return mAbstractMainThread; 68 } 69 70 MediaDecoderOwner* ChannelMediaDecoder::ResourceCallback::GetMediaOwner() 71 const { 72 MOZ_ASSERT(NS_IsMainThread()); 73 return mDecoder ? mDecoder->GetOwner() : nullptr; 74 } 75 76 void ChannelMediaDecoder::ResourceCallback::NotifyNetworkError( 77 const MediaResult& aError) { 78 MOZ_ASSERT(NS_IsMainThread()); 79 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log, 80 "network_error", aError); 81 if (mDecoder) { 82 mDecoder->NetworkError(aError); 83 } 84 } 85 86 /* static */ 87 void ChannelMediaDecoder::ResourceCallback::TimerCallback(nsITimer* aTimer, 88 void* aClosure) { 89 MOZ_ASSERT(NS_IsMainThread()); 90 ResourceCallback* thiz = static_cast<ResourceCallback*>(aClosure); 91 MOZ_ASSERT(thiz->mDecoder); 92 thiz->mDecoder->NotifyReaderDataArrived(); 93 thiz->mTimerArmed = false; 94 } 95 96 void ChannelMediaDecoder::ResourceCallback::NotifyDataArrived() { 97 MOZ_ASSERT(NS_IsMainThread()); 98 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log, 99 "data_arrived", true); 100 101 if (!mDecoder) { 102 return; 103 } 104 105 mDecoder->DownloadProgressed(); 106 107 if (mTimerArmed) { 108 return; 109 } 110 // In situations where these notifications come from stochastic network 111 // activity, we can save significant computation by throttling the 112 // calls to MediaDecoder::NotifyDataArrived() which will update the buffer 113 // ranges of the reader. 114 mTimerArmed = true; 115 mTimer->InitWithNamedFuncCallback( 116 TimerCallback, this, sDelay, nsITimer::TYPE_ONE_SHOT, 117 "ChannelMediaDecoder::ResourceCallback::TimerCallback"_ns); 118 } 119 120 void ChannelMediaDecoder::ResourceCallback::NotifyDataEnded(nsresult aStatus) { 121 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log, 122 "data_ended", aStatus); 123 MOZ_ASSERT(NS_IsMainThread()); 124 if (mDecoder) { 125 mDecoder->NotifyDownloadEnded(aStatus); 126 } 127 } 128 129 void ChannelMediaDecoder::ResourceCallback::NotifyPrincipalChanged() { 130 MOZ_ASSERT(NS_IsMainThread()); 131 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log, 132 "principal_changed", true); 133 if (mDecoder) { 134 mDecoder->NotifyPrincipalChanged(); 135 } 136 } 137 138 void ChannelMediaDecoder::NotifyPrincipalChanged() { 139 MOZ_ASSERT(NS_IsMainThread()); 140 MediaDecoder::NotifyPrincipalChanged(); 141 if (!mInitialChannelPrincipalKnown) { 142 // We'll receive one notification when the channel's initial principal 143 // is known, after all HTTP redirects have resolved. This isn't really a 144 // principal change, so return here to avoid the mSameOriginMedia check 145 // below. 146 mInitialChannelPrincipalKnown = true; 147 return; 148 } 149 if (!mSameOriginMedia) { 150 // Block mid-flight redirects to non CORS same origin destinations. 151 // See bugs 1441153, 1443942. 152 LOG("ChannnelMediaDecoder prohibited cross origin redirect blocked."); 153 NetworkError(MediaResult(NS_ERROR_DOM_BAD_URI, 154 "Prohibited cross origin redirect blocked")); 155 } 156 } 157 158 void ChannelMediaDecoder::ResourceCallback::NotifySuspendedStatusChanged( 159 bool aSuspendedByCache) { 160 MOZ_ASSERT(NS_IsMainThread()); 161 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log, 162 "suspended_status_changed", aSuspendedByCache); 163 MediaDecoderOwner* owner = GetMediaOwner(); 164 if (owner) { 165 owner->NotifySuspendedByCache(aSuspendedByCache); 166 } 167 } 168 169 ChannelMediaDecoder::ChannelMediaDecoder(MediaDecoderInit& aInit) 170 : MediaDecoder(aInit), 171 mResourceCallback( 172 new ResourceCallback(aInit.mOwner->AbstractMainThread())) { 173 mResourceCallback->Connect(this); 174 } 175 176 /* static */ 177 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Create( 178 MediaDecoderInit& aInit, DecoderDoctorDiagnostics* aDiagnostics) { 179 MOZ_ASSERT(NS_IsMainThread()); 180 RefPtr<ChannelMediaDecoder> decoder; 181 if (DecoderTraits::CanHandleContainerType(aInit.mContainerType, 182 aDiagnostics) != CANPLAY_NO) { 183 decoder = new ChannelMediaDecoder(aInit); 184 return decoder.forget(); 185 } 186 187 return nullptr; 188 } 189 190 bool ChannelMediaDecoder::CanClone() { 191 MOZ_ASSERT(NS_IsMainThread()); 192 return mResource && mResource->CanClone(); 193 } 194 195 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Clone( 196 MediaDecoderInit& aInit) { 197 if (!mResource || DecoderTraits::CanHandleContainerType( 198 aInit.mContainerType, nullptr) == CANPLAY_NO) { 199 return nullptr; 200 } 201 RefPtr<ChannelMediaDecoder> decoder = new ChannelMediaDecoder(aInit); 202 nsresult rv = decoder->Load(mResource); 203 if (NS_FAILED(rv)) { 204 decoder->Shutdown(); 205 return nullptr; 206 } 207 return decoder.forget(); 208 } 209 210 MediaDecoderStateMachineBase* ChannelMediaDecoder::CreateStateMachine( 211 bool aDisableExternalEngine) { 212 MOZ_ASSERT(NS_IsMainThread()); 213 MediaFormatReaderInit init; 214 init.mVideoFrameContainer = GetVideoFrameContainer(); 215 init.mKnowsCompositor = GetCompositor(); 216 init.mCrashHelper = GetOwner()->CreateGMPCrashHelper(); 217 init.mFrameStats = mFrameStats; 218 init.mResource = mResource; 219 init.mMediaDecoderOwnerID = mOwner; 220 static Atomic<uint32_t> sTrackingIdCounter(0); 221 init.mTrackingId.emplace(TrackingId::Source::ChannelDecoder, 222 sTrackingIdCounter++, 223 TrackingId::TrackAcrossProcesses::Yes); 224 mReader = DecoderTraits::CreateReader(ContainerType(), init); 225 if (NS_WARN_IF(!mReader)) { 226 return nullptr; 227 } 228 229 #ifdef MOZ_WMF_MEDIA_ENGINE 230 // This state machine is mainly used for the encrypted playback. However, for 231 // testing purpose we would also use it the non-encrypted playback. 232 // 1=enabled encrypted and clear, 3=enabled clear 233 if ((StaticPrefs::media_wmf_media_engine_enabled() == 1 || 234 StaticPrefs::media_wmf_media_engine_enabled() == 3) && 235 StaticPrefs::media_wmf_media_engine_channel_decoder_enabled() && 236 !aDisableExternalEngine) { 237 return new ExternalEngineStateMachine(this, mReader); 238 } 239 #endif 240 return new MediaDecoderStateMachine(this, mReader); 241 } 242 243 void ChannelMediaDecoder::Shutdown() { 244 mResourceCallback->Disconnect(); 245 MediaDecoder::Shutdown(); 246 247 if (mResource) { 248 // Force any outstanding seek and byterange requests to complete 249 // to prevent shutdown from deadlocking. 250 mResourceClosePromise = mResource->Close(); 251 } 252 } 253 254 void ChannelMediaDecoder::ShutdownInternal() { 255 if (!mResourceClosePromise) { 256 MediaShutdownManager::Instance().Unregister(this); 257 return; 258 } 259 260 mResourceClosePromise->Then( 261 AbstractMainThread(), __func__, 262 [self = RefPtr<ChannelMediaDecoder>(this)] { 263 MediaShutdownManager::Instance().Unregister(self); 264 }); 265 } 266 267 nsresult ChannelMediaDecoder::Load(nsIChannel* aChannel, 268 bool aIsPrivateBrowsing, 269 nsIStreamListener** aStreamListener) { 270 MOZ_ASSERT(NS_IsMainThread()); 271 MOZ_ASSERT(!mResource); 272 MOZ_ASSERT(aStreamListener); 273 274 mResource = BaseMediaResource::Create(mResourceCallback, aChannel, 275 aIsPrivateBrowsing); 276 if (!mResource) { 277 return NS_ERROR_FAILURE; 278 } 279 DDLINKCHILD("resource", mResource.get()); 280 281 nsresult rv = MediaShutdownManager::Instance().Register(this); 282 if (NS_WARN_IF(NS_FAILED(rv))) { 283 return rv; 284 } 285 286 rv = mResource->Open(aStreamListener); 287 NS_ENSURE_SUCCESS(rv, rv); 288 return CreateAndInitStateMachine(mResource->IsLiveStream()); 289 } 290 291 nsresult ChannelMediaDecoder::Load(BaseMediaResource* aOriginal) { 292 MOZ_ASSERT(NS_IsMainThread()); 293 MOZ_ASSERT(!mResource); 294 295 mResource = aOriginal->CloneData(mResourceCallback); 296 if (!mResource) { 297 return NS_ERROR_FAILURE; 298 } 299 DDLINKCHILD("resource", mResource.get()); 300 301 nsresult rv = MediaShutdownManager::Instance().Register(this); 302 if (NS_WARN_IF(NS_FAILED(rv))) { 303 return rv; 304 } 305 return CreateAndInitStateMachine(mResource->IsLiveStream()); 306 } 307 308 void ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus) { 309 MOZ_ASSERT(NS_IsMainThread()); 310 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); 311 312 LOG("NotifyDownloadEnded, status=%" PRIx32, static_cast<uint32_t>(aStatus)); 313 314 if (NS_SUCCEEDED(aStatus)) { 315 // Download ends successfully. This is a stream with a finite length. 316 GetStateMachine()->DispatchIsLiveStream(false); 317 } 318 319 MediaDecoderOwner* owner = GetOwner(); 320 if (NS_SUCCEEDED(aStatus) || aStatus == NS_BASE_STREAM_CLOSED) { 321 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 322 "ChannelMediaDecoder::UpdatePlaybackRate", 323 [playbackStats = mPlaybackStatistics, 324 res = RefPtr<BaseMediaResource>(mResource), 325 duration = mDuration.match(DurationToTimeUnit())]() { 326 (void)UpdateResourceOfPlaybackByteRate(playbackStats, res, duration); 327 }); 328 nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget()); 329 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 330 (void)rv; 331 owner->DownloadSuspended(); 332 // NotifySuspendedStatusChanged will tell the element that download 333 // has been suspended "by the cache", which is true since we never 334 // download anything. The element can then transition to HAVE_ENOUGH_DATA. 335 owner->NotifySuspendedByCache(true); 336 } else if (aStatus == NS_BINDING_ABORTED) { 337 // Download has been cancelled by user. 338 owner->LoadAborted(); 339 } else { 340 NetworkError(MediaResult(aStatus, "Download aborted")); 341 } 342 } 343 344 bool ChannelMediaDecoder::CanPlayThroughImpl() { 345 MOZ_ASSERT(NS_IsMainThread()); 346 return mCanPlayThrough; 347 } 348 349 void ChannelMediaDecoder::OnPlaybackEvent(const MediaPlaybackEvent& aEvent) { 350 MOZ_ASSERT(NS_IsMainThread()); 351 switch (aEvent.mType) { 352 case MediaPlaybackEvent::PlaybackStarted: 353 mPlaybackByteOffset = aEvent.mData.as<int64_t>(); 354 mPlaybackStatistics.Start(); 355 break; 356 case MediaPlaybackEvent::PlaybackProgressed: { 357 int64_t newPos = aEvent.mData.as<int64_t>(); 358 mPlaybackStatistics.AddBytes(newPos - mPlaybackByteOffset); 359 mPlaybackByteOffset = newPos; 360 break; 361 } 362 case MediaPlaybackEvent::PlaybackStopped: { 363 int64_t newPos = aEvent.mData.as<int64_t>(); 364 mPlaybackStatistics.AddBytes(newPos - mPlaybackByteOffset); 365 mPlaybackByteOffset = newPos; 366 mPlaybackStatistics.Stop(); 367 break; 368 } 369 default: 370 break; 371 } 372 MediaDecoder::OnPlaybackEvent(aEvent); 373 } 374 375 void ChannelMediaDecoder::DurationChanged() { 376 MOZ_ASSERT(NS_IsMainThread()); 377 MediaDecoder::DurationChanged(); 378 // Duration has changed so we should recompute playback byte rate 379 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 380 "ChannelMediaDecoder::UpdatePlaybackRate", 381 [playbackStats = mPlaybackStatistics, 382 res = RefPtr<BaseMediaResource>(mResource), 383 duration = mDuration.match(DurationToTimeUnit())]() { 384 (void)UpdateResourceOfPlaybackByteRate(playbackStats, res, duration); 385 }); 386 nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget()); 387 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 388 (void)rv; 389 } 390 391 void ChannelMediaDecoder::DownloadProgressed() { 392 MOZ_ASSERT(NS_IsMainThread()); 393 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown()); 394 395 GetOwner()->DownloadProgressed(); 396 397 using StatsPromise = MozPromise<MediaStatistics, bool, true>; 398 InvokeAsync(GetStateMachine()->OwnerThread(), __func__, 399 [playbackStats = mPlaybackStatistics, 400 res = RefPtr<BaseMediaResource>(mResource), 401 duration = mDuration.match(DurationToTimeUnit()), 402 playbackByteOffset = mPlaybackByteOffset]() { 403 auto rateInfo = UpdateResourceOfPlaybackByteRate(playbackStats, 404 res, duration); 405 MediaStatistics result; 406 result.mDownloadByteRate = 407 res->GetDownloadRate(&result.mDownloadByteRateReliable); 408 result.mDownloadBytePosition = 409 res->GetCachedDataEnd(playbackByteOffset); 410 result.mTotalBytes = res->GetLength(); 411 result.mPlaybackByteRate = rateInfo.mRate; 412 result.mPlaybackByteRateReliable = rateInfo.mReliable; 413 result.mPlaybackByteOffset = playbackByteOffset; 414 return StatsPromise::CreateAndResolve(result, __func__); 415 }) 416 ->Then( 417 mAbstractMainThread, __func__, 418 [=, 419 self = RefPtr<ChannelMediaDecoder>(this)](MediaStatistics aStats) { 420 if (IsShutdown()) { 421 return; 422 } 423 mCanPlayThrough = aStats.CanPlayThrough(); 424 LOGD("Can play through: {} [{}]", mCanPlayThrough, 425 aStats.ToString()); 426 GetStateMachine()->DispatchCanPlayThrough(mCanPlayThrough); 427 mResource->ThrottleReadahead(ShouldThrottleDownload(aStats)); 428 // Update readyState since mCanPlayThrough might have changed. 429 GetOwner()->UpdateReadyState(); 430 }, 431 []() { MOZ_ASSERT_UNREACHABLE("Promise not resolved"); }); 432 } 433 434 /* static */ 435 ChannelMediaDecoder::PlaybackRateInfo 436 ChannelMediaDecoder::UpdateResourceOfPlaybackByteRate( 437 const MediaChannelStatistics& aStats, BaseMediaResource* aResource, 438 const TimeUnit& aDuration) { 439 MOZ_ASSERT(!NS_IsMainThread()); 440 441 uint32_t byteRatePerSecond = 0; 442 int64_t length = aResource->GetLength(); 443 bool rateIsReliable = false; 444 if (aDuration.IsValid() && !aDuration.IsInfinite() && 445 aDuration.IsPositive() && length >= 0 && 446 length / aDuration.ToSeconds() < UINT32_MAX) { 447 // Both the duration and total content length are known. 448 byteRatePerSecond = uint32_t(length / aDuration.ToSeconds()); 449 rateIsReliable = true; 450 } else { 451 byteRatePerSecond = aStats.GetRate(&rateIsReliable); 452 } 453 454 // Adjust rate if necessary. 455 if (rateIsReliable) { 456 // Avoid passing a zero rate 457 byteRatePerSecond = std::max(byteRatePerSecond, 1u); 458 } else { 459 // Set a minimum rate of 10,000 bytes per second ... sometimes we just 460 // don't have good data 461 byteRatePerSecond = std::max(byteRatePerSecond, 10000u); 462 } 463 aResource->SetPlaybackRate(byteRatePerSecond); 464 return {byteRatePerSecond, rateIsReliable}; 465 } 466 467 bool ChannelMediaDecoder::ShouldThrottleDownload( 468 const MediaStatistics& aStats) { 469 // We throttle the download if either the throttle override pref is set 470 // (so that we always throttle at the readahead limit on mobile if using 471 // a cellular network) or if the download is fast enough that there's no 472 // concern about playback being interrupted. 473 MOZ_ASSERT(NS_IsMainThread()); 474 NS_ENSURE_TRUE(GetStateMachine(), false); 475 476 int64_t length = aStats.mTotalBytes; 477 if (length > 0 && 478 length <= int64_t(StaticPrefs::media_memory_cache_max_size()) * 1024) { 479 // Don't throttle the download of small resources. This is to speed 480 // up seeking, as seeks into unbuffered ranges would require starting 481 // up a new HTTP transaction, which adds latency. 482 LOGD("Not throttling download: media resource is small"); 483 return false; 484 } 485 486 if (OnCellularConnection() && 487 Preferences::GetBool( 488 "media.throttle-cellular-regardless-of-download-rate", false)) { 489 LOGD( 490 "Throttling download: on cellular, and " 491 "media.throttle-cellular-regardless-of-download-rate is true."); 492 return true; 493 } 494 495 if (!aStats.mDownloadByteRateReliable || !aStats.mPlaybackByteRateReliable) { 496 LOGD( 497 "Not throttling download: download rate ({}) playback rate ({}) is not " 498 "reliable", 499 aStats.mDownloadByteRate, aStats.mPlaybackByteRate); 500 return false; 501 } 502 uint32_t factor = 503 std::max(2u, Preferences::GetUint("media.throttle-factor", 2)); 504 bool throttle = aStats.mDownloadByteRate > factor * aStats.mPlaybackByteRate; 505 LOGD( 506 "ShouldThrottleDownload: {} (download rate({}) > factor({}) * playback " 507 "rate({}))", 508 throttle ? "true" : "false", aStats.mDownloadByteRate, factor, 509 aStats.mPlaybackByteRate); 510 return throttle; 511 } 512 513 void ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) { 514 MOZ_ASSERT(NS_IsMainThread()); 515 if (mResource) { 516 aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf); 517 } 518 } 519 520 already_AddRefed<nsIPrincipal> ChannelMediaDecoder::GetCurrentPrincipal() { 521 MOZ_ASSERT(NS_IsMainThread()); 522 return mResource ? mResource->GetCurrentPrincipal() : nullptr; 523 } 524 525 bool ChannelMediaDecoder::HadCrossOriginRedirects() { 526 MOZ_ASSERT(NS_IsMainThread()); 527 return mResource ? mResource->HadCrossOriginRedirects() : false; 528 } 529 530 bool ChannelMediaDecoder::IsTransportSeekable() { 531 MOZ_ASSERT(NS_IsMainThread()); 532 return mResource->IsTransportSeekable(); 533 } 534 535 void ChannelMediaDecoder::SetLoadInBackground(bool aLoadInBackground) { 536 MOZ_ASSERT(NS_IsMainThread()); 537 if (mResource) { 538 mResource->SetLoadInBackground(aLoadInBackground); 539 } 540 } 541 542 void ChannelMediaDecoder::Suspend() { 543 MOZ_ASSERT(NS_IsMainThread()); 544 if (mResource) { 545 mResource->Suspend(true); 546 } 547 MediaDecoder::Suspend(); 548 } 549 550 void ChannelMediaDecoder::Resume() { 551 MOZ_ASSERT(NS_IsMainThread()); 552 if (mResource) { 553 mResource->Resume(); 554 } 555 MediaDecoder::Resume(); 556 } 557 558 void ChannelMediaDecoder::MetadataLoaded( 559 UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags, 560 MediaDecoderEventVisibility aEventVisibility) { 561 MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags), 562 aEventVisibility); 563 // Set mode to PLAYBACK after reading metadata. 564 mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK); 565 } 566 567 void ChannelMediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo) { 568 MediaDecoder::GetDebugInfo(aInfo); 569 if (mResource) { 570 mResource->GetDebugInfo(aInfo.mResource); 571 } 572 } 573 574 bool ChannelMediaDecoder::MediaStatistics::CanPlayThrough() const { 575 // Number of estimated seconds worth of data we need to have buffered 576 // ahead of the current playback position before we allow the media decoder 577 // to report that it can play through the entire media without the decode 578 // catching up with the download. Having this margin make the 579 // CanPlayThrough() calculation more stable in the case of 580 // fluctuating bitrates. 581 static const int64_t CAN_PLAY_THROUGH_MARGIN = 1; 582 583 LOGD( 584 "CanPlayThrough: mPlaybackByteRate: {}, mDownloadByteRate: {}, " 585 "mTotalBytes" 586 ": {}, mDownloadBytePosition: {}, mPlaybackByteOffset: {}, " 587 "mDownloadByteRateReliable: {}, mPlaybackByteRateReliable: {}", 588 mPlaybackByteRate, mDownloadByteRate, mTotalBytes, mDownloadBytePosition, 589 mPlaybackByteOffset, mDownloadByteRateReliable, 590 mPlaybackByteRateReliable); 591 592 if ((mTotalBytes < 0 && mDownloadByteRateReliable) || 593 (mTotalBytes >= 0 && mTotalBytes == mDownloadBytePosition)) { 594 LOGD("CanPlayThrough: true (early return)"); 595 return true; 596 } 597 598 if (!mDownloadByteRateReliable || !mPlaybackByteRateReliable) { 599 LOGD("CanPlayThrough: false (rate unreliable: download({})/playback({}))", 600 mDownloadByteRateReliable, mPlaybackByteRateReliable); 601 return false; 602 } 603 604 int64_t bytesToDownload = mTotalBytes - mDownloadBytePosition; 605 int64_t bytesToPlayback = mTotalBytes - mPlaybackByteOffset; 606 double timeToDownload = bytesToDownload / mDownloadByteRate; 607 double timeToPlay = bytesToPlayback / mPlaybackByteRate; 608 609 if (timeToDownload > timeToPlay) { 610 // Estimated time to download is greater than the estimated time to play. 611 // We probably can't play through without having to stop to buffer. 612 LOGD("CanPlayThrough: false (download speed too low)"); 613 return false; 614 } 615 616 // Estimated time to download is less than the estimated time to play. 617 // We can probably play through without having to buffer, but ensure that 618 // we've got a reasonable amount of data buffered after the current 619 // playback position, so that if the bitrate of the media fluctuates, or if 620 // our download rate or decode rate estimation is otherwise inaccurate, 621 // we don't suddenly discover that we need to buffer. This is particularly 622 // required near the start of the media, when not much data is downloaded. 623 int64_t readAheadMargin = 624 static_cast<int64_t>(mPlaybackByteRate * CAN_PLAY_THROUGH_MARGIN); 625 return mDownloadBytePosition > mPlaybackByteOffset + readAheadMargin; 626 } 627 628 nsCString ChannelMediaDecoder::MediaStatistics::ToString() const { 629 nsCString str; 630 str.AppendFmt("MediaStatistics: "); 631 str.AppendFmt(" mTotalBytes={}", mTotalBytes); 632 str.AppendFmt(" mDownloadBytePosition={}", mDownloadBytePosition); 633 str.AppendFmt(" mPlaybackByteOffset={}", mPlaybackByteOffset); 634 str.AppendFmt(" mDownloadByteRate={}", mDownloadByteRate); 635 str.AppendFmt(" mPlaybackByteRate={}", mPlaybackByteRate); 636 str.AppendFmt(" mDownloadByteRateReliable={}", mDownloadByteRateReliable); 637 str.AppendFmt(" mPlaybackByteRateReliable={}", mPlaybackByteRateReliable); 638 return str; 639 } 640 641 } // namespace mozilla 642 643 // avoid redefined macro in unified build 644 #undef LOG 645 #undef LOGD