Repository: pion/mediadevices
Pull request #170 (2020-05-24 02:15:31)

Select by DeviceID using StringConstraint
Caused panic at https://github.com/pion/mediadevices/blob/27d966611e1766d574ae78940df7c25656aaf428/pkg/prop/prop.go#L69-L70 when DeviceID was specified.

Compare DeviceID field on `FitnessDistance()` which was directly selected at upper layer.

### Reference issue
follows up #168 


at-wat: DeviceID field maybe better to support OneOf constraint.
at-wat: working as expected now

Patch:
diff --git a/mediadevices.go b/mediadevices.go
index 1462df5..2b31e91 100644
--- a/mediadevices.go
+++ b/mediadevices.go
@@ -224,13 +224,8 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
 
 func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker, error) {
 	typeFilter := driver.FilterAudioRecorder()
-	filter := typeFilter
-	if constraints.DeviceID != "" {
-		idFilter := driver.FilterID(constraints.DeviceID)
-		filter = driver.FilterAnd(typeFilter, idFilter)
-	}
 
-	d, c, err := selectBestDriver(filter, constraints)
+	d, c, err := selectBestDriver(typeFilter, constraints)
 	if err != nil {
 		return nil, err
 	}
@@ -241,10 +236,6 @@ func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker,
 	typeFilter := driver.FilterVideoRecorder()
 	notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen))
 	filter := driver.FilterAnd(typeFilter, notScreenFilter)
-	if constraints.DeviceID != "" {
-		idFilter := driver.FilterID(constraints.DeviceID)
-		filter = driver.FilterAnd(typeFilter, notScreenFilter, idFilter)
-	}
 
 	d, c, err := selectBestDriver(filter, constraints)
 	if err != nil {
@@ -258,10 +249,6 @@ func (m *mediaDevices) selectScreen(constraints MediaTrackConstraints) (Tracker,
 	typeFilter := driver.FilterVideoRecorder()
 	screenFilter := driver.FilterDeviceType(driver.Screen)
 	filter := driver.FilterAnd(typeFilter, screenFilter)
-	if constraints.DeviceID != "" {
-		idFilter := driver.FilterID(constraints.DeviceID)
-		filter = driver.FilterAnd(typeFilter, screenFilter, idFilter)
-	}
 
 	d, c, err := selectBestDriver(filter, constraints)
 	if err != nil {
diff --git a/pkg/driver/wrapper.go b/pkg/driver/wrapper.go
index 1ccaf06..a137f3a 100644
--- a/pkg/driver/wrapper.go
+++ b/pkg/driver/wrapper.go
@@ -71,7 +71,11 @@ func (w *adapterWrapper) Properties() []prop.Media {
 		return nil
 	}
 
-	return w.Adapter.Properties()
+	p := w.Adapter.Properties()
+	for i := range p {
+		p[i].DeviceID = w.id
+	}
+	return p
 }
 
 func (w *adapterWrapper) VideoRecord(p prop.Media) (r video.Reader, err error) {
diff --git a/pkg/prop/prop.go b/pkg/prop/prop.go
index 26bf4cb..5fa11b2 100644
--- a/pkg/prop/prop.go
+++ b/pkg/prop/prop.go
@@ -10,7 +10,7 @@ import (
 // MediaConstraints represents set of media propaty constraints.
 // Each field constrains property by min/ideal/max range, exact match, or oneof match.
 type MediaConstraints struct {
-	DeviceID string
+	DeviceID StringConstraint
 	VideoConstraints
 	AudioConstraints
 }
@@ -66,6 +66,10 @@ func (p *Media) Merge(o MediaConstraints) {
 				if v, ok := c.Value(); ok {
 					fieldA.Set(reflect.ValueOf(v))
 				}
+			case StringConstraint:
+				if v, ok := c.Value(); ok {
+					fieldA.Set(reflect.ValueOf(v))
+				}
 			default:
 				panic("unsupported property type")
 			}
@@ -79,6 +83,7 @@ func (p *Media) Merge(o MediaConstraints) {
 // If no media satisfies given constraints, second return value will be false.
 func (p *MediaConstraints) FitnessDistance(o Media) (float64, bool) {
 	cmps := comparisons{}
+	cmps.add(p.DeviceID, o.DeviceID)
 	cmps.add(p.Width, o.Width)
 	cmps.add(p.Height, o.Height)
 	cmps.add(p.FrameFormat, o.FrameFormat)
@@ -133,6 +138,12 @@ func (c *comparisons) fitnessDistance() (float64, bool) {
 			} else {
 				panic("wrong type of actual value")
 			}
+		case StringConstraint:
+			if actual, typeOK := field.actual.(string); typeOK {
+				d, ok = c.Compare(actual)
+			} else {
+				panic("wrong type of actual value")
+			}
 		default:
 			panic("unsupported constraint type")
 		}
diff --git a/pkg/prop/string.go b/pkg/prop/string.go
new file mode 100644
index 0000000..f9a9471
--- /dev/null
+++ b/pkg/prop/string.go
@@ -0,0 +1,52 @@
+package prop
+
+// StringConstraint is an interface to represent string constraint.
+type StringConstraint interface {
+	Compare(string) (float64, bool)
+	Value() (string, bool)
+}
+
+// String specifies expected string.
+// Any value may be selected, but matched value takes priority.
+type String string
+
+// Compare implements StringConstraint.
+func (f String) Compare(a string) (float64, bool) {
+	if string(f) == a {
+		return 0.0, true
+	}
+	return 1.0, true
+}
+
+// Value implements StringConstraint.
+func (f String) Value() (string, bool) { return string(f), true }
+
+// StringExact specifies exact string.
+type StringExact string
+
+// Compare implements StringConstraint.
+func (f StringExact) Compare(a string) (float64, bool) {
+	if string(f) == a {
+		return 0.0, true
+	}
+	return 1.0, false
+}
+
+// Value implements StringConstraint.
+func (f StringExact) Value() (string, bool) { return string(f), true }
+
+// StringOneOf specifies list of expected string.
+type StringOneOf []string
+
+// Compare implements StringConstraint.
+func (f StringOneOf) Compare(a string) (float64, bool) {
+	for _, ff := range f {
+		if ff == a {
+			return 0.0, true
+		}
+	}
+	return 1.0, false
+}
+
+// Value implements StringConstraint.
+func (StringOneOf) Value() (string, bool) { return "", false }


Test patch:
diff --git a/pkg/prop/prop_test.go b/pkg/prop/prop_test.go
index eda2a7a..9d5db2d 100644
--- a/pkg/prop/prop_test.go
+++ b/pkg/prop/prop_test.go
@@ -13,6 +13,24 @@ func TestCompareMatch(t *testing.T) {
 		b     Media
 		match bool
 	}{
+		"DeviceIDExactUnmatch": {
+			MediaConstraints{
+				DeviceID: StringExact("abc"),
+			},
+			Media{
+				DeviceID: "cde",
+			},
+			false,
+		},
+		"DeviceIDExactMatch": {
+			MediaConstraints{
+				DeviceID: StringExact("abc"),
+			},
+			Media{
+				DeviceID: "abc",
+			},
+			true,
+		},
 		"IntIdealUnmatch": {
 			MediaConstraints{VideoConstraints: VideoConstraints{
 				Width: Int(30),