Files
server-sdk-go/engine.go
David Zhao a23b81de61 chore: pin GHA by commit, switch to golangci-lint (#884)
* chore: pin GHA by commit, switch to golangci-lint

* fix
2026-04-12 12:53:40 -07:00

1387 lines
37 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lksdk
import (
"context"
"errors"
"io"
"net/http"
"sync"
"time"
"github.com/pion/webrtc/v4"
"go.uber.org/atomic"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"github.com/livekit/protocol/livekit"
protoLogger "github.com/livekit/protocol/logger"
protosignalling "github.com/livekit/protocol/signalling"
"github.com/livekit/server-sdk-go/v2/signalling"
)
// -------------------------------------------
type engineHandler interface {
OnLocalTrackUnpublished(response *livekit.TrackUnpublishedResponse)
OnTrackRemoteMuted(request *livekit.MuteTrackRequest)
OnDisconnected(reason DisconnectionReason)
OnMediaTrack(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver)
OnParticipantUpdate([]*livekit.ParticipantInfo)
OnSpeakersChanged([]*livekit.SpeakerInfo)
OnDataPacket(identity string, dataPacket DataPacket)
OnConnectionQuality([]*livekit.ConnectionQualityInfo)
OnRoomUpdate(room *livekit.Room)
OnRoomMoved(moved *livekit.RoomMovedResponse)
OnRestarting()
OnRestarted(
room *livekit.Room,
participant *livekit.ParticipantInfo,
otherParticipants []*livekit.ParticipantInfo,
)
OnResuming()
OnResumed()
OnTranscription(*livekit.Transcription)
OnRoomJoined(
room *livekit.Room,
participant *livekit.ParticipantInfo,
otherParticipants []*livekit.ParticipantInfo,
serverInfo *livekit.ServerInfo,
sifTrailer []byte,
)
OnRpcRequest(callerIdentity, requestId, method, payload string, responseTimeout time.Duration, version uint32)
OnRpcAck(requestId string)
OnRpcResponse(requestId string, payload *string, error *RpcError)
OnStreamHeader(*livekit.DataStream_Header, string)
OnStreamChunk(*livekit.DataStream_Chunk)
OnStreamTrailer(*livekit.DataStream_Trailer)
OnLocalTrackSubscribed(trackSubscribed *livekit.TrackSubscribed)
OnSubscribedQualityUpdate(subscribedQualityUpdate *livekit.SubscribedQualityUpdate)
OnSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate *livekit.SubscribedAudioCodecUpdate)
OnMediaSectionsRequirement(mediaSectionsRequirement *livekit.MediaSectionsRequirement)
}
// -------------------------------------------
var (
_ signalling.SignalTransportHandler = (*RTCEngine)(nil)
_ signalling.SignalProcessor = (*RTCEngine)(nil)
)
// -------------------------------------------
const (
reliableDataChannelName = "_reliable"
lossyDataChannelName = "_lossy"
maxReconnectCount = 10
initialReconnectInterval = 300 * time.Millisecond
maxReconnectInterval = 60 * time.Second
)
type RTCEngine struct {
log protoLogger.Logger
useSinglePeerConnection bool
engineHandler engineHandler
cbGetLocalParticipantSID func() string
pclock sync.Mutex
publisher *PCTransport
pendingPublisherOffer webrtc.SessionDescription
subscriber *PCTransport
signalling signalling.Signalling
signalHandler signalling.SignalHandler
signalTransport signalling.SignalTransport
dclock sync.RWMutex
reliableDC *webrtc.DataChannel
lossyDC *webrtc.DataChannel
reliableDCSub *webrtc.DataChannel
lossyDCSub *webrtc.DataChannel
reliableMsgLock sync.Mutex
reliableMsgSeq uint32
trackPublishedListenersLock sync.Mutex
trackPublishedListeners map[string]chan *livekit.TrackPublishedResponse
subscriberPrimary bool
hasConnected atomic.Bool
hasPublish atomic.Bool
closed atomic.Bool
reconnecting atomic.Bool
requiresFullReconnect atomic.Bool
url string
token atomic.String
connParams *signalling.ConnectParams
joinTimeout time.Duration
onClose []func()
onCloseLock sync.Mutex
}
func NewRTCEngine(
useSinglePeerConnection bool,
engineHandler engineHandler,
getLocalParticipantSID func() string,
) *RTCEngine {
e := &RTCEngine{
log: logger,
useSinglePeerConnection: useSinglePeerConnection,
engineHandler: engineHandler,
cbGetLocalParticipantSID: getLocalParticipantSID,
trackPublishedListeners: make(map[string]chan *livekit.TrackPublishedResponse),
joinTimeout: 15 * time.Second,
reliableMsgSeq: 1,
}
if !useSinglePeerConnection {
e.signalling = signalling.NewSignalling(signalling.SignallingParams{
Logger: e.log,
})
} else {
e.signalling = signalling.NewSignallingJoinRequest(signalling.SignallingJoinRequestParams{
Logger: e.log,
})
}
e.signalHandler = signalling.NewSignalHandler(signalling.SignalHandlerParams{
Logger: e.log,
Processor: e,
})
e.signalTransport = signalling.NewSignalTransportWebSocket(signalling.SignalTransportWebSocketParams{
Logger: e.log,
Version: Version,
Protocol: PROTOCOL,
Signalling: e.signalling,
SignalTransportHandler: e,
SignalHandler: e.signalHandler,
})
e.onClose = []func(){}
return e
}
// SetLogger overrides default logger.
func (e *RTCEngine) SetLogger(l protoLogger.Logger) {
e.log = l
e.signalling.SetLogger(l)
e.signalHandler.SetLogger(l)
e.signalTransport.SetLogger(l)
if e.publisher != nil {
e.publisher.SetLogger(l.WithValues("transport", livekit.SignalTarget_PUBLISHER))
}
if e.subscriber != nil {
e.subscriber.SetLogger(l.WithValues("transport", livekit.SignalTarget_SUBSCRIBER))
}
}
func (e *RTCEngine) JoinContext(
ctx context.Context,
url string,
token string,
connectParams *signalling.ConnectParams,
) (bool, error) {
e.url = url
e.token.Store(token)
e.connParams = connectParams
var (
publisherOffer webrtc.SessionDescription
err error
)
if e.signalling.PublishInJoin() {
e.pclock.Lock()
e.createPublisherPCLocked(webrtc.Configuration{})
publisherOffer, err = e.publisher.GetLocalOffer()
if err != nil {
e.pclock.Unlock()
return false, err
}
e.pendingPublisherOffer = publisherOffer
e.pclock.Unlock()
}
if err = e.signalTransport.Join(ctx, url, token, *connectParams, nil, publisherOffer); err != nil {
if verr := e.validate(ctx, url, token, connectParams, ""); verr != nil {
return false, verr
}
return false, ErrCannotConnectSignal
}
if err = e.waitUntilConnected(); err != nil {
return false, err
}
e.hasConnected.Store(true)
return true, nil
}
func (e *RTCEngine) OnClose(onClose func()) {
e.onCloseLock.Lock()
e.onClose = append(e.onClose, onClose)
e.onCloseLock.Unlock()
}
func (e *RTCEngine) Close() {
if !e.closed.CompareAndSwap(false, true) {
return
}
go func() {
for e.reconnecting.Load() {
time.Sleep(50 * time.Millisecond)
}
e.onCloseLock.Lock()
onClose := e.onClose
e.onClose = []func(){}
e.onCloseLock.Unlock()
for _, onCloseHandler := range onClose {
onCloseHandler()
}
if publisher, ok := e.Publisher(); ok {
_ = publisher.Close()
}
if subscriber, ok := e.Subscriber(); ok {
_ = subscriber.Close()
}
e.signalTransport.Close()
}()
}
func (e *RTCEngine) IsConnected() bool {
e.pclock.Lock()
defer e.pclock.Unlock()
if e.publisher == nil || (!e.useSinglePeerConnection && e.subscriber == nil) {
return false
}
if e.subscriberPrimary {
return e.subscriber.IsConnected()
}
return e.publisher.IsConnected()
}
func (e *RTCEngine) Publisher() (*PCTransport, bool) {
e.pclock.Lock()
defer e.pclock.Unlock()
return e.publisher, e.publisher != nil
}
func (e *RTCEngine) Subscriber() (*PCTransport, bool) {
e.pclock.Lock()
defer e.pclock.Unlock()
return e.subscriber, e.subscriber != nil
}
func (e *RTCEngine) setRTT(rtt uint32) {
if subscriber, ok := e.Subscriber(); ok {
subscriber.SetRTT(rtt)
}
}
func (e *RTCEngine) configure(
iceServers []*livekit.ICEServer,
clientConfig *livekit.ClientConfiguration,
subscriberPrimary *bool,
) error {
e.log.Debugw("Using ICE servers", "servers", iceServers)
configuration := e.makeRTCConfiguration(iceServers, clientConfig)
// reset reliable message sequence
e.reliableMsgLock.Lock()
e.reliableMsgSeq = 1
e.reliableMsgLock.Unlock()
e.pclock.Lock()
defer e.pclock.Unlock()
if subscriberPrimary != nil {
e.subscriberPrimary = *subscriberPrimary
}
if e.publisher != nil {
setConfiguration(e.publisher, configuration)
} else {
if err := e.createPublisherPCLocked(configuration); err != nil {
return err
}
}
if e.subscriber != nil {
setConfiguration(e.subscriber, configuration)
} else {
if err := e.createSubscriberPCLocked(configuration); err != nil {
return err
}
}
return nil
}
func (e *RTCEngine) createPublisherPCLocked(configuration webrtc.Configuration) error {
var err error
if e.publisher, err = NewPCTransport(PCTransportParams{
Configuration: configuration,
Codecs: e.connParams.Codecs,
RetransmitBufferSize: e.connParams.RetransmitBufferSize,
Pacer: e.connParams.Pacer,
Interceptors: e.connParams.Interceptors,
IncludeDefaultInterceptors: e.connParams.IncludeDefaultInterceptors,
OnRTTUpdate: e.setRTT,
IsSender: true,
DTLSEllipticCurves: e.connParams.DTLSEllipticCurves,
}); err != nil {
return err
}
e.publisher.SetLogger(e.log.WithValues("transport", livekit.SignalTarget_PUBLISHER))
e.publisher.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
// done
return
}
init := candidate.ToJSON()
e.log.Debugw(
"local ICE candidate",
"transport", livekit.SignalTarget_PUBLISHER,
"candidate", init.Candidate,
)
if err := e.signalTransport.SendMessage(
e.signalling.SignalICECandidate(
protosignalling.ToProtoTrickle(init, livekit.SignalTarget_PUBLISHER, false),
),
); err != nil {
e.log.Errorw(
"could not send ICE candidate", err,
"transport", livekit.SignalTarget_PUBLISHER,
)
}
})
e.publisher.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
e.pclock.Lock()
publisher := e.publisher
e.pclock.Unlock()
e.handleICEConnectionStateChange(publisher, livekit.SignalTarget_PUBLISHER, state)
})
e.publisher.OnOffer = func(offer webrtc.SessionDescription) {
e.hasPublish.Store(true)
if err := e.signalTransport.SendMessage(
e.signalling.SignalSdpOffer(
protosignalling.ToProtoSessionDescription(offer, 0, nil),
),
); err != nil {
e.log.Errorw("could not send offer for publisher pc", err)
}
}
trueVal := true
falseVal := false
maxRetries := uint16(1)
e.dclock.Lock()
e.lossyDC, err = e.publisher.pc.CreateDataChannel(lossyDataChannelName, &webrtc.DataChannelInit{
Ordered: &falseVal,
MaxRetransmits: &maxRetries,
})
if err != nil {
e.dclock.Unlock()
return err
}
e.lossyDC.OnMessage(e.handleDataPacket)
e.reliableDC, err = e.publisher.pc.CreateDataChannel(reliableDataChannelName, &webrtc.DataChannelInit{
Ordered: &trueVal,
})
if err != nil {
e.dclock.Unlock()
return err
}
e.reliableDC.OnMessage(e.handleDataPacket)
e.dclock.Unlock()
return nil
}
func (e *RTCEngine) createSubscriberPCLocked(configuration webrtc.Configuration) error {
if e.useSinglePeerConnection {
return nil
}
var err error
if e.subscriber, err = NewPCTransport(PCTransportParams{
Configuration: configuration,
Codecs: e.connParams.Codecs,
RetransmitBufferSize: e.connParams.RetransmitBufferSize,
Interceptors: e.connParams.Interceptors,
IncludeDefaultInterceptors: e.connParams.IncludeDefaultInterceptors,
DTLSEllipticCurves: e.connParams.DTLSEllipticCurves,
}); err != nil {
return err
}
e.subscriber.SetLogger(e.log.WithValues("transport", livekit.SignalTarget_SUBSCRIBER))
e.subscriber.OnRemoteDescriptionSettled(e.createSubscriberPCAnswerAndSend)
e.subscriber.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
// done
return
}
init := candidate.ToJSON()
e.log.Debugw(
"local ICE candidate",
"transport", livekit.SignalTarget_SUBSCRIBER,
"candidate", init.Candidate,
)
if err := e.signalTransport.SendMessage(
e.signalling.SignalICECandidate(
protosignalling.ToProtoTrickle(init, livekit.SignalTarget_SUBSCRIBER, false),
),
); err != nil {
e.log.Errorw(
"could not send ICE candidate", err,
"transport", livekit.SignalTarget_SUBSCRIBER,
)
}
})
e.subscriber.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
e.pclock.Lock()
subscriber := e.subscriber
e.pclock.Unlock()
e.handleICEConnectionStateChange(subscriber, livekit.SignalTarget_SUBSCRIBER, state)
})
e.subscriber.pc.OnTrack(func(remote *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
e.engineHandler.OnMediaTrack(remote, receiver)
})
e.subscriber.pc.OnDataChannel(func(c *webrtc.DataChannel) {
e.dclock.Lock()
defer e.dclock.Unlock()
if c.Label() == reliableDataChannelName {
e.reliableDCSub = c
} else if c.Label() == lossyDataChannelName {
e.lossyDCSub = c
} else {
return
}
c.OnMessage(e.handleDataPacket)
})
return nil
}
func (e *RTCEngine) handleICEConnectionStateChange(
transport *PCTransport,
signalTarget livekit.SignalTarget,
state webrtc.ICEConnectionState,
) {
if transport == nil {
return
}
switch state {
case webrtc.ICEConnectionStateConnected:
var fields []any
if pair, err := transport.GetSelectedCandidatePair(); err == nil {
fields = append(fields, "transport", signalTarget, "iceCandidatePair", pair)
}
e.log.Debugw("ICE connected", fields...)
case webrtc.ICEConnectionStateDisconnected:
e.log.Debugw("ICE disconnected", "transport", signalTarget)
case webrtc.ICEConnectionStateFailed:
e.log.Debugw("ICE failed", "transport", signalTarget)
e.handleDisconnect(false)
}
}
func (e *RTCEngine) closePeerConnections() {
e.pclock.Lock()
defer e.pclock.Unlock()
if e.publisher != nil {
e.publisher.Close()
e.publisher = nil
}
if e.subscriber != nil {
e.subscriber.Close()
e.subscriber = nil
}
}
func (e *RTCEngine) GetDataChannel(kind livekit.DataPacket_Kind) *webrtc.DataChannel {
e.dclock.RLock()
defer e.dclock.RUnlock()
if kind == livekit.DataPacket_RELIABLE {
return e.reliableDC
}
return e.lossyDC
}
func (e *RTCEngine) GetDataChannelSub(kind livekit.DataPacket_Kind) *webrtc.DataChannel {
e.dclock.RLock()
defer e.dclock.RUnlock()
if kind == livekit.DataPacket_RELIABLE {
return e.reliableDCSub
}
return e.lossyDCSub
}
func waitUntilConnected(d time.Duration, test func() bool) error {
if test() {
return nil
}
timeout := time.NewTimer(d)
defer timeout.Stop()
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-timeout.C:
return ErrConnectionTimeout
case <-ticker.C:
if test() {
return nil
}
}
}
}
func (e *RTCEngine) waitUntilConnected() error {
return waitUntilConnected(e.joinTimeout, func() bool {
if e.IsConnected() {
e.requiresFullReconnect.Store(false)
return true
}
return false
})
}
func (e *RTCEngine) ensurePublisherConnected(ensureDataReady bool) error {
e.pclock.Lock()
subscriberPrimary := e.subscriberPrimary
e.pclock.Unlock()
if !subscriberPrimary {
return e.waitUntilConnected()
}
var negotiated bool
return waitUntilConnected(e.joinTimeout, func() bool {
if publisher, ok := e.Publisher(); ok {
if publisher.IsConnected() && (!ensureDataReady || e.dataPubChannelReady()) {
return true
}
if !negotiated {
publisher.Negotiate()
negotiated = true
}
}
return false
})
}
func (e *RTCEngine) dataPubChannelReady() bool {
e.dclock.RLock()
defer e.dclock.RUnlock()
return e.reliableDC.ReadyState() == webrtc.DataChannelStateOpen && e.lossyDC.ReadyState() == webrtc.DataChannelStateOpen
}
func (e *RTCEngine) RegisterTrackPublishedListener(cid string, c chan *livekit.TrackPublishedResponse) {
e.trackPublishedListenersLock.Lock()
e.trackPublishedListeners[cid] = c
e.trackPublishedListenersLock.Unlock()
}
func (e *RTCEngine) UnregisterTrackPublishedListener(cid string) {
e.trackPublishedListenersLock.Lock()
delete(e.trackPublishedListeners, cid)
e.trackPublishedListenersLock.Unlock()
}
func (e *RTCEngine) handleDataPacket(msg webrtc.DataChannelMessage) {
packet, err := e.readDataPacket(msg)
if err != nil {
return
}
identity := packet.ParticipantIdentity
switch msg := packet.Value.(type) {
case *livekit.DataPacket_User:
m := msg.User
//lint:ignore SA1019 backward compatibility
if ptr := &m.ParticipantIdentity; *ptr == "" {
*ptr = identity
}
//lint:ignore SA1019 backward compatibility
if ptr := &m.DestinationIdentities; len(*ptr) == 0 {
*ptr = packet.DestinationIdentities
}
if identity == "" {
//lint:ignore SA1019 backward compatibility
identity = m.ParticipantIdentity
}
e.engineHandler.OnDataPacket(identity, &UserDataPacket{
Payload: m.Payload,
Topic: m.GetTopic(),
})
case *livekit.DataPacket_SipDtmf:
e.engineHandler.OnDataPacket(identity, msg.SipDtmf)
case *livekit.DataPacket_ChatMessage:
e.engineHandler.OnDataPacket(identity, msg.ChatMessage)
case *livekit.DataPacket_Transcription:
e.engineHandler.OnTranscription(msg.Transcription)
case *livekit.DataPacket_RpcRequest:
e.engineHandler.OnRpcRequest(
packet.ParticipantIdentity,
msg.RpcRequest.Id,
msg.RpcRequest.Method,
msg.RpcRequest.Payload,
time.Duration(msg.RpcRequest.ResponseTimeoutMs)*time.Millisecond,
msg.RpcRequest.Version,
)
case *livekit.DataPacket_RpcAck:
e.engineHandler.OnRpcAck(msg.RpcAck.RequestId)
case *livekit.DataPacket_RpcResponse:
switch res := msg.RpcResponse.Value.(type) {
case *livekit.RpcResponse_Payload:
e.engineHandler.OnRpcResponse(msg.RpcResponse.RequestId, &res.Payload, nil)
case *livekit.RpcResponse_Error:
e.engineHandler.OnRpcResponse(msg.RpcResponse.RequestId, nil, fromProto(res.Error))
}
case *livekit.DataPacket_StreamHeader:
e.engineHandler.OnStreamHeader(msg.StreamHeader, identity)
case *livekit.DataPacket_StreamChunk:
e.engineHandler.OnStreamChunk(msg.StreamChunk)
case *livekit.DataPacket_StreamTrailer:
e.engineHandler.OnStreamTrailer(msg.StreamTrailer)
}
}
func (e *RTCEngine) readDataPacket(msg webrtc.DataChannelMessage) (*livekit.DataPacket, error) {
dataPacket := &livekit.DataPacket{}
if msg.IsString {
err := protojson.Unmarshal(msg.Data, dataPacket)
return dataPacket, err
}
err := proto.Unmarshal(msg.Data, dataPacket)
return dataPacket, err
}
func (e *RTCEngine) handleDisconnect(fullReconnect bool) {
// do not retry until fully connected
if e.closed.Load() || !e.hasConnected.Load() {
return
}
if !e.reconnecting.CompareAndSwap(false, true) {
if fullReconnect {
e.requiresFullReconnect.Store(true)
}
return
}
go func() {
defer e.reconnecting.Store(false)
for reconnectCount := 0; reconnectCount < maxReconnectCount && !e.closed.Load(); reconnectCount++ {
if e.requiresFullReconnect.Load() {
fullReconnect = true
}
if fullReconnect {
if reconnectCount == 0 {
e.engineHandler.OnRestarting()
}
e.log.Infow("restarting connection...", "reconnectCount", reconnectCount)
if err := e.restartConnection(); err != nil {
e.log.Errorw("restart connection failed", err)
} else {
return
}
} else {
if reconnectCount == 0 {
e.engineHandler.OnResuming()
}
e.log.Infow("resuming connection...", "reconnectCount", reconnectCount)
if err := e.resumeConnection(); err != nil {
e.log.Errorw("resume connection failed", err)
} else {
return
}
}
delay := time.Duration(reconnectCount*reconnectCount) * initialReconnectInterval
if delay > maxReconnectInterval {
break
}
if reconnectCount < maxReconnectCount-1 {
time.Sleep(delay)
}
}
e.engineHandler.OnDisconnected(Failed)
}()
}
func (e *RTCEngine) resumeConnection() error {
err := e.signalTransport.Reconnect(
e.url,
e.token.Load(),
*e.connParams,
e.cbGetLocalParticipantSID(),
)
if err != nil {
if verr := e.validate(
context.TODO(),
e.url,
e.token.Load(),
e.connParams,
e.cbGetLocalParticipantSID(),
); verr != nil {
return verr
}
return ErrCannotConnectSignal
}
e.signalTransport.Start()
// send offer if publisher enabled
e.pclock.Lock()
sendOffer := !e.subscriberPrimary || e.hasPublish.Load()
publisher := e.publisher
e.pclock.Unlock()
if sendOffer {
if err := publisher.createAndSendOffer(&webrtc.OfferOptions{
ICERestart: true,
}); err != nil {
return err
}
}
if err = e.waitUntilConnected(); err != nil {
return err
}
e.engineHandler.OnResumed()
return nil
}
func (e *RTCEngine) restartConnection() error {
if e.signalTransport.IsStarted() {
// TODO: special reason for reconnect?
e.SendLeaveWithReason(livekit.DisconnectReason_UNKNOWN_REASON)
}
e.signalTransport.Close()
e.closePeerConnections()
_, err := e.JoinContext(context.TODO(), e.url, e.token.Load(), e.connParams)
return err
}
func (e *RTCEngine) createSubscriberPCAnswerAndSend() error {
answer, err := e.subscriber.pc.CreateAnswer(nil)
if err != nil {
e.log.Errorw("could not create answer", err)
return err
}
if err := e.subscriber.pc.SetLocalDescription(answer); err != nil {
e.log.Errorw("could not set subscriber local description", err)
return err
}
e.log.Debugw("sending answer for subscriber", "answer", answer)
if err := e.signalTransport.SendMessage(
e.signalling.SignalSdpAnswer(
protosignalling.ToProtoSessionDescription(answer, 0, nil),
),
); err != nil {
e.log.Errorw("could not send answer for subscriber pc", err)
}
return nil
}
func (e *RTCEngine) makeRTCConfiguration(iceServers []*livekit.ICEServer, clientConfig *livekit.ClientConfiguration) webrtc.Configuration {
rtcICEServers := protosignalling.FromProtoIceServers(iceServers)
configuration := webrtc.Configuration{
ICEServers: rtcICEServers,
ICETransportPolicy: e.connParams.ICETransportPolicy,
}
if clientConfig != nil &&
clientConfig.GetForceRelay() == livekit.ClientConfigSetting_ENABLED {
configuration.ICETransportPolicy = webrtc.ICETransportPolicyRelay
}
return configuration
}
func (e *RTCEngine) publishDataPacket(pck *livekit.DataPacket, kind livekit.DataPacket_Kind) error {
err := e.ensurePublisherConnected(true)
if err != nil {
e.log.Errorw("could not ensure publisher connected", err)
return err
}
dc := e.GetDataChannel(kind)
if dc == nil {
e.log.Errorw("could not get data channel", nil, "kind", kind)
return errors.New("datachannel not found")
}
if kind == livekit.DataPacket_RELIABLE {
e.reliableMsgLock.Lock()
defer e.reliableMsgLock.Unlock()
pck.Sequence = e.reliableMsgSeq
e.reliableMsgSeq++
}
data, err := proto.Marshal(pck)
if err != nil {
e.log.Errorw("could not marshal data packet", err)
return err
}
dc.Send(data)
return nil
}
func (e *RTCEngine) publishDataPacketReliable(pck *livekit.DataPacket) error {
return e.publishDataPacket(pck, livekit.DataPacket_RELIABLE)
}
//lint:ignore U1000 Ignore unused function
func (e *RTCEngine) publishDataPacketLossy(pck *livekit.DataPacket) error {
return e.publishDataPacket(pck, livekit.DataPacket_LOSSY)
}
// TODO: adjust RPC methods to return error on publishDataPacket failure
func (e *RTCEngine) publishRpcResponse(destinationIdentity, requestId string, payload *string, err *RpcError) error {
packet := &livekit.DataPacket{
DestinationIdentities: []string{destinationIdentity},
Value: &livekit.DataPacket_RpcResponse{
RpcResponse: &livekit.RpcResponse{
RequestId: requestId,
},
},
}
if err != nil {
packet.Value.(*livekit.DataPacket_RpcResponse).RpcResponse.Value = &livekit.RpcResponse_Error{
Error: err.toProto(),
}
} else {
if payload == nil {
emptyStr := ""
payload = &emptyStr
}
packet.Value.(*livekit.DataPacket_RpcResponse).RpcResponse.Value = &livekit.RpcResponse_Payload{
Payload: *payload,
}
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish rpc response", publishErr)
}
return publishErr
}
func (e *RTCEngine) publishRpcAck(destinationIdentity, requestId string) error {
packet := &livekit.DataPacket{
DestinationIdentities: []string{destinationIdentity},
Value: &livekit.DataPacket_RpcAck{
RpcAck: &livekit.RpcAck{
RequestId: requestId,
},
},
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish rpc ack", publishErr)
}
return publishErr
}
func (e *RTCEngine) publishRpcRequest(destinationIdentity, requestId, method, payload string, responseTimeout time.Duration) error {
packet := &livekit.DataPacket{
DestinationIdentities: []string{destinationIdentity},
Value: &livekit.DataPacket_RpcRequest{
RpcRequest: &livekit.RpcRequest{
Id: requestId,
Method: method,
Payload: payload,
ResponseTimeoutMs: uint32(responseTimeout.Milliseconds()),
Version: 1,
},
},
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish rpc request", publishErr)
}
return publishErr
}
func (e *RTCEngine) publishStreamHeader(header *livekit.DataStream_Header, destinationIdentities []string) error {
packet := &livekit.DataPacket{
DestinationIdentities: destinationIdentities,
Value: &livekit.DataPacket_StreamHeader{
StreamHeader: header,
},
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish stream header", publishErr)
}
return publishErr
}
func (e *RTCEngine) publishStreamChunk(chunk *livekit.DataStream_Chunk, destinationIdentities []string) error {
packet := &livekit.DataPacket{
DestinationIdentities: destinationIdentities,
Value: &livekit.DataPacket_StreamChunk{
StreamChunk: chunk,
},
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish stream chunk", publishErr)
}
return publishErr
}
func (e *RTCEngine) publishStreamTrailer(streamId string, destinationIdentities []string) error {
packet := &livekit.DataPacket{
DestinationIdentities: destinationIdentities,
Value: &livekit.DataPacket_StreamTrailer{
StreamTrailer: &livekit.DataStream_Trailer{
StreamId: streamId,
},
},
}
publishErr := e.publishDataPacketReliable(packet)
if publishErr != nil {
e.log.Errorw("could not publish stream trailer", publishErr)
}
return publishErr
}
func (e *RTCEngine) isBufferStatusLow(kind livekit.DataPacket_Kind) bool {
dc := e.GetDataChannel(kind)
if dc != nil {
return dc.BufferedAmount() <= dc.BufferedAmountLowThreshold()
}
return false
}
func (e *RTCEngine) waitForBufferStatusLow(kind livekit.DataPacket_Kind) {
for !e.isBufferStatusLow(kind) {
time.Sleep(10 * time.Millisecond)
}
}
func (e *RTCEngine) SendAddTrack(addTrack *livekit.AddTrackRequest) error {
return e.signalTransport.SendMessage(e.signalling.SignalAddTrack(addTrack))
}
func (e *RTCEngine) SendSubscriptionPermission(subscriptionPermission *livekit.SubscriptionPermission) error {
return e.signalTransport.SendMessage(e.signalling.SignalSubscriptionPermission(subscriptionPermission))
}
func (e *RTCEngine) SendUpdateTrackSettings(settings *livekit.UpdateTrackSettings) error {
return e.signalTransport.SendMessage(e.signalling.SignalUpdateTrackSettings(settings))
}
func (e *RTCEngine) SendUpdateParticipantMetadata(metadata *livekit.UpdateParticipantMetadata) error {
return e.signalTransport.SendMessage(e.signalling.SignalUpdateParticipantMetadata(metadata))
}
func (e *RTCEngine) SendMuteTrack(sid string, muted bool) error {
return e.signalTransport.SendMessage(
e.signalling.SignalMuteTrack(
&livekit.MuteTrackRequest{
Sid: sid,
Muted: muted,
},
),
)
}
func (e *RTCEngine) SendUpdateSubscription(updateSubscription *livekit.UpdateSubscription) error {
return e.signalTransport.SendMessage(e.signalling.SignalUpdateSubscription(updateSubscription))
}
func (e *RTCEngine) SendSyncState(syncState *livekit.SyncState) error {
return e.signalTransport.SendMessage(e.signalling.SignalSyncState(syncState))
}
func (e *RTCEngine) SendLeaveWithReason(reason livekit.DisconnectReason) error {
return e.signalTransport.SendMessage(
e.signalling.SignalLeaveRequest(
&livekit.LeaveRequest{
Reason: reason,
},
),
)
}
func (e *RTCEngine) Simulate(scenario SimulateScenario) {
switch scenario {
case SimulateSignalReconnect:
e.signalTransport.Close()
case SimulateForceTCP:
// pion does not support active tcp candidate, skip
case SimulateForceTLS:
e.signalTransport.SendMessage(
e.signalling.SignalSimulateScenario(
&livekit.SimulateScenario{
Scenario: &livekit.SimulateScenario_SwitchCandidateProtocol{
SwitchCandidateProtocol: livekit.CandidateProtocol_TLS,
},
},
),
)
e.OnLeave(&livekit.LeaveRequest{
Action: livekit.LeaveRequest_RECONNECT,
Reason: livekit.DisconnectReason_CLIENT_INITIATED,
})
case SimulateSpeakerUpdate:
e.signalTransport.SendMessage(
e.signalling.SignalSimulateScenario(
&livekit.SimulateScenario{
Scenario: &livekit.SimulateScenario_SpeakerUpdate{
SpeakerUpdate: SimulateSpeakerUpdateInterval,
},
},
),
)
case SimulateMigration:
e.signalTransport.SendMessage(
e.signalling.SignalSimulateScenario(
&livekit.SimulateScenario{
Scenario: &livekit.SimulateScenario_Migration{
Migration: true,
},
},
),
)
case SimulateServerLeave:
e.signalTransport.SendMessage(
e.signalling.SignalSimulateScenario(
&livekit.SimulateScenario{
Scenario: &livekit.SimulateScenario_ServerLeave{
ServerLeave: true,
},
},
),
)
case SimulateNodeFailure:
e.signalTransport.SendMessage(
e.signalling.SignalSimulateScenario(
&livekit.SimulateScenario{
Scenario: &livekit.SimulateScenario_NodeFailure{
NodeFailure: true,
},
},
),
)
}
}
func (e *RTCEngine) validate(
ctx context.Context,
urlPrefix string,
token string,
connectParams *signalling.ConnectParams,
participantSID string,
) error {
req, err := e.signalling.HTTPRequestForValidate(
ctx,
Version,
PROTOCOL,
urlPrefix,
token,
connectParams,
participantSID,
)
if err != nil {
return err
}
hresp, err := http.DefaultClient.Do(req)
if err != nil {
e.log.Errorw("error getting validation", err, "httpResponse", hresp)
return signalling.ErrCannotDialSignal
}
defer hresp.Body.Close()
if hresp.StatusCode == http.StatusOK {
// no specific errors to return if validate succeeds
e.log.Infow("validate succeeded")
return nil
}
var errString string
switch hresp.StatusCode {
case http.StatusUnauthorized:
errString = "unauthorized: "
case http.StatusNotFound:
errString = "not found"
case http.StatusServiceUnavailable:
errString = "unavailable: "
}
if hresp.StatusCode != http.StatusNotFound {
body, err := io.ReadAll(hresp.Body)
if err == nil {
errString += e.signalling.DecodeErrorResponse(body)
}
e.log.Errorw("validation error", errors.New(errString), "httpResponse", hresp)
} else {
e.log.Errorw("validation error", errors.New(errString))
}
return errors.New(errString)
}
// signalling.SignalTransportHandler implementation
func (e *RTCEngine) OnTransportClose() {
e.handleDisconnect(false)
}
// signalling.SignalProcessor implementation
func (e *RTCEngine) OnJoinResponse(res *livekit.JoinResponse) error {
isRestarting := e.reconnecting.Load() && e.requiresFullReconnect.Load()
err := e.configure(res.IceServers, res.ClientConfiguration, proto.Bool(res.SubscriberPrimary))
if err != nil {
e.log.Warnw("could not configure", err)
return err
}
e.engineHandler.OnRoomJoined(
res.Room,
res.Participant,
res.OtherParticipants,
res.ServerInfo,
res.SifTrailer,
)
e.signalTransport.Start()
if e.signalling.PublishInJoin() {
if publisher, ok := e.Publisher(); ok {
e.pclock.Lock()
pendingPublisherOffer := e.pendingPublisherOffer
e.pendingPublisherOffer = webrtc.SessionDescription{}
e.pclock.Unlock()
if pendingPublisherOffer.SDP != "" {
publisher.SetLocalOffer(pendingPublisherOffer)
}
}
} else {
// send offer
if !res.SubscriberPrimary || res.FastPublish {
if publisher, ok := e.Publisher(); ok {
publisher.Negotiate()
} else {
e.log.Warnw("no publisher peer connection", ErrNoPeerConnection)
}
}
}
if isRestarting {
e.engineHandler.OnRestarted(res.Room, res.Participant, res.OtherParticipants)
}
return nil
}
func (e *RTCEngine) OnReconnectResponse(res *livekit.ReconnectResponse) error {
configuration := e.makeRTCConfiguration(res.IceServers, res.ClientConfiguration)
e.pclock.Lock()
defer e.pclock.Unlock()
if e.publisher != nil {
if err := e.publisher.SetConfiguration(configuration); err != nil {
e.log.Errorw("could not set rtc configuration for publisher", err)
return err
}
}
if e.subscriber != nil {
if err := e.subscriber.SetConfiguration(configuration); err != nil {
e.log.Errorw("could not set rtc configuration for subscriber", err)
return err
}
}
return nil
}
func (e *RTCEngine) OnAnswer(sd webrtc.SessionDescription, answerId uint32, _midToTrackID map[string]string) {
if e.closed.Load() {
e.log.Debugw("ignoring SDP answer after closed")
return
}
if err := e.publisher.SetRemoteDescription(sd); err != nil {
e.log.Errorw("could not set remote description", err)
} else {
e.log.Debugw("successfully set publisher answer")
}
}
func (e *RTCEngine) OnOffer(sd webrtc.SessionDescription, offerId uint32, _midToTrackID map[string]string) {
if e.closed.Load() {
e.log.Debugw("ignoring SDP offer after closed")
return
}
e.log.Debugw("received offer for subscriber", "offer", sd, "offerId", offerId)
if err := e.subscriber.SetRemoteDescription(sd); err != nil {
e.log.Errorw("could not set remote description", err)
return
}
}
func (e *RTCEngine) OnTrickle(init webrtc.ICECandidateInit, target livekit.SignalTarget) {
if e.closed.Load() {
e.log.Debugw("ignoring trickle after closed")
return
}
var err error
e.log.Debugw("remote ICE candidate",
"target", target,
"candidate", init.Candidate,
)
switch target {
case livekit.SignalTarget_PUBLISHER:
err = e.publisher.AddICECandidate(init)
case livekit.SignalTarget_SUBSCRIBER:
err = e.subscriber.AddICECandidate(init)
}
if err != nil {
e.log.Errorw("could not add ICE candidate", err)
}
}
func (e *RTCEngine) OnParticipantUpdate(info []*livekit.ParticipantInfo) {
e.engineHandler.OnParticipantUpdate(info)
}
func (e *RTCEngine) OnLocalTrackPublished(res *livekit.TrackPublishedResponse) {
e.trackPublishedListenersLock.Lock()
listener, ok := e.trackPublishedListeners[res.Cid]
e.trackPublishedListenersLock.Unlock()
if ok {
listener <- res
}
}
func (e *RTCEngine) OnLocalTrackUnpublished(res *livekit.TrackUnpublishedResponse) {
e.engineHandler.OnLocalTrackUnpublished(res)
}
func (e *RTCEngine) OnSpeakersChanged(si []*livekit.SpeakerInfo) {
e.engineHandler.OnSpeakersChanged(si)
}
func (e *RTCEngine) OnConnectionQuality(cqi []*livekit.ConnectionQualityInfo) {
e.engineHandler.OnConnectionQuality(cqi)
}
func (e *RTCEngine) OnRoomUpdate(room *livekit.Room) {
e.engineHandler.OnRoomUpdate(room)
}
func (e *RTCEngine) OnRoomMoved(moved *livekit.RoomMovedResponse) {
e.engineHandler.OnRoomMoved(moved)
}
func (e *RTCEngine) OnTrackRemoteMuted(request *livekit.MuteTrackRequest) {
e.engineHandler.OnTrackRemoteMuted(request)
}
func (e *RTCEngine) OnTokenRefresh(refreshToken string) {
e.token.Store(refreshToken)
}
func (e *RTCEngine) OnLeave(leave *livekit.LeaveRequest) {
e.log.Debugw("received leave request", "action", leave.GetAction())
switch leave.GetAction() {
case livekit.LeaveRequest_DISCONNECT:
e.Close()
reason := leave.GetReason()
e.log.Infow("server initiated leave", "reason", reason)
e.engineHandler.OnDisconnected(GetDisconnectionReason(reason))
case livekit.LeaveRequest_RECONNECT:
e.handleDisconnect(true)
case livekit.LeaveRequest_RESUME:
e.handleDisconnect(false)
default:
}
}
func (e *RTCEngine) OnLocalTrackSubscribed(trackSubscribed *livekit.TrackSubscribed) {
e.engineHandler.OnLocalTrackSubscribed(trackSubscribed)
}
func (e *RTCEngine) OnSubscribedQualityUpdate(subscribedQualityUpdate *livekit.SubscribedQualityUpdate) {
e.engineHandler.OnSubscribedQualityUpdate(subscribedQualityUpdate)
}
func (e *RTCEngine) OnSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate *livekit.SubscribedAudioCodecUpdate) {
e.engineHandler.OnSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate)
}
func (e *RTCEngine) OnMediaSectionsRequirement(mediaSectionsRequirement *livekit.MediaSectionsRequirement) {
e.engineHandler.OnMediaSectionsRequirement(mediaSectionsRequirement)
}
// ------------------------------------
func setConfiguration(pcTransport *PCTransport, configuration webrtc.Configuration) {
if pcTransport != nil {
pcTransport.SetConfiguration(configuration)
}
}