@@ -32,6 +32,7 @@ import (
3232
3333 "github.com/frostbyte73/core"
3434 "github.com/icholy/digest"
35+ psdp "github.com/pion/sdp/v3"
3536 "github.com/pkg/errors"
3637
3738 "github.com/livekit/media-sdk/dtmf"
@@ -859,6 +860,7 @@ func (c *inboundCall) runMediaConn(offerData []byte, enc livekit.SIPMediaEncrypt
859860 if err != nil {
860861 return nil , err
861862 }
863+ c .cc .nextSDPVersion = answer .SDP .Origin .SessionVersion + 1
862864 c .mon .SDPSize (len (answerData ), false )
863865 c .log .Debugw ("SDP answer" , "sdp" , string (answerData ))
864866
@@ -1245,6 +1247,48 @@ func (c *inboundCall) transferCall(ctx context.Context, transferTo string, heade
12451247
12461248}
12471249
1250+ func (c * inboundCall ) holdCall (ctx context.Context ) error {
1251+ c .log .Infow ("holding inbound call" )
1252+
1253+ // Disable media timeout during hold to prevent call termination
1254+ if c .media != nil {
1255+ c .media .EnableTimeout (false )
1256+ c .log .Infow ("media timeout disabled for hold" )
1257+ }
1258+
1259+ err := c .cc .holdCall (ctx )
1260+ if err != nil {
1261+ c .log .Infow ("inbound call failed to hold" , "error" , err )
1262+ // Re-enable timeout if hold failed
1263+ if c .media != nil {
1264+ c .media .EnableTimeout (true )
1265+ }
1266+ return err
1267+ }
1268+
1269+ c .log .Infow ("inbound call held" )
1270+ return nil
1271+ }
1272+
1273+ func (c * inboundCall ) unholdCall (ctx context.Context ) error {
1274+ c .log .Infow ("unholding inbound call" )
1275+
1276+ err := c .cc .unholdCall (ctx )
1277+ if err != nil {
1278+ c .log .Infow ("inbound call failed to unhold" , "error" , err )
1279+ return err
1280+ }
1281+
1282+ // Re-enable media timeout after unhold
1283+ if c .media != nil {
1284+ c .media .EnableTimeout (true )
1285+ c .log .Infow ("media timeout re-enabled after unhold" )
1286+ }
1287+
1288+ c .log .Infow ("inbound call unheld" )
1289+ return nil
1290+ }
1291+
12481292func (s * Server ) newInbound (log logger.Logger , id LocalTag , contact URI , invite * sip.Request , inviteTx sip.ServerTransaction , getHeaders setHeadersFunc ) * sipInbound {
12491293 c := & sipInbound {
12501294 log : log ,
@@ -1296,6 +1340,7 @@ type sipInbound struct {
12961340 ringing chan struct {}
12971341 acked core.Fuse
12981342 setHeaders setHeadersFunc
1343+ nextSDPVersion uint64
12991344}
13001345
13011346func (c * sipInbound ) ValidateInvite () error {
@@ -1744,3 +1789,169 @@ func (c *sipInbound) CloseWithStatus(code sip.StatusCode, status string) {
17441789 c .drop ()
17451790 }
17461791}
1792+
1793+ func (c * sipInbound ) setMediaDirection (sdpData []byte , direction string ) ([]byte , error ) {
1794+
1795+ if len (sdpData ) == 0 {
1796+ return sdpData , nil
1797+ }
1798+
1799+ // Parse SDP using the base Parse function (works for both offers and answers)
1800+ desc , err := sdp .Parse (sdpData )
1801+ if err != nil {
1802+ return nil , fmt .Errorf ("failed to parse SDP: %w" , err )
1803+ }
1804+
1805+ // Modify direction attributes in each media description
1806+ for _ , mediaDesc := range desc .SDP .MediaDescriptions {
1807+ if mediaDesc == nil {
1808+ continue
1809+ }
1810+
1811+ // Find and remove existing direction attributes
1812+ var newAttributes []psdp.Attribute
1813+ for _ , attr := range mediaDesc .Attributes {
1814+ // Keep all attributes except direction-related ones
1815+ if attr .Key != "sendrecv" && attr .Key != "sendonly" &&
1816+ attr .Key != "recvonly" && attr .Key != "inactive" {
1817+ newAttributes = append (newAttributes , attr )
1818+ }
1819+ }
1820+
1821+ // Add the new direction attribute
1822+ newAttributes = append (newAttributes , psdp.Attribute {
1823+ Key : direction ,
1824+ Value : "" ,
1825+ })
1826+
1827+ mediaDesc .Attributes = newAttributes
1828+ }
1829+
1830+ // Set session version to current value plus current unix timestamp
1831+ desc .SDP .Origin .SessionVersion = c .nextSDPVersion
1832+ c .nextSDPVersion += 1
1833+
1834+ // Marshal back to bytes
1835+ modifiedSDP , err := desc .SDP .Marshal ()
1836+ if err != nil {
1837+ return nil , fmt .Errorf ("failed to marshal modified SDP: %w" , err )
1838+ }
1839+
1840+ return modifiedSDP , nil
1841+ }
1842+
1843+ func (c * sipInbound ) holdCall (ctx context.Context ) error {
1844+ c .mu .Lock ()
1845+
1846+ if c .invite == nil || c .inviteOk == nil {
1847+ c .mu .Unlock ()
1848+ return psrpc .NewErrorf (psrpc .FailedPrecondition , "can't hold non established call" )
1849+ }
1850+
1851+ // Create INVITE with SDP modified for hold (a=sendonly)
1852+ req := sip .NewRequest (sip .INVITE , c .invite .Recipient )
1853+ c .setCSeq (req )
1854+
1855+ // Copy headers from original INVITE
1856+ req .AppendHeader (c .invite .From ())
1857+ req .AppendHeader (c .invite .To ())
1858+ req .AppendHeader (c .invite .CallID ())
1859+ req .AppendHeader (c .contact )
1860+ req .AppendHeader (sip .NewHeader ("Content-Type" , "application/sdp" ))
1861+ req .AppendHeader (sip .NewHeader ("Allow" , "INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE" ))
1862+
1863+ // Modify SDP to set direction to sendonly (hold)
1864+ sdpOffer := c .inviteOk .Body ()
1865+ if len (sdpOffer ) > 0 {
1866+ modifiedSDP , err := c .setMediaDirection (sdpOffer , "sendonly" )
1867+ if err != nil {
1868+ return err
1869+ }
1870+ req .SetBody (modifiedSDP )
1871+ }
1872+
1873+ c .swapSrcDst (req )
1874+ c .mu .Unlock ()
1875+
1876+ // Send the INVITE request
1877+ tx , err := c .Transaction (req )
1878+ if err != nil {
1879+ return err
1880+ }
1881+ defer tx .Terminate ()
1882+
1883+ resp , err := sipResponse (ctx , tx , c .s .closing .Watch (), nil )
1884+ if err != nil {
1885+ return err
1886+ }
1887+
1888+ if resp .StatusCode != sip .StatusOK {
1889+ return & livekit.SIPStatus {Code : livekit .SIPStatusCode (resp .StatusCode )}
1890+ }
1891+
1892+ // Send ACK for the hold INVITE
1893+ ack := sip .NewAckRequest (req , resp , nil )
1894+ if err := c .WriteRequest (ack ); err != nil {
1895+ return err
1896+ }
1897+
1898+ return nil
1899+ }
1900+
1901+ func (c * sipInbound ) unholdCall (ctx context.Context ) error {
1902+ c .mu .Lock ()
1903+
1904+ if c .invite == nil || c .inviteOk == nil {
1905+ c .mu .Unlock ()
1906+ return psrpc .NewErrorf (psrpc .FailedPrecondition , "can't unhold non established call" )
1907+ }
1908+
1909+ // Create INVITE with SDP modified for unhold (a=sendrecv)
1910+ req := sip .NewRequest (sip .INVITE , c .invite .Recipient )
1911+ c .setCSeq (req )
1912+
1913+ // Copy headers from original INVITE
1914+ req .AppendHeader (c .invite .From ())
1915+ req .AppendHeader (c .invite .To ())
1916+ req .AppendHeader (c .invite .CallID ())
1917+ req .AppendHeader (c .contact )
1918+ req .AppendHeader (sip .NewHeader ("Content-Type" , "application/sdp" ))
1919+ req .AppendHeader (sip .NewHeader ("Allow" , "INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE" ))
1920+
1921+ // Modify SDP to set direction to sendrecv (unhold)
1922+ sdpOffer := c .inviteOk .Body ()
1923+ if len (sdpOffer ) > 0 {
1924+ modifiedSDP , err := c .setMediaDirection (sdpOffer , "sendrecv" )
1925+ if err != nil {
1926+ return err
1927+ }
1928+ req .SetBody (modifiedSDP )
1929+ }
1930+
1931+ c .swapSrcDst (req )
1932+ c .mu .Unlock ()
1933+
1934+ // Send the INVITE request
1935+ tx , err := c .Transaction (req )
1936+ if err != nil {
1937+ return err
1938+ }
1939+ defer tx .Terminate ()
1940+
1941+ resp , err := sipResponse (ctx , tx , c .s .closing .Watch (), nil )
1942+ if err != nil {
1943+ return err
1944+ }
1945+
1946+ if resp .StatusCode != sip .StatusOK {
1947+ return & livekit.SIPStatus {Code : livekit .SIPStatusCode (resp .StatusCode )}
1948+ }
1949+
1950+ // Send ACK for the unhold INVITE
1951+ ack := sip .NewAckRequest (req , resp , nil )
1952+ if err := c .WriteRequest (ack ); err != nil {
1953+ return err
1954+ }
1955+
1956+ return nil
1957+ }
0 commit comments