mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
Replace json protocol by protobuf generate protobuf files with `bun buf generate` or just `buf generate` - [x] Implement all datatypes with proto files - [x] Map to ts types or use the generated proto types directly with: - [x] web frontend - [x] relay - [x] runner - [ ] final performance test (to be done when CI builds new images) --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
257 lines
8.0 KiB
Go
257 lines
8.0 KiB
Go
package relay
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/pion/webrtc/v4"
|
|
"io"
|
|
"log"
|
|
"strings"
|
|
)
|
|
|
|
func ingestHandler(room *Room) {
|
|
// Callback for closing PeerConnection
|
|
onPCClose := func() {
|
|
if GetFlags().Verbose {
|
|
log.Printf("Closed PeerConnection for room: '%s'\n", room.Name)
|
|
}
|
|
room.Online = false
|
|
DeleteRoomIfEmpty(room)
|
|
}
|
|
|
|
var err error
|
|
room.PeerConnection, err = CreatePeerConnection(onPCClose)
|
|
if err != nil {
|
|
log.Printf("Failed to create PeerConnection for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
|
|
room.PeerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
|
var localTrack *webrtc.TrackLocalStaticRTP
|
|
if remoteTrack.Kind() == webrtc.RTPCodecTypeVideo {
|
|
if GetFlags().Verbose {
|
|
log.Printf("Received video track for room: '%s'\n", room.Name)
|
|
}
|
|
localTrack, err = webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", fmt.Sprint("nestri-", room.Name))
|
|
if err != nil {
|
|
log.Printf("Failed to create local video track for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
room.VideoTrack = localTrack
|
|
} else if remoteTrack.Kind() == webrtc.RTPCodecTypeAudio {
|
|
if GetFlags().Verbose {
|
|
log.Printf("Received audio track for room: '%s'\n", room.Name)
|
|
}
|
|
localTrack, err = webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "audio", fmt.Sprint("nestri-", room.Name))
|
|
if err != nil {
|
|
log.Printf("Failed to create local audio track for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
room.AudioTrack = localTrack
|
|
}
|
|
|
|
// If both audio and video tracks are set, set online state
|
|
if room.AudioTrack != nil && room.VideoTrack != nil {
|
|
room.Online = true
|
|
if GetFlags().Verbose {
|
|
log.Printf("Room online and receiving: '%s' - signaling participants\n", room.Name)
|
|
}
|
|
room.signalParticipantsWithTracks()
|
|
}
|
|
|
|
rtpBuffer := make([]byte, 1400)
|
|
for {
|
|
read, _, err := remoteTrack.Read(rtpBuffer)
|
|
if err != nil {
|
|
// EOF is expected when stopping room
|
|
if !errors.Is(err, io.EOF) {
|
|
log.Printf("RTP read error from room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
break
|
|
}
|
|
|
|
_, err = localTrack.Write(rtpBuffer[:read])
|
|
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
log.Printf("Failed to write RTP to local track for room: '%s' - reason: %s\n", room.Name, err)
|
|
break
|
|
}
|
|
}
|
|
|
|
if remoteTrack.Kind() == webrtc.RTPCodecTypeVideo {
|
|
room.VideoTrack = nil
|
|
} else if remoteTrack.Kind() == webrtc.RTPCodecTypeAudio {
|
|
room.AudioTrack = nil
|
|
}
|
|
|
|
if room.VideoTrack == nil && room.AudioTrack == nil {
|
|
room.Online = false
|
|
if GetFlags().Verbose {
|
|
log.Printf("Room offline and not receiving: '%s'\n", room.Name)
|
|
}
|
|
// Signal participants of room offline
|
|
room.signalParticipantsOffline()
|
|
DeleteRoomIfEmpty(room)
|
|
}
|
|
})
|
|
|
|
room.PeerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
|
|
room.DataChannel = NewNestriDataChannel(dc)
|
|
if GetFlags().Verbose {
|
|
log.Printf("New DataChannel for room: '%s' - '%s'\n", room.Name, room.DataChannel.Label())
|
|
}
|
|
|
|
// Register channel opening handling
|
|
room.DataChannel.RegisterOnOpen(func() {
|
|
if GetFlags().Verbose {
|
|
log.Printf("DataChannel for room: '%s' - '%s' open\n", room.Name, room.DataChannel.Label())
|
|
}
|
|
})
|
|
|
|
room.DataChannel.OnClose(func() {
|
|
if GetFlags().Verbose {
|
|
log.Printf("DataChannel for room: '%s' - '%s' closed\n", room.Name, room.DataChannel.Label())
|
|
}
|
|
})
|
|
|
|
// We do not handle any messages from ingest via DataChannel yet
|
|
})
|
|
|
|
room.PeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
if candidate == nil {
|
|
return
|
|
}
|
|
if GetFlags().Verbose {
|
|
log.Printf("ICE candidate for room: '%s'\n", room.Name)
|
|
}
|
|
err = room.WebSocket.SendICECandidateMessageWS(candidate.ToJSON())
|
|
if err != nil {
|
|
log.Printf("Failed to send ICE candidate for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
})
|
|
|
|
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
|
|
|
// ICE callback
|
|
room.WebSocket.RegisterMessageCallback("ice", func(data []byte) {
|
|
var iceMsg MessageICECandidate
|
|
if err = json.Unmarshal(data, &iceMsg); err != nil {
|
|
log.Printf("Failed to decode ICE candidate message from ingest for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
candidate := webrtc.ICECandidateInit{
|
|
Candidate: iceMsg.Candidate.Candidate,
|
|
}
|
|
if room.PeerConnection != nil {
|
|
// If remote isn't set yet, store ICE candidates
|
|
if room.PeerConnection.RemoteDescription() != nil {
|
|
if err = room.PeerConnection.AddICECandidate(candidate); err != nil {
|
|
log.Printf("Failed to add ICE candidate for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
// Add any held ICE candidates
|
|
for _, heldCandidate := range iceHolder {
|
|
if err = room.PeerConnection.AddICECandidate(heldCandidate); err != nil {
|
|
log.Printf("Failed to add held ICE candidate for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
}
|
|
iceHolder = nil
|
|
} else {
|
|
iceHolder = append(iceHolder, candidate)
|
|
}
|
|
} else {
|
|
log.Printf("ICE candidate received before PeerConnection for room: '%s'\n", room.Name)
|
|
}
|
|
})
|
|
|
|
// SDP offer callback
|
|
room.WebSocket.RegisterMessageCallback("sdp", func(data []byte) {
|
|
var sdpMsg MessageSDP
|
|
if err = json.Unmarshal(data, &sdpMsg); err != nil {
|
|
log.Printf("Failed to decode SDP message from ingest for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
answer := handleIngestSDP(room, sdpMsg)
|
|
if answer != nil {
|
|
if err = room.WebSocket.SendSDPMessageWS(*answer); err != nil {
|
|
log.Printf("Failed to send SDP answer to ingest for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
} else {
|
|
log.Printf("Failed to handle SDP message from ingest for room: '%s'\n", room.Name)
|
|
}
|
|
})
|
|
|
|
// Log callback
|
|
room.WebSocket.RegisterMessageCallback("log", func(data []byte) {
|
|
var logMsg MessageLog
|
|
if err = json.Unmarshal(data, &logMsg); err != nil {
|
|
log.Printf("Failed to decode log message from ingest for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
// TODO: Handle log message sending to metrics server
|
|
})
|
|
|
|
// Metrics callback
|
|
room.WebSocket.RegisterMessageCallback("metrics", func(data []byte) {
|
|
var metricsMsg MessageMetrics
|
|
if err = json.Unmarshal(data, &metricsMsg); err != nil {
|
|
log.Printf("Failed to decode metrics message from ingest for room: '%s' - reason: %s\n", room.Name, err)
|
|
return
|
|
}
|
|
// TODO: Handle metrics message sending to metrics server
|
|
})
|
|
|
|
room.WebSocket.RegisterOnClose(func() {
|
|
// If PeerConnection is still open, close it
|
|
if room.PeerConnection != nil {
|
|
if err = room.PeerConnection.Close(); err != nil {
|
|
log.Printf("Failed to close PeerConnection for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
room.PeerConnection = nil
|
|
}
|
|
room.Online = false
|
|
DeleteRoomIfEmpty(room)
|
|
})
|
|
|
|
log.Printf("Room: '%s' is ready, sending an OK\n", room.Name)
|
|
if err = room.WebSocket.SendAnswerMessageWS(AnswerOK); err != nil {
|
|
log.Printf("Failed to send OK answer for room: '%s' - reason: %s\n", room.Name, err)
|
|
}
|
|
}
|
|
|
|
// SDP offer handler, returns SDP answer
|
|
func handleIngestSDP(room *Room, offerMsg MessageSDP) *webrtc.SessionDescription {
|
|
var err error
|
|
|
|
// Get SDP offer
|
|
sdpOffer := offerMsg.SDP.SDP
|
|
|
|
// Modify SDP offer to remove opus "sprop-maxcapturerate=24000" (fixes opus bad quality issue, present in GStreamer)
|
|
sdpOffer = strings.Replace(sdpOffer, ";sprop-maxcapturerate=24000", "", -1)
|
|
|
|
// Set new remote description
|
|
err = room.PeerConnection.SetRemoteDescription(webrtc.SessionDescription{
|
|
Type: webrtc.SDPTypeOffer,
|
|
SDP: sdpOffer,
|
|
})
|
|
if err != nil {
|
|
log.Printf("Failed to set remote description for room: '%s' - reason: %s\n", room.Name, err)
|
|
return nil
|
|
}
|
|
|
|
// Create SDP answer
|
|
answer, err := room.PeerConnection.CreateAnswer(nil)
|
|
if err != nil {
|
|
log.Printf("Failed to create SDP answer for room: '%s' - reason: %s\n", room.Name, err)
|
|
return nil
|
|
}
|
|
|
|
// Set local description
|
|
err = room.PeerConnection.SetLocalDescription(answer)
|
|
if err != nil {
|
|
log.Printf("Failed to set local description for room: '%s' - reason: %s\n", room.Name, err)
|
|
return nil
|
|
}
|
|
|
|
return &answer
|
|
}
|