gecko-dev/dom/media/ChannelMediaDecoder.cpp
Nicholas Nethercote 18fae65f38 Bug 1563139 - Remove StaticPrefs.h. r=glandium
This requires replacing inclusions of it with inclusions of more specific prefs
files.

The exception is that StaticPrefsAll.h, which is equivalent to StaticPrefs.h,
and is used in `Codegen.py` because doing something smarter is tricky and
suitable for a follow-up. As a result, any change to StaticPrefList.yaml will
still trigger recompilation of all the generated DOM bindings files, but that's
still a big improvement over trigger recompilation of every file that uses
static prefs.

Most of the changes in this commit are very boring. The only changes that are
not boring are modules/libpref/*, Codegen.py, and ServoBindings.toml.

Differential Revision: https://phabricator.services.mozilla.com/D39138

--HG--
extra : moz-landing-system : lando
2019-07-26 01:10:23 +00:00

555 lines
18 KiB
C++

/* -*- 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 "ChannelMediaResource.h"
#include "DecoderTraits.h"
#include "MediaDecoderStateMachine.h"
#include "MediaFormatReader.h"
#include "BaseMediaResource.h"
#include "MediaShutdownManager.h"
#include "mozilla/StaticPrefs_media.h"
#include "VideoUtils.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 &&
length / aDuration < UINT32_MAX) {
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 always throttle at the readahead limit on mobile if using
// a cellular network) 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::media_memory_cache_max_size()) * 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 (OnCellularConnection() &&
Preferences::GetBool(
"media.throttle-cellular-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::HadCrossOriginRedirects() {
MOZ_ASSERT(NS_IsMainThread());
return mResource ? mResource->HadCrossOriginRedirects() : false;
}
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);
}
void ChannelMediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo) {
MediaDecoder::GetDebugInfo(aInfo);
if (mResource) {
mResource->GetDebugInfo(aInfo.mResource);
}
}
} // namespace mozilla
// avoid redefined macro in unified build
#undef LOG