@@ -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+
13241368func (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
13791424func (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