Bug 934425 - Implement setSinkId in HTMLMediaElement. r?bryce
MozReview-Commit-ID: DzEzhtzqMpG
--- 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