blob: 7c40e23db29c407ec66f8bb39db843695d2a5301 [file] [log] [blame]
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of Google Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "MediaConstraints.h"
#if ENABLE(MEDIA_STREAM)
#include "RealtimeMediaSourceCenter.h"
#include "RealtimeMediaSourceSupportedConstraints.h"
#include <wtf/StdLibExtras.h>
namespace WebCore {
const String& StringConstraint::find(const WTF::Function<bool(const String&)>& filter) const
{
for (auto& constraint : m_exact) {
if (filter(constraint))
return constraint;
}
for (auto& constraint : m_ideal) {
if (filter(constraint))
return constraint;
}
return emptyString();
}
double StringConstraint::fitnessDistance(const String& value) const
{
// https://w3c.github.io/mediacapture-main/#dfn-applyconstraints
// 1. If the constraint is not supported by the browser, the fitness distance is 0.
if (isEmpty())
return 0;
// 2. If the constraint is required ('min', 'max', or 'exact'), and the settings
// dictionary's value for the constraint does not satisfy the constraint, the
// fitness distance is positive infinity.
if (!m_exact.isEmpty() && m_exact.find(value) == notFound)
return std::numeric_limits<double>::infinity();
// 3. If no ideal value is specified, the fitness distance is 0.
if (m_ideal.isEmpty())
return 0;
// 5. For all string and enum non-required constraints (deviceId, groupId, facingMode,
// echoCancellation), the fitness distance is the result of the formula
// (actual == ideal) ? 0 : 1
return m_ideal.find(value) != notFound ? 0 : 1;
}
double StringConstraint::fitnessDistance(const Vector<String>& values) const
{
if (isEmpty())
return 0;
double minimumDistance = std::numeric_limits<double>::infinity();
for (auto& value : values)
minimumDistance = std::min(minimumDistance, fitnessDistance(value));
return minimumDistance;
}
void StringConstraint::merge(const MediaConstraint& other)
{
ASSERT(other.isString());
const StringConstraint& typedOther = downcast<StringConstraint>(other);
if (typedOther.isEmpty())
return;
Vector<String> values;
if (typedOther.getExact(values)) {
if (m_exact.isEmpty())
m_exact = values;
else {
for (auto& value : values) {
if (m_exact.find(value) == notFound)
m_exact.append(value);
}
}
}
if (typedOther.getIdeal(values)) {
if (m_ideal.isEmpty())
m_ideal = values;
else {
for (auto& value : values) {
if (m_ideal.find(value) == notFound)
m_ideal.append(value);
}
}
}
}
void FlattenedConstraint::set(const MediaConstraint& constraint)
{
if (find(constraint.constraintType()))
return;
append(constraint);
}
void FlattenedConstraint::merge(const MediaConstraint& constraint)
{
for (auto& variant : *this) {
if (variant.constraintType() != constraint.constraintType())
continue;
switch (variant.dataType()) {
case MediaConstraint::DataType::Integer:
ASSERT(constraint.isInt());
downcast<const IntConstraint>(variant).merge(downcast<const IntConstraint>(constraint));
return;
case MediaConstraint::DataType::Double:
ASSERT(constraint.isDouble());
downcast<const DoubleConstraint>(variant).merge(downcast<const DoubleConstraint>(constraint));
return;
case MediaConstraint::DataType::Boolean:
ASSERT(constraint.isBoolean());
downcast<const BooleanConstraint>(variant).merge(downcast<const BooleanConstraint>(constraint));
return;
case MediaConstraint::DataType::String:
ASSERT(constraint.isString());
downcast<const StringConstraint>(variant).merge(downcast<const StringConstraint>(constraint));
return;
case MediaConstraint::DataType::None:
ASSERT_NOT_REACHED();
return;
}
}
append(constraint);
}
void FlattenedConstraint::append(const MediaConstraint& constraint)
{
#ifndef NDEBUG
++m_generation;
#endif
m_variants.append(ConstraintHolder::create(constraint));
}
const MediaConstraint* FlattenedConstraint::find(MediaConstraintType type) const
{
for (auto& variant : m_variants) {
if (variant.constraintType() == type)
return &variant.constraint();
}
return nullptr;
}
void MediaTrackConstraintSetMap::forEach(WTF::Function<void(const MediaConstraint&)>&& callback) const
{
filter([callback = WTFMove(callback)] (const MediaConstraint& constraint) mutable {
callback(constraint);
return false;
});
}
void MediaTrackConstraintSetMap::filter(const WTF::Function<bool(const MediaConstraint&)>& callback) const
{
if (m_width && !m_width->isEmpty() && callback(*m_width))
return;
if (m_height && !m_height->isEmpty() && callback(*m_height))
return;
if (m_sampleRate && !m_sampleRate->isEmpty() && callback(*m_sampleRate))
return;
if (m_sampleSize && !m_sampleSize->isEmpty() && callback(*m_sampleSize))
return;
if (m_aspectRatio && !m_aspectRatio->isEmpty() && callback(*m_aspectRatio))
return;
if (m_frameRate && !m_frameRate->isEmpty() && callback(*m_frameRate))
return;
if (m_volume && !m_volume->isEmpty() && callback(*m_volume))
return;
if (m_echoCancellation && !m_echoCancellation->isEmpty() && callback(*m_echoCancellation))
return;
if (m_facingMode && !m_facingMode->isEmpty() && callback(*m_facingMode))
return;
if (m_deviceId && !m_deviceId->isEmpty() && callback(*m_deviceId))
return;
if (m_groupId && !m_groupId->isEmpty() && callback(*m_groupId))
return;
}
void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<IntConstraint>&& constraint)
{
switch (constraintType) {
case MediaConstraintType::Width:
m_width = WTFMove(constraint);
break;
case MediaConstraintType::Height:
m_height = WTFMove(constraint);
break;
case MediaConstraintType::SampleRate:
m_sampleRate = WTFMove(constraint);
break;
case MediaConstraintType::SampleSize:
m_sampleSize = WTFMove(constraint);
break;
case MediaConstraintType::AspectRatio:
case MediaConstraintType::FrameRate:
case MediaConstraintType::Volume:
case MediaConstraintType::EchoCancellation:
case MediaConstraintType::FacingMode:
case MediaConstraintType::DeviceId:
case MediaConstraintType::GroupId:
case MediaConstraintType::DisplaySurface:
case MediaConstraintType::LogicalSurface:
case MediaConstraintType::Unknown:
ASSERT_NOT_REACHED();
break;
}
}
void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<DoubleConstraint>&& constraint)
{
switch (constraintType) {
case MediaConstraintType::AspectRatio:
m_aspectRatio = WTFMove(constraint);
break;
case MediaConstraintType::FrameRate:
m_frameRate = WTFMove(constraint);
break;
case MediaConstraintType::Volume:
m_volume = WTFMove(constraint);
break;
case MediaConstraintType::Width:
case MediaConstraintType::Height:
case MediaConstraintType::SampleRate:
case MediaConstraintType::SampleSize:
case MediaConstraintType::EchoCancellation:
case MediaConstraintType::FacingMode:
case MediaConstraintType::DeviceId:
case MediaConstraintType::GroupId:
case MediaConstraintType::DisplaySurface:
case MediaConstraintType::LogicalSurface:
case MediaConstraintType::Unknown:
ASSERT_NOT_REACHED();
break;
}
}
void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<BooleanConstraint>&& constraint)
{
switch (constraintType) {
case MediaConstraintType::EchoCancellation:
m_echoCancellation = WTFMove(constraint);
break;
case MediaConstraintType::DisplaySurface:
m_displaySurface = WTFMove(constraint);
break;
case MediaConstraintType::LogicalSurface:
m_logicalSurface = WTFMove(constraint);
break;
case MediaConstraintType::Width:
case MediaConstraintType::Height:
case MediaConstraintType::SampleRate:
case MediaConstraintType::SampleSize:
case MediaConstraintType::AspectRatio:
case MediaConstraintType::FrameRate:
case MediaConstraintType::Volume:
case MediaConstraintType::FacingMode:
case MediaConstraintType::DeviceId:
case MediaConstraintType::GroupId:
case MediaConstraintType::Unknown:
ASSERT_NOT_REACHED();
break;
}
}
void MediaTrackConstraintSetMap::set(MediaConstraintType constraintType, Optional<StringConstraint>&& constraint)
{
switch (constraintType) {
case MediaConstraintType::FacingMode:
m_facingMode = WTFMove(constraint);
break;
case MediaConstraintType::DeviceId:
if (constraint)
constraint->removeEmptyStringConstraint();
m_deviceId = WTFMove(constraint);
break;
case MediaConstraintType::GroupId:
m_groupId = WTFMove(constraint);
break;
case MediaConstraintType::Width:
case MediaConstraintType::Height:
case MediaConstraintType::SampleRate:
case MediaConstraintType::SampleSize:
case MediaConstraintType::AspectRatio:
case MediaConstraintType::FrameRate:
case MediaConstraintType::Volume:
case MediaConstraintType::EchoCancellation:
case MediaConstraintType::DisplaySurface:
case MediaConstraintType::LogicalSurface:
case MediaConstraintType::Unknown:
ASSERT_NOT_REACHED();
break;
}
}
size_t MediaTrackConstraintSetMap::size() const
{
size_t count = 0;
forEach([&count] (const MediaConstraint&) mutable {
++count;
});
return count;
}
bool MediaTrackConstraintSetMap::isEmpty() const
{
return !size();
}
static inline void addDefaultVideoConstraints(MediaTrackConstraintSetMap& videoConstraints, bool addFrameRateConstraint, bool addSizeConstraint, bool addFacingModeConstraint)
{
if (addFrameRateConstraint) {
DoubleConstraint frameRateConstraint({ }, MediaConstraintType::FrameRate);
frameRateConstraint.setIdeal(30);
videoConstraints.set(MediaConstraintType::FrameRate, WTFMove(frameRateConstraint));
}
if (addSizeConstraint) {
IntConstraint widthConstraint({ }, MediaConstraintType::Width);
widthConstraint.setIdeal(640);
videoConstraints.set(MediaConstraintType::Width, WTFMove(widthConstraint));
IntConstraint heightConstraint({ }, MediaConstraintType::Height);
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 WTF::Function<bool(const MediaTrackConstraintSetMap&)>& callback)
{
if (callback(mandatoryConstraints))
return true;
for (const auto& constraint : advancedConstraints) {
if (callback(constraint))
return true;
}
return false;
}
void MediaConstraints::setDefaultVideoConstraints()
{
// 640x480, 30fps, front-facing camera
bool needsFrameRateConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
return !!constraint.frameRate() || !!constraint.width() || !!constraint.height();
});
bool needsSizeConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
return !!constraint.width() || !!constraint.height();
});
bool needsFacingModeConstraints = !isConstraintSet([](const MediaTrackConstraintSetMap& constraint) {
return !!constraint.facingMode() || !!constraint.deviceId();
});
addDefaultVideoConstraints(mandatoryConstraints, needsFrameRateConstraints, needsSizeConstraints, needsFacingModeConstraints);
}
}
#endif // ENABLE(MEDIA_STREAM)