mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-06 00:37:00 +08:00
182 lines
4.6 KiB
Go
182 lines
4.6 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
|
"github.com/pion/rtcp"
|
|
"github.com/pion/webrtc/v3"
|
|
"time"
|
|
)
|
|
|
|
type Conn struct {
|
|
streamer.Element
|
|
|
|
UserAgent string
|
|
Desc string
|
|
Mode streamer.Mode
|
|
|
|
pc *webrtc.PeerConnection
|
|
|
|
medias []*streamer.Media
|
|
tracks []*streamer.Track
|
|
|
|
receive int
|
|
send int
|
|
|
|
offer string
|
|
remote string
|
|
closed core.Waiter
|
|
}
|
|
|
|
func NewConn(pc *webrtc.PeerConnection) *Conn {
|
|
c := &Conn{pc: pc}
|
|
|
|
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
// last candidate will be empty
|
|
if candidate != nil {
|
|
c.Fire(candidate)
|
|
}
|
|
})
|
|
|
|
pc.OnDataChannel(func(channel *webrtc.DataChannel) {
|
|
c.Fire(channel)
|
|
})
|
|
|
|
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
|
if state != webrtc.ICEConnectionStateChecking {
|
|
return
|
|
}
|
|
pc.SCTP().Transport().ICETransport().OnSelectedCandidatePairChange(
|
|
func(pair *webrtc.ICECandidatePair) {
|
|
c.remote = pair.Remote.String()
|
|
},
|
|
)
|
|
})
|
|
|
|
pc.OnTrack(func(remote *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
|
|
track := c.getRecvTrack(remote)
|
|
if track == nil {
|
|
return // it's OK when we not need, for example, audio from producer
|
|
}
|
|
|
|
if c.Mode == streamer.ModePassiveProducer && remote.Kind() == webrtc.RTPCodecTypeVideo {
|
|
go func() {
|
|
pkts := []rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(remote.SSRC())}}
|
|
for range time.NewTicker(time.Second * 2).C {
|
|
if err := pc.WriteRTCP(pkts); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
for {
|
|
packet, _, err := remote.ReadRTP()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(packet.Payload) == 0 {
|
|
continue
|
|
}
|
|
c.receive += len(packet.Payload)
|
|
_ = track.WriteRTP(packet)
|
|
}
|
|
})
|
|
|
|
// OK connection:
|
|
// 15:01:46 ICE connection state changed: checking
|
|
// 15:01:46 peer connection state changed: connected
|
|
// 15:01:54 peer connection state changed: disconnected
|
|
// 15:02:20 peer connection state changed: failed
|
|
//
|
|
// Fail connection:
|
|
// 14:53:08 ICE connection state changed: checking
|
|
// 14:53:39 peer connection state changed: failed
|
|
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
|
c.Fire(state)
|
|
|
|
switch state {
|
|
case webrtc.PeerConnectionStateDisconnected, webrtc.PeerConnectionStateFailed, webrtc.PeerConnectionStateClosed:
|
|
// disconnect event comes earlier, than failed
|
|
// but it comes only for success connections
|
|
_ = c.Close()
|
|
}
|
|
})
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Conn) Close() error {
|
|
c.closed.Done()
|
|
return c.pc.Close()
|
|
}
|
|
|
|
func (c *Conn) AddCandidate(candidate string) error {
|
|
// pion uses only candidate value from json/object candidate struct
|
|
return c.pc.AddICECandidate(webrtc.ICECandidateInit{Candidate: candidate})
|
|
}
|
|
|
|
func (c *Conn) getRecvTrack(remote *webrtc.TrackRemote) *streamer.Track {
|
|
payloadType := uint8(remote.PayloadType())
|
|
|
|
switch c.Mode {
|
|
case streamer.ModePassiveConsumer:
|
|
// Situation:
|
|
// - Browser (passive consumer) connects to go2rtc for receiving AV from IP-camera
|
|
// - Video and audio tracks marked as local "sendonly"
|
|
// - Browser sends microphone remote track to go2rtc, this track marked as local "recvonly"
|
|
// - go2rtc should ReadRTP from this remote track and sends it to camera
|
|
for _, track := range c.tracks {
|
|
if track.Direction == streamer.DirectionRecvonly && track.Codec.PayloadType == payloadType {
|
|
return track
|
|
}
|
|
}
|
|
|
|
case streamer.ModeActiveProducer:
|
|
// Situation:
|
|
// - go2rtc (active producer) connects to remote server (ex. webtorrent) for receiving AV
|
|
// - remote server sends remote tracks, this tracks marked as remote "sendonly"
|
|
for _, track := range c.tracks {
|
|
if track.Direction == streamer.DirectionSendonly && track.Codec.PayloadType == payloadType {
|
|
return track
|
|
}
|
|
}
|
|
|
|
case streamer.ModePassiveProducer:
|
|
// Situation:
|
|
// - OBS Studio (passive producer) connects to go2rtc for send AV
|
|
// - OBS sends remote tracks, this tracks marked as remote "sendonly"
|
|
for i, media := range c.medias {
|
|
// check only tracks with same kind
|
|
if media.Kind != remote.Kind().String() {
|
|
continue
|
|
}
|
|
|
|
// check only incoming tracks (remote media "sendonly")
|
|
if media.Direction != streamer.DirectionSendonly {
|
|
continue
|
|
}
|
|
|
|
for _, codec := range media.Codecs {
|
|
if codec.PayloadType != payloadType {
|
|
continue
|
|
}
|
|
|
|
// leave only one codec in supported media list
|
|
if len(media.Codecs) > 1 {
|
|
c.medias[i].Codecs = []*streamer.Codec{codec}
|
|
}
|
|
|
|
// forward request to passive producer GetTrack
|
|
// will create NewTrack for sendonly media
|
|
return c.GetTrack(media, codec)
|
|
}
|
|
}
|
|
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
|
|
return nil
|
|
}
|