Skip to content

Commit a672d07

Browse files
committed
Add initial implementation of Hold/Resume call
1 parent 050eeba commit a672d07

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

pkg/sip/inbound.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/frostbyte73/core"
3232
"github.com/icholy/digest"
33+
psdp "github.com/pion/sdp/v3"
3334
"github.com/pkg/errors"
3435

3536
msdk "github.com/livekit/media-sdk"
@@ -1003,6 +1004,7 @@ func (c *inboundCall) runMediaConn(tid traceid.ID, offerData []byte, enc livekit
10031004
if err != nil {
10041005
return nil, err
10051006
}
1007+
c.cc.nextSDPVersion = answer.SDP.Origin.SessionVersion + 1
10061008
c.mon.SDPSize(len(answerData), false)
10071009
c.log().Debugw("SDP answer", "sdp", string(answerData))
10081010

@@ -1421,6 +1423,48 @@ func (c *inboundCall) transferCall(ctx context.Context, transferTo string, heade
14211423

14221424
}
14231425

1426+
func (c *inboundCall) holdCall(ctx context.Context) error {
1427+
c.log.Infow("holding inbound call")
1428+
1429+
// Disable media timeout during hold to prevent call termination
1430+
if c.media != nil {
1431+
c.media.EnableTimeout(false)
1432+
c.log.Infow("media timeout disabled for hold")
1433+
}
1434+
1435+
err := c.cc.holdCall(ctx)
1436+
if err != nil {
1437+
c.log.Infow("inbound call failed to hold", "error", err)
1438+
// Re-enable timeout if hold failed
1439+
if c.media != nil {
1440+
c.media.EnableTimeout(true)
1441+
}
1442+
return err
1443+
}
1444+
1445+
c.log.Infow("inbound call held")
1446+
return nil
1447+
}
1448+
1449+
func (c *inboundCall) unholdCall(ctx context.Context) error {
1450+
c.log.Infow("unholding inbound call")
1451+
1452+
err := c.cc.unholdCall(ctx)
1453+
if err != nil {
1454+
c.log.Infow("inbound call failed to unhold", "error", err)
1455+
return err
1456+
}
1457+
1458+
// Re-enable media timeout after unhold
1459+
if c.media != nil {
1460+
c.media.EnableTimeout(true)
1461+
c.log.Infow("media timeout re-enabled after unhold")
1462+
}
1463+
1464+
c.log.Infow("inbound call unheld")
1465+
return nil
1466+
}
1467+
14241468
func (s *Server) newInbound(log logger.Logger, contact URI, invite *sip.Request, inviteTx sip.ServerTransaction, getHeaders setHeadersFunc) *sipInbound {
14251469
c := &sipInbound{
14261470
log: log,
@@ -1467,13 +1511,15 @@ type sipInbound struct {
14671511
referDone chan error
14681512

14691513
mu sync.RWMutex
1514+
sdpMu sync.RWMutex
14701515
lastSDP []byte
14711516
inviteOk *sip.Response
14721517
nextRequestCSeq uint32
14731518
referCseq uint32
14741519
ringing chan struct{}
14751520
acked core.Fuse
14761521
setHeaders setHeadersFunc
1522+
nextSDPVersion uint64
14771523
}
14781524

14791525
func (c *sipInbound) ValidateInvite() error {
@@ -1952,3 +1998,136 @@ func (c *sipInbound) CloseWithStatus(code sip.StatusCode, status string) {
19521998
c.drop()
19531999
}
19542000
}
2001+
2002+
func (c *sipInbound) setSDPMediaDirection(sdpData []byte, direction string) ([]byte, error) {
2003+
if len(sdpData) == 0 {
2004+
return sdpData, nil
2005+
}
2006+
2007+
// Parse SDP using the base Parse function (works for both offers and answers)
2008+
desc, err := sdp.Parse(sdpData)
2009+
if err != nil {
2010+
return nil, fmt.Errorf("failed to parse SDP: %w", err)
2011+
}
2012+
2013+
// Modify direction attributes in each media description
2014+
for _, mediaDesc := range desc.SDP.MediaDescriptions {
2015+
if mediaDesc == nil {
2016+
continue
2017+
}
2018+
2019+
// Find and remove existing direction attributes
2020+
newAttributes := slices.DeleteFunc(mediaDesc.Attributes, func(attr psdp.Attribute) bool {
2021+
switch attr.Key {
2022+
case "sendrecv", "sendonly", "recvonly", "inactive":
2023+
return true
2024+
default:
2025+
return false
2026+
}
2027+
})
2028+
2029+
// Add the new direction attribute
2030+
newAttributes = append(newAttributes, psdp.Attribute{
2031+
Key: direction,
2032+
Value: "",
2033+
})
2034+
2035+
mediaDesc.Attributes = newAttributes
2036+
}
2037+
2038+
// Set session version to current value plus current unix timestamp
2039+
desc.SDP.Origin.SessionVersion = c.nextSDPVersion
2040+
c.nextSDPVersion += 1
2041+
2042+
// Marshal back to bytes
2043+
modifiedSDP, err := desc.SDP.Marshal()
2044+
if err != nil {
2045+
return nil, fmt.Errorf("failed to marshal modified SDP: %w", err)
2046+
}
2047+
2048+
return modifiedSDP, nil
2049+
}
2050+
2051+
func (c *sipInbound) createMediaUpdateRequest(direction string) (*sip.Request, error) {
2052+
c.mu.Lock()
2053+
defer c.mu.Unlock()
2054+
c.sdpMu.Lock()
2055+
defer c.sdpMu.Unlock()
2056+
2057+
if c.invite == nil || c.inviteOk == nil {
2058+
return nil, psrpc.NewErrorf(psrpc.FailedPrecondition, "can't update media direction for non established call")
2059+
}
2060+
2061+
// Create INVITE with SDP modified for media update
2062+
req := sip.NewRequest(sip.INVITE, c.invite.Recipient)
2063+
c.setCSeq(req)
2064+
2065+
// Copy headers from original INVITE
2066+
req.AppendHeader(c.invite.From())
2067+
req.AppendHeader(c.invite.To())
2068+
req.AppendHeader(c.invite.CallID())
2069+
req.AppendHeader(c.contact)
2070+
req.AppendHeader(sip.NewHeader("Content-Type", "application/sdp"))
2071+
req.AppendHeader(sip.NewHeader("Allow", "INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE"))
2072+
2073+
// Modify SDP to set direction
2074+
sdpOffer := c.inviteOk.Body()
2075+
if len(sdpOffer) > 0 {
2076+
modifiedSDP, err := c.setSDPMediaDirection(sdpOffer, direction)
2077+
if err != nil {
2078+
return nil, err
2079+
}
2080+
req.SetBody(modifiedSDP)
2081+
}
2082+
2083+
c.swapSrcDst(req)
2084+
return req, nil
2085+
}
2086+
2087+
func (c *sipInbound) sendMediaUpdateRequest(ctx context.Context, direction string) error {
2088+
req, err := c.createMediaUpdateRequest(direction)
2089+
if err != nil {
2090+
return err
2091+
}
2092+
2093+
// Send the INVITE request
2094+
tx, err := c.Transaction(req)
2095+
if err != nil {
2096+
return err
2097+
}
2098+
defer tx.Terminate()
2099+
2100+
resp, err := sipResponse(ctx, tx, c.s.closing.Watch(), nil)
2101+
if err != nil {
2102+
return err
2103+
}
2104+
2105+
if resp.StatusCode != sip.StatusOK {
2106+
return &livekit.SIPStatus{
2107+
Code: livekit.SIPStatusCode(resp.StatusCode),
2108+
Status: resp.Reason,
2109+
}
2110+
}
2111+
2112+
// Send ACK for the unhold INVITE
2113+
ack := sip.NewAckRequest(req, resp, nil)
2114+
if err := c.WriteRequest(ack); err != nil {
2115+
return err
2116+
}
2117+
2118+
return nil
2119+
}
2120+
2121+
func (c *sipInbound) holdCall(ctx context.Context) error {
2122+
if err := c.sendMediaUpdateRequest(ctx, "sendonly"); err != nil {
2123+
return err
2124+
}
2125+
return nil
2126+
}
2127+
2128+
func (c *sipInbound) unholdCall(ctx context.Context) error {
2129+
if err := c.sendMediaUpdateRequest(ctx, "sendrecv"); err != nil {
2130+
return err
2131+
}
2132+
return nil
2133+
}

0 commit comments

Comments
 (0)