Bug 1190023 - Ensure that muting a media element dispatches the media-playback event, and also include the muted state in computing whether a media element is actively playing audio; r=cpearce
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 01 Aug 2015 10:31:04 -0400
changeset 287909 a0e2077727c9824353022a60f4f8c230a686f935
parent 287908 c8c81a51b3fcd57307f74deb41b58395517a3dd5
child 287910 5c158420a7faecf31952003a2ae28790f423fc05
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1190023
milestone42.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 1190023 - Ensure that muting a media element dispatches the media-playback event, and also include the muted state in computing whether a media element is actively playing audio; r=cpearce
dom/base/test/mochitest.ini
dom/base/test/test_noAudioNotificationOnMutedElement.html
dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html
dom/base/test/test_noAudioNotificationOnVolume0Element.html
dom/html/HTMLMediaElement.cpp
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -296,16 +296,19 @@ skip-if = buildapp == 'mulet' || buildap
 [test_history_state_null.html]
 [test_Image_constructor.html]
 [test_innersize_scrollport.html]
 [test_messagemanager_targetchain.html]
 [test_named_frames.html]
 [test_navigator_resolve_identity.html]
 [test_navigator_language.html]
 [test_noAudioNotification.html]
+[test_noAudioNotificationOnMutedElement.html]
+[test_noAudioNotificationOnMutedOrVolume0Element.html]
+[test_noAudioNotificationOnVolume0Element.html]
 [test_openDialogChromeOnly.html]
 [test_open_null_features.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, tracked in bug 1011874
 [test_postMessage_solidus.html]
 [test_pluginAudioNotification.html]
 skip-if = (buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android') # Plugins don't work on Android and/or B2G/Mulet
 [test_screen_orientation.html]
 [test_settimeout_extra_arguments.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnMutedElement.html
@@ -0,0 +1,133 @@
+<!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>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    if (expectedNotification !== null) {
+      is(topic, "media-playback", "media-playback received");
+      is(data, expectedNotification, "This is the right notification");
+      runTest();
+    } else {
+      ok(false, "should not receive media-playback notification!");
+    }
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+  },
+
+  function() {
+    observerService.addObserver(observer, "media-playback", false);
+    ok(true, "Observer set");
+
+    audio.src = "audio.ogg";
+    // Wait for the audio metadata to become available so that we have an audio track.
+    audio.onloadedmetadata = runTest;
+  },
+
+  // Verify that muting and unmuting dispatches the events as expected.
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.muted = true;
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.muted = false;
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  // Verify that no events are dispatched when muted.
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.muted = true;
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.onpause  = function() {
+      // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          SimpleTest.executeSoon(function() {
+            audio.onpause = null;
+            runTest();
+          });
+        });
+      });
+    };
+    audio.pause();
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.muted = false;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    observerService.removeObserver(observer, "media-playback");
+    ok(true, "Observer removed");
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html
@@ -0,0 +1,166 @@
+<!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>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    if (expectedNotification !== null) {
+      is(topic, "media-playback", "media-playback received");
+      is(data, expectedNotification, "This is the right notification");
+      runTest();
+    } else {
+      ok(false, "should not receive media-playback notification!");
+    }
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+  },
+
+  function() {
+    observerService.addObserver(observer, "media-playback", false);
+    ok(true, "Observer set");
+
+    audio.src = "audio.ogg";
+    // Wait for the audio metadata to become available so that we have an audio track.
+    audio.onloadedmetadata = runTest;
+  },
+
+  // Verify that unmuting when the volume is 0 doesn't dispatch the events.
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.muted = true;
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.volume = 0;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.muted = false;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.volume = 0.5;
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  // Verify that raising the volume when muted doesn't dispatch the events.
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.muted = true;
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.volume = 0;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.volume = 0.5;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.muted = false;
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  function() {
+    observerService.removeObserver(observer, "media-playback");
+    ok(true, "Observer removed");
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnVolume0Element.html
@@ -0,0 +1,133 @@
+<!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>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    if (expectedNotification !== null) {
+      is(topic, "media-playback", "media-playback received");
+      is(data, expectedNotification, "This is the right notification");
+      runTest();
+    } else {
+      ok(false, "should not receive media-playback notification!");
+    }
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+  },
+
+  function() {
+    observerService.addObserver(observer, "media-playback", false);
+    ok(true, "Observer set");
+
+    audio.src = "audio.ogg";
+    // Wait for the audio metadata to become available so that we have an audio track.
+    audio.onloadedmetadata = runTest;
+  },
+
+  // Verify that muting and unmuting dispatches the events as expected.
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.volume = 0;
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.volume = 1;
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  // Verify that no events are dispatched when volume is set to 0..
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.volume = 0;
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.onpause  = function() {
+      // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          SimpleTest.executeSoon(function() {
+            audio.onpause = null;
+            runTest();
+          });
+        });
+      });
+    };
+    audio.pause();
+  },
+
+  function() {
+    expectedNotification = null;
+    audio.volume = 1;
+    // Yield to the event loop a few times to make sure that media-playback is not dispatched.
+    SimpleTest.executeSoon(function() {
+      SimpleTest.executeSoon(function() {
+        SimpleTest.executeSoon(function() {
+          runTest();
+        });
+      });
+    });
+  },
+
+  function() {
+    observerService.removeObserver(observer, "media-playback");
+    ok(true, "Observer removed");
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -84,16 +84,17 @@
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/TextTrack.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/Telemetry.h"
 
 #include "ImageContainer.h"
 #include "nsRange.h"
 #include <algorithm>
+#include <cmath>
 
 static PRLogModuleInfo* gMediaElementLog;
 static PRLogModuleInfo* gMediaElementEventsLog;
 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
 
 #include "nsIContentSecurityPolicy.h"
 
@@ -1795,16 +1796,18 @@ void HTMLMediaElement::SetVolumeInternal
 {
   float effectiveVolume = ComputedVolume();
 
   if (mDecoder) {
     mDecoder->SetVolume(effectiveVolume);
   } else if (mSrcStream) {
     GetSrcMediaStream()->SetAudioOutputVolume(this, effectiveVolume);
   }
+
+  UpdateAudioChannelPlayingState();
 }
 
 NS_IMETHODIMP HTMLMediaElement::SetMuted(bool aMuted)
 {
   if (aMuted == Muted()) {
     return NS_OK;
   }
 
@@ -4469,16 +4472,18 @@ nsresult HTMLMediaElement::UpdateChannel
 void HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   if (!UseAudioChannelService()) {
     return;
   }
 
   bool playingThroughTheAudioChannel =
      (!mPaused &&
+      !Muted() &&
+      std::fabs(Volume()) > 1e-7 &&
       (HasAttr(kNameSpaceID_None, nsGkAtoms::loop) ||
        (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
         !IsPlaybackEnded()) ||
        mPlayingThroughTheAudioChannelBeforeSeek));
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.