mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
## 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>
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)
|
|
}
|
|
}
|