Bug 934425 - Implement setSinkId in HTMLMediaElement. r?bryce draft
authorAlex Chronopoulos <achronop@gmail.com>
Thu, 09 Aug 2018 17:14:28 +0300
changeset 827856 855b9882bf68ffdbf01a9a49396b14d0dfd9c0a1
parent 827855 f79db264e82c5b7a931818296312515122a1873e
child 827857 da535568f290872945a3f39882cd5471394569a6
push id118601
push userachronop@gmail.com
push dateThu, 09 Aug 2018 14:17:09 +0000
reviewersbryce
bugs934425
milestone63.0a1
Bug 934425 - Implement setSinkId in HTMLMediaElement. r?bryce MozReview-Commit-ID: DzEzhtzqMpG
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -26,16 +26,17 @@
 #include "HLSDecoder.h"
 #endif
 #include "HTMLMediaElement.h"
 #include "ImageContainer.h"
 #include "Layers.h"
 #include "MP4Decoder.h"
 #include "MediaContainerType.h"
 #include "MediaError.h"
+#include "MediaManager.h"
 #include "MediaMetadataManager.h"
 #include "MediaResource.h"
 #include "MediaSourceDecoder.h"
 #include "MediaStreamError.h"
 #include "MediaStreamGraph.h"
 #include "MediaStreamListener.h"
 #include "MediaTrackList.h"
 #include "SVGObserverUtils.h"
@@ -5067,16 +5068,20 @@ HTMLMediaElement::FinishDecoderSetup(Med
 
   if (!mPaused) {
     SetPlayedOrSeeked(true);
     if (!mPausedForInactiveDocumentOrChannel) {
       mDecoder->Play();
     }
   }
 
+  if (mSinkDeviceInfo) {
+    mDecoder->SetSinkDevice(mSinkDeviceInfo);
+  }
+
   return NS_OK;
 }
 
 class HTMLMediaElement::StreamListener : public MediaStreamListener
 {
 public:
   StreamListener(HTMLMediaElement* aElement, const char* aName)
     : mElement(aElement)
@@ -5256,16 +5261,19 @@ HTMLMediaElement::UpdateSrcMediaStreamPl
     mSrcStreamPausedCurrentTime = -1;
 
     mMediaStreamListener =
       new StreamListener(this, "HTMLMediaElement::mMediaStreamListener");
     stream->AddListener(mMediaStreamListener);
 
     stream->AddAudioOutput(this);
     SetVolumeInternal();
+    if (mSinkDeviceInfo) {
+      NS_WARNING("Set Sink Id on a DOMMediaStream is not supported yet and will be ignored");
+    }
 
     VideoFrameContainer* container = GetVideoFrameContainer();
     if (mSelectedVideoStreamTrack && container) {
       mSelectedVideoStreamTrack->AddVideoOutput(container);
     }
 
     SetCapturedOutputStreamsEnabled(true); // Unmute
     // If the input is a media stream, we don't check its data and always regard
@@ -8131,13 +8139,81 @@ HTMLMediaElement::ReportCanPlayTelemetry
             Telemetry::Accumulate(
               Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER, h264);
             thread->AsyncShutdown();
           }));
       }),
     NS_DISPATCH_NORMAL);
 }
 
+already_AddRefed<Promise>
+HTMLMediaElement::SetSinkId(const nsAString& aSinkId, ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindowInner> win = OwnerDoc()->GetInnerWindow();
+  if (!win) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+  RefPtr<Promise> promise = DetailedPromise::Create(
+    win->AsGlobal(), aRv, NS_LITERAL_CSTRING("HTMLMediaElement.setSinkId"));
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (mSinkId.Equals(aSinkId)) {
+    promise->MaybeResolveWithUndefined();
+    return promise.forget();
+  }
+
+  nsString sinkId(aSinkId);
+  RefPtr<HTMLMediaElement> self = this;
+  mSinkIdPromise = MediaManager::Get()->GetSinkDevice(win, sinkId);
+  mSinkIdPromise
+    ->Then(mAbstractMainThread, __func__,
+           [promise, sinkId, win, self] (RefPtr<AudioDeviceInfo> info) {
+             if (!info) {
+               // Sink was not found
+               promise->MaybeReject(new MediaStreamError(win,
+                 MediaStreamError::Name::NotFoundError));
+               return;
+             }
+             // Sink found switch output device.
+             // TODO: Check if the application is not authorized to play audio
+             // through this device. If not reject promise with a SecurityError
+             if (self->mDecoder) {
+               self->mSwitchSinkPromise = self->mDecoder->SetSinkDevice(info);
+               self->mSwitchSinkPromise
+                 ->Then(self->mAbstractMainThread, __func__,
+                        [promise, sinkId, info, self] (bool playing) {
+                          self->mSinkId = sinkId;
+                          self->mSinkDeviceInfo = info;
+                          promise->MaybeResolveWithUndefined();
+                        },
+                        [promise, win] (nsresult res) {
+                          promise->MaybeReject(new MediaStreamError(win,
+                            MediaStreamError::Name::AbortError));
+                        });
+             } else if (self->GetSrcMediaStream()) {
+               // Set Sink Id through MSG is not supported yet.
+               promise->MaybeReject(new MediaStreamError(win,
+                 MediaStreamError::Name::AbortError));
+             } else {
+               // No media attached to the element save it for later.
+               self->mSinkId = sinkId;
+               self->mSinkDeviceInfo = info;
+               promise->MaybeResolveWithUndefined();
+             }
+           },
+           // Promise is rejected, not common.
+           [promise, win] (nsresult res){
+              promise->MaybeReject(new MediaStreamError(win,
+                MediaStreamError::Name::NotFoundError));
+           });
+
+  aRv = NS_OK;
+  return promise.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
 
 #undef LOG
 #undef LOG_EVENT
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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_HTMLMediaElement_h
 #define mozilla_dom_HTMLMediaElement_h
 
+#include "AudioDeviceInfo.h"
 #include "nsAutoPtr.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaEventSource.h"
 #include "SeekTarget.h"
 #include "MediaDecoderOwner.h"
 #include "MediaPromiseDefs.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
@@ -822,16 +823,19 @@ public:
   void AsyncResolveSeekDOMPromiseIfExists() override;
   void AsyncRejectSeekDOMPromiseIfExists() override;
 
   nsISerialEventTarget* MainThreadEventTarget()
   {
     return mMainThreadEventTarget;
   }
 
+  already_AddRefed<Promise> SetSinkId(const nsAString& aSinkId, ErrorResult& aRv);
+  void GetSinkId(nsString& retval) {retval = mSinkId;}
+
 protected:
   virtual ~HTMLMediaElement();
 
   class AudioChannelAgentCallback;
   class ChannelLoader;
   class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
@@ -1853,16 +1857,32 @@ private:
 
   // A pending seek promise which is created at Seek() method call and is
   // resolved/rejected at AsyncResolveSeekDOMPromiseIfExists()/
   // AsyncRejectSeekDOMPromiseIfExists() methods.
   RefPtr<dom::Promise> mSeekDOMPromise;
 
   // For debugging bug 1407148.
   void AssertReadyStateIsNothing();
+
+  // Contains the unique id of the sink device. It can be invalid if the device
+  // has been unplugged. It can be empty. It follows the spec attribute:
+  // https://w3c.github.io/mediacapture-output/#htmlmediaelement-extensions
+  nsString mSinkId;
+
+  // Information about mSink when the mSinkId is set.
+  RefPtr<AudioDeviceInfo> mSinkDeviceInfo;
+
+  // Check asynchronously if the current sink id exists in the active sinks.
+  // It is resolved in MediaManager.
+  RefPtr<MozPromise<RefPtr<AudioDeviceInfo>, nsresult, true>> mSinkIdPromise;
+
+  // Switch sink device asynchronously if the sink id is set in the
+  // middle of playback. It is resolved in MediaDecoderStateMachine.
+  RefPtr<GenericPromise> mSwitchSinkPromise;
 };
 
 // Check if the context is chrome or has the debugger or tabs permission
 bool
 HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj);
 
 } // namespace dom
 } // namespace mozilla