diff --git a/go.mod b/go.mod index 45d5327c..997737cf 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,20 @@ go 1.20 require ( github.com/asticode/go-astits v1.13.0 - github.com/expr-lang/expr v1.16.9 + github.com/expr-lang/expr v1.17.2 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.63 - github.com/pion/ice/v2 v2.3.37 + github.com/pion/ice/v4 v4.0.9 github.com/pion/interceptor v0.1.37 github.com/pion/rtcp v1.2.15 - github.com/pion/rtp v1.8.11 - github.com/pion/sdp/v3 v3.0.10 - github.com/pion/srtp/v2 v2.0.20 - github.com/pion/stun v0.6.1 - github.com/pion/webrtc/v3 v3.3.5 - github.com/rs/zerolog v1.33.0 + github.com/pion/rtp v1.8.13 + github.com/pion/sdp/v3 v3.0.11 + github.com/pion/srtp/v3 v3.0.4 + github.com/pion/stun/v3 v3.0.0 + github.com/pion/webrtc/v4 v4.0.14 + github.com/rs/zerolog v1.34.0 github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f github.com/stretchr/testify v1.10.0 @@ -27,19 +27,18 @@ require ( ) require ( - github.com/asticode/go-astikit v0.52.0 // indirect + github.com/asticode/go-astikit v0.54.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/pion/datachannel v1.5.10 // indirect - github.com/pion/dtls/v2 v2.2.12 // indirect + github.com/pion/dtls/v3 v3.0.6 // indirect github.com/pion/logging v0.2.3 // indirect - github.com/pion/mdns v0.0.12 // indirect + github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/sctp v1.8.36 // indirect - github.com/pion/transport/v2 v2.2.10 // indirect + github.com/pion/sctp v1.8.37 // indirect github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/turn/v2 v2.1.6 // indirect + github.com/pion/turn/v4 v4.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/wlynxg/anet v0.0.5 // indirect golang.org/x/mod v0.20.0 // indirect diff --git a/go.sum b/go.sum index a0fdcb88..c5a92c73 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astikit v0.52.0 h1:kTl2XjgiVQhUl1H7kim7NhmTtCMwVBbPrXKqhQhbk8Y= -github.com/asticode/go-astikit v0.52.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astikit v0.54.0 h1:uq9eurgisdkYwJU9vSWIQaPH4MH0cac82sQH00kmSNQ= +github.com/asticode/go-astikit v0.54.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c= github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -8,19 +8,15 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= -github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso= +github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -34,47 +30,36 @@ github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= -github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/ice/v2 v2.3.37 h1:ObIdaNDu1rCo7hObhs34YSBcO7fjslJMZV0ux+uZWh0= -github.com/pion/ice/v2 v2.3.37/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= +github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= +github.com/pion/ice/v4 v4.0.9 h1:VKgU4MwA2LUDVLq+WBkpEHTcAb8c5iCvFMECeuPOZNk= +github.com/pion/ice/v4 v4.0.9/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= -github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= -github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= +github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= +github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk= -github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= -github.com/pion/sctp v1.8.36 h1:owNudmnz1xmhfYje5L/FCav3V9wpPRePHle3Zi+P+M0= -github.com/pion/sctp v1.8.36/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA= -github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= -github.com/pion/srtp/v2 v2.0.20/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= -github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= -github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= -github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= -github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= -github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= -github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg= +github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= +github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= +github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= +github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= +github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= +github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= -github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/webrtc/v3 v3.3.5 h1:ZsSzaMz/i9nblPdiAkZoP+E6Kmjw+jnyq3bEmU3EtRg= -github.com/pion/webrtc/v3 v3.3.5/go.mod h1:liNa+E1iwyzyXqNUwvoMRNQ10x8h8FOeJKL8RkIbamE= +github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= +github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= +github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg= +github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= @@ -82,96 +67,38 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= -github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/eseecloud/eseecloud.go b/internal/eseecloud/eseecloud.go new file mode 100644 index 00000000..bb4d9d31 --- /dev/null +++ b/internal/eseecloud/eseecloud.go @@ -0,0 +1,10 @@ +package eseecloud + +import ( + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/eseecloud" +) + +func Init() { + streams.HandleFunc("eseecloud", eseecloud.Dial) +} diff --git a/internal/flussonic/flussonic.go b/internal/flussonic/flussonic.go new file mode 100644 index 00000000..6e874285 --- /dev/null +++ b/internal/flussonic/flussonic.go @@ -0,0 +1,10 @@ +package flussonic + +import ( + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/flussonic" +) + +func Init() { + streams.HandleFunc("flussonic", flussonic.Dial) +} diff --git a/internal/ivideon/ivideon.go b/internal/ivideon/ivideon.go index 03feb742..51ddb890 100644 --- a/internal/ivideon/ivideon.go +++ b/internal/ivideon/ivideon.go @@ -2,12 +2,9 @@ package ivideon import ( "github.com/AlexxIT/go2rtc/internal/streams" - "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/ivideon" ) func Init() { - streams.HandleFunc("ivideon", func(source string) (core.Producer, error) { - return ivideon.Dial(source) - }) + streams.HandleFunc("ivideon", ivideon.Dial) } diff --git a/internal/webrtc/candidates.go b/internal/webrtc/candidates.go index a15c4e7d..1138db76 100644 --- a/internal/webrtc/candidates.go +++ b/internal/webrtc/candidates.go @@ -8,7 +8,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/AlexxIT/go2rtc/pkg/xnet" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) type Address struct { diff --git a/internal/webrtc/client.go b/internal/webrtc/client.go index 106b603e..5fbf2175 100644 --- a/internal/webrtc/client.go +++ b/internal/webrtc/client.go @@ -15,7 +15,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/gorilla/websocket" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) // streamsHandler supports: diff --git a/internal/webrtc/client_creality.go b/internal/webrtc/client_creality.go index 0a3685a9..4618044e 100644 --- a/internal/webrtc/client_creality.go +++ b/internal/webrtc/client_creality.go @@ -10,6 +10,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" + "github.com/pion/sdp/v3" ) // https://github.com/AlexxIT/go2rtc/issues/1600 @@ -27,7 +28,6 @@ func crealityClient(url string) (core.Producer, error) { medias := []*core.Media{ {Kind: core.KindVideo, Direction: core.DirectionRecvonly}, - {Kind: core.KindAudio, Direction: core.DirectionRecvonly}, } // TODO: return webrtc.SessionDescription @@ -36,6 +36,8 @@ func crealityClient(url string) (core.Producer, error) { return nil, err } + log.Trace().Msgf("[webrtc] offer:\n%s", offer) + body, err := offerToB64(offer) if err != nil { return nil, err @@ -61,6 +63,12 @@ func crealityClient(url string) (core.Producer, error) { return nil, err } + log.Trace().Msgf("[webrtc] answer:\n%s", answer) + + if answer, err = fixCrealitySDP(answer); err != nil { + return nil, err + } + if err = prod.SetAnswer(answer); err != nil { return nil, err } @@ -108,3 +116,37 @@ func answerFromB64(r io.Reader) (string, error) { // string "v=0..." return v["sdp"], nil } + +func fixCrealitySDP(value string) (string, error) { + var sd sdp.SessionDescription + if err := sd.UnmarshalString(value); err != nil { + return "", err + } + + md := sd.MediaDescriptions[0] + + // important to skip first codec, because second codec will be used + skip := md.MediaName.Formats[0] + md.MediaName.Formats = md.MediaName.Formats[1:] + + attrs := make([]sdp.Attribute, 0, len(md.Attributes)) + for _, attr := range md.Attributes { + switch attr.Key { + case "fmtp", "rtpmap": + // important to skip fmtp with x-google, because this is second fmtp for same codec + // and pion library will fail parsing this SDP + if strings.HasPrefix(attr.Value, skip) || strings.Contains(attr.Value, "x-google") { + continue + } + } + attrs = append(attrs, attr) + } + + md.Attributes = attrs + + b, err := sd.Marshal() + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/internal/webrtc/kinesis.go b/internal/webrtc/kinesis.go index b11d1d31..8bfaeb9b 100644 --- a/internal/webrtc/kinesis.go +++ b/internal/webrtc/kinesis.go @@ -12,7 +12,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/gorilla/websocket" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) type kinesisRequest struct { diff --git a/internal/webrtc/milestone.go b/internal/webrtc/milestone.go index 6a696cb0..fe1cedcf 100644 --- a/internal/webrtc/milestone.go +++ b/internal/webrtc/milestone.go @@ -12,7 +12,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/tcp" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) // This package handles the Milestone WebRTC session lifecycle, including authentication, diff --git a/internal/webrtc/openipc.go b/internal/webrtc/openipc.go index 8a951d04..2f2db119 100644 --- a/internal/webrtc/openipc.go +++ b/internal/webrtc/openipc.go @@ -9,7 +9,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/gorilla/websocket" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) func openIPCClient(rawURL string, query url.Values) (core.Producer, error) { diff --git a/internal/webrtc/server.go b/internal/webrtc/server.go index 51565a74..48bd5380 100644 --- a/internal/webrtc/server.go +++ b/internal/webrtc/server.go @@ -13,7 +13,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/streams" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) const MimeSDP = "application/sdp" diff --git a/internal/webrtc/webrtc.go b/internal/webrtc/webrtc.go index 989600f9..11e9db89 100644 --- a/internal/webrtc/webrtc.go +++ b/internal/webrtc/webrtc.go @@ -10,7 +10,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/streams" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" "github.com/rs/zerolog" ) diff --git a/internal/webrtc/webrtc_test.go b/internal/webrtc/webrtc_test.go index e014c31c..1f82a0a7 100644 --- a/internal/webrtc/webrtc_test.go +++ b/internal/webrtc/webrtc_test.go @@ -2,10 +2,11 @@ package webrtc import ( "encoding/json" + "strings" "testing" "github.com/AlexxIT/go2rtc/internal/api/ws" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" "github.com/stretchr/testify/require" ) @@ -36,3 +37,37 @@ func TestWebRTCAPIv2(t *testing.T) { require.Equal(t, "v=0\n...", offer.SDP) require.Equal(t, "stun:stun.l.google.com:19302", offer.ICEServers[0].URLs[0]) } + +func TestCrealitySDP(t *testing.T) { + sdp := `v=0 +o=- 1495799811084970 1495799811084970 IN IP4 0.0.0.0 +s=- +t=0 0 +a=msid-semantic:WMS * +a=group:BUNDLE 0 +m=video 9 UDP/TLS/RTP/SAVPF 96 98 +a=rtcp-fb:98 nack +a=rtcp-fb:98 nack pli +a=fmtp:96 profile-level-id=42e01f;level-asymmetry-allowed=1 +a=fmtp:98 profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1 +a=fmtp:98 x-google-max-bitrate=6000;x-google-min-bitrate=2000;x-google-start-bitrate=4000 +a=rtpmap:96 H264/90000 +a=rtpmap:98 H264/90000 +a=ssrc:1 cname:pear +c=IN IP4 0.0.0.0 +a=sendonly +a=mid:0 +a=rtcp-mux +a=ice-ufrag:7AVa +a=ice-pwd:T+F/5y05Paw+mtG5Jrd8N3 +a=ice-options:trickle +a=fingerprint:sha-256 A5:AB:C0:4E:29:5B:BD:3B:7D:88:24:6C:56:6B:2A:79:A3:76:99:35:57:75:AD:C8:5A:A6:34:20:88:1B:68:EF +a=setup:passive +a=candidate:1 1 UDP 2015363327 172.22.233.10 48929 typ host +a=candidate:2 1 TCP 1015021823 172.22.233.10 0 typ host tcptype active +a=candidate:3 1 TCP 1010827519 172.22.233.10 60677 typ host tcptype passive +` + sdp, err := fixCrealitySDP(sdp) + require.Nil(t, err) + require.False(t, strings.Contains(sdp, "x-google-max-bitrate")) +} diff --git a/main.go b/main.go index 8a62bdb6..0f36cafb 100644 --- a/main.go +++ b/main.go @@ -9,9 +9,11 @@ import ( "github.com/AlexxIT/go2rtc/internal/doorbird" "github.com/AlexxIT/go2rtc/internal/dvrip" "github.com/AlexxIT/go2rtc/internal/echo" + "github.com/AlexxIT/go2rtc/internal/eseecloud" "github.com/AlexxIT/go2rtc/internal/exec" "github.com/AlexxIT/go2rtc/internal/expr" "github.com/AlexxIT/go2rtc/internal/ffmpeg" + "github.com/AlexxIT/go2rtc/internal/flussonic" "github.com/AlexxIT/go2rtc/internal/gopro" "github.com/AlexxIT/go2rtc/internal/hass" "github.com/AlexxIT/go2rtc/internal/hls" @@ -88,6 +90,8 @@ func main() { gopro.Init() // gopro source doorbird.Init() // doorbird source v4l2.Init() // v4l2 source + flussonic.Init() + eseecloud.Init() // 6. Helper modules diff --git a/pkg/bits/reader.go b/pkg/bits/reader.go index 435cf5f7..2a957409 100644 --- a/pkg/bits/reader.go +++ b/pkg/bits/reader.go @@ -89,6 +89,12 @@ func (r *Reader) ReadBits64(n byte) (res uint64) { return } +func (r *Reader) ReadFloat32() float64 { + i := r.ReadUint16() + f := r.ReadUint16() + return float64(i) + float64(f)/65536 +} + func (r *Reader) ReadBytes(n int) (b []byte) { if r.bits == 0 { if r.pos+n > len(r.buf) { diff --git a/pkg/core/track.go b/pkg/core/track.go index d3f1467d..08765659 100644 --- a/pkg/core/track.go +++ b/pkg/core/track.go @@ -97,13 +97,17 @@ func NewSender(media *Media, codec *Codec) *Sender { buf: buf, } s.Input = func(packet *Packet) { - // writing to nil chan - OK, writing to closed chan - panic s.mu.Lock() - select { - case s.buf <- packet: - s.Bytes += len(packet.Payload) - s.Packets++ - default: + if s.buf != nil { + // unblocked write to channel + select { + case s.buf <- packet: + s.Bytes += len(packet.Payload) + s.Packets++ + default: + s.Drops++ + } + } else { s.Drops++ } s.mu.Unlock() @@ -139,13 +143,13 @@ func (s *Sender) Start() { } s.done = make(chan struct{}) - go func() { - // for range on nil chan is OK - for packet := range s.buf { + // pass buf directly so that it's impossible for buf to be nil + go func(buf chan *Packet) { + for packet := range buf { s.Output(packet) } close(s.done) - }() + }(s.buf) } func (s *Sender) Wait() { diff --git a/pkg/eseecloud/eseecloud.go b/pkg/eseecloud/eseecloud.go new file mode 100644 index 00000000..05209d22 --- /dev/null +++ b/pkg/eseecloud/eseecloud.go @@ -0,0 +1,180 @@ +package eseecloud + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net/http" + "regexp" + "strings" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" + "github.com/pion/rtp" +) + +type Producer struct { + core.Connection + rd *core.ReadBuffer + + videoPT, audioPT uint8 +} + +func Dial(rawURL string) (core.Producer, error) { + rawURL, _ = strings.CutPrefix(rawURL, "eseecloud") + res, err := http.Get("http" + rawURL) + if err != nil { + return nil, err + } + + prod, err := Open(res.Body) + if err != nil { + return nil, err + } + + if info, ok := prod.(core.Info); ok { + info.SetProtocol("http") + info.SetURL(rawURL) + } + + return prod, nil +} + +func Open(r io.Reader) (core.Producer, error) { + prod := &Producer{ + Connection: core.Connection{ + ID: core.NewID(), + FormatName: "eseecloud", + Transport: r, + }, + rd: core.NewReadBuffer(r), + } + + if err := prod.probe(); err != nil { + return nil, err + } + + return prod, nil +} + +func (p *Producer) probe() error { + b, err := p.rd.Peek(1024) + if err != nil { + return err + } + + i := bytes.Index(b, []byte("\r\n\r\n")) + if i == -1 { + return io.EOF + } + + b = make([]byte, i+4) + _, _ = p.rd.Read(b) + + re := regexp.MustCompile(`m=(video|audio) (\d+) (\w+)/(\d+)\S*`) + for _, item := range re.FindAllStringSubmatch(string(b), 2) { + p.SDP += item[0] + "\n" + + switch item[3] { + case "H264", "H265": + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindVideo, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ + { + Name: item[3], + ClockRate: 90000, + PayloadType: core.PayloadTypeRAW, + }, + }, + }) + p.videoPT = byte(core.Atoi(item[2])) + + case "G711": + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ + { + Name: core.CodecPCMA, + ClockRate: 8000, + }, + }, + }) + p.audioPT = byte(core.Atoi(item[2])) + } + } + + return nil +} + +func (p *Producer) Start() error { + receivers := make(map[uint8]*core.Receiver) + + for _, receiver := range p.Receivers { + switch receiver.Codec.Kind() { + case core.KindVideo: + receivers[p.videoPT] = receiver + case core.KindAudio: + receivers[p.audioPT] = receiver + } + } + + for { + pkt, err := p.readPacket() + if err != nil { + return err + } + + if recv := receivers[pkt.PayloadType]; recv != nil { + switch recv.Codec.Name { + case core.CodecH264, core.CodecH265: + // timestamp = seconds x 1000000 + pkt = &rtp.Packet{ + Header: rtp.Header{ + Timestamp: uint32(uint64(pkt.Timestamp) * 90000 / 1000000), + }, + Payload: annexb.EncodeToAVCC(pkt.Payload), + } + case core.CodecPCMA: + pkt = &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: pkt.SequenceNumber, + Timestamp: uint32(uint64(pkt.Timestamp) * 8000 / 1000000), + }, + Payload: pkt.Payload, + } + } + recv.WriteRTP(pkt) + } + } +} + +func (p *Producer) readPacket() (*core.Packet, error) { + b := make([]byte, 8) + + if _, err := io.ReadFull(p.rd, b); err != nil { + return nil, err + } + + if b[0] != '$' { + return nil, errors.New("eseecloud: wrong start byte") + } + + size := binary.BigEndian.Uint32(b[4:]) + b = make([]byte, size) + if _, err := io.ReadFull(p.rd, b); err != nil { + return nil, err + } + + pkt := &core.Packet{} + if err := pkt.Unmarshal(b); err != nil { + return nil, err + } + + p.Recv += int(size) + + return pkt, nil +} diff --git a/pkg/flussonic/flussonic.go b/pkg/flussonic/flussonic.go new file mode 100644 index 00000000..70b6e9d4 --- /dev/null +++ b/pkg/flussonic/flussonic.go @@ -0,0 +1,176 @@ +package flussonic + +import ( + "strings" + + "github.com/AlexxIT/go2rtc/pkg/aac" + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/iso" + "github.com/gorilla/websocket" + "github.com/pion/rtp" +) + +type Producer struct { + core.Connection + conn *websocket.Conn + + videoTrackID, audioTrackID uint32 + videoTimeScale, audioTimeScale float32 +} + +func Dial(source string) (core.Producer, error) { + url, _ := strings.CutPrefix(source, "flussonic:") + conn, _, err := websocket.DefaultDialer.Dial(url, nil) + if err != nil { + return nil, err + } + + prod := &Producer{ + Connection: core.Connection{ + ID: core.NewID(), + FormatName: "flussonic", + Protocol: core.Before(url, ":"), // wss + RemoteAddr: conn.RemoteAddr().String(), + URL: url, + Transport: conn, + }, + conn: conn, + } + + if err = prod.probe(); err != nil { + _ = conn.Close() + return nil, err + } + + return prod, nil +} + +func (p *Producer) probe() error { + var init struct { + //Metadata struct { + // Tracks []struct { + // Width int `json:"width,omitempty"` + // Height int `json:"height,omitempty"` + // Fps int `json:"fps,omitempty"` + // Content string `json:"content"` + // TrackId string `json:"trackId"` + // Bitrate int `json:"bitrate"` + // } `json:"tracks"` + //} `json:"metadata"` + Tracks []struct { + Content string `json:"content"` + Id uint32 `json:"id"` + Payload []byte `json:"payload"` + } `json:"tracks"` + //Type string `json:"type"` + } + + if err := p.conn.ReadJSON(&init); err != nil { + return err + } + + var timeScale uint32 + + for _, track := range init.Tracks { + atoms, _ := iso.DecodeAtoms(track.Payload) + for _, atom := range atoms { + switch atom := atom.(type) { + case *iso.AtomMdhd: + timeScale = atom.TimeScale + case *iso.AtomVideo: + switch atom.Name { + case "avc1": + codec := h264.AVCCToCodec(atom.Config) + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindVideo, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }) + p.videoTrackID = track.Id + p.videoTimeScale = float32(codec.ClockRate) / float32(timeScale) + } + case *iso.AtomAudio: + switch atom.Name { + case "mp4a": + codec := aac.ConfigToCodec(atom.Config) + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }) + p.audioTrackID = track.Id + p.audioTimeScale = float32(codec.ClockRate) / float32(timeScale) + } + } + } + } + + return nil +} + +func (p *Producer) Start() error { + if err := p.conn.WriteMessage(websocket.TextMessage, []byte("resume")); err != nil { + return err + } + + receivers := make(map[uint32]*core.Receiver) + timeScales := make(map[uint32]float32) + + for _, receiver := range p.Receivers { + switch receiver.Codec.Kind() { + case core.KindVideo: + receivers[p.videoTrackID] = receiver + timeScales[p.videoTrackID] = p.videoTimeScale + case core.KindAudio: + receivers[p.audioTrackID] = receiver + timeScales[p.audioTrackID] = p.audioTimeScale + } + } + + ch := make(chan []byte, 10) + defer close(ch) + + go func() { + for b := range ch { + atoms, err := iso.DecodeAtoms(b) + if err != nil { + continue + } + + var trackID uint32 + var decodeTime uint64 + + for _, atom := range atoms { + switch atom := atom.(type) { + case *iso.AtomTfhd: + trackID = atom.TrackID + case *iso.AtomTfdt: + decodeTime = atom.DecodeTime + case *iso.AtomMdat: + b = atom.Data + } + } + + if recv := receivers[trackID]; recv != nil { + timestamp := uint32(float32(decodeTime) * timeScales[trackID]) + packet := &rtp.Packet{ + Header: rtp.Header{Timestamp: timestamp}, + Payload: b, + } + recv.WriteRTP(packet) + } + } + }() + + for { + mType, b, err := p.conn.ReadMessage() + if err != nil { + return err + } + if mType == websocket.BinaryMessage { + p.Recv += len(b) + ch <- b + } + } +} diff --git a/pkg/hass/client.go b/pkg/hass/client.go index 5b236051..a9ea0264 100644 --- a/pkg/hass/client.go +++ b/pkg/hass/client.go @@ -6,7 +6,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) type Client struct { diff --git a/pkg/iso/reader.go b/pkg/iso/reader.go index ec436af7..175e2563 100644 --- a/pkg/iso/reader.go +++ b/pkg/iso/reader.go @@ -1,6 +1,7 @@ package iso import ( + "bytes" "encoding/binary" "io" @@ -10,89 +11,192 @@ import ( type Atom struct { Name string Data []byte - - DecodeTime uint64 - - SamplesDuration []uint32 - SamplesSize []uint32 } -func DecodeAtoms(b []byte) ([]*Atom, error) { - var atoms []*Atom - for len(b) > 8 { - size := binary.BigEndian.Uint32(b) - if uint32(len(b)) < size { - return nil, io.EOF +type AtomTkhd struct { + TrackID uint32 +} + +type AtomMdhd struct { + TimeScale uint32 +} + +type AtomVideo struct { + Name string + Config []byte +} + +type AtomAudio struct { + Name string + Channels uint16 + SampleRate uint32 + Config []byte +} + +type AtomMfhd struct { + Sequence uint32 +} + +type AtomMdat struct { + Data []byte +} + +type AtomTfhd struct { + TrackID uint32 + SampleDuration uint32 + SampleSize uint32 + SampleFlags uint32 +} +type AtomTfdt struct { + DecodeTime uint64 +} + +type AtomTrun struct { + DataOffset uint32 + FirstSampleFlags uint32 + SamplesDuration []uint32 + SamplesSize []uint32 + SamplesFlags []uint32 + SamplesCTS []uint32 +} + +func DecodeAtom(b []byte) (any, error) { + size := binary.BigEndian.Uint32(b) + if len(b) < int(size) { + return nil, io.EOF + } + + name := string(b[4:8]) + data := b[8:size] + + switch name { + // useful containers + case Moov, MoovTrak, MoovTrakMdia, MoovTrakMdiaMinf, MoovTrakMdiaMinfStbl, Moof, MoofTraf: + return DecodeAtoms(data) + + case MoovTrakTkhd: + return &AtomTkhd{TrackID: binary.BigEndian.Uint32(data[1+3+4+4:])}, nil + + case MoovTrakMdiaMdhd: + return &AtomMdhd{TimeScale: binary.BigEndian.Uint32(data[1+3+4+4:])}, nil + + case MoovTrakMdiaMinfStblStsd: + // support only 1 codec entry + if n := binary.BigEndian.Uint32(data[1+3:]); n == 1 { + return DecodeAtom(data[1+3+4:]) } - name := string(b[4:8]) - data := b[8:size] + case "avc1", "hev1": + b = data[6+2+2+2+4+4+4+2+2+4+4+4+2+32+2+2:] + atom, err := DecodeAtom(b) + if err != nil { + return nil, err + } + if conf, ok := atom.(*Atom); ok { + return &AtomVideo{Name: name, Config: conf.Data}, nil + } - b = b[size:] + case "mp4a": + atom := &AtomAudio{Name: name} - switch name { - case Moof, MoofTraf: - childs, err := DecodeAtoms(data) - if err != nil { - return nil, err + rd := bits.NewReader(data) + rd.ReadBytes(6 + 2 + 2 + 2 + 4) // skip + atom.Channels = rd.ReadUint16() + rd.ReadBytes(2 + 2 + 2) // skip + atom.SampleRate = uint32(rd.ReadFloat32()) + + atom2, _ := DecodeAtom(rd.Left()) + if conf, ok := atom2.(*Atom); ok { + _, b, _ = bytes.Cut(conf.Data, []byte{5, 0x80, 0x80, 0x80}) + if n := len(b); n > 0 && n > 1+int(b[0]) { + atom.Config = b[1 : 1+b[0]] } + } + return atom, nil + + case MoofMfhd: + return &AtomMfhd{Sequence: binary.BigEndian.Uint32(data[4:])}, nil + + case MoofTrafTfhd: + rd := bits.NewReader(data) + _ = rd.ReadByte() // version + flags := rd.ReadUint24() + + atom := &AtomTfhd{ + TrackID: rd.ReadUint32(), + } + + if flags&TfhdDefaultSampleDuration != 0 { + atom.SampleDuration = rd.ReadUint32() + + } + if flags&TfhdDefaultSampleSize != 0 { + atom.SampleSize = rd.ReadUint32() + } + if flags&TfhdDefaultSampleFlags != 0 { + atom.SampleFlags = rd.ReadUint32() // skip + } + + return atom, nil + + case MoofTrafTfdt: + return &AtomTfdt{DecodeTime: binary.BigEndian.Uint64(data[4:])}, nil + + case MoofTrafTrun: + rd := bits.NewReader(data) + _ = rd.ReadByte() // version + flags := rd.ReadUint24() + samples := rd.ReadUint32() + + atom := &AtomTrun{} + + if flags&TrunDataOffset != 0 { + atom.DataOffset = rd.ReadUint32() + } + if flags&TrunFirstSampleFlags != 0 { + atom.FirstSampleFlags = rd.ReadUint32() + } + + for i := uint32(0); i < samples; i++ { + if flags&TrunSampleDuration != 0 { + atom.SamplesDuration = append(atom.SamplesDuration, rd.ReadUint32()) + } + if flags&TrunSampleSize != 0 { + atom.SamplesSize = append(atom.SamplesSize, rd.ReadUint32()) + } + if flags&TrunSampleFlags != 0 { + atom.SamplesFlags = append(atom.SamplesFlags, rd.ReadUint32()) + } + if flags&TrunSampleCTS != 0 { + atom.SamplesCTS = append(atom.SamplesCTS, rd.ReadUint32()) + } + } + + return atom, nil + + case Mdat: + return &AtomMdat{Data: data}, nil + } + + return &Atom{Name: name, Data: data}, nil +} + +func DecodeAtoms(b []byte) (atoms []any, err error) { + for len(b) > 0 { + atom, err := DecodeAtom(b) + if err != nil { + return nil, err + } + + if childs, ok := atom.([]any); ok { atoms = append(atoms, childs...) - - case MoofMfhd, MoofTrafTfhd: - continue - - case MoofTrafTfdt: - if len(data) < 8 { - return nil, io.EOF - } - - dt := binary.BigEndian.Uint64(data[4:]) - atoms = append(atoms, &Atom{Name: name, DecodeTime: dt}) - - case MoofTrafTrun: - rd := bits.NewReader(data) - - _ = rd.ReadByte() // version - flags := rd.ReadUint24() - samples := rd.ReadUint32() - - if flags&TrunDataOffset != 0 { - _ = rd.ReadUint32() // skip - } - if flags&TrunFirstSampleFlags != 0 { - _ = rd.ReadUint32() // skip - } - - atom := &Atom{Name: name} - - for i := uint32(0); i < samples; i++ { - if flags&TrunSampleDuration != 0 { - atom.SamplesDuration = append(atom.SamplesDuration, rd.ReadUint32()) - } - if flags&TrunSampleSize != 0 { - atom.SamplesSize = append(atom.SamplesSize, rd.ReadUint32()) - } - if flags&TrunSampleFlags != 0 { - _ = rd.ReadUint32() // skip - } - if flags&TrunSampleCTS != 0 { - _ = rd.ReadUint32() // skip - } - } - - if rd.EOF { - return nil, io.EOF - } - + } else { atoms = append(atoms, atom) - - case Mdat: - atoms = append(atoms, &Atom{Name: name, Data: data}) - - default: - println("iso: unsupported atom: " + name) } + + size := binary.BigEndian.Uint32(b) + b = b[size:] } return atoms, nil diff --git a/pkg/ivideon/client.go b/pkg/ivideon/client.go deleted file mode 100644 index ef79010e..00000000 --- a/pkg/ivideon/client.go +++ /dev/null @@ -1,314 +0,0 @@ -package ivideon - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "sync" - "time" - - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/h264" - "github.com/AlexxIT/go2rtc/pkg/iso" - "github.com/gorilla/websocket" - "github.com/pion/rtp" -) - -type State byte - -const ( - StateNone State = iota - StateConn - StateHandle -) - -// Deprecated: should be rewritten to core.Connection -type Client struct { - core.Listener - - ID string - - conn *websocket.Conn - - medias []*core.Media - receiver *core.Receiver - - msg *message - t0 time.Time - - buffer chan []byte - state State - mu sync.Mutex - - recv int -} - -func Dial(source string) (*Client, error) { - id := strings.Replace(source[8:], "/", ":", 1) - client := &Client{ID: id} - if err := client.Dial(); err != nil { - return nil, err - } - return client, nil -} - -func (c *Client) Dial() (err error) { - resp, err := http.Get( - "https://openapi-alpha.ivideon.com/cameras/" + c.ID + - "/live_stream?op=GET&access_token=public&q=2&" + - "video_codecs=h264&format=ws-fmp4", - ) - - data, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - var v liveResponse - if err = json.Unmarshal(data, &v); err != nil { - return err - } - - if !v.Success { - return fmt.Errorf("wrong response: %s", data) - } - - c.conn, _, err = websocket.DefaultDialer.Dial(v.Result.URL, nil) - if err != nil { - return err - } - - if err = c.getTracks(); err != nil { - _ = c.conn.Close() - return err - } - - c.state = StateConn - - return nil -} - -func (c *Client) Handle() error { - // add delay to the stream for smooth playing (not a best solution) - c.t0 = time.Now().Add(time.Second) - - c.mu.Lock() - - if c.state == StateConn { - c.buffer = make(chan []byte, 5) - c.state = StateHandle - - // processing stream in separate thread for lower delay between packets - go c.worker(c.buffer) - } - - c.mu.Unlock() - - _, data, err := c.conn.ReadMessage() - if err != nil { - return err - } - - if c.receiver != nil && c.receiver.ID == c.msg.Track { - c.mu.Lock() - if c.state == StateHandle { - c.buffer <- data - c.recv += len(data) - } - c.mu.Unlock() - } - - // we have one unprocessed msg after getTracks - for { - _, data, err = c.conn.ReadMessage() - if err != nil { - return err - } - - var msg message - if err = json.Unmarshal(data, &msg); err != nil { - return err - } - - switch msg.Type { - case "stream-init": - continue - - case "metadata": - continue - - case "fragment": - _, data, err = c.conn.ReadMessage() - if err != nil { - return err - } - - if c.receiver != nil && c.receiver.ID == msg.Track { - c.mu.Lock() - if c.state == StateHandle { - c.buffer <- data - c.recv += len(data) - } - c.mu.Unlock() - } - - default: - return fmt.Errorf("wrong message type: %s", data) - } - } -} - -func (c *Client) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - - switch c.state { - case StateNone: - return nil - case StateConn: - case StateHandle: - close(c.buffer) - } - - c.state = StateNone - - return c.conn.Close() -} - -func (c *Client) getTracks() error { - for { - _, data, err := c.conn.ReadMessage() - if err != nil { - return err - } - - var msg message - if err = json.Unmarshal(data, &msg); err != nil { - return err - } - - switch msg.Type { - case "metadata": - continue - - case "stream-init": - s := msg.CodecString - i := strings.IndexByte(s, '.') - if i > 0 { - s = s[:i] - } - - switch s { - case "avc1": // avc1.4d0029 - // skip multiple identical init - if c.receiver != nil { - continue - } - - i = bytes.Index(msg.Data, []byte("avcC")) - 4 - if i < 0 { - return fmt.Errorf("ivideon: wrong AVC: %s", msg.Data) - } - - avccLen := binary.BigEndian.Uint32(msg.Data[i:]) - data = msg.Data[i+8 : i+int(avccLen)] - - codec := h264.ConfigToCodec(data) - - media := &core.Media{ - Kind: core.KindVideo, - Direction: core.DirectionRecvonly, - Codecs: []*core.Codec{codec}, - } - c.medias = append(c.medias, media) - - c.receiver = core.NewReceiver(media, codec) - c.receiver.ID = msg.TrackID - - case "mp4a": // mp4a.40.2 - } - - case "fragment": - c.msg = &msg - return nil - - default: - return fmt.Errorf("wrong message type: %s", data) - } - } -} - -func (c *Client) worker(buffer chan []byte) { - for data := range buffer { - atoms, err := iso.DecodeAtoms(data) - if err != nil { - continue - } - - var trun *iso.Atom - var ts uint32 - - for _, atom := range atoms { - switch atom.Name { - case iso.MoofTrafTrun: - trun = atom - case iso.MoofTrafTfdt: - ts = uint32(atom.DecodeTime) - case iso.Mdat: - data = atom.Data - } - } - - if trun == nil || trun.SamplesDuration == nil || trun.SamplesSize == nil { - continue - } - - for i := 0; i < len(trun.SamplesDuration); i++ { - duration := trun.SamplesDuration[i] - size := trun.SamplesSize[i] - - // synchronize framerate for WebRTC and MSE - d := time.Duration(ts)*time.Millisecond - time.Since(c.t0) - if d < 0 { - d = time.Duration(duration) * time.Millisecond / 2 - } - time.Sleep(d) - - // can be SPS, PPS and IFrame in one packet - packet := &rtp.Packet{ - // ivideon clockrate=1000, RTP clockrate=90000 - Header: rtp.Header{Timestamp: ts * 90}, - Payload: data[:size], - } - c.receiver.WriteRTP(packet) - - data = data[size:] - ts += duration - } - } -} - -type liveResponse struct { - Result struct { - URL string `json:"url"` - } `json:"result"` - Success bool `json:"success"` -} - -type message struct { - Type string `json:"type"` - - CodecString string `json:"codec_string"` - Data []byte `json:"data"` - TrackID byte `json:"track_id"` - - Track byte `json:"track"` - StartTime float32 `json:"start_time"` - Duration float32 `json:"duration"` - IsKey bool `json:"is_key"` - DataOffset uint32 `json:"data_offset"` -} diff --git a/pkg/ivideon/ivideon.go b/pkg/ivideon/ivideon.go new file mode 100644 index 00000000..973b9ba0 --- /dev/null +++ b/pkg/ivideon/ivideon.go @@ -0,0 +1,187 @@ +package ivideon + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/mp4" + "github.com/gorilla/websocket" +) + +type Producer struct { + core.Connection + conn *websocket.Conn + + buf []byte + + dem *mp4.Demuxer +} + +func Dial(source string) (core.Producer, error) { + id := strings.Replace(source[8:], "/", ":", 1) + + url, err := GetLiveStream(id) + if err != nil { + return nil, err + } + + conn, _, err := websocket.DefaultDialer.Dial(url, nil) + if err != nil { + return nil, err + } + + prod := &Producer{ + Connection: core.Connection{ + ID: core.NewID(), + FormatName: "ivideon", + Protocol: core.Before(url, ":"), // wss + RemoteAddr: conn.RemoteAddr().String(), + Source: source, + URL: url, + Transport: conn, + }, + conn: conn, + } + + if err = prod.probe(); err != nil { + _ = conn.Close() + return nil, err + } + + return prod, nil +} + +func GetLiveStream(id string) (string, error) { + // &video_codecs=h264,h265&audio_codecs=aac,mp3,pcma,pcmu,none + resp, err := http.Get( + "https://openapi-alpha.ivideon.com/cameras/" + id + + "/live_stream?op=GET&access_token=public&q=2&video_codecs=h264&format=ws-fmp4", + ) + if err != nil { + return "", err + } + + var v struct { + Message string `json:"message"` + Result struct { + URL string `json:"url"` + } `json:"result"` + Success bool `json:"success"` + } + if err = json.NewDecoder(resp.Body).Decode(&v); err != nil { + return "", err + } + + if !v.Success { + return "", fmt.Errorf("ivideon: can't get live_stream: " + v.Message) + } + + return v.Result.URL, nil +} + +func (p *Producer) Start() error { + receivers := make(map[uint32]*core.Receiver) + for _, receiver := range p.Receivers { + trackID := p.dem.GetTrackID(receiver.Codec) + receivers[trackID] = receiver + } + + ch := make(chan []byte, 10) + defer close(ch) + + ch <- p.buf + + go func() { + // add delay to the stream for smooth playing (not a best solution) + t0 := time.Now() + + for data := range ch { + trackID, packets := p.dem.Demux(data) + if receiver := receivers[trackID]; receiver != nil { + clockRate := time.Duration(receiver.Codec.ClockRate) + for _, packet := range packets { + // synchronize framerate for WebRTC and MSE + ts := time.Second * time.Duration(packet.Timestamp) / clockRate + d := ts - time.Since(t0) + if d < 0 { + d = 10 * time.Millisecond + } + time.Sleep(d) + + receiver.WriteRTP(packet) + } + } + } + }() + + for { + var msg message + if err := p.conn.ReadJSON(&msg); err != nil { + return err + } + + switch msg.Type { + case "stream-init", "metadata": + continue + + case "fragment": + _, b, err := p.conn.ReadMessage() + if err != nil { + return err + } + + p.Recv += len(b) + ch <- b + + default: + return errors.New("ivideon: wrong message type: " + msg.Type) + } + } +} + +func (p *Producer) probe() (err error) { + p.dem = &mp4.Demuxer{} + + for { + var msg message + if err = p.conn.ReadJSON(&msg); err != nil { + return err + } + + switch msg.Type { + case "metadata": + continue + + case "stream-init": + // it's difficult to maintain audio + if strings.HasPrefix(msg.CodecString, "avc1") { + medias := p.dem.Probe(msg.Data) + p.Medias = append(p.Medias, medias...) + } + + case "fragment": + _, p.buf, err = p.conn.ReadMessage() + return + + default: + return errors.New("ivideon: wrong message type: " + msg.Type) + } + } +} + +type message struct { + Type string `json:"type"` + CodecString string `json:"codec_string"` + Data []byte `json:"data"` + //TrackID byte `json:"track_id"` + //Track byte `json:"track"` + //StartTime float32 `json:"start_time"` + //Duration float32 `json:"duration"` + //IsKey bool `json:"is_key"` + //DataOffset uint32 `json:"data_offset"` +} diff --git a/pkg/ivideon/producer.go b/pkg/ivideon/producer.go deleted file mode 100644 index 78084123..00000000 --- a/pkg/ivideon/producer.go +++ /dev/null @@ -1,51 +0,0 @@ -package ivideon - -import ( - "encoding/json" - - "github.com/AlexxIT/go2rtc/pkg/core" -) - -func (c *Client) GetMedias() []*core.Media { - return c.medias -} - -func (c *Client) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) { - if c.receiver != nil { - return c.receiver, nil - } - return nil, core.ErrCantGetTrack -} - -func (c *Client) Start() error { - err := c.Handle() - if c.buffer == nil { - return nil - } - return err -} - -func (c *Client) Stop() error { - if c.receiver != nil { - c.receiver.Close() - } - return c.Close() -} - -func (c *Client) MarshalJSON() ([]byte, error) { - info := &core.Connection{ - ID: core.ID(c), - FormatName: "ivideon", - Protocol: "ws", - URL: c.ID, - Medias: c.medias, - Recv: c.recv, - } - if c.conn != nil { - info.RemoteAddr = c.conn.RemoteAddr().String() - } - if c.receiver != nil { - info.Receivers = []*core.Receiver{c.receiver} - } - return json.Marshal(info) -} diff --git a/pkg/mp4/demuxer.go b/pkg/mp4/demuxer.go new file mode 100644 index 00000000..25c8c70e --- /dev/null +++ b/pkg/mp4/demuxer.go @@ -0,0 +1,116 @@ +package mp4 + +import ( + "github.com/AlexxIT/go2rtc/pkg/aac" + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/iso" + "github.com/pion/rtp" +) + +type Demuxer struct { + codecs map[uint32]*core.Codec + timeScales map[uint32]float32 +} + +func (d *Demuxer) Probe(init []byte) (medias []*core.Media) { + var trackID, timeScale uint32 + + if d.codecs == nil { + d.codecs = make(map[uint32]*core.Codec) + d.timeScales = make(map[uint32]float32) + } + + atoms, _ := iso.DecodeAtoms(init) + for _, atom := range atoms { + var codec *core.Codec + + switch atom := atom.(type) { + case *iso.AtomTkhd: + trackID = atom.TrackID + case *iso.AtomMdhd: + timeScale = atom.TimeScale + case *iso.AtomVideo: + switch atom.Name { + case "avc1": + codec = h264.ConfigToCodec(atom.Config) + } + case *iso.AtomAudio: + switch atom.Name { + case "mp4a": + codec = aac.ConfigToCodec(atom.Config) + } + } + + if codec != nil { + d.codecs[trackID] = codec + d.timeScales[trackID] = float32(codec.ClockRate) / float32(timeScale) + + medias = append(medias, &core.Media{ + Kind: codec.Kind(), + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }) + } + } + + return +} + +func (d *Demuxer) GetTrackID(codec *core.Codec) uint32 { + for trackID, c := range d.codecs { + if c == codec { + return trackID + } + } + return 0 +} + +func (d *Demuxer) Demux(data2 []byte) (trackID uint32, packets []*core.Packet) { + atoms, err := iso.DecodeAtoms(data2) + if err != nil { + return 0, nil + } + + var ts uint32 + var trun *iso.AtomTrun + var data []byte + + for _, atom := range atoms { + switch atom := atom.(type) { + case *iso.AtomTfhd: + trackID = atom.TrackID + case *iso.AtomTfdt: + ts = uint32(atom.DecodeTime) + case *iso.AtomTrun: + trun = atom + case *iso.AtomMdat: + data = atom.Data + } + } + + timeScale := d.timeScales[trackID] + if timeScale == 0 { + return 0, nil + } + + n := len(trun.SamplesDuration) + packets = make([]*core.Packet, n) + + for i := 0; i < n; i++ { + duration := trun.SamplesDuration[i] + size := trun.SamplesSize[i] + + // can be SPS, PPS and IFrame in one packet + timestamp := uint32(float32(ts) * timeScale) + packets[i] = &rtp.Packet{ + Header: rtp.Header{Timestamp: timestamp}, + Payload: data[:size], + } + + data = data[size:] + ts += duration + } + + return +} diff --git a/pkg/nest/client.go b/pkg/nest/client.go index 93c4ce64..2c812834 100644 --- a/pkg/nest/client.go +++ b/pkg/nest/client.go @@ -8,7 +8,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/rtsp" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) type WebRTCClient struct { diff --git a/pkg/ring/client.go b/pkg/ring/client.go index 4c473276..18244a39 100644 --- a/pkg/ring/client.go +++ b/pkg/ring/client.go @@ -12,7 +12,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/google/uuid" "github.com/gorilla/websocket" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) type Client struct { @@ -536,6 +536,6 @@ func (c *Client) MarshalJSON() ([]byte, error) { if webrtcProd, ok := c.prod.(*webrtc.Conn); ok { return webrtcProd.MarshalJSON() } - + return json.Marshal(c.prod) } diff --git a/pkg/roborock/client.go b/pkg/roborock/client.go index ef221e65..4940b74c 100644 --- a/pkg/roborock/client.go +++ b/pkg/roborock/client.go @@ -15,7 +15,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/roborock/iot" "github.com/AlexxIT/go2rtc/pkg/webrtc" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) // Deprecated: should be rewritten to core.Connection diff --git a/pkg/srtp/session.go b/pkg/srtp/session.go index f70f9df6..0ab81648 100644 --- a/pkg/srtp/session.go +++ b/pkg/srtp/session.go @@ -6,7 +6,7 @@ import ( "github.com/pion/rtcp" "github.com/pion/rtp" - "github.com/pion/srtp/v2" + "github.com/pion/srtp/v3" ) type Session struct { diff --git a/pkg/webrtc/api.go b/pkg/webrtc/api.go index 013a2f25..fe49ef1e 100644 --- a/pkg/webrtc/api.go +++ b/pkg/webrtc/api.go @@ -5,9 +5,9 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/xnet" - "github.com/pion/ice/v2" + "github.com/pion/ice/v4" "github.com/pion/interceptor" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) // ReceiveMTU = Ethernet MTU (1500) - IP Header (20) - UDP Header (8) diff --git a/pkg/webrtc/client.go b/pkg/webrtc/client.go index 9a7a7b2f..84e9e86b 100644 --- a/pkg/webrtc/client.go +++ b/pkg/webrtc/client.go @@ -3,7 +3,7 @@ package webrtc import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/pion/sdp/v3" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) func (c *Conn) CreateOffer(medias []*core.Media) (string, error) { diff --git a/pkg/webrtc/client_test.go b/pkg/webrtc/client_test.go index 45c8c88d..ce50ba65 100644 --- a/pkg/webrtc/client_test.go +++ b/pkg/webrtc/client_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/webrtc/conn.go b/pkg/webrtc/conn.go index 0845bdda..092b05c8 100644 --- a/pkg/webrtc/conn.go +++ b/pkg/webrtc/conn.go @@ -9,7 +9,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/pion/rtcp" "github.com/pion/rtp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) type Conn struct { diff --git a/pkg/webrtc/helpers.go b/pkg/webrtc/helpers.go index b6cd3ab3..766254a0 100644 --- a/pkg/webrtc/helpers.go +++ b/pkg/webrtc/helpers.go @@ -11,10 +11,10 @@ import ( "time" "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/ice/v2" + "github.com/pion/ice/v4" "github.com/pion/sdp/v3" - "github.com/pion/stun" - "github.com/pion/webrtc/v3" + "github.com/pion/stun/v3" + "github.com/pion/webrtc/v4" ) func UnmarshalMedias(descriptions []*sdp.MediaDescription) (medias []*core.Media) { diff --git a/pkg/webrtc/producer.go b/pkg/webrtc/producer.go index a0910c39..822b1644 100644 --- a/pkg/webrtc/producer.go +++ b/pkg/webrtc/producer.go @@ -2,7 +2,7 @@ package webrtc import ( "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) func (c *Conn) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) { diff --git a/pkg/webrtc/server.go b/pkg/webrtc/server.go index 9cc89778..f8abc70a 100644 --- a/pkg/webrtc/server.go +++ b/pkg/webrtc/server.go @@ -3,7 +3,7 @@ package webrtc import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/pion/sdp/v3" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) func (c *Conn) SetOffer(offer string) (err error) { diff --git a/pkg/webrtc/track.go b/pkg/webrtc/track.go index 3102abd1..657eee1f 100644 --- a/pkg/webrtc/track.go +++ b/pkg/webrtc/track.go @@ -4,7 +4,7 @@ import ( "sync" "github.com/pion/rtp" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" ) type Track struct { diff --git a/pkg/webrtc/webrtc_test.go b/pkg/webrtc/webrtc_test.go index c864a22b..6b20b089 100644 --- a/pkg/webrtc/webrtc_test.go +++ b/pkg/webrtc/webrtc_test.go @@ -3,7 +3,7 @@ package webrtc import ( "testing" - "github.com/pion/webrtc/v3" + "github.com/pion/webrtc/v4" "github.com/stretchr/testify/require" ) diff --git a/pkg/webtorrent/client.go b/pkg/webtorrent/client.go index 3594679d..04eeccda 100644 --- a/pkg/webtorrent/client.go +++ b/pkg/webtorrent/client.go @@ -9,7 +9,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/gorilla/websocket" - pion "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v4" ) func NewClient(tracker, share, pwd string, pc *pion.PeerConnection) (*webrtc.Conn, error) { diff --git a/scripts/README.md b/scripts/README.md index 3832475c..669fe2b2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -7,8 +7,12 @@ So we will set `go 1.20` (minimum version) inside `go.mod` file. And will use en `win32` and `mac_amd64` binaries. All other binaries will use latest go version. ``` +github.com/miekg/dns v1.1.63 golang.org/x/crypto v0.33.0 golang.org/x/mod v0.20.0 // indirect +golang.org/x/net v0.35.0 // indirect +golang.org/x/sync v0.11.0 // indirect +golang.org/x/sys v0.30.0 // indirect golang.org/x/tools v0.24.0 // indirect ``` diff --git a/scripts/build.cmd b/scripts/build.cmd index 85dd9531..a543ea80 100644 --- a/scripts/build.cmd +++ b/scripts/build.cmd @@ -21,34 +21,34 @@ go build -ldflags "-s -w" -trimpath && 7z a -mx9 -sdel %FILENAME% go2rtc.exe @SET GOOS=linux @SET GOARCH=amd64 @SET FILENAME=go2rtc_linux_amd64 -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOOS=linux @SET GOARCH=386 @SET FILENAME=go2rtc_linux_i386 -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOOS=linux @SET GOARCH=arm64 @SET FILENAME=go2rtc_linux_arm64 -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOOS=linux @SET GOARCH=arm @SET GOARM=7 @SET FILENAME=go2rtc_linux_arm -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOOS=linux @SET GOARCH=arm @SET GOARM=6 @SET FILENAME=go2rtc_linux_armv6 -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOOS=linux @SET GOARCH=mipsle @SET FILENAME=go2rtc_linux_mipsel -go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx %FILENAME% +go build -ldflags "-s -w" -trimpath -o %FILENAME% && upx --best --lzma %FILENAME% @SET GOTOOLCHAIN=go1.20.14 @SET GOOS=darwin