Bug 1200208 - Send the audio-playback notification when the page calls HTMLMediaElement::Play() before the metadata has been fully loaded; r=baku a=sylvestre
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 31 Aug 2015 12:50:20 -0400
changeset 289105 530b62fff9e79e4744f6e77937c29c9550f08eb9
parent 289104 408d44ce14a193f3d836f447ad3a221def75384c
child 289106 de1f4476b12cac70c2b08d711c4d35dc4d438d17
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)
reviewersbaku, sylvestre
bugs1200208
milestone42.0a2
Bug 1200208 - Send the audio-playback notification when the page calls HTMLMediaElement::Play() before the metadata has been fully loaded; r=baku a=sylvestre
dom/base/test/mochitest.ini
dom/base/test/test_audioNotificationWithEarlyPlay.html
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -257,16 +257,18 @@ support-files =
 [test_appname_override.html]
 [test_async_setTimeout_stack.html]
 [test_async_setTimeout_stack_across_globals.html]
 [test_audioWindowUtils.html]
 [test_audioNotification.html]
 skip-if = buildapp == 'mulet'
 [test_audioNotificationStopOnNavigation.html]
 skip-if = buildapp == 'mulet'
+[test_audioNotificationWithEarlyPlay.html]
+skip-if = buildapp == 'mulet'
 [test_bug1091883.html]
 [test_bug116083.html]
 [test_bug793311.html]
 [test_bug913761.html]
 [test_bug976673.html]
 [test_bug978522.html]
 [test_bug979109.html]
 [test_bug989665.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_audioNotificationWithEarlyPlay.html
@@ -0,0 +1,73 @@
+<!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) {
+    is(topic, "audio-playback", "audio-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 audio = new Audio();
+audio.loop = true;
+audio.preload = "metadata";
+
+var tests = [
+  function() {
+    observerService.addObserver(observer, "audio-playback", false);
+    ok(true, "Observer set");
+    runTest();
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.src = "audio.ogg";
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  function() {
+    observerService.removeObserver(observer, "audio-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
@@ -111,16 +111,44 @@ static PRLogModuleInfo* gMediaElementEve
 // This controls the b2g specific of pausing the media element when the
 // AudioChannel tells us to mute it.
 #define PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
 #endif
 
 using namespace mozilla::layers;
 using mozilla::net::nsMediaFragmentURIParser;
 
+class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
+{
+  nsRefPtr<mozilla::dom::HTMLMediaElement> mElement;
+  bool mShouldNotify;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+public:
+  AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement,
+                              bool aNotify
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+    : mElement(aElement)
+    , mShouldNotify(aNotify)
+  {
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    if (mShouldNotify) {
+      mElement->NotifyAudioChannelAgent(false);
+    }
+  }
+  ~AutoNotifyAudioChannelAgent()
+  {
+    if (mShouldNotify) {
+      // The audio channel agent is destroyed at this point.
+      if (mElement->MaybeCreateAudioChannelAgent()) {
+        mElement->NotifyAudioChannelAgent(true);
+      }
+    }
+  }
+};
+
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between progress events as defined by spec
 static const uint32_t PROGRESS_MS = 350;
 
 // Number of milliseconds of no data before a stall event is fired as defined by spec
 static const uint32_t STALL_MS = 3000;
@@ -3181,16 +3209,24 @@ void HTMLMediaElement::ProcessMediaFragm
   }
 }
 
 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
                                       nsAutoPtr<const MetadataTags> aTags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // If the element is gaining or losing an audio track, we need to notify
+  // the audio channel agent so that the correct audio-playback events will
+  // get dispatched.
+  bool audioTrackChanging = mMediaInfo.HasAudio() != aInfo->HasAudio();
+  AutoNotifyAudioChannelAgent autoNotify(this,
+                                         audioTrackChanging &&
+                                         mPlayingThroughTheAudioChannel);
+
   mMediaInfo = *aInfo;
   mIsEncrypted = aInfo->IsEncrypted()
 #ifdef MOZ_EME
                  || mPendingEncryptedInitData.IsEncrypted()
 #endif // MOZ_EME
                  ;
   mTags = aTags.forget();
   mLoadedDataFired = false;
@@ -4476,17 +4512,35 @@ nsresult HTMLMediaElement::UpdateChannel
   }
 
 #ifdef PAUSE_MEDIA_ELEMENT_FROM_AUDIOCHANNEL
   SuspendOrResumeElement(ComputedMuted(), false);
 #endif
   return NS_OK;
 }
 
-void HTMLMediaElement::UpdateAudioChannelPlayingState()
+bool
+HTMLMediaElement::MaybeCreateAudioChannelAgent()
+{
+  if (!mAudioChannelAgent) {
+    nsresult rv;
+    mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+    }
+    MOZ_ASSERT(mAudioChannelAgent);
+    mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
+                                             static_cast<int32_t>(mAudioChannel),
+                                             this);
+  }
+  return true;
+}
+
+void
+HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   if (!UseAudioChannelService()) {
     return;
   }
 
   bool playingThroughTheAudioChannel =
      (!mPaused &&
       !Muted() &&
@@ -4498,28 +4552,19 @@ void HTMLMediaElement::UpdateAudioChanne
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.
     if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) {
        return;
     }
 
-    if (!mAudioChannelAgent) {
-      nsresult rv;
-      mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
-      if (!mAudioChannelAgent) {
-        return;
-      }
-      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
-                                               static_cast<int32_t>(mAudioChannel),
-                                               this);
+    if (MaybeCreateAudioChannelAgent()) {
+      NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
     }
-
-    NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
   }
 }
 
 void
 HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
 {
   // Immediately check if this should go to the MSG instead of the normal
   // media playback route.
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -30,20 +30,16 @@
 #undef CurrentTime
 #endif
 
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
-class nsIChannel;
-class nsIHttpChannel;
-class nsILoadGroup;
-
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
@@ -51,19 +47,23 @@ namespace dom {
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 class MediaTrack;
 } // namespace dom
 } // namespace mozilla
 
+class AutoNotifyAudioChannelAgent;
+class nsIChannel;
+class nsIHttpChannel;
+class nsILoadGroup;
+class nsIRunnable;
 class nsITimer;
 class nsRange;
-class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between timeupdate events as defined by spec
 #define TIMEUPDATE_MS 250
 
 class MediaError;
@@ -73,16 +73,18 @@ class AudioTrackList;
 class VideoTrackList;
 
 class HTMLMediaElement : public nsGenericHTMLElement,
                          public nsIDOMHTMLMediaElement,
                          public nsIObserver,
                          public MediaDecoderOwner,
                          public nsIAudioChannelAgentCallback
 {
+  friend AutoNotifyAudioChannelAgent;
+
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
   typedef mozilla::MediaStream MediaStream;
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::MediaDecoderOwner MediaDecoderOwner;
   typedef mozilla::MetadataTags MetadataTags;
@@ -1045,16 +1047,20 @@ protected:
   TextTrackManager* GetOrCreateTextTrackManager();
 
   // Recomputes ready state and fires events as necessary based on current state.
   void UpdateReadyStateInternal();
 
   // Notifies the audio channel agent when the element starts or stops playing.
   void NotifyAudioChannelAgent(bool aPlaying);
 
+  // Creates the audio channel agent if needed.  Returns true if the audio
+  // channel agent is ready to be used.
+  bool MaybeCreateAudioChannelAgent();
+
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;