Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Default camera whose facingMode is unknown should be selected by getU…
…serMedia if there is no facingMode constraint

https://bugs.webkit.org/show_bug.cgi?id=255451
rdar://problem/108045715

Reviewed by Eric Carlson.

Remove the facingMode constraint that was added by default to favor user facing cameras.
Instead, we now rely on the order of camera devices, which should favor the front camera over the background cameras by default.
If another camera becomes the default camera, we will favor this camera.
Selection of the default camera in case fitness distance is the same is guaranteed by the fact we are using a stable sort.

We update RealtimeMediaSource::selectSettings as not setting anymore the facing mode might end up with empty candidates in RealtimeMediaSource::selectSettings
in the case there is no provided mandatory constraints but advanced constraints are added.
In this case, we should not have empty candidates but all possible candidates, hence why we remove the early return defined in the spec.

Update test infrastructure so that adding a mock camera that has an unknown facing mode will make this camera the default camera.
Covered by LayoutTests/fast/mediastream/default-camera-test.html.
Updating LayoutTests/fast/mediastream/getUserMedia-default.html to expect 60fps since ideal is set to 60 fps and this constraint no longer competes with facingMode constraint.

* LayoutTests/fast/mediastream/default-camera-test-expected.txt: Added.
* LayoutTests/fast/mediastream/default-camera-test.html: Added.
* LayoutTests/fast/mediastream/getUserMedia-default.html:
* Source/WebCore/platform/mediastream/MediaConstraints.cpp:
(WebCore::addDefaultVideoConstraints):
(WebCore::MediaConstraints::setDefaultVideoConstraints):
* Source/WebCore/platform/mediastream/RealtimeMediaSource.cpp:
(WebCore::RealtimeMediaSource::selectSettings):
* Source/WebCore/platform/mock/MockMediaDevice.h:
(WebCore::MockMediaDevice::cameraProperties const):
* Source/WebCore/platform/mock/MockRealtimeMediaSourceCenter.cpp:
(WebCore::createMockDevice):
(WebCore::MockRealtimeMediaSourceCenter::setDevices):
(WebCore::shouldBeDefaultDevice):
(WebCore::MockRealtimeMediaSourceCenter::addDevice):

Canonical link: https://commits.webkit.org/263022@main
  • Loading branch information
youennf committed Apr 17, 2023
1 parent 61e71fb commit 302d5fe
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 21 deletions.
4 changes: 4 additions & 0 deletions LayoutTests/fast/mediastream/default-camera-test-expected.txt
@@ -0,0 +1,4 @@


PASS Check default cameras in case of default device having an unknown facing mode

37 changes: 37 additions & 0 deletions LayoutTests/fast/mediastream/default-camera-test.html
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<video id="video"></video>
<script>
let setup = async (test) => {
if (!window.testRunner)
return Promise.reject("test requires internal API");

test.add_cleanup(() => {
testRunner.resetMockMediaDevices();
});
}

promise_test(async (test) => {
await setup(test);

// camera device should be the default device.
testRunner.addMockCameraDevice("myCamera", "my new camera", { facingMode: "unknown", fillColor: "green" });

let stream = await navigator.mediaDevices.getUserMedia({ video: true });
assert_equals(stream.getVideoTracks()[0].label, "my new camera");

stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
assert_equals(stream.getVideoTracks()[0].label, "Mock video device 1");

stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
assert_equals(stream.getVideoTracks()[0].label, "Mock video device 2");
}, "Check default cameras in case of default device having an unknown facing mode");
</script>
</body>
</html>
4 changes: 2 additions & 2 deletions LayoutTests/fast/mediastream/getUserMedia-default.html
Expand Up @@ -44,9 +44,9 @@
}, "Checking default video tracks settings except height");

promise_test((test) => {
return navigator.mediaDevices.getUserMedia({ audio: true, video: { frameRate: {ideal: 60 } } }).then((stream) => {
return navigator.mediaDevices.getUserMedia({ audio: true, video: { frameRate: { ideal: 60 } } }).then((stream) => {
let settings = stream.getVideoTracks()[0].getSettings();
assert_equals(settings.frameRate, 30, "frame rate");
assert_equals(settings.frameRate, 60, "frame rate");
assert_equals(settings.width, settings.height == 640 ? 480 : 640, "frame width");
assert_equals(settings.height, settings.width = 640 ? 480 : 640, "frame height");
});
Expand Down
15 changes: 3 additions & 12 deletions Source/WebCore/platform/mediastream/MediaConstraints.cpp
Expand Up @@ -364,7 +364,7 @@ bool MediaTrackConstraintSetMap::isEmpty() const
return !size();
}

static inline void addDefaultVideoConstraints(MediaTrackConstraintSetMap& videoConstraints, bool addFrameRateConstraint, bool addSizeConstraint, bool addFacingModeConstraint)
static inline void addDefaultVideoConstraints(MediaTrackConstraintSetMap& videoConstraints, bool addFrameRateConstraint, bool addSizeConstraint)
{
if (addFrameRateConstraint) {
DoubleConstraint frameRateConstraint({ }, MediaConstraintType::FrameRate);
Expand All @@ -380,11 +380,6 @@ static inline void addDefaultVideoConstraints(MediaTrackConstraintSetMap& videoC
heightConstraint.setIdeal(480);
videoConstraints.set(MediaConstraintType::Height, WTFMove(heightConstraint));
}
if (addFacingModeConstraint) {
StringConstraint facingModeConstraint({ }, MediaConstraintType::FacingMode);
facingModeConstraint.setIdeal("user"_s);
videoConstraints.set(MediaConstraintType::FacingMode, WTFMove(facingModeConstraint));
}
}

bool MediaConstraints::isConstraintSet(const Function<bool(const MediaTrackConstraintSetMap&)>& callback)
Expand All @@ -401,7 +396,7 @@ bool MediaConstraints::isConstraintSet(const Function<bool(const MediaTrackConst

void MediaConstraints::setDefaultVideoConstraints()
{
// 640x480, 30fps, front-facing camera
// 640x480, 30fps camera
bool needsFrameRateConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
return !!constraint.frameRate() || !!constraint.width() || !!constraint.height();
});
Expand All @@ -410,11 +405,7 @@ void MediaConstraints::setDefaultVideoConstraints()
return !!constraint.width() || !!constraint.height();
});

bool needsFacingModeConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
return !!constraint.facingMode() || !!constraint.deviceId();
});

addDefaultVideoConstraints(mandatoryConstraints, needsFrameRateConstraints, needsSizeConstraints, needsFacingModeConstraints);
addDefaultVideoConstraints(mandatoryConstraints, needsFrameRateConstraints, needsSizeConstraints);
}

void MediaConstraint::log() const
Expand Down
3 changes: 1 addition & 2 deletions Source/WebCore/platform/mediastream/RealtimeMediaSource.cpp
Expand Up @@ -782,8 +782,7 @@ bool RealtimeMediaSource::selectSettings(const MediaConstraints& constraints, Fl
minimumDistance = distance;

// 4. If candidates is empty, return undefined as the result of the SelectSettings() algorithm.
if (candidates.isEmpty())
return true;
// We skip this check since our implementation will compute empty candidates in case no mandatory constraints is given.

// 5. Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified.
// For each ConstraintSet:
Expand Down
5 changes: 5 additions & 0 deletions Source/WebCore/platform/mock/MockMediaDevice.h
Expand Up @@ -198,6 +198,11 @@ struct MockMediaDevice {
return isSpeaker() ? &std::get<MockSpeakerProperties>(properties) : nullptr;
}

const MockCameraProperties* cameraProperties() const
{
return isCamera() ? &std::get<MockCameraProperties>(properties) : nullptr;
}

template<class Encoder>
void encode(Encoder& encoder) const
{
Expand Down
23 changes: 18 additions & 5 deletions Source/WebCore/platform/mock/MockRealtimeMediaSourceCenter.cpp
Expand Up @@ -303,9 +303,12 @@ static CaptureDevice toCaptureDevice(const MockMediaDevice& device)
return captureDevice;
}

static void createMockDevice(const MockMediaDevice& device)
static void createMockDevice(const MockMediaDevice& device, bool isDefault)
{
deviceListForDevice(device).append(toCaptureDevice(device));
if (isDefault)
deviceListForDevice(device).insert(0, toCaptureDevice(device));
else
deviceListForDevice(device).append(toCaptureDevice(device));
}

void MockRealtimeMediaSourceCenter::resetDevices()
Expand Down Expand Up @@ -346,16 +349,26 @@ void MockRealtimeMediaSourceCenter::setDevices(Vector<MockMediaDevice>&& newMock

for (const auto& device : mockDevices) {
map.add(device.persistentId, device);
createMockDevice(device);
createMockDevice(device, false);
}
RealtimeMediaSourceCenter::singleton().captureDevicesChanged();
}

static bool shouldBeDefaultDevice(const MockMediaDevice& device)
{
auto* cameraProperties = device.cameraProperties();
return cameraProperties && cameraProperties->facingMode == VideoFacingMode::Unknown;
}

void MockRealtimeMediaSourceCenter::addDevice(const MockMediaDevice& device)
{
devices().append(device);
bool isDefault = shouldBeDefaultDevice(device);
if (isDefault)
devices().insert(0, device);
else
devices().append(device);
deviceMap().set(device.persistentId, device);
createMockDevice(device);
createMockDevice(device, isDefault);
RealtimeMediaSourceCenter::singleton().captureDevicesChanged();
}

Expand Down

0 comments on commit 302d5fe

Please sign in to comment.