mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
140 lines
3.6 KiB
Go
140 lines
3.6 KiB
Go
package shared
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"relay/internal/common"
|
|
"relay/internal/connections"
|
|
"sync"
|
|
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/oklog/ulid/v2"
|
|
"github.com/pion/webrtc/v4"
|
|
)
|
|
|
|
type Participant struct {
|
|
ID ulid.ULID
|
|
SessionID string // Track session for reconnection
|
|
PeerID peer.ID // libp2p peer ID
|
|
PeerConnection *webrtc.PeerConnection
|
|
DataChannel *connections.NestriDataChannel
|
|
|
|
// Per-viewer tracks and channels
|
|
VideoTrack *webrtc.TrackLocalStaticRTP
|
|
AudioTrack *webrtc.TrackLocalStaticRTP
|
|
|
|
// Per-viewer RTP state for retiming
|
|
VideoSequenceNumber uint16
|
|
VideoTimestamp uint32
|
|
AudioSequenceNumber uint16
|
|
AudioTimestamp uint32
|
|
|
|
packetQueue chan *participantPacket
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
func NewParticipant(sessionID string, peerID peer.ID) (*Participant, error) {
|
|
id, err := common.NewULID()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create ULID for Participant: %w", err)
|
|
}
|
|
p := &Participant{
|
|
ID: id,
|
|
SessionID: sessionID,
|
|
PeerID: peerID,
|
|
VideoSequenceNumber: 0,
|
|
VideoTimestamp: 0,
|
|
AudioSequenceNumber: 0,
|
|
AudioTimestamp: 0,
|
|
packetQueue: make(chan *participantPacket, 1000),
|
|
}
|
|
|
|
go p.packetWriter()
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// SetTrack sets audio/video track for Participant
|
|
func (p *Participant) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalStaticRTP) {
|
|
switch trackType {
|
|
case webrtc.RTPCodecTypeAudio:
|
|
p.AudioTrack = track
|
|
_, err := p.PeerConnection.AddTrack(track)
|
|
if err != nil {
|
|
slog.Error("Failed to add audio track", "participant", p.ID, "err", err)
|
|
}
|
|
case webrtc.RTPCodecTypeVideo:
|
|
p.VideoTrack = track
|
|
_, err := p.PeerConnection.AddTrack(track)
|
|
if err != nil {
|
|
slog.Error("Failed to add video track", "participant", p.ID, "err", err)
|
|
}
|
|
default:
|
|
slog.Warn("Unknown track type", "participant", p.ID, "trackType", trackType)
|
|
}
|
|
}
|
|
|
|
// Close cleans up participant resources
|
|
func (p *Participant) Close() {
|
|
p.closeOnce.Do(func() {
|
|
close(p.packetQueue)
|
|
})
|
|
if p.DataChannel != nil {
|
|
err := p.DataChannel.Close()
|
|
if err != nil {
|
|
slog.Error("Failed to close DataChannel", "participant", p.ID, "err", err)
|
|
}
|
|
p.DataChannel = nil
|
|
}
|
|
if p.PeerConnection != nil {
|
|
err := p.PeerConnection.Close()
|
|
if err != nil {
|
|
slog.Error("Failed to close PeerConnection", "participant", p.ID, "err", err)
|
|
}
|
|
p.PeerConnection = nil
|
|
}
|
|
if p.VideoTrack != nil {
|
|
p.VideoTrack = nil
|
|
}
|
|
if p.AudioTrack != nil {
|
|
p.AudioTrack = nil
|
|
}
|
|
}
|
|
|
|
func (p *Participant) packetWriter() {
|
|
for pkt := range p.packetQueue {
|
|
var track *webrtc.TrackLocalStaticRTP
|
|
var sequenceNumber uint16
|
|
var timestamp uint32
|
|
|
|
// No mutex needed - only this goroutine modifies these
|
|
if pkt.kind == webrtc.RTPCodecTypeAudio {
|
|
track = p.AudioTrack
|
|
p.AudioSequenceNumber = uint16(int(p.AudioSequenceNumber) + pkt.sequenceDiff)
|
|
p.AudioTimestamp = uint32(int64(p.AudioTimestamp) + pkt.timeDiff)
|
|
sequenceNumber = p.AudioSequenceNumber
|
|
timestamp = p.AudioTimestamp
|
|
} else {
|
|
track = p.VideoTrack
|
|
p.VideoSequenceNumber = uint16(int(p.VideoSequenceNumber) + pkt.sequenceDiff)
|
|
p.VideoTimestamp = uint32(int64(p.VideoTimestamp) + pkt.timeDiff)
|
|
sequenceNumber = p.VideoSequenceNumber
|
|
timestamp = p.VideoTimestamp
|
|
}
|
|
|
|
if track != nil {
|
|
pkt.packet.SequenceNumber = sequenceNumber
|
|
pkt.packet.Timestamp = timestamp
|
|
|
|
if err := track.WriteRTP(pkt.packet); err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
slog.Error("WriteRTP failed", "participant", p.ID, "kind", pkt.kind, "err", err)
|
|
}
|
|
}
|
|
|
|
// Return packet struct to pool
|
|
participantPacketPool.Put(pkt)
|
|
}
|
|
}
|