1 
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "MediaPlaybackDelayPolicy.h"
7 
8 #include "nsPIDOMWindow.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/HTMLMediaElement.h"
11 #include "mozilla/StaticPrefs_media.h"
12 
13 namespace mozilla::dom {
14 
15 using AudibleState = AudioChannelService::AudibleState;
16 
DetermineMediaAudibleState(const HTMLMediaElement * aElement,bool aIsAudible)17 static AudibleState DetermineMediaAudibleState(const HTMLMediaElement* aElement,
18                                                bool aIsAudible) {
19   MOZ_ASSERT(aElement);
20   if (!aElement->HasAudio()) {
21     return AudibleState::eNotAudible;
22   }
23   // `eMaybeAudible` is used to distinguish if the media has audio track or not,
24   // because we would only show the delayed media playback icon for media with
25   // an audio track.
26   return aIsAudible ? AudibleState::eAudible : AudibleState::eMaybeAudible;
27 }
28 
~ResumeDelayedPlaybackAgent()29 ResumeDelayedPlaybackAgent::~ResumeDelayedPlaybackAgent() {
30   if (mDelegate) {
31     mDelegate->Clear();
32     mDelegate = nullptr;
33   }
34 }
35 
InitDelegate(const HTMLMediaElement * aElement,bool aIsAudible)36 bool ResumeDelayedPlaybackAgent::InitDelegate(const HTMLMediaElement* aElement,
37                                               bool aIsAudible) {
38   MOZ_ASSERT(!mDelegate, "Delegate has been initialized!");
39   mDelegate = new ResumePlayDelegate();
40   if (!mDelegate->Init(aElement, aIsAudible)) {
41     mDelegate->Clear();
42     mDelegate = nullptr;
43     return false;
44   }
45   return true;
46 }
47 
48 RefPtr<ResumeDelayedPlaybackAgent::ResumePromise>
GetResumePromise()49 ResumeDelayedPlaybackAgent::GetResumePromise() {
50   MOZ_ASSERT(mDelegate);
51   return mDelegate->GetResumePromise();
52 }
53 
UpdateAudibleState(const HTMLMediaElement * aElement,bool aIsAudible)54 void ResumeDelayedPlaybackAgent::UpdateAudibleState(
55     const HTMLMediaElement* aElement, bool aIsAudible) {
56   MOZ_ASSERT(aElement);
57   MOZ_ASSERT(mDelegate);
58   mDelegate->UpdateAudibleState(aElement, aIsAudible);
59 }
60 
NS_IMPL_ISUPPORTS(ResumeDelayedPlaybackAgent::ResumePlayDelegate,nsIAudioChannelAgentCallback)61 NS_IMPL_ISUPPORTS(ResumeDelayedPlaybackAgent::ResumePlayDelegate,
62                   nsIAudioChannelAgentCallback)
63 
64 ResumeDelayedPlaybackAgent::ResumePlayDelegate::~ResumePlayDelegate() {
65   MOZ_ASSERT(!mAudioChannelAgent);
66 }
67 
Init(const HTMLMediaElement * aElement,bool aIsAudible)68 bool ResumeDelayedPlaybackAgent::ResumePlayDelegate::Init(
69     const HTMLMediaElement* aElement, bool aIsAudible) {
70   MOZ_ASSERT(aElement);
71   if (!aElement->OwnerDoc()->GetInnerWindow()) {
72     return false;
73   }
74 
75   MOZ_ASSERT(!mAudioChannelAgent);
76   mAudioChannelAgent = new AudioChannelAgent();
77   nsresult rv =
78       mAudioChannelAgent->Init(aElement->OwnerDoc()->GetInnerWindow(), this);
79   if (NS_WARN_IF(NS_FAILED(rv))) {
80     Clear();
81     return false;
82   }
83 
84   // Start AudioChannelAgent in order to wait the suspended state change when we
85   // are able to resume delayed playback.
86   AudibleState audibleState = DetermineMediaAudibleState(aElement, aIsAudible);
87   rv = mAudioChannelAgent->NotifyStartedPlaying(audibleState);
88   if (NS_WARN_IF(NS_FAILED(rv))) {
89     Clear();
90     return false;
91   }
92 
93   return true;
94 }
95 
Clear()96 void ResumeDelayedPlaybackAgent::ResumePlayDelegate::Clear() {
97   if (mAudioChannelAgent) {
98     mAudioChannelAgent->NotifyStoppedPlaying();
99     mAudioChannelAgent = nullptr;
100   }
101   mPromise.RejectIfExists(true, __func__);
102 }
103 
104 RefPtr<ResumeDelayedPlaybackAgent::ResumePromise>
GetResumePromise()105 ResumeDelayedPlaybackAgent::ResumePlayDelegate::GetResumePromise() {
106   return mPromise.Ensure(__func__);
107 }
108 
UpdateAudibleState(const HTMLMediaElement * aElement,bool aIsAudible)109 void ResumeDelayedPlaybackAgent::ResumePlayDelegate::UpdateAudibleState(
110     const HTMLMediaElement* aElement, bool aIsAudible) {
111   MOZ_ASSERT(aElement);
112   // It's possible for the owner of `ResumeDelayedPlaybackAgent` to call
113   // `UpdateAudibleState()` after we resolve the promise and clean resource.
114   if (!mAudioChannelAgent) {
115     return;
116   }
117   AudibleState audibleState = DetermineMediaAudibleState(aElement, aIsAudible);
118   mAudioChannelAgent->NotifyStartedAudible(
119       audibleState,
120       AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
121 }
122 
123 NS_IMETHODIMP
WindowVolumeChanged(float aVolume,bool aMuted)124 ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowVolumeChanged(
125     float aVolume, bool aMuted) {
126   return NS_OK;
127 }
128 
129 NS_IMETHODIMP
WindowAudioCaptureChanged(bool aCapture)130 ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowAudioCaptureChanged(
131     bool aCapture) {
132   return NS_OK;
133 }
134 
135 NS_IMETHODIMP
WindowSuspendChanged(SuspendTypes aSuspend)136 ResumeDelayedPlaybackAgent::ResumePlayDelegate::WindowSuspendChanged(
137     SuspendTypes aSuspend) {
138   if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED) {
139     mPromise.ResolveIfExists(true, __func__);
140     Clear();
141   }
142   return NS_OK;
143 }
144 
ShouldDelayPlayback(const HTMLMediaElement * aElement)145 bool MediaPlaybackDelayPolicy::ShouldDelayPlayback(
146     const HTMLMediaElement* aElement) {
147   MOZ_ASSERT(aElement);
148   if (!StaticPrefs::media_block_autoplay_until_in_foreground()) {
149     return false;
150   }
151 
152   const Document* doc = aElement->OwnerDoc();
153   nsPIDOMWindowInner* inner = nsPIDOMWindowInner::From(doc->GetInnerWindow());
154   nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::GetFromCurrentInner(inner);
155   if (!outer) {
156     return false;
157   }
158   return outer->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK;
159 }
160 
161 RefPtr<ResumeDelayedPlaybackAgent>
CreateResumeDelayedPlaybackAgent(const HTMLMediaElement * aElement,bool aIsAudible)162 MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(
163     const HTMLMediaElement* aElement, bool aIsAudible) {
164   MOZ_ASSERT(aElement);
165   RefPtr<ResumeDelayedPlaybackAgent> agent = new ResumeDelayedPlaybackAgent();
166   return agent->InitDelegate(aElement, aIsAudible) ? agent : nullptr;
167 }
168 
169 }  // namespace mozilla::dom
170