dom/media/ChannelMediaDecoder.cpp
author Mike Hommey <mh+mozilla@glandium.org>
Fri, 11 Jan 2019 16:01:15 +0000
changeset 453570 daf50f25895db073e44d50fecf2e4f6fe873865d
parent 446960 0ceae9db9ec0be18daa1a279511ad305723185d4
child 461887 f41cee9bf14931b453838d1bbcbda528e3b064e8
permissions -rw-r--r--
Bug 1519307 - Add a new project to build useful parts of breakpad independently. r=froydnj With `ac_add_options --enable-project=tools/crashreporter` in a mozconfig, `./mach build` builds minidump_stackwalk, dump_syms and fileid. One caveat is that due to limitation in how the build system works currently, it's cumbersome to keep dump_syms as a host program for Gecko, and to make it a target program for this project. For now, keep it as a host program. We're not going to use it on automation, but it's still convenient to have for quick local builds (I've had to resort to awful hacks downstream). Differential Revision: https://phabricator.services.mozilla.com/D16299

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ChannelMediaDecoder.h"
#include "DecoderTraits.h"
#include "MediaDecoderStateMachine.h"
#include "MediaFormatReader.h"
#include "BaseMediaResource.h"
#include "MediaShutdownManager.h"
#include "mozilla/StaticPrefs.h"

namespace mozilla {

extern LazyLogModule gMediaDecoderLog;
#define LOG(x, ...) \
  DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)

ChannelMediaDecoder::ResourceCallback::ResourceCallback(
    AbstractThread* aMainThread)
    : mAbstractMainThread(aMainThread) {
  MOZ_ASSERT(aMainThread);
  DecoderDoctorLogger::LogConstructionAndBase(
      "ChannelMediaDecoder::ResourceCallback", this,
      static_cast<const MediaResourceCallback*>(this));
}

ChannelMediaDecoder::ResourceCallback::~ResourceCallback() {
  DecoderDoctorLogger::LogDestruction("ChannelMediaDecoder::ResourceCallback",
                                      this);
}

void ChannelMediaDecoder::ResourceCallback::Connect(
    ChannelMediaDecoder* aDecoder) {
  MOZ_ASSERT(NS_IsMainThread());
  mDecoder = aDecoder;
  DecoderDoctorLogger::LinkParentAndChild(
      "ChannelMediaDecoder::ResourceCallback", this, "decoder", mDecoder);
  mTimer = NS_NewTimer(mAbstractMainThread->AsEventTarget());
}

void ChannelMediaDecoder::ResourceCallback::Disconnect() {
  MOZ_ASSERT(NS_IsMainThread());
  if (mDecoder) {
    DecoderDoctorLogger::UnlinkParentAndChild(
        "ChannelMediaDecoder::ResourceCallback", this, mDecoder);
    mDecoder = nullptr;
    mTimer->Cancel();
    mTimer = nullptr;
  }
}

AbstractThread* ChannelMediaDecoder::ResourceCallback::AbstractMainThread()
    const {
  return mAbstractMainThread;
}

MediaDecoderOwner* ChannelMediaDecoder::ResourceCallback::GetMediaOwner()
    const {
  MOZ_ASSERT(NS_IsMainThread());
  return mDecoder ? mDecoder->GetOwner() : nullptr;
}

void ChannelMediaDecoder::ResourceCallback::NotifyNetworkError(
    const MediaResult& aError) {
  MOZ_ASSERT(NS_IsMainThread());
  DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
           "network_error", aError);
  if (mDecoder) {
    mDecoder->NetworkError(aError);
  }
}

/* static */ void ChannelMediaDecoder::ResourceCallback::TimerCallback(
    nsITimer* aTimer, void* aClosure) {
  MOZ_ASSERT(NS_IsMainThread());
  ResourceCallback* thiz = static_cast<ResourceCallback*>(aClosure);
  MOZ_ASSERT(thiz->mDecoder);
  thiz->mDecoder->NotifyReaderDataArrived();
  thiz->mTimerArmed = false;
}

void ChannelMediaDecoder::ResourceCallback::NotifyDataArrived() {
  MOZ_ASSERT(NS_IsMainThread());
  DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
           "data_arrived", true);

  if (!mDecoder) {
    return;
  }

  mDecoder->DownloadProgressed();

  if (mTimerArmed) {
    return;
  }
  // In situations where these notifications come from stochastic network
  // activity, we can save significant computation by throttling the
  // calls to MediaDecoder::NotifyDataArrived() which will update the buffer
  // ranges of the reader.
  mTimerArmed = true;
  mTimer->InitWithNamedFuncCallback(
      TimerCallback, this, sDelay, nsITimer::TYPE_ONE_SHOT,
      "ChannelMediaDecoder::ResourceCallback::TimerCallback");
}

void ChannelMediaDecoder::ResourceCallback::NotifyDataEnded(nsresult aStatus) {
  DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
           "data_ended", aStatus);
  MOZ_ASSERT(NS_IsMainThread());
  if (mDecoder) {
    mDecoder->NotifyDownloadEnded(aStatus);
  }
}

void ChannelMediaDecoder::ResourceCallback::NotifyPrincipalChanged() {
  MOZ_ASSERT(NS_IsMainThread());
  DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
           "principal_changed", true);
  if (mDecoder) {
    mDecoder->NotifyPrincipalChanged();
  }
}

void ChannelMediaDecoder::NotifyPrincipalChanged() {
  MOZ_ASSERT(NS_IsMainThread());
  MediaDecoder::NotifyPrincipalChanged();
  if (!mInitialChannelPrincipalKnown) {
    // We'll receive one notification when the channel's initial principal
    // is known, after all HTTP redirects have resolved. This isn't really a
    // principal change, so return here to avoid the mSameOriginMedia check
    // below.
    mInitialChannelPrincipalKnown = true;
    return;
  }
  if (!mSameOriginMedia &&
      Preferences::GetBool("media.block-midflight-redirects", true)) {
    // Block mid-flight redirects to non CORS same origin destinations.
    // See bugs 1441153, 1443942.
    LOG("ChannnelMediaDecoder prohibited cross origin redirect blocked.");
    NetworkError(MediaResult(NS_ERROR_DOM_BAD_URI,
                             "Prohibited cross origin redirect blocked"));
  }
}

void ChannelMediaDecoder::ResourceCallback::NotifySuspendedStatusChanged(
    bool aSuspendedByCache) {
  MOZ_ASSERT(NS_IsMainThread());
  DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
           "suspended_status_changed", aSuspendedByCache);
  MediaDecoderOwner* owner = GetMediaOwner();
  if (owner) {
    AbstractThread::AutoEnter context(owner->AbstractMainThread());
    owner->NotifySuspendedByCache(aSuspendedByCache);
  }
}

ChannelMediaDecoder::ChannelMediaDecoder(MediaDecoderInit& aInit)
    : MediaDecoder(aInit),
      mResourceCallback(
          new ResourceCallback(aInit.mOwner->AbstractMainThread())) {
  mResourceCallback->Connect(this);
}

/* static */
already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Create(
    MediaDecoderInit& aInit, DecoderDoctorDiagnostics* aDiagnostics) {
  MOZ_ASSERT(NS_IsMainThread());
  RefPtr<ChannelMediaDecoder> decoder;

  const MediaContainerType& type = aInit.mContainerType;
  if (DecoderTraits::IsSupportedType(type)) {
    decoder = new ChannelMediaDecoder(aInit);
    return decoder.forget();
  }

  if (DecoderTraits::IsHttpLiveStreamingType(type)) {
    // We don't have an HLS decoder.
    Telemetry::Accumulate(Telemetry::MEDIA_HLS_DECODER_SUCCESS, false);
  }

  return nullptr;
}

bool ChannelMediaDecoder::CanClone() {
  MOZ_ASSERT(NS_IsMainThread());
  return mResource && mResource->CanClone();
}

already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Clone(
    MediaDecoderInit& aInit) {
  if (!mResource || !DecoderTraits::IsSupportedType(aInit.mContainerType)) {
    return nullptr;
  }
  RefPtr<ChannelMediaDecoder> decoder = new ChannelMediaDecoder(aInit);
  if (!decoder) {
    return nullptr;
  }
  nsresult rv = decoder->Load(mResource);
  if (NS_FAILED(rv)) {
    decoder->Shutdown();
    return nullptr;
  }
  return decoder.forget();
}

MediaDecoderStateMachine* ChannelMediaDecoder::CreateStateMachine() {
  MOZ_ASSERT(NS_IsMainThread());
  MediaFormatReaderInit init;
  init.mVideoFrameContainer = GetVideoFrameContainer();
  init.mKnowsCompositor = GetCompositor();
  init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
  init.mFrameStats = mFrameStats;
  init.mResource = mResource;
  init.mMediaDecoderOwnerID = mOwner;
  mReader = DecoderTraits::CreateReader(ContainerType(), init);
  return new MediaDecoderStateMachine(this, mReader);
}

void ChannelMediaDecoder::Shutdown() {
  mResourceCallback->Disconnect();
  MediaDecoder::Shutdown();

  // Force any outstanding seek and byterange requests to complete
  // to prevent shutdown from deadlocking.
  if (mResource) {
    mResource->Close();
  }
}

nsresult ChannelMediaDecoder::Load(nsIChannel* aChannel,
                                   bool aIsPrivateBrowsing,
                                   nsIStreamListener** aStreamListener) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!mResource);
  MOZ_ASSERT(aStreamListener);
  AbstractThread::AutoEnter context(AbstractMainThread());

  mResource = BaseMediaResource::Create(mResourceCallback, aChannel,
                                        aIsPrivateBrowsing);
  if (!mResource) {
    return NS_ERROR_FAILURE;
  }
  DDLINKCHILD("resource", mResource.get());

  nsresult rv = MediaShutdownManager::Instance().Register(this);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = mResource->Open(aStreamListener);
  NS_ENSURE_SUCCESS(rv, rv);

  SetStateMachine(CreateStateMachine());
  NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);

  GetStateMachine()->DispatchIsLiveStream(mResource->IsLiveStream());

  return InitializeStateMachine();
}

nsresult ChannelMediaDecoder::Load(BaseMediaResource* aOriginal) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!mResource);
  AbstractThread::AutoEnter context(AbstractMainThread());

  mResource = aOriginal->CloneData(mResourceCallback);
  if (!mResource) {
    return NS_ERROR_FAILURE;
  }
  DDLINKCHILD("resource", mResource.get());

  nsresult rv = MediaShutdownManager::Instance().Register(this);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  SetStateMachine(CreateStateMachine());
  NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);

  GetStateMachine()->DispatchIsLiveStream(mResource->IsLiveStream());

  return InitializeStateMachine();
}

void ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
  AbstractThread::AutoEnter context(AbstractMainThread());

  LOG("NotifyDownloadEnded, status=%" PRIx32, static_cast<uint32_t>(aStatus));

  if (NS_SUCCEEDED(aStatus)) {
    // Download ends successfully. This is a stream with a finite length.
    GetStateMachine()->DispatchIsLiveStream(false);
  }

  MediaDecoderOwner* owner = GetOwner();
  if (NS_SUCCEEDED(aStatus) || aStatus == NS_BASE_STREAM_CLOSED) {
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
        "ChannelMediaDecoder::UpdatePlaybackRate",
        [stats = mPlaybackStatistics,
         res = RefPtr<BaseMediaResource>(mResource), duration = mDuration]() {
          auto rate = ComputePlaybackRate(stats, res, duration);
          UpdatePlaybackRate(rate, res);
        });
    nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
    owner->DownloadSuspended();
    // NotifySuspendedStatusChanged will tell the element that download
    // has been suspended "by the cache", which is true since we never
    // download anything. The element can then transition to HAVE_ENOUGH_DATA.
    owner->NotifySuspendedByCache(true);
  } else if (aStatus == NS_BINDING_ABORTED) {
    // Download has been cancelled by user.
    owner->LoadAborted();
  } else {
    NetworkError(MediaResult(aStatus, "Download aborted"));
  }
}

bool ChannelMediaDecoder::CanPlayThroughImpl() {
  MOZ_ASSERT(NS_IsMainThread());
  return mCanPlayThrough;
}

void ChannelMediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
  MOZ_ASSERT(NS_IsMainThread());
  switch (aEvent.mType) {
    case MediaPlaybackEvent::PlaybackStarted:
      mPlaybackPosition = aEvent.mData.as<int64_t>();
      mPlaybackStatistics.Start();
      break;
    case MediaPlaybackEvent::PlaybackProgressed: {
      int64_t newPos = aEvent.mData.as<int64_t>();
      mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
      mPlaybackPosition = newPos;
      break;
    }
    case MediaPlaybackEvent::PlaybackStopped: {
      int64_t newPos = aEvent.mData.as<int64_t>();
      mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
      mPlaybackPosition = newPos;
      mPlaybackStatistics.Stop();
      break;
    }
    default:
      break;
  }
  MediaDecoder::OnPlaybackEvent(std::move(aEvent));
}

void ChannelMediaDecoder::DurationChanged() {
  MOZ_ASSERT(NS_IsMainThread());
  AbstractThread::AutoEnter context(AbstractMainThread());
  MediaDecoder::DurationChanged();
  // Duration has changed so we should recompute playback rate
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "ChannelMediaDecoder::UpdatePlaybackRate",
      [stats = mPlaybackStatistics, res = RefPtr<BaseMediaResource>(mResource),
       duration = mDuration]() {
        auto rate = ComputePlaybackRate(stats, res, duration);
        UpdatePlaybackRate(rate, res);
      });
  nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
  Unused << rv;
}

void ChannelMediaDecoder::DownloadProgressed() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());

  GetOwner()->DownloadProgressed();

  using StatsPromise = MozPromise<MediaStatistics, bool, true>;
  InvokeAsync(GetStateMachine()->OwnerThread(), __func__,
              [playbackStats = mPlaybackStatistics,
               res = RefPtr<BaseMediaResource>(mResource), duration = mDuration,
               pos = mPlaybackPosition]() {
                auto rate = ComputePlaybackRate(playbackStats, res, duration);
                UpdatePlaybackRate(rate, res);
                MediaStatistics stats = GetStatistics(rate, res, pos);
                return StatsPromise::CreateAndResolve(stats, __func__);
              })
      ->Then(mAbstractMainThread, __func__,
             [=, self = RefPtr<ChannelMediaDecoder>(this)](
                 MediaStatistics aStats) {
               if (IsShutdown()) {
                 return;
               }
               mCanPlayThrough = aStats.CanPlayThrough();
               GetStateMachine()->DispatchCanPlayThrough(mCanPlayThrough);
               mResource->ThrottleReadahead(ShouldThrottleDownload(aStats));
               // Update readyState since mCanPlayThrough might have changed.
               GetOwner()->UpdateReadyState();
             },
             []() { MOZ_ASSERT_UNREACHABLE("Promise not resolved"); });
}

/* static */ ChannelMediaDecoder::PlaybackRateInfo
ChannelMediaDecoder::ComputePlaybackRate(const MediaChannelStatistics& aStats,
                                         BaseMediaResource* aResource,
                                         double aDuration) {
  MOZ_ASSERT(!NS_IsMainThread());

  int64_t length = aResource->GetLength();
  if (mozilla::IsFinite<double>(aDuration) && aDuration > 0 && length >= 0) {
    return {uint32_t(length / aDuration), true};
  }

  bool reliable = false;
  uint32_t rate = aStats.GetRate(&reliable);
  return {rate, reliable};
}

/* static */ void ChannelMediaDecoder::UpdatePlaybackRate(
    const PlaybackRateInfo& aInfo, BaseMediaResource* aResource) {
  MOZ_ASSERT(!NS_IsMainThread());

  uint32_t rate = aInfo.mRate;

  if (aInfo.mReliable) {
    // Avoid passing a zero rate
    rate = std::max(rate, 1u);
  } else {
    // Set a minimum rate of 10,000 bytes per second ... sometimes we just
    // don't have good data
    rate = std::max(rate, 10000u);
  }

  aResource->SetPlaybackRate(rate);
}

/* static */ MediaStatistics ChannelMediaDecoder::GetStatistics(
    const PlaybackRateInfo& aInfo, BaseMediaResource* aRes,
    int64_t aPlaybackPosition) {
  MOZ_ASSERT(!NS_IsMainThread());

  MediaStatistics result;
  result.mDownloadRate = aRes->GetDownloadRate(&result.mDownloadRateReliable);
  result.mDownloadPosition = aRes->GetCachedDataEnd(aPlaybackPosition);
  result.mTotalBytes = aRes->GetLength();
  result.mPlaybackRate = aInfo.mRate;
  result.mPlaybackRateReliable = aInfo.mReliable;
  result.mPlaybackPosition = aPlaybackPosition;
  return result;
}

bool ChannelMediaDecoder::ShouldThrottleDownload(
    const MediaStatistics& aStats) {
  // We throttle the download if either the throttle override pref is set
  // (so that we can always throttle in Firefox on mobile) or if the download
  // is fast enough that there's no concern about playback being interrupted.
  MOZ_ASSERT(NS_IsMainThread());
  NS_ENSURE_TRUE(GetStateMachine(), false);

  int64_t length = aStats.mTotalBytes;
  if (length > 0 &&
      length <= int64_t(StaticPrefs::MediaMemoryCacheMaxSize()) * 1024) {
    // Don't throttle the download of small resources. This is to speed
    // up seeking, as seeks into unbuffered ranges would require starting
    // up a new HTTP transaction, which adds latency.
    return false;
  }

  if (Preferences::GetBool("media.throttle-regardless-of-download-rate",
                           false)) {
    return true;
  }

  if (!aStats.mDownloadRateReliable || !aStats.mPlaybackRateReliable) {
    return false;
  }
  uint32_t factor =
      std::max(2u, Preferences::GetUint("media.throttle-factor", 2));
  return aStats.mDownloadRate > factor * aStats.mPlaybackRate;
}

void ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
  MOZ_ASSERT(NS_IsMainThread());
  if (mResource) {
    aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf);
  }
}

already_AddRefed<nsIPrincipal> ChannelMediaDecoder::GetCurrentPrincipal() {
  MOZ_ASSERT(NS_IsMainThread());
  return mResource ? mResource->GetCurrentPrincipal() : nullptr;
}

bool ChannelMediaDecoder::IsTransportSeekable() {
  MOZ_ASSERT(NS_IsMainThread());
  return mResource->IsTransportSeekable();
}

void ChannelMediaDecoder::SetLoadInBackground(bool aLoadInBackground) {
  MOZ_ASSERT(NS_IsMainThread());
  if (mResource) {
    mResource->SetLoadInBackground(aLoadInBackground);
  }
}

void ChannelMediaDecoder::Suspend() {
  MOZ_ASSERT(NS_IsMainThread());
  if (mResource) {
    mResource->Suspend(true);
  }
}

void ChannelMediaDecoder::Resume() {
  MOZ_ASSERT(NS_IsMainThread());
  if (mResource) {
    mResource->Resume();
  }
}

void ChannelMediaDecoder::MetadataLoaded(
    UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags,
    MediaDecoderEventVisibility aEventVisibility) {
  MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags),
                               aEventVisibility);
  // Set mode to PLAYBACK after reading metadata.
  mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
}

nsCString ChannelMediaDecoder::GetDebugInfo() {
  nsCString str = MediaDecoder::GetDebugInfo();
  if (mResource) {
    AppendStringIfNotEmpty(str, mResource->GetDebugInfo());
  }
  return str;
}

}  // namespace mozilla

// avoid redefined macro in unified build
#undef LOG