Files
mediadevices/track.go
T
Atsushi Watanabe 2bac44aa8a Close drivers on stream initialization failure
In GetUserMedia and GetDisplayMedia, driver was left opened if
initialization failure. Since Trackers are not returned on error,
there was no way to close them.
This commit closes Trackers on GetUserMedia/GetDisplayMedia failure.
2020-02-19 20:21:21 -08:00

256 lines
4.8 KiB
Go

package mediadevices
import (
"fmt"
"io"
"math/rand"
"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"
)
// Tracker is an interface that represent MediaStreamTrack
// Reference: https://w3c.github.io/mediacapture-main/#mediastreamtrack
type Tracker interface {
Track() *webrtc.Track
LocalTrack() LocalTrack
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
// immediately called.
OnEnded(func(error))
}
type LocalTrack interface {
WriteSample(s media.Sample) error
Codec() *webrtc.RTPCodec
ID() string
Kind() webrtc.RTPCodecType
}
type track struct {
t LocalTrack
s *sampler
onErrorHandler func(error)
err error
mu sync.Mutex
endOnce sync.Once
}
func newTrack(codecs []*webrtc.RTPCodec, trackGenerator TrackGenerator, d driver.Driver, codecName string) (*track, error) {
var selectedCodec *webrtc.RTPCodec
for _, c := range codecs {
if c.Name == codecName {
selectedCodec = c
break
}
}
if selectedCodec == nil {
return nil, fmt.Errorf("track: %s is not registered in media engine", codecName)
}
t, err := trackGenerator(
selectedCodec.PayloadType,
rand.Uint32(),
d.ID(),
selectedCodec.Type.String(),
selectedCodec,
)
if err != nil {
return nil, err
}
return &track{
t: t,
s: newSampler(t),
}, nil
}
func (t *track) OnEnded(handler func(error)) {
t.mu.Lock()
t.onErrorHandler = handler
err := t.err
t.mu.Unlock()
if err != nil && handler != nil {
// Already errored.
t.endOnce.Do(func() {
handler(err)
})
}
}
func (t *track) onError(err error) {
t.mu.Lock()
t.err = err
handler := t.onErrorHandler
t.mu.Unlock()
if handler != nil {
t.endOnce.Do(func() {
handler(err)
})
}
}
func (t *track) Track() *webrtc.Track {
return t.t.(*webrtc.Track)
}
func (t *track) LocalTrack() LocalTrack {
return t.t
}
type videoTrack struct {
*track
d driver.Driver
constraints MediaTrackConstraints
encoder io.ReadCloser
}
var _ Tracker = &videoTrack{}
func newVideoTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrackConstraints) (*videoTrack, error) {
codecName := constraints.CodecName
t, err := newTrack(opts.codecs[webrtc.RTPCodecTypeVideo], opts.trackGenerator, d, codecName)
if err != nil {
return nil, err
}
err = d.Open()
if err != nil {
return nil, err
}
vr := d.(driver.VideoRecorder)
r, err := vr.VideoRecord(constraints.Media)
if err != nil {
return nil, err
}
if constraints.VideoTransform != nil {
r = constraints.VideoTransform(r)
}
encoder, err := codec.BuildVideoEncoder(r, constraints.Media)
if err != nil {
_ = d.Close()
return nil, err
}
vt := videoTrack{
track: t,
d: d,
constraints: constraints,
encoder: encoder,
}
go vt.start()
return &vt, nil
}
func (vt *videoTrack) start() {
var n int
var err error
buff := make([]byte, 1024)
for {
n, err = vt.encoder.Read(buff)
if err != nil {
if e, ok := err.(*mio.InsufficientBufferError); ok {
buff = make([]byte, 2*e.RequiredSize)
continue
}
vt.track.onError(err)
return
}
if err := vt.s.sample(buff[:n]); err != nil {
vt.track.onError(err)
return
}
}
}
func (vt *videoTrack) Stop() {
vt.d.Close()
vt.encoder.Close()
}
type audioTrack struct {
*track
d driver.Driver
constraints MediaTrackConstraints
encoder io.ReadCloser
}
var _ Tracker = &audioTrack{}
func newAudioTrack(opts *MediaDevicesOptions, d driver.Driver, constraints MediaTrackConstraints) (*audioTrack, error) {
codecName := constraints.CodecName
t, err := newTrack(opts.codecs[webrtc.RTPCodecTypeAudio], opts.trackGenerator, d, codecName)
if err != nil {
return nil, err
}
err = d.Open()
if err != nil {
return nil, err
}
ar := d.(driver.AudioRecorder)
reader, err := ar.AudioRecord(constraints.Media)
if err != nil {
return nil, err
}
if constraints.AudioTransform != nil {
reader = constraints.AudioTransform(reader)
}
encoder, err := codec.BuildAudioEncoder(reader, constraints.Media)
if err != nil {
_ = d.Close()
return nil, err
}
at := audioTrack{
track: t,
d: d,
constraints: constraints,
encoder: encoder,
}
go at.start()
return &at, nil
}
func (t *audioTrack) start() {
buff := make([]byte, 1024)
sampleSize := uint32(float64(t.constraints.SampleRate) * t.constraints.Latency.Seconds())
for {
n, err := t.encoder.Read(buff)
if err != nil {
t.track.onError(err)
return
}
if err := t.t.WriteSample(media.Sample{
Data: buff[:n],
Samples: sampleSize,
}); err != nil {
t.track.onError(err)
return
}
}
}
func (t *audioTrack) Stop() {
t.d.Close()
t.encoder.Close()
}