tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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