mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 20:17:08 +00:00
207 lines
5.5 KiB
Go
207 lines
5.5 KiB
Go
package tgsig
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"dev.narayana.im/narayana/telegabber/calls/signaling"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/zelenin/go-tdlib/client"
|
|
)
|
|
|
|
// runs for a brand-new inbound Pending; must build the bridge, register
|
|
// the Callee with Manager.Register, and start the bridge; false drops the call
|
|
type IncomingCallHandler func(jid string, callID int32, userID int64, isVideo bool) (ok bool)
|
|
|
|
// router for tdlib call updates; callIDs are per-tdlib-client so key is (jid, callID)
|
|
type Manager struct {
|
|
mu sync.Mutex
|
|
sides map[managerKey]*callBase
|
|
incoming IncomingCallHandler
|
|
}
|
|
|
|
type managerKey struct {
|
|
jid string
|
|
callID int32
|
|
}
|
|
|
|
// h may be nil and set later via SetIncomingHandler
|
|
func NewManager(h IncomingCallHandler) *Manager {
|
|
return &Manager{
|
|
sides: make(map[managerKey]*callBase),
|
|
incoming: h,
|
|
}
|
|
}
|
|
|
|
func (m *Manager) SetIncomingHandler(h IncomingCallHandler) {
|
|
m.mu.Lock()
|
|
m.incoming = h
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Manager) incomingHandler() IncomingCallHandler {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.incoming
|
|
}
|
|
|
|
func (m *Manager) Register(jid string, callID int32, s *callBase) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
m.mu.Lock()
|
|
m.sides[managerKey{jid, callID}] = s
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Manager) Drop(jid string, callID int32) {
|
|
m.mu.Lock()
|
|
delete(m.sides, managerKey{jid, callID})
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
func (m *Manager) lookup(jid string, callID int32) *callBase {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.sides[managerKey{jid, callID}]
|
|
}
|
|
|
|
func (m *Manager) OnUpdateCall(jid string, u *client.UpdateCall) {
|
|
call := u.Call
|
|
entry := log.WithFields(log.Fields{
|
|
"jid": jid,
|
|
"call_id": call.Id,
|
|
"user_id": call.UserId,
|
|
"is_outgoing": call.IsOutgoing,
|
|
"is_video": call.IsVideo,
|
|
})
|
|
|
|
switch call.State.CallStateType() {
|
|
case client.TypeCallStatePending:
|
|
entry.Info("Call pending")
|
|
if call.IsOutgoing {
|
|
// Pending fires twice: server-created (IsReceived=false) and
|
|
// peer-notified (IsReceived=true); only the second is ringing.
|
|
// Near-instant pickups may skip the second one - the bridge
|
|
// state guard at ExchangingKeys covers that.
|
|
pending, _ := call.State.(*client.CallStatePending)
|
|
if pending == nil || !pending.IsReceived {
|
|
return
|
|
}
|
|
if b := m.bridgeFor(jid, call.Id); b != nil {
|
|
b.Ringing()
|
|
}
|
|
return
|
|
}
|
|
if existing := m.lookup(jid, call.Id); existing != nil {
|
|
// duplicate Pending
|
|
return
|
|
}
|
|
h := m.incomingHandler()
|
|
if h == nil {
|
|
entry.Warn("Inbound call but no incoming-call handler wired; ignoring")
|
|
return
|
|
}
|
|
if ok := h(jid, call.Id, call.UserId, call.IsVideo); !ok {
|
|
entry.Warn("Incoming-call handler dropped the call")
|
|
}
|
|
|
|
case client.TypeCallStateExchangingKeys:
|
|
entry.Info("Call exchanging keys")
|
|
// outbound: peer accepted, bridge fires Callee.Accept on xmpp side
|
|
// inbound: AcceptCall already ran, state guard makes this a no-op
|
|
if b := m.bridgeFor(jid, call.Id); b != nil {
|
|
b.CalleeAccepted()
|
|
}
|
|
|
|
case client.TypeCallStateReady:
|
|
state, ok := call.State.(*client.CallStateReady)
|
|
if !ok {
|
|
entry.Error("Failed to cast CallStateReady")
|
|
return
|
|
}
|
|
entry.Info("Call ready, setting up P2P connection")
|
|
s := m.lookup(jid, call.Id)
|
|
if s == nil {
|
|
entry.Warn("Ready for unknown call")
|
|
return
|
|
}
|
|
s.onReady(state, call.IsOutgoing, entry)
|
|
|
|
case client.TypeCallStateHangingUp:
|
|
entry.Info("Call hanging up")
|
|
m.endCall(jid, call.Id, signaling.ReasonHangup)
|
|
|
|
case client.TypeCallStateDiscarded:
|
|
reason := "unknown"
|
|
if disc, ok := call.State.(*client.CallStateDiscarded); ok && disc.Reason != nil {
|
|
reason = disc.Reason.CallDiscardReasonType()
|
|
}
|
|
entry.WithField("reason", reason).Info("Call discarded")
|
|
m.endCall(jid, call.Id, mapDiscardReason(reason))
|
|
|
|
case client.TypeCallStateError:
|
|
errMsg := "unknown"
|
|
if errState, ok := call.State.(*client.CallStateError); ok && errState.Error != nil {
|
|
errMsg = errState.Error.Message
|
|
}
|
|
entry.WithField("error", errMsg).Error("Call error")
|
|
m.endCall(jid, call.Id, signaling.ReasonMediaFailed)
|
|
}
|
|
}
|
|
|
|
// forwards tdlib signaling data into ntgcalls
|
|
func (m *Manager) OnNewSignalingData(jid string, u *client.UpdateNewCallSignalingData) {
|
|
s := m.lookup(jid, u.CallId)
|
|
if s == nil {
|
|
log.WithFields(log.Fields{
|
|
"jid": jid,
|
|
"call_id": u.CallId,
|
|
}).Warn("Signaling data for unknown call")
|
|
return
|
|
}
|
|
s.adapter.withCallContext(func() {
|
|
if err := s.adapter.ntg.SendSignalingData(s.userID, u.Data); err != nil {
|
|
log.WithFields(log.Fields{
|
|
"jid": jid,
|
|
"call_id": u.CallId,
|
|
"user_id": s.userID,
|
|
}).WithError(err).Error("Failed to forward signaling data to ntgcalls")
|
|
}
|
|
})
|
|
}
|
|
|
|
func (m *Manager) bridgeFor(jid string, callID int32) *signaling.Bridge {
|
|
s := m.lookup(jid, callID)
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return s.getBridge()
|
|
}
|
|
|
|
// unified teardown for HangingUp / Discarded / Error
|
|
func (m *Manager) endCall(jid string, callID int32, reason signaling.TerminationReason) {
|
|
s := m.lookup(jid, callID)
|
|
if s == nil {
|
|
return
|
|
}
|
|
if b := s.getBridge(); b != nil {
|
|
b.Terminate(reason)
|
|
}
|
|
s.cleanup()
|
|
}
|
|
|
|
func mapDiscardReason(tdlibReason string) signaling.TerminationReason {
|
|
switch tdlibReason {
|
|
case "callDiscardReasonDeclined":
|
|
return signaling.ReasonDecline
|
|
case "callDiscardReasonMissed", "callDiscardReasonExpired":
|
|
return signaling.ReasonRingTimeout
|
|
case "callDiscardReasonDisconnected":
|
|
return signaling.ReasonMediaFailed
|
|
case "callDiscardReasonHungUp", "callDiscardReasonEmpty":
|
|
return signaling.ReasonHangup
|
|
default:
|
|
return signaling.ReasonUnknown
|
|
}
|
|
}
|