Bug 1346880 - part1 : keep AudioChannelAgent alive all the time for not offline context. r=padenot
authorPaul Adenot <paul@paul.cx>
Sat, 24 Oct 2020 03:31:25 +0000
changeset 554675 eb9c32faa3e666ea71bd0b838d019e9456ae4dda
parent 554674 8de8cd3371e801d408650f102df04252c846f33d
child 554676 a56fc7ee5a044717e0b0674ba03f6eadb219861a
push id37898
push userabutkovits@mozilla.com
push dateWed, 28 Oct 2020 09:24:21 +0000
treeherdermozilla-central@83bf4fd3b1fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1346880
milestone84.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 1346880 - part1 : keep AudioChannelAgent alive all the time for not offline context. r=padenot The main purposes of this patch is to - changes the way the destination node holding the audio channel agent (holding the agent all the time) That would help us - earlier mute the destination node in order to prevent sound leaking - request wakelock based on the actual audible state (considering more factors) - improve the readability of handling audible state (the old way contains some weird workaround, eg. setting the node as audible in the beginning) Differential Revision: https://phabricator.services.mozilla.com/D94181
dom/media/webaudio/AudioContext.cpp
dom/media/webaudio/AudioContext.h
dom/media/webaudio/AudioDestinationNode.cpp
dom/media/webaudio/AudioDestinationNode.h
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -169,18 +169,19 @@ AudioContext::AudioContext(nsPIDOMWindow
       mWasEverAllowedToStart(false),
       mWasEverBlockedToStart(false),
       mWouldBeAllowedToStart(true) {
   bool mute = aWindow->AddAudioContext(this);
 
   // Note: AudioDestinationNode needs an AudioContext that must already be
   // bound to the window.
   const bool allowedToStart = AutoplayPolicy::IsAllowedToPlay(*this);
-  mDestination = new AudioDestinationNode(this, aIsOffline, allowedToStart,
-                                          aNumberOfChannels, aLength);
+  mDestination =
+      new AudioDestinationNode(this, aIsOffline, aNumberOfChannels, aLength);
+  mDestination->Init();
   // If an AudioContext is not allowed to start, we would postpone its state
   // transition from `suspended` to `running` until sites explicitly call
   // AudioContext.resume() or AudioScheduledSourceNode.start().
   if (!allowedToStart) {
     MOZ_ASSERT(!mIsOffline);
     AUTOPLAY_LOG("AudioContext %p is not allowed to start", this);
     ReportBlocked();
   } else if (!mIsOffline) {
@@ -214,27 +215,16 @@ void AudioContext::StartBlockedAudioCont
   // blocked because of the auto-play policy.
   if (isAllowedToPlay && !mSuspendedByContent) {
     ResumeInternal(AudioContextOperationFlags::SendStateChange);
   } else {
     ReportBlocked();
   }
 }
 
-nsresult AudioContext::Init() {
-  if (!mIsOffline) {
-    nsresult rv = mDestination->CreateAudioChannelAgent();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  return NS_OK;
-}
-
 void AudioContext::DisconnectFromWindow() {
   nsPIDOMWindowInner* window = GetOwner();
   if (window) {
     window->RemoveAudioContext(this);
   }
 }
 
 AudioContext::~AudioContext() {
@@ -287,20 +277,16 @@ already_AddRefed<AudioContext> AudioCont
     return nullptr;
   }
   float sampleRate = aOptions.mSampleRate.WasPassed()
                          ? aOptions.mSampleRate.Value()
                          : MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE;
 
   RefPtr<AudioContext> object =
       new AudioContext(window, false, 2, 0, sampleRate);
-  aRv = object->Init();
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
 
   RegisterWeakMemoryReporter(object);
 
   return object.forget();
 }
 
 /* static */
 already_AddRefed<AudioContext> AudioContext::Constructor(
@@ -903,16 +889,17 @@ void AudioContext::OnStateChanged(void* 
   }
 
   if (mAudioContextState != aNewState) {
     RefPtr<OnStateChangeTask> task = new OnStateChangeTask(this);
     Dispatch(task.forget());
   }
 
   mAudioContextState = aNewState;
+  Destination()->NotifyAudioContextStateChanged();
 }
 
 nsTArray<RefPtr<mozilla::MediaTrack>> AudioContext::GetAllTracks() const {
   nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
   for (auto iter = mAllNodes.ConstIter(); !iter.Done(); iter.Next()) {
     AudioNode* node = iter.Get()->GetKey();
     mozilla::MediaTrack* t = node->GetTrack();
     if (t) {
@@ -1171,17 +1158,17 @@ void AudioContext::OfflineClose() {
 }
 
 void AudioContext::CloseInternal(void* aPromise,
                                  AudioContextOperationFlags aFlags) {
   // This can be called when freeing a document, and the tracks are dead at
   // this point, so we need extra null-checks.
   AudioNodeTrack* ds = DestinationTrack();
   if (ds && !mIsOffline) {
-    Destination()->DestroyAudioChannelAgent();
+    Destination()->Close();
 
     nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
     // If mSuspendCalled or mCloseCalled are true then we already suspended
     // all our tracks, so don't suspend them again. But we still need to do
     // ApplyAudioContextOperation to ensure our new promise is resolved.
     if (!mSuspendCalled && !mCloseCalled) {
       tracks = GetAllTracks();
     }
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -131,18 +131,16 @@ struct AudioContextOptions;
 class AudioContext final : public DOMEventTargetHelper,
                            public nsIMemoryReporter,
                            public RelativeTimeline {
   AudioContext(nsPIDOMWindowInner* aParentWindow, bool aIsOffline,
                uint32_t aNumberOfChannels = 0, uint32_t aLength = 0,
                float aSampleRate = 0.0f);
   ~AudioContext();
 
-  nsresult Init();
-
  public:
   typedef uint64_t AudioContextId;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioContext, DOMEventTargetHelper)
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
   nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -217,17 +217,17 @@ class DestinationNodeEngine final : publ
       mLastInputAudible = isInputAudible;
       RefPtr<AudioNodeTrack> track = aTrack;
       auto r = [track, isInputAudible]() -> void {
         MOZ_ASSERT(NS_IsMainThread());
         RefPtr<AudioNode> node = track->Engine()->NodeMainThread();
         if (node) {
           RefPtr<AudioDestinationNode> destinationNode =
               static_cast<AudioDestinationNode*>(node.get());
-          destinationNode->NotifyAudibleStateChanged(isInputAudible);
+          destinationNode->NotifyDataAudibleStateChanged(isInputAudible);
         }
       };
 
       aTrack->Graph()->DispatchToMainThreadStableState(NS_NewRunnableFunction(
           "dom::WebAudioAudibleStateChangedRunnable", r));
     }
   }
 
@@ -283,25 +283,22 @@ NS_IMPL_ADDREF_INHERITED(AudioDestinatio
 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
 
 const AudioNodeTrack::Flags kTrackFlags =
     AudioNodeTrack::NEED_MAIN_THREAD_CURRENT_TIME |
     AudioNodeTrack::NEED_MAIN_THREAD_ENDED | AudioNodeTrack::EXTERNAL_OUTPUT;
 
 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
                                            bool aIsOffline,
-                                           bool aAllowedToStart,
                                            uint32_t aNumberOfChannels,
                                            uint32_t aLength)
     : AudioNode(aContext, aNumberOfChannels, ChannelCountMode::Explicit,
                 ChannelInterpretation::Speakers),
       mFramesToProduce(aLength),
       mIsOffline(aIsOffline),
-      mAudioChannelSuspended(false),
-      mAudible(AudioChannelService::AudibleState::eAudible),
       mCreatedTime(TimeStamp::Now()) {
   if (aIsOffline) {
     // The track is created on demand to avoid creating a graph thread that
     // may not be used.
     return;
   }
 
   // GetParentObject can return nullptr here. This will end up creating another
@@ -310,24 +307,61 @@ AudioDestinationNode::AudioDestinationNo
       MediaTrackGraph::AUDIO_THREAD_DRIVER, aContext->GetParentObject(),
       aContext->SampleRate(), MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
   AudioNodeEngine* engine = new DestinationNodeEngine(this);
 
   mTrack = AudioNodeTrack::Create(aContext, engine, kTrackFlags, graph);
   mTrack->AddMainThreadListener(this);
   // null key is fine: only one output per mTrack
   mTrack->AddAudioOutput(nullptr);
+}
 
-  if (aAllowedToStart) {
-    CreateAudioWakeLockIfNeeded();
+void AudioDestinationNode::Init() {
+  // The reason we don't do that in ctor is because we have to keep AudioContext
+  // holding a strong reference to the destination node first. If we don't do
+  // that, initializing the agent would cause an unexpected destroy of the
+  // destination node when destroying the local weak reference inside
+  // `InitWithWeakCallback()`.
+  if (!mIsOffline) {
+    CreateAndStartAudioChannelAgent();
   }
 }
 
+void AudioDestinationNode::Close() {
+  DestroyAudioChannelAgentIfExists();
+  ReleaseAudioWakeLockIfExists();
+}
+
+void AudioDestinationNode::CreateAndStartAudioChannelAgent() {
+  MOZ_ASSERT(!mIsOffline);
+  MOZ_ASSERT(!mAudioChannelAgent);
+
+  AudioChannelAgent* agent = new AudioChannelAgent();
+  nsresult rv = agent->InitWithWeakCallback(GetOwner(), this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    AUDIO_CHANNEL_LOG("Failed to init audio channel agent");
+    return;
+  }
+
+  AudibleState state =
+      IsAudible() ? AudibleState::eAudible : AudibleState::eNotAudible;
+  rv = agent->NotifyStartedPlaying(state);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    AUDIO_CHANNEL_LOG("Failed to start audio channel agent");
+    return;
+  }
+
+  mAudioChannelAgent = agent;
+  mAudioChannelAgent->PullInitialUpdate();
+}
+
 AudioDestinationNode::~AudioDestinationNode() {
-  ReleaseAudioWakeLockIfExists();
+  MOZ_ASSERT(!mAudioChannelAgent);
+  MOZ_ASSERT(!mWakeLock);
+  MOZ_ASSERT(!mCaptureTrackPort);
 }
 
 size_t AudioDestinationNode::SizeOfExcludingThis(
     MallocSizeOf aMallocSizeOf) const {
   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
   // Might be useful in the future:
   // - mAudioChannelAgent
   return amount;
@@ -357,31 +391,28 @@ AudioNodeTrack* AudioDestinationNode::Tr
   AudioNodeEngine* engine = new OfflineDestinationNodeEngine(this);
 
   mTrack = AudioNodeTrack::Create(context, engine, kTrackFlags, graph);
   mTrack->AddMainThreadListener(this);
 
   return mTrack;
 }
 
-void AudioDestinationNode::DestroyAudioChannelAgent() {
-  if (mAudioChannelAgent && !Context()->IsOffline()) {
+void AudioDestinationNode::DestroyAudioChannelAgentIfExists() {
+  if (mAudioChannelAgent) {
     mAudioChannelAgent->NotifyStoppedPlaying();
     mAudioChannelAgent = nullptr;
-    // Reset the state, and it would always be regard as audible.
-    mAudible = AudioChannelService::AudibleState::eAudible;
     if (IsCapturingAudio()) {
       StopAudioCapturingTrack();
     }
   }
 }
 
 void AudioDestinationNode::DestroyMediaTrack() {
-  DestroyAudioChannelAgent();
-
+  Close();
   if (!mTrack) {
     return;
   }
 
   Context()->ShutdownWorklet();
 
   mTrack->RemoveMainThreadListener(this);
   AudioNode::DestroyMediaTrack();
@@ -448,25 +479,25 @@ void AudioDestinationNode::Mute() {
 }
 
 void AudioDestinationNode::Unmute() {
   MOZ_ASSERT(Context() && !Context()->IsOffline());
   SendDoubleParameterToTrack(DestinationNodeEngine::VOLUME, 1.0f);
 }
 
 void AudioDestinationNode::Suspend() {
-  DestroyAudioChannelAgent();
   SendInt32ParameterToTrack(DestinationNodeEngine::SUSPENDED, 1);
-  ReleaseAudioWakeLockIfExists();
 }
 
 void AudioDestinationNode::Resume() {
-  CreateAudioChannelAgent();
   SendInt32ParameterToTrack(DestinationNodeEngine::SUSPENDED, 0);
-  CreateAudioWakeLockIfNeeded();
+}
+
+void AudioDestinationNode::NotifyAudioContextStateChanged() {
+  UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::ePauseStateChanged);
 }
 
 void AudioDestinationNode::OfflineShutdown() {
   MOZ_ASSERT(Context() && Context()->IsOffline(),
              "Should only be called on a valid OfflineAudioContext");
 
   mOfflineRenderingRef.Drop(this);
 }
@@ -479,79 +510,61 @@ JSObject* AudioDestinationNode::WrapObje
 void AudioDestinationNode::StartRendering(Promise* aPromise) {
   mOfflineRenderingPromise = aPromise;
   mOfflineRenderingRef.Take(this);
   Track()->Graph()->StartNonRealtimeProcessing(mFramesToProduce);
 }
 
 NS_IMETHODIMP
 AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted) {
+  MOZ_ASSERT(mAudioChannelAgent);
   if (!mTrack) {
     return NS_OK;
   }
 
   AUDIO_CHANNEL_LOG(
       "AudioDestinationNode %p WindowVolumeChanged, "
       "aVolume = %f, aMuted = %s\n",
       this, aVolume, aMuted ? "true" : "false");
 
-  float volume = aMuted ? 0.0f : aVolume;
-  mTrack->SetAudioOutputVolume(nullptr, volume);
-
-  AudioChannelService::AudibleState audible =
-      volume > 0.0 ? AudioChannelService::AudibleState::eAudible
-                   : AudioChannelService::AudibleState::eNotAudible;
-  if (mAudible != audible) {
-    mAudible = audible;
-    mAudioChannelAgent->NotifyStartedAudible(
-        mAudible, AudioChannelService::AudibleChangedReasons::eVolumeChanged);
-  }
+  mAudioChannelVolume = aMuted ? 0.0f : aVolume;
+  mTrack->SetAudioOutputVolume(nullptr, mAudioChannelVolume);
+  UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::eVolumeChanged);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AudioDestinationNode::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
+  MOZ_ASSERT(mAudioChannelAgent);
   if (!mTrack) {
     return NS_OK;
   }
 
-  bool suspended = (aSuspend != nsISuspendedTypes::NONE_SUSPENDED);
-  if (mAudioChannelSuspended == suspended) {
+  const bool shouldDisable = aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK;
+  if (mAudioChannelDisabled == shouldDisable) {
     return NS_OK;
   }
+  mAudioChannelDisabled = shouldDisable;
 
   AUDIO_CHANNEL_LOG(
-      "AudioDestinationNode %p WindowSuspendChanged, "
-      "aSuspend = %s\n",
-      this, SuspendTypeToStr(aSuspend));
-
-  mAudioChannelSuspended = suspended;
-
-  DisabledTrackMode disabledMode =
-      suspended ? DisabledTrackMode::SILENCE_BLACK : DisabledTrackMode::ENABLED;
-  mTrack->SetDisabledTrackMode(disabledMode);
+      "AudioDestinationNode %p WindowSuspendChanged, shouldDisable = %d\n",
+      this, mAudioChannelDisabled);
 
-  AudioChannelService::AudibleState audible =
-      aSuspend == nsISuspendedTypes::NONE_SUSPENDED
-          ? AudioChannelService::AudibleState::eAudible
-          : AudioChannelService::AudibleState::eNotAudible;
-  if (mAudible != audible) {
-    mAudible = audible;
-    mAudioChannelAgent->NotifyStartedAudible(
-        audible,
-        AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
-  }
+  DisabledTrackMode disabledMode = mAudioChannelDisabled
+                                       ? DisabledTrackMode::SILENCE_BLACK
+                                       : DisabledTrackMode::ENABLED;
+  mTrack->SetDisabledTrackMode(disabledMode);
+  UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::ePauseStateChanged);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AudioDestinationNode::WindowAudioCaptureChanged(bool aCapture) {
   MOZ_ASSERT(mAudioChannelAgent);
-
-  if (!mTrack || Context()->IsOffline()) {
+  if (!mTrack) {
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = GetOwner();
   if (!ownerWindow) {
     return NS_OK;
   }
 
@@ -581,17 +594,17 @@ void AudioDestinationNode::StartAudioCap
 
 void AudioDestinationNode::StopAudioCapturingTrack() {
   MOZ_ASSERT(IsCapturingAudio());
   mCaptureTrackPort->Destroy();
   mCaptureTrackPort = nullptr;
 }
 
 void AudioDestinationNode::CreateAudioWakeLockIfNeeded() {
-  if (!mWakeLock) {
+  if (!mWakeLock && IsAudible()) {
     RefPtr<power::PowerManagerService> pmService =
         power::PowerManagerService::GetInstance();
     NS_ENSURE_TRUE_VOID(pmService);
 
     ErrorResult rv;
     mWakeLock = pmService->NewWakeLock(u"audio-playing"_ns, GetOwner(), rv);
   }
 }
@@ -599,64 +612,61 @@ void AudioDestinationNode::CreateAudioWa
 void AudioDestinationNode::ReleaseAudioWakeLockIfExists() {
   if (mWakeLock) {
     IgnoredErrorResult rv;
     mWakeLock->Unlock(rv);
     mWakeLock = nullptr;
   }
 }
 
-nsresult AudioDestinationNode::CreateAudioChannelAgent() {
-  if (mIsOffline || mAudioChannelAgent) {
-    return NS_OK;
-  }
-
-  mAudioChannelAgent = new AudioChannelAgent();
-  nsresult rv = mAudioChannelAgent->InitWithWeakCallback(GetOwner(), this);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-void AudioDestinationNode::NotifyAudibleStateChanged(bool aAudible) {
-  MOZ_ASSERT(Context() && !Context()->IsOffline());
-
-  if (!mAudioChannelAgent) {
-    if (!aAudible) {
-      return;
-    }
-    CreateAudioChannelAgent();
-  }
+void AudioDestinationNode::NotifyDataAudibleStateChanged(bool aAudible) {
+  MOZ_ASSERT(!mIsOffline);
 
   AUDIO_CHANNEL_LOG(
-      "AudioDestinationNode %p NotifyAudibleStateChanged, audible=%d", this,
+      "AudioDestinationNode %p NotifyDataAudibleStateChanged, audible=%d", this,
       aAudible);
 
-  if (!aAudible) {
-    mAudioChannelAgent->NotifyStoppedPlaying();
-    // Reset the state, and it would always be regard as audible.
-    mAudible = AudioChannelService::AudibleState::eAudible;
-    if (IsCapturingAudio()) {
-      StopAudioCapturingTrack();
-    }
-    ReleaseAudioWakeLockIfExists();
-    return;
-  }
-
   if (mDurationBeforeFirstTimeAudible.IsZero()) {
     MOZ_ASSERT(aAudible);
     mDurationBeforeFirstTimeAudible = TimeStamp::Now() - mCreatedTime;
     Telemetry::Accumulate(Telemetry::WEB_AUDIO_BECOMES_AUDIBLE_TIME,
                           mDurationBeforeFirstTimeAudible.ToSeconds());
   }
 
-  nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(mAudible);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  mIsDataAudible = aAudible;
+  UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::eDataAudibleChanged);
+}
+
+void AudioDestinationNode::UpdateFinalAudibleStateIfNeeded(
+    AudibleChangedReasons aReason) {
+  // The audio context has been closed and we've destroyed the agent.
+  if (!mAudioChannelAgent) {
+    return;
+  }
+  const bool newAudibleState = IsAudible();
+  if (mFinalAudibleState == newAudibleState) {
     return;
   }
+  AUDIO_CHANNEL_LOG("AudioDestinationNode %p Final audible state=%d", this,
+                    newAudibleState);
+  mFinalAudibleState = newAudibleState;
+  AudibleState state =
+      mFinalAudibleState ? AudibleState::eAudible : AudibleState::eNotAudible;
+  mAudioChannelAgent->NotifyStartedAudible(state, aReason);
+  if (mFinalAudibleState) {
+    CreateAudioWakeLockIfNeeded();
+  } else {
+    ReleaseAudioWakeLockIfExists();
+  }
+}
 
-  mAudioChannelAgent->PullInitialUpdate();
+bool AudioDestinationNode::IsAudible() const {
+  // The desitionation node will be regarded as audible if all following
+  // conditions are true.
+  // (1) data audible state : both audio input and output are audible
+  // (2) window audible state : the tab isn't muted by tab sound indicator
+  // (3) audio context state : audio context should be running
+  return Context()->State() == AudioContextState::Running && mIsDataAudible &&
+         mAudioChannelVolume != 0.0;
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -20,92 +20,113 @@ class WakeLock;
 
 class AudioDestinationNode final : public AudioNode,
                                    public nsIAudioChannelAgentCallback,
                                    public MainThreadMediaTrackListener {
  public:
   // This node type knows what MediaTrackGraph to use based on
   // whether it's in offline mode.
   AudioDestinationNode(AudioContext* aContext, bool aIsOffline,
-                       bool aAllowedToStart, uint32_t aNumberOfChannels,
-                       uint32_t aLength);
+                       uint32_t aNumberOfChannels, uint32_t aLength);
 
   void DestroyMediaTrack() override;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationNode, AudioNode)
   NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   uint16_t NumberOfOutputs() const final { return 0; }
 
   uint32_t MaxChannelCount() const;
   void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override;
 
+  void Init();
+  void Close();
+
   // Returns the track or null after unlink.
   AudioNodeTrack* Track();
 
   void Mute();
   void Unmute();
 
   void Suspend();
   void Resume();
 
   void StartRendering(Promise* aPromise);
 
   void OfflineShutdown();
 
   void NotifyMainThreadTrackEnded() override;
   void FireOfflineCompletionEvent();
 
-  nsresult CreateAudioChannelAgent();
-  void DestroyAudioChannelAgent();
-
   const char* NodeType() const override { return "AudioDestinationNode"; }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
-  void NotifyAudibleStateChanged(bool aAudible);
+  void NotifyDataAudibleStateChanged(bool aAudible);
   void ResolvePromise(AudioBuffer* aRenderedBuffer);
 
   unsigned long Length() {
     MOZ_ASSERT(mIsOffline);
     return mFramesToProduce;
   }
 
+  void NotifyAudioContextStateChanged();
+
  protected:
   virtual ~AudioDestinationNode();
 
  private:
-  // These function are related to audio capturing. We would start capturing
+  // This would be created for non-offline audio context in order to receive
+  // tab's mute/suspend/audio capture state change and update the audible state
+  // to the tab.
+  void CreateAndStartAudioChannelAgent();
+  void DestroyAudioChannelAgentIfExists();
+  RefPtr<AudioChannelAgent> mAudioChannelAgent;
+
+  // These members are related to audio capturing. We would start capturing
   // audio if we're starting capturing audio from whole window, and MUST stop
   // capturing explicitly when we don't need to capture audio any more, because
   // we have to release the resource we allocated before.
   bool IsCapturingAudio() const;
   void StartAudioCapturingTrack();
   void StopAudioCapturingTrack();
+  RefPtr<MediaInputPort> mCaptureTrackPort;
+
+  // These members are used to determine if the destination node is actual
+  // audible and `mFinalAudibleState` represents the final result.
+  using AudibleChangedReasons = AudioChannelService::AudibleChangedReasons;
+  using AudibleState = AudioChannelService::AudibleState;
+  void UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons aReason);
+  bool IsAudible() const;
+  bool mFinalAudibleState = false;
+  bool mIsDataAudible = false;
+  float mAudioChannelVolume = 1.0;
+
+  // True if the audio channel disables the track for unvisited tab, and the
+  // track will be enabled again when the tab gets first visited, or a user
+  // presses the tab play icon.
+  bool mAudioChannelDisabled = false;
+
+  // When the destination node is audible, we would request a wakelock to
+  // prevent computer from sleeping in order to keep audio playing.
   void CreateAudioWakeLockIfNeeded();
   void ReleaseAudioWakeLockIfExists();
+  RefPtr<WakeLock> mWakeLock;
 
   SelfReference<AudioDestinationNode> mOfflineRenderingRef;
   uint32_t mFramesToProduce;
 
-  RefPtr<AudioChannelAgent> mAudioChannelAgent;
-  RefPtr<MediaInputPort> mCaptureTrackPort;
-
   RefPtr<Promise> mOfflineRenderingPromise;
-  RefPtr<WakeLock> mWakeLock;
 
   bool mIsOffline;
-  bool mAudioChannelSuspended;
-
-  AudioChannelService::AudibleState mAudible;
 
   // These varaibles are used to know how long AudioContext would become audible
   // since it was created.
   TimeStamp mCreatedTime;
   TimeDuration mDurationBeforeFirstTimeAudible;
 };
 
 }  // namespace dom