2022-10-18 00:39:31 +13:00
|
|
|
package webrtc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/pion/rtcp"
|
|
|
|
"github.com/pion/webrtc/v3"
|
|
|
|
"github.com/pion/webrtc/v3/pkg/media"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
|
|
|
"github.com/demodesk/neko/pkg/types"
|
|
|
|
"github.com/demodesk/neko/pkg/types/codec"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Track struct {
|
2023-03-07 12:08:53 +13:00
|
|
|
logger zerolog.Logger
|
|
|
|
track *webrtc.TrackLocalStaticSample
|
|
|
|
|
|
|
|
rtcpCh chan []rtcp.Packet
|
|
|
|
sample chan types.Sample
|
2023-02-21 07:48:04 +13:00
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
videoAuto bool
|
|
|
|
videoAutoMu sync.RWMutex
|
2022-10-18 00:39:31 +13:00
|
|
|
|
2023-02-21 07:48:04 +13:00
|
|
|
paused bool
|
2022-10-18 00:39:31 +13:00
|
|
|
stream types.StreamSinkManager
|
|
|
|
streamMu sync.Mutex
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
bitrateChange func(int) (bool, error)
|
|
|
|
videoChange func(string) (bool, error)
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
type option func(*Track)
|
|
|
|
|
|
|
|
func WithVideoAuto(auto bool) option {
|
|
|
|
return func(t *Track) {
|
|
|
|
t.videoAuto = auto
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
func WithRtcpChan(rtcp chan []rtcp.Packet) option {
|
|
|
|
return func(t *Track) {
|
|
|
|
t.rtcpCh = rtcp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
func NewTrack(logger zerolog.Logger, codec codec.RTPCodec, connection *webrtc.PeerConnection, opts ...option) (*Track, error) {
|
2022-10-18 00:39:31 +13:00
|
|
|
id := codec.Type.String()
|
|
|
|
track, err := webrtc.NewTrackLocalStaticSample(codec.Capability, id, "stream")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
logger = logger.With().Str("id", id).Logger()
|
|
|
|
|
|
|
|
t := &Track{
|
|
|
|
logger: logger,
|
|
|
|
track: track,
|
2023-03-07 12:08:53 +13:00
|
|
|
rtcpCh: make(chan []rtcp.Packet),
|
|
|
|
sample: make(chan types.Sample),
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
for _, opt := range opts {
|
|
|
|
opt(t)
|
|
|
|
}
|
|
|
|
|
2022-10-18 00:39:31 +13:00
|
|
|
sender, err := connection.AddTrack(t.track)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go t.rtcpReader(sender)
|
2023-03-07 12:08:53 +13:00
|
|
|
go t.sampleReader()
|
2022-10-18 00:39:31 +13:00
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
func (t *Track) Shutdown() {
|
|
|
|
t.RemoveStream()
|
|
|
|
close(t.sample)
|
|
|
|
}
|
|
|
|
|
2022-10-18 00:39:31 +13:00
|
|
|
func (t *Track) rtcpReader(sender *webrtc.RTPSender) {
|
|
|
|
for {
|
2023-02-15 09:19:14 +13:00
|
|
|
packets, _, err := sender.ReadRTCP()
|
2022-10-18 00:39:31 +13:00
|
|
|
if err != nil {
|
2023-03-07 12:08:53 +13:00
|
|
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) {
|
|
|
|
t.logger.Debug().Msg("track rtcp reader closed")
|
2022-10-18 00:39:31 +13:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
t.logger.Warn().Err(err).Msg("failed to read track rtcp")
|
2022-10-18 00:39:31 +13:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
if t.rtcpCh != nil {
|
|
|
|
t.rtcpCh <- packets
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) sampleReader() {
|
|
|
|
for {
|
|
|
|
sample, ok := <-t.sample
|
|
|
|
if !ok {
|
|
|
|
t.logger.Debug().Msg("track sample reader closed")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err := t.track.WriteSample(media.Sample{
|
|
|
|
Data: sample.Data,
|
|
|
|
Duration: sample.Duration,
|
|
|
|
Timestamp: sample.Timestamp,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
|
|
t.logger.Warn().Err(err).Msg("failed to write sample to track")
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
func (t *Track) SetStream(stream types.StreamSinkManager) (bool, error) {
|
2022-10-18 00:39:31 +13:00
|
|
|
t.streamMu.Lock()
|
|
|
|
defer t.streamMu.Unlock()
|
|
|
|
|
2022-10-23 22:31:25 +13:00
|
|
|
// if we already listen to the stream, do nothing
|
|
|
|
if t.stream == stream {
|
2023-02-07 07:45:51 +13:00
|
|
|
return false, nil
|
2022-10-23 22:31:25 +13:00
|
|
|
}
|
|
|
|
|
2023-02-21 07:48:04 +13:00
|
|
|
// if paused, we switch the stream but don't add the listener
|
|
|
|
if t.paused {
|
|
|
|
t.stream = stream
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2022-10-18 00:39:31 +13:00
|
|
|
var err error
|
|
|
|
if t.stream != nil {
|
2023-03-07 12:08:53 +13:00
|
|
|
err = t.stream.MoveListenerTo(t, stream)
|
2022-10-18 00:39:31 +13:00
|
|
|
} else {
|
2023-03-07 12:08:53 +13:00
|
|
|
err = stream.AddListener(t)
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
2023-02-07 07:45:51 +13:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
t.stream = stream
|
|
|
|
return true, nil
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) RemoveStream() {
|
|
|
|
t.streamMu.Lock()
|
|
|
|
defer t.streamMu.Unlock()
|
|
|
|
|
2023-02-21 07:48:04 +13:00
|
|
|
// if there is no stream, or paused, do nothing
|
|
|
|
if t.stream == nil || t.paused {
|
|
|
|
return
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
2023-02-21 07:48:04 +13:00
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
err := t.stream.RemoveListener(t)
|
2023-02-21 07:48:04 +13:00
|
|
|
if err != nil {
|
|
|
|
t.logger.Warn().Err(err).Msg("failed to remove listener from stream")
|
|
|
|
}
|
|
|
|
|
|
|
|
t.stream = nil
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) SetPaused(paused bool) {
|
2023-02-21 07:48:04 +13:00
|
|
|
t.streamMu.Lock()
|
|
|
|
defer t.streamMu.Unlock()
|
|
|
|
|
|
|
|
// if there is no state change or no stream, do nothing
|
|
|
|
if t.paused == paused || t.stream == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
if paused {
|
2023-03-07 12:08:53 +13:00
|
|
|
err = t.stream.RemoveListener(t)
|
2023-02-21 07:48:04 +13:00
|
|
|
} else {
|
2023-03-07 12:08:53 +13:00
|
|
|
err = t.stream.AddListener(t)
|
2023-02-21 07:48:04 +13:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.logger.Warn().Err(err).Msg("failed to change listener state")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-18 00:39:31 +13:00
|
|
|
t.paused = paused
|
|
|
|
}
|
|
|
|
|
2023-03-07 12:08:53 +13:00
|
|
|
func (t *Track) WriteSample(sample types.Sample) {
|
|
|
|
t.sample <- sample
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
func (t *Track) SetBitrate(bitrate int) (bool, error) {
|
2022-10-26 07:25:00 +13:00
|
|
|
if t.bitrateChange == nil {
|
2023-02-07 07:45:51 +13:00
|
|
|
return false, fmt.Errorf("bitrate change not supported")
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2022-10-26 07:25:00 +13:00
|
|
|
return t.bitrateChange(bitrate)
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
|
|
|
|
2023-02-07 07:45:51 +13:00
|
|
|
func (t *Track) SetVideoID(videoID string) (bool, error) {
|
|
|
|
if t.videoChange == nil {
|
|
|
|
return false, fmt.Errorf("video change not supported")
|
|
|
|
}
|
|
|
|
|
|
|
|
return t.videoChange(videoID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) OnBitrateChange(f func(bitrate int) (bool, error)) {
|
2022-10-26 07:25:00 +13:00
|
|
|
t.bitrateChange = f
|
2022-10-18 00:39:31 +13:00
|
|
|
}
|
2023-02-07 07:45:51 +13:00
|
|
|
|
|
|
|
func (t *Track) OnVideoChange(f func(string) (bool, error)) {
|
|
|
|
t.videoChange = f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) SetVideoAuto(auto bool) {
|
|
|
|
t.videoAutoMu.Lock()
|
|
|
|
defer t.videoAutoMu.Unlock()
|
|
|
|
t.videoAuto = auto
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Track) VideoAuto() bool {
|
|
|
|
t.videoAutoMu.RLock()
|
|
|
|
defer t.videoAutoMu.RUnlock()
|
|
|
|
return t.videoAuto
|
|
|
|
}
|