Backed out changeset 06f41c22f3a6 (bug 1888460) for causing linux xpcshell failures...
[gecko.git] / dom / media / ChannelMediaDecoder.cpp
blob12f6c11e47e34fcaeb15b9e27f75c87569a6ce24
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/. */
7 #include "ChannelMediaDecoder.h"
8 #include "ChannelMediaResource.h"
9 #include "DecoderTraits.h"
10 #include "ExternalEngineStateMachine.h"
11 #include "MediaDecoderStateMachine.h"
12 #include "MediaFormatReader.h"
13 #include "BaseMediaResource.h"
14 #include "MediaShutdownManager.h"
15 #include "base/process_util.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/StaticPrefs_media.h"
18 #include "VideoUtils.h"
20 namespace mozilla {
22 using TimeUnit = media::TimeUnit;
24 extern LazyLogModule gMediaDecoderLog;
25 #define LOG(x, ...) \
26 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
28 ChannelMediaDecoder::ResourceCallback::ResourceCallback(
29 AbstractThread* aMainThread)
30 : mAbstractMainThread(aMainThread) {
31 MOZ_ASSERT(aMainThread);
32 DecoderDoctorLogger::LogConstructionAndBase(
33 "ChannelMediaDecoder::ResourceCallback", this,
34 static_cast<const MediaResourceCallback*>(this));
37 ChannelMediaDecoder::ResourceCallback::~ResourceCallback() {
38 DecoderDoctorLogger::LogDestruction("ChannelMediaDecoder::ResourceCallback",
39 this);
42 void ChannelMediaDecoder::ResourceCallback::Connect(
43 ChannelMediaDecoder* aDecoder) {
44 MOZ_ASSERT(NS_IsMainThread());
45 mDecoder = aDecoder;
46 DecoderDoctorLogger::LinkParentAndChild(
47 "ChannelMediaDecoder::ResourceCallback", this, "decoder", mDecoder);
48 mTimer = NS_NewTimer(mAbstractMainThread->AsEventTarget());
51 void ChannelMediaDecoder::ResourceCallback::Disconnect() {
52 MOZ_ASSERT(NS_IsMainThread());
53 if (mDecoder) {
54 DecoderDoctorLogger::UnlinkParentAndChild(
55 "ChannelMediaDecoder::ResourceCallback", this, mDecoder);
56 mDecoder = nullptr;
57 mTimer->Cancel();
58 mTimer = nullptr;
62 AbstractThread* ChannelMediaDecoder::ResourceCallback::AbstractMainThread()
63 const {
64 return mAbstractMainThread;
67 MediaDecoderOwner* ChannelMediaDecoder::ResourceCallback::GetMediaOwner()
68 const {
69 MOZ_ASSERT(NS_IsMainThread());
70 return mDecoder ? mDecoder->GetOwner() : nullptr;
73 void ChannelMediaDecoder::ResourceCallback::NotifyNetworkError(
74 const MediaResult& aError) {
75 MOZ_ASSERT(NS_IsMainThread());
76 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
77 "network_error", aError);
78 if (mDecoder) {
79 mDecoder->NetworkError(aError);
83 /* static */
84 void ChannelMediaDecoder::ResourceCallback::TimerCallback(nsITimer* aTimer,
85 void* aClosure) {
86 MOZ_ASSERT(NS_IsMainThread());
87 ResourceCallback* thiz = static_cast<ResourceCallback*>(aClosure);
88 MOZ_ASSERT(thiz->mDecoder);
89 thiz->mDecoder->NotifyReaderDataArrived();
90 thiz->mTimerArmed = false;
93 void ChannelMediaDecoder::ResourceCallback::NotifyDataArrived() {
94 MOZ_ASSERT(NS_IsMainThread());
95 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
96 "data_arrived", true);
98 if (!mDecoder) {
99 return;
102 mDecoder->DownloadProgressed();
104 if (mTimerArmed) {
105 return;
107 // In situations where these notifications come from stochastic network
108 // activity, we can save significant computation by throttling the
109 // calls to MediaDecoder::NotifyDataArrived() which will update the buffer
110 // ranges of the reader.
111 mTimerArmed = true;
112 mTimer->InitWithNamedFuncCallback(
113 TimerCallback, this, sDelay, nsITimer::TYPE_ONE_SHOT,
114 "ChannelMediaDecoder::ResourceCallback::TimerCallback");
117 void ChannelMediaDecoder::ResourceCallback::NotifyDataEnded(nsresult aStatus) {
118 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
119 "data_ended", aStatus);
120 MOZ_ASSERT(NS_IsMainThread());
121 if (mDecoder) {
122 mDecoder->NotifyDownloadEnded(aStatus);
126 void ChannelMediaDecoder::ResourceCallback::NotifyPrincipalChanged() {
127 MOZ_ASSERT(NS_IsMainThread());
128 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
129 "principal_changed", true);
130 if (mDecoder) {
131 mDecoder->NotifyPrincipalChanged();
135 void ChannelMediaDecoder::NotifyPrincipalChanged() {
136 MOZ_ASSERT(NS_IsMainThread());
137 MediaDecoder::NotifyPrincipalChanged();
138 if (!mInitialChannelPrincipalKnown) {
139 // We'll receive one notification when the channel's initial principal
140 // is known, after all HTTP redirects have resolved. This isn't really a
141 // principal change, so return here to avoid the mSameOriginMedia check
142 // below.
143 mInitialChannelPrincipalKnown = true;
144 return;
146 if (!mSameOriginMedia) {
147 // Block mid-flight redirects to non CORS same origin destinations.
148 // See bugs 1441153, 1443942.
149 LOG("ChannnelMediaDecoder prohibited cross origin redirect blocked.");
150 NetworkError(MediaResult(NS_ERROR_DOM_BAD_URI,
151 "Prohibited cross origin redirect blocked"));
155 void ChannelMediaDecoder::ResourceCallback::NotifySuspendedStatusChanged(
156 bool aSuspendedByCache) {
157 MOZ_ASSERT(NS_IsMainThread());
158 DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
159 "suspended_status_changed", aSuspendedByCache);
160 MediaDecoderOwner* owner = GetMediaOwner();
161 if (owner) {
162 owner->NotifySuspendedByCache(aSuspendedByCache);
166 ChannelMediaDecoder::ChannelMediaDecoder(MediaDecoderInit& aInit)
167 : MediaDecoder(aInit),
168 mResourceCallback(
169 new ResourceCallback(aInit.mOwner->AbstractMainThread())) {
170 mResourceCallback->Connect(this);
173 /* static */
174 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Create(
175 MediaDecoderInit& aInit, DecoderDoctorDiagnostics* aDiagnostics) {
176 MOZ_ASSERT(NS_IsMainThread());
177 RefPtr<ChannelMediaDecoder> decoder;
178 if (DecoderTraits::CanHandleContainerType(aInit.mContainerType,
179 aDiagnostics) != CANPLAY_NO) {
180 decoder = new ChannelMediaDecoder(aInit);
181 return decoder.forget();
184 return nullptr;
187 bool ChannelMediaDecoder::CanClone() {
188 MOZ_ASSERT(NS_IsMainThread());
189 return mResource && mResource->CanClone();
192 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Clone(
193 MediaDecoderInit& aInit) {
194 if (!mResource || DecoderTraits::CanHandleContainerType(
195 aInit.mContainerType, nullptr) == CANPLAY_NO) {
196 return nullptr;
198 RefPtr<ChannelMediaDecoder> decoder = new ChannelMediaDecoder(aInit);
199 nsresult rv = decoder->Load(mResource);
200 if (NS_FAILED(rv)) {
201 decoder->Shutdown();
202 return nullptr;
204 return decoder.forget();
207 MediaDecoderStateMachineBase* ChannelMediaDecoder::CreateStateMachine(
208 bool aDisableExternalEngine) {
209 MOZ_ASSERT(NS_IsMainThread());
210 MediaFormatReaderInit init;
211 init.mVideoFrameContainer = GetVideoFrameContainer();
212 init.mKnowsCompositor = GetCompositor();
213 init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
214 init.mFrameStats = mFrameStats;
215 init.mResource = mResource;
216 init.mMediaDecoderOwnerID = mOwner;
217 static Atomic<uint32_t> sTrackingIdCounter(0);
218 init.mTrackingId.emplace(TrackingId::Source::ChannelDecoder,
219 sTrackingIdCounter++,
220 TrackingId::TrackAcrossProcesses::Yes);
221 mReader = DecoderTraits::CreateReader(ContainerType(), init);
223 #ifdef MOZ_WMF_MEDIA_ENGINE
224 // This state machine is mainly used for the encrypted playback. However, for
225 // testing purpose we would also use it the non-encrypted playback.
226 // 1=enabled encrypted and clear, 3=enabled clear
227 if ((StaticPrefs::media_wmf_media_engine_enabled() == 1 ||
228 StaticPrefs::media_wmf_media_engine_enabled() == 3) &&
229 StaticPrefs::media_wmf_media_engine_channel_decoder_enabled() &&
230 !aDisableExternalEngine) {
231 return new ExternalEngineStateMachine(this, mReader);
233 #endif
234 return new MediaDecoderStateMachine(this, mReader);
237 void ChannelMediaDecoder::Shutdown() {
238 mResourceCallback->Disconnect();
239 MediaDecoder::Shutdown();
241 if (mResource) {
242 // Force any outstanding seek and byterange requests to complete
243 // to prevent shutdown from deadlocking.
244 mResourceClosePromise = mResource->Close();
248 void ChannelMediaDecoder::ShutdownInternal() {
249 if (!mResourceClosePromise) {
250 MediaShutdownManager::Instance().Unregister(this);
251 return;
254 mResourceClosePromise->Then(
255 AbstractMainThread(), __func__,
256 [self = RefPtr<ChannelMediaDecoder>(this)] {
257 MediaShutdownManager::Instance().Unregister(self);
261 nsresult ChannelMediaDecoder::Load(nsIChannel* aChannel,
262 bool aIsPrivateBrowsing,
263 nsIStreamListener** aStreamListener) {
264 MOZ_ASSERT(NS_IsMainThread());
265 MOZ_ASSERT(!mResource);
266 MOZ_ASSERT(aStreamListener);
268 mResource = BaseMediaResource::Create(mResourceCallback, aChannel,
269 aIsPrivateBrowsing);
270 if (!mResource) {
271 return NS_ERROR_FAILURE;
273 DDLINKCHILD("resource", mResource.get());
275 nsresult rv = MediaShutdownManager::Instance().Register(this);
276 if (NS_WARN_IF(NS_FAILED(rv))) {
277 return rv;
280 rv = mResource->Open(aStreamListener);
281 NS_ENSURE_SUCCESS(rv, rv);
282 return CreateAndInitStateMachine(mResource->IsLiveStream());
285 nsresult ChannelMediaDecoder::Load(BaseMediaResource* aOriginal) {
286 MOZ_ASSERT(NS_IsMainThread());
287 MOZ_ASSERT(!mResource);
289 mResource = aOriginal->CloneData(mResourceCallback);
290 if (!mResource) {
291 return NS_ERROR_FAILURE;
293 DDLINKCHILD("resource", mResource.get());
295 nsresult rv = MediaShutdownManager::Instance().Register(this);
296 if (NS_WARN_IF(NS_FAILED(rv))) {
297 return rv;
299 return CreateAndInitStateMachine(mResource->IsLiveStream());
302 void ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus) {
303 MOZ_ASSERT(NS_IsMainThread());
304 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
306 LOG("NotifyDownloadEnded, status=%" PRIx32, static_cast<uint32_t>(aStatus));
308 if (NS_SUCCEEDED(aStatus)) {
309 // Download ends successfully. This is a stream with a finite length.
310 GetStateMachine()->DispatchIsLiveStream(false);
313 MediaDecoderOwner* owner = GetOwner();
314 if (NS_SUCCEEDED(aStatus) || aStatus == NS_BASE_STREAM_CLOSED) {
315 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
316 "ChannelMediaDecoder::UpdatePlaybackRate",
317 [stats = mPlaybackStatistics,
318 res = RefPtr<BaseMediaResource>(mResource), duration = mDuration]() {
319 auto rate = ComputePlaybackRate(stats, res,
320 duration.match(DurationToTimeUnit()));
321 UpdatePlaybackRate(rate, res);
323 nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
324 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
325 Unused << rv;
326 owner->DownloadSuspended();
327 // NotifySuspendedStatusChanged will tell the element that download
328 // has been suspended "by the cache", which is true since we never
329 // download anything. The element can then transition to HAVE_ENOUGH_DATA.
330 owner->NotifySuspendedByCache(true);
331 } else if (aStatus == NS_BINDING_ABORTED) {
332 // Download has been cancelled by user.
333 owner->LoadAborted();
334 } else {
335 NetworkError(MediaResult(aStatus, "Download aborted"));
339 bool ChannelMediaDecoder::CanPlayThroughImpl() {
340 MOZ_ASSERT(NS_IsMainThread());
341 return mCanPlayThrough;
344 void ChannelMediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
345 MOZ_ASSERT(NS_IsMainThread());
346 switch (aEvent.mType) {
347 case MediaPlaybackEvent::PlaybackStarted:
348 mPlaybackPosition = aEvent.mData.as<int64_t>();
349 mPlaybackStatistics.Start();
350 break;
351 case MediaPlaybackEvent::PlaybackProgressed: {
352 int64_t newPos = aEvent.mData.as<int64_t>();
353 mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
354 mPlaybackPosition = newPos;
355 break;
357 case MediaPlaybackEvent::PlaybackStopped: {
358 int64_t newPos = aEvent.mData.as<int64_t>();
359 mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
360 mPlaybackPosition = newPos;
361 mPlaybackStatistics.Stop();
362 break;
364 default:
365 break;
367 MediaDecoder::OnPlaybackEvent(std::move(aEvent));
370 void ChannelMediaDecoder::DurationChanged() {
371 MOZ_ASSERT(NS_IsMainThread());
372 MediaDecoder::DurationChanged();
373 // Duration has changed so we should recompute playback rate
374 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
375 "ChannelMediaDecoder::UpdatePlaybackRate",
376 [stats = mPlaybackStatistics, res = RefPtr<BaseMediaResource>(mResource),
377 duration = mDuration]() {
378 auto rate = ComputePlaybackRate(stats, res,
379 duration.match(DurationToTimeUnit()));
380 UpdatePlaybackRate(rate, res);
382 nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
383 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
384 Unused << rv;
387 void ChannelMediaDecoder::DownloadProgressed() {
388 MOZ_ASSERT(NS_IsMainThread());
389 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
391 GetOwner()->DownloadProgressed();
393 using StatsPromise = MozPromise<MediaStatistics, bool, true>;
394 InvokeAsync(GetStateMachine()->OwnerThread(), __func__,
395 [playbackStats = mPlaybackStatistics,
396 res = RefPtr<BaseMediaResource>(mResource), duration = mDuration,
397 pos = mPlaybackPosition]() {
398 auto rate = ComputePlaybackRate(
399 playbackStats, res, duration.match(DurationToTimeUnit()));
400 UpdatePlaybackRate(rate, res);
401 MediaStatistics stats = GetStatistics(rate, res, pos);
402 return StatsPromise::CreateAndResolve(stats, __func__);
404 ->Then(
405 mAbstractMainThread, __func__,
407 self = RefPtr<ChannelMediaDecoder>(this)](MediaStatistics aStats) {
408 if (IsShutdown()) {
409 return;
411 mCanPlayThrough = aStats.CanPlayThrough();
412 GetStateMachine()->DispatchCanPlayThrough(mCanPlayThrough);
413 mResource->ThrottleReadahead(ShouldThrottleDownload(aStats));
414 // Update readyState since mCanPlayThrough might have changed.
415 GetOwner()->UpdateReadyState();
417 []() { MOZ_ASSERT_UNREACHABLE("Promise not resolved"); });
420 /* static */ ChannelMediaDecoder::PlaybackRateInfo
421 ChannelMediaDecoder::ComputePlaybackRate(const MediaChannelStatistics& aStats,
422 BaseMediaResource* aResource,
423 const TimeUnit& aDuration) {
424 MOZ_ASSERT(!NS_IsMainThread());
426 int64_t length = aResource->GetLength();
427 if (aDuration.IsValid() && !aDuration.IsInfinite() &&
428 aDuration.IsPositive() && length >= 0 &&
429 length / aDuration.ToSeconds() < UINT32_MAX) {
430 return {uint32_t(length / aDuration.ToSeconds()), true};
433 bool reliable = false;
434 uint32_t rate = aStats.GetRate(&reliable);
435 return {rate, reliable};
438 /* static */
439 void ChannelMediaDecoder::UpdatePlaybackRate(const PlaybackRateInfo& aInfo,
440 BaseMediaResource* aResource) {
441 MOZ_ASSERT(!NS_IsMainThread());
443 uint32_t rate = aInfo.mRate;
445 if (aInfo.mReliable) {
446 // Avoid passing a zero rate
447 rate = std::max(rate, 1u);
448 } else {
449 // Set a minimum rate of 10,000 bytes per second ... sometimes we just
450 // don't have good data
451 rate = std::max(rate, 10000u);
454 aResource->SetPlaybackRate(rate);
457 /* static */
458 MediaStatistics ChannelMediaDecoder::GetStatistics(
459 const PlaybackRateInfo& aInfo, BaseMediaResource* aRes,
460 int64_t aPlaybackPosition) {
461 MOZ_ASSERT(!NS_IsMainThread());
463 MediaStatistics result;
464 result.mDownloadRate = aRes->GetDownloadRate(&result.mDownloadRateReliable);
465 result.mDownloadPosition = aRes->GetCachedDataEnd(aPlaybackPosition);
466 result.mTotalBytes = aRes->GetLength();
467 result.mPlaybackRate = aInfo.mRate;
468 result.mPlaybackRateReliable = aInfo.mReliable;
469 result.mPlaybackPosition = aPlaybackPosition;
470 return result;
473 bool ChannelMediaDecoder::ShouldThrottleDownload(
474 const MediaStatistics& aStats) {
475 // We throttle the download if either the throttle override pref is set
476 // (so that we always throttle at the readahead limit on mobile if using
477 // a cellular network) or if the download is fast enough that there's no
478 // concern about playback being interrupted.
479 MOZ_ASSERT(NS_IsMainThread());
480 NS_ENSURE_TRUE(GetStateMachine(), false);
482 int64_t length = aStats.mTotalBytes;
483 if (length > 0 &&
484 length <= int64_t(StaticPrefs::media_memory_cache_max_size()) * 1024) {
485 // Don't throttle the download of small resources. This is to speed
486 // up seeking, as seeks into unbuffered ranges would require starting
487 // up a new HTTP transaction, which adds latency.
488 return false;
491 if (OnCellularConnection() &&
492 Preferences::GetBool(
493 "media.throttle-cellular-regardless-of-download-rate", false)) {
494 return true;
497 if (!aStats.mDownloadRateReliable || !aStats.mPlaybackRateReliable) {
498 return false;
500 uint32_t factor =
501 std::max(2u, Preferences::GetUint("media.throttle-factor", 2));
502 return aStats.mDownloadRate > factor * aStats.mPlaybackRate;
505 void ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
506 MOZ_ASSERT(NS_IsMainThread());
507 if (mResource) {
508 aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf);
512 already_AddRefed<nsIPrincipal> ChannelMediaDecoder::GetCurrentPrincipal() {
513 MOZ_ASSERT(NS_IsMainThread());
514 return mResource ? mResource->GetCurrentPrincipal() : nullptr;
517 bool ChannelMediaDecoder::HadCrossOriginRedirects() {
518 MOZ_ASSERT(NS_IsMainThread());
519 return mResource ? mResource->HadCrossOriginRedirects() : false;
522 bool ChannelMediaDecoder::IsTransportSeekable() {
523 MOZ_ASSERT(NS_IsMainThread());
524 return mResource->IsTransportSeekable();
527 void ChannelMediaDecoder::SetLoadInBackground(bool aLoadInBackground) {
528 MOZ_ASSERT(NS_IsMainThread());
529 if (mResource) {
530 mResource->SetLoadInBackground(aLoadInBackground);
534 void ChannelMediaDecoder::Suspend() {
535 MOZ_ASSERT(NS_IsMainThread());
536 if (mResource) {
537 mResource->Suspend(true);
539 MediaDecoder::Suspend();
542 void ChannelMediaDecoder::Resume() {
543 MOZ_ASSERT(NS_IsMainThread());
544 if (mResource) {
545 mResource->Resume();
547 MediaDecoder::Resume();
550 void ChannelMediaDecoder::MetadataLoaded(
551 UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags,
552 MediaDecoderEventVisibility aEventVisibility) {
553 MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags),
554 aEventVisibility);
555 // Set mode to PLAYBACK after reading metadata.
556 mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
559 void ChannelMediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo) {
560 MediaDecoder::GetDebugInfo(aInfo);
561 if (mResource) {
562 mResource->GetDebugInfo(aInfo.mResource);
566 } // namespace mozilla
568 // avoid redefined macro in unified build
569 #undef LOG