From ccf0b4baaee7ecd56896162bc17d6f9eca9bc043 Mon Sep 17 00:00:00 2001 From: Jan-Ivar Bruaroey Date: Thu, 2 Jul 2015 18:01:52 -0400 Subject: [PATCH] Bug 1037389 - add support for deviceId in gUM constraints (merged 11 patches). r=smaug, r=jesup --- dom/media/MediaManager.cpp | 950 ++++++++++-------- dom/media/MediaManager.h | 36 +- dom/media/systemservices/MediaParent.cpp | 4 - .../test/test_streams_individual_pause.html | 9 +- dom/media/tests/mochitest/head.js | 9 +- .../mochitest/test_enumerateDevices.html | 41 +- dom/media/webrtc/MediaEngine.h | 20 +- .../webrtc/MediaEngineCameraVideoSource.cpp | 124 +-- .../webrtc/MediaEngineCameraVideoSource.h | 24 +- dom/media/webrtc/MediaEngineDefault.cpp | 34 +- dom/media/webrtc/MediaEngineDefault.h | 22 +- .../webrtc/MediaEngineGonkVideoSource.cpp | 5 +- dom/media/webrtc/MediaEngineGonkVideoSource.h | 3 +- .../webrtc/MediaEngineTabVideoSource.cpp | 3 +- dom/media/webrtc/MediaEngineTabVideoSource.h | 6 +- dom/media/webrtc/MediaEngineWebRTC.h | 14 +- dom/media/webrtc/MediaEngineWebRTCAudio.cpp | 24 +- dom/media/webrtc/MediaEngineWebRTCVideo.cpp | 5 +- dom/media/webrtc/MediaTrackConstraints.cpp | 126 +++ dom/media/webrtc/MediaTrackConstraints.h | 23 + dom/webidl/MediaStream.webidl | 14 +- dom/webidl/MediaTrackConstraintSet.webidl | 4 +- 22 files changed, 886 insertions(+), 614 deletions(-) diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 593d821eed0..b08aaa0cdbb 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -298,8 +298,10 @@ protected: */ NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice) -MediaDevice::MediaDevice(MediaEngineSource* aSource) - : mSource(aSource) { +MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo) + : mSource(aSource) + , mIsVideo(aIsVideo) +{ mSource->GetName(mName); nsCString id; mSource->GetUUID(id); @@ -307,7 +309,8 @@ MediaDevice::MediaDevice(MediaEngineSource* aSource) } VideoDevice::VideoDevice(MediaEngineVideoSource* aSource) - : MediaDevice(aSource) { + : MediaDevice(aSource, true) +{ mMediaSource = aSource->GetMediaSource(); } @@ -316,33 +319,78 @@ VideoDevice::VideoDevice(MediaEngineVideoSource* aSource) * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5 */ +bool +MediaDevice::StringsContain(const OwningStringOrStringSequence& aStrings, + nsString aN) +{ + return aStrings.IsString() ? aStrings.GetAsString() == aN + : aStrings.GetAsStringSequence().Contains(aN); +} + +/* static */ uint32_t +MediaDevice::FitnessDistance(nsString aN, + const ConstrainDOMStringParameters& aParams) +{ + if (aParams.mExact.WasPassed() && !StringsContain(aParams.mExact.Value(), aN)) { + return UINT32_MAX; + } + if (aParams.mIdeal.WasPassed() && !StringsContain(aParams.mIdeal.Value(), aN)) { + return 1; + } + return 0; +} + +// Binding code doesn't templatize well... + +/* static */ uint32_t +MediaDevice::FitnessDistance(nsString aN, + const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint) +{ + if (aConstraint.IsString()) { + ConstrainDOMStringParameters params; + params.mIdeal.Construct(); + params.mIdeal.Value().SetAsString() = aConstraint.GetAsString(); + return FitnessDistance(aN, params); + } else if (aConstraint.IsStringSequence()) { + ConstrainDOMStringParameters params; + params.mIdeal.Construct(); + params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence(); + return FitnessDistance(aN, params); + } else { + return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters()); + } +} + // Reminder: add handling for new constraints both here and in GetSources below! uint32_t -VideoDevice::GetBestFitnessDistance( +MediaDevice::GetBestFitnessDistance( const nsTArray& aConstraintSets) { - // Interrogate device-inherent properties first. - for (const auto& constraint : aConstraintSets) { - nsString s; - GetMediaSource(s); - if (s != constraint->mMediaSource) { - return UINT32_MAX; + nsString mediaSource; + GetMediaSource(mediaSource); + + // This code is reused for audio, where the mediaSource constraint does + // not currently have a function, but because it defaults to "camera" in + // webidl, we ignore it for audio here. + if (!mediaSource.EqualsASCII("microphone")) { + for (const auto& constraint : aConstraintSets) { + if (mediaSource != constraint->mMediaSource) { + return UINT32_MAX; + } } } // Forward request to underlying object to interrogate per-mode capabilities. - return GetSource()->GetBestFitnessDistance(aConstraintSets); + // Pass in device's origin-specific id for deviceId constraint comparison. + nsString id; + GetId(id); + return mSource->GetBestFitnessDistance(aConstraintSets, id); } AudioDevice::AudioDevice(MediaEngineAudioSource* aSource) - : MediaDevice(aSource) {} - -uint32_t -AudioDevice::GetBestFitnessDistance( - const nsTArray& aConstraintSets) + : MediaDevice(aSource, false) { - // TODO: Add audio-specific constraints - return 0; + mMediaSource = aSource->GetMediaSource(); } NS_IMETHODIMP @@ -411,6 +459,16 @@ AudioDevice::GetSource() return static_cast(&*mSource); } +nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints, + const MediaEnginePrefs &aPrefs) { + return GetSource()->Allocate(aConstraints, aPrefs, mID); +} + +nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints, + const MediaEnginePrefs &aPrefs) { + return GetSource()->Allocate(aConstraints, aPrefs, mID); +} + /** * A subclass that we only use to stash internal pointers to MediaStreamGraph objects * that need to be cleaned up. @@ -825,50 +883,48 @@ GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) { aUnion.GetAsMediaTrackConstraints() : empty; } -// Source getter that constrains list returned +// Source getter returning full list -template +template static void - GetSources(MediaEngine *engine, - ConstraintsType &aConstraints, - void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum, - nsTArray >*), - nsTArray>& aResult, - const char* media_device_name = nullptr) +GetSources(MediaEngine *engine, dom::MediaSourceEnum aSrcType, + void (MediaEngine::* aEnumerate)(dom::MediaSourceEnum, + nsTArray >*), + nsTArray>& aResult, + const char* media_device_name = nullptr) { - typedef nsTArray> SourceSet; + nsTArray> sources; - nsString deviceName; - // First collect sources - SourceSet candidateSet; - { - nsTArray > sources; - - MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings, - aConstraints.mMediaSource, - dom::MediaSourceEnum::Other); - (engine->*aEnumerate)(src, &sources); - /** - * We're allowing multiple tabs to access the same camera for parity - * with Chrome. See bug 811757 for some of the issues surrounding - * this decision. To disallow, we'd filter by IsAvailable() as we used - * to. - */ - for (uint32_t len = sources.Length(), i = 0; i < len; i++) { - sources[i]->GetName(deviceName); - if (media_device_name && strlen(media_device_name) > 0) { - if (deviceName.EqualsASCII(media_device_name)) { - candidateSet.AppendElement(new DeviceType(sources[i])); - break; - } - } else { - candidateSet.AppendElement(new DeviceType(sources[i])); + (engine->*aEnumerate)(aSrcType, &sources); + /** + * We're allowing multiple tabs to access the same camera for parity + * with Chrome. See bug 811757 for some of the issues surrounding + * this decision. To disallow, we'd filter by IsAvailable() as we used + * to. + */ + if (media_device_name && *media_device_name) { + for (auto& source : sources) { + nsString deviceName; + source->GetName(deviceName); + if (deviceName.EqualsASCII(media_device_name)) { + aResult.AppendElement(new DeviceType(source)); + break; } } + } else { + for (auto& source : sources) { + aResult.AppendElement(new DeviceType(source)); + } } +} - // Apply constraints to the list of sources. +// Apply constrains to a supplied list of sources (removes items from the list) +template +static void +ApplyConstraints(const MediaTrackConstraints &aConstraints, + nsTArray>& aSources) +{ auto& c = aConstraints; // First apply top-level constraints. @@ -881,20 +937,20 @@ static void std::multimap> ordered; - for (uint32_t i = 0; i < candidateSet.Length();) { - uint32_t distance = candidateSet[i]->GetBestFitnessDistance(aggregateConstraints); + for (uint32_t i = 0; i < aSources.Length();) { + uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints); if (distance == UINT32_MAX) { - candidateSet.RemoveElementAt(i); + aSources.RemoveElementAt(i); } else { ordered.insert(std::pair>(distance, - candidateSet[i])); + aSources[i])); ++i; } } // Order devices by shortest distance for (auto& ordinal : ordered) { - candidateSet.RemoveElement(ordinal.second); - candidateSet.AppendElement(ordinal.second); + aSources.RemoveElement(ordinal.second); + aSources.AppendElement(ordinal.second); } // Then apply advanced constraints. @@ -904,22 +960,66 @@ static void for (int i = 0; i < int(array.Length()); i++) { aggregateConstraints.AppendElement(&array[i]); - SourceSet rejects; - for (uint32_t j = 0; j < candidateSet.Length();) { - if (candidateSet[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) { - rejects.AppendElement(candidateSet[j]); - candidateSet.RemoveElementAt(j); + nsTArray> rejects; + for (uint32_t j = 0; j < aSources.Length();) { + if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) { + rejects.AppendElement(aSources[j]); + aSources.RemoveElementAt(j); } else { ++j; } } - if (!candidateSet.Length()) { - candidateSet.MoveElementsFrom(rejects); + if (!aSources.Length()) { + aSources.MoveElementsFrom(rejects); aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1); } } } - aResult.MoveElementsFrom(candidateSet); +} + +static bool +ApplyConstraints(MediaStreamConstraints &aConstraints, + nsTArray>& aSources) +{ + // Since the advanced part of the constraints algorithm needs to know when + // a candidate set is overconstrained (zero members), we must split up the + // list into videos and audios, and put it back together again at the end. + + bool overconstrained = false; + nsTArray> videos; + nsTArray> audios; + + for (auto& source : aSources) { + if (source->mIsVideo) { + nsRefPtr video = static_cast(source.get()); + videos.AppendElement(video); + } else { + nsRefPtr audio = static_cast(source.get()); + audios.AppendElement(audio); + } + } + aSources.Clear(); + MOZ_ASSERT(!aSources.Length()); + + if (IsOn(aConstraints.mVideo)) { + ApplyConstraints(GetInvariant(aConstraints.mVideo), videos); + if (!videos.Length()) { + overconstrained = true; + } + for (auto& video : videos) { + aSources.AppendElement(video); + } + } + if (IsOn(aConstraints.mAudio)) { + ApplyConstraints(GetInvariant(aConstraints.mAudio), audios); + if (!audios.Length()) { + overconstrained = true; + } + for (auto& audio : audios) { + aSources.AppendElement(audio); + } + } + return !overconstrained; } /** @@ -933,34 +1033,13 @@ static void class GetUserMediaTask : public Task { public: - GetUserMediaTask( - const MediaStreamConstraints& aConstraints, - already_AddRefed aOnSuccess, - already_AddRefed aOnFailure, - uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, - MediaEnginePrefs &aPrefs) - : mConstraints(aConstraints) - , mOnSuccess(aOnSuccess) - , mOnFailure(aOnFailure) - , mWindowID(aWindowID) - , mListener(aListener) - , mPrefs(aPrefs) - , mDeviceChosen(false) - , mBackend(nullptr) - , mManager(MediaManager::GetInstance()) - {} - - /** - * The caller can also choose to provide their own backend instead of - * using the one provided by MediaManager::GetBackend. - */ GetUserMediaTask( const MediaStreamConstraints& aConstraints, already_AddRefed aOnSuccess, already_AddRefed aOnFailure, uint64_t aWindowID, GetUserMediaCallbackMediaStreamListener *aListener, MediaEnginePrefs &aPrefs, - MediaEngine* aBackend) + MediaManager::SourceSet* aSourceSet) : mConstraints(aConstraints) , mOnSuccess(aOnSuccess) , mOnFailure(aOnFailure) @@ -968,7 +1047,7 @@ public: , mListener(aListener) , mPrefs(aPrefs) , mDeviceChosen(false) - , mBackend(aBackend) + , mSourceSet(aSourceSet) , mManager(MediaManager::GetInstance()) {} @@ -976,8 +1055,7 @@ public: } void - Fail(const nsAString& aName, - const nsAString& aMessage = EmptyString()) { + Fail(const nsAString& aName, const nsAString& aMessage = EmptyString()) { nsRefPtr error = new MediaMgrError(aName, aMessage); nsRefPtr> runnable = new ErrorCallbackRunnable(mOnSuccess, @@ -999,26 +1077,48 @@ public: MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mOnSuccess); MOZ_ASSERT(mOnFailure); + MOZ_ASSERT(mDeviceChosen); - MediaEngine* backend = mBackend; - // Was a backend provided? - if (!backend) { - backend = mManager->GetBackend(mWindowID); - } + // Allocate a video or audio device and return a MediaStream via + // a GetUserMediaStreamRunnable. - // Was a device provided? - if (!mDeviceChosen) { - nsresult rv = SelectDevice(backend); - if (rv != NS_OK) { + nsresult rv; + + if (mAudioDevice) { + rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs); + if (NS_FAILED(rv)) { + LOG(("Failed to allocate audiosource %d",rv)); + Fail(NS_LITERAL_STRING("SourceUnavailableError"), + NS_LITERAL_STRING("Failed to allocate audiosource")); return; } } + if (mVideoDevice) { + rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); + if (NS_FAILED(rv)) { + LOG(("Failed to allocate videosource %d\n",rv)); + if (mAudioDevice) { + mAudioDevice->GetSource()->Deallocate(); + } + Fail(NS_LITERAL_STRING("SourceUnavailableError"), + NS_LITERAL_STRING("Failed to allocate videosource")); + return; + } + } + PeerIdentity* peerIdentity = nullptr; + if (!mConstraints.mPeerIdentity.IsEmpty()) { + peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); + } - // There's a bug in the permission code that can leave us with mAudio but no audio device - ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ? - mAudioDevice->GetSource() : nullptr), - ((IsOn(mConstraints.mVideo) && mVideoDevice) ? - mVideoDevice->GetSource() : nullptr)); + NS_DispatchToMainThread(new GetUserMediaStreamRunnable( + mOnSuccess, mOnFailure, mWindowID, mListener, + (mAudioDevice? mAudioDevice->GetSource() : nullptr), + (mVideoDevice? mVideoDevice->GetSource() : nullptr), + peerIdentity + )); + + MOZ_ASSERT(!mOnSuccess); + MOZ_ASSERT(!mOnFailure); } nsresult @@ -1080,97 +1180,6 @@ public: return NS_OK; } - nsresult - SelectDevice(MediaEngine* backend) - { - MOZ_ASSERT(mOnSuccess); - MOZ_ASSERT(mOnFailure); - - if (!IsOn(mConstraints.mVideo) && !IsOn(mConstraints.mAudio)) { - Fail(NS_LITERAL_STRING("NotSupportedError")); - return NS_ERROR_FAILURE; - } - if (IsOn(mConstraints.mVideo)) { - nsTArray> sources; - GetSources(backend, GetInvariant(mConstraints.mVideo), - &MediaEngine::EnumerateVideoDevices, sources); - if (!sources.Length()) { - Fail(NS_LITERAL_STRING("NotFoundError")); - return NS_ERROR_FAILURE; - } - // Pick the first available device. - mVideoDevice = sources[0]; - LOG(("Selected video device")); - } - if (IsOn(mConstraints.mAudio)) { - nsTArray> sources; - GetSources(backend, GetInvariant(mConstraints.mAudio), - &MediaEngine::EnumerateAudioDevices, sources); - if (!sources.Length()) { - Fail(NS_LITERAL_STRING("NotFoundError")); - return NS_ERROR_FAILURE; - } - // Pick the first available device. - mAudioDevice = sources[0]; - LOG(("Selected audio device")); - } - - if (!mAudioDevice && !mVideoDevice) { - Fail(NS_LITERAL_STRING("NotFoundError")); - return NS_ERROR_FAILURE; - } - - return NS_OK; - } - - /** - * Allocates a video or audio device and returns a MediaStream via - * a GetUserMediaStreamRunnable. Runs off the main thread. - */ - void - ProcessGetUserMedia(MediaEngineAudioSource* aAudioSource, - MediaEngineVideoSource* aVideoSource) - { - MOZ_ASSERT(mOnSuccess); - MOZ_ASSERT(mOnFailure); - nsresult rv; - if (aAudioSource) { - rv = aAudioSource->Allocate(GetInvariant(mConstraints.mAudio), mPrefs); - if (NS_FAILED(rv)) { - LOG(("Failed to allocate audiosource %d",rv)); - Fail(NS_LITERAL_STRING("SourceUnavailableError"), - NS_LITERAL_STRING("Failed to allocate audiosource")); - return; - } - } - if (aVideoSource) { - rv = aVideoSource->Allocate(GetInvariant(mConstraints.mVideo), mPrefs); - if (NS_FAILED(rv)) { - LOG(("Failed to allocate videosource %d\n",rv)); - if (aAudioSource) { - aAudioSource->Deallocate(); - } - Fail(NS_LITERAL_STRING("SourceUnavailableError"), - NS_LITERAL_STRING("Failed to allocate videosource")); - return; - } - } - PeerIdentity* peerIdentity = nullptr; - if (!mConstraints.mPeerIdentity.IsEmpty()) { - peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); - } - - NS_DispatchToMainThread(new GetUserMediaStreamRunnable( - mOnSuccess, mOnFailure, mWindowID, mListener, aAudioSource, aVideoSource, - peerIdentity - )); - - MOZ_ASSERT(!mOnSuccess); - MOZ_ASSERT(!mOnFailure); - - return; - } - private: MediaStreamConstraints mConstraints; @@ -1183,8 +1192,9 @@ private: MediaEnginePrefs mPrefs; bool mDeviceChosen; - - RefPtr mBackend; +public: + nsAutoPtr mSourceSet; +private: nsRefPtr mManager; // get ref to this when creating the runnable }; @@ -1223,8 +1233,8 @@ static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices; */ already_AddRefed -MediaManager::EnumerateRawDevices(uint64_t aWindowId, - const MediaStreamConstraints& aConstraints) +MediaManager::EnumerateRawDevices(uint64_t aWindowId, MediaSourceEnum aVideoType, + bool aFake, bool aFakeTracks) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr p = new PledgeSourceSet(); @@ -1232,41 +1242,55 @@ MediaManager::EnumerateRawDevices(uint64_t aWindowId, // Check if the preference for using audio/video loopback devices is // enabled. This is currently used for automated media tests only. - auto audioLoopDev = Preferences::GetCString("media.audio_loopback_dev"); - auto videoLoopDev = Preferences::GetCString("media.video_loopback_dev"); - bool fake = Preferences::GetBool("media.navigator.streams.fake", false); + // + // If present (and we're doing non-exotic cameras and microphones) use them + // instead of our built-in fake devices, except if fake tracks are requested + // (a feature of the built-in ones only). - MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aConstraints, aWindowId, - audioLoopDev, videoLoopDev, - fake]() mutable { + nsAdoptingCString audioLoopDev, videoLoopDev; + if (!aFakeTracks) { + if (aVideoType == dom::MediaSourceEnum::Camera) { + audioLoopDev = Preferences::GetCString("media.audio_loopback_dev"); + videoLoopDev = Preferences::GetCString("media.video_loopback_dev"); + + if (aFake && !audioLoopDev.IsEmpty() && !videoLoopDev.IsEmpty()) { + aFake = false; + } + } else { + aFake = false; + } + } + + MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aWindowId, audioLoopDev, + videoLoopDev, aVideoType, + aFake, aFakeTracks]() mutable { nsRefPtr backend; - if (aConstraints.mFake || fake) { - backend = new MediaEngineDefault(aConstraints.mFakeTracks); + if (aFake) { + backend = new MediaEngineDefault(aFakeTracks); } else { nsRefPtr manager = MediaManager_GetInstance(); backend = manager->GetBackend(aWindowId); } ScopedDeletePtr result(new SourceSet); - if (IsOn(aConstraints.mVideo)) { - nsTArray> sources; - GetSources(backend, GetInvariant(aConstraints.mVideo), - &MediaEngine::EnumerateVideoDevices, sources, videoLoopDev); - for (auto& source : sources) { - result->AppendElement(source); - } + + nsTArray> videos; + GetSources(backend, aVideoType, &MediaEngine::EnumerateVideoDevices, videos, + videoLoopDev); + for (auto& source : videos) { + result->AppendElement(source); } - if (IsOn(aConstraints.mAudio)) { - nsTArray> sources; - GetSources(backend, GetInvariant(aConstraints.mAudio), - &MediaEngine::EnumerateAudioDevices, sources, audioLoopDev); - for (auto& source : sources) { - result->AppendElement(source); - } + + nsTArray> audios; + GetSources(backend, dom::MediaSourceEnum::Microphone, + &MediaEngine::EnumerateAudioDevices, audios, audioLoopDev); + for (auto& source : audios) { + result->AppendElement(source); } + SourceSet* handoff = result.forget(); NS_DispatchToMainThread(NewRunnableFrom([id, handoff]() mutable { - ScopedDeletePtr result(handoff); + ScopedDeletePtr result(handoff); // grab result nsRefPtr mgr = MediaManager_GetInstance(); if (!mgr) { return NS_OK; @@ -1345,6 +1369,7 @@ MediaManager::Get() { nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->AddObserver(sSingleton, "xpcom-will-shutdown", false); + obs->AddObserver(sSingleton, "getUserMedia:privileged:allow", false); obs->AddObserver(sSingleton, "getUserMedia:response:allow", false); obs->AddObserver(sSingleton, "getUserMedia:response:deny", false); obs->AddObserver(sSingleton, "getUserMedia:revoke", false); @@ -1358,6 +1383,10 @@ MediaManager::Get() { prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); } +#ifdef MOZ_B2G + // Init MediaPermissionManager before sending out any permission requests. + (void) MediaPermissionManager::GetInstance(); +#endif //MOZ_B2G } return sSingleton; } @@ -1460,6 +1489,57 @@ MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, return NS_OK; } +bool MediaManager::IsPrivileged() +{ + bool permission = nsContentUtils::IsCallerChrome(); + + // Developer preference for turning off permission check. + if (Preferences::GetBool("media.navigator.permission.disabled", false)) { + permission = true; + } + return permission; +} + +bool MediaManager::IsLoop(nsIURI* aDocURI) +{ + MOZ_ASSERT(aDocURI); + + nsCOMPtr loopURI; + nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation"); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + bool result = false; + rv = aDocURI->EqualsExceptRef(loopURI, &result); + NS_ENSURE_SUCCESS(rv, false); + return result; +} + +bool MediaManager::IsPrivateBrowsing(nsPIDOMWindow *window) +{ + nsCOMPtr doc = window->GetDoc(); + nsCOMPtr loadContext = doc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +nsresult MediaManager::GenerateUUID(nsAString& aResult) +{ + nsresult rv; + nsCOMPtr uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Generate a call ID. + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + char buffer[NSID_LENGTH]; + id.ToProvidedString(buffer); + aResult.Assign(NS_ConvertUTF8toUTF16(buffer)); + return NS_OK; +} + /** * The entry point for this file. A call from Navigator::mozGetUserMedia * will end up here. MediaManager is a singleton that is responsible @@ -1467,7 +1547,7 @@ MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow, */ nsresult MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, - const MediaStreamConstraints& aConstraints, + const MediaStreamConstraints& aConstraintsPassedIn, nsIDOMGetUserMediaSuccessCallback* aOnSuccess, nsIDOMGetUserMediaErrorCallback* aOnFailure) { @@ -1477,87 +1557,106 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, MOZ_ASSERT(aOnSuccess); nsCOMPtr onSuccess(aOnSuccess); nsCOMPtr onFailure(aOnFailure); - - bool privileged = nsContentUtils::IsCallerChrome(); - - MediaStreamConstraints c(aConstraints); // copy - - static bool created = false; - if (!created) { - // Force MediaManager to startup before we try to access it from other threads - // Hack: should init singleton earlier unless it's expensive (mem or CPU) - (void) MediaManager::Get(); -#ifdef MOZ_B2G - // Initialize MediaPermissionManager before send out any permission request. - (void) MediaPermissionManager::GetInstance(); -#endif //MOZ_B2G - } - uint64_t windowID = aWindow->WindowID(); - StreamListeners* listeners = AddWindowID(windowID); - // Create a disabled listener to act as a placeholder - GetUserMediaCallbackMediaStreamListener* listener = - new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID); + MediaStreamConstraints c(aConstraintsPassedIn); // use a modifiable copy - // No need for locking because we always do this in the main thread. - listeners->AppendElement(listener); + // Do all the validation we can while we're sync (to return an + // already-rejected promise on failure). - // Developer preference for turning off permission check. - if (Preferences::GetBool("media.navigator.permission.disabled", false)) { - privileged = true; + if (!IsOn(c.mVideo) && !IsOn(c.mAudio)) { + nsRefPtr error = + new MediaStreamError(aWindow, + NS_LITERAL_STRING("NotSupportedError"), + NS_LITERAL_STRING("audio and/or video is required")); + onFailure->OnError(error); + return NS_OK; } + if (sInShutdown) { + nsRefPtr error = + new MediaStreamError(aWindow, + NS_LITERAL_STRING("AbortError"), + NS_LITERAL_STRING("In shutdown")); + onFailure->OnError(error); + return NS_OK; + } + + // Determine permissions early (while we still have a stack). + + nsIURI* docURI = aWindow->GetDocumentURI(); + bool loop = IsLoop(docURI); + bool privileged = loop || IsPrivileged(); + bool isHTTPS = false; + if (docURI) { + docURI->SchemeIs("https", &isHTTPS); + } + if (!Preferences::GetBool("media.navigator.video.enabled", true)) { c.mVideo.SetAsBoolean() = false; } - bool fake = true; - if (!c.mFake && - !Preferences::GetBool("media.navigator.streams.fake", false)) { - fake = false; - } - // Pass callbacks and MediaStreamListener along to GetUserMediaTask. - nsAutoPtr task; - if (fake) { - // Fake stream from default backend. - task = new GetUserMediaTask(c, onSuccess.forget(), - onFailure.forget(), windowID, listener, mPrefs, new MediaEngineDefault(c.mFakeTracks)); - } else { - // Stream from default device from WebRTC backend. - task = new GetUserMediaTask(c, onSuccess.forget(), - onFailure.forget(), windowID, listener, mPrefs); - } - if (sInShutdown) { - return task->Denied(NS_LITERAL_STRING("In shutdown")); - } - - nsIURI* docURI = aWindow->GetDocumentURI(); - - bool isLoop = false; - nsCOMPtr loopURI; - nsresult rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation"); - NS_ENSURE_SUCCESS(rv, rv); - rv = docURI->EqualsExceptRef(loopURI, &isLoop); - NS_ENSURE_SUCCESS(rv, rv); - - if (isLoop) { - privileged = true; - } + MediaSourceEnum videoType = dom::MediaSourceEnum::Camera; if (c.mVideo.IsMediaTrackConstraints()) { auto& vc = c.mVideo.GetAsMediaTrackConstraints(); - MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings, - vc.mMediaSource, - dom::MediaSourceEnum::Other); - if (vc.mAdvanced.WasPassed()) { - if (src != dom::MediaSourceEnum::Camera) { - // iterate through advanced, forcing mediaSource to match "root" - const char *camera = EnumToASCII(dom::MediaSourceEnumValues::strings, - dom::MediaSourceEnum::Camera); - for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) { - if (cs.mMediaSource.EqualsASCII(camera)) { - cs.mMediaSource = vc.mMediaSource; - } + videoType = StringToEnum(dom::MediaSourceEnumValues::strings, + vc.mMediaSource, + videoType); + switch (videoType) { + case dom::MediaSourceEnum::Camera: + break; + + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Application: + case dom::MediaSourceEnum::Window: + // Deny screensharing request if support is disabled, or + // the requesting document is not from a host on the whitelist, or + // we're on Mac OSX 10.6 and WinXP until proved that they work + if (!Preferences::GetBool(((videoType == dom::MediaSourceEnum::Browser)? + "media.getusermedia.browser.enabled" : + "media.getusermedia.screensharing.enabled"), + false) || +#if defined(XP_MACOSX) || defined(XP_WIN) + ( + // Allow tab sharing for all platforms including XP and OSX 10.6 + (videoType != dom::MediaSourceEnum::Browser) && + !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms", + false) && +#if defined(XP_MACOSX) + !nsCocoaFeatures::OnLionOrLater() +#endif +#if defined (XP_WIN) + !IsVistaOrLater() +#endif + ) || +#endif + (!privileged && !HostHasPermission(*docURI))) { + nsRefPtr error = + new MediaStreamError(aWindow, + NS_LITERAL_STRING("PermissionDeniedError")); + onFailure->OnError(error); + return NS_OK; + } + break; + + case dom::MediaSourceEnum::Microphone: + case dom::MediaSourceEnum::Other: + default: { + nsRefPtr error = + new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError")); + onFailure->OnError(error); + return NS_OK; + } + } + + if (vc.mAdvanced.WasPassed() && videoType != dom::MediaSourceEnum::Camera) { + // iterate through advanced, forcing mediaSource to match "root" + const char *camera = EnumToASCII(dom::MediaSourceEnumValues::strings, + dom::MediaSourceEnum::Camera); + for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) { + if (cs.mMediaSource.EqualsASCII(camera)) { + cs.mMediaSource = vc.mMediaSource; } } } @@ -1575,75 +1674,27 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, } } - switch (src) { - case dom::MediaSourceEnum::Camera: - break; - - case dom::MediaSourceEnum::Browser: - case dom::MediaSourceEnum::Screen: - case dom::MediaSourceEnum::Application: - case dom::MediaSourceEnum::Window: - // Deny screensharing request if support is disabled, or - // the requesting document is not from a host on the whitelist, or - // we're on Mac OSX 10.6 and WinXP until proved that they work - if (!Preferences::GetBool(((src == dom::MediaSourceEnum::Browser)? - "media.getusermedia.browser.enabled" : - "media.getusermedia.screensharing.enabled"), - false) || -#if defined(XP_MACOSX) || defined(XP_WIN) - ( - // Allow tab sharing for all platforms including XP and OSX 10.6 - (src != dom::MediaSourceEnum::Browser) && - !Preferences::GetBool("media.getusermedia.screensharing.allow_on_old_platforms", - false) && -#if defined(XP_MACOSX) - !nsCocoaFeatures::OnLionOrLater() -#endif -#if defined (XP_WIN) - !IsVistaOrLater() -#endif - ) || -#endif - (!privileged && !HostHasPermission(*docURI))) { - return task->Denied(NS_LITERAL_STRING("PermissionDeniedError")); - } - break; - - case dom::MediaSourceEnum::Microphone: - case dom::MediaSourceEnum::Other: - default: - return task->Denied(NS_LITERAL_STRING("NotFoundError")); - } - // For all but tab sharing, Loop needs to prompt as we are using the // permission menu for selection of the device currently. For tab sharing, // Loop has implicit permissions within Firefox, as it is built-in, // and will manage the active tab and provide appropriate UI. - if (isLoop && - (src == dom::MediaSourceEnum::Window || - src == dom::MediaSourceEnum::Application || - src == dom::MediaSourceEnum::Screen)) { + if (loop && (videoType == dom::MediaSourceEnum::Window || + videoType == dom::MediaSourceEnum::Application || + videoType == dom::MediaSourceEnum::Screen)) { privileged = false; } } + StreamListeners* listeners = AddWindowID(windowID); -#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK) - if (mCameraManager == nullptr) { - mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); - } -#endif + // Create a disabled listener to act as a placeholder + nsRefPtr listener = + new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID); - // XXX No full support for picture in Desktop yet (needs proper UI) - if (privileged || - (fake && !Preferences::GetBool("media.navigator.permission.fake"))) { - MediaManager::PostTask(FROM_HERE, task.forget()); - } else { - bool isHTTPS = false; - if (docURI) { - docURI->SchemeIs("https", &isHTTPS); - } + // No need for locking because we always do this in the main thread. + listeners->AppendElement(listener); - // Check if this site has persistent permissions. + if (!privileged) { + // Check if this site has had persistent permissions denied. nsresult rv; nsCOMPtr permManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); @@ -1665,47 +1716,106 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow, if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) && (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) { - return task->Denied(NS_LITERAL_STRING("PermissionDeniedError")); + nsRefPtr error = + new MediaStreamError(aWindow, NS_LITERAL_STRING("PermissionDeniedError")); + onFailure->OnError(error); + RemoveFromWindowList(windowID, listener); + return NS_OK; + } + } + +#if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK) + if (mCameraManager == nullptr) { + mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); + } +#endif + + // Get list of all devices, with origin-specific device ids. + + MediaEnginePrefs prefs = mPrefs; + + nsString callID; + nsresult rv = GenerateUUID(callID); + NS_ENSURE_SUCCESS(rv, rv); + + bool fake = c.mFake.WasPassed()? c.mFake.Value() : + Preferences::GetBool("media.navigator.streams.fake"); + + bool fakeTracks = c.mFakeTracks.WasPassed()? c.mFakeTracks.Value() : false; + + bool askPermission = !privileged && + (!fake || Preferences::GetBool("media.navigator.permission.fake")); + + nsRefPtr p = EnumerateDevicesImpl(windowID, videoType, + fake, fakeTracks); + p->Then([this, onSuccess, onFailure, windowID, c, listener, + askPermission, prefs, isHTTPS, callID](SourceSet*& aDevices) mutable { + ScopedDeletePtr devices(aDevices); // grab result + + // Ensure this pointer is still valid, and window is still alive. + nsRefPtr mgr = MediaManager::GetInstance(); + nsRefPtr window = static_cast + (nsGlobalWindow::GetInnerWindowWithId(windowID)); + if (!mgr || !window) { + return; } - // Ask for user permission, and dispatch task (or not) when a response - // is received via an observer notification. Each call is paired with its - // task by a GUID. - nsCOMPtr uuidgen = - do_GetService("@mozilla.org/uuid-generator;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + // Apply any constraints. This modifies the list. - // Generate a call ID. - nsID id; - rv = uuidgen->GenerateUUIDInPlace(&id); - NS_ENSURE_SUCCESS(rv, rv); + if (!ApplyConstraints(c, *devices)) { + nsRefPtr error = + new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError")); + onFailure->OnError(error); + return; + } - char buffer[NSID_LENGTH]; - id.ToProvidedString(buffer); - NS_ConvertUTF8toUTF16 callID(buffer); + nsCOMPtr devicesCopy; // before we give up devices below + if (!askPermission) { + nsresult rv = NS_NewISupportsArray(getter_AddRefs(devicesCopy)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + for (auto& device : *devices) { + rv = devicesCopy->AppendElement(device); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + } - // Store the current unarmed task w/callbacks. + // Pass callbacks and MediaStreamListener along to GetUserMediaTask. + nsAutoPtr task (new GetUserMediaTask(c, onSuccess.forget(), + onFailure.forget(), + windowID, listener, + prefs, + devices.forget())); + // Store the task w/callbacks. mActiveCallbacks.Put(callID, task.forget()); // Add a WindowID cross-reference so OnNavigation can tear things down nsTArray* array; if (!mCallIds.Get(windowID, &array)) { array = new nsTArray(); - array->AppendElement(callID); mCallIds.Put(windowID, array); - } else { - array->AppendElement(callID); } + array->AppendElement(callID); + nsCOMPtr obs = services::GetObserverService(); - nsRefPtr req = new GetUserMediaRequest(aWindow, - callID, c, isHTTPS); - obs->NotifyObservers(req, "getUserMedia:request", nullptr); - } + if (!askPermission) { + obs->NotifyObservers(devicesCopy, "getUserMedia:privileged:allow", + callID.BeginReading()); + } else { + nsRefPtr req = + new GetUserMediaRequest(window, callID, c, isHTTPS); + obs->NotifyObservers(req, "getUserMedia:request", nullptr); + } #ifdef MOZ_WEBRTC - EnableWebRtcLog(); + EnableWebRtcLog(); #endif - + }, [onFailure](MediaStreamError& reason) mutable { + onFailure->OnError(&reason); + }); return NS_OK; } @@ -1793,10 +1903,12 @@ MediaManager::ToJSArray(SourceSet& aDevices) } already_AddRefed -MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, - const MediaStreamConstraints& aConstraints) +MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, MediaSourceEnum aVideoType, + bool aFake, bool aFakeTracks) { MOZ_ASSERT(NS_IsMainThread()); + nsPIDOMWindow *window = static_cast + (nsGlobalWindow::GetInnerWindowWithId(aWindowId)); // This function returns a pledge, a promise-like object with the future result nsRefPtr pledge = new PledgeSourceSet(); @@ -1807,28 +1919,23 @@ MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, // 2. Get the raw devices list // 3. Anonymize the raw list with the origin-key. + bool privateBrowsing = IsPrivateBrowsing(window); nsCString origin; - bool privateBrowsing; - { - nsPIDOMWindow *window = static_cast - (nsGlobalWindow::GetInnerWindowWithId(aWindowId)); - nsPrincipal::GetOriginForURI(window->GetDocumentURI(), origin); + nsPrincipal::GetOriginForURI(window->GetDocumentURI(), origin); - nsCOMPtr doc = window->GetDoc(); - nsCOMPtr loadContext = doc->GetLoadContext(); - privateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); - } // GetOriginKey is an async API that returns a pledge (a promise-like // pattern). We use .Then() to pass in a lambda to run back on this same // thread later once GetOriginKey resolves. Needed variables are "captured" // (passed by value) safely into the lambda. nsRefPtr> p = media::GetOriginKey(origin, privateBrowsing); - p->Then([id, aWindowId, aConstraints](const nsCString& aOriginKey) mutable { + p->Then([id, aWindowId, aVideoType, + aFake, aFakeTracks](const nsCString& aOriginKey) mutable { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr mgr = MediaManager_GetInstance(); - nsRefPtr p = mgr->EnumerateRawDevices(aWindowId, aConstraints); + nsRefPtr p = mgr->EnumerateRawDevices(aWindowId, aVideoType, + aFake, aFakeTracks); p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable { ScopedDeletePtr devices(aDevices); // secondary result @@ -1862,11 +1969,11 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow, AddWindowID(windowId); - MediaStreamConstraints c; - c.mVideo.SetAsBoolean() = true; - c.mAudio.SetAsBoolean() = true; + bool fake = Preferences::GetBool("media.navigator.streams.fake"); - nsRefPtr p = EnumerateDevicesImpl(windowId, c); + nsRefPtr p = EnumerateDevicesImpl(windowId, + dom::MediaSourceEnum::Camera, + fake); p->Then([onSuccess](SourceSet*& aDevices) mutable { ScopedDeletePtr devices(aDevices); // grab result nsCOMPtr array = MediaManager_ToJSArray(*devices); @@ -1895,27 +2002,22 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow, aWindowId = aWindow->WindowID(); } - nsRefPtr p = EnumerateDevicesImpl(aWindowId, aConstraints); - p->Then([aWindowId, onSuccess, onFailure](SourceSet*& aDevices) mutable { - ScopedDeletePtr devices(aDevices); // grab result + // Ignore passed-in constraints, instead locate + return already-constrained list. - if (devices->Length()) { - nsCOMPtr array = MediaManager_ToJSArray(*devices); + nsTArray* callIDs; + if (!mCallIds.Get(aWindowId, &callIDs)) { + return NS_ERROR_UNEXPECTED; + } + + for (auto& callID : *callIDs) { + GetUserMediaTask* task; + if (mActiveCallbacks.Get(callID, &task)) { + nsCOMPtr array = MediaManager_ToJSArray(*task->mSourceSet); onSuccess->OnSuccess(array); - } else { - nsRefPtr window = nsGlobalWindow::GetInnerWindowWithId(aWindowId); - if (!window) { - return NS_ERROR_UNEXPECTED; - } - nsRefPtr reason = - new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError")); - onFailure->OnError(reason); + return NS_OK; } - return NS_OK; - }, [onFailure](MediaStreamError& reason) mutable { - onFailure->OnError(&reason); - }); - return NS_OK; + } + return NS_ERROR_UNEXPECTED; } MediaEngine* @@ -1968,10 +2070,10 @@ MediaManager::OnNavigation(uint64_t aWindowID) // Invalidate this window. The runnables check this value before making // a call to content. - nsTArray* callIds; - if (mCallIds.Get(aWindowID, &callIds)) { - for (int i = 0, len = callIds->Length(); i < len; ++i) { - mActiveCallbacks.Remove((*callIds)[i]); + nsTArray* callIDs; + if (mCallIds.Get(aWindowID, &callIDs)) { + for (auto& callID : *callIDs) { + mActiveCallbacks.Remove(callID); } mCallIds.Remove(aWindowID); } @@ -2106,6 +2208,7 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, sInShutdown = true; obs->RemoveObserver(this, "xpcom-will-shutdown"); + obs->RemoveObserver(this, "getUserMedia:privileged:allow"); obs->RemoveObserver(this, "getUserMedia:response:allow"); obs->RemoveObserver(this, "getUserMedia:response:deny"); obs->RemoveObserver(this, "getUserMedia:revoke"); @@ -2187,7 +2290,8 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, }))); return NS_OK; - } else if (!strcmp(aTopic, "getUserMedia:response:allow")) { + } else if (!strcmp(aTopic, "getUserMedia:privileged:allow") || + !strcmp(aTopic, "getUserMedia:response:allow")) { nsString key(aData); nsAutoPtr task; mActiveCallbacks.RemoveAndForget(key, task); @@ -2202,12 +2306,12 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, MOZ_ASSERT(array); uint32_t len = 0; array->Count(&len); - MOZ_ASSERT(len); if (!len) { // neither audio nor video were selected task->Denied(NS_LITERAL_STRING("PermissionDeniedError")); return NS_OK; } + bool videoFound = false, audioFound = false; for (uint32_t i = 0; i < len; i++) { nsCOMPtr supports; array->GetElementAt(i,getter_AddRefs(supports)); @@ -2217,9 +2321,15 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, nsString type; device->GetType(type); if (type.EqualsLiteral("video")) { - task->SetVideoDevice(static_cast(device.get())); + if (!videoFound) { + task->SetVideoDevice(static_cast(device.get())); + videoFound = true; + } } else if (type.EqualsLiteral("audio")) { - task->SetAudioDevice(static_cast(device.get())); + if (!audioFound) { + task->SetAudioDevice(static_cast(device.get())); + audioFound = true; + } } else { NS_WARNING("Unknown device type in getUserMedia"); } diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index 360d9048269..54c2d6564b6 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -465,13 +465,25 @@ public: NS_DECL_NSIMEDIADEVICE void SetId(const nsAString& aID); + virtual uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets); protected: virtual ~MediaDevice() {} - explicit MediaDevice(MediaEngineSource* aSource); + explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo); + static uint32_t FitnessDistance(nsString aN, + const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint); +private: + static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings, + nsString aN); + static uint32_t FitnessDistance(nsString aN, + const dom::ConstrainDOMStringParameters& aParams); +protected: nsString mName; nsString mID; dom::MediaSourceEnum mMediaSource; nsRefPtr mSource; +public: + bool mIsVideo; }; class VideoDevice : public MediaDevice @@ -482,8 +494,8 @@ public: explicit VideoDevice(Source* aSource); NS_IMETHOD GetType(nsAString& aType); Source* GetSource(); - uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets); + nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, + const MediaEnginePrefs &aPrefs); }; class AudioDevice : public MediaDevice @@ -494,8 +506,8 @@ public: explicit AudioDevice(Source* aSource); NS_IMETHOD GetType(nsAString& aType); Source* GetSource(); - uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets); + nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, + const MediaEnginePrefs &aPrefs); }; // we could add MediaManager if needed @@ -571,21 +583,25 @@ public: MediaEnginePrefs mPrefs; -private: typedef nsTArray> SourceSet; +private: typedef media::Pledge PledgeSourceSet; + static bool IsPrivileged(); + static bool IsLoop(nsIURI* aDocURI); + static bool IsPrivateBrowsing(nsPIDOMWindow *window); + static nsresult GenerateUUID(nsAString& aResult); static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey); public: // TODO: make private once we upgrade to GCC 4.8+ on linux. static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey); static already_AddRefed ToJSArray(SourceSet& aDevices); private: already_AddRefed - EnumerateRawDevices(uint64_t aWindowId, - const dom::MediaStreamConstraints& aConstraints); + EnumerateRawDevices(uint64_t aWindowId, dom::MediaSourceEnum aSrcType, + bool aFake, bool aFakeTracks); already_AddRefed - EnumerateDevicesImpl(uint64_t aWindowId, - const dom::MediaStreamConstraints& aConstraints); + EnumerateDevicesImpl(uint64_t aWindowId, dom::MediaSourceEnum aSrcType, + bool aFake = false, bool aFakeTracks = false); StreamListeners* AddWindowID(uint64_t aWindowId); WindowTable *GetActiveWindows() { diff --git a/dom/media/systemservices/MediaParent.cpp b/dom/media/systemservices/MediaParent.cpp index d5ae2d1f0a5..0a2e8155065 100644 --- a/dom/media/systemservices/MediaParent.cpp +++ b/dom/media/systemservices/MediaParent.cpp @@ -503,16 +503,12 @@ Parent::Parent(bool aSameProcess) if (!gMediaParentLog) gMediaParentLog = PR_NewLogModule("MediaParent"); LOG(("media::Parent: %p", this)); - - MOZ_COUNT_CTOR(Parent); } template Parent::~Parent() { LOG(("~media::Parent: %p", this)); - - MOZ_COUNT_DTOR(Parent); } PMediaParent* diff --git a/dom/media/test/test_streams_individual_pause.html b/dom/media/test/test_streams_individual_pause.html index e3447c8b14f..e99e8ab39f3 100644 --- a/dom/media/test/test_streams_individual_pause.html +++ b/dom/media/test/test_streams_individual_pause.html @@ -23,7 +23,14 @@ var getVideoImagePixelData = function(v) { "a" + imgData[3]; } -navigator.mozGetUserMedia({video: true, fake: true}, function(stream) { +// This test does not appear to work with the "Dummy video source" provided on +// linux through the "media.video_loopback_dev" pref in the tree test environment. +// To force the built-in fake streams to always be used instead, we specify +// fakeTracks, a feature solely of the built-in fake streams (even though we +// don't use the extra tracks). + +navigator.mozGetUserMedia({video: true, fake: true, fakeTracks: true }, + function(stream) { var stream = stream; var video1 = document.getElementById('video1'); var video2 = document.getElementById('video2'); diff --git a/dom/media/tests/mochitest/head.js b/dom/media/tests/mochitest/head.js index e07fa708873..68158f28ee5 100644 --- a/dom/media/tests/mochitest/head.js +++ b/dom/media/tests/mochitest/head.js @@ -99,17 +99,13 @@ function createMediaElement(type, label) { /** - * Wrapper function for mozGetUserMedia to allow a singular area of control - * for determining whether we run this with fake devices or not. + * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether + * to use fake devices or not is now determined in pref further below instead. * * @param {Dictionary} constraints * The constraints for this mozGetUserMedia callback */ function getUserMedia(constraints) { - if (!("fake" in constraints) && FAKE_ENABLED) { - constraints["fake"] = FAKE_ENABLED; - } - info("Call getUserMedia for " + JSON.stringify(constraints)); return navigator.mediaDevices.getUserMedia(constraints); } @@ -138,6 +134,7 @@ function setupEnvironment() { ['media.peerconnection.ice.stun_client_maximum_transmits', 14], ['media.peerconnection.ice.trickle_grace_period', 30000], ['media.navigator.permission.disabled', true], + ['media.navigator.streams.fake', FAKE_ENABLED], ['media.getusermedia.screensharing.enabled', true], ['media.getusermedia.screensharing.allowed_domains', "mochi.test"] ] diff --git a/dom/media/tests/mochitest/test_enumerateDevices.html b/dom/media/tests/mochitest/test_enumerateDevices.html index c43b3e20fba..7db3c894c47 100644 --- a/dom/media/tests/mochitest/test_enumerateDevices.html +++ b/dom/media/tests/mochitest/test_enumerateDevices.html @@ -8,10 +8,24 @@ diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h index 2f5caf69457..94be3e0c09c 100644 --- a/dom/media/webrtc/MediaEngine.h +++ b/dom/media/webrtc/MediaEngine.h @@ -164,6 +164,15 @@ public: mHasFakeTracks = aHasFakeTracks; } + /* This call reserves but does not start the device. */ + virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) = 0; + + virtual uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) = 0; + protected: // Only class' own members can be initialized in constructor initializer list. explicit MediaEngineSource(MediaEngineState aState) @@ -224,13 +233,6 @@ class MediaEngineVideoSource : public MediaEngineSource public: virtual ~MediaEngineVideoSource() {} - /* This call reserves but does not start the device. */ - virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) = 0; - - virtual uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets) = 0; - protected: explicit MediaEngineVideoSource(MediaEngineState aState) : MediaEngineSource(aState) {} @@ -246,10 +248,6 @@ class MediaEngineAudioSource : public MediaEngineSource public: virtual ~MediaEngineAudioSource() {} - /* This call reserves but does not start the device. */ - virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) = 0; - protected: explicit MediaEngineAudioSource(MediaEngineState aState) : MediaEngineSource(aState) {} diff --git a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp index 196d166f607..e216633c6c8 100644 --- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp @@ -51,121 +51,17 @@ MediaEngineCameraVideoSource::GetCapability(size_t aIndex, aOut = mHardcodedCapabilities[aIndex]; } -// The full algorithm for all cameras. Sources that don't list capabilities -// need to fake it and hardcode some by populating mHardcodedCapabilities above. - -// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX - -template -/* static */ uint32_t -MediaEngineCameraVideoSource::FitnessDistance(ValueType aN, - const ConstrainRange& aRange) -{ - if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) || - (aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) || - (aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) { - return UINT32_MAX; - } - if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) { - return 0; - } - return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) / - std::max(std::abs(aN), std::abs(aRange.mIdeal.Value())))); -} - -// Binding code doesn't templatize well... - -/*static*/ uint32_t -MediaEngineCameraVideoSource::FitnessDistance(int32_t aN, - const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced) -{ - if (aConstraint.IsLong()) { - ConstrainLongRange range; - (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong()); - return FitnessDistance(aN, range); - } else { - return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange()); - } -} - -/*static*/ uint32_t -MediaEngineCameraVideoSource::FitnessDistance(double aN, - const OwningDoubleOrConstrainDoubleRange& aConstraint, - bool aAdvanced) -{ - if (aConstraint.IsDouble()) { - ConstrainDoubleRange range; - (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble()); - return FitnessDistance(aN, range); - } else { - return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange()); - } -} - -// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX - -/* static */ uint32_t -MediaEngineCameraVideoSource::FitnessDistance(nsString aN, - const ConstrainDOMStringParameters& aParams) -{ - struct Func - { - static bool - Contains(const OwningStringOrStringSequence& aStrings, nsString aN) - { - return aStrings.IsString() ? aStrings.GetAsString() == aN - : aStrings.GetAsStringSequence().Contains(aN); - } - }; - - if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) { - return UINT32_MAX; - } - if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) { - return 1000; - } - return 0; -} - -/* static */ uint32_t -MediaEngineCameraVideoSource::FitnessDistance(nsString aN, - const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint, - bool aAdvanced) -{ - if (aConstraint.IsString()) { - ConstrainDOMStringParameters params; - if (aAdvanced) { - params.mExact.Construct(); - params.mExact.Value().SetAsString() = aConstraint.GetAsString(); - } else { - params.mIdeal.Construct(); - params.mIdeal.Value().SetAsString() = aConstraint.GetAsString(); - } - return FitnessDistance(aN, params); - } else if (aConstraint.IsStringSequence()) { - ConstrainDOMStringParameters params; - if (aAdvanced) { - params.mExact.Construct(); - params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence(); - } else { - params.mIdeal.Construct(); - params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence(); - } - return FitnessDistance(aN, params); - } else { - return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters()); - } -} - uint32_t MediaEngineCameraVideoSource::GetFitnessDistance(const webrtc::CaptureCapability& aCandidate, const MediaTrackConstraintSet &aConstraints, - bool aAdvanced) + bool aAdvanced, + const nsString& aDeviceId) { // Treat width|height|frameRate == 0 on capability as "can do any". // This allows for orthogonal capabilities that are not in discrete steps. uint64_t distance = + uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced)) + uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode, aAdvanced)) + uint64_t(aCandidate.width? FitnessDistance(int32_t(aCandidate.width), aConstraints.mWidth, @@ -209,7 +105,8 @@ MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) { uint32_t MediaEngineCameraVideoSource::GetBestFitnessDistance( - const nsTArray& aConstraintSets) + const nsTArray& aConstraintSets, + const nsString& aDeviceId) { size_t num = NumCapabilities(); @@ -224,7 +121,7 @@ MediaEngineCameraVideoSource::GetBestFitnessDistance( auto& candidate = candidateSet[i]; webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); - uint32_t distance = GetFitnessDistance(cap, *cs, !first); + uint32_t distance = GetFitnessDistance(cap, *cs, !first, aDeviceId); if (distance == UINT32_MAX) { candidateSet.RemoveElementAt(i); } else { @@ -268,7 +165,8 @@ MediaEngineCameraVideoSource::LogConstraints( bool MediaEngineCameraVideoSource::ChooseCapability( const MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) { if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) { LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps", @@ -296,7 +194,7 @@ MediaEngineCameraVideoSource::ChooseCapability( auto& candidate = candidateSet[i]; webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); - candidate.mDistance = GetFitnessDistance(cap, aConstraints, false); + candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId); if (candidate.mDistance == UINT32_MAX) { candidateSet.RemoveElementAt(i); } else { @@ -313,7 +211,7 @@ MediaEngineCameraVideoSource::ChooseCapability( auto& candidate = candidateSet[i]; webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); - if (GetFitnessDistance(cap, cs, true) == UINT32_MAX) { + if (GetFitnessDistance(cap, cs, true, aDeviceId) == UINT32_MAX) { rejects.AppendElement(candidate); candidateSet.RemoveElementAt(i); } else { @@ -345,7 +243,7 @@ MediaEngineCameraVideoSource::ChooseCapability( for (auto& candidate : candidateSet) { webrtc::CaptureCapability cap; GetCapability(candidate.mIndex, cap); - candidate.mDistance = GetFitnessDistance(cap, prefs, false); + candidate.mDistance = GetFitnessDistance(cap, prefs, false, aDeviceId); } TrimLessFitCandidates(candidateSet); } diff --git a/dom/media/webrtc/MediaEngineCameraVideoSource.h b/dom/media/webrtc/MediaEngineCameraVideoSource.h index 14ba7be9f36..2bda3020921 100644 --- a/dom/media/webrtc/MediaEngineCameraVideoSource.h +++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h @@ -16,7 +16,8 @@ namespace mozilla { -class MediaEngineCameraVideoSource : public MediaEngineVideoSource +class MediaEngineCameraVideoSource : public MediaEngineVideoSource, + private MediaConstraintsHelper { public: explicit MediaEngineCameraVideoSource(int aIndex, @@ -59,7 +60,8 @@ public: } uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets) override; + const nsTArray& aConstraintSets, + const nsString& aDeviceId) override; virtual void Shutdown() override {}; @@ -80,28 +82,18 @@ protected: layers::Image* aImage, TrackID aID, StreamTime delta); - template - static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange); - static uint32_t FitnessDistance(int32_t aN, - const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced); - static uint32_t FitnessDistance(double aN, - const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced); - static uint32_t FitnessDistance(nsString aN, - const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint, - bool aAdvanced); - static uint32_t FitnessDistance(nsString aN, - const dom::ConstrainDOMStringParameters& aParams); - uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate, const dom::MediaTrackConstraintSet &aConstraints, - bool aAdvanced); + bool aAdvanced, + const nsString& aDeviceId); static void TrimLessFitCandidates(CapabilitySet& set); static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints, bool aAdvanced); virtual size_t NumCapabilities(); virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut); bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs); + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId); void SetName(nsString aName); void SetUUID(const char* aUUID); const nsCString& GetUUID(); // protected access diff --git a/dom/media/webrtc/MediaEngineDefault.cpp b/dom/media/webrtc/MediaEngineDefault.cpp index 81dbd066fc3..b604054adb5 100644 --- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -68,9 +68,24 @@ MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID) return; } +uint32_t +MediaEngineDefaultVideoSource::GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) +{ + uint32_t distance = 0; + + for (const MediaTrackConstraintSet* cs : aConstraintSets) { + distance = GetMinimumFitnessDistance(*cs, false, aDeviceId); + break; // distance is read from first entry only + } + return distance; +} + nsresult MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) { if (mState != kReleased) { return NS_ERROR_FAILURE; @@ -348,9 +363,24 @@ MediaEngineDefaultAudioSource::GetUUID(nsACString& aUUID) return; } +uint32_t +MediaEngineDefaultAudioSource::GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) +{ + uint32_t distance = 0; + + for (const MediaTrackConstraintSet* cs : aConstraintSets) { + distance = GetMinimumFitnessDistance(*cs, false, aDeviceId); + break; // distance is read from first entry only + } + return distance; +} + nsresult MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) { if (mState != kReleased) { return NS_ERROR_FAILURE; diff --git a/dom/media/webrtc/MediaEngineDefault.h b/dom/media/webrtc/MediaEngineDefault.h index 3e5a22edc0d..847693dcd25 100644 --- a/dom/media/webrtc/MediaEngineDefault.h +++ b/dom/media/webrtc/MediaEngineDefault.h @@ -31,7 +31,8 @@ class MediaEngineDefault; * The default implementation of the MediaEngine interface. */ class MediaEngineDefaultVideoSource : public nsITimerCallback, - public MediaEngineVideoSource + public MediaEngineVideoSource, + private MediaConstraintsHelper { public: MediaEngineDefaultVideoSource(); @@ -42,7 +43,8 @@ public: virtual void GetUUID(nsACString&) override; virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) override; + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override; @@ -56,10 +58,8 @@ public: TrackID aId, StreamTime aDesiredTime) override; virtual uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets) override - { - return true; - } + const nsTArray& aConstraintSets, + const nsString& aDeviceId) override; virtual bool IsFake() override { return true; @@ -101,7 +101,8 @@ protected: class SineWaveGenerator; class MediaEngineDefaultAudioSource : public nsITimerCallback, - public MediaEngineAudioSource + public MediaEngineAudioSource, + private MediaConstraintsHelper { public: MediaEngineDefaultAudioSource(); @@ -112,7 +113,8 @@ public: virtual void GetUUID(nsACString&) override; virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) override; + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override; @@ -139,6 +141,10 @@ public: return NS_ERROR_NOT_IMPLEMENTED; } + virtual uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) override; + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITIMERCALLBACK diff --git a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp index b1db023c870..90d19143edb 100644 --- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp @@ -147,13 +147,14 @@ MediaEngineGonkVideoSource::NumCapabilities() nsresult MediaEngineGonkVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints, - const MediaEnginePrefs& aPrefs) + const MediaEnginePrefs& aPrefs, + const nsString& aDeviceId) { LOG((__FUNCTION__)); ReentrantMonitorAutoEnter sync(mCallbackMonitor); if (mState == kReleased && mInitDone) { - ChooseCapability(aConstraints, aPrefs); + ChooseCapability(aConstraints, aPrefs, aDeviceId); NS_DispatchToMainThread(WrapRunnable(nsRefPtr(this), &MediaEngineGonkVideoSource::AllocImpl)); mCallbackMonitor.Wait(); diff --git a/dom/media/webrtc/MediaEngineGonkVideoSource.h b/dom/media/webrtc/MediaEngineGonkVideoSource.h index bfa12293f76..22a8ec838e0 100644 --- a/dom/media/webrtc/MediaEngineGonkVideoSource.h +++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h @@ -61,7 +61,8 @@ public: } virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) override; + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override; virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override; diff --git a/dom/media/webrtc/MediaEngineTabVideoSource.cpp b/dom/media/webrtc/MediaEngineTabVideoSource.cpp index cbc443c316a..b53c2991bc6 100644 --- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp @@ -120,7 +120,8 @@ MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid) nsresult MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints, - const MediaEnginePrefs& aPrefs) + const MediaEnginePrefs& aPrefs, + const nsString& aDeviceId) { // windowId and scrollWithPage are not proper constraints, so just read them. // They have no well-defined behavior in advanced, so ignore them there. diff --git a/dom/media/webrtc/MediaEngineTabVideoSource.h b/dom/media/webrtc/MediaEngineTabVideoSource.h index 1f2eca16d36..5e1b1e38abc 100644 --- a/dom/media/webrtc/MediaEngineTabVideoSource.h +++ b/dom/media/webrtc/MediaEngineTabVideoSource.h @@ -22,7 +22,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList virtual void GetName(nsAString_internal&) override; virtual void GetUUID(nsACString_internal&) override; virtual nsresult Allocate(const dom::MediaTrackConstraints &, - const mozilla::MediaEnginePrefs&) override; + const mozilla::MediaEnginePrefs&, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override; virtual void SetDirectListeners(bool aHasDirectListeners) override {}; @@ -34,7 +35,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList return dom::MediaSourceEnum::Browser; } virtual uint32_t GetBestFitnessDistance( - const nsTArray& aConstraintSets) override + const nsTArray& aConstraintSets, + const nsString& aDeviceId) override { return 0; } diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h index de3894bc8a2..105cca56004 100644 --- a/dom/media/webrtc/MediaEngineWebRTC.h +++ b/dom/media/webrtc/MediaEngineWebRTC.h @@ -86,11 +86,13 @@ public: , mMediaSource(aMediaSource) { MOZ_ASSERT(aVideoEnginePtr); + MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other); Init(); } virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, - const MediaEnginePrefs& aPrefs) override; + const MediaEnginePrefs& aPrefs, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(SourceMediaStream*, TrackID) override; virtual nsresult Stop(SourceMediaStream*, TrackID) override; @@ -132,7 +134,8 @@ private: }; class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource, - public webrtc::VoEMediaProcess + public webrtc::VoEMediaProcess, + private MediaConstraintsHelper { public: MediaEngineWebRTCAudioSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr, @@ -161,7 +164,8 @@ public: virtual void GetUUID(nsACString& aUUID) override; virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, - const MediaEnginePrefs& aPrefs) override; + const MediaEnginePrefs& aPrefs, + const nsString& aDeviceId) override; virtual nsresult Deallocate() override; virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override; virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override; @@ -189,6 +193,10 @@ public: return NS_ERROR_NOT_IMPLEMENTED; } + virtual uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) override; + // VoEMediaProcess. void Process(int channel, webrtc::ProcessingTypes type, int16_t audio10ms[], int length, diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp index c9c238c2899..c85c5710c19 100644 --- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -259,9 +259,31 @@ MediaEngineWebRTCAudioSource::Config(bool aEchoOn, uint32_t aEcho, return NS_OK; } +// GetBestFitnessDistance returns the best distance the capture device can offer +// as a whole, given an accumulated number of ConstraintSets. +// Ideal values are considered in the first ConstraintSet only. +// Plain values are treated as Ideal in the first ConstraintSet. +// Plain values are treated as Exact in subsequent ConstraintSets. +// Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets. +// A finite result may be used to calculate this device's ranking as a choice. + +uint32_t MediaEngineWebRTCAudioSource::GetBestFitnessDistance( + const nsTArray& aConstraintSets, + const nsString& aDeviceId) +{ + uint32_t distance = 0; + + for (const MediaTrackConstraintSet* cs : aConstraintSets) { + distance = GetMinimumFitnessDistance(*cs, false, aDeviceId); + break; // distance is read from first entry only + } + return distance; +} + nsresult MediaEngineWebRTCAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) { if (mState == kReleased) { if (mInitDone) { diff --git a/dom/media/webrtc/MediaEngineWebRTCVideo.cpp b/dom/media/webrtc/MediaEngineWebRTCVideo.cpp index 9f70402d3f4..32f1d4e5c6a 100644 --- a/dom/media/webrtc/MediaEngineWebRTCVideo.cpp +++ b/dom/media/webrtc/MediaEngineWebRTCVideo.cpp @@ -208,14 +208,15 @@ MediaEngineWebRTCVideoSource::GetCapability(size_t aIndex, nsresult MediaEngineWebRTCVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints, - const MediaEnginePrefs &aPrefs) + const MediaEnginePrefs &aPrefs, + const nsString& aDeviceId) { LOG((__FUNCTION__)); if (mState == kReleased && mInitDone) { // Note: if shared, we don't allow a later opener to affect the resolution. // (This may change depending on spec changes for Constraints/settings) - if (!ChooseCapability(aConstraints, aPrefs)) { + if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) { return NS_ERROR_UNEXPECTED; } if (mViECapture->AllocateCaptureDevice(GetUUID().get(), diff --git a/dom/media/webrtc/MediaTrackConstraints.cpp b/dom/media/webrtc/MediaTrackConstraints.cpp index f01c2920a7e..6989f38b309 100644 --- a/dom/media/webrtc/MediaTrackConstraints.cpp +++ b/dom/media/webrtc/MediaTrackConstraints.cpp @@ -81,4 +81,130 @@ FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOt } } +// MediaEngine helper +// +// The full algorithm for all devices. Sources that don't list capabilities +// need to fake it and hardcode some by populating mHardcodedCapabilities above. +// +// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX + +// First, all devices have a minimum distance based on their deviceId. +// If you have no other constraints, use this one. Reused by all device types. + +uint32_t +MediaConstraintsHelper::GetMinimumFitnessDistance( + const dom::MediaTrackConstraintSet &aConstraints, + bool aAdvanced, + const nsString& aDeviceId) +{ + uint64_t distance = + uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId, aAdvanced)); + + // This function is modeled on MediaEngineCameraVideoSource::GetFitnessDistance + // and will make more sense once more audio constraints are added. + + return uint32_t(std::min(distance, uint64_t(UINT32_MAX))); +} + +template +/* static */ uint32_t +MediaConstraintsHelper::FitnessDistance(ValueType aN, + const ConstrainRange& aRange) +{ + if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != aN) || + (aRange.mMin.WasPassed() && aRange.mMin.Value() > aN) || + (aRange.mMax.WasPassed() && aRange.mMax.Value() < aN)) { + return UINT32_MAX; + } + if (!aRange.mIdeal.WasPassed() || aN == aRange.mIdeal.Value()) { + return 0; + } + return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.Value()) * 1000) / + std::max(std::abs(aN), std::abs(aRange.mIdeal.Value())))); +} + +// Binding code doesn't templatize well... + +/*static*/ uint32_t +MediaConstraintsHelper::FitnessDistance(int32_t aN, + const OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced) +{ + if (aConstraint.IsLong()) { + ConstrainLongRange range; + (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsLong()); + return FitnessDistance(aN, range); + } else { + return FitnessDistance(aN, aConstraint.GetAsConstrainLongRange()); + } +} + +/*static*/ uint32_t +MediaConstraintsHelper::FitnessDistance(double aN, + const OwningDoubleOrConstrainDoubleRange& aConstraint, + bool aAdvanced) +{ + if (aConstraint.IsDouble()) { + ConstrainDoubleRange range; + (aAdvanced ? range.mExact : range.mIdeal).Construct(aConstraint.GetAsDouble()); + return FitnessDistance(aN, range); + } else { + return FitnessDistance(aN, aConstraint.GetAsConstrainDoubleRange()); + } +} + +// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX + +/* static */ uint32_t +MediaConstraintsHelper::FitnessDistance(nsString aN, + const ConstrainDOMStringParameters& aParams) +{ + struct Func + { + static bool + Contains(const OwningStringOrStringSequence& aStrings, nsString aN) + { + return aStrings.IsString() ? aStrings.GetAsString() == aN + : aStrings.GetAsStringSequence().Contains(aN); + } + }; + + if (aParams.mExact.WasPassed() && !Func::Contains(aParams.mExact.Value(), aN)) { + return UINT32_MAX; + } + if (aParams.mIdeal.WasPassed() && !Func::Contains(aParams.mIdeal.Value(), aN)) { + return 1000; + } + return 0; +} + +/* static */ uint32_t +MediaConstraintsHelper::FitnessDistance(nsString aN, + const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint, + bool aAdvanced) +{ + if (aConstraint.IsString()) { + ConstrainDOMStringParameters params; + if (aAdvanced) { + params.mExact.Construct(); + params.mExact.Value().SetAsString() = aConstraint.GetAsString(); + } else { + params.mIdeal.Construct(); + params.mIdeal.Value().SetAsString() = aConstraint.GetAsString(); + } + return FitnessDistance(aN, params); + } else if (aConstraint.IsStringSequence()) { + ConstrainDOMStringParameters params; + if (aAdvanced) { + params.mExact.Construct(); + params.mExact.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence(); + } else { + params.mIdeal.Construct(); + params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence(); + } + return FitnessDistance(aN, params); + } else { + return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters()); + } +} + } diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h index 1848d2e9e86..ce7cb485382 100644 --- a/dom/media/webrtc/MediaTrackConstraints.h +++ b/dom/media/webrtc/MediaTrackConstraints.h @@ -83,6 +83,29 @@ struct FlattenedConstraints : public NormalizedConstraintSet explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther); }; +// A helper class for MediaEngines + +class MediaConstraintsHelper +{ +protected: + template + static uint32_t FitnessDistance(ValueType aN, const ConstrainRange& aRange); + static uint32_t FitnessDistance(int32_t aN, + const dom::OwningLongOrConstrainLongRange& aConstraint, bool aAdvanced); + static uint32_t FitnessDistance(double aN, + const dom::OwningDoubleOrConstrainDoubleRange& aConstraint, bool aAdvanced); + static uint32_t FitnessDistance(nsString aN, + const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint, + bool aAdvanced); + static uint32_t FitnessDistance(nsString aN, + const dom::ConstrainDOMStringParameters& aParams); + + static uint32_t + GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints, + bool aAdvanced, + const nsString& aDeviceId); +}; + } #endif /* MEDIATRACKCONSTRAINTS_H_ */ diff --git a/dom/webidl/MediaStream.webidl b/dom/webidl/MediaStream.webidl index b19b08f6807..50ac83b989d 100644 --- a/dom/webidl/MediaStream.webidl +++ b/dom/webidl/MediaStream.webidl @@ -17,13 +17,13 @@ dictionary MediaStreamConstraints { (boolean or MediaTrackConstraints) audio = false; (boolean or MediaTrackConstraints) video = false; boolean picture = false; // Mozilla legacy - boolean fake = false; // For testing purpose. Generates frames of solid - // colors if video is enabled, and sound of 1Khz sine - // wave if audio is enabled. - boolean fakeTracks = false; // For testing purpose, works only if fake is - // enabled. Enable fakeTracks returns a stream - // with two extra empty video tracks and three - // extra empty audio tracks. + boolean fake; // For testing purpose. Generates frames of solid + // colors if video is enabled, and sound of 1Khz sine + // wave if audio is enabled. + boolean fakeTracks; // For testing purpose, works only if fake is + // enabled. Enable fakeTracks returns a stream + // with two extra empty video tracks and three + // extra empty audio tracks. DOMString? peerIdentity = null; }; diff --git a/dom/webidl/MediaTrackConstraintSet.webidl b/dom/webidl/MediaTrackConstraintSet.webidl index f7f5b4d931b..a44de9f8475 100644 --- a/dom/webidl/MediaTrackConstraintSet.webidl +++ b/dom/webidl/MediaTrackConstraintSet.webidl @@ -15,7 +15,8 @@ enum SupportedVideoConstraints { "frameRate", "mediaSource", "browserWindow", - "scrollWithPage" + "scrollWithPage", + "deviceId" }; enum SupportedAudioConstraints { @@ -30,6 +31,7 @@ dictionary MediaTrackConstraintSet { DOMString mediaSource = "camera"; long long browserWindow; boolean scrollWithPage; + ConstrainDOMString deviceId; }; typedef (long or ConstrainLongRange) ConstrainLong;