mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-08 11:35:16 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
708
transport/call/livekit/messages.go
Normal file
708
transport/call/livekit/messages.go
Normal file
@@ -0,0 +1,708 @@
|
||||
package livekit
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
signalReqOffer = 1
|
||||
signalReqAnswer = 2
|
||||
signalReqTrickle = 3
|
||||
signalReqAddTrack = 4
|
||||
signalReqLeave = 8
|
||||
signalReqPingLegacy = 14
|
||||
signalReqPingReq = 16
|
||||
|
||||
signalRespJoin = 1
|
||||
signalRespAnswer = 2
|
||||
signalRespOffer = 3
|
||||
signalRespTrickle = 4
|
||||
signalRespUpdate = 5
|
||||
signalRespTrackPublished = 6
|
||||
signalRespLeave = 8
|
||||
signalRespRoomUpdate = 11
|
||||
signalRespRefreshToken = 16
|
||||
signalRespPongResp = 20
|
||||
signalRespRequestResponse = 22
|
||||
signalRespTrackSubscribed = 23
|
||||
|
||||
sdpFieldType = 1
|
||||
sdpFieldSDP = 2
|
||||
sdpFieldID = 3
|
||||
|
||||
trickleFieldCandidate = 1
|
||||
trickleFieldTarget = 2
|
||||
trickleFieldFinal = 3
|
||||
|
||||
addTrackFieldCID = 1
|
||||
addTrackFieldName = 2
|
||||
addTrackFieldType = 3
|
||||
addTrackFieldWidth = 4
|
||||
addTrackFieldHeight = 5
|
||||
addTrackFieldSource = 8
|
||||
addTrackFieldLayers = 9
|
||||
|
||||
videoLayerFieldQuality = 1
|
||||
videoLayerFieldWidth = 2
|
||||
videoLayerFieldHeight = 3
|
||||
|
||||
videoQualityHigh = 2
|
||||
|
||||
joinFieldRoom = 1
|
||||
joinFieldParticipant = 2
|
||||
joinFieldOtherParticipants = 3
|
||||
joinFieldServerVersion = 4
|
||||
joinFieldICEServers = 5
|
||||
joinFieldSubscriberPrimary = 6
|
||||
joinFieldServerRegion = 9
|
||||
joinFieldPingTimeout = 10
|
||||
joinFieldPingInterval = 11
|
||||
|
||||
iceServerFieldURLs = 1
|
||||
iceServerFieldUsername = 2
|
||||
iceServerFieldCredential = 3
|
||||
|
||||
pingFieldTimestamp = 1
|
||||
pingFieldRTT = 2
|
||||
|
||||
dataPacketFieldKind = 1
|
||||
dataPacketFieldUser = 2
|
||||
|
||||
userPacketFieldPayload = 2
|
||||
|
||||
DataPacketKindReliable = 0
|
||||
DataPacketKindLossy = 1
|
||||
|
||||
leaveFieldCanReconnect = 1
|
||||
leaveFieldReason = 2
|
||||
leaveFieldAction = 3
|
||||
|
||||
roomFieldSID = 1
|
||||
roomFieldName = 2
|
||||
|
||||
participantFieldSID = 1
|
||||
participantFieldIdentity = 2
|
||||
participantFieldState = 3
|
||||
participantFieldName = 9
|
||||
|
||||
trackTypeAudio = 0
|
||||
trackTypeVideo = 1
|
||||
trackTypeData = 2
|
||||
|
||||
signalTargetPublisher = 0
|
||||
signalTargetSubscriber = 1
|
||||
|
||||
trackSourceCamera = 1
|
||||
trackSourceScreenShare = 3
|
||||
)
|
||||
|
||||
const (
|
||||
ParticipantStateJoining int32 = 0
|
||||
ParticipantStateJoined int32 = 1
|
||||
ParticipantStateActive int32 = 2
|
||||
ParticipantStateDisconnected int32 = 3
|
||||
)
|
||||
|
||||
var disconnectReasonNames = map[int]string{
|
||||
0: "UNKNOWN",
|
||||
1: "CLIENT_INITIATED",
|
||||
2: "DUPLICATE_IDENTITY",
|
||||
3: "SERVER_SHUTDOWN",
|
||||
4: "PARTICIPANT_REMOVED",
|
||||
5: "ROOM_DELETED",
|
||||
6: "STATE_MISMATCH",
|
||||
7: "JOIN_FAILURE",
|
||||
8: "MIGRATION",
|
||||
9: "SIGNAL_CLOSE",
|
||||
10: "ROOM_CLOSED",
|
||||
11: "USER_UNAVAILABLE",
|
||||
12: "USER_REJECTED",
|
||||
13: "SIP_TRUNK_FAILURE",
|
||||
14: "CONNECTION_TIMEOUT",
|
||||
15: "MEDIA_FAILURE",
|
||||
16: "AGENT_ERROR",
|
||||
}
|
||||
|
||||
var leaveActionNames = map[int]string{
|
||||
0: "DISCONNECT",
|
||||
1: "RESUME",
|
||||
2: "RECONNECT",
|
||||
}
|
||||
|
||||
type LeaveInfo struct {
|
||||
Reason int
|
||||
Action int
|
||||
}
|
||||
|
||||
type sessionDescription struct {
|
||||
Type string
|
||||
SDP string
|
||||
ID uint32
|
||||
}
|
||||
|
||||
type trickleMsg struct {
|
||||
CandidateInit string
|
||||
Target int
|
||||
Final bool
|
||||
}
|
||||
|
||||
type iceServer struct {
|
||||
URLs []string
|
||||
Username string
|
||||
Credential string
|
||||
}
|
||||
|
||||
type joinResponse struct {
|
||||
RoomSID string
|
||||
RoomName string
|
||||
ParticipantSID string
|
||||
ParticipantID string
|
||||
ServerVersion string
|
||||
ServerRegion string
|
||||
ICEServers []iceServer
|
||||
SubscriberPrimary bool
|
||||
PingTimeoutSec int32
|
||||
PingIntervalSec int32
|
||||
}
|
||||
|
||||
type signalResponse struct {
|
||||
Kind int
|
||||
Join *joinResponse
|
||||
SDP *sessionDescription
|
||||
Trickle *trickleMsg
|
||||
Token string
|
||||
PongTime int64
|
||||
Leave *LeaveInfo
|
||||
Participants []ParticipantInfo
|
||||
}
|
||||
|
||||
type ParticipantInfo struct {
|
||||
SID string
|
||||
Identity string
|
||||
State int32
|
||||
Name string
|
||||
}
|
||||
|
||||
func DisconnectReasonName(code int) string {
|
||||
if name, ok := disconnectReasonNames[code]; ok {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("CODE_%d", code)
|
||||
}
|
||||
|
||||
func LeaveActionName(code int) string {
|
||||
if name, ok := leaveActionNames[code]; ok {
|
||||
return name
|
||||
}
|
||||
return fmt.Sprintf("CODE_%d", code)
|
||||
}
|
||||
|
||||
func DecodeLeaveRequest(data []byte) LeaveInfo {
|
||||
r := pbReader{buf: data}
|
||||
var li LeaveInfo
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return li
|
||||
}
|
||||
switch {
|
||||
case field == leaveFieldReason && wire == wireVarint:
|
||||
v, _ := r.varint()
|
||||
li.Reason = int(v)
|
||||
case field == leaveFieldAction && wire == wireVarint:
|
||||
v, _ := r.varint()
|
||||
li.Action = int(v)
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return li
|
||||
}
|
||||
}
|
||||
}
|
||||
return li
|
||||
}
|
||||
|
||||
func EncodeDataPacketUser(payload []byte, kind int) []byte {
|
||||
w := pbWriter{}
|
||||
if kind != 0 {
|
||||
w.int32(dataPacketFieldKind, int32(kind))
|
||||
}
|
||||
w.message(dataPacketFieldUser, encUserPacket(payload))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func DecodeDataPacketUser(data []byte) ([]byte, bool) {
|
||||
r := pbReader{buf: data}
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if field == dataPacketFieldUser && wire == wireBytes {
|
||||
inner, err := r.bytes()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
ur := pbReader{buf: inner}
|
||||
for !ur.eof() {
|
||||
ufield, uwire, uerr := ur.tag()
|
||||
if uerr != nil {
|
||||
return nil, false
|
||||
}
|
||||
if ufield == userPacketFieldPayload && uwire == wireBytes {
|
||||
payload, perr := ur.bytes()
|
||||
if perr != nil {
|
||||
return nil, false
|
||||
}
|
||||
out := make([]byte, len(payload))
|
||||
copy(out, payload)
|
||||
return out, true
|
||||
}
|
||||
if err := ur.skipWire(uwire); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func DecodeParticipantInfo(data []byte) ParticipantInfo {
|
||||
r := pbReader{buf: data}
|
||||
var info ParticipantInfo
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return info
|
||||
}
|
||||
switch {
|
||||
case field == participantFieldSID && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return info
|
||||
}
|
||||
info.SID = string(b)
|
||||
case field == participantFieldIdentity && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return info
|
||||
}
|
||||
info.Identity = string(b)
|
||||
case field == participantFieldState && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return info
|
||||
}
|
||||
info.State = int32(v)
|
||||
case field == participantFieldName && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return info
|
||||
}
|
||||
info.Name = string(b)
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return info
|
||||
}
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func DecodeParticipantUpdate(data []byte) []ParticipantInfo {
|
||||
r := pbReader{buf: data}
|
||||
var out []ParticipantInfo
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
if field == 1 && wire == wireBytes {
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
out = append(out, DecodeParticipantInfo(b))
|
||||
} else {
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func encSessionDescription(sd sessionDescription) []byte {
|
||||
w := pbWriter{}
|
||||
if sd.Type != "" {
|
||||
w.string(sdpFieldType, sd.Type)
|
||||
}
|
||||
if sd.SDP != "" {
|
||||
w.string(sdpFieldSDP, sd.SDP)
|
||||
}
|
||||
if sd.ID != 0 {
|
||||
w.uint32(sdpFieldID, sd.ID)
|
||||
}
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encTrickle(m trickleMsg) []byte {
|
||||
w := pbWriter{}
|
||||
w.string(trickleFieldCandidate, m.CandidateInit)
|
||||
w.int32(trickleFieldTarget, int32(m.Target))
|
||||
if m.Final {
|
||||
w.bool(trickleFieldFinal, true)
|
||||
}
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encVideoLayer(quality, width, height uint32) []byte {
|
||||
w := pbWriter{}
|
||||
if quality != 0 {
|
||||
w.uint32(videoLayerFieldQuality, quality)
|
||||
}
|
||||
if width != 0 {
|
||||
w.uint32(videoLayerFieldWidth, width)
|
||||
}
|
||||
if height != 0 {
|
||||
w.uint32(videoLayerFieldHeight, height)
|
||||
}
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encAddTrack(cid, name string, trackType, source int, width, height uint32) []byte {
|
||||
w := pbWriter{}
|
||||
w.string(addTrackFieldCID, cid)
|
||||
w.string(addTrackFieldName, name)
|
||||
w.int32(addTrackFieldType, int32(trackType))
|
||||
if width != 0 {
|
||||
w.uint32(addTrackFieldWidth, width)
|
||||
}
|
||||
if height != 0 {
|
||||
w.uint32(addTrackFieldHeight, height)
|
||||
}
|
||||
w.int32(addTrackFieldSource, int32(source))
|
||||
if trackType == trackTypeVideo {
|
||||
w.message(addTrackFieldLayers, encVideoLayer(videoQualityHigh, width, height))
|
||||
}
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encPing(timestamp int64) []byte {
|
||||
w := pbWriter{}
|
||||
w.int64(pingFieldTimestamp, timestamp)
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encUserPacket(payload []byte) []byte {
|
||||
w := pbWriter{}
|
||||
w.bytes(userPacketFieldPayload, payload)
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestOffer(sd sessionDescription) []byte {
|
||||
w := pbWriter{}
|
||||
w.message(signalReqOffer, encSessionDescription(sd))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestAnswer(sd sessionDescription) []byte {
|
||||
w := pbWriter{}
|
||||
w.message(signalReqAnswer, encSessionDescription(sd))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestTrickle(m trickleMsg) []byte {
|
||||
w := pbWriter{}
|
||||
w.message(signalReqTrickle, encTrickle(m))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestAddTrack(cid, name string, trackType, source int, width, height uint32) []byte {
|
||||
w := pbWriter{}
|
||||
w.message(signalReqAddTrack, encAddTrack(cid, name, trackType, source, width, height))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestLeave() []byte {
|
||||
w := pbWriter{}
|
||||
w.message(signalReqLeave, []byte{})
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func encSignalRequestPing(timestamp int64) []byte {
|
||||
w := pbWriter{}
|
||||
w.int64(signalReqPingLegacy, timestamp)
|
||||
w.message(signalReqPingReq, encPing(timestamp))
|
||||
return w.buf
|
||||
}
|
||||
|
||||
func decSessionDescription(data []byte) (sessionDescription, error) {
|
||||
r := pbReader{buf: data}
|
||||
var sd sessionDescription
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return sd, err
|
||||
}
|
||||
switch {
|
||||
case field == sdpFieldType && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return sd, err
|
||||
}
|
||||
sd.Type = string(b)
|
||||
case field == sdpFieldSDP && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return sd, err
|
||||
}
|
||||
sd.SDP = string(b)
|
||||
case field == sdpFieldID && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return sd, err
|
||||
}
|
||||
sd.ID = uint32(v)
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return sd, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func decTrickle(data []byte) (trickleMsg, error) {
|
||||
r := pbReader{buf: data}
|
||||
var m trickleMsg
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
switch {
|
||||
case field == trickleFieldCandidate && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.CandidateInit = string(b)
|
||||
case field == trickleFieldTarget && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.Target = int(v)
|
||||
case field == trickleFieldFinal && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
m.Final = v != 0
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return m, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func decICEServer(data []byte) (iceServer, error) {
|
||||
r := pbReader{buf: data}
|
||||
var s iceServer
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
switch {
|
||||
case field == iceServerFieldURLs && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.URLs = append(s.URLs, string(b))
|
||||
case field == iceServerFieldUsername && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.Username = string(b)
|
||||
case field == iceServerFieldCredential && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.Credential = string(b)
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func decRoom(data []byte) (string, string, error) {
|
||||
r := pbReader{buf: data}
|
||||
var sid, name string
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
switch {
|
||||
case field == roomFieldSID && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
sid = string(b)
|
||||
case field == roomFieldName && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
name = string(b)
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return sid, name, nil
|
||||
}
|
||||
|
||||
func decParticipant(data []byte) (string, string, error) {
|
||||
info := DecodeParticipantInfo(data)
|
||||
return info.SID, info.Identity, nil
|
||||
}
|
||||
|
||||
func decJoinResponse(data []byte) (joinResponse, error) {
|
||||
r := pbReader{buf: data}
|
||||
var jr joinResponse
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
switch {
|
||||
case field == joinFieldRoom && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
sid, name, _ := decRoom(b)
|
||||
jr.RoomSID = sid
|
||||
jr.RoomName = name
|
||||
case field == joinFieldParticipant && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
sid, identity, _ := decParticipant(b)
|
||||
jr.ParticipantSID = sid
|
||||
jr.ParticipantID = identity
|
||||
case field == joinFieldServerVersion && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
jr.ServerVersion = string(b)
|
||||
case field == joinFieldICEServers && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
s, _ := decICEServer(b)
|
||||
jr.ICEServers = append(jr.ICEServers, s)
|
||||
case field == joinFieldSubscriberPrimary && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
jr.SubscriberPrimary = v != 0
|
||||
case field == joinFieldServerRegion && wire == wireBytes:
|
||||
b, err := r.bytes()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
jr.ServerRegion = string(b)
|
||||
case field == joinFieldPingTimeout && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
jr.PingTimeoutSec = int32(v)
|
||||
case field == joinFieldPingInterval && wire == wireVarint:
|
||||
v, err := r.varint()
|
||||
if err != nil {
|
||||
return jr, err
|
||||
}
|
||||
jr.PingIntervalSec = int32(v)
|
||||
case field == joinFieldOtherParticipants && wire == wireBytes:
|
||||
if _, err := r.bytes(); err != nil {
|
||||
return jr, err
|
||||
}
|
||||
default:
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return jr, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return jr, nil
|
||||
}
|
||||
|
||||
func decSignalResponse(data []byte) (signalResponse, error) {
|
||||
r := pbReader{buf: data}
|
||||
var sr signalResponse
|
||||
for !r.eof() {
|
||||
field, wire, err := r.tag()
|
||||
if err != nil {
|
||||
return sr, err
|
||||
}
|
||||
if wire != wireBytes {
|
||||
if err := r.skipWire(wire); err != nil {
|
||||
return sr, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
inner, err := r.bytes()
|
||||
if err != nil {
|
||||
return sr, err
|
||||
}
|
||||
sr.Kind = int(field)
|
||||
switch field {
|
||||
case signalRespJoin:
|
||||
jr, err := decJoinResponse(inner)
|
||||
if err != nil {
|
||||
return sr, err
|
||||
}
|
||||
sr.Join = &jr
|
||||
case signalRespAnswer, signalRespOffer:
|
||||
sd, err := decSessionDescription(inner)
|
||||
if err != nil {
|
||||
return sr, err
|
||||
}
|
||||
sr.SDP = &sd
|
||||
case signalRespTrickle:
|
||||
tm, err := decTrickle(inner)
|
||||
if err != nil {
|
||||
return sr, err
|
||||
}
|
||||
sr.Trickle = &tm
|
||||
case signalRespRefreshToken:
|
||||
sr.Token = string(inner)
|
||||
case signalRespLeave:
|
||||
li := DecodeLeaveRequest(inner)
|
||||
sr.Leave = &li
|
||||
case signalRespUpdate:
|
||||
sr.Participants = DecodeParticipantUpdate(inner)
|
||||
}
|
||||
return sr, nil
|
||||
}
|
||||
return sr, nil
|
||||
}
|
||||
Reference in New Issue
Block a user