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 #include "DecoderTraits.h"
9 #include "MediaDecoderStateMachine.h"
10 #include "MediaFormatReader.h"
11 #include "BaseMediaResource.h"
12 #include "MediaShutdownManager.h"
13 
14 namespace mozilla {
15 
16 extern LazyLogModule gMediaDecoderLog;
17 #define LOG(x, ...) \
18   DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
19 
ResourceCallback(AbstractThread * aMainThread)20 ChannelMediaDecoder::ResourceCallback::ResourceCallback(
21     AbstractThread* aMainThread)
22     : mAbstractMainThread(aMainThread) {
23   MOZ_ASSERT(aMainThread);
24   DecoderDoctorLogger::LogConstructionAndBase(
25       "ChannelMediaDecoder::ResourceCallback", this,
26       static_cast<const MediaResourceCallback*>(this));
27 }
28 
~ResourceCallback()29 ChannelMediaDecoder::ResourceCallback::~ResourceCallback() {
30   DecoderDoctorLogger::LogDestruction("ChannelMediaDecoder::ResourceCallback",
31                                       this);
32 }
33 
Connect(ChannelMediaDecoder * aDecoder)34 void ChannelMediaDecoder::ResourceCallback::Connect(
35     ChannelMediaDecoder* aDecoder) {
36   MOZ_ASSERT(NS_IsMainThread());
37   mDecoder = aDecoder;
38   DecoderDoctorLogger::LinkParentAndChild(
39       "ChannelMediaDecoder::ResourceCallback", this, "decoder", mDecoder);
40   mTimer = NS_NewTimer(mAbstractMainThread->AsEventTarget());
41 }
42 
Disconnect()43 void ChannelMediaDecoder::ResourceCallback::Disconnect() {
44   MOZ_ASSERT(NS_IsMainThread());
45   if (mDecoder) {
46     DecoderDoctorLogger::UnlinkParentAndChild(
47         "ChannelMediaDecoder::ResourceCallback", this, mDecoder);
48     mDecoder = nullptr;
49     mTimer->Cancel();
50     mTimer = nullptr;
51   }
52 }
53 
AbstractMainThread() const54 AbstractThread* ChannelMediaDecoder::ResourceCallback::AbstractMainThread()
55     const {
56   return mAbstractMainThread;
57 }
58 
GetMediaOwner() const59 MediaDecoderOwner* ChannelMediaDecoder::ResourceCallback::GetMediaOwner()
60     const {
61   MOZ_ASSERT(NS_IsMainThread());
62   return mDecoder ? mDecoder->GetOwner() : nullptr;
63 }
64 
NotifyNetworkError(const MediaResult & aError)65 void ChannelMediaDecoder::ResourceCallback::NotifyNetworkError(
66     const MediaResult& aError) {
67   MOZ_ASSERT(NS_IsMainThread());
68   DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
69            "network_error", aError);
70   if (mDecoder) {
71     mDecoder->NetworkError(aError);
72   }
73 }
74 
TimerCallback(nsITimer * aTimer,void * aClosure)75 /* static */ void ChannelMediaDecoder::ResourceCallback::TimerCallback(
76     nsITimer* aTimer, void* aClosure) {
77   MOZ_ASSERT(NS_IsMainThread());
78   ResourceCallback* thiz = static_cast<ResourceCallback*>(aClosure);
79   MOZ_ASSERT(thiz->mDecoder);
80   thiz->mDecoder->NotifyReaderDataArrived();
81   thiz->mTimerArmed = false;
82 }
83 
NotifyDataArrived()84 void ChannelMediaDecoder::ResourceCallback::NotifyDataArrived() {
85   MOZ_ASSERT(NS_IsMainThread());
86   DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
87            "data_arrived", true);
88 
89   if (!mDecoder) {
90     return;
91   }
92 
93   mDecoder->DownloadProgressed();
94 
95   if (mTimerArmed) {
96     return;
97   }
98   // In situations where these notifications come from stochastic network
99   // activity, we can save significant computation by throttling the
100   // calls to MediaDecoder::NotifyDataArrived() which will update the buffer
101   // ranges of the reader.
102   mTimerArmed = true;
103   mTimer->InitWithNamedFuncCallback(
104       TimerCallback, this, sDelay, nsITimer::TYPE_ONE_SHOT,
105       "ChannelMediaDecoder::ResourceCallback::TimerCallback");
106 }
107 
NotifyDataEnded(nsresult aStatus)108 void ChannelMediaDecoder::ResourceCallback::NotifyDataEnded(nsresult aStatus) {
109   DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
110            "data_ended", aStatus);
111   MOZ_ASSERT(NS_IsMainThread());
112   if (mDecoder) {
113     mDecoder->NotifyDownloadEnded(aStatus);
114   }
115 }
116 
NotifyPrincipalChanged()117 void ChannelMediaDecoder::ResourceCallback::NotifyPrincipalChanged() {
118   MOZ_ASSERT(NS_IsMainThread());
119   DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
120            "principal_changed", true);
121   if (mDecoder) {
122     mDecoder->NotifyPrincipalChanged();
123   }
124 }
125 
NotifyPrincipalChanged()126 void ChannelMediaDecoder::NotifyPrincipalChanged() {
127   MOZ_ASSERT(NS_IsMainThread());
128   MediaDecoder::NotifyPrincipalChanged();
129   if (!mInitialChannelPrincipalKnown) {
130     // We'll receive one notification when the channel's initial principal
131     // is known, after all HTTP redirects have resolved. This isn't really a
132     // principal change, so return here to avoid the mSameOriginMedia check
133     // below.
134     mInitialChannelPrincipalKnown = true;
135     return;
136   }
137   if (!mSameOriginMedia &&
138       DecoderTraits::CrossOriginRedirectsProhibited(ContainerType())) {
139     // For some content types we block mid-flight channel redirects to cross
140     // origin destinations due to security constraints. See bug 1441153.
141     LOG("ChannnelMediaDecoder prohibited cross origin redirect blocked.");
142     NetworkError(MediaResult(NS_ERROR_DOM_BAD_URI,
143                              "Prohibited cross origin redirect blocked"));
144   }
145 }
146 
NotifySuspendedStatusChanged(bool aSuspendedByCache)147 void ChannelMediaDecoder::ResourceCallback::NotifySuspendedStatusChanged(
148     bool aSuspendedByCache) {
149   MOZ_ASSERT(NS_IsMainThread());
150   DDLOGEX2("ChannelMediaDecoder::ResourceCallback", this, DDLogCategory::Log,
151            "suspended_status_changed", aSuspendedByCache);
152   MediaDecoderOwner* owner = GetMediaOwner();
153   if (owner) {
154     AbstractThread::AutoEnter context(owner->AbstractMainThread());
155     owner->NotifySuspendedByCache(aSuspendedByCache);
156   }
157 }
158 
ChannelMediaDecoder(MediaDecoderInit & aInit)159 ChannelMediaDecoder::ChannelMediaDecoder(MediaDecoderInit& aInit)
160     : MediaDecoder(aInit),
161       mResourceCallback(
162           new ResourceCallback(aInit.mOwner->AbstractMainThread())) {
163   mResourceCallback->Connect(this);
164 }
165 
166 /* static */
Create(MediaDecoderInit & aInit,DecoderDoctorDiagnostics * aDiagnostics)167 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Create(
168     MediaDecoderInit& aInit, DecoderDoctorDiagnostics* aDiagnostics) {
169   MOZ_ASSERT(NS_IsMainThread());
170   RefPtr<ChannelMediaDecoder> decoder;
171 
172   const MediaContainerType& type = aInit.mContainerType;
173   if (DecoderTraits::IsSupportedType(type)) {
174     decoder = new ChannelMediaDecoder(aInit);
175     return decoder.forget();
176   }
177 
178   if (DecoderTraits::IsHttpLiveStreamingType(type)) {
179     // We don't have an HLS decoder.
180     Telemetry::Accumulate(Telemetry::MEDIA_HLS_DECODER_SUCCESS, false);
181   }
182 
183   return nullptr;
184 }
185 
CanClone()186 bool ChannelMediaDecoder::CanClone() {
187   MOZ_ASSERT(NS_IsMainThread());
188   return mResource && mResource->CanClone();
189 }
190 
Clone(MediaDecoderInit & aInit)191 already_AddRefed<ChannelMediaDecoder> ChannelMediaDecoder::Clone(
192     MediaDecoderInit& aInit) {
193   if (!mResource || !DecoderTraits::IsSupportedType(aInit.mContainerType)) {
194     return nullptr;
195   }
196   RefPtr<ChannelMediaDecoder> decoder = new ChannelMediaDecoder(aInit);
197   if (!decoder) {
198     return nullptr;
199   }
200   nsresult rv = decoder->Load(mResource);
201   if (NS_FAILED(rv)) {
202     decoder->Shutdown();
203     return nullptr;
204   }
205   return decoder.forget();
206 }
207 
CreateStateMachine()208 MediaDecoderStateMachine* ChannelMediaDecoder::CreateStateMachine() {
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   mReader = DecoderTraits::CreateReader(ContainerType(), init);
218   return new MediaDecoderStateMachine(this, mReader);
219 }
220 
Shutdown()221 void ChannelMediaDecoder::Shutdown() {
222   mResourceCallback->Disconnect();
223   MediaDecoder::Shutdown();
224 
225   // Force any outstanding seek and byterange requests to complete
226   // to prevent shutdown from deadlocking.
227   if (mResource) {
228     mResource->Close();
229   }
230 }
231 
Load(nsIChannel * aChannel,bool aIsPrivateBrowsing,nsIStreamListener ** aStreamListener)232 nsresult ChannelMediaDecoder::Load(nsIChannel* aChannel,
233                                    bool aIsPrivateBrowsing,
234                                    nsIStreamListener** aStreamListener) {
235   MOZ_ASSERT(NS_IsMainThread());
236   MOZ_ASSERT(!mResource);
237   MOZ_ASSERT(aStreamListener);
238   AbstractThread::AutoEnter context(AbstractMainThread());
239 
240   mResource = BaseMediaResource::Create(mResourceCallback, aChannel,
241                                         aIsPrivateBrowsing);
242   if (!mResource) {
243     return NS_ERROR_FAILURE;
244   }
245   DDLINKCHILD("resource", mResource.get());
246 
247   nsresult rv = MediaShutdownManager::Instance().Register(this);
248   if (NS_WARN_IF(NS_FAILED(rv))) {
249     return rv;
250   }
251 
252   rv = mResource->Open(aStreamListener);
253   NS_ENSURE_SUCCESS(rv, rv);
254 
255   SetStateMachine(CreateStateMachine());
256   NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);
257 
258   GetStateMachine()->DispatchIsLiveStream(mResource->IsLiveStream());
259 
260   return InitializeStateMachine();
261 }
262 
Load(BaseMediaResource * aOriginal)263 nsresult ChannelMediaDecoder::Load(BaseMediaResource* aOriginal) {
264   MOZ_ASSERT(NS_IsMainThread());
265   MOZ_ASSERT(!mResource);
266   AbstractThread::AutoEnter context(AbstractMainThread());
267 
268   mResource = aOriginal->CloneData(mResourceCallback);
269   if (!mResource) {
270     return NS_ERROR_FAILURE;
271   }
272   DDLINKCHILD("resource", mResource.get());
273 
274   nsresult rv = MediaShutdownManager::Instance().Register(this);
275   if (NS_WARN_IF(NS_FAILED(rv))) {
276     return rv;
277   }
278 
279   SetStateMachine(CreateStateMachine());
280   NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);
281 
282   GetStateMachine()->DispatchIsLiveStream(mResource->IsLiveStream());
283 
284   return InitializeStateMachine();
285 }
286 
NotifyDownloadEnded(nsresult aStatus)287 void ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus) {
288   MOZ_ASSERT(NS_IsMainThread());
289   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
290   AbstractThread::AutoEnter context(AbstractMainThread());
291 
292   LOG("NotifyDownloadEnded, status=%" PRIx32, static_cast<uint32_t>(aStatus));
293 
294   if (NS_SUCCEEDED(aStatus)) {
295     // Download ends successfully. This is a stream with a finite length.
296     GetStateMachine()->DispatchIsLiveStream(false);
297   }
298 
299   MediaDecoderOwner* owner = GetOwner();
300   if (NS_SUCCEEDED(aStatus) || aStatus == NS_BASE_STREAM_CLOSED) {
301     nsCOMPtr<nsIRunnable> r =
302         NS_NewRunnableFunction("ChannelMediaDecoder::UpdatePlaybackRate", [
303           stats = mPlaybackStatistics,
304           res = RefPtr<BaseMediaResource>(mResource), duration = mDuration
305         ]() {
306           auto rate = ComputePlaybackRate(stats, res, duration);
307           UpdatePlaybackRate(rate, res);
308         });
309     nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
310     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
311     Unused << rv;
312     owner->DownloadSuspended();
313     // NotifySuspendedStatusChanged will tell the element that download
314     // has been suspended "by the cache", which is true since we never
315     // download anything. The element can then transition to HAVE_ENOUGH_DATA.
316     owner->NotifySuspendedByCache(true);
317   } else if (aStatus == NS_BINDING_ABORTED) {
318     // Download has been cancelled by user.
319     owner->LoadAborted();
320   } else {
321     NetworkError(MediaResult(aStatus, "Download aborted"));
322   }
323 }
324 
CanPlayThroughImpl()325 bool ChannelMediaDecoder::CanPlayThroughImpl() {
326   MOZ_ASSERT(NS_IsMainThread());
327   return mCanPlayThrough;
328 }
329 
OnPlaybackEvent(MediaPlaybackEvent && aEvent)330 void ChannelMediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
331   MOZ_ASSERT(NS_IsMainThread());
332   switch (aEvent.mType) {
333     case MediaPlaybackEvent::PlaybackStarted:
334       mPlaybackPosition = aEvent.mData.as<int64_t>();
335       mPlaybackStatistics.Start();
336       break;
337     case MediaPlaybackEvent::PlaybackProgressed: {
338       int64_t newPos = aEvent.mData.as<int64_t>();
339       mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
340       mPlaybackPosition = newPos;
341       break;
342     }
343     case MediaPlaybackEvent::PlaybackStopped: {
344       int64_t newPos = aEvent.mData.as<int64_t>();
345       mPlaybackStatistics.AddBytes(newPos - mPlaybackPosition);
346       mPlaybackPosition = newPos;
347       mPlaybackStatistics.Stop();
348       break;
349     }
350     default:
351       break;
352   }
353   MediaDecoder::OnPlaybackEvent(Move(aEvent));
354 }
355 
DurationChanged()356 void ChannelMediaDecoder::DurationChanged() {
357   MOZ_ASSERT(NS_IsMainThread());
358   AbstractThread::AutoEnter context(AbstractMainThread());
359   MediaDecoder::DurationChanged();
360   // Duration has changed so we should recompute playback rate
361   nsCOMPtr<nsIRunnable> r =
362       NS_NewRunnableFunction("ChannelMediaDecoder::UpdatePlaybackRate", [
363         stats = mPlaybackStatistics, res = RefPtr<BaseMediaResource>(mResource),
364         duration = mDuration
365       ]() {
366         auto rate = ComputePlaybackRate(stats, res, duration);
367         UpdatePlaybackRate(rate, res);
368       });
369   nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
370   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
371   Unused << rv;
372 }
373 
DownloadProgressed()374 void ChannelMediaDecoder::DownloadProgressed() {
375   MOZ_ASSERT(NS_IsMainThread());
376   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
377 
378   GetOwner()->DownloadProgressed();
379 
380   using StatsPromise = MozPromise<MediaStatistics, bool, true>;
381   InvokeAsync(GetStateMachine()->OwnerThread(), __func__,
382               [
383                 playbackStats = mPlaybackStatistics,
384                 res = RefPtr<BaseMediaResource>(mResource),
385                 duration = mDuration, pos = mPlaybackPosition
386               ]() {
387                 auto rate = ComputePlaybackRate(playbackStats, res, duration);
388                 UpdatePlaybackRate(rate, res);
389                 MediaStatistics stats = GetStatistics(rate, res, pos);
390                 return StatsPromise::CreateAndResolve(stats, __func__);
391               })
392       ->Then(mAbstractMainThread, __func__,
393              [ =, self = RefPtr<ChannelMediaDecoder>(this) ](
394                  MediaStatistics aStats) {
395                if (IsShutdown()) {
396                  return;
397                }
398                mCanPlayThrough = aStats.CanPlayThrough();
399                GetStateMachine()->DispatchCanPlayThrough(mCanPlayThrough);
400                mResource->ThrottleReadahead(ShouldThrottleDownload(aStats));
401                // Update readyState since mCanPlayThrough might have changed.
402                GetOwner()->UpdateReadyState();
403              },
404              []() { MOZ_ASSERT_UNREACHABLE("Promise not resolved"); });
405 }
406 
407 /* static */ ChannelMediaDecoder::PlaybackRateInfo
ComputePlaybackRate(const MediaChannelStatistics & aStats,BaseMediaResource * aResource,double aDuration)408 ChannelMediaDecoder::ComputePlaybackRate(const MediaChannelStatistics& aStats,
409                                          BaseMediaResource* aResource,
410                                          double aDuration) {
411   MOZ_ASSERT(!NS_IsMainThread());
412 
413   int64_t length = aResource->GetLength();
414   if (mozilla::IsFinite<double>(aDuration) && aDuration > 0 && length >= 0) {
415     return {uint32_t(length / aDuration), true};
416   }
417 
418   bool reliable = false;
419   uint32_t rate = aStats.GetRate(&reliable);
420   return {rate, reliable};
421 }
422 
UpdatePlaybackRate(const PlaybackRateInfo & aInfo,BaseMediaResource * aResource)423 /* static */ void ChannelMediaDecoder::UpdatePlaybackRate(
424     const PlaybackRateInfo& aInfo, BaseMediaResource* aResource) {
425   MOZ_ASSERT(!NS_IsMainThread());
426 
427   uint32_t rate = aInfo.mRate;
428 
429   if (aInfo.mReliable) {
430     // Avoid passing a zero rate
431     rate = std::max(rate, 1u);
432   } else {
433     // Set a minimum rate of 10,000 bytes per second ... sometimes we just
434     // don't have good data
435     rate = std::max(rate, 10000u);
436   }
437 
438   aResource->SetPlaybackRate(rate);
439 }
440 
GetStatistics(const PlaybackRateInfo & aInfo,BaseMediaResource * aRes,int64_t aPlaybackPosition)441 /* static */ MediaStatistics ChannelMediaDecoder::GetStatistics(
442     const PlaybackRateInfo& aInfo, BaseMediaResource* aRes,
443     int64_t aPlaybackPosition) {
444   MOZ_ASSERT(!NS_IsMainThread());
445 
446   MediaStatistics result;
447   result.mDownloadRate = aRes->GetDownloadRate(&result.mDownloadRateReliable);
448   result.mDownloadPosition = aRes->GetCachedDataEnd(aPlaybackPosition);
449   result.mTotalBytes = aRes->GetLength();
450   result.mPlaybackRate = aInfo.mRate;
451   result.mPlaybackRateReliable = aInfo.mReliable;
452   result.mPlaybackPosition = aPlaybackPosition;
453   return result;
454 }
455 
ShouldThrottleDownload(const MediaStatistics & aStats)456 bool ChannelMediaDecoder::ShouldThrottleDownload(
457     const MediaStatistics& aStats) {
458   // We throttle the download if either the throttle override pref is set
459   // (so that we can always throttle in Firefox on mobile) or if the download
460   // is fast enough that there's no concern about playback being interrupted.
461   MOZ_ASSERT(NS_IsMainThread());
462   NS_ENSURE_TRUE(GetStateMachine(), false);
463 
464   int64_t length = aStats.mTotalBytes;
465   if (length > 0 &&
466       length <= int64_t(MediaPrefs::MediaMemoryCacheMaxSize()) * 1024) {
467     // Don't throttle the download of small resources. This is to speed
468     // up seeking, as seeks into unbuffered ranges would require starting
469     // up a new HTTP transaction, which adds latency.
470     return false;
471   }
472 
473   if (Preferences::GetBool("media.throttle-regardless-of-download-rate",
474                            false)) {
475     return true;
476   }
477 
478   if (!aStats.mDownloadRateReliable || !aStats.mPlaybackRateReliable) {
479     return false;
480   }
481   uint32_t factor =
482       std::max(2u, Preferences::GetUint("media.throttle-factor", 2));
483   return aStats.mDownloadRate > factor * aStats.mPlaybackRate;
484 }
485 
AddSizeOfResources(ResourceSizes * aSizes)486 void ChannelMediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
487   MOZ_ASSERT(NS_IsMainThread());
488   if (mResource) {
489     aSizes->mByteSize += mResource->SizeOfIncludingThis(aSizes->mMallocSizeOf);
490   }
491 }
492 
GetCurrentPrincipal()493 already_AddRefed<nsIPrincipal> ChannelMediaDecoder::GetCurrentPrincipal() {
494   MOZ_ASSERT(NS_IsMainThread());
495   return mResource ? mResource->GetCurrentPrincipal() : nullptr;
496 }
497 
IsTransportSeekable()498 bool ChannelMediaDecoder::IsTransportSeekable() {
499   MOZ_ASSERT(NS_IsMainThread());
500   return mResource->IsTransportSeekable();
501 }
502 
SetLoadInBackground(bool aLoadInBackground)503 void ChannelMediaDecoder::SetLoadInBackground(bool aLoadInBackground) {
504   MOZ_ASSERT(NS_IsMainThread());
505   if (mResource) {
506     mResource->SetLoadInBackground(aLoadInBackground);
507   }
508 }
509 
Suspend()510 void ChannelMediaDecoder::Suspend() {
511   MOZ_ASSERT(NS_IsMainThread());
512   if (mResource) {
513     mResource->Suspend(true);
514   }
515 }
516 
Resume()517 void ChannelMediaDecoder::Resume() {
518   MOZ_ASSERT(NS_IsMainThread());
519   if (mResource) {
520     mResource->Resume();
521   }
522 }
523 
MetadataLoaded(UniquePtr<MediaInfo> aInfo,UniquePtr<MetadataTags> aTags,MediaDecoderEventVisibility aEventVisibility)524 void ChannelMediaDecoder::MetadataLoaded(
525     UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags,
526     MediaDecoderEventVisibility aEventVisibility) {
527   MediaDecoder::MetadataLoaded(Move(aInfo), Move(aTags), aEventVisibility);
528   // Set mode to PLAYBACK after reading metadata.
529   mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
530 }
531 
GetDebugInfo()532 nsCString ChannelMediaDecoder::GetDebugInfo() {
533   auto&& str = MediaDecoder::GetDebugInfo();
534   if (mResource) {
535     AppendStringIfNotEmpty(str, mResource->GetDebugInfo());
536   }
537   return str;
538 }
539 
540 }  // namespace mozilla
541 
542 // avoid redefined macro in unified build
543 #undef LOG
544