Bug 1578615 - part3 : implement delay media playback policy. r=chunmin
authoralwu <alwu@mozilla.com>
Wed, 25 Sep 2019 23:41:23 +0000
changeset 495062 d7933b57b9a57b2a9f5a6810b5fa7a8304e8ff96
parent 495061 b38eec7c0555dcd9d53ac6e785d7187ed856f261
child 495063 7ade7615fccf02a0126bb3821448c0a444694386
push id96405
push useralwu@mozilla.com
push dateThu, 26 Sep 2019 06:03:48 +0000
treeherderautoland@aeec93cc3005 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschunmin
bugs1578615
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1578615 - part3 : implement delay media playback policy. r=chunmin Separate the logic of delaying media playback from `HTMLMediaElement`'s `mAudioChannelWrapper` to a new class, because delaying media playback is a special usage of using `AudioChannelAgent`. It would make the code cleaner and separate the normal usage and the special usage, to reduce confusion. We usually start `AudioChannelAgent` when media playback starts and stop it when media playback stops, however, in this case, we would start the agent even if media hasn't be started yet, because we would like to use the agent as a connection with `AudioChannelService` in order to receive the resume command when we're able to play delayed playback. Differential Revision: https://phabricator.services.mozilla.com/D44746
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/MediaPlaybackDelayPolicy.cpp
dom/media/MediaPlaybackDelayPolicy.h
dom/media/moz.build
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2022,16 +2022,20 @@ void HTMLMediaElement::AbortExistingLoad
   }
 
   if (IsVideo() && hadVideo) {
     // Ensure we render transparent black after resetting video resolution.
     Maybe<nsIntSize> size = Some(nsIntSize(0, 0));
     Invalidate(true, size, false);
   }
 
+  // As aborting current load would stop current playback, so we have no need to
+  // resume a paused media element.
+  ClearResumeDelayedMediaPlaybackAgentIfNeeded();
+
   // We may have changed mPaused, mAutoplaying, and other
   // things which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
   mIsRunningSelectResource = false;
 
   mEventDeliveryPaused = false;
   mPendingEvents.Clear();
@@ -3055,16 +3059,19 @@ void HTMLMediaElement::Pause(ErrorResult
   mAutoplaying = false;
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
   UpdateSrcMediaStreamPlaying();
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->NotifyPlayStateChanged();
   }
 
+  // We don't need to resume media which is paused explicitly by user.
+  ClearResumeDelayedMediaPlaybackAgentIfNeeded();
+
   if (!oldPaused) {
     FireTimeUpdate(false);
     DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
     AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
   }
 }
 
 void HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv) {
@@ -3740,16 +3747,21 @@ HTMLMediaElement::~HTMLMediaElement() {
     mChannelLoader->Cancel();
   }
 
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->Shutdown();
     mAudioChannelWrapper = nullptr;
   }
 
+  if (mResumeDelayedPlaybackAgent) {
+    mResumePlaybackRequest.DisconnectIfExists();
+    mResumeDelayedPlaybackAgent = nullptr;
+  }
+
   WakeLockRelease();
   ReportPlayedTimeAfterBlockedTelemetry();
 
   DecoderDoctorLogger::LogDestruction(this);
 }
 
 void HTMLMediaElement::StopSuspendingAfterFirstFrame() {
   mAllowSuspendAfterFirstFrame = false;
@@ -3773,20 +3785,16 @@ void HTMLMediaElement::SetPlayedOrSeeked
     return;
   }
   frame->PresShell()->FrameNeedsReflow(frame, IntrinsicDirty::TreeChange,
                                        NS_FRAME_IS_DIRTY);
 }
 
 void HTMLMediaElement::NotifyXPCOMShutdown() { ShutdownDecoder(); }
 
-bool HTMLMediaElement::AudioChannelAgentDelayingPlayback() {
-  return mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked();
-}
-
 void HTMLMediaElement::UpdateHadAudibleAutoplayState() {
   // If we're audible, and autoplaying...
   if ((Volume() > 0.0 && !Muted()) &&
       (!OwnerDoc()->HasBeenUserGestureActivated() || Autoplay())) {
     OwnerDoc()->SetDocTreeHadAudibleMedia();
     if (AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(*this)) {
       ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_BE_ALLOWED_COUNT, 1);
     } else {
@@ -3826,25 +3834,25 @@ already_AddRefed<Promise> HTMLMediaEleme
     return promise.forget();
   }
 
   // 4.8.12.8 - Step 3:
   // Let promise be a new promise and append promise to the list of pending
   // play promises.
   // Note: Promise appended to list of pending promises as needed below.
 
-  if (AudioChannelAgentDelayingPlayback()) {
-    // The audio channel agent may delay starting playback of a media resource
-    // until the tab the media element is in has been in the foreground.
-    // Save a reference to the promise, and return it. The AudioChannelAgent
-    // will call Play() again if the tab is brought to the foreground, or the
-    // audio tab indicator is clicked, which will resolve the promise if we end
-    // up playing.
-    LOG(LogLevel::Debug, ("%p Play() call delayed by AudioChannelAgent", this));
+  // We may delay starting playback of a media resource for an unvisited tab
+  // until it's going to foreground or being resumed by the play tab icon.
+  if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
+    CreateResumeDelayedMediaPlaybackAgentIfNeeded();
+    LOG(LogLevel::Debug, ("%p delay Play() call", this));
     MaybeDoLoad();
+    // When play is delayed, save a reference to the promise, and return it.
+    // The promise will be resolved when we resume play by either the tab is
+    // brought to the foreground, or the audio tab indicator is clicked.
     mPendingPlayPromises.AppendElement(promise);
     return promise.forget();
   }
 
   if (AudioChannelAgentBlockedPlay()) {
     LOG(LogLevel::Debug, ("%p play blocked by AudioChannelAgent.", this));
     promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
     return promise.forget();
@@ -5796,16 +5804,22 @@ bool HTMLMediaElement::CanActivateAutopl
   }
 
   // Static document is used for print preview and printing, should not be
   // autoplay
   if (OwnerDoc()->IsStaticDocument()) {
     return false;
   }
 
+  if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
+    CreateResumeDelayedMediaPlaybackAgentIfNeeded();
+    LOG(LogLevel::Debug, ("%p delay playing from autoplay", this));
+    return false;
+  }
+
   if (mAudioChannelWrapper) {
     // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
     // state.
     if (mAudioChannelWrapper->GetSuspendType() ==
             nsISuspendedTypes::SUSPENDED_PAUSE ||
         mAudioChannelWrapper->GetSuspendType() ==
             nsISuspendedTypes::SUSPENDED_BLOCK ||
         mAudioChannelWrapper->IsPlaybackBlocked()) {
@@ -6149,16 +6163,18 @@ void HTMLMediaElement::SuspendOrResumeEl
         nsAutoString keySystem;
         mMediaKeys->GetKeySystem(keySystem);
       }
       if (mDecoder) {
         mDecoder->Pause();
         mDecoder->Suspend();
       }
       mEventDeliveryPaused = aSuspendEvents;
+      // We won't want to resume media element from the bfcache.
+      ClearResumeDelayedMediaPlaybackAgentIfNeeded();
     } else {
       if (!mPaused) {
         mCurrentLoadPlayTime.Start();
       }
       if (mDecoder) {
         mDecoder->Resume();
         if (!mPaused && !mDecoder->IsEnded()) {
           mDecoder->Play();
@@ -7049,20 +7065,18 @@ bool HTMLMediaElement::ShouldElementBePa
   }
 
   return false;
 }
 
 void HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo) {
   const bool oldHasAudio = mMediaInfo.HasAudio();
   mMediaInfo = aInfo;
-  if (aInfo.HasAudio() != oldHasAudio) {
-    UpdateAudioChannelPlayingState();
-    NotifyAudioPlaybackChanged(
-        AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
+  if ((aInfo.HasAudio() != oldHasAudio) && mResumeDelayedPlaybackAgent) {
+    mResumeDelayedPlaybackAgent->UpdateAudibleState(GetAudibleState());
   }
   if (mAudioChannelWrapper) {
     mAudioChannelWrapper->AudioCaptureStreamChangeIfNeeded();
   }
   UpdateWakeLock();
 }
 
 void HTMLMediaElement::AudioCaptureStreamChange(bool aCapture) {
@@ -7444,13 +7458,51 @@ void HTMLMediaElement::NotifyTextTrackMo
                                GetTextTracks()->CreateAndDispatchChangeEvent();
                                // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag
                                if (!mShowPoster) {
                                  mTextTrackManager->TimeMarchesOn();
                                }
                              }));
 }
 
+void HTMLMediaElement::CreateResumeDelayedMediaPlaybackAgentIfNeeded() {
+  if (mResumeDelayedPlaybackAgent) {
+    return;
+  }
+  mResumeDelayedPlaybackAgent =
+      MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(
+          this, GetAudibleState());
+  if (!mResumeDelayedPlaybackAgent) {
+    LOG(LogLevel::Debug,
+        ("%p Failed to create a delayed playback agant", this));
+    return;
+  }
+  mResumeDelayedPlaybackAgent->GetResumePromise()
+      ->Then(
+          mAbstractMainThread, __func__,
+          [self = RefPtr<HTMLMediaElement>(this)]() {
+            LOG(LogLevel::Debug, ("%p Resume delayed Play() call", self.get()));
+            IgnoredErrorResult dummy;
+            RefPtr<Promise> toBeIgnored = self->Play(dummy);
+            self->mResumePlaybackRequest.Complete();
+            self->mResumeDelayedPlaybackAgent = nullptr;
+          },
+          [self = RefPtr<HTMLMediaElement>(this)]() {
+            LOG(LogLevel::Debug,
+                ("%p Can not resume delayed Play() call", self.get()));
+            self->mResumePlaybackRequest.Complete();
+            self->mResumeDelayedPlaybackAgent = nullptr;
+          })
+      ->Track(mResumePlaybackRequest);
+}
+
+void HTMLMediaElement::ClearResumeDelayedMediaPlaybackAgentIfNeeded() {
+  if (mResumeDelayedPlaybackAgent) {
+    mResumePlaybackRequest.DisconnectIfExists();
+    mResumeDelayedPlaybackAgent = nullptr;
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
 
 #undef LOG
 #undef LOG_EVENT
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -7,16 +7,17 @@
 #define mozilla_dom_HTMLMediaElement_h
 
 #include "nsAutoPtr.h"
 #include "nsGenericHTMLElement.h"
 #include "AudioChannelService.h"
 #include "MediaEventSource.h"
 #include "SeekTarget.h"
 #include "MediaDecoderOwner.h"
+#include "MediaPlaybackDelayPolicy.h"
 #include "MediaPromiseDefs.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/StateWatching.h"
@@ -1259,21 +1260,16 @@ class HTMLMediaElement : public nsGeneri
   void MakeAssociationWithCDMResolved();
   void SetCDMProxyFailure(const MediaResult& aResult);
   void ResetSetMediaKeysTempVariables();
 
   void PauseIfShouldNotBePlaying();
 
   WatchManager<HTMLMediaElement> mWatchManager;
 
-  // If the media element's tab has never been in the foreground, this
-  // registers as with the AudioChannelAgent to notify us when the tab
-  // is put in the foreground, whereupon we will begin playback.
-  bool AudioChannelAgentDelayingPlayback();
-
   // Update the silence range of the audio track when the audible status of
   // silent audio track changes or seeking to the new position where the audio
   // track is silent.
   void UpdateAudioTrackSilenceRange(bool aAudible);
 
   // When silent audio track becomes audible or seeking to new place, we would
   // end the current silence range and accumulate it to the total silence
   // proportion of audio track and update current silence range.
@@ -1834,16 +1830,26 @@ class HTMLMediaElement : public nsGeneri
   // https://w3c.github.io/mediacapture-output/#htmlmediaelement-extensions
   // Read/Write from the main thread only.
   Pair<nsString, RefPtr<AudioDeviceInfo>> mSink;
 
   // This flag is used to control when the user agent is to show a poster frame
   // for a video element instead of showing the video contents.
   // https://html.spec.whatwg.org/multipage/media.html#show-poster-flag
   bool mShowPoster;
+
+  // We may delay starting playback of a media for an unvisited tab until it's
+  // going to foreground. We would create ResumeDelayedMediaPlaybackAgent to
+  // handle related operations at the time whenever delaying media playback is
+  // needed.
+  void CreateResumeDelayedMediaPlaybackAgentIfNeeded();
+  void ClearResumeDelayedMediaPlaybackAgentIfNeeded();
+  RefPtr<ResumeDelayedPlaybackAgent> mResumeDelayedPlaybackAgent;
+  MozPromiseRequestHolder<ResumeDelayedPlaybackAgent::ResumePromise>
+      mResumePlaybackRequest;
 };
 
 // Check if the context is chrome or has the debugger or tabs permission
 bool HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj);
 
 }  // namespace dom
 }  // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaPlaybackDelayPolicy.cpp
@@ -0,0 +1,158 @@
+
+/* 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 "MediaPlaybackDelayPolicy.h"
+
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+
+namespace mozilla {
+namespace dom {
+
+using AudibleState = AudioChannelService::AudibleState;
+
+ResumeDelayedPlaybackAgent::~ResumeDelayedPlaybackAgent() {
+  if (mDelegate) {
+    mDelegate->Clear();
+    mDelegate = nullptr;
+  }
+}
+
+bool ResumeDelayedPlaybackAgent::InitDelegate(const HTMLMediaElement* aElement,
+                                              AudibleState aAudibleState) {
+  MOZ_ASSERT(!mDelegate, "Delegate has been initialized!");
+  mDelegate = new ResumePlayDelegate();
+  if (!mDelegate->Init(aElement, aAudibleState)) {
+    mDelegate->Clear();
+    mDelegate = nullptr;
+    return false;
+  }
+  return true;
+}
+
+RefPtr<ResumeDelayedPlaybackAgent::ResumePromise>
+ResumeDelayedPlaybackAgent::GetResumePromise() {
+  MOZ_ASSERT(mDelegate);
+  return mDelegate->GetResumePromise();
+}
+
+void ResumeDelayedPlaybackAgent::UpdateAudibleState(
+    AudibleState aAudibleState) {
+  MOZ_ASSERT(mDelegate);
+  mDelegate->UpdateAudibleState(aAudibleState);
+}
+
+NS_IMPL_ISUPPORTS(ResumeDelayedPlaybackAgent::ResumePlayDelegate,
+                  nsIAudioChannelAgentCallback)
+
+ResumeDelayedPlaybackAgent::ResumePlayDelegate::~ResumePlayDelegate() {
+  MOZ_ASSERT(!mAudioChannelAgent);
+}
+
+bool ResumeDelayedPlaybackAgent::ResumePlayDelegate::Init(
+    const HTMLMediaElement* aElement, AudibleState aAudibleState) {
+  MOZ_ASSERT(aElement);
+  MOZ_ASSERT(aElement->OwnerDoc());
+  if (!aElement->OwnerDoc()->GetInnerWindow()) {
+    return false;
+  }
+
+  MOZ_ASSERT(!mAudioChannelAgent);
+  mAudioChannelAgent = new AudioChannelAgent();
+  nsresult rv =
+      mAudioChannelAgent->Init(aElement->OwnerDoc()->GetInnerWindow(), this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Clear();
+    return false;
+  }
+
+  // Start AudioChannelAgent in order to wait the suspended state change when we
+  // are able to resume delayed playback.
+  rv = mAudioChannelAgent->NotifyStartedPlaying(aAudibleState);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Clear();
+    return false;
+  }
+
+  return true;
+}
+
+void ResumeDelayedPlaybackAgent::ResumePlayDelegate::Clear() {
+  if (mAudioChannelAgent) {
+    mAudioChannelAgent->NotifyStoppedPlaying();
+    mAudioChannelAgent = nullptr;
+  }
+  mPromise.RejectIfExists(true, __func__);
+}
+
+RefPtr<ResumeDelayedPlaybackAgent::ResumePromise>
+ResumeDelayedPlaybackAgent::ResumePlayDelegate::GetResumePromise() {
+  return mPromise.Ensure(__func__);
+}
+
+void ResumeDelayedPlaybackAgent::ResumePlayDelegate::UpdateAudibleState(
+    AudibleState aAudibleState) {
+  // It's possible for the owner of `ResumeDelayedPlaybackAgent` to call
+  // `UpdateAudibleState()` after we resolve the promise and clean resource.
+  if (!mAudioChannelAgent) {
+    return;
+  }
+  mAudioChannelAgent->NotifyStartedAudible(
+      aAudibleState,
+      AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
+}
+
+NS_IMETHODIMP
+ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowVolumeChanged(
+    float aVolume, bool aMuted) {
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowAudioCaptureChanged(
+    bool aCapture) {
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowSuspendChanged(
+    SuspendTypes aSuspend) {
+  if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
+    mPromise.ResolveIfExists(true, __func__);
+    Clear();
+  }
+  return NS_OK;
+}
+
+bool MediaPlaybackDelayPolicy::ShouldDelayPlayback(
+    const HTMLMediaElement* aElement) {
+  MOZ_ASSERT(aElement);
+  if (!StaticPrefs::media_block_autoplay_until_in_foreground()) {
+    return false;
+  }
+
+  const Document* doc = aElement->OwnerDoc();
+  if (!doc) {
+    return false;
+  }
+
+  nsPIDOMWindowInner* inner = nsPIDOMWindowInner::From(doc->GetInnerWindow());
+  nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::GetFromCurrentInner(inner);
+  if (!outer) {
+    return false;
+  }
+  return outer->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK;
+}
+
+RefPtr<ResumeDelayedPlaybackAgent>
+MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(
+    const HTMLMediaElement* aElement, AudibleState aAudibleState) {
+  MOZ_ASSERT(aElement);
+  RefPtr<ResumeDelayedPlaybackAgent> agent = new ResumeDelayedPlaybackAgent();
+  return agent->InitDelegate(aElement, aAudibleState) ? agent : nullptr;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaPlaybackDelayPolicy.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_mediaplaybackdelaypolicy_h__
+#define mozilla_dom_mediaplaybackdelaypolicy_h__
+
+#include "AudioChannelAgent.h"
+#include "AudioChannelService.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+
+typedef uint32_t SuspendTypes;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMediaElement;
+/**
+ * We usaually start AudioChannelAgent when media starts and stop it when media
+ * stops. However, when we decide to delay media playback for unvisited tab, we
+ * would start AudioChannelAgent even if media doesn't start in order to
+ * register the agent to AudioChannelService, so that the service could notify
+ * us when we are able to resume media playback. Therefore,
+ * ResumeDelayedPlaybackAgent is used to handle this special use case of
+ * AudioChannelAgent.
+ * - Use `GetResumePromise()` to require resume-promise and then do follow-up
+ *   resume behavior when promise is resolved.
+ * - Use `UpdateAudibleState()` to update audible state only when media info
+ *   changes. As having audio track or not is the only thing for us to decide
+ *   whether we would show the delayed media playback icon on the tab bar.
+ */
+class ResumeDelayedPlaybackAgent {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ResumeDelayedPlaybackAgent);
+  ResumeDelayedPlaybackAgent() = default;
+
+  using ResumePromise = MozPromise<bool, bool, true /* IsExclusive */>;
+  RefPtr<ResumePromise> GetResumePromise();
+  void UpdateAudibleState(AudioChannelService::AudibleState aAudibleState);
+
+ private:
+  friend class MediaPlaybackDelayPolicy;
+
+  ~ResumeDelayedPlaybackAgent();
+  bool InitDelegate(const HTMLMediaElement* aElement,
+                    AudioChannelService::AudibleState aAudibleState);
+
+  class ResumePlayDelegate final : public nsIAudioChannelAgentCallback {
+   public:
+    NS_DECL_ISUPPORTS
+
+    ResumePlayDelegate() = default;
+
+    bool Init(const HTMLMediaElement* aElement,
+              AudioChannelService::AudibleState aAudibleState);
+    void UpdateAudibleState(AudioChannelService::AudibleState aAudibleState);
+    RefPtr<ResumePromise> GetResumePromise();
+    void Clear();
+
+    NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override;
+    NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override;
+    NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override;
+
+   private:
+    virtual ~ResumePlayDelegate();
+
+    MozPromiseHolder<ResumePromise> mPromise;
+    RefPtr<AudioChannelAgent> mAudioChannelAgent;
+  };
+
+  RefPtr<ResumePlayDelegate> mDelegate;
+};
+
+class MediaPlaybackDelayPolicy {
+ public:
+  static bool ShouldDelayPlayback(const HTMLMediaElement* aElement);
+  static RefPtr<ResumeDelayedPlaybackAgent> CreateResumeDelayedPlaybackAgent(
+      const HTMLMediaElement* aElement,
+      AudioChannelService::AudibleState aAudibleState);
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -139,16 +139,17 @@ EXPORTS += [
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderStateMachine.h',
     'MediaEventSource.h',
     'MediaFormatReader.h',
     'MediaInfo.h',
     'MediaMetadataManager.h',
     'MediaMIMETypes.h',
+    'MediaPlaybackDelayPolicy.h',
     'MediaPromiseDefs.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaResourceCallback.h',
     'MediaResult.h',
     'MediaSegment.h',
     'MediaShutdownManager.h',
@@ -263,16 +264,17 @@ UNIFIED_SOURCES += [
     'MediaDecoder.cpp',
     'MediaDecoderStateMachine.cpp',
     'MediaDeviceInfo.cpp',
     'MediaDevices.cpp',
     'MediaFormatReader.cpp',
     'MediaInfo.cpp',
     'MediaManager.cpp',
     'MediaMIMETypes.cpp',
+    'MediaPlaybackDelayPolicy.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamError.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamListener.cpp',
     'MediaStreamTrack.cpp',
     'MediaStreamWindowCapturer.cpp',