Bug 860150 - [Audio] Audio_Data API should join audio channel. r=kinetik
authorMarco Chen <mchen@mozilla.com>
Mon, 29 Apr 2013 14:45:13 +0800
changeset 141121 06f4af96580ee7f6b48f6e2d4c87554461a38889
parent 141120 1be592ac3c8d2c1288921f21c6af37331d538248
child 141128 a885c91e069a8daa8f045f77268940715d6a4aea
child 141181 5e2db92c9675ffb512f1c477b48eada8fa0dc0a7
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs860150
milestone23.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 860150 - [Audio] Audio_Data API should join audio channel. r=kinetik
content/html/content/public/HTMLAudioElement.h
content/html/content/public/HTMLMediaElement.h
content/html/content/src/HTMLAudioElement.cpp
--- a/content/html/content/public/HTMLAudioElement.h
+++ b/content/html/content/public/HTMLAudioElement.h
@@ -1,27 +1,29 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef mozilla_dom_HTMLAudioElement_h
 #define mozilla_dom_HTMLAudioElement_h
 
+#include "nsITimer.h"
 #include "nsIDOMHTMLAudioElement.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/TypedArray.h"
 
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 namespace dom {
 
 class HTMLAudioElement : public HTMLMediaElement,
+                         public nsITimerCallback,
                          public nsIDOMHTMLAudioElement
 {
 public:
   HTMLAudioElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~HTMLAudioElement();
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
@@ -34,16 +36,22 @@ public:
 
   // nsIDOMHTMLElement
   NS_FORWARD_NSIDOMHTMLELEMENT_TO_GENERIC
 
   // nsIDOMHTMLMediaElement
   using HTMLMediaElement::GetPaused;
   NS_FORWARD_NSIDOMHTMLMEDIAELEMENT(HTMLMediaElement::)
 
+  // nsIAudioChannelAgentCallback
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
+
+  // NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSITIMERCALLBACK
+
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel);
 
   virtual nsIDOMNode* AsDOMNode() { return this; }
 
   // WebIDL
 
   static already_AddRefed<HTMLAudioElement> Audio(const GlobalObject& global,
@@ -63,14 +71,23 @@ public:
   uint32_t MozWriteAudio(const float* aData, uint32_t aLength,
                          ErrorResult& aRv);
 
   uint64_t MozCurrentSampleOffset(ErrorResult& aRv);
 
 protected:
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  // Update the audio channel playing state
+  virtual void UpdateAudioChannelPlayingState() MOZ_OVERRIDE;
+
+  // Due to that audio data API doesn't indicate the timing of pause or end,
+  // the timer is used to defer the timing of pause/stop after writing data.
+  nsCOMPtr<nsITimer> mDeferStopPlayTimer;
+  // To indicate mDeferStopPlayTimer is on fire or not.
+  bool mTimerActivated;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLAudioElement_h
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -797,17 +797,17 @@ protected:
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
   // This method does the check for muting/unmuting the audio channel.
   nsresult UpdateChannelMuteState(bool aCanPlay);
 
   // Update the audio channel playing state
-  void UpdateAudioChannelPlayingState();
+  virtual void UpdateAudioChannelPlayingState();
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;
 
   // A reference to the VideoFrameContainer which contains the current frame
   // of video to display.
   nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
--- a/content/html/content/src/HTMLAudioElement.cpp
+++ b/content/html/content/src/HTMLAudioElement.cpp
@@ -30,27 +30,29 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Audio)
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ADDREF_INHERITED(HTMLAudioElement, HTMLMediaElement)
 NS_IMPL_RELEASE_INHERITED(HTMLAudioElement, HTMLMediaElement)
 
 NS_INTERFACE_TABLE_HEAD(HTMLAudioElement)
-NS_HTML_CONTENT_INTERFACE_TABLE2(HTMLAudioElement, nsIDOMHTMLMediaElement,
-                                 nsIDOMHTMLAudioElement)
+NS_HTML_CONTENT_INTERFACE_TABLE4(HTMLAudioElement, nsIDOMHTMLMediaElement,
+                                 nsIDOMHTMLAudioElement, nsITimerCallback,
+                                 nsIAudioChannelAgentCallback)
 NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(HTMLAudioElement,
                                              HTMLMediaElement)
 NS_HTML_CONTENT_INTERFACE_MAP_END
 
 NS_IMPL_ELEMENT_CLONE(HTMLAudioElement)
 
 
 HTMLAudioElement::HTMLAudioElement(already_AddRefed<nsINodeInfo> aNodeInfo)
-  : HTMLMediaElement(aNodeInfo)
+  : HTMLMediaElement(aNodeInfo),
+    mTimerActivated(false)
 {
   SetIsDOMBinding();
 }
 
 HTMLAudioElement::~HTMLAudioElement()
 {
 }
 
@@ -108,16 +110,24 @@ HTMLAudioElement::MozSetup(uint32_t aCha
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   if (mAudioStream) {
     mAudioStream->Shutdown();
   }
 
+#ifdef MOZ_B2G
+  if (mTimerActivated) {
+    mDeferStopPlayTimer->Cancel();
+    mTimerActivated = false;
+    UpdateAudioChannelPlayingState();
+  }
+#endif
+
   mAudioStream = AudioStream::AllocateStream();
   aRv = mAudioStream->Init(aChannels, aRate, mAudioChannelType);
   if (aRv.Failed()) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     return;
   }
 
@@ -141,16 +151,31 @@ HTMLAudioElement::MozWriteAudio(const fl
 
   // Make sure that we are going to write the correct amount of data based
   // on number of channels.
   if (aLength % mChannels != 0) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return 0;
   }
 
+#ifdef MOZ_B2G
+  if (!mDeferStopPlayTimer) {
+    mDeferStopPlayTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
+
+  if (mTimerActivated) {
+    mDeferStopPlayTimer->Cancel();
+  }
+  // The maximum buffer size of audio backend is 1 second, so waiting for 1
+  // second is sufficient enough.
+  mDeferStopPlayTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
+  mTimerActivated = true;
+  UpdateAudioChannelPlayingState();
+#endif
+
   // Don't write more than can be written without blocking.
   uint32_t writeLen = std::min(mAudioStream->Available(), aLength / mChannels);
 
   // Convert the samples back to integers as we are using fixed point audio in
   // the AudioStream.
   // This could be optimized to avoid allocation and memcpy when
   // AudioDataValue is 'float', but it's not worth it for this deprecated API.
   nsAutoArrayPtr<AudioDataValue> audioData(new AudioDataValue[writeLen * mChannels]);
@@ -210,10 +235,79 @@ nsresult HTMLAudioElement::SetAcceptHead
 }
 
 JSObject*
 HTMLAudioElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return HTMLAudioElementBinding::Wrap(aCx, aScope, this);
 }
 
+/* void canPlayChanged (in boolean canPlay); */
+NS_IMETHODIMP
+HTMLAudioElement::CanPlayChanged(bool canPlay)
+{
+  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+  // Only Audio_Data API will initialize the mAudioStream, so we call the parent
+  // one when this audio tag is not used by Audio_Data API.
+  if (!mAudioStream) {
+    return HTMLMediaElement::CanPlayChanged(canPlay);
+  }
+#ifdef MOZ_B2G
+  if (mChannelSuspended == !canPlay) {
+    return NS_OK;
+  }
+  mChannelSuspended = !canPlay;
+  SetMutedInternal(mChannelSuspended);
+#endif
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAudioElement::Notify(nsITimer* aTimer)
+{
+#ifdef MOZ_B2G
+  mTimerActivated = false;
+  UpdateAudioChannelPlayingState();
+#endif
+  return NS_OK;
+}
+
+void
+HTMLAudioElement::UpdateAudioChannelPlayingState()
+{
+  if (!mAudioStream) {
+    HTMLMediaElement::UpdateAudioChannelPlayingState();
+    return;
+  }
+  // The HTMLAudioElement is registered to the AudioChannelService only on B2G.
+#ifdef MOZ_B2G
+  if (mTimerActivated != mPlayingThroughTheAudioChannel) {
+    mPlayingThroughTheAudioChannel = mTimerActivated;
+
+    if (!mAudioChannelAgent) {
+      nsresult rv;
+      mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
+      if (!mAudioChannelAgent) {
+        return;
+      }
+      // Use a weak ref so the audio channel agent can't leak |this|.
+      mAudioChannelAgent->InitWithWeakCallback(mAudioChannelType, this);
+
+      nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
+      if (domDoc) {
+        bool hidden = false;
+        domDoc->GetHidden(&hidden);
+        mAudioChannelAgent->SetVisibilityState(!hidden);
+      }
+    }
+
+    if (mPlayingThroughTheAudioChannel) {
+      bool canPlay;
+      mAudioChannelAgent->StartPlaying(&canPlay);
+    } else {
+      mAudioChannelAgent->StopPlaying();
+      mAudioChannelAgent = nullptr;
+    }
+  }
+#endif
+}
 } // namespace dom
 } // namespace mozilla