mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
feat: Fully use protobuf, fix controller issues and cleanup (#305)
## Description ### First commit Restructured protobuf schemas to make them easier to use across languages, switched to using them in-place of JSON for signaling as well, so there's no 2 different message formats flying about. Few new message types to deal with clients and nestri-servers better (not final format, may see changes still). General cleanup of dead/unused code along some bug squashing and package updates. TODO for future commits: - [x] Fix additional controllers not doing inputs (possibly needs vimputti changes) - [x] ~~Restructure relay protocols code a bit, to reduce bloatiness of the currently single file for them, more code re-use.~~ - Gonna keep this PR somewhat manageable without poking more at relay.. - [x] ~~Try to fix issue where with multiple clients, static stream content causes video to freeze until there's some movement.~~ - Was caused by server tuned profile being `throughput-performance`, causing CPU latency to be too high. - [x] Ponder the orb ### Second + third commit Redid the controller polling handling and fixed multi-controller handling in vimputti and nestri code sides. Remove some dead relay code as well to clean up the protocol source file, we'll revisit the meshing functionality later. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added software rendering option and MangoHud runtime config; controller sessions now support reconnection and batched state updates with persistent session IDs. * **Bug Fixes** * Restored previously-filtered NES-like gamepads so they connect correctly. * **Chores** * Modernized dependencies and protobuf tooling, migrated to protobuf-based messaging and streaming, and removed obsolete CUDA build steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
32341574dc
commit
d87a0b35dd
@@ -1,44 +1,139 @@
|
||||
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() (*Participant, error) {
|
||||
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)
|
||||
}
|
||||
return &Participant{
|
||||
ID: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Participant) addTrack(trackLocal *webrtc.TrackLocalStaticRTP) error {
|
||||
rtpSender, err := p.PeerConnection.AddTrack(trackLocal)
|
||||
if err != nil {
|
||||
return err
|
||||
p := &Participant{
|
||||
ID: id,
|
||||
SessionID: sessionID,
|
||||
PeerID: peerID,
|
||||
VideoSequenceNumber: 0,
|
||||
VideoTimestamp: 0,
|
||||
AudioSequenceNumber: 0,
|
||||
AudioTimestamp: 0,
|
||||
packetQueue: make(chan *participantPacket, 1000),
|
||||
}
|
||||
|
||||
go func() {
|
||||
rtcpBuffer := make([]byte, 1400)
|
||||
for {
|
||||
if _, _, rtcpErr := rtpSender.Read(rtcpBuffer); rtcpErr != nil {
|
||||
break
|
||||
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 nil
|
||||
// Return packet struct to pool
|
||||
participantPacketPool.Put(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user