mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-13 01:05:37 +02:00
This adds: - [x] Keyboard and mouse handling on the frontend - [x] Video and audio streaming from the backend to the frontend - [x] Input server that works with Websockets Update - 17/11 - [ ] Master docker container to run this - [ ] Steam runtime - [ ] Entrypoint.sh --------- Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com> Co-authored-by: Kristian Ollikainen <DatCaptainHorse@users.noreply.github.com>
180 lines
4.9 KiB
Go
180 lines
4.9 KiB
Go
package relay
|
|
|
|
import (
|
|
"github.com/google/uuid"
|
|
"github.com/pion/webrtc/v4"
|
|
"log"
|
|
"sync"
|
|
)
|
|
|
|
var Rooms = make(map[uuid.UUID]*Room) //< Room ID -> Room
|
|
var RoomsMutex = sync.RWMutex{}
|
|
|
|
func GetRoomByID(id uuid.UUID) *Room {
|
|
RoomsMutex.RLock()
|
|
defer RoomsMutex.RUnlock()
|
|
if room, ok := Rooms[id]; ok {
|
|
return room
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetRoomByName(name string) *Room {
|
|
RoomsMutex.RLock()
|
|
defer RoomsMutex.RUnlock()
|
|
for _, room := range Rooms {
|
|
if room.Name == name {
|
|
return room
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetOrCreateRoom(name string) *Room {
|
|
if room := GetRoomByName(name); room != nil {
|
|
return room
|
|
}
|
|
RoomsMutex.Lock()
|
|
room := NewRoom(name)
|
|
Rooms[room.ID] = room
|
|
if GetFlags().Verbose {
|
|
log.Printf("New room: '%s'\n", room.Name)
|
|
}
|
|
RoomsMutex.Unlock()
|
|
return room
|
|
}
|
|
|
|
func DeleteRoomIfEmpty(room *Room) {
|
|
room.ParticipantsMutex.RLock()
|
|
defer room.ParticipantsMutex.RUnlock()
|
|
if !room.Online && len(room.Participants) <= 0 {
|
|
RoomsMutex.Lock()
|
|
delete(Rooms, room.ID)
|
|
RoomsMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
type Room struct {
|
|
ID uuid.UUID //< Internal IDs are useful to keeping unique internal track
|
|
Name string
|
|
Online bool //< Whether the room is currently online, i.e. receiving data from a nestri-server
|
|
WebSocket *SafeWebSocket
|
|
PeerConnection *webrtc.PeerConnection
|
|
AudioTrack webrtc.TrackLocal
|
|
VideoTrack webrtc.TrackLocal
|
|
DataChannel *NestriDataChannel
|
|
Participants map[uuid.UUID]*Participant
|
|
ParticipantsMutex sync.RWMutex
|
|
}
|
|
|
|
func NewRoom(name string) *Room {
|
|
return &Room{
|
|
ID: uuid.New(),
|
|
Name: name,
|
|
Online: false,
|
|
Participants: make(map[uuid.UUID]*Participant),
|
|
}
|
|
}
|
|
|
|
// Assigns a WebSocket connection to a Room
|
|
func (r *Room) assignWebSocket(ws *SafeWebSocket) {
|
|
// If WS already assigned, warn
|
|
if r.WebSocket != nil {
|
|
log.Printf("Warning: Room '%s' already has a WebSocket assigned\n", r.Name)
|
|
}
|
|
r.WebSocket = ws
|
|
}
|
|
|
|
// Adds a Participant to a Room
|
|
func (r *Room) addParticipant(participant *Participant) {
|
|
r.ParticipantsMutex.Lock()
|
|
r.Participants[participant.ID] = participant
|
|
r.ParticipantsMutex.Unlock()
|
|
}
|
|
|
|
// Removes a Participant from a Room by participant's ID.
|
|
// If Room is offline and this is the last participant, the room is deleted
|
|
func (r *Room) removeParticipantByID(pID uuid.UUID) {
|
|
r.ParticipantsMutex.Lock()
|
|
delete(r.Participants, pID)
|
|
r.ParticipantsMutex.Unlock()
|
|
DeleteRoomIfEmpty(r)
|
|
}
|
|
|
|
// Removes a Participant from a Room by participant's name.
|
|
// If Room is offline and this is the last participant, the room is deleted
|
|
func (r *Room) removeParticipantByName(pName string) {
|
|
r.ParticipantsMutex.Lock()
|
|
for id, p := range r.Participants {
|
|
if p.Name == pName {
|
|
delete(r.Participants, id)
|
|
break
|
|
}
|
|
}
|
|
r.ParticipantsMutex.Unlock()
|
|
DeleteRoomIfEmpty(r)
|
|
}
|
|
|
|
// Signals all participants with offer and add tracks to their PeerConnections
|
|
func (r *Room) signalParticipantsWithTracks() {
|
|
r.ParticipantsMutex.RLock()
|
|
for _, participant := range r.Participants {
|
|
// Add tracks to participant's PeerConnection
|
|
if r.AudioTrack != nil {
|
|
if err := participant.addTrack(&r.AudioTrack); err != nil {
|
|
log.Printf("Failed to add audio track to participant: '%s' - reason: %s\n", participant.ID, err)
|
|
}
|
|
}
|
|
if r.VideoTrack != nil {
|
|
if err := participant.addTrack(&r.VideoTrack); err != nil {
|
|
log.Printf("Failed to add video track to participant: '%s' - reason: %s\n", participant.ID, err)
|
|
}
|
|
}
|
|
// Signal participant with offer
|
|
if err := participant.signalOffer(); err != nil {
|
|
log.Printf("Error signaling participant: %v\n", err)
|
|
}
|
|
}
|
|
r.ParticipantsMutex.RUnlock()
|
|
}
|
|
|
|
// Signals all participants that the Room is offline
|
|
func (r *Room) signalParticipantsOffline() {
|
|
r.ParticipantsMutex.RLock()
|
|
for _, participant := range r.Participants {
|
|
if err := participant.WebSocket.SendAnswerMessageWS(AnswerOffline); err != nil {
|
|
log.Printf("Failed to send Offline answer for participant: '%s' - reason: %s\n", participant.ID, err)
|
|
}
|
|
}
|
|
r.ParticipantsMutex.RUnlock()
|
|
}
|
|
|
|
// Broadcasts a message to Room's Participant's - excluding one given ID of
|
|
func (r *Room) broadcastMessage(msg webrtc.DataChannelMessage, excludeID uuid.UUID) {
|
|
r.ParticipantsMutex.RLock()
|
|
for d, participant := range r.Participants {
|
|
if participant.DataChannel != nil {
|
|
if d != excludeID { // Don't send back to the sender
|
|
if err := participant.DataChannel.SendText(string(msg.Data)); err != nil {
|
|
log.Printf("Error broadcasting to %s: %v\n", participant.Name, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if r.DataChannel != nil {
|
|
if err := r.DataChannel.SendText(string(msg.Data)); err != nil {
|
|
log.Printf("Error broadcasting to Room: %v\n", err)
|
|
}
|
|
}
|
|
r.ParticipantsMutex.RUnlock()
|
|
}
|
|
|
|
// Sends message to Room (nestri-server)
|
|
func (r *Room) sendToRoom(msg webrtc.DataChannelMessage) {
|
|
if r.DataChannel != nil {
|
|
if err := r.DataChannel.SendText(string(msg.Data)); err != nil {
|
|
log.Printf("Error broadcasting to Room: %v\n", err)
|
|
}
|
|
}
|
|
}
|