Files
livekit/pkg/service/rtc.go
2020-12-01 23:32:15 -08:00

222 lines
5.8 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 {
skipTokenCheck bool
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.skipTokenCheck = true
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")
pName := r.FormValue("name")
log := logger.GetLogger()
log.Infow("new client connected",
"roomId", roomId,
"participantName", pName,
)
room := s.manager.GetRoom(roomId)
if room == nil {
writeJSONError(w, http.StatusNotFound, "room not found")
return
}
if !s.skipTokenCheck && room.Token != token {
writeJSONError(w, http.StatusUnauthorized, "invalid room token")
return
}
// upgrade only once the basics are good to go
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
logger.GetLogger().Warnw("could not upgrade to WS",
"err", err,
)
}
conn.SetCloseHandler(func(code int, text string) error {
log.Infow("websocket closed by remote")
return nil
})
signalConn := rtc.NewWSSignalConnection(conn)
participant, err := rtc.NewParticipant(s.manager.Config(), signalConn, pName)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, "could not create participant", err.Error())
return
}
if err := room.Join(participant); err != nil {
writeJSONError(w, http.StatusInternalServerError, "could not join room", err.Error())
return
}
defer func() {
participant.Close()
log.Infow("WS connection closed")
}()
// read connection and wait for commands
//ctx := context.Background()
for {
req, err := signalConn.ReadRequest()
if err != nil {
logger.GetLogger().Errorw("error reading WS",
"err", err,
"participantName", pName,
"roomId", roomId)
return
}
switch msg := req.Message.(type) {
case *livekit.SignalRequest_Offer:
err = s.handleOffer(participant, msg.Offer)
if err != nil {
log.Errorw("could not handle join", "err", err, "participant", participant.ID())
return
}
defer func() {
// remove peer from room upon disconnection
room.RemoveParticipant(participant.ID())
}()
case *livekit.SignalRequest_Negotiate:
if participant.State() == livekit.ParticipantInfo_JOINING {
log.Errorw("cannot negotiate before peer offer", "participant", participant.ID())
//conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot negotiate before peer offer"))
return
}
err = s.handleNegotiate(signalConn, participant, msg.Negotiate)
if err != nil {
log.Errorw("could not handle negotiate", "participant", participant.ID(), "err", err)
//conn.WriteJSON(
// jsonError(http.StatusInternalServerError, "could not handle negotiate", err.Error()))
return
}
case *livekit.SignalRequest_Trickle:
if participant.State() == livekit.ParticipantInfo_JOINING {
log.Errorw("cannot trickle before peer offer", "participant", participant.ID())
//conn.WriteJSON(jsonError(http.StatusNotAcceptable, "cannot trickle before peer offer"))
return
}
err = s.handleTrickle(participant, msg.Trickle)
if err != nil {
log.Errorw("could not handle trickle", "participant", participant.ID(), "err", err)
//conn.WriteJSON(
// jsonError(http.StatusInternalServerError, "could not handle trickle", err.Error()))
return
}
}
}
}
func (s *RTCService) handleOffer(participant *rtc.Participant, offer *livekit.SessionDescription) error {
log := logger.GetLogger()
_, err := participant.Answer(rtc.FromProtoSessionDescription(offer))
if err != nil {
return errors.Wrap(err, "could not answer offer")
}
log.Debugw("answered client offer")
return nil
}
func (s *RTCService) handleNegotiate(sc rtc.SignalConnection, peer *rtc.Participant, neg *livekit.SessionDescription) error {
logger.GetLogger().Debugw("handling incoming negotiate")
if neg.Type == webrtc.SDPTypeOffer.String() {
offer := rtc.FromProtoSessionDescription(neg)
answer, err := peer.Answer(offer)
if err != nil {
return err
}
err = sc.WriteResponse(&livekit.SignalResponse{
Message: &livekit.SignalResponse_Negotiate{
Negotiate: rtc.ToProtoSessionDescription(answer),
},
})
if err != nil {
return err
}
} else if neg.Type == webrtc.SDPTypeAnswer.String() {
answer := rtc.FromProtoSessionDescription(neg)
err := peer.SetRemoteDescription(answer)
if err != nil {
return err
}
}
return nil
}
func (s *RTCService) handleTrickle(peer *rtc.Participant, trickle *livekit.Trickle) error {
candidateInit := rtc.FromProtoTrickle(trickle)
logger.GetLogger().Debugw("adding peer candidate", "participantId", peer.ID())
if err := peer.AddICECandidate(*candidateInit); 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
}