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"
22 using TimeUnit
= media::TimeUnit
;
24 extern LazyLogModule gMediaDecoderLog
;
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",
42 void ChannelMediaDecoder::ResourceCallback::Connect(
43 ChannelMediaDecoder
* aDecoder
) {
44 MOZ_ASSERT(NS_IsMainThread());
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());
54 DecoderDoctorLogger::UnlinkParentAndChild(
55 "ChannelMediaDecoder::ResourceCallback", this, mDecoder
);
62 AbstractThread
* ChannelMediaDecoder::ResourceCallback::AbstractMainThread()
64 return mAbstractMainThread
;
67 MediaDecoderOwner
* ChannelMediaDecoder::ResourceCallback::GetMediaOwner()
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
);
79 mDecoder
->NetworkError(aError
);
84 void ChannelMediaDecoder::ResourceCallback::TimerCallback(nsITimer
* aTimer
,
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);
102 mDecoder
->DownloadProgressed();
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.
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());
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);
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
143 mInitialChannelPrincipalKnown
= true;
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();
162 owner
->NotifySuspendedByCache(aSuspendedByCache
);
166 ChannelMediaDecoder::ChannelMediaDecoder(MediaDecoderInit
& aInit
)
167 : MediaDecoder(aInit
),
169 new ResourceCallback(aInit
.mOwner
->AbstractMainThread())) {
170 mResourceCallback
->Connect(this);
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();
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
) {
198 RefPtr
<ChannelMediaDecoder
> decoder
= new ChannelMediaDecoder(aInit
);
199 nsresult rv
= decoder
->Load(mResource
);
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
);
234 return new MediaDecoderStateMachine(this, mReader
);
237 void ChannelMediaDecoder::Shutdown() {
238 mResourceCallback
->Disconnect();
239 MediaDecoder::Shutdown();
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);
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
,
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
))) {
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
);
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
))) {
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
));
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();
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();
351 case MediaPlaybackEvent::PlaybackProgressed
: {
352 int64_t newPos
= aEvent
.mData
.as
<int64_t>();
353 mPlaybackStatistics
.AddBytes(newPos
- mPlaybackPosition
);
354 mPlaybackPosition
= newPos
;
357 case MediaPlaybackEvent::PlaybackStopped
: {
358 int64_t newPos
= aEvent
.mData
.as
<int64_t>();
359 mPlaybackStatistics
.AddBytes(newPos
- mPlaybackPosition
);
360 mPlaybackPosition
= newPos
;
361 mPlaybackStatistics
.Stop();
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
));
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__
);
405 mAbstractMainThread
, __func__
,
407 self
= RefPtr
<ChannelMediaDecoder
>(this)](MediaStatistics aStats
) {
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
};
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);
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
);
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
;
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
;
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.
491 if (OnCellularConnection() &&
492 Preferences::GetBool(
493 "media.throttle-cellular-regardless-of-download-rate", false)) {
497 if (!aStats
.mDownloadRateReliable
|| !aStats
.mPlaybackRateReliable
) {
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());
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());
530 mResource
->SetLoadInBackground(aLoadInBackground
);
534 void ChannelMediaDecoder::Suspend() {
535 MOZ_ASSERT(NS_IsMainThread());
537 mResource
->Suspend(true);
539 MediaDecoder::Suspend();
542 void ChannelMediaDecoder::Resume() {
543 MOZ_ASSERT(NS_IsMainThread());
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
),
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
);
562 mResource
->GetDebugInfo(aInfo
.mResource
);
566 } // namespace mozilla
568 // avoid redefined macro in unified build