blob: 8b20fa97888f3d672bb0256fd9a2099ff87520fa [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
* 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.
*/
#pragma once
#if ENABLE(MEDIA_STREAM)
#include "RealtimeMediaSourceSupportedConstraints.h"
#include <cstdlib>
#include <wtf/Function.h>
#include <wtf/Vector.h>
namespace WebCore {
class MediaConstraint {
public:
enum class DataType { None, Integer, Double, Boolean, String };
bool isInt() const { return m_dataType == DataType::Integer; }
bool isDouble() const { return m_dataType == DataType::Double; }
bool isBoolean() const { return m_dataType == DataType::Boolean; }
bool isString() const { return m_dataType == DataType::String; }
DataType dataType() const { return m_dataType; }
MediaConstraintType constraintType() const { return m_constraintType; }
const String& name() const { return m_name; }
template <class Encoder> void encode(Encoder& encoder) const
{
encoder.encodeEnum(m_constraintType);
encoder << m_name;
encoder.encodeEnum(m_dataType);
}
template <class Decoder> static bool decode(Decoder& decoder, MediaConstraint& constraint)
{
if (!decoder.decodeEnum(constraint.m_constraintType))
return false;
if (!decoder.decode(constraint.m_name))
return false;
if (!decoder.decodeEnum(constraint.m_dataType))
return false;
return true;
}
protected:
MediaConstraint(const String& name, MediaConstraintType constraintType, DataType dataType)
: m_name(name)
, m_constraintType(constraintType)
, m_dataType(dataType)
{
}
MediaConstraint() = default;
~MediaConstraint() = default;
private:
String m_name;
MediaConstraintType m_constraintType { MediaConstraintType::Unknown };
DataType m_dataType { DataType::None };
};
template<class ValueType>
class NumericConstraint : public MediaConstraint {
public:
void setMin(ValueType value) { m_min = value; }
void setMax(ValueType value) { m_max = value; }
void setExact(ValueType value) { m_exact = value; }
void setIdeal(ValueType value) { m_ideal = value; }
bool getMin(ValueType& min) const
{
if (!m_min)
return false;
min = m_min.value();
return true;
}
bool getMax(ValueType& max) const
{
if (!m_max)
return false;
max = m_max.value();
return true;
}
bool getExact(ValueType& exact) const
{
if (!m_exact)
return false;
exact = m_exact.value();
return true;
}
bool getIdeal(ValueType& ideal) const
{
if (!m_ideal)
return false;
ideal = m_ideal.value();
return true;
}
bool nearlyEqual(double a, double b) const
{
// Don't require strict equality when comparing constraints, or many floating point constraint values,
// e.g. "aspectRatio: 1.333", will never match.
const double epsilon = 0.00001;
return std::abs(a - b) <= epsilon;
}
double fitnessDistance(ValueType rangeMin, ValueType rangeMax) 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.
bool valid = validForRange(rangeMin, rangeMax);
if (m_exact) {
if (valid && m_min && m_exact.value() < m_min.value())
valid = false;
if (valid && m_max && m_exact.value() > m_max.value())
valid = false;
if (!valid)
return std::numeric_limits<double>::infinity();
}
if (m_min) {
if (valid && m_max && m_min.value() > m_max.value())
valid = false;
if (!valid)
return std::numeric_limits<double>::infinity();
}
if (m_max) {
if (valid && m_min && m_max.value() < m_min.value())
valid = false;
if (!valid)
return std::numeric_limits<double>::infinity();
}
// 3. If no ideal value is specified, the fitness distance is 0.
if (!m_ideal)
return 0;
// 4. For all positive numeric non-required constraints (such as height, width, frameRate,
// aspectRatio, sampleRate and sampleSize), the fitness distance is the result of the formula
//
// (actual == ideal) ? 0 : |actual - ideal| / max(|actual|,|ideal|)
ValueType ideal = m_ideal.value();
if (ideal >= rangeMin && ideal <= rangeMax)
return 0;
ideal = ideal > std::max(rangeMin, rangeMax) ? rangeMax : rangeMin;
return static_cast<double>(std::abs(ideal - m_ideal.value())) / std::max(std::abs(ideal), std::abs(m_ideal.value()));
}
double fitnessDistance(const Vector<ValueType>& discreteCapabilityValues) const
{
double minDistance = std::numeric_limits<double>::infinity();
for (auto& value : discreteCapabilityValues) {
auto distance = fitnessDistance(value, value);
if (distance < minDistance)
minDistance = distance;
}
return minDistance;
}
bool validForRange(ValueType rangeMin, ValueType rangeMax) const
{
if (isEmpty())
return false;
if (m_exact) {
const ValueType exact = m_exact.value();
if (exact < rangeMin && !nearlyEqual(exact, rangeMin))
return false;
if (exact > rangeMax && !nearlyEqual(exact, rangeMax))
return false;
}
if (m_min) {
const ValueType constraintMin = m_min.value();
if (constraintMin > rangeMax && !nearlyEqual(constraintMin, rangeMax))
return false;
}
if (m_max) {
const ValueType constraintMax = m_max.value();
if (constraintMax < rangeMin && !nearlyEqual(constraintMax, rangeMin))
return false;
}
return true;
}
ValueType find(const WTF::Function<bool(ValueType)>& function) const
{
if (m_min && function(m_min.value()))
return m_min.value();
if (m_max && function(m_max.value()))
return m_max.value();
if (m_exact && function(m_exact.value()))
return m_exact.value();
if (m_ideal && function(m_ideal.value()))
return m_ideal.value();
return 0;
}
ValueType valueForCapabilityRange(ValueType current, ValueType capabilityMin, ValueType capabilityMax) const
{
ValueType value { 0 };
ValueType min { capabilityMin };
ValueType max { capabilityMax };
if (m_exact) {
ASSERT(validForRange(capabilityMin, capabilityMax));
return m_exact.value();
}
if (m_min) {
value = m_min.value();
ASSERT(validForRange(value, capabilityMax));
if (value > min)
min = value;
if (value < min)
value = min;
// If there is no ideal, don't change if minimum is smaller than current.
if (!m_ideal && value < current)
value = current;
}
if (m_max) {
value = m_max.value();
ASSERT(validForRange(capabilityMin, value));
if (value < max)
max = value;
if (value > max)
value = max;
}
if (m_ideal)
value = std::max(min, std::min(max, m_ideal.value()));
return value;
}
ValueType valueForDiscreteCapabilityValues(ValueType current, const Vector<ValueType>& discreteCapabilityValues) const
{
ValueType value { 0 };
Optional<ValueType> min;
Optional<ValueType> max;
if (m_exact) {
ASSERT(discreteCapabilityValues.contains(m_exact.value()));
return m_exact.value();
}
if (m_min) {
auto index = discreteCapabilityValues.findMatching([&](ValueType value) { return m_min.value() >= value; });
if (index != notFound) {
min = value = discreteCapabilityValues[index];
// If there is no ideal, don't change if minimum is smaller than current.
if (!m_ideal && value < current)
value = current;
}
}
if (m_max && m_max.value() >= discreteCapabilityValues[0]) {
for (auto& discreteValue : discreteCapabilityValues) {
if (m_max.value() <= discreteValue)
max = value = discreteValue;
}
}
if (m_ideal && discreteCapabilityValues.contains(m_ideal.value())) {
value = m_ideal.value();
if (max)
value = std::min(max.value(), value);
if (min)
value = std::max(min.value(), value);
}
return value;
}
bool isEmpty() const { return !m_min && !m_max && !m_exact && !m_ideal; }
bool isMandatory() const { return m_min || m_max || m_exact; }
template <class Encoder> void encode(Encoder& encoder) const
{
MediaConstraint::encode(encoder);
encoder << m_min;
encoder << m_max;
encoder << m_exact;
encoder << m_ideal;
}
template <class Decoder> static bool decode(Decoder& decoder, NumericConstraint& constraint)
{
if (!MediaConstraint::decode(decoder, constraint))
return false;
if (!decoder.decode(constraint.m_min))
return false;
if (!decoder.decode(constraint.m_max))
return false;
if (!decoder.decode(constraint.m_exact))
return false;
if (!decoder.decode(constraint.m_ideal))
return false;
return true;
}
protected:
NumericConstraint(const String& name, MediaConstraintType type, DataType dataType)
: MediaConstraint(name, type, dataType)
{
}
NumericConstraint() = default;
void innerMerge(const NumericConstraint& other)
{
if (other.isEmpty())
return;
ValueType value;
if (other.getExact(value))
m_exact = value;
if (other.getMin(value))
m_min = value;
if (other.getMax(value))
m_max = value;
// https://w3c.github.io/mediacapture-main/#constrainable-interface
// When processing advanced constraints:
// ... the User Agent must attempt to apply, individually, any 'ideal' constraints or
// a constraint given as a bare value for the property. Of these properties, it must
// satisfy the largest number that it can, in any order.
if (other.getIdeal(value)) {
if (!m_ideal || value > m_ideal.value())
m_ideal = value;
}
}
Optional<ValueType> m_min;
Optional<ValueType> m_max;
Optional<ValueType> m_exact;
Optional<ValueType> m_ideal;
};
class IntConstraint final : public NumericConstraint<int> {
public:
IntConstraint(const String& name, MediaConstraintType type)
: NumericConstraint<int>(name, type, DataType::Integer)
{
}
IntConstraint() = default;
void merge(const MediaConstraint& other)
{
ASSERT(other.isInt());
NumericConstraint::innerMerge(downcast<const IntConstraint>(other));
}
};
class DoubleConstraint final : public NumericConstraint<double> {
public:
DoubleConstraint(const String& name, MediaConstraintType type)
: NumericConstraint<double>(name, type, DataType::Double)
{
}
DoubleConstraint() = default;
void merge(const MediaConstraint& other)
{
ASSERT(other.isDouble());
NumericConstraint::innerMerge(downcast<DoubleConstraint>(other));
}
};
class BooleanConstraint final : public MediaConstraint {
public:
BooleanConstraint(const String& name, MediaConstraintType type)
: MediaConstraint(name, type, DataType::Boolean)
{
}
BooleanConstraint() = default;
void setExact(bool value) { m_exact = value; }
void setIdeal(bool value) { m_ideal = value; }
bool getExact(bool& exact) const
{
if (!m_exact)
return false;
exact = m_exact.value();
return true;
}
bool getIdeal(bool& ideal) const
{
if (!m_ideal)
return false;
ideal = m_ideal.value();
return true;
}
double fitnessDistance(bool 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 && value != m_exact.value())
return std::numeric_limits<double>::infinity();
// 3. If no ideal value is specified, the fitness distance is 0.
if (!m_ideal || m_ideal.value() == value)
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 1;
}
void merge(const MediaConstraint& other)
{
ASSERT(other.isBoolean());
const BooleanConstraint& typedOther = downcast<BooleanConstraint>(other);
if (typedOther.isEmpty())
return;
bool value;
if (typedOther.getExact(value))
m_exact = value;
if (typedOther.getIdeal(value)) {
if (!m_ideal || (value && !m_ideal.value()))
m_ideal = value;
}
}
bool isEmpty() const { return !m_exact && !m_ideal; };
bool isMandatory() const { return bool(m_exact); }
template <class Encoder> void encode(Encoder& encoder) const
{
MediaConstraint::encode(encoder);
encoder << m_exact;
encoder << m_ideal;
}
template <class Decoder> static bool decode(Decoder& decoder, BooleanConstraint& constraint)
{
if (!MediaConstraint::decode(decoder, constraint))
return false;
if (!decoder.decode(constraint.m_exact))
return false;
if (!decoder.decode(constraint.m_ideal))
return false;
return true;
}
private:
Optional<bool> m_exact;
Optional<bool> m_ideal;
};
class StringConstraint : public MediaConstraint {
public:
StringConstraint(const String& name, MediaConstraintType type)
: MediaConstraint(name, type, DataType::String)
{
}
StringConstraint() = default;
void setExact(const String& value)
{
m_exact.clear();
m_exact.append(value);
}
void appendExact(const String& value)
{
m_exact.append(value);
}
void setIdeal(const String& value)
{
m_ideal.clear();
m_ideal.append(value);
}
void appendIdeal(const String& value)
{
m_ideal.append(value);
}
bool getExact(Vector<String>& exact) const
{
if (!m_exact.isEmpty())
return false;
exact = m_exact;
return true;
}
bool getIdeal(Vector<String>& ideal) const
{
if (!m_ideal.isEmpty())
return false;
ideal = m_ideal;
return true;
}
double fitnessDistance(const String&) const;
double fitnessDistance(const Vector<String>&) const;
const String& find(const WTF::Function<bool(const String&)>&) const;
bool isEmpty() const { return m_exact.isEmpty() && m_ideal.isEmpty(); }
bool isMandatory() const { return !m_exact.isEmpty(); }
WEBCORE_EXPORT void merge(const MediaConstraint&);
template <class Encoder> void encode(Encoder& encoder) const
{
MediaConstraint::encode(encoder);
encoder << m_exact;
encoder << m_ideal;
}
template <class Decoder> static bool decode(Decoder& decoder, StringConstraint& constraint)
{
if (!MediaConstraint::decode(decoder, constraint))
return false;
if (!decoder.decode(constraint.m_exact))
return false;
if (!decoder.decode(constraint.m_ideal))
return false;
return true;
}
void removeEmptyStringConstraint()
{
m_exact.removeAllMatching([](auto& constraint) {
return constraint.isEmpty();
});
m_ideal.removeAllMatching([](auto& constraint) {
return constraint.isEmpty();
});
}
private:
Vector<String> m_exact;
Vector<String> m_ideal;
};
class UnknownConstraint final : public MediaConstraint {
public:
UnknownConstraint(const String& name, MediaConstraintType type)
: MediaConstraint(name, type, DataType::None)
{
}
private:
bool isEmpty() const { return true; }
bool isMandatory() const { return false; }
void merge(const MediaConstraint&) { }
};
class MediaTrackConstraintSetMap {
public:
WEBCORE_EXPORT void forEach(WTF::Function<void(const MediaConstraint&)>&&) const;
void filter(const WTF::Function<bool(const MediaConstraint&)>&) const;
bool isEmpty() const;
WEBCORE_EXPORT size_t size() const;
WEBCORE_EXPORT void set(MediaConstraintType, Optional<IntConstraint>&&);
WEBCORE_EXPORT void set(MediaConstraintType, Optional<DoubleConstraint>&&);
WEBCORE_EXPORT void set(MediaConstraintType, Optional<BooleanConstraint>&&);
WEBCORE_EXPORT void set(MediaConstraintType, Optional<StringConstraint>&&);
Optional<IntConstraint> width() const { return m_width; }
Optional<IntConstraint> height() const { return m_height; }
Optional<IntConstraint> sampleRate() const { return m_sampleRate; }
Optional<IntConstraint> sampleSize() const { return m_sampleSize; }
Optional<DoubleConstraint> aspectRatio() const { return m_aspectRatio; }
Optional<DoubleConstraint> frameRate() const { return m_frameRate; }
Optional<DoubleConstraint> volume() const { return m_volume; }
Optional<BooleanConstraint> echoCancellation() const { return m_echoCancellation; }
Optional<BooleanConstraint> displaySurface() const { return m_displaySurface; }
Optional<BooleanConstraint> logicalSurface() const { return m_logicalSurface; }
Optional<StringConstraint> facingMode() const { return m_facingMode; }
Optional<StringConstraint> deviceId() const { return m_deviceId; }
Optional<StringConstraint> groupId() const { return m_groupId; }
template <class Encoder> void encode(Encoder& encoder) const
{
encoder << m_width;
encoder << m_height;
encoder << m_sampleRate;
encoder << m_sampleSize;
encoder << m_aspectRatio;
encoder << m_frameRate;
encoder << m_volume;
encoder << m_echoCancellation;
encoder << m_displaySurface;
encoder << m_logicalSurface;
encoder << m_facingMode;
encoder << m_deviceId;
encoder << m_groupId;
}
template <class Decoder> static Optional<MediaTrackConstraintSetMap> decode(Decoder& decoder)
{
MediaTrackConstraintSetMap map;
if (!decoder.decode(map.m_width))
return WTF::nullopt;
if (!decoder.decode(map.m_height))
return WTF::nullopt;
if (!decoder.decode(map.m_sampleRate))
return WTF::nullopt;
if (!decoder.decode(map.m_sampleSize))
return WTF::nullopt;
if (!decoder.decode(map.m_aspectRatio))
return WTF::nullopt;
if (!decoder.decode(map.m_frameRate))
return WTF::nullopt;
if (!decoder.decode(map.m_volume))
return WTF::nullopt;
if (!decoder.decode(map.m_echoCancellation))
return WTF::nullopt;
if (!decoder.decode(map.m_displaySurface))
return WTF::nullopt;
if (!decoder.decode(map.m_logicalSurface))
return WTF::nullopt;
if (!decoder.decode(map.m_facingMode))
return WTF::nullopt;
if (!decoder.decode(map.m_deviceId))
return WTF::nullopt;
if (!decoder.decode(map.m_groupId))
return WTF::nullopt;
return map;
}
private:
Optional<IntConstraint> m_width;
Optional<IntConstraint> m_height;
Optional<IntConstraint> m_sampleRate;
Optional<IntConstraint> m_sampleSize;
Optional<DoubleConstraint> m_aspectRatio;
Optional<DoubleConstraint> m_frameRate;
Optional<DoubleConstraint> m_volume;
Optional<BooleanConstraint> m_echoCancellation;
Optional<BooleanConstraint> m_displaySurface;
Optional<BooleanConstraint> m_logicalSurface;
Optional<StringConstraint> m_facingMode;
Optional<StringConstraint> m_deviceId;
Optional<StringConstraint> m_groupId;
};
class FlattenedConstraint {
public:
void set(const MediaConstraint&);
void merge(const MediaConstraint&);
void append(const MediaConstraint&);
const MediaConstraint* find(MediaConstraintType) const;
bool isEmpty() const { return m_variants.isEmpty(); }
class iterator {
public:
iterator(const FlattenedConstraint* constraint, size_t index)
: m_constraint(constraint)
, m_index(index)
#ifndef NDEBUG
, m_generation(constraint->m_generation)
#endif
{
}
MediaConstraint& operator*() const
{
return m_constraint->m_variants.at(m_index).constraint();
}
iterator& operator++()
{
#ifndef NDEBUG
ASSERT(m_generation == m_constraint->m_generation);
#endif
m_index++;
return *this;
}
bool operator==(const iterator& other) const { return m_index == other.m_index; }
bool operator!=(const iterator& other) const { return !(*this == other); }
private:
const FlattenedConstraint* m_constraint { nullptr };
size_t m_index { 0 };
#ifndef NDEBUG
int m_generation { 0 };
#endif
};
const iterator begin() const { return iterator(this, 0); }
const iterator end() const { return iterator(this, m_variants.size()); }
private:
class ConstraintHolder {
public:
static ConstraintHolder create(const MediaConstraint& value) { return ConstraintHolder(value); }
~ConstraintHolder()
{
if (m_value.asRaw) {
switch (dataType()) {
case MediaConstraint::DataType::Integer:
delete m_value.asInteger;
break;
case MediaConstraint::DataType::Double:
delete m_value.asDouble;
break;
case MediaConstraint::DataType::Boolean:
delete m_value.asBoolean;
break;
case MediaConstraint::DataType::String:
delete m_value.asString;
break;
case MediaConstraint::DataType::None:
ASSERT_NOT_REACHED();
break;
}
}
#ifndef NDEBUG
m_value.asRaw = reinterpret_cast<MediaConstraint*>(0xbbadbeef);
#endif
}
ConstraintHolder(ConstraintHolder&& other)
{
switch (other.dataType()) {
case MediaConstraint::DataType::Integer:
m_value.asInteger = std::exchange(other.m_value.asInteger, nullptr);
break;
case MediaConstraint::DataType::Double:
m_value.asDouble = std::exchange(other.m_value.asDouble, nullptr);
break;
case MediaConstraint::DataType::Boolean:
m_value.asBoolean = std::exchange(other.m_value.asBoolean, nullptr);
break;
case MediaConstraint::DataType::String:
m_value.asString = std::exchange(other.m_value.asString, nullptr);
break;
case MediaConstraint::DataType::None:
ASSERT_NOT_REACHED();
break;
}
}
MediaConstraint& constraint() const { return *m_value.asRaw; }
MediaConstraint::DataType dataType() const { return constraint().dataType(); }
MediaConstraintType constraintType() const { return constraint().constraintType(); }
private:
explicit ConstraintHolder(const MediaConstraint& value)
{
switch (value.dataType()) {
case MediaConstraint::DataType::Integer:
m_value.asInteger = new IntConstraint(downcast<const IntConstraint>(value));
break;
case MediaConstraint::DataType::Double:
m_value.asDouble = new DoubleConstraint(downcast<DoubleConstraint>(value));
break;
case MediaConstraint::DataType::Boolean:
m_value.asBoolean = new BooleanConstraint(downcast<BooleanConstraint>(value));
break;
case MediaConstraint::DataType::String:
m_value.asString = new StringConstraint(downcast<StringConstraint>(value));
break;
case MediaConstraint::DataType::None:
ASSERT_NOT_REACHED();
break;
}
}
union {
MediaConstraint* asRaw;
IntConstraint* asInteger;
DoubleConstraint* asDouble;
BooleanConstraint* asBoolean;
StringConstraint* asString;
} m_value;
};
Vector<ConstraintHolder> m_variants;
#ifndef NDEBUG
int m_generation { 0 };
#endif
};
struct MediaConstraints {
void setDefaultVideoConstraints();
bool isConstraintSet(const WTF::Function<bool(const MediaTrackConstraintSetMap&)>&);
MediaTrackConstraintSetMap mandatoryConstraints;
Vector<MediaTrackConstraintSetMap> advancedConstraints;
bool isValid { false };
};
} // namespace WebCore
#define SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(ConstraintType, predicate) \
SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ConstraintType) \
static bool isType(const WebCore::MediaConstraint& constraint) { return constraint.predicate; } \
SPECIALIZE_TYPE_TRAITS_END()
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(IntConstraint, isInt())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(DoubleConstraint, isDouble())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(StringConstraint, isString())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(BooleanConstraint, isBoolean())
#endif // ENABLE(MEDIA_STREAM)