Bug 1180539 - Correctly dispatch media-playback notifications when an AudioContext is closed/suspended/resumed; r=baku
Since navigating away from a page with an active AudioContext will close
it internally, this patch fixes a similar issue to
bug 1180535 for Web
Audio too.
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_webaudioLoop.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+fetch("audio.ogg").then(response => {
+ return response.arrayBuffer();
+}).then(ab => {
+ return ac.decodeAudioData(ab);
+}).then(ab => {
+ var src = ac.createBufferSource();
+ src.buffer = ab;
+ src.loop = true;
+ src.start();
+ src.connect(ac.destination);
+ parent.runTest();
+});
+
+var suspendPromise;
+function suspendAC() {
+ suspendPromise = ac.suspend();
+}
+
+var resumePromise;
+function resumeAC() {
+ suspendPromise.then(() => {
+ resumePromise = ac.resume();
+ });
+}
+
+function closeAC() {
+ resumePromise.then(() => {
+ ac.close();
+ });
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_webaudioLoop2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+fetch("audio.ogg").then(response => {
+ return response.arrayBuffer();
+}).then(ab => {
+ return ac.decodeAudioData(ab);
+}).then(ab => {
+ var src = ac.createBufferSource();
+ src.buffer = ab;
+ src.loop = true;
+ src.start();
+ src.connect(ac.destination);
+});
+</script>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -232,16 +232,18 @@ support-files =
variable_style_sheet.sjs
viewport_helpers.js
w3element_traversal.svg
wholeTexty-helper.xml
file_nonascii_blob_url.html
referrerHelper.js
test_performance_user_timing.js
img_referrer_testserver.sjs
+ file_webaudioLoop.html
+ file_webaudioLoop2.html
[test_anonymousContent_api.html]
[test_anonymousContent_append_after_reflow.html]
[test_anonymousContent_insert.html]
[test_anonymousContent_manipulate_content.html]
[test_appname_override.html]
[test_audioWindowUtils.html]
[test_audioNotification.html]
@@ -300,16 +302,20 @@ skip-if = e10s || buildapp == 'b2g'
[test_url.html]
[test_url_data.html]
[test_url_empty_port.html]
[test_url_malformedHost.html]
[test_urlExceptions.html]
[test_urlSearchParams.html]
[test_urlSearchParams_utf8.html]
[test_urlutils_stringify.html]
+[test_webaudioNotification.html]
+skip-if = buildapp == 'mulet'
+[test_webaudioNotificationStopOnNavigation.html]
+skip-if = buildapp == 'mulet'
[test_window_constructor.html]
[test_window_cross_origin_props.html]
[test_window_define_symbol.html]
[test_window_enumeration.html]
[test_window_extensible.html]
[test_window_indexing.html]
[test_window_named_frame_enumeration.html]
[test_writable-replaceable.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_webaudioNotification.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "media-playback", "media-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ SimpleTest.executeSoon(runTest);
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+ },
+
+ function() {
+ iframe.src = "file_webaudioLoop.html";
+ },
+
+ function() {
+ observerService.addObserver(observer, "media-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'inactive';
+ iframe.contentWindow.suspendAC();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.contentWindow.resumeAC();
+ },
+
+ function() {
+ expectedNotification = 'inactive';
+ iframe.contentWindow.closeAC();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "media-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_webaudioNotificationStopOnNavigation.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "media-playback", "media-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+ },
+
+ function() {
+ observerService.addObserver(observer, "media-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.src = "file_webaudioLoop2.html";
+ },
+
+ function() {
+ expectedNotification = 'inactive';
+ iframe.src = "data:text/html,page without audio";
+ },
+
+ function() {
+ observerService.removeObserver(observer, "media-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -846,16 +846,18 @@ AudioContext::Suspend(ErrorResult& aRv)
return promise.forget();
}
if (mAudioContextState == AudioContextState::Suspended) {
promise->MaybeResolve(JS::UndefinedHandleValue);
return promise.forget();
}
+ Destination()->DestroyAudioChannelAgent();
+
MediaStream* ds = DestinationStream();
if (ds) {
ds->BlockStreamIfNeeded();
}
mPromiseGripArray.AppendElement(promise);
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
AudioContextOperation::Suspend, promise);
@@ -884,16 +886,18 @@ AudioContext::Resume(ErrorResult& aRv)
return promise.forget();
}
if (mAudioContextState == AudioContextState::Running) {
promise->MaybeResolve(JS::UndefinedHandleValue);
return promise.forget();
}
+ Destination()->CreateAudioChannelAgent();
+
MediaStream* ds = DestinationStream();
if (ds) {
ds->UnblockStreamIfNeeded();
}
mPromiseGripArray.AppendElement(promise);
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
AudioContextOperation::Resume, promise);
@@ -918,16 +922,18 @@ AudioContext::Close(ErrorResult& aRv)
if (mAudioContextState == AudioContextState::Closed) {
promise->MaybeResolve(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
mCloseCalled = true;
+ Destination()->DestroyAudioChannelAgent();
+
mPromiseGripArray.AppendElement(promise);
// This can be called when freeing a document, and the streams are dead at
// this point, so we need extra null-checks.
MediaStream* ds = DestinationStream();
if (ds) {
Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(),
AudioContextOperation::Close, promise);
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -398,29 +398,35 @@ AudioDestinationNode::SizeOfExcludingThi
size_t
AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
void
-AudioDestinationNode::DestroyMediaStream()
+AudioDestinationNode::DestroyAudioChannelAgent()
{
if (mAudioChannelAgent && !Context()->IsOffline()) {
mAudioChannelAgent->StopPlaying();
mAudioChannelAgent = nullptr;
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE_VOID(target);
target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
mEventProxyHelper,
/* useCapture = */ true);
}
+}
+
+void
+AudioDestinationNode::DestroyMediaStream()
+{
+ DestroyAudioChannelAgent();
if (!mStream)
return;
mStream->RemoveMainThreadListener(this);
MediaStreamGraph* graph = mStream->Graph();
if (graph->IsNonRealtime()) {
MediaStreamGraph::DestroyNonRealtimeInstance(graph);
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -73,16 +73,17 @@ public:
// An amount that should be added to the MediaStream's current time to
// get the AudioContext.currentTime.
double ExtraCurrentTime();
// When aIsOnlyNode is true, this is the only node for the AudioContext.
void SetIsOnlyNodeForContext(bool aIsOnlyNode);
void CreateAudioChannelAgent();
+ void DestroyAudioChannelAgent();
virtual const char* NodeType() const override
{
return "AudioDestinationNode";
}
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;