Skip to content

Commit 2211bf2

Browse files
Adding a handler for end of calls (#393)
* Adding a handler for end of calls * Fixing bugs
1 parent cb96d58 commit 2211bf2

File tree

5 files changed

+93
-0
lines changed

5 files changed

+93
-0
lines changed

pkg/service/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,7 @@ func (s *Service) DeregisterTransferSIPParticipantTopic(sipCallId string) {
218218
s.rpcSIPServer.DeregisterTransferSIPParticipantTopic(sipCallId)
219219
}
220220
}
221+
222+
func (s *Service) OnCallEnd(ctx context.Context, callInfo *livekit.SIPCallInfo, reason string) {
223+
s.log.Infow("SIP call ended", "callID", callInfo.CallId, "reason", reason)
224+
}

pkg/sip/inbound.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,11 @@ func (c *inboundCall) close(error bool, status CallStatus, reason string) {
825825

826826
c.s.DeregisterTransferSIPParticipant(c.cc.ID())
827827

828+
// Call the handler asynchronously to avoid blocking
829+
if c.s.handler != nil {
830+
go c.s.handler.OnCallEnd(context.Background(), c.state.callInfo, reason)
831+
}
832+
828833
c.cancel()
829834
}
830835

pkg/sip/outbound.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ func (c *outboundCall) close(err error, status CallStatus, description string, r
298298
c.c.cmu.Unlock()
299299

300300
c.c.DeregisterTransferSIPParticipant(string(c.cc.ID()))
301+
302+
// Call the handler asynchronously to avoid blocking
303+
if c.c.handler != nil {
304+
go c.c.handler.OnCallEnd(context.Background(), c.state.callInfo, description)
305+
}
301306
})
302307
}
303308

pkg/sip/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ type Handler interface {
106106

107107
RegisterTransferSIPParticipantTopic(sipCallId string) error
108108
DeregisterTransferSIPParticipantTopic(sipCallId string)
109+
110+
OnCallEnd(ctx context.Context, callInfo *livekit.SIPCallInfo, reason string)
109111
}
110112

111113
type Server struct {

pkg/sip/service_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func expectNoResponse(t *testing.T, tx sip.ClientTransaction) {
5757
type TestHandler struct {
5858
GetAuthCredentialsFunc func(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error)
5959
DispatchCallFunc func(ctx context.Context, info *CallInfo) CallDispatch
60+
OnCallEndFunc func(ctx context.Context, callInfo *livekit.SIPCallInfo, reason string)
6061
}
6162

6263
func (h TestHandler) GetAuthCredentials(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error) {
@@ -80,6 +81,12 @@ func (h TestHandler) DeregisterTransferSIPParticipantTopic(sipCallId string) {
8081
// no-op
8182
}
8283

84+
func (h TestHandler) OnCallEnd(ctx context.Context, callInfo *livekit.SIPCallInfo, reason string) {
85+
if h.OnCallEndFunc != nil {
86+
h.OnCallEndFunc(ctx, callInfo, reason)
87+
}
88+
}
89+
8390
func testInvite(t *testing.T, h Handler, hidden bool, from, to string, test func(tx sip.ClientTransaction)) {
8491
sipPort := rand.Intn(testPortSIPMax-testPortSIPMin) + testPortSIPMin
8592
localIP, err := config.GetLocalIP()
@@ -169,3 +176,73 @@ func TestService_AuthDrop(t *testing.T) {
169176
expectNoResponse(t, tx)
170177
})
171178
}
179+
180+
func TestService_OnCallEnd(t *testing.T) {
181+
const (
182+
expectedCallID = "test-call-id"
183+
expectedReason = "test-reason"
184+
)
185+
186+
callEnded := make(chan struct{})
187+
var receivedCallInfo *livekit.SIPCallInfo
188+
var receivedReason string
189+
190+
h := &TestHandler{
191+
GetAuthCredentialsFunc: func(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error) {
192+
return AuthInfo{Result: AuthAccept}, nil
193+
},
194+
DispatchCallFunc: func(ctx context.Context, info *CallInfo) CallDispatch {
195+
return CallDispatch{
196+
Result: DispatchAccept,
197+
Room: RoomConfig{
198+
RoomName: "test-room",
199+
Participant: ParticipantConfig{
200+
Identity: "test-participant",
201+
},
202+
},
203+
}
204+
},
205+
OnCallEndFunc: func(ctx context.Context, callInfo *livekit.SIPCallInfo, reason string) {
206+
receivedCallInfo = callInfo
207+
receivedReason = reason
208+
close(callEnded)
209+
},
210+
}
211+
212+
// Create a new service
213+
sipPort := rand.Intn(testPortSIPMax-testPortSIPMin) + testPortSIPMin
214+
215+
mon, err := stats.NewMonitor(&config.Config{MaxCpuUtilization: 0.9})
216+
require.NoError(t, err)
217+
218+
log := logger.NewTestLogger(t)
219+
s, err := NewService("", &config.Config{
220+
SIPPort: sipPort,
221+
SIPPortListen: sipPort,
222+
RTPPort: rtcconfig.PortRange{Start: testPortRTPMin, End: testPortRTPMax},
223+
}, mon, log, func(projectID string) rpc.IOInfoClient { return nil })
224+
require.NoError(t, err)
225+
require.NotNil(t, s)
226+
t.Cleanup(s.Stop)
227+
228+
s.SetHandler(h)
229+
require.NoError(t, s.Start())
230+
231+
// Call OnCallEnd directly
232+
h.OnCallEnd(context.Background(), &livekit.SIPCallInfo{
233+
CallId: expectedCallID,
234+
}, expectedReason)
235+
236+
// Wait for OnCallEnd to be called
237+
select {
238+
case <-callEnded:
239+
// Success
240+
case <-time.After(time.Second):
241+
t.Fatal("OnCallEnd was not called")
242+
}
243+
244+
// Verify the parameters passed to OnCallEnd
245+
require.NotNil(t, receivedCallInfo, "CallInfo should not be nil")
246+
require.Equal(t, expectedCallID, receivedCallInfo.CallId, "CallInfo.CallId should match")
247+
require.Equal(t, expectedReason, receivedReason, "Reason should match")
248+
}

0 commit comments

Comments
 (0)