Skip to content

Commit 9861143

Browse files
committed
Add initial implementation of Hold/Resume call
1 parent 2bb5e36 commit 9861143

File tree

2 files changed

+431
-0
lines changed

2 files changed

+431
-0
lines changed

pkg/sip/inbound.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
"github.com/frostbyte73/core"
3535
"github.com/icholy/digest"
36+
psdp "github.com/pion/sdp/v3"
3637
"github.com/pkg/errors"
3738

3839
"github.com/livekit/media-sdk/dtmf"
@@ -907,6 +908,7 @@ func (c *inboundCall) runMediaConn(offerData []byte, enc livekit.SIPMediaEncrypt
907908
if err != nil {
908909
return nil, err
909910
}
911+
c.cc.nextSDPVersion = answer.SDP.Origin.SessionVersion + 1
910912
c.mon.SDPSize(len(answerData), false)
911913
c.log.Debugw("SDP answer", "sdp", string(answerData))
912914

@@ -1321,6 +1323,48 @@ func (c *inboundCall) transferCall(ctx context.Context, transferTo string, heade
13211323

13221324
}
13231325

1326+
func (c *inboundCall) holdCall(ctx context.Context) error {
1327+
c.log.Infow("holding inbound call")
1328+
1329+
// Disable media timeout during hold to prevent call termination
1330+
if c.media != nil {
1331+
c.media.EnableTimeout(false)
1332+
c.log.Infow("media timeout disabled for hold")
1333+
}
1334+
1335+
err := c.cc.holdCall(ctx)
1336+
if err != nil {
1337+
c.log.Infow("inbound call failed to hold", "error", err)
1338+
// Re-enable timeout if hold failed
1339+
if c.media != nil {
1340+
c.media.EnableTimeout(true)
1341+
}
1342+
return err
1343+
}
1344+
1345+
c.log.Infow("inbound call held")
1346+
return nil
1347+
}
1348+
1349+
func (c *inboundCall) unholdCall(ctx context.Context) error {
1350+
c.log.Infow("unholding inbound call")
1351+
1352+
err := c.cc.unholdCall(ctx)
1353+
if err != nil {
1354+
c.log.Infow("inbound call failed to unhold", "error", err)
1355+
return err
1356+
}
1357+
1358+
// Re-enable media timeout after unhold
1359+
if c.media != nil {
1360+
c.media.EnableTimeout(true)
1361+
c.log.Infow("media timeout re-enabled after unhold")
1362+
}
1363+
1364+
c.log.Infow("inbound call unheld")
1365+
return nil
1366+
}
1367+
13241368
func (s *Server) newInbound(log logger.Logger, id LocalTag, contact URI, invite *sip.Request, inviteTx sip.ServerTransaction, getHeaders setHeadersFunc) *sipInbound {
13251369
c := &sipInbound{
13261370
log: log,
@@ -1374,6 +1418,7 @@ type sipInbound struct {
13741418
ringing chan struct{}
13751419
acked core.Fuse
13761420
setHeaders setHeadersFunc
1421+
nextSDPVersion uint64
13771422
}
13781423

13791424
func (c *sipInbound) ValidateInvite() error {
@@ -1830,3 +1875,169 @@ func (c *sipInbound) CloseWithStatus(code sip.StatusCode, status string) {
18301875
c.drop()
18311876
}
18321877
}
1878+
1879+
func (c *sipInbound) setMediaDirection(sdpData []byte, direction string) ([]byte, error) {
1880+
1881+
if len(sdpData) == 0 {
1882+
return sdpData, nil
1883+
}
1884+
1885+
// Parse SDP using the base Parse function (works for both offers and answers)
1886+
desc, err := sdp.Parse(sdpData)
1887+
if err != nil {
1888+
return nil, fmt.Errorf("failed to parse SDP: %w", err)
1889+
}
1890+
1891+
// Modify direction attributes in each media description
1892+
for _, mediaDesc := range desc.SDP.MediaDescriptions {
1893+
if mediaDesc == nil {
1894+
continue
1895+
}
1896+
1897+
// Find and remove existing direction attributes
1898+
var newAttributes []psdp.Attribute
1899+
for _, attr := range mediaDesc.Attributes {
1900+
// Keep all attributes except direction-related ones
1901+
if attr.Key != "sendrecv" && attr.Key != "sendonly" &&
1902+
attr.Key != "recvonly" && attr.Key != "inactive" {
1903+
newAttributes = append(newAttributes, attr)
1904+
}
1905+
}
1906+
1907+
// Add the new direction attribute
1908+
newAttributes = append(newAttributes, psdp.Attribute{
1909+
Key: direction,
1910+
Value: "",
1911+
})
1912+
1913+
mediaDesc.Attributes = newAttributes
1914+
}
1915+
1916+
// Set session version to current value plus current unix timestamp
1917+
desc.SDP.Origin.SessionVersion = c.nextSDPVersion
1918+
c.nextSDPVersion += 1
1919+
1920+
// Marshal back to bytes
1921+
modifiedSDP, err := desc.SDP.Marshal()
1922+
if err != nil {
1923+
return nil, fmt.Errorf("failed to marshal modified SDP: %w", err)
1924+
}
1925+
1926+
return modifiedSDP, nil
1927+
}
1928+
1929+
func (c *sipInbound) holdCall(ctx context.Context) error {
1930+
c.mu.Lock()
1931+
1932+
if c.invite == nil || c.inviteOk == nil {
1933+
c.mu.Unlock()
1934+
return psrpc.NewErrorf(psrpc.FailedPrecondition, "can't hold non established call")
1935+
}
1936+
1937+
// Create INVITE with SDP modified for hold (a=sendonly)
1938+
req := sip.NewRequest(sip.INVITE, c.invite.Recipient)
1939+
c.setCSeq(req)
1940+
1941+
// Copy headers from original INVITE
1942+
req.AppendHeader(c.invite.From())
1943+
req.AppendHeader(c.invite.To())
1944+
req.AppendHeader(c.invite.CallID())
1945+
req.AppendHeader(c.contact)
1946+
req.AppendHeader(sip.NewHeader("Content-Type", "application/sdp"))
1947+
req.AppendHeader(sip.NewHeader("Allow", "INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE"))
1948+
1949+
// Modify SDP to set direction to sendonly (hold)
1950+
sdpOffer := c.inviteOk.Body()
1951+
if len(sdpOffer) > 0 {
1952+
modifiedSDP, err := c.setMediaDirection(sdpOffer, "sendonly")
1953+
if err != nil {
1954+
return err
1955+
}
1956+
req.SetBody(modifiedSDP)
1957+
}
1958+
1959+
c.swapSrcDst(req)
1960+
c.mu.Unlock()
1961+
1962+
// Send the INVITE request
1963+
tx, err := c.Transaction(req)
1964+
if err != nil {
1965+
return err
1966+
}
1967+
defer tx.Terminate()
1968+
1969+
resp, err := sipResponse(ctx, tx, c.s.closing.Watch(), nil)
1970+
if err != nil {
1971+
return err
1972+
}
1973+
1974+
if resp.StatusCode != sip.StatusOK {
1975+
return &livekit.SIPStatus{Code: livekit.SIPStatusCode(resp.StatusCode)}
1976+
}
1977+
1978+
// Send ACK for the hold INVITE
1979+
ack := sip.NewAckRequest(req, resp, nil)
1980+
if err := c.WriteRequest(ack); err != nil {
1981+
return err
1982+
}
1983+
1984+
return nil
1985+
}
1986+
1987+
func (c *sipInbound) unholdCall(ctx context.Context) error {
1988+
c.mu.Lock()
1989+
1990+
if c.invite == nil || c.inviteOk == nil {
1991+
c.mu.Unlock()
1992+
return psrpc.NewErrorf(psrpc.FailedPrecondition, "can't unhold non established call")
1993+
}
1994+
1995+
// Create INVITE with SDP modified for unhold (a=sendrecv)
1996+
req := sip.NewRequest(sip.INVITE, c.invite.Recipient)
1997+
c.setCSeq(req)
1998+
1999+
// Copy headers from original INVITE
2000+
req.AppendHeader(c.invite.From())
2001+
req.AppendHeader(c.invite.To())
2002+
req.AppendHeader(c.invite.CallID())
2003+
req.AppendHeader(c.contact)
2004+
req.AppendHeader(sip.NewHeader("Content-Type", "application/sdp"))
2005+
req.AppendHeader(sip.NewHeader("Allow", "INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE"))
2006+
2007+
// Modify SDP to set direction to sendrecv (unhold)
2008+
sdpOffer := c.inviteOk.Body()
2009+
if len(sdpOffer) > 0 {
2010+
modifiedSDP, err := c.setMediaDirection(sdpOffer, "sendrecv")
2011+
if err != nil {
2012+
return err
2013+
}
2014+
req.SetBody(modifiedSDP)
2015+
}
2016+
2017+
c.swapSrcDst(req)
2018+
c.mu.Unlock()
2019+
2020+
// Send the INVITE request
2021+
tx, err := c.Transaction(req)
2022+
if err != nil {
2023+
return err
2024+
}
2025+
defer tx.Terminate()
2026+
2027+
resp, err := sipResponse(ctx, tx, c.s.closing.Watch(), nil)
2028+
if err != nil {
2029+
return err
2030+
}
2031+
2032+
if resp.StatusCode != sip.StatusOK {
2033+
return &livekit.SIPStatus{Code: livekit.SIPStatusCode(resp.StatusCode)}
2034+
}
2035+
2036+
// Send ACK for the unhold INVITE
2037+
ack := sip.NewAckRequest(req, resp, nil)
2038+
if err := c.WriteRequest(ack); err != nil {
2039+
return err
2040+
}
2041+
2042+
return nil
2043+
}

0 commit comments

Comments
 (0)