mirror of
https://github.com/livekit/livekit.git
synced 2026-05-30 16:04:33 +00:00
243 lines
5.7 KiB
Go
243 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/pion/webrtc/v3"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/livekit/livekit-server/pkg/config"
|
|
"github.com/livekit/livekit-server/pkg/logger"
|
|
"github.com/livekit/livekit-server/pkg/rtc"
|
|
"github.com/livekit/livekit-server/proto/livekit"
|
|
)
|
|
|
|
type RTCService struct {
|
|
manager *rtc.RoomManager
|
|
upgrader websocket.Upgrader
|
|
}
|
|
|
|
func NewRTCService(conf *config.Config, manager *rtc.RoomManager) *RTCService {
|
|
s := &RTCService{
|
|
manager: manager,
|
|
upgrader: websocket.Upgrader{},
|
|
}
|
|
|
|
if conf.Development {
|
|
s.upgrader.CheckOrigin = func(r *http.Request) bool {
|
|
// allow all in dev
|
|
return true
|
|
}
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
roomId := r.FormValue("room_id")
|
|
token := r.FormValue("token")
|
|
peerId := r.FormValue("peer_id")
|
|
|
|
logger.GetLogger().Infow("new client connected",
|
|
"roomId", roomId,
|
|
"peerId", peerId,
|
|
)
|
|
|
|
room := s.manager.GetRoom(roomId)
|
|
if room == nil {
|
|
writeJSONError(w, http.StatusNotFound, "room not found")
|
|
return
|
|
}
|
|
|
|
if room.Token != token {
|
|
writeJSONError(w, http.StatusUnauthorized, "invalid room token")
|
|
return
|
|
}
|
|
|
|
conn, err := s.upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
logger.GetLogger().Warnw("could not upgrade to WS",
|
|
"err", err,
|
|
)
|
|
}
|
|
|
|
signalConn := NewWSSignalConnection(conn, peerId)
|
|
var peer *rtc.WebRTCPeer
|
|
|
|
// read connection and wait for commands
|
|
|
|
// TODO: pass in context from WS, so termination of WS would disconnect RTC
|
|
//ctx := context.Background()
|
|
for {
|
|
req, err := signalConn.ReadRequest()
|
|
if err != nil {
|
|
logger.GetLogger().Errorw("error reading WS",
|
|
"err", err,
|
|
"peerId", peerId,
|
|
"roomId", roomId)
|
|
}
|
|
|
|
switch msg := req.Message.(type) {
|
|
case *livekit.SignalRequest_Offer:
|
|
peer, err = s.handleJoin(signalConn, room, msg.Offer.Sdp)
|
|
case *livekit.SignalRequest_Negotiate:
|
|
if peer == nil {
|
|
conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot negotiate before peer offer"))
|
|
return
|
|
}
|
|
err = s.handleNegotiate(signalConn, peer, msg.Negotiate)
|
|
if err != nil {
|
|
conn.WriteJSON(
|
|
jsonError(http.StatusInternalServerError, "could not handle negotiate", err.Error()))
|
|
return
|
|
}
|
|
case *livekit.SignalRequest_Trickle:
|
|
if peer != nil {
|
|
conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot trickle before peer offer"))
|
|
return
|
|
}
|
|
|
|
err = s.handleTrickle(peer, msg.Trickle)
|
|
if err != nil {
|
|
conn.WriteJSON(
|
|
jsonError(http.StatusInternalServerError, "could not handle trickle", err.Error()))
|
|
return
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (s *RTCService) handleJoin(sc SignalConnection, room *rtc.Room, sdp string) (*rtc.WebRTCPeer, error) {
|
|
peer, err := room.Join(sc.PeerId(), sdp)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not join room")
|
|
}
|
|
|
|
offer := webrtc.SessionDescription{
|
|
Type: webrtc.SDPTypeOffer,
|
|
SDP: sdp,
|
|
}
|
|
answer, err := peer.Answer(offer)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not answer offer")
|
|
}
|
|
|
|
// TODO: it might be better to return error instead of nil
|
|
peer.OnICECandidate = func(c *webrtc.ICECandidateInit) {
|
|
bytes, err := json.Marshal(c)
|
|
if err != nil {
|
|
logger.GetLogger().Errorf("could not marshal ice candidate: %v", err)
|
|
return
|
|
}
|
|
|
|
err = sc.WriteResponse(&livekit.SignalResponse{
|
|
Message: &livekit.SignalResponse_Trickle{
|
|
Trickle: &livekit.Trickle{
|
|
CandidateInit: string(bytes),
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.GetLogger().Errorw("could not send trickle", "err", err)
|
|
}
|
|
}
|
|
|
|
// send peer new offer
|
|
peer.OnOffer = func(o webrtc.SessionDescription) {
|
|
err := sc.WriteResponse(&livekit.SignalResponse{
|
|
Message: &livekit.SignalResponse_Negotiate{
|
|
Negotiate: ToProtoSessionDescription(o),
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.GetLogger().Errorw("could not send offer to peer",
|
|
"err", err)
|
|
}
|
|
}
|
|
|
|
// finally send answer
|
|
err = sc.WriteResponse(&livekit.SignalResponse{
|
|
Message: &livekit.SignalResponse_Answer{
|
|
Answer: ToProtoSessionDescription(answer),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not join")
|
|
}
|
|
|
|
return peer, nil
|
|
}
|
|
|
|
func (s *RTCService) handleNegotiate(sc SignalConnection, peer *rtc.WebRTCPeer, neg *livekit.SessionDescription) error {
|
|
if neg.Type == webrtc.SDPTypeOffer.String() {
|
|
offer := FromProtoSessionDescription(neg)
|
|
answer, err := peer.Answer(offer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = sc.WriteResponse(&livekit.SignalResponse{
|
|
Message: &livekit.SignalResponse_Negotiate{
|
|
Negotiate: ToProtoSessionDescription(answer),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if neg.Type == webrtc.SDPTypeAnswer.String() {
|
|
answer := FromProtoSessionDescription(neg)
|
|
err := peer.SetRemoteDescription(answer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *RTCService) handleTrickle(peer *rtc.WebRTCPeer, trickle *livekit.Trickle) error {
|
|
var candidate webrtc.ICECandidateInit
|
|
err := json.Unmarshal([]byte(trickle.CandidateInit), &candidate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = peer.AddICECandidate(candidate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type errStruct struct {
|
|
StatusCode int `json:"statusCode"`
|
|
Error string `json:"error"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func writeJSONError(w http.ResponseWriter, code int, error ...string) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.WriteHeader(code)
|
|
|
|
json.NewEncoder(w).Encode(jsonError(code, error...))
|
|
}
|
|
|
|
func jsonError(code int, error ...string) errStruct {
|
|
es := errStruct{
|
|
StatusCode: code,
|
|
}
|
|
if len(error) > 0 {
|
|
es.Error = error[0]
|
|
}
|
|
if len(error) > 1 {
|
|
es.Message = error[1]
|
|
}
|
|
return es
|
|
}
|