Bug 1513733 - part2 : try to start AudioContext when media element which is as a source for web audio starts r=cpearce,karlt
authoralwu <alwu@mozilla.com>
Fri, 11 Jan 2019 20:43:01 +0000
changeset 513517 c34e287f2f7c
parent 513516 05005b52bb56
child 513518 22ce92cbdf64
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, karlt
bugs1513733
milestone66.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 1513733 - part2 : try to start AudioContext when media element which is as a source for web audio starts r=cpearce,karlt If media element is used as a source for AudioContext, we would try to start AudioContext which was not allowed to start when media element starts playing. Differential Revision: https://phabricator.services.mozilla.com/D14593
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/webaudio/AudioContext.h
dom/media/webaudio/MediaElementAudioSourceNode.cpp
dom/media/webaudio/MediaElementAudioSourceNode.h
dom/media/webaudio/MediaStreamAudioSourceNode.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3183,16 +3183,28 @@ already_AddRefed<DOMMediaStream> HTMLMed
   if (!stream) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   return stream.forget();
 }
 
+RefPtr<GenericNonExclusivePromise> HTMLMediaElement::GetAllowedToPlayPromise() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mOutputStreams.IsEmpty(),
+             "This method should only be called during stream capturing!");
+  if (AutoplayPolicy::IsAllowedToPlay(*this)) {
+    AUTOPLAY_LOG("MediaElement %p has allowed to play, resolve promise", this);
+    return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+  }
+  AUTOPLAY_LOG("create allow-to-play promise for MediaElement %p", this);
+  return mAllowedToPlayPromise.Ensure(__func__);
+}
+
 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStream(
     ErrorResult& aRv) {
   MediaStreamGraph::GraphDriverType graphDriverType =
       HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
                  : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
 
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   if (!window) {
@@ -3477,16 +3489,18 @@ HTMLMediaElement::~HTMLMediaElement() {
   mShutdownObserver->Unsubscribe();
 
   if (mVideoFrameContainer) {
     mVideoFrameContainer->ForgetElement();
   }
   UnregisterActivityObserver();
 
   mSetCDMRequest.DisconnectIfExists();
+  mAllowedToPlayPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+
   if (mDecoder) {
     ShutdownDecoder();
   }
   if (mProgressTimer) {
     StopProgress();
   }
   if (mVideoDecodeSuspendTimer) {
     mVideoDecodeSuspendTimer->Cancel();
@@ -3629,16 +3643,17 @@ already_AddRefed<Promise> HTMLMediaEleme
 
   UpdateHadAudibleAutoplayState();
 
   const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
   mPendingPlayPromises.AppendElement(promise);
 
   if (AutoplayPolicy::IsAllowedToPlay(*this)) {
     AUTOPLAY_LOG("allow MediaElement %p to play", this);
+    mAllowedToPlayPromise.ResolveIfExists(true, __func__);
     PlayInternal(handlingUserInput);
     UpdateCustomPolicyAfterPlayed();
   } else {
     AUTOPLAY_LOG("reject MediaElement %p to play", this);
     AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
   }
   return promise.forget();
 }
@@ -5556,16 +5571,17 @@ void HTMLMediaElement::CheckAutoplayData
   }
 
   UpdateHadAudibleAutoplayState();
   if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
     DispatchEventsWhenPlayWasNotAllowed();
     return;
   }
 
+  mAllowedToPlayPromise.ResolveIfExists(true, __func__);
   mPaused = false;
   // We changed mPaused which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
   if (mDecoder) {
     SetPlayedOrSeeked(true);
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -718,16 +718,21 @@ class HTMLMediaElement : public nsGeneri
                                       ErrorResult& aRv);
   // Get the sink id of the device that audio is being played. Initial value is
   // empty and the default device is being used.
   void GetSinkId(nsString& aSinkId) {
     MOZ_ASSERT(NS_IsMainThread());
     aSinkId = mSink.first();
   }
 
+  // This is used to notify MediaElementAudioSourceNode that media element is
+  // allowed to play when media element is used as a source for web audio, so
+  // that we can start AudioContext if it was not allowed to start.
+  RefPtr<GenericNonExclusivePromise> GetAllowedToPlayPromise();
+
  protected:
   virtual ~HTMLMediaElement();
 
   class AudioChannelAgentCallback;
   class ChannelLoader;
   class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTrackListener;
@@ -1653,16 +1658,21 @@ class HTMLMediaElement : public nsGeneri
   // Note this flag is false when the element is in a phase after creation and
   // before attaching to the DOM tree.
   bool mUnboundFromTree = false;
 
   // True if the autoplay media was blocked because it hadn't loaded metadata
   // yet.
   bool mBlockedAsWithoutMetadata = false;
 
+  // This promise is used to notify MediaElementAudioSourceNode that media
+  // element is allowed to play when MediaElement is used as a source for web
+  // audio.
+  MozPromiseHolder<GenericNonExclusivePromise> mAllowedToPlayPromise;
+
  public:
   // Helper class to measure times for MSE telemetry stats
   class TimeDurationAccumulator {
    public:
     TimeDurationAccumulator() : mCount(0) {}
     void Start() {
       if (IsStarted()) {
         return;
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -178,18 +178,18 @@ class AudioContext final : public DOMEve
   AudioListener* Listener();
 
   AudioContextState State() const { return mAudioContextState; }
 
   Worklet* GetAudioWorklet(ErrorResult& aRv);
 
   bool IsRunning() const;
 
-  // Called when an AudioScheduledSourceNode started, this method might resume
-  // the AudioContext if it was not allowed to start.
+  // Called when an AudioScheduledSourceNode started or the source node starts,
+  // this method might resume the AudioContext if it was not allowed to start.
   void StartBlockedAudioContextIfAllowed();
 
   // Those three methods return a promise to content, that is resolved when an
   // (possibly long) operation is completed on the MSG (and possibly other)
   // thread(s). To avoid having to match the calls and asychronous result when
   // the operation is completed, we keep a reference to the promises on the main
   // thread, and then send the promises pointers down the MSG thread, as a void*
   // (to make it very clear that the pointer is to merely be treated as an ID).
--- a/dom/media/webaudio/MediaElementAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.cpp
@@ -38,18 +38,40 @@ MediaElementAudioSourceNode::Create(
     return nullptr;
   }
 
   node->Init(stream, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  node->ListenForAllowedToPlay(aOptions);
   return node.forget();
 }
 
 JSObject* MediaElementAudioSourceNode::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return MediaElementAudioSourceNode_Binding::Wrap(aCx, this, aGivenProto);
 }
 
+void MediaElementAudioSourceNode::ListenForAllowedToPlay(
+    const MediaElementAudioSourceOptions& aOptions) {
+  aOptions.mMediaElement->GetAllowedToPlayPromise()
+      ->Then(
+          AbstractMainThread(), __func__,
+          // Capture by reference to bypass the mozilla-refcounted-inside-lambda
+          // static analysis. We capture a non-owning reference so as to allow
+          // cycle collection of the node. The reference is cleared via
+          // DisconnectIfExists() from Destroy() when the node is collected.
+          [& self = *this]() {
+            self.Context()->StartBlockedAudioContextIfAllowed();
+            self.mAllowedToPlayRequest.Complete();
+          })
+      ->Track(mAllowedToPlayRequest);
+}
+
+void MediaElementAudioSourceNode::Destroy() {
+  mAllowedToPlayRequest.DisconnectIfExists();
+  MediaStreamAudioSourceNode::Destroy();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/webaudio/MediaElementAudioSourceNode.h
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.h
@@ -39,14 +39,22 @@ class MediaElementAudioSourceNode final 
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
  private:
   explicit MediaElementAudioSourceNode(AudioContext* aContext);
+
+  void Destroy() override;
+
+  // If AudioContext was not allowed to start, we would try to start it when
+  // source starts.
+  void ListenForAllowedToPlay(const MediaElementAudioSourceOptions& aOptions);
+
+  MozPromiseRequestHolder<GenericNonExclusivePromise> mAllowedToPlayRequest;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h
@@ -88,17 +88,17 @@ class MediaStreamAudioSourceNode
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override;
 
   // From PrincipalChangeObserver<MediaStreamTrack>.
   void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override;
 
  protected:
   explicit MediaStreamAudioSourceNode(AudioContext* aContext);
   void Init(DOMMediaStream* aMediaStream, ErrorResult& aRv);
-  void Destroy();
+  virtual void Destroy();
   virtual ~MediaStreamAudioSourceNode();
 
  private:
   RefPtr<MediaInputPort> mInputPort;
   RefPtr<DOMMediaStream> mInputStream;
 
   // On construction we set this to the first audio track of mInputStream.
   RefPtr<MediaStreamTrack> mInputTrack;