Bug 1301055 - part4 : special error dispatching stragedy for Fennec. draft
authorAlastor Wu <alwu@mozilla.com>
Thu, 24 Nov 2016 10:24:08 +0800
changeset 443246 90936a8f5186a16e3f4b6871bae8e8d8b0f65e7b
parent 443245 cd018dfc828616aa925c6a8a62507ae537d8c506
child 538000 4dab855ea5b18d50ed0c424113339cd36d59ed27
push id36935
push useralwu@mozilla.com
push dateThu, 24 Nov 2016 03:29:08 +0000
bugs1301055
milestone53.0a1
Bug 1301055 - part4 : special error dispatching stragedy for Fennec. On Fennec, we would use external app (Android view) to play the media whih is unsupported by gecko. We implement some special logic in error sink. (1) dispatch "OpenMediaWithExternalApp" event we use this event to start the android video player (2) doesn't dispatch "error" event some JS players won't let user to trigger play() again after receving the "error". So we don't dispatch that event if we want to play the unsupported media more than once. MozReview-Commit-ID: 7fZK5hdvaOG
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -710,47 +710,95 @@ public:
     }
 
     if (!IsValidErrorCode(aErrorCode)) {
       NS_ASSERTION(false, "Undefined MediaError codes!");
       return;
     }
 
     mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
-    mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
-    if (mOwner->ReadyState() == HAVE_NOTHING &&
-        aErrorCode == MEDIA_ERR_ABORTED) {
-      // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
-      // "If the media data fetching process is aborted by the user"
-      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
-      mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
-      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
-    } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
+    if (CanOwnerPlayUnsupportedTypeMedia()) {
       mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+      OpenUnsupportedMediaForOwner();
     } else {
-      mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+      mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
+      if (mOwner->ReadyState() == HAVE_NOTHING &&
+          aErrorCode == MEDIA_ERR_ABORTED) {
+        // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
+        // "If the media data fetching process is aborted by the user"
+        mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
+        mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
+        mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+      } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
+        mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+      } else {
+        mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+      }
     }
   }
 
   void ResetError()
   {
     mError = nullptr;
   }
 
+  void NotifyPlayStarted()
+  {
+    if (CanOwnerPlayUnsupportedTypeMedia()) {
+      OpenUnsupportedMediaForOwner();
+    }
+  }
+
   RefPtr<MediaError> mError;
 
 private:
   bool IsValidErrorCode(const uint16_t& aErrorCode) const
   {
     return (aErrorCode == MEDIA_ERR_DECODE  ||
             aErrorCode == MEDIA_ERR_NETWORK ||
             aErrorCode == MEDIA_ERR_ABORTED ||
             aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
   }
 
+  bool CanOwnerPlayUnsupportedTypeMedia() const
+  {
+#if defined(MOZ_WIDGET_ANDROID)
+    // On Fennec, we will user an external app to open unsupported media types.
+    if (!Preferences::GetBool("media.openUnsupportedTypeWithExternalApp")) {
+      return false;
+    }
+
+    if (!mError) {
+      return false;
+    }
+
+    uint16_t errorCode = mError->Code();
+    if (errorCode != MEDIA_ERR_SRC_NOT_SUPPORTED) {
+      return false;
+    }
+
+    // If media doesn't start playing, we don't need to open it.
+    if (mOwner->Paused()) {
+      return false;
+    }
+
+    return true;
+#endif
+    return false;
+  }
+
+  void OpenUnsupportedMediaForOwner() const
+  {
+    nsContentUtils::DispatchTrustedEvent(mOwner->OwnerDoc(),
+                                         static_cast<nsIContent*>(mOwner),
+                                         NS_LITERAL_STRING("OpenMediaWithExternalApp"),
+                                         true,
+                                         true);
+  }
+
   // Media elememt's life cycle would be longer than error sink, so we use the
   // raw pointer and this class would only be referenced by media element.
   HTMLMediaElement* mOwner;
 };
 
 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
 
@@ -3095,16 +3143,18 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     MaybeDoLoad();
     return;
   }
 
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
+
+  OpenUnsupportedMediaWithExternalAppIfNeeded();
 }
 
 nsresult
 HTMLMediaElement::PlayInternal()
 {
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
@@ -3141,20 +3191,16 @@ HTMLMediaElement::PlayInternal()
 
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   // and our preload status.
   AddRemoveSelfReference();
   UpdatePreloadAction();
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
-  // The check here is to handle the case that the media element starts playing
-  // after it loaded fail. eg. preload the data before playing.
-  OpenUnsupportedMediaWithExtenalAppIfNeeded();
-
   // We should check audio channel playing state before dispatching any events,
   // because we don't want to dispatch events for blocked media. For blocked
   // media, the event would be pending until media is resumed.
   // TODO: If the playback has ended, then the user agent must set
   // seek to the effective start.
   if (oldPaused) {
     DispatchAsyncEvent(NS_LITERAL_STRING("play"));
     switch (mReadyState) {
@@ -3187,17 +3233,23 @@ HTMLMediaElement::MaybeDoLoad()
 
 NS_IMETHODIMP HTMLMediaElement::Play()
 {
   if (!IsAllowedToPlay()) {
     MaybeDoLoad();
     return NS_OK;
   }
 
-  return PlayInternal();
+  nsresult rv = PlayInternal();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  OpenUnsupportedMediaWithExternalAppIfNeeded();
+  return NS_OK;
 }
 
 HTMLMediaElement::WakeLockBoolWrapper&
 HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
 {
   if (mValue == val) {
     return *this;
   }
@@ -5684,16 +5736,24 @@ MediaStream* HTMLMediaElement::GetSrcMed
 }
 
 MediaError*
 HTMLMediaElement::GetError() const
 {
   return mErrorSink->mError;
 }
 
+void
+HTMLMediaElement::OpenUnsupportedMediaWithExternalAppIfNeeded() const
+{
+  // Error sink would check the error state and other conditions to decide
+  // whether we can open unsupported type media with an external app.
+  mErrorSink->NotifyPlayStarted();
+}
+
 void HTMLMediaElement::GetCurrentSpec(nsCString& aString)
 {
   if (mLoadingSrc) {
     mLoadingSrc->GetSpec(aString);
   } else {
     aString.Truncate();
   }
 }
@@ -6594,51 +6654,16 @@ HTMLMediaElement::MaybeNotifyMediaResume
     wrapper->SetData(windowID);
     observerService->NotifyObservers(wrapper,
                                      "media-playback-resumed",
                                      u"active");
   }));
 }
 
 bool
-HTMLMediaElement::HaveFailedWithSourceNotSupportedError() const
-{
-  const MediaError* error = mErrorSink->mError;
-  if (!error) {
-    return false;
-  }
-
-  uint16_t errorCode = error->Code();
-  return (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE &&
-          errorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
-}
-
-void
-HTMLMediaElement::OpenUnsupportedMediaWithExtenalAppIfNeeded()
-{
-  if (!Preferences::GetBool("media.openUnsupportedTypeWithExternalApp")) {
-    return;
-  }
-
-  if (!HaveFailedWithSourceNotSupportedError()) {
-    return;
-  }
-
-  // If media doesn't start playing, we don't need to open it.
-  if (mPaused) {
-    return;
-  }
-
-  nsContentUtils::DispatchTrustedEvent(OwnerDoc(), static_cast<nsIContent*>(this),
-                                       NS_LITERAL_STRING("OpenMediaWithExternalApp"),
-                                       true,
-                                       true);
-}
-
-bool
 HTMLMediaElement::ShouldElementBePaused()
 {
   // Bfcached page or inactive document.
   if (!IsActive()) {
     return true;
   }
 
   return false;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1276,29 +1276,30 @@ protected:
   // If the network state is empty and then we would trigger DoLoad().
   void MaybeDoLoad();
 
   // True if the tab which media element belongs to has been to foreground at
   // least once or activated by manually clicking the unblocking tab icon.
   bool IsTabActivated() const;
 
   bool IsAudible() const;
-  bool HaveFailedWithSourceNotSupportedError() const;
-
-  void OpenUnsupportedMediaWithExtenalAppIfNeeded();
 
   // It's used for fennec only, send the notification when the user resumes the
   // media which was paused by media control.
   void MaybeNotifyMediaResumed(SuspendTypes aSuspend);
 
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
+  // Open unsupported types media with the external app when the media element
+  // triggers play() after loaded fail. eg. preload the data before start play.
+  void OpenUnsupportedMediaWithExternalAppIfNeeded() const;
+
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
   // Observers listening to changes to the mDecoder principal.
   // Used by streams captured from this element.
   nsTArray<DecoderPrincipalChangeObserver*> mDecoderPrincipalChangeObservers;