зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1489278
- part1 : show doorhanger when create AudioContext r=padenot
In the ideal situation, sites should create AudioContext only when sites are going to produce sound, so we would show doorhanger to ask users whether they want to allow autoplay. We delay the AudioContext's state transition from `suspended` to `running` until (1) user click 'Allow' button in doorhanger (2) user interact with sites, and then AudioContext calls resume() again Differential Revision: https://phabricator.services.mozilla.com/D5610 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
6f1500f089
Коммит
d7a75adbdb
|
@ -195,7 +195,7 @@ AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
|
|||
}
|
||||
|
||||
/* static */ bool
|
||||
AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
|
||||
AutoplayPolicy::IsAllowedToPlay(const AudioContext& aContext)
|
||||
{
|
||||
if (!Preferences::GetBool("media.autoplay.block-webaudio", false)) {
|
||||
return true;
|
||||
|
@ -210,11 +210,11 @@ AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
|
|||
}
|
||||
|
||||
// Offline context won't directly output sound to audio devices.
|
||||
if (aContext->IsOffline()) {
|
||||
if (aContext.IsOffline()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsWindowAllowedToPlay(aContext->GetOwner())) {
|
||||
if (IsWindowAllowedToPlay(aContext.GetParentObject())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ public:
|
|||
// Returns whether a given media element is allowed to play.
|
||||
static bool IsAllowedToPlay(const HTMLMediaElement& aElement);
|
||||
|
||||
// Returns whether a given AudioContext is allowed to play.
|
||||
static bool IsAllowedToPlay(const AudioContext& aContext);
|
||||
|
||||
// Returns true if a given media element would be allowed to play
|
||||
// if block autoplay was enabled. If this returns false, it means we would
|
||||
// either block or ask for permission.
|
||||
|
@ -45,9 +48,6 @@ public:
|
|||
// which enable/disable block autoplay. Do not use for blocking logic!
|
||||
static bool WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement);
|
||||
|
||||
// Returns whether a given AudioContext is allowed to play.
|
||||
static bool IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext);
|
||||
|
||||
// Returns the AutoplayPermissionManager that a given document must request on
|
||||
// for autoplay permission.
|
||||
static already_AddRefed<AutoplayPermissionManager> RequestFor(
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "blink/PeriodicWave.h"
|
||||
|
||||
#include "mozilla/AutoplayPermissionManager.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/OwningNonNull.h"
|
||||
|
@ -74,6 +75,11 @@
|
|||
#include "StereoPannerNode.h"
|
||||
#include "WaveShaperNode.h"
|
||||
|
||||
extern mozilla::LazyLogModule gAutoplayPermissionLog;
|
||||
|
||||
#define AUTOPLAY_LOG(msg, ...) \
|
||||
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
@ -153,7 +159,7 @@ AudioContext::AudioContext(nsPIDOMWindowInner* aWindow,
|
|||
|
||||
// Note: AudioDestinationNode needs an AudioContext that must already be
|
||||
// bound to the window.
|
||||
bool allowedToStart = AutoplayPolicy::IsAudioContextAllowedToPlay(WrapNotNull(this));
|
||||
bool allowedToStart = AutoplayPolicy::IsAllowedToPlay(*this);
|
||||
mDestination = new AudioDestinationNode(this,
|
||||
aIsOffline,
|
||||
allowedToStart,
|
||||
|
@ -166,19 +172,47 @@ AudioContext::AudioContext(nsPIDOMWindowInner* aWindow,
|
|||
Mute();
|
||||
}
|
||||
|
||||
// If we won't allow audio context to start, we need to suspend all its stream
|
||||
// in order to delay the state changing from 'suspend' to 'start'.
|
||||
if (!allowedToStart) {
|
||||
ErrorResult rv;
|
||||
RefPtr<Promise> dummy = Suspend(rv);
|
||||
MOZ_ASSERT(!rv.Failed(), "can't create promise");
|
||||
MOZ_ASSERT(dummy->State() != Promise::PromiseState::Rejected,
|
||||
"suspend failed");
|
||||
// Not allowed to start, delay the transition from `suspended` to `running`.
|
||||
SuspendInternal(nullptr);
|
||||
EnsureAutoplayRequested();
|
||||
}
|
||||
|
||||
FFTBlock::MainThreadInit();
|
||||
}
|
||||
|
||||
void
|
||||
AudioContext::EnsureAutoplayRequested()
|
||||
{
|
||||
nsPIDOMWindowInner* parent = GetParentObject();
|
||||
if (!parent || !parent->AsGlobal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<AutoplayPermissionManager> request =
|
||||
AutoplayPolicy::RequestFor(*(parent->GetExtantDoc()));
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<AudioContext> self = this;
|
||||
request->RequestWithPrompt()
|
||||
->Then(parent->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other),
|
||||
__func__,
|
||||
[ self, request ](
|
||||
bool aApproved) {
|
||||
AUTOPLAY_LOG("%p Autoplay request approved request=%p",
|
||||
self.get(),
|
||||
request.get());
|
||||
self->ResumeInternal();
|
||||
},
|
||||
[self, request](nsresult aError) {
|
||||
AUTOPLAY_LOG("%p Autoplay request denied request=%p",
|
||||
self.get(),
|
||||
request.get());
|
||||
});
|
||||
}
|
||||
|
||||
nsresult
|
||||
AudioContext::Init()
|
||||
{
|
||||
|
@ -930,11 +964,6 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
|||
#endif // XP_MACOSX
|
||||
#endif // WIN32
|
||||
|
||||
MOZ_ASSERT(
|
||||
mIsOffline || aPromise || aNewState == AudioContextState::Running,
|
||||
"We should have a promise here if this is a real-time AudioContext."
|
||||
"Or this is the first time we switch to \"running\".");
|
||||
|
||||
if (aPromise) {
|
||||
Promise* promise = reinterpret_cast<Promise*>(aPromise);
|
||||
// It is possible for the promise to have been removed from
|
||||
|
@ -998,9 +1027,15 @@ AudioContext::Suspend(ErrorResult& aRv)
|
|||
return promise.forget();
|
||||
}
|
||||
|
||||
Destination()->Suspend();
|
||||
|
||||
mPromiseGripArray.AppendElement(promise);
|
||||
SuspendInternal(promise);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
AudioContext::SuspendInternal(void* aPromise)
|
||||
{
|
||||
Destination()->Suspend();
|
||||
|
||||
nsTArray<MediaStream*> streams;
|
||||
// If mSuspendCalled is true then we already suspended all our streams,
|
||||
|
@ -1012,11 +1047,10 @@ AudioContext::Suspend(ErrorResult& aRv)
|
|||
}
|
||||
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
|
||||
streams,
|
||||
AudioContextOperation::Suspend, promise);
|
||||
AudioContextOperation::Suspend,
|
||||
aPromise);
|
||||
|
||||
mSuspendCalled = true;
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
|
@ -1042,26 +1076,35 @@ AudioContext::Resume(ErrorResult& aRv)
|
|||
|
||||
mPendingResumePromises.AppendElement(promise);
|
||||
|
||||
if (AutoplayPolicy::IsAudioContextAllowedToPlay(WrapNotNull(this))) {
|
||||
Destination()->Resume();
|
||||
|
||||
nsTArray<MediaStream*> streams;
|
||||
// If mSuspendCalled is false then we already resumed all our streams,
|
||||
// so don't resume them again (since suspend(); resume(); resume(); should
|
||||
// be OK). But we still need to do ApplyAudioContextOperation
|
||||
// to ensure our new promise is resolved.
|
||||
if (mSuspendCalled) {
|
||||
streams = GetAllStreams();
|
||||
}
|
||||
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
|
||||
streams,
|
||||
AudioContextOperation::Resume, promise);
|
||||
mSuspendCalled = false;
|
||||
const bool isAllowedToPlay = AutoplayPolicy::IsAllowedToPlay(*this);
|
||||
if (isAllowedToPlay) {
|
||||
ResumeInternal();
|
||||
}
|
||||
|
||||
AUTOPLAY_LOG("Resume AudioContext %p, IsAllowedToPlay=%d",
|
||||
this, isAllowedToPlay);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
AudioContext::ResumeInternal()
|
||||
{
|
||||
Destination()->Resume();
|
||||
|
||||
nsTArray<MediaStream*> streams;
|
||||
// If mSuspendCalled is false then we already resumed all our streams,
|
||||
// so don't resume them again (since suspend(); resume(); resume(); should
|
||||
// be OK). But we still need to do ApplyAudioContextOperation
|
||||
// to ensure our new promise is resolved.
|
||||
if (mSuspendCalled) {
|
||||
streams = GetAllStreams();
|
||||
}
|
||||
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
|
||||
streams,
|
||||
AudioContextOperation::Resume,
|
||||
nullptr);
|
||||
mSuspendCalled = false;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
AudioContext::Close(ErrorResult& aRv)
|
||||
{
|
||||
|
|
|
@ -341,6 +341,12 @@ private:
|
|||
|
||||
nsTArray<MediaStream*> GetAllStreams() const;
|
||||
|
||||
// Request the prompt to ask for user's approval for autoplay.
|
||||
void EnsureAutoplayRequested();
|
||||
|
||||
void ResumeInternal();
|
||||
void SuspendInternal(void* aPromise);
|
||||
|
||||
private:
|
||||
// Each AudioContext has an id, that is passed down the MediaStreams that
|
||||
// back the AudioNodes, so we can easily compute the set of all the
|
||||
|
|
Загрузка…
Ссылка в новой задаче