mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1037389 - add support for deviceId in gUM constraints (merged 11 patches). r=smaug, r=jesup
This commit is contained in:
parent
eece65fcc3
commit
ccf0b4baae
File diff suppressed because it is too large
Load Diff
@ -465,13 +465,25 @@ public:
|
||||
NS_DECL_NSIMEDIADEVICE
|
||||
|
||||
void SetId(const nsAString& aID);
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& 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<MediaEngineSource> 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<const dom::MediaTrackConstraintSet*>& 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<const dom::MediaTrackConstraintSet*>& 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<nsRefPtr<MediaDevice>> SourceSet;
|
||||
private:
|
||||
typedef media::Pledge<SourceSet*, dom::MediaStreamError> 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<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
|
||||
private:
|
||||
already_AddRefed<PledgeSourceSet>
|
||||
EnumerateRawDevices(uint64_t aWindowId,
|
||||
const dom::MediaStreamConstraints& aConstraints);
|
||||
EnumerateRawDevices(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
|
||||
bool aFake, bool aFakeTracks);
|
||||
already_AddRefed<PledgeSourceSet>
|
||||
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() {
|
||||
|
@ -503,16 +503,12 @@ Parent<Super>::Parent(bool aSameProcess)
|
||||
if (!gMediaParentLog)
|
||||
gMediaParentLog = PR_NewLogModule("MediaParent");
|
||||
LOG(("media::Parent: %p", this));
|
||||
|
||||
MOZ_COUNT_CTOR(Parent);
|
||||
}
|
||||
|
||||
template<class Super>
|
||||
Parent<Super>::~Parent()
|
||||
{
|
||||
LOG(("~media::Parent: %p", this));
|
||||
|
||||
MOZ_COUNT_DTOR(Parent);
|
||||
}
|
||||
|
||||
PMediaParent*
|
||||
|
@ -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');
|
||||
|
@ -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"]
|
||||
]
|
||||
|
@ -8,10 +8,24 @@
|
||||
<script type="application/javascript">
|
||||
createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
|
||||
/**
|
||||
Tests covering enumerateDevices API. Exercise code.
|
||||
Tests covering enumerateDevices API and deviceId constraint. Exercise code.
|
||||
*/
|
||||
|
||||
runTest(() => navigator.mediaDevices.enumerateDevices()
|
||||
function mustSucceed(msg, f) {
|
||||
return f().then(() => ok(true, msg + " must succeed"),
|
||||
e => is(e.name, null, msg + " must succeed: " + e.message));
|
||||
}
|
||||
|
||||
function mustFailWith(msg, reason, f) {
|
||||
return f().then(() => ok(false, msg + " must fail"),
|
||||
e => is(e.name, reason, msg + " must fail: " + e.message));
|
||||
}
|
||||
|
||||
var pushPrefs = dict => new Promise(res => SpecialPowers.pushPrefEnv(dict, res));
|
||||
|
||||
runTest(() =>
|
||||
pushPrefs({ set : [["media.navigator.streams.fake", true]] })
|
||||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then(devices => {
|
||||
ok(devices.length > 0, "At least one device found");
|
||||
devices.forEach(d => {
|
||||
@ -20,7 +34,28 @@ runTest(() => navigator.mediaDevices.enumerateDevices()
|
||||
ok(d.label.length !== undefined, "Device label: " + d.label);
|
||||
is(d.groupId, "", "Don't support groupId yet");
|
||||
});
|
||||
}));
|
||||
})
|
||||
// Check deviceId failure paths for video.
|
||||
.then(() => mustSucceed("unknown plain deviceId on video",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustSucceed("unknown plain deviceId on audio",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
})))
|
||||
.then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
|
||||
() => navigator.mediaDevices.getUserMedia({
|
||||
audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
|
||||
fake: true,
|
||||
}))));
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& 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<const dom::MediaTrackConstraintSet*>& 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) {}
|
||||
|
@ -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<class ValueType, class ConstrainRange>
|
||||
/* 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<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
const nsTArray<const MediaTrackConstraintSet*>& 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);
|
||||
}
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& aConstraintSets) override;
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
virtual void Shutdown() override {};
|
||||
|
||||
@ -80,28 +82,18 @@ protected:
|
||||
layers::Image* aImage,
|
||||
TrackID aID,
|
||||
StreamTime delta);
|
||||
template<class ValueType, class ConstrainRange>
|
||||
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
|
||||
|
@ -68,9 +68,24 @@ MediaEngineDefaultVideoSource::GetUUID(nsACString& aUUID)
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MediaEngineDefaultVideoSource::GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& 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<const dom::MediaTrackConstraintSet*>& 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;
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& 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<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
|
@ -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<MediaEngineGonkVideoSource>(this),
|
||||
&MediaEngineGonkVideoSource::AllocImpl));
|
||||
mCallbackMonitor.Wait();
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& aConstraintSets) override
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& aConstraintSets,
|
||||
const nsString& aDeviceId) override;
|
||||
|
||||
// VoEMediaProcess.
|
||||
void Process(int channel, webrtc::ProcessingTypes type,
|
||||
int16_t audio10ms[], int length,
|
||||
|
@ -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<const dom::MediaTrackConstraintSet*>& 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) {
|
||||
|
@ -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(),
|
||||
|
@ -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<class ValueType, class ConstrainRange>
|
||||
/* 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -83,6 +83,29 @@ struct FlattenedConstraints : public NormalizedConstraintSet
|
||||
explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
|
||||
};
|
||||
|
||||
// A helper class for MediaEngines
|
||||
|
||||
class MediaConstraintsHelper
|
||||
{
|
||||
protected:
|
||||
template<class ValueType, class ConstrainRange>
|
||||
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_ */
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user