mirror of
https://github.com/Monibuca/plugin-webrtc.git
synced 2025-10-03 14:06:26 +08:00
v4 first commit
This commit is contained in:
35
go.mod
35
go.mod
@@ -1,12 +1,31 @@
|
||||
module github.com/Monibuca/plugin-webrtc/v3
|
||||
module m7s.live/plugin/webrtc/v4
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine/v3 v3.4.5
|
||||
github.com/Monibuca/utils/v3 v3.0.5
|
||||
github.com/pion/interceptor v0.1.0
|
||||
github.com/pion/rtcp v1.2.8
|
||||
github.com/pion/rtp v1.7.4
|
||||
github.com/pion/webrtc/v3 v3.1.6
|
||||
github.com/pion/interceptor v0.1.10
|
||||
github.com/pion/rtcp v1.2.9
|
||||
github.com/pion/rtp v1.7.9
|
||||
github.com/pion/webrtc/v3 v3.1.25
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/pion/datachannel v1.5.2 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.3 // indirect
|
||||
github.com/pion/ice/v2 v2.2.2 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/sctp v1.8.2 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.4 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.5 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.0 // indirect
|
||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
|
132
go.sum
132
go.sum
@@ -1,28 +1,8 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Monibuca/engine/v3 v3.4.5 h1:gPRsliBVC70EhKK7sJz/3LoNO7lfnXNC8uUaOIHZkUE=
|
||||
github.com/Monibuca/engine/v3 v3.4.5/go.mod h1:Dik9pFxU9TFI5vj8Sv5QXZM+ooCs2fm9P7Uhe4yYNkQ=
|
||||
github.com/Monibuca/utils/v3 v3.0.5 h1:w14x0HkWTbF4MmHbINLlOwe4VJNoSOeaQChMk5E/4es=
|
||||
github.com/Monibuca/utils/v3 v3.0.5/go.mod h1:RpNS95gapWs6gimwh8Xn2x72FN5tO7Powabj7dTFyvE=
|
||||
github.com/cnotch/apirouter v0.0.0-20200731232942-89e243a791f3/go.mod h1:5deJPLON/x/s2dLOQfuKS0lenhOIT4xX0pvtN/OEIuY=
|
||||
github.com/cnotch/ipchub v1.1.0 h1:hH0lh2mU3AZXPiqMwA0pdtqrwo7PFIMRGush9OobMUs=
|
||||
github.com/cnotch/ipchub v1.1.0/go.mod h1:2PbeBs2q2VxxTVCn1eYCDwpAWuVXbq1+N0FU7GimOH4=
|
||||
github.com/cnotch/loader v0.0.0-20200405015128-d9d964d09439/go.mod h1:oWpDagHB6p+Kqqq7RoRZKyC4XAXft50hR8pbTxdbYYs=
|
||||
github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
|
||||
github.com/cnotch/queue v0.0.0-20201224060551-4191569ce8f6/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
|
||||
github.com/cnotch/scheduler v0.0.0-20200522024700-1d2da93eefc5/go.mod h1:F4GE3SZkJZ8an1Y0ZCqvSM3jeozNuKzoC67erG1PhIo=
|
||||
github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl3sR3yMGgxYupMn2btzdtJUwoxFPUE5E=
|
||||
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/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -31,47 +11,35 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE=
|
||||
github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es=
|
||||
github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
|
||||
github.com/pion/dtls/v2 v2.0.10 h1:wgys7gPR1NMbWjmjJ3CW7lkUGaun8djgH8nahpNLnxI=
|
||||
github.com/pion/dtls/v2 v2.0.10/go.mod h1:00OxfeCRWHShcqT9jx8pKKmBWuTt0NCZoVPCaC4VKvU=
|
||||
github.com/pion/ice/v2 v2.1.13 h1:/YNYcIw56LT/whwuzkTnrprcRnapj2ZNqUsR0W8elmo=
|
||||
github.com/pion/ice/v2 v2.1.13/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
|
||||
github.com/pion/interceptor v0.1.0 h1:SlXKaDlEvSl7cr4j8fJykzVz4UdH+7UDtcvx+u01wLU=
|
||||
github.com/pion/interceptor v0.1.0/go.mod h1:j5NIl3tJJPB3u8+Z2Xz8MZs/VV6rc+If9mXEKNuFmEM=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
|
||||
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
|
||||
github.com/pion/ice/v2 v2.2.2 h1:UfmAslxZ0u0itVjA4x7aw7WeQIv22FdF8VjW9cM+74g=
|
||||
github.com/pion/ice/v2 v2.2.2/go.mod h1:vLI7dFqxw8zMSb9J+ca74XU7JjLhddgfQB9+BbTydCo=
|
||||
github.com/pion/interceptor v0.1.10 h1:DJ2GjMGm4XGIQgMJxuEpdaExdY/6RdngT7Uh4oVmquU=
|
||||
github.com/pion/interceptor v0.1.10/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
@@ -79,41 +47,35 @@ github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01
|
||||
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.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtcp v1.2.8 h1:Cys8X6r0xxU65ESTmXkqr8eU1Q1Wx+lNkoZCUH4zD7E=
|
||||
github.com/pion/rtcp v1.2.8/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
|
||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
|
||||
github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
|
||||
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/rtp v1.7.7/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp v1.7.9 h1:17W5Mt2IM3MVfOh7yRfzXbbKXYzBZxV8eG4KKAy+0bg=
|
||||
github.com/pion/rtp v1.7.9/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
|
||||
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.6 h1:r6WQRayW2SyKTYeRl4vBUQ43XXp7RSwBJ9+tNQWI5zQ=
|
||||
github.com/pion/webrtc/v3 v3.1.6/go.mod h1:tkwdWNYdZhc200hH/wPx6AtNo/rcTAM6MICA6dg1je8=
|
||||
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pion/webrtc/v3 v3.1.25 h1:8bTa2lLI5rszkKCKVO6L9VlKhhmmwMXZ/beSWLwQK14=
|
||||
github.com/pion/webrtc/v3 v3.1.25/go.mod h1:BA9hvF1mCu64w/to7nHrSNMxx+zMqVst990zA7xgfCM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY=
|
||||
github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -123,54 +85,45 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
@@ -185,13 +138,12 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
24
io.go
Normal file
24
io.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
. "github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
type WebRTCIO struct {
|
||||
*PeerConnection
|
||||
SDP string
|
||||
}
|
||||
|
||||
func (IO *WebRTCIO) GetAnswer() (string, error) {
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
answer, err := IO.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gatherComplete := GatheringCompletePromise(IO.PeerConnection)
|
||||
if err := IO.SetLocalDescription(answer); err != nil {
|
||||
return "", err
|
||||
}
|
||||
<-gatherComplete
|
||||
return IO.LocalDescription().SDP, nil
|
||||
}
|
426
main.go
426
main.go
@@ -1,37 +1,19 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-webrtc/v3/webrtc"
|
||||
|
||||
"github.com/Monibuca/utils/v3"
|
||||
"github.com/pion/rtcp"
|
||||
. "github.com/pion/webrtc/v3"
|
||||
"github.com/pion/webrtc/v3/pkg/media"
|
||||
"m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/config"
|
||||
"m7s.live/plugin/webrtc/v4/webrtc"
|
||||
)
|
||||
|
||||
var config = struct {
|
||||
ICEServers []string
|
||||
PublicIP []string
|
||||
PortMin uint16
|
||||
PortMax uint16
|
||||
PLI time.Duration
|
||||
}{nil, nil, 0, 0, 2000}
|
||||
|
||||
// }{[]string{
|
||||
// "stun:stun.ekiga.net",
|
||||
// "stun:stun.ideasip.com",
|
||||
@@ -59,359 +41,117 @@ var config = struct {
|
||||
// port int
|
||||
// }
|
||||
var (
|
||||
playWaitList WaitList
|
||||
reg_level = regexp.MustCompile("profile-level-id=(4.+f)")
|
||||
api *API
|
||||
)
|
||||
|
||||
type WaitList struct {
|
||||
m map[string]*WebRTC
|
||||
l sync.Mutex
|
||||
type WebRTCConfig struct {
|
||||
config.Publish
|
||||
config.Subscribe
|
||||
ICEServers []string
|
||||
PublicIP []string
|
||||
PortMin uint16
|
||||
PortMax uint16
|
||||
PLI time.Duration
|
||||
m MediaEngine
|
||||
s SettingEngine
|
||||
api *API
|
||||
}
|
||||
|
||||
func (wl *WaitList) Set(k string, v *WebRTC) {
|
||||
wl.l.Lock()
|
||||
defer wl.l.Unlock()
|
||||
if wl.m == nil {
|
||||
wl.m = make(map[string]*WebRTC)
|
||||
}
|
||||
wl.m[k] = v
|
||||
}
|
||||
func (wl *WaitList) Get(k string) *WebRTC {
|
||||
wl.l.Lock()
|
||||
defer wl.l.Unlock()
|
||||
defer delete(wl.m, k)
|
||||
return wl.m[k]
|
||||
}
|
||||
func init() {
|
||||
pc := engine.PluginConfig{
|
||||
Config: &config,
|
||||
Name: "WebRTC",
|
||||
}
|
||||
pc.Install(run)
|
||||
}
|
||||
|
||||
type WebRTC struct {
|
||||
*PeerConnection
|
||||
}
|
||||
|
||||
func (rtc *WebRTC) Publish(streamPath string) bool {
|
||||
if _, err := rtc.AddTransceiverFromKind(RTPCodecTypeVideo); err != nil {
|
||||
if err != nil {
|
||||
utils.Println("AddTransceiverFromKind video", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, err := rtc.AddTransceiverFromKind(RTPCodecTypeAudio); err != nil {
|
||||
if err != nil {
|
||||
utils.Println("AddTransceiverFromKind audio", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
stream := &engine.Stream{
|
||||
Type: "WebRTC",
|
||||
StreamPath: streamPath,
|
||||
}
|
||||
if stream.Publish() {
|
||||
rtc.OnICEConnectionStateChange(func(connectionState ICEConnectionState) {
|
||||
utils.Printf("%s Connection State has changed %s ", streamPath, connectionState.String())
|
||||
switch connectionState {
|
||||
case ICEConnectionStateDisconnected, ICEConnectionStateFailed:
|
||||
stream.Close()
|
||||
}
|
||||
})
|
||||
//f, _ := os.OpenFile("resource/live/rtc.h264", os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
rtc.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
|
||||
if codec := track.Codec(); track.Kind() == RTPCodecTypeAudio {
|
||||
var at *engine.RTPAudio
|
||||
switch codec.PayloadType {
|
||||
case 8:
|
||||
at = stream.NewRTPAudio(7)
|
||||
at.SoundRate = 8000
|
||||
at.SoundSize = 16
|
||||
at.Channels = 1
|
||||
at.ExtraData = []byte{(at.CodecID << 4) | (1 << 1)}
|
||||
case 0:
|
||||
at = stream.NewRTPAudio(8)
|
||||
at.SoundRate = 8000
|
||||
at.SoundSize = 16
|
||||
at.Channels = 1
|
||||
at.ExtraData = []byte{(at.CodecID << 4) | (1 << 1)}
|
||||
default:
|
||||
return
|
||||
}
|
||||
for {
|
||||
b := make([]byte, 1460)
|
||||
if i, _, err := track.Read(b); err == nil {
|
||||
at.Push(b[:i])
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Millisecond * config.PLI)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if rtcpErr := rtc.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil {
|
||||
fmt.Println(rtcpErr)
|
||||
}
|
||||
case <-stream.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
vt := stream.NewRTPVideo(7)
|
||||
for {
|
||||
b := make([]byte, 1460)
|
||||
if i, _, err := track.Read(b); err == nil {
|
||||
vt.Push(b[:i])
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (rtc *WebRTC) GetAnswer() ([]byte, error) {
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
answer, err := rtc.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gatherComplete := GatheringCompletePromise(rtc.PeerConnection)
|
||||
if err := rtc.SetLocalDescription(answer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
<-gatherComplete
|
||||
if bytes, err := json.Marshal(rtc.LocalDescription()); err != nil {
|
||||
utils.Println(err)
|
||||
return bytes, err
|
||||
} else {
|
||||
return bytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
func run() {
|
||||
var m MediaEngine
|
||||
var s SettingEngine
|
||||
//m.RegisterDefaultCodecs()
|
||||
webrtc.RegisterCodecs(&m)
|
||||
|
||||
func (conf *WebRTCConfig) OnEvent(event any) {
|
||||
switch event.(type) {
|
||||
case engine.FirstConfig:
|
||||
webrtc.RegisterCodecs(&conf.m)
|
||||
i := &interceptor.Registry{}
|
||||
if len(config.PublicIP) > 0 {
|
||||
s.SetNAT1To1IPs(config.PublicIP, ICECandidateTypeHost)
|
||||
if len(conf.PublicIP) > 0 {
|
||||
conf.s.SetNAT1To1IPs(conf.PublicIP, ICECandidateTypeHost)
|
||||
}
|
||||
if config.PortMin > 0 && config.PortMax > 0 {
|
||||
s.SetEphemeralUDPPortRange(config.PortMin, config.PortMax)
|
||||
if conf.PortMin > 0 && conf.PortMax > 0 {
|
||||
conf.s.SetEphemeralUDPPortRange(conf.PortMin, conf.PortMax)
|
||||
}
|
||||
if len(config.PublicIP) > 0 {
|
||||
s.SetNAT1To1IPs(config.PublicIP, ICECandidateTypeHost)
|
||||
if len(conf.PublicIP) > 0 {
|
||||
conf.s.SetNAT1To1IPs(conf.PublicIP, ICECandidateTypeHost)
|
||||
}
|
||||
s.SetNetworkTypes([]NetworkType{NetworkTypeUDP4, NetworkTypeUDP6})
|
||||
if err := RegisterDefaultInterceptors(&m, i); err != nil {
|
||||
conf.s.SetNetworkTypes([]NetworkType{NetworkTypeUDP4, NetworkTypeUDP6})
|
||||
if err := RegisterDefaultInterceptors(&conf.m, i); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
api := NewAPI(WithMediaEngine(&m),
|
||||
WithInterceptorRegistry(i), WithSettingEngine(s))
|
||||
http.HandleFunc("/api/webrtc/play", func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.CORS(w, r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
conf.api = NewAPI(WithMediaEngine(&conf.m),
|
||||
WithInterceptorRegistry(i), WithSettingEngine(conf.s))
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *WebRTCConfig) API_play(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/sdp")
|
||||
streamPath := r.URL.Query().Get("streamPath")
|
||||
var offer SessionDescription
|
||||
var rtc WebRTC
|
||||
sub := engine.Subscriber{
|
||||
ID: r.RemoteAddr,
|
||||
Type: "WebRTC",
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
utils.Println(err)
|
||||
fmt.Fprintf(w, `{"errmsg":"%s"}`, err)
|
||||
var suber WebRTCSubscriber
|
||||
suber.SDP = string(bytes)
|
||||
if suber.PeerConnection, err = api.NewPeerConnection(Configuration{}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(bytes, &offer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sub.Subscribe(streamPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rtc.PeerConnection, err = api.NewPeerConnection(Configuration{}); err != nil {
|
||||
return
|
||||
}
|
||||
rtc.OnICECandidate(func(ice *ICECandidate) {
|
||||
suber.OnICECandidate(func(ice *ICECandidate) {
|
||||
if ice != nil {
|
||||
utils.Println(ice.ToJSON().Candidate)
|
||||
suber.Info(ice.ToJSON().Candidate)
|
||||
}
|
||||
})
|
||||
if err = rtc.SetRemoteDescription(offer); err != nil {
|
||||
if err = suber.SetRemoteDescription(SessionDescription{SDP: suber.SDP}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
vt := sub.WaitVideoTrack("h264")
|
||||
at := sub.WaitAudioTrack("pcma", "pcmu")
|
||||
var videoTrack *TrackLocalStaticRTP
|
||||
var rtpSender *RTPSender
|
||||
if vt != nil {
|
||||
pli := "42001f"
|
||||
pli = fmt.Sprintf("%x", vt.ExtraData.NALUs[0][1:4])
|
||||
if !strings.Contains(offer.SDP, pli) {
|
||||
pli = reg_level.FindAllStringSubmatch(offer.SDP, -1)[0][1]
|
||||
}
|
||||
if videoTrack, err = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, "video", "m7s"); err != nil {
|
||||
if err = plugin.Subscribe(streamPath, &suber); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rtpSender, err = rtc.AddTrack(videoTrack)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var lastTimeStampV uint32
|
||||
|
||||
var vpacketer rtp.Packetizer
|
||||
ssrc := uintptr(unsafe.Pointer(&sub))
|
||||
vpacketer = rtp.NewPacketizer(1200, 96, uint32(ssrc), &codecs.H264Payloader{}, rtp.NewFixedSequencer(1), 90000)
|
||||
|
||||
sub.OnVideo = func(ts uint32, pack *engine.VideoPack) {
|
||||
var s uint32 = 40
|
||||
if lastTimeStampV > 0 {
|
||||
s = ts - lastTimeStampV
|
||||
}
|
||||
lastTimeStampV = ts
|
||||
if pack.IDR {
|
||||
for _, nalu := range vt.ExtraData.NALUs {
|
||||
for _, packet := range vpacketer.Packetize(nalu, 0) {
|
||||
err = videoTrack.WriteRTP(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
for naluIndex, nalu := range pack.NALUs {
|
||||
var packets []*rtp.Packet
|
||||
if naluIndex == len(pack.NALUs)-1 {
|
||||
packets = vpacketer.Packetize(nalu, s)
|
||||
if sdp, err := suber.GetAnswer(); err == nil {
|
||||
w.Write([]byte(sdp))
|
||||
} else {
|
||||
packets = vpacketer.Packetize(nalu, 0)
|
||||
}
|
||||
for packIndex, packet := range packets {
|
||||
packet.Marker = naluIndex == len(pack.NALUs)-1 && packIndex == len(packets)-1
|
||||
err = videoTrack.WriteRTP(packet)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
rtcpBuf := make([]byte, 1500)
|
||||
for {
|
||||
if n, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
|
||||
|
||||
return
|
||||
} else {
|
||||
if p, err := rtcp.Unmarshal(rtcpBuf[:n]); err == nil {
|
||||
for _, pp := range p {
|
||||
switch pp.(type) {
|
||||
case *rtcp.PictureLossIndication:
|
||||
|
||||
fmt.Println("PictureLossIndication")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
if at == nil {
|
||||
engine.TriggerHook(engine.HOOK_REQUEST_TRANSAUDIO, &engine.TransCodeReq{
|
||||
Subscriber: &sub,
|
||||
RequestCodec: "pcma",
|
||||
})
|
||||
at = sub.WaitAudioTrack("pcma")
|
||||
}
|
||||
if at != nil {
|
||||
var audioTrack *TrackLocalStaticSample
|
||||
audioMimeType := MimeTypePCMA
|
||||
if at.CodecID == 8 {
|
||||
audioMimeType = MimeTypePCMU
|
||||
}
|
||||
if audioTrack, err = NewTrackLocalStaticSample(RTPCodecCapability{audioMimeType, 8000, 0, "", nil}, "audio", "m7s"); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = rtc.AddTrack(audioTrack); err != nil {
|
||||
return
|
||||
}
|
||||
var lastTimeStampA uint32
|
||||
sub.OnAudio = func(ts uint32, pack *engine.AudioPack) {
|
||||
var s uint32
|
||||
if lastTimeStampA > 0 {
|
||||
s = ts - lastTimeStampA
|
||||
}
|
||||
lastTimeStampA = ts
|
||||
audioTrack.WriteSample(media.Sample{
|
||||
Data: pack.Raw, Duration: time.Millisecond * time.Duration(s),
|
||||
})
|
||||
}
|
||||
}
|
||||
if bytes, err := rtc.GetAnswer(); err == nil {
|
||||
w.Write(bytes)
|
||||
rtc.OnConnectionStateChange(func(pcs PeerConnectionState) {
|
||||
utils.Printf("%s Connection State has changed %s ", streamPath, pcs.String())
|
||||
switch pcs {
|
||||
case PeerConnectionStateConnected:
|
||||
if at != nil {
|
||||
go sub.PlayAudio(at)
|
||||
}
|
||||
if vt != nil {
|
||||
go sub.PlayVideo(vt)
|
||||
}
|
||||
case PeerConnectionStateDisconnected, PeerConnectionStateFailed:
|
||||
sub.Close()
|
||||
rtc.PeerConnection.Close()
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/api/webrtc/publish", func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.CORS(w, r)
|
||||
func (conf *WebRTCConfig) API_publish(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/sdp")
|
||||
streamPath := r.URL.Query().Get("streamPath")
|
||||
offer := SessionDescription{}
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
err = json.Unmarshal(bytes, &offer)
|
||||
if err != nil {
|
||||
utils.Println(err)
|
||||
var puber WebRTCPublisher
|
||||
puber.SDP = string(bytes)
|
||||
if puber.PeerConnection, err = api.NewPeerConnection(Configuration{}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rtc := new(WebRTC)
|
||||
if rtc.PeerConnection, err = api.NewPeerConnection(Configuration{}); err != nil {
|
||||
return
|
||||
}
|
||||
if rtc.Publish(streamPath) {
|
||||
if err := rtc.SetRemoteDescription(offer); err != nil {
|
||||
utils.Println(err)
|
||||
return
|
||||
}
|
||||
if bytes, err = rtc.GetAnswer(); err == nil {
|
||||
w.Write(bytes)
|
||||
} else {
|
||||
utils.Println("GetAnswer:", err)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("bad name"))
|
||||
puber.OnICECandidate(func(ice *ICECandidate) {
|
||||
if ice != nil {
|
||||
puber.Info(ice.ToJSON().Candidate)
|
||||
}
|
||||
})
|
||||
if _, err = puber.AddTransceiverFromKind(RTPCodecTypeVideo); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err = puber.AddTransceiverFromKind(RTPCodecTypeAudio); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err = plugin.Publish(streamPath, &puber); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := puber.SetRemoteDescription(SessionDescription{SDP: puber.SDP}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if answer, err := puber.GetAnswer(); err == nil {
|
||||
w.Write([]byte(answer))
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var webrtcConfig = &WebRTCConfig{
|
||||
PLI: time.Second * 2,
|
||||
}
|
||||
|
||||
var plugin = engine.InstallPlugin(webrtcConfig)
|
||||
|
78
publisher.go
Normal file
78
publisher.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
. "github.com/pion/webrtc/v3"
|
||||
. "m7s.live/engine/v4"
|
||||
. "m7s.live/engine/v4/track"
|
||||
)
|
||||
|
||||
type WebRTCPublisher struct {
|
||||
Publisher
|
||||
WebRTCIO
|
||||
}
|
||||
|
||||
func (puber *WebRTCPublisher) OnEvent(event any) {
|
||||
switch v := event.(type) {
|
||||
case IPublisher:
|
||||
puber.OnICEConnectionStateChange(func(connectionState ICEConnectionState) {
|
||||
puber.Info("Connection State has changed:" + connectionState.String())
|
||||
switch connectionState {
|
||||
case ICEConnectionStateDisconnected, ICEConnectionStateFailed:
|
||||
puber.Stop()
|
||||
}
|
||||
})
|
||||
puber.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
|
||||
if codec := track.Codec(); track.Kind() == RTPCodecTypeAudio {
|
||||
if puber.Equal(v) || puber.AudioTrack == nil {
|
||||
switch codec.PayloadType {
|
||||
case 8:
|
||||
puber.AudioTrack = NewG711(puber.Stream, true)
|
||||
case 0:
|
||||
puber.AudioTrack = NewG711(puber.Stream, false)
|
||||
default:
|
||||
puber.AudioTrack = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
b := make([]byte, 1460)
|
||||
if i, _, err := track.Read(b); err == nil {
|
||||
puber.AudioTrack.WriteRTP(b[:i])
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Millisecond * webrtcConfig.PLI)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if rtcpErr := puber.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil {
|
||||
fmt.Println(rtcpErr)
|
||||
}
|
||||
case <-puber.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
if puber.Equal(v) {
|
||||
puber.VideoTrack = NewH264(puber.Stream)
|
||||
}
|
||||
for {
|
||||
b := make([]byte, 1460)
|
||||
if i, _, err := track.Read(b); err == nil {
|
||||
puber.VideoTrack.WriteRTP(b[:i])
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
puber.Publisher.OnEvent(event)
|
||||
}
|
85
subscriber.go
Normal file
85
subscriber.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
. "github.com/pion/webrtc/v3"
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/codec"
|
||||
"m7s.live/engine/v4/track"
|
||||
)
|
||||
|
||||
type WebRTCSubscriber struct {
|
||||
Subscriber
|
||||
WebRTCIO
|
||||
videoTrack *TrackLocalStaticRTP
|
||||
audioTrack *TrackLocalStaticRTP
|
||||
}
|
||||
|
||||
func (suber *WebRTCSubscriber) OnEvent(event any) {
|
||||
switch v := event.(type) {
|
||||
case *track.Video:
|
||||
if v.CodecID == codec.CodecID_H264 {
|
||||
pli := "42001f"
|
||||
pli = fmt.Sprintf("%x", v.GetDecoderConfiguration().Raw[0][1:4])
|
||||
if !strings.Contains(suber.SDP, pli) {
|
||||
pli = reg_level.FindAllStringSubmatch(suber.SDP, -1)[0][1]
|
||||
}
|
||||
suber.videoTrack, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, "video", "m7s")
|
||||
rtpSender, _ := suber.PeerConnection.AddTrack(suber.videoTrack)
|
||||
go func() {
|
||||
rtcpBuf := make([]byte, 1500)
|
||||
for {
|
||||
if n, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
|
||||
|
||||
return
|
||||
} else {
|
||||
if p, err := rtcp.Unmarshal(rtcpBuf[:n]); err == nil {
|
||||
for _, pp := range p {
|
||||
switch pp.(type) {
|
||||
case *rtcp.PictureLossIndication:
|
||||
|
||||
fmt.Println("PictureLossIndication")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
suber.Subscriber.AddTrack(v) //接受这个track
|
||||
}
|
||||
case *track.Audio:
|
||||
audioMimeType := MimeTypePCMA
|
||||
if v.CodecID == codec.CodecID_PCMU {
|
||||
audioMimeType = MimeTypePCMU
|
||||
}
|
||||
if v.CodecID == codec.CodecID_PCMA || v.CodecID == codec.CodecID_PCMU {
|
||||
suber.audioTrack, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: audioMimeType}, "audio", "m7s")
|
||||
suber.PeerConnection.AddTrack(suber.audioTrack)
|
||||
suber.Subscriber.AddTrack(v) //接受这个track
|
||||
}
|
||||
case *VideoFrame:
|
||||
for _, p := range v.RTP {
|
||||
suber.videoTrack.Write(p.Raw)
|
||||
}
|
||||
case *AudioFrame:
|
||||
for _, p := range v.RTP {
|
||||
suber.audioTrack.Write(p.Raw)
|
||||
}
|
||||
case ISubscriber:
|
||||
suber.OnConnectionStateChange(func(pcs PeerConnectionState) {
|
||||
suber.Info("Connection State has changed:" + pcs.String())
|
||||
switch pcs {
|
||||
case PeerConnectionStateConnected:
|
||||
go suber.PlayBlock(suber)
|
||||
case PeerConnectionStateDisconnected, PeerConnectionStateFailed:
|
||||
suber.Stop()
|
||||
suber.PeerConnection.Close()
|
||||
}
|
||||
})
|
||||
default:
|
||||
suber.Subscriber.OnEvent(event)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user