mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-05 08:36:55 +08:00
162 lines
4.7 KiB
Go
162 lines
4.7 KiB
Go
package mediadevices
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/pion/mediadevices/pkg/driver"
|
|
"github.com/pion/mediadevices/pkg/prop"
|
|
"github.com/pion/webrtc/v2"
|
|
)
|
|
|
|
// MediaDevices is an interface that's defined on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
|
|
type MediaDevices interface {
|
|
GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error)
|
|
}
|
|
|
|
// NewMediaDevices creates MediaDevices interface that provides access to connected media input devices
|
|
// like cameras and microphones, as well as screen sharing.
|
|
// In essence, it lets you obtain access to any hardware source of media data.
|
|
func NewMediaDevices(pc *webrtc.PeerConnection) MediaDevices {
|
|
codecs := make(map[webrtc.RTPCodecType][]*webrtc.RTPCodec)
|
|
for _, kind := range []webrtc.RTPCodecType{
|
|
webrtc.RTPCodecTypeAudio,
|
|
webrtc.RTPCodecTypeVideo,
|
|
} {
|
|
codecs[kind] = pc.GetRegisteredRTPCodecs(kind)
|
|
}
|
|
|
|
return &mediaDevices{codecs}
|
|
}
|
|
|
|
// NewMediaDevicesFromCodecs creates MediaDevices interface from lists of the available codecs
|
|
// that provides access to connected media input devices like cameras and microphones,
|
|
// as well as screen sharing.
|
|
// In essence, it lets you obtain access to any hardware source of media data.
|
|
func NewMediaDevicesFromCodecs(codecs map[webrtc.RTPCodecType][]*webrtc.RTPCodec) MediaDevices {
|
|
return &mediaDevices{codecs}
|
|
}
|
|
|
|
type mediaDevices struct {
|
|
codecs map[webrtc.RTPCodecType][]*webrtc.RTPCodec
|
|
}
|
|
|
|
// 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 (m *mediaDevices) GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) {
|
|
// TODO: It should return media stream based on constraints
|
|
trackers := make([]Tracker, 0)
|
|
|
|
var videoConstraints, audioConstraints MediaTrackConstraints
|
|
if constraints.Video != nil {
|
|
constraints.Video(&videoConstraints)
|
|
}
|
|
|
|
if constraints.Audio != nil {
|
|
constraints.Audio(&audioConstraints)
|
|
}
|
|
|
|
if videoConstraints.Enabled {
|
|
tracker, err := m.selectVideo(videoConstraints)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
trackers = append(trackers, tracker)
|
|
}
|
|
|
|
if audioConstraints.Enabled {
|
|
tracker, err := m.selectAudio(audioConstraints)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
trackers = append(trackers, tracker)
|
|
}
|
|
|
|
s, err := NewMediaStream(trackers...)
|
|
if err != nil {
|
|
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 MediaTrackConstraints) (driver.Driver, MediaTrackConstraints, error) {
|
|
var bestDriver driver.Driver
|
|
var bestProp prop.Media
|
|
minFitnessDist := math.Inf(1)
|
|
|
|
driverProperties := queryDriverProperties(filter)
|
|
for d, props := range driverProperties {
|
|
for _, p := range props {
|
|
fitnessDist := constraints.Media.FitnessDistance(p)
|
|
if fitnessDist < minFitnessDist {
|
|
minFitnessDist = fitnessDist
|
|
bestDriver = d
|
|
bestProp = p
|
|
}
|
|
}
|
|
}
|
|
|
|
if bestDriver == nil {
|
|
return nil, MediaTrackConstraints{}, fmt.Errorf("failed to find the best driver that fits the constraints")
|
|
}
|
|
|
|
// Reset Codec because bestProp only contains either audio.Prop or video.Prop
|
|
bestProp.Codec = constraints.Codec
|
|
bestConstraint := MediaTrackConstraints{
|
|
Media: bestProp,
|
|
Enabled: true,
|
|
AudioTransform: constraints.AudioTransform,
|
|
VideoTransform: constraints.VideoTransform,
|
|
}
|
|
return bestDriver, bestConstraint, nil
|
|
}
|
|
|
|
func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker, error) {
|
|
d, c, err := selectBestDriver(driver.FilterAudioRecorder(), constraints)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newAudioTrack(m.codecs[webrtc.RTPCodecTypeAudio], d, c)
|
|
}
|
|
func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker, error) {
|
|
d, c, err := selectBestDriver(driver.FilterVideoRecorder(), constraints)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newVideoTrack(m.codecs[webrtc.RTPCodecTypeVideo], d, c)
|
|
}
|