Compare commits

...

5 Commits

Author SHA1 Message Date
Lukas Herman
031b9a95c6 Add experimental new webrtc extension 2020-05-13 22:51:38 -04:00
Lukas Herman
668ef32cd5 Merge branch 'refractor' of github.com:pion/mediadevices into refractor 2020-05-06 22:47:08 -04:00
Lukas Herman
7e739a814b WIP 2020-05-05 23:28:05 -04:00
Lukas Herman
22282bc1d7 Redesign GetUserMedia API
Resolves https://github.com/pion/mediadevices/issues/145

* Move encoder builders and transformers to MediaDevicesOption
* Remove Enable from constraints
* Implicitly disable a media when nil
2020-05-05 08:18:05 -04:00
Lukas Herman
d7ee554323 Make track to be more DRY 2020-04-18 13:36:14 -04:00
16 changed files with 627 additions and 621 deletions

View File

@@ -6,11 +6,10 @@ import (
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
@@ -56,22 +55,22 @@ func main() {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
md := mediadevices.NewMediaDevices(peerConnection)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
md := mediadevices.NewMediaDevices(
peerConnection,
mediadevices.WithVideoEncoders(&vp8Params),
mediadevices.WithVideoTransformers(markFacesTransformer),
)
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatI420 // most of the encoder accepts I420
c.Enabled = true
c.Width = 640
c.Height = 480
c.VideoTransform = markFacesTransformer
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
Video: func(p *prop.Media) {
p.Width = 640
p.Height = 480
},
})
if err != nil {

View File

@@ -6,10 +6,9 @@ import (
"os"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/rtp"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v2/pkg/media"
@@ -25,6 +24,12 @@ func main() {
return
}
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
md := mediadevices.NewMediaDevicesFromCodecs(
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
webrtc.RTPCodecTypeVideo: []*webrtc.RTPCodec{
@@ -38,21 +43,13 @@ func main() {
return newTrack(codec, id, os.Args[1]), nil
},
),
mediadevices.WithVideoEncoders(&vp8Params),
)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
_, err = md.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatYUY2
c.Enabled = true
c.Width = 640
c.Height = 480
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
Video: func(p *prop.Media) {
p.Width = 640
p.Height = 480
},
})
if err != nil {

View File

@@ -5,10 +5,13 @@ import (
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
_ "github.com/pion/mediadevices/pkg/driver/screen" // This is required to register screen capture adapter
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/codec/openh264"
// This is required to use VP8/VP9 video encoder
// _ "github.com/pion/mediadevices/pkg/driver/screen" // This is required to register screen capture adapter
_ "github.com/pion/mediadevices/pkg/driver/videotest" // This is required to register screen capture adapter
extwebrtc "github.com/pion/mediadevices/pkg/ext/webrtc"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
@@ -25,12 +28,16 @@ func main() {
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{}
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
openh264Encoder, err := openh264.NewParams()
if err != nil {
panic(err)
}
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
openh264Encoder.BitRate = 100000 // 100kbps
// Create a new RTCPeerConnection
mediaEngine := extwebrtc.MediaEngine{}
mediaEngine.AddEncoderBuilders(&openh264Encoder)
api := extwebrtc.NewAPI(extwebrtc.WithMediaEngine(mediaEngine))
peerConnection, err := api.NewPeerConnection(config)
if err != nil {
panic(err)
@@ -42,32 +49,15 @@ func main() {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
md := mediadevices.NewMediaDevices(peerConnection)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetDisplayMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.VideoTransform = video.Scale(-1, 360, nil) // Resize to 360p
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
s, err := mediadevices.GetDisplayMedia(mediadevices.MediaStreamConstraints{
Video: func(p *prop.Media) {},
})
if err != nil {
panic(err)
}
for _, tracker := range s.GetTracks() {
t := tracker.Track()
tracker.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n",
t.ID(), t.Label(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(t,
for _, track := range s.GetTracks() {
_, err = peerConnection.ExtAddTransceiverFromTrack(track,
webrtc.RtpTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
},

View File

@@ -2,130 +2,64 @@ package main
import (
"fmt"
"image/jpeg"
"io"
"log"
"mime/multipart"
"net/http"
"net/textproto"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/examples/internal/signal"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/webrtc/v2"
// This is required to use opus audio encoder
"github.com/pion/mediadevices/pkg/codec/opus"
// If you don't like vpx, you can also use x264 by importing as below
// "github.com/pion/mediadevices/pkg/codec/x264" // This is required to use h264 video encoder
// or you can also use openh264 for alternative h264 implementation
// "github.com/pion/mediadevices/pkg/codec/openh264"
"github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder
"github.com/pion/mediadevices/pkg/prop"
// Note: If you don't have a camera or microphone or your adapters are not supported,
// you can always swap your adapters with our dummy adapters below.
// _ "github.com/pion/mediadevices/pkg/driver/videotest"
// _ "github.com/pion/mediadevices/pkg/driver/audiotest"
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
_ "github.com/pion/mediadevices/pkg/driver/microphone" // This is required to register microphone adapter
)
const (
videoCodecName = webrtc.VP8
_ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter
)
func main() {
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Create a new RTCPeerConnection
mediaEngine := webrtc.MediaEngine{}
if err := mediaEngine.PopulateFromSDP(offer); err != nil {
panic(err)
}
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
peerConnection, err := api.NewPeerConnection(config)
if err != nil {
panic(err)
}
// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
md := mediadevices.NewMediaDevices(peerConnection)
opusParams, err := opus.NewParams()
if err != nil {
panic(err)
}
opusParams.BitRate = 32000 // 32kbps
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
Audio: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&opusParams}
},
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = frame.FormatYUY2
c.Enabled = true
c.Width = 640
c.Height = 480
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{
Video: func(p *prop.Media) {},
})
if err != nil {
panic(err)
}
for _, tracker := range s.GetTracks() {
t := tracker.Track()
tracker.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n",
t.ID(), t.Label(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(t,
webrtc.RtpTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
},
)
if err != nil {
panic(err)
t := s.GetVideoTracks()[0]
defer t.Stop()
videoTrack := t.(*mediadevices.VideoTrack)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
videoReader := videoTrack.NewReader()
mimeWriter := multipart.NewWriter(w)
contentType := fmt.Sprintf("multipart/x-mixed-replace;boundary=%s", mimeWriter.Boundary())
w.Header().Add("Content-Type", contentType)
partHeader := make(textproto.MIMEHeader)
partHeader.Add("Content-Type", "image/jpeg")
for {
frame, err := videoReader.Read()
if err != nil {
if err == io.EOF {
return
}
panic(err)
}
partWriter, err := mimeWriter.CreatePart(partHeader)
if err != nil {
panic(err)
}
err = jpeg.Encode(partWriter, frame, nil)
if err != nil {
panic(err)
}
}
}
})
// Set the remote SessionDescription
err = peerConnection.SetRemoteDescription(offer)
if err != nil {
panic(err)
}
// Create an answer
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
select {}
log.Println(http.ListenAndServe(":1313", nil))
}

View File

@@ -6,106 +6,37 @@ import (
"github.com/pion/mediadevices/pkg/driver"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
var errNotFound = fmt.Errorf("failed to find the best driver that fits the constraints")
// MediaDevices is an interface that's defined on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices
type MediaDevices interface {
GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error)
GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error)
EnumerateDevices() []MediaDeviceInfo
}
// 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, opts ...MediaDevicesOption) MediaDevices {
codecs := make(map[webrtc.RTPCodecType][]*webrtc.RTPCodec)
for _, kind := range []webrtc.RTPCodecType{
webrtc.RTPCodecTypeAudio,
webrtc.RTPCodecTypeVideo,
} {
codecs[kind] = pc.GetRegisteredRTPCodecs(kind)
}
return NewMediaDevicesFromCodecs(codecs, opts...)
}
// 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, opts ...MediaDevicesOption) MediaDevices {
mdo := MediaDevicesOptions{
codecs: codecs,
trackGenerator: defaultTrackGenerator,
}
for _, o := range opts {
o(&mdo)
}
return &mediaDevices{
MediaDevicesOptions: mdo,
}
}
// TrackGenerator is a function to create new track.
type TrackGenerator func(payloadType uint8, ssrc uint32, id, label string, codec *webrtc.RTPCodec) (LocalTrack, error)
var defaultTrackGenerator = TrackGenerator(func(pt uint8, ssrc uint32, id, label string, codec *webrtc.RTPCodec) (LocalTrack, error) {
return webrtc.NewTrack(pt, ssrc, id, label, codec)
})
type mediaDevices struct {
MediaDevicesOptions
}
// MediaDevicesOptions stores parameters used by MediaDevices.
type MediaDevicesOptions struct {
codecs map[webrtc.RTPCodecType][]*webrtc.RTPCodec
trackGenerator TrackGenerator
}
// MediaDevicesOption is a type of MediaDevices functional option.
type MediaDevicesOption func(*MediaDevicesOptions)
// WithTrackGenerator specifies a TrackGenerator to use customized track.
func WithTrackGenerator(gen TrackGenerator) MediaDevicesOption {
return func(o *MediaDevicesOptions) {
o.trackGenerator = gen
}
}
// 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 (m *mediaDevices) GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
trackers := make([]Tracker, 0)
func GetDisplayMedia(constraints MediaStreamConstraints) (MediaStream, error) {
tracks := make([]Track, 0)
cleanTrackers := func() {
for _, t := range trackers {
cleanTracks := func() {
for _, t := range tracks {
t.Stop()
}
}
var videoConstraints MediaTrackConstraints
if constraints.Video != nil {
constraints.Video(&videoConstraints)
}
if videoConstraints.Enabled {
tracker, err := m.selectScreen(videoConstraints)
var p prop.Media
constraints.Video(&p)
track, err := selectScreen(p)
if err != nil {
cleanTrackers()
cleanTracks()
return nil, err
}
trackers = append(trackers, tracker)
tracks = append(tracks, track)
}
s, err := NewMediaStream(trackers...)
s, err := NewMediaStream(tracks...)
if err != nil {
cleanTrackers()
cleanTracks()
return nil, err
}
@@ -115,48 +46,42 @@ func (m *mediaDevices) GetDisplayMedia(constraints MediaStreamConstraints) (Medi
// 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)
func GetUserMedia(constraints MediaStreamConstraints) (MediaStream, error) {
tracks := make([]Track, 0)
cleanTrackers := func() {
for _, t := range trackers {
cleanTracks := func() {
for _, t := range tracks {
t.Stop()
}
}
var videoConstraints, audioConstraints MediaTrackConstraints
if constraints.Video != nil {
constraints.Video(&videoConstraints)
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 {
constraints.Audio(&audioConstraints)
}
if videoConstraints.Enabled {
tracker, err := m.selectVideo(videoConstraints)
var p prop.Media
constraints.Audio(&p)
track, err := selectAudio(p)
if err != nil {
cleanTrackers()
cleanTracks()
return nil, err
}
trackers = append(trackers, tracker)
tracks = append(tracks, track)
}
if audioConstraints.Enabled {
tracker, err := m.selectAudio(audioConstraints)
if err != nil {
cleanTrackers()
return nil, err
}
trackers = append(trackers, tracker)
}
s, err := NewMediaStream(trackers...)
s, err := NewMediaStream(tracks...)
if err != nil {
cleanTrackers()
cleanTracks()
return nil, err
}
@@ -191,7 +116,7 @@ func queryDriverProperties(filter driver.FilterFn) map[driver.Driver][]prop.Medi
// select implements SelectSettings algorithm.
// Reference: https://w3c.github.io/mediacapture-main/#dfn-selectsettings
func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints) (driver.Driver, MediaTrackConstraints, error) {
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)
@@ -200,7 +125,7 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
for d, props := range driverProperties {
priority := float64(d.Info().Priority)
for _, p := range props {
fitnessDist := constraints.Media.FitnessDistance(p) - priority
fitnessDist := constraints.FitnessDistance(p) - priority
if fitnessDist < minFitnessDist {
minFitnessDist = fitnessDist
bestDriver = d
@@ -210,14 +135,14 @@ func selectBestDriver(filter driver.FilterFn, constraints MediaTrackConstraints)
}
if bestDriver == nil {
return nil, MediaTrackConstraints{}, errNotFound
return nil, prop.Media{}, errNotFound
}
constraints.Merge(bestProp)
return bestDriver, constraints, nil
}
func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker, error) {
func selectAudio(constraints prop.Media) (Track, error) {
typeFilter := driver.FilterAudioRecorder()
filter := typeFilter
if constraints.DeviceID != "" {
@@ -230,9 +155,10 @@ func (m *mediaDevices) selectAudio(constraints MediaTrackConstraints) (Tracker,
return nil, err
}
return newTrack(&m.MediaDevicesOptions, d, c)
return newAudioTrack(d, c)
}
func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker, error) {
func selectVideo(constraints prop.Media) (Track, error) {
typeFilter := driver.FilterVideoRecorder()
notScreenFilter := driver.FilterNot(driver.FilterDeviceType(driver.Screen))
filter := driver.FilterAnd(typeFilter, notScreenFilter)
@@ -246,10 +172,10 @@ func (m *mediaDevices) selectVideo(constraints MediaTrackConstraints) (Tracker,
return nil, err
}
return newTrack(&m.MediaDevicesOptions, d, c)
return newVideoTrack(d, c)
}
func (m *mediaDevices) selectScreen(constraints MediaTrackConstraints) (Tracker, error) {
func selectScreen(constraints prop.Media) (Track, error) {
typeFilter := driver.FilterVideoRecorder()
screenFilter := driver.FilterDeviceType(driver.Screen)
filter := driver.FilterAnd(typeFilter, screenFilter)
@@ -263,10 +189,10 @@ func (m *mediaDevices) selectScreen(constraints MediaTrackConstraints) (Tracker,
return nil, err
}
return newTrack(&m.MediaDevicesOptions, d, c)
return newVideoTrack(d, c)
}
func (m *mediaDevices) EnumerateDevices() []MediaDeviceInfo {
func EnumerateDevices() []MediaDeviceInfo {
drivers := driver.GetManager().Query(
driver.FilterFn(func(driver.Driver) bool { return true }))
info := make([]MediaDeviceInfo, 0, len(drivers))

View File

@@ -18,18 +18,25 @@ import (
)
func TestGetUserMedia(t *testing.T) {
videoParams := mockParams{
BaseParams: codec.BaseParams{
BitRate: 100000,
},
brokenVideoParams := mockParams{
name: "MockVideo",
}
videoParams := brokenVideoParams
videoParams.BitRate = 100000
audioParams := mockParams{
BaseParams: codec.BaseParams{
BitRate: 32000,
},
name: "MockAudio",
}
constraints := MediaStreamConstraints{
Video: func(p *prop.Media) {
p.Width = 640
p.Height = 480
},
Audio: func(p *prop.Media) {},
}
md := NewMediaDevicesFromCodecs(
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
webrtc.RTPCodecTypeVideo: []*webrtc.RTPCodec{
@@ -46,43 +53,36 @@ func TestGetUserMedia(t *testing.T) {
return newMockTrack(codec, id), nil
},
),
WithVideoEncoders(&brokenVideoParams),
WithAudioEncoders(&audioParams),
)
constraints := MediaStreamConstraints{
Video: func(c *MediaTrackConstraints) {
c.Enabled = true
c.Width = 640
c.Height = 480
params := videoParams
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
},
Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
},
}
constraintsWrong := MediaStreamConstraints{
Video: func(c *MediaTrackConstraints) {
c.Enabled = true
c.Width = 640
c.Height = 480
params := videoParams
params.BitRate = 0
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&params}
},
Audio: func(c *MediaTrackConstraints) {
c.Enabled = true
params := audioParams
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&params}
},
}
// GetUserMedia with broken parameters
ms, err := md.GetUserMedia(constraintsWrong)
ms, err := md.GetUserMedia(constraints)
if err == nil {
t.Fatal("Expected error, but got nil")
}
md = NewMediaDevicesFromCodecs(
map[webrtc.RTPCodecType][]*webrtc.RTPCodec{
webrtc.RTPCodecTypeVideo: []*webrtc.RTPCodec{
&webrtc.RTPCodec{Type: webrtc.RTPCodecTypeVideo, Name: "MockVideo", PayloadType: 1},
},
webrtc.RTPCodecTypeAudio: []*webrtc.RTPCodec{
&webrtc.RTPCodec{Type: webrtc.RTPCodecTypeAudio, Name: "MockAudio", PayloadType: 2},
},
},
WithTrackGenerator(
func(_ uint8, _ uint32, id, _ string, codec *webrtc.RTPCodec) (
LocalTrack, error,
) {
return newMockTrack(codec, id), nil
},
),
WithVideoEncoders(&videoParams),
WithAudioEncoders(&audioParams),
)
// GetUserMedia with correct parameters
ms, err = md.GetUserMedia(constraints)
if err != nil {

View File

@@ -9,82 +9,82 @@ import (
// MediaStream is an interface that represents a collection of existing tracks.
type MediaStream interface {
// GetAudioTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
GetAudioTracks() []Tracker
GetAudioTracks() []Track
// GetVideoTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
GetVideoTracks() []Tracker
GetVideoTracks() []Track
// GetTracks implements https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
GetTracks() []Tracker
GetTracks() []Track
// AddTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
AddTrack(t Tracker)
AddTrack(t Track)
// RemoveTrack implements https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
RemoveTrack(t Tracker)
RemoveTrack(t Track)
}
type mediaStream struct {
trackers map[string]Tracker
l sync.RWMutex
tracks map[string]Track
l sync.RWMutex
}
const rtpCodecTypeDefault webrtc.RTPCodecType = 0
// NewMediaStream creates a MediaStream interface that's defined in
// https://w3c.github.io/mediacapture-main/#dom-mediastream
func NewMediaStream(trackers ...Tracker) (MediaStream, error) {
m := mediaStream{trackers: make(map[string]Tracker)}
func NewMediaStream(tracks ...Track) (MediaStream, error) {
m := mediaStream{tracks: make(map[string]Track)}
for _, tracker := range trackers {
id := tracker.LocalTrack().ID()
if _, ok := m.trackers[id]; !ok {
m.trackers[id] = tracker
for _, track := range tracks {
id := track.ID()
if _, ok := m.tracks[id]; !ok {
m.tracks[id] = track
}
}
return &m, nil
}
func (m *mediaStream) GetAudioTracks() []Tracker {
return m.queryTracks(webrtc.RTPCodecTypeAudio)
func (m *mediaStream) GetAudioTracks() []Track {
return m.queryTracks(func(t Track) bool { return t.Kind() == TrackKindAudio })
}
func (m *mediaStream) GetVideoTracks() []Tracker {
return m.queryTracks(webrtc.RTPCodecTypeVideo)
func (m *mediaStream) GetVideoTracks() []Track {
return m.queryTracks(func(t Track) bool { return t.Kind() == TrackKindVideo })
}
func (m *mediaStream) GetTracks() []Tracker {
return m.queryTracks(rtpCodecTypeDefault)
func (m *mediaStream) GetTracks() []Track {
return m.queryTracks(func(t Track) bool { return true })
}
// queryTracks returns all tracks that are the same kind as t.
// If t is 0, which is the default, queryTracks will return all the tracks.
func (m *mediaStream) queryTracks(t webrtc.RTPCodecType) []Tracker {
func (m *mediaStream) queryTracks(filter func(track Track) bool) []Track {
m.l.RLock()
defer m.l.RUnlock()
result := make([]Tracker, 0)
for _, tracker := range m.trackers {
if tracker.LocalTrack().Kind() == t || t == rtpCodecTypeDefault {
result = append(result, tracker)
result := make([]Track, 0)
for _, track := range m.tracks {
if filter(track) {
result = append(result, track)
}
}
return result
}
func (m *mediaStream) AddTrack(t Tracker) {
func (m *mediaStream) AddTrack(t Track) {
m.l.Lock()
defer m.l.Unlock()
id := t.LocalTrack().ID()
if _, ok := m.trackers[id]; ok {
id := t.ID()
if _, ok := m.tracks[id]; ok {
return
}
m.trackers[id] = t
m.tracks[id] = t
}
func (m *mediaStream) RemoveTrack(t Tracker) {
func (m *mediaStream) RemoveTrack(t Track) {
m.l.Lock()
defer m.l.Unlock()
delete(m.trackers, t.LocalTrack().ID())
delete(m.tracks, t.ID())
}

View File

@@ -1,39 +1,13 @@
package mediadevices
import (
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type MediaStreamConstraints struct {
Audio MediaOption
Video MediaOption
Audio MediaTrackConstraints
Video MediaTrackConstraints
}
// MediaTrackConstraints represents https://w3c.github.io/mediacapture-main/#dom-mediatrackconstraints
type MediaTrackConstraints struct {
prop.Media
Enabled bool
// VideoEncoderBuilders are codec builders that are used for encoding the video
// and later being used for sending the appropriate RTP payload type.
//
// If one encoder builder fails to build the codec, the next builder will be used,
// repeating until a codec builds. If no builders build successfully, an error is returned.
VideoEncoderBuilders []codec.VideoEncoderBuilder
// AudioEncoderBuilders are codec builders that are used for encoding the audio
// and later being used for sending the appropriate RTP payload type.
//
// If one encoder builder fails to build the codec, the next builder will be used,
// repeating until a codec builds. If no builders build successfully, an error is returned.
AudioEncoderBuilders []codec.AudioEncoderBuilder
// VideoTransform will be used to transform the video that's coming from the driver.
// So, basically it'll look like following: driver -> VideoTransform -> codec
VideoTransform video.TransformFunc
// AudioTransform will be used to transform the audio that's coming from the driver.
// So, basically it'll look like following: driver -> AudioTransform -> code
AudioTransform audio.TransformFunc
}
type MediaOption func(*MediaTrackConstraints)
type MediaTrackConstraints func(*prop.Media)

77
pkg/codec/adapter.go Normal file
View File

@@ -0,0 +1,77 @@
package codec
import (
"math/rand"
mio "github.com/pion/mediadevices/pkg/io"
"github.com/pion/rtp"
"github.com/pion/webrtc/v2"
)
const (
defaultMTU = 1200
)
type rtpReadCloserImpl struct {
packetize func(payload []byte) []*rtp.Packet
encoder ReadCloser
buff []byte
unreadRTPPackets []*rtp.Packet
}
func NewRTPReadCloser(codec *webrtc.RTPCodec, reader ReadCloser, sample SamplerFunc) (RTPReadCloser, error) {
packetizer := rtp.NewPacketizer(
defaultMTU,
codec.PayloadType,
rand.Uint32(),
codec.Payloader,
rtp.NewRandomSequencer(),
codec.ClockRate,
)
return &rtpReadCloserImpl{
packetize: func(payload []byte) []*rtp.Packet {
return packetizer.Packetize(payload, sample())
},
}, nil
}
func (rc *rtpReadCloserImpl) ReadRTP() (packet *rtp.Packet, err error) {
var n int
packet = rc.readRTPPacket()
if packet != nil {
return
}
for {
n, err = rc.encoder.Read(rc.buff)
if err == nil {
break
}
e, ok := err.(*mio.InsufficientBufferError)
if !ok {
return nil, err
}
rc.buff = make([]byte, 2*e.RequiredSize)
}
rc.unreadRTPPackets = rc.packetize(rc.buff[:n])
return rc.readRTPPacket(), nil
}
// readRTPPacket reads unreadRTPPackets and mark the rtp packet as "read",
// which essentially removes it from the list. If the return value is nil,
// it means that there's no unread rtp packets.
func (rc *rtpReadCloserImpl) readRTPPacket() (packet *rtp.Packet) {
if len(rc.unreadRTPPackets) == 0 {
return
}
packet, rc.unreadRTPPackets = rc.unreadRTPPackets[0], rc.unreadRTPPackets[1:]
return
}
func (rc *rtpReadCloserImpl) Close() {
rc.encoder.Close()
}

View File

@@ -3,33 +3,23 @@ package codec
import (
"io"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices"
"github.com/pion/rtp"
"github.com/pion/webrtc/v2"
)
// AudioEncoderBuilder is the interface that wraps basic operations that are
// necessary to build the audio encoder.
//
// This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users.
type AudioEncoderBuilder interface {
// Name represents the codec name
Name() string
// BuildAudioEncoder builds audio encoder by given media params and audio input
BuildAudioEncoder(r audio.Reader, p prop.Media) (ReadCloser, error)
type RTPReader interface {
ReadRTP() (*rtp.Packet, error)
}
// VideoEncoderBuilder is the interface that wraps basic operations that are
// necessary to build the video encoder.
//
// This interface is for codec implementors to provide codec specific params,
// but still giving generality for the users.
type VideoEncoderBuilder interface {
// Name represents the codec name
Name() string
// BuildVideoEncoder builds video encoder by given media params and video input
BuildVideoEncoder(r video.Reader, p prop.Media) (ReadCloser, error)
type RTPReadCloser interface {
RTPReader
Close()
}
type EncoderBuilder interface {
Codec() *webrtc.RTPCodec
BuildEncoder(mediadevices.Track) (RTPReadCloser, error)
}
// ReadCloser is an io.ReadCloser with methods for rate limiting: SetBitRate and ForceKeyFrame

View File

@@ -15,10 +15,10 @@ import (
"sync"
"unsafe"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
mio "github.com/pion/mediadevices/pkg/io"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type encoder struct {
@@ -30,17 +30,19 @@ type encoder struct {
closed bool
}
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
func newEncoder(track *mediadevices.VideoTrack, params Params) (codec.ReadCloser, error) {
if params.BitRate == 0 {
params.BitRate = 100000
}
constraints := track.GetConstraints()
var rv C.int
cEncoder := C.enc_new(C.EncoderOptions{
width: C.int(p.Width),
height: C.int(p.Height),
width: C.int(constraints.Width),
height: C.int(constraints.Height),
target_bitrate: C.int(params.BitRate),
max_fps: C.float(p.FrameRate),
max_fps: C.float(constraints.FrameRate),
}, &rv)
if err := errResult(rv); err != nil {
return nil, fmt.Errorf("failed in creating encoder: %v", err)

View File

@@ -1,9 +1,10 @@
package openh264
import (
"fmt"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
@@ -22,11 +23,25 @@ func NewParams() (Params, error) {
}
// Name represents the codec name
func (p *Params) Name() string {
return webrtc.H264
func (p *Params) Codec() *webrtc.RTPCodec {
return webrtc.NewRTPH264Codec(webrtc.DefaultPayloadTypeH264, 90000)
}
// BuildVideoEncoder builds openh264 encoder with given params
func (p *Params) BuildVideoEncoder(r video.Reader, property prop.Media) (codec.ReadCloser, error) {
return newEncoder(r, property, *p)
func (p *Params) BuildEncoder(track mediadevices.Track) (codec.RTPReadCloser, error) {
videoTrack, ok := track.(*mediadevices.VideoTrack)
if !ok {
return nil, fmt.Errorf("track is not a video track")
}
encoder, err := newEncoder(videoTrack, *p)
if err != nil {
return nil, err
}
return codec.NewRTPReadCloser(
p.Codec(),
encoder,
codec.NewVideoSampler(p.Codec().ClockRate),
)
}

35
pkg/codec/sampler.go Normal file
View File

@@ -0,0 +1,35 @@
package codec
import (
"math"
"time"
)
// SamplerFunc returns the number of samples. Each invocation may return different
// different amount of samples due to how it's calculated/measured.
type SamplerFunc func() uint32
// NewVideoSampler creates a video sampler that uses the actual video frame rate and
// the codec's clock rate to come up with a duration for each sample.
func NewVideoSampler(clockRate uint32) SamplerFunc {
clockRateFloat := float64(clockRate)
lastTimestamp := time.Now()
return SamplerFunc(func() uint32 {
now := time.Now()
duration := now.Sub(lastTimestamp).Seconds()
samples := uint32(math.Round(clockRateFloat * duration))
lastTimestamp = now
return samples
})
}
// NewAudioSampler creates a audio sampler that uses a fixed latency and
// the codec's clock rate to come up with a duration for each sample.
func NewAudioSampler(clockRate uint32, latency time.Duration) SamplerFunc {
samples := uint32(math.Round(float64(clockRate) * latency.Seconds()))
return SamplerFunc(func() uint32 {
return samples
})
}

117
pkg/ext/webrtc/webrtc.go Normal file
View File

@@ -0,0 +1,117 @@
package webrtc
import (
"fmt"
"math/rand"
"github.com/pion/mediadevices"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/webrtc/v2"
)
type Track interface {
mediadevices.Track
}
type LocalTrack interface {
codec.RTPReadCloser
}
type EncoderBuilder interface {
Codec() *webrtc.RTPCodec
BuildEncoder(Track) (LocalTrack, error)
}
type MediaEngine struct {
webrtc.MediaEngine
encoderBuilders []EncoderBuilder
}
func (engine *MediaEngine) AddEncoderBuilders(builders ...EncoderBuilder) {
engine.encoderBuilders = append(engine.encoderBuilders, builders...)
for _, builder := range builders {
engine.RegisterCodec(builder.Codec())
}
}
type API struct {
webrtc.API
mediaEngine MediaEngine
}
func NewAPI(options ...func(*API)) *API {
var api API
for _, option := range options {
option(&api)
}
return &api
}
func WithMediaEngine(m MediaEngine) func(*API) {
return func(a *API) {
a.mediaEngine = m
}
}
func (api *API) NewPeerConnection(configuration webrtc.Configuration) (*PeerConnection, error) {
pc, err := api.API.NewPeerConnection(configuration)
return &PeerConnection{
PeerConnection: pc,
api: api,
}, err
}
type PeerConnection struct {
webrtc.PeerConnection
api *API
}
func buildEncoder(encoderBuilders []EncoderBuilder, track Track) LocalTrack {
for _, encoderBuilder := range encoderBuilders {
encoder, err := encoderBuilder.BuildEncoder(track)
if err == nil {
return encoder
}
}
return nil
}
func (pc *PeerConnection) ExtAddTransceiverFromTrack(track Track, init ...webrtc.RtpTransceiverInit) (*webrtc.RTPTransceiver, error) {
encoder := buildEncoder(pc.api.mediaEngine.encoderBuilders, track)
if builder == nil {
return nil, fmt.Errorf("failed to find a compatible encoder")
}
trackImpl, err := pc.NewTrack(rtpCodec.PayloadType, rand.Uint32(), track.ID(), rtpCodec.Type.String())
if err != nil {
return nil, err
}
localTrack, err := builder.BuildEncoder(track)
if err != nil {
return nil, err
}
trans, err := pc.AddTransceiverFromTrack(trackImpl, init...)
if err != nil {
return nil, err
}
go func() {
for {
rtpPackets, err := localTrack.ReadRTP()
if err != nil {
return
}
for _, rtpPacket := range rtpPackets {
err = trackImpl.WriteRTP(rtpPacket)
if err != nil {
return
}
}
}
}()
return trans, nil
}

View File

@@ -1,35 +0,0 @@
package mediadevices
import (
"math"
"time"
"github.com/pion/webrtc/v2/pkg/media"
)
type samplerFunc func(b []byte) error
// newVideoSampler creates a video sampler that uses the actual video frame rate and
// the codec's clock rate to come up with a duration for each sample.
func newVideoSampler(t LocalTrack) samplerFunc {
clockRate := float64(t.Codec().ClockRate)
lastTimestamp := time.Now()
return samplerFunc(func(b []byte) error {
now := time.Now()
duration := now.Sub(lastTimestamp).Seconds()
samples := uint32(math.Round(clockRate * duration))
lastTimestamp = now
return t.WriteSample(media.Sample{Data: b, Samples: samples})
})
}
// newAudioSampler creates a audio sampler that uses a fixed latency and
// the codec's clock rate to come up with a duration for each sample.
func newAudioSampler(t LocalTrack, latency time.Duration) samplerFunc {
samples := uint32(math.Round(float64(t.Codec().ClockRate) * latency.Seconds()))
return samplerFunc(func(b []byte) error {
return t.WriteSample(media.Sample{Data: b, Samples: samples})
})
}

343
track.go
View File

@@ -2,21 +2,29 @@ package mediadevices
import (
"fmt"
"math/rand"
"image"
"sync"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/driver"
mio "github.com/pion/mediadevices/pkg/io"
"github.com/pion/webrtc/v2"
"github.com/pion/webrtc/v2/pkg/media"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/mediadevices/pkg/wave"
)
// Tracker is an interface that represent MediaStreamTrack
type TrackKind string
const (
TrackKindVideo TrackKind = "video"
TrackKindAudio TrackKind = "audio"
)
// Track is an interface that represent MediaStreamTrack
// Reference: https://w3c.github.io/mediacapture-main/#mediastreamtrack
type Tracker interface {
Track() *webrtc.Track
LocalTrack() LocalTrack
type Track interface {
ID() string
GetConstraints() prop.Media
Kind() TrackKind
Stop()
// OnEnded registers a handler to receive an error from the media stream track.
// If the error is already occured before registering, the handler will be
@@ -24,18 +32,147 @@ type Tracker interface {
OnEnded(func(error))
}
type LocalTrack interface {
WriteSample(s media.Sample) error
Codec() *webrtc.RTPCodec
ID() string
Kind() webrtc.RTPCodecType
type VideoTrack struct {
baseTrack
src video.Reader
transformed video.Reader
mux sync.Mutex
frameCount int
lastFrame image.Image
lastErr error
}
type track struct {
localTrack LocalTrack
d driver.Driver
sample samplerFunc
encoder codec.ReadCloser
func newVideoTrack(d driver.Driver, constraints prop.Media) (*VideoTrack, error) {
err := d.Open()
if err != nil {
return nil, err
}
recorder, ok := d.(driver.VideoRecorder)
if !ok {
d.Close()
return nil, fmt.Errorf("driver is not an video recorder")
}
r, err := recorder.VideoRecord(constraints)
if err != nil {
d.Close()
return nil, err
}
return &VideoTrack{
baseTrack: baseTrack{d: d, constraints: constraints},
src: r,
transformed: r,
}, nil
}
func (track *VideoTrack) Kind() TrackKind {
return TrackKindVideo
}
func (track *VideoTrack) NewReader() video.Reader {
var curFrameCount int
return video.ReaderFunc(func() (img image.Image, err error) {
track.mux.Lock()
defer track.mux.Unlock()
if curFrameCount != track.frameCount {
img = copyFrame(img, track.lastFrame)
err = track.lastErr
} else {
img, err = track.transformed.Read()
track.lastFrame = img
track.lastErr = err
track.frameCount++
}
curFrameCount = track.frameCount
return
})
}
// TODO: implement copy in place
func copyFrame(dst, src image.Image) image.Image { return src }
func (track *VideoTrack) Transform(fns ...video.TransformFunc) {
track.mux.Lock()
defer track.mux.Unlock()
track.transformed = video.Merge(fns...)(track.src)
}
type AudioTrack struct {
baseTrack
src audio.Reader
transformed audio.Reader
mux sync.Mutex
chunkCount int
lastChunks wave.Audio
lastErr error
}
func newAudioTrack(d driver.Driver, constraints prop.Media) (*AudioTrack, error) {
err := d.Open()
if err != nil {
return nil, err
}
recorder, ok := d.(driver.AudioRecorder)
if !ok {
d.Close()
return nil, fmt.Errorf("driver is not an audio recorder")
}
r, err := recorder.AudioRecord(constraints)
if err != nil {
d.Close()
return nil, err
}
return &AudioTrack{
baseTrack: baseTrack{d: d, constraints: constraints},
src: r,
transformed: r,
}, nil
}
func (track *AudioTrack) Kind() TrackKind {
return TrackKindAudio
}
func (track *AudioTrack) NewReader() audio.Reader {
var currChunkCount int
return audio.ReaderFunc(func() (chunks wave.Audio, err error) {
track.mux.Lock()
defer track.mux.Unlock()
if currChunkCount != track.chunkCount {
chunks = copyChunks(chunks, track.lastChunks)
err = track.lastErr
} else {
chunks, err = track.transformed.Read()
track.lastChunks = chunks
track.lastErr = err
track.chunkCount++
}
currChunkCount = track.chunkCount
return
})
}
// TODO: implement copy in place
func copyChunks(dst, src wave.Audio) wave.Audio { return src }
func (track *AudioTrack) Transform(fns ...audio.TransformFunc) {
track.mux.Lock()
defer track.mux.Unlock()
track.transformed = audio.Merge(fns...)(track.src)
}
type baseTrack struct {
d driver.Driver
constraints prop.Media
onErrorHandler func(error)
err error
@@ -43,83 +180,17 @@ type track struct {
endOnce sync.Once
}
func newTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrackConstraints) (*track, error) {
var encoderBuilders []encoderBuilder
var rtpCodecs []*webrtc.RTPCodec
var buildSampler func(t LocalTrack) samplerFunc
var err error
func (t *baseTrack) ID() string {
return t.d.ID()
}
err = d.Open()
if err != nil {
return nil, err
}
switch r := d.(type) {
case driver.VideoRecorder:
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeVideo]
buildSampler = newVideoSampler
encoderBuilders, err = newVideoEncoderBuilders(r, constraints)
case driver.AudioRecorder:
rtpCodecs = opts.codecs[webrtc.RTPCodecTypeAudio]
buildSampler = func(t LocalTrack) samplerFunc {
return newAudioSampler(t, constraints.Latency)
}
encoderBuilders, err = newAudioEncoderBuilders(r, constraints)
default:
err = fmt.Errorf("newTrack: invalid driver type")
}
if err != nil {
d.Close()
return nil, err
}
for _, builder := range encoderBuilders {
var matchedRTPCodec *webrtc.RTPCodec
for _, rtpCodec := range rtpCodecs {
if rtpCodec.Name == builder.name {
matchedRTPCodec = rtpCodec
break
}
}
if matchedRTPCodec == nil {
continue
}
localTrack, err := opts.trackGenerator(
matchedRTPCodec.PayloadType,
rand.Uint32(),
d.ID(),
matchedRTPCodec.Type.String(),
matchedRTPCodec,
)
if err != nil {
continue
}
encoder, err := builder.build()
if err != nil {
continue
}
t := track{
localTrack: localTrack,
sample: buildSampler(localTrack),
d: d,
encoder: encoder,
}
go t.start()
return &t, nil
}
d.Close()
return nil, fmt.Errorf("newTrack: failed to find a matching codec")
func (t *baseTrack) GetConstraints() prop.Media {
return t.constraints
}
// OnEnded sets an error handler. When a track has been created and started, if an
// error occurs, handler will get called with the error given to the parameter.
func (t *track) OnEnded(handler func(error)) {
func (t *baseTrack) OnEnded(handler func(error)) {
t.mu.Lock()
t.onErrorHandler = handler
err := t.err
@@ -134,7 +205,7 @@ func (t *track) OnEnded(handler func(error)) {
}
// onError is a callback when an error occurs
func (t *track) onError(err error) {
func (t *baseTrack) onError(err error) {
t.mu.Lock()
t.err = err
handler := t.onErrorHandler
@@ -147,92 +218,6 @@ func (t *track) onError(err error) {
}
}
// start starts the data flow from the driver all the way to the localTrack
func (t *track) start() {
var n int
var err error
buff := make([]byte, 1024)
for {
n, err = t.encoder.Read(buff)
if err != nil {
if e, ok := err.(*mio.InsufficientBufferError); ok {
buff = make([]byte, 2*e.RequiredSize)
continue
}
t.onError(err)
return
}
if err := t.sample(buff[:n]); err != nil {
t.onError(err)
return
}
}
}
// Stop stops the underlying driver and encoder
func (t *track) Stop() {
func (t *baseTrack) Stop() {
t.d.Close()
t.encoder.Close()
}
func (t *track) Track() *webrtc.Track {
return t.localTrack.(*webrtc.Track)
}
func (t *track) LocalTrack() LocalTrack {
return t.localTrack
}
// encoderBuilder is a generic encoder builder that acts as a delegator for codec.VideoEncoderBuilder and
// codec.AudioEncoderBuilder. The idea of having a delegator is to reduce redundant codes that are being
// duplicated for managing video and audio.
type encoderBuilder struct {
name string
build func() (codec.ReadCloser, error)
}
// newVideoEncoderBuilders transforms video given by VideoRecorder with the video transformer that is passed through
// constraints and create a list of generic encoder builders
func newVideoEncoderBuilders(vr driver.VideoRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
r, err := vr.VideoRecord(constraints.Media)
if err != nil {
return nil, err
}
if constraints.VideoTransform != nil {
r = constraints.VideoTransform(r)
}
encoderBuilders := make([]encoderBuilder, len(constraints.VideoEncoderBuilders))
for i, b := range constraints.VideoEncoderBuilders {
encoderBuilders[i].name = b.Name()
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
return b.BuildVideoEncoder(r, constraints.Media)
}
}
return encoderBuilders, nil
}
// newAudioEncoderBuilders transforms audio given by AudioRecorder with the audio transformer that is passed through
// constraints and create a list of generic encoder builders
func newAudioEncoderBuilders(ar driver.AudioRecorder, constraints MediaTrackConstraints) ([]encoderBuilder, error) {
r, err := ar.AudioRecord(constraints.Media)
if err != nil {
return nil, err
}
if constraints.AudioTransform != nil {
r = constraints.AudioTransform(r)
}
encoderBuilders := make([]encoderBuilder, len(constraints.AudioEncoderBuilders))
for i, b := range constraints.AudioEncoderBuilders {
encoderBuilders[i].name = b.Name()
encoderBuilders[i].build = func() (codec.ReadCloser, error) {
return b.BuildAudioEncoder(r, constraints.Media)
}
}
return encoderBuilders, nil
}