package relay import ( "encoding/json" "github.com/pion/webrtc/v4" "google.golang.org/protobuf/proto" "log" gen "relay/internal/proto" ) func participantHandler(participant *Participant, room *Room) { // Callback for closing PeerConnection onPCClose := func() { if GetFlags().Verbose { log.Printf("Closed PeerConnection for participant: '%s'\n", participant.ID) } room.removeParticipantByID(participant.ID) } var err error participant.PeerConnection, err = CreatePeerConnection(onPCClose) if err != nil { log.Printf("Failed to create PeerConnection for participant: '%s' - reason: %s\n", participant.ID, err) return } // Data channel settings settingOrdered := true settingMaxRetransmits := uint16(0) dc, err := participant.PeerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ Ordered: &settingOrdered, MaxRetransmits: &settingMaxRetransmits, }) if err != nil { log.Printf("Failed to create data channel for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } participant.DataChannel = NewNestriDataChannel(dc) // Register channel opening handling participant.DataChannel.RegisterOnOpen(func() { if GetFlags().Verbose { log.Printf("DataChannel open for participant: %s\n", participant.ID) } }) // Register channel closing handling participant.DataChannel.RegisterOnClose(func() { if GetFlags().Verbose { log.Printf("DataChannel closed for participant: %s\n", participant.ID) } }) // Register text message handling participant.DataChannel.RegisterMessageCallback("input", func(data []byte) { // Send to room if it has a DataChannel if room.DataChannel != nil { // If debug mode, decode and add our timestamp, otherwise just send to room if GetFlags().Debug { var inputMsg gen.ProtoMessageInput if err = proto.Unmarshal(data, &inputMsg); err != nil { log.Printf("Failed to decode input message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } protoLat := inputMsg.GetMessageBase().GetLatency() if protoLat != nil { lat := LatencyTrackerFromProto(protoLat) lat.AddTimestamp("relay_to_node") protoLat = lat.ToProto() } // Marshal and send if data, err = proto.Marshal(&inputMsg); err != nil { log.Printf("Failed to marshal input message for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } } if err = room.DataChannel.SendBinary(data); err != nil { log.Printf("Failed to send input message to room: '%s' - reason: %s\n", room.Name, err) } } }) participant.PeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) { if candidate == nil { return } if GetFlags().Verbose { log.Printf("ICE candidate for participant: '%s' in room: '%s'\n", participant.ID, room.Name) } err = participant.WebSocket.SendICECandidateMessageWS(candidate.ToJSON()) if err != nil { log.Printf("Failed to send ICE candidate for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } }) iceHolder := make([]webrtc.ICECandidateInit, 0) // ICE callback participant.WebSocket.RegisterMessageCallback("ice", func(data []byte) { var iceMsg MessageICECandidate if err = json.Unmarshal(data, &iceMsg); err != nil { log.Printf("Failed to decode ICE message from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } candidate := webrtc.ICECandidateInit{ Candidate: iceMsg.Candidate.Candidate, } if participant.PeerConnection.RemoteDescription() != nil { if err = participant.PeerConnection.AddICECandidate(candidate); err != nil { log.Printf("Failed to add ICE candidate from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } // Add held ICE candidates for _, heldCandidate := range iceHolder { if err = participant.PeerConnection.AddICECandidate(heldCandidate); err != nil { log.Printf("Failed to add held ICE candidate from participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } } iceHolder = nil } else { iceHolder = append(iceHolder, candidate) } }) // SDP answer callback participant.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 participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } handleParticipantSDP(participant, sdpMsg) }) // Log callback participant.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 participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) return } // TODO: Handle log message sending to metrics server }) // Metrics callback participant.WebSocket.RegisterMessageCallback("metrics", func(data []byte) { // Ignore for now }) participant.WebSocket.RegisterOnClose(func() { if GetFlags().Verbose { log.Printf("WebSocket closed for participant: '%s' in room: '%s'\n", participant.ID, room.Name) } // Remove from Room room.removeParticipantByID(participant.ID) }) log.Printf("Participant: '%s' in room: '%s' is now ready, sending an OK\n", participant.ID, room.Name) if err = participant.WebSocket.SendAnswerMessageWS(AnswerOK); err != nil { log.Printf("Failed to send OK answer for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } // If room is already online, send also offer if room.Online { if room.AudioTrack != nil { if err = participant.addTrack(&room.AudioTrack); err != nil { log.Printf("Failed to add audio track for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } } if room.VideoTrack != nil { if err = participant.addTrack(&room.VideoTrack); err != nil { log.Printf("Failed to add video track for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } } if err = participant.signalOffer(); err != nil { log.Printf("Failed to signal offer for participant: '%s' in room: '%s' - reason: %s\n", participant.ID, room.Name, err) } } } // SDP answer handler for participants func handleParticipantSDP(participant *Participant, answerMsg MessageSDP) { // Get SDP offer sdpAnswer := answerMsg.SDP.SDP // Set remote description err := participant.PeerConnection.SetRemoteDescription(webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP: sdpAnswer, }) if err != nil { log.Printf("Failed to set remote description for participant: '%s' - reason: %s\n", participant.ID, err) } }