@@ -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+
14241468func (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
14791525func (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