Files
mediadevices/mediadevices.go
Lukas Herman 7e739a814b WIP
2020-05-05 23:28:05 -04:00

219 lines
5.2 KiB
Go

package mediadevices
import (
"fmt"
"math"
"github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/prop"
)
var errNotFound = fmt.Errorf("failed to find the best driver that fits the constraints")
// GetDisplayMedia prompts the user to select and grant permission to capture the contents
// of a display or portion thereof (such as a window) as a MediaStream.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
func GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
tracks := make([]Track, 0)
cleanTracks := func() {
for _, t := range tracks {
t.Stop()
}
}
if constraints.Video != nil {
var p prop.Media
constraints.Video(&p)
track, err := selectScreen(p)
if err != nil {
cleanTracks()
return nil, err
}
tracks = append(tracks, track)
}
s, err := NewMediaStream(tracks...)
if err != nil {
cleanTracks()
return nil, err
}
return s, nil
}
// GetUserMedia prompts the user for permission to use a media input which produces a MediaStream
// with tracks containing the requested types of media.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
func GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) {
tracks := make([]Track, 0)
cleanTracks := func() {
for _, t := range tracks {
t.Stop()
}
}
if constraints.Video != nil {
var p prop.Media
constraints.Video(&p)
track, err := selectVideo(p)
if err != nil {
cleanTracks()
return nil, err
}
tracks = append(tracks, track)
}
if constraints.Audio != nil {
var p prop.Media
constraints.Audio(&p)
track, err := selectAudio(p)
if err != nil {
cleanTracks()
return nil, err
}
tracks = append(tracks, track)
}
s, err := NewMediaStream(tracks...)
if err != nil {
cleanTracks()
return nil, err
}
return s, nil
}
func queryDriverProperties(filter driver.FilterFn) map[driver.Driver][]prop.Media {
var needToClose []driver.Driver
drivers := driver.GetManager().Query(filter)
m := make(map[driver.Driver][]prop.Media)
for _, d := range drivers {
if d.Status() == driver.StateClosed {
err := d.Open()
if err != nil {
// Skip this driver if we failed to open because we can't get the properties
continue
}
needToClose = append(needToClose, d)
}
m[d] = d.Properties()
}
for _, d := range needToClose {
// Since it was closed, we should close it to avoid a leak
d.Close()
}
return m
}
// select implements SelectSettings algorithm.
// Reference: https://w3c.github.io/mediacapture-main/#dfn-selectsettings
func selectBestDriver(filter driver.FilterFn, constraints prop.Media) (driver.Driver, prop.Media, error) {
var bestDriver driver.Driver
var bestProp prop.Media
minFitnessDist := math.Inf(1)
driverProperties := queryDriverProperties(filter)
for d, props := range driverProperties {
priority := float64(d.Info().Priority)
for _, p := range props {
fitnessDist := constraints.FitnessDistance(p) - priority
if fitnessDist < minFitnessDist {
minFitnessDist = fitnessDist
bestDriver = d
bestProp = p
}
}
}
if bestDriver == nil {
return nil, prop.Media{}, errNotFound
}
constraints.Merge(bestProp)
return bestDriver, constraints, nil
}
func selectAudio(constraints prop.Media) (Track, 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)
if err != nil {
return nil, err
}
return newAudioTrack(d, c)
}
func selectVideo(constraints prop.Media) (Track, error) {
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 {
return nil, err
}
return newVideoTrack(d, c)
}
func selectScreen(constraints prop.Media) (Track, error) {
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 {
return nil, err
}
return newVideoTrack(d, c)
}
func EnumerateDevices() []MediaDeviceInfo {
drivers := driver.GetManager().Query(
driver.FilterFn(func(driver.Driver) bool { return true }))
info := make([]MediaDeviceInfo, 0, len(drivers))
for _, d := range drivers {
var kind MediaDeviceType
switch {
case driver.FilterVideoRecorder()(d):
kind = VideoInput
case driver.FilterAudioRecorder()(d):
kind = AudioInput
default:
continue
}
driverInfo := d.Info()
info = append(info, MediaDeviceInfo{
DeviceID: d.ID(),
Kind: kind,
Label: driverInfo.Label,
DeviceType: driverInfo.DeviceType,
})
}
return info
}