├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── batcher.go ├── go.mod ├── go.sum ├── io.go ├── main.go ├── publish.html ├── publisher.go ├── subscribe.html ├── subscriber.go └── webrtc └── config.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | node_modules 4 | /.history 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present, dexter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | _[简体中文](https://github.com/Monibuca/plugin-webrtc) | English_ 2 | # WebRTC Plugin 3 | 4 | This plugin provides the functionality to stream videos to Monibuca through a web page and to play streams from Monibuca using WebRTC technology. It follows the WHIP specification. 5 | 6 | ## Plugin URL 7 | 8 | https://github.com/Monibuca/plugin-webrtc 9 | 10 | ## Plugin Import 11 | 12 | ```go 13 | import ( _ "m7s.live/plugin/webrtc/v4" ) 14 | ``` 15 | 16 | ## Default Configuration 17 | 18 | ```yaml 19 | webrtc: 20 | iceservers: [] 21 | publicip: [] # can be an array or a single string (automatically converted to an array) 22 | port: tcp:9000 # can be a range of ports like udp:8000-9000 or a single port like udp:9000 23 | pli: 2s # 2s 24 | ``` 25 | 26 | ### ICE Server Configuration Format 27 | 28 | ```yaml 29 | webrtc: 30 | iceservers: 31 | - urls: 32 | - stun:stun.l.google.com:19302 33 | - turn:turn.example.org 34 | username: user 35 | credential: pass 36 | ``` 37 | 38 | ### Configuration for Local Testing 39 | 40 | If testing locally, no change in configuration is required. However, if you are accessing it remotely, then you need to configure the public IP. 41 | 42 | ## Basic Principle 43 | 44 | The exchange of SDP messages between the browser and Monibuca takes place and RTP packets are read or sent to stream videos. 45 | 46 | ## API 47 | 48 | ### Play address 49 | `/webrtc/play/[streamPath]` 50 | 51 | Body: `SDP` 52 | 53 | Content-Type: `application/sdp` 54 | 55 | Response Body: `SDP` 56 | 57 | ### Push address 58 | 59 | `/webrtc/push/[streamPath]` 60 | 61 | Body: `SDP` 62 | 63 | Content-Type: `application/sdp` 64 | 65 | Response Body: `SDP` 66 | 67 | ### Push Test Page 68 | 69 | `/webrtc/test/publish` 70 | - `?streamPath=xxx` The streamPath to publish, default is `live/webrtc` 71 | - you can add other query parameters to the URL 72 | 73 | ### ScreenShare Test Page 74 | 75 | `/webrtc/test/screenshare` 76 | - `?streamPath=xxx` The streamPath to publish, default is `live/webrtc` 77 | - you can add other query parameters to the URL 78 | 79 | ### Play Test Page 80 | 81 | `/webrtc/test/subscribe` 82 | - `?streamPath=xxx` The streamPath to play, default is `live/webrtc` 83 | - you can add other query parameters to the URL 84 | ## WHIP 85 | 86 | WebRTC-HTTP ingestion protocol 87 | A specification for the exchange of SDP messages between WebRTC clients. 88 | 89 | [WHIP ietf](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip-02) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _[English](https://github.com/Monibuca/plugin-webrtc/blob/v4/README.en.md) | 简体中文_ 2 | # WebRTC 插件 3 | 4 | 提供通过网页发布视频到monibuca,以及从monibuca拉流通过webrtc进行播放的功能,遵循WHIP规范 5 | 6 | ## 插件地址 7 | 8 | https://github.com/Monibuca/plugin-webrtc 9 | 10 | ## 插件引入 11 | ```go 12 | import ( _ "m7s.live/plugin/webrtc/v4" ) 13 | ``` 14 | 15 | ## 默认配置 16 | 17 | ```yaml 18 | webrtc: 19 | iceservers: [] 20 | publicip: [] # 可以是数组也可以是字符串(内部自动转成数组) 21 | port: tcp:9000 # 可以是udp:8000-9000 范围端口,也可以udp:9000 单个端口 22 | pli: 2s # 2s 23 | ``` 24 | ### ICE服务器配置格式 25 | 26 | ```yaml 27 | webrtc: 28 | iceservers: 29 | - urls: 30 | - stun:stun.l.google.com:19302 31 | - turn:turn.example.org 32 | username: user 33 | credential: pass 34 | ``` 35 | 36 | 37 | ### 本地测试无需修改配置,如果远程访问,则需要配置publicip 38 | 39 | ## 基本原理 40 | 41 | 通过浏览器和monibuca交换sdp信息,然后读取rtp包或者发送rtp的方式进行 42 | 43 | ## API 44 | 45 | ### 播放地址 46 | `/webrtc/play/[streamPath]` 47 | 48 | Body: `SDP` 49 | 50 | Content-Type: `application/sdp` 51 | 52 | Response Body: `SDP` 53 | 54 | ### 推流地址 55 | 56 | `/webrtc/push/[streamPath]` 57 | 58 | Body: `SDP` 59 | 60 | Content-Type: `application/sdp` 61 | 62 | Response Body: `SDP` 63 | 64 | ### 推流测试页面 65 | 66 | `/webrtc/test/publish` 67 | - 可增加参数`?streamPath=xxx`指定推流地址,默认为`live/webrtc` 68 | - 可以增加其他推流参数 69 | 70 | ### 屏幕分享测试 71 | 72 | `/webrtc/test/screenshare` 73 | - 可增加参数`?streamPath=xxx`指定推流地址,默认为`live/webrtc` 74 | - 可以增加其他推流参数 75 | ### 播放测试页面 76 | 77 | `/webrtc/test/subscribe` 78 | - 可增加参数`?streamPath=xxx`指定播放地址,默认为`live/webrtc` 79 | - 可以增加其他播放参数 80 | ## WHIP 81 | WebRTC-HTTP ingestion protocol 82 | 用于WebRTC交换SDP信息的规范 83 | 84 | [WHIP ietf](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip) 85 | -------------------------------------------------------------------------------- /batcher.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | . "github.com/pion/webrtc/v3" 8 | "go.uber.org/zap" 9 | "m7s.live/engine/v4/codec" 10 | "m7s.live/engine/v4/util" 11 | ) 12 | 13 | type Signal struct { 14 | Type string `json:"type"` 15 | StreamList []string `json:"streamList"` 16 | Offer string `json:"offer"` 17 | Answer string `json:"answer"` 18 | StreamPath string `json:"streamPath"` 19 | } 20 | 21 | type SignalStreamPath struct { 22 | Type string `json:"type"` 23 | StreamPath string `json:"streamPath"` 24 | } 25 | 26 | func NewRemoveSingal(streamPath string) string { 27 | s := SignalStreamPath{ 28 | Type: "remove", 29 | StreamPath: streamPath, 30 | } 31 | b, _ := json.Marshal(s) 32 | return string(b) 33 | } 34 | 35 | type SignalSDP struct { 36 | Type string `json:"type"` 37 | SDP string `json:"sdp"` 38 | } 39 | 40 | func NewAnswerSingal(sdp string) string { 41 | s := SignalSDP{ 42 | Type: "answer", 43 | SDP: sdp, 44 | } 45 | b, _ := json.Marshal(s) 46 | return string(b) 47 | } 48 | 49 | type WebRTCBatcher struct { 50 | PageSize int 51 | PageNum int 52 | subscribers util.Map[string, *WebRTCBatchSubscriber] 53 | signalChannel *DataChannel 54 | WebRTCPublisher 55 | } 56 | 57 | func (suber *WebRTCBatcher) Start() (err error) { 58 | suber.OnICECandidate(func(ice *ICECandidate) { 59 | if ice != nil { 60 | WebRTCPlugin.Info(ice.ToJSON().Candidate) 61 | } 62 | }) 63 | suber.OnDataChannel(func(d *DataChannel) { 64 | WebRTCPlugin.Info("OnDataChannel:" + d.Label()) 65 | suber.signalChannel = d 66 | suber.signalChannel.OnMessage(suber.Signal) 67 | }) 68 | if err = suber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: suber.SDP}); err != nil { 69 | return 70 | } 71 | suber.OnConnectionStateChange(func(pcs PeerConnectionState) { 72 | WebRTCPlugin.Info("Connection State has changed:" + pcs.String()) 73 | switch pcs { 74 | case PeerConnectionStateConnected: 75 | 76 | case PeerConnectionStateDisconnected, PeerConnectionStateFailed: 77 | zr := zap.String("reason", pcs.String()) 78 | suber.subscribers.Range(func(key string, value *WebRTCBatchSubscriber) { 79 | value.Stop(zr) 80 | }) 81 | if suber.Publisher.Stream != nil { 82 | suber.Publisher.Stop(zr) 83 | } 84 | suber.PeerConnection.Close() 85 | } 86 | }) 87 | return 88 | } 89 | 90 | func (suber *WebRTCBatcher) RemoveSubscribe(streamPath string) { 91 | suber.signalChannel.SendText(NewRemoveSingal(streamPath)) 92 | } 93 | func (suber *WebRTCBatcher) Answer() (err error) { 94 | var answer string 95 | if answer, err = suber.GetAnswer(); err == nil { 96 | err = suber.signalChannel.SendText(NewAnswerSingal(answer)) 97 | } 98 | if err != nil { 99 | WebRTCPlugin.Error("Signal GetAnswer", zap.Error(err)) 100 | } 101 | return 102 | } 103 | 104 | func (suber *WebRTCBatcher) Signal(msg DataChannelMessage) { 105 | var s Signal 106 | // var offer SessionDescription 107 | if err := json.Unmarshal(msg.Data, &s); err != nil { 108 | WebRTCPlugin.Error("Signal", zap.Error(err)) 109 | } else { 110 | switch s.Type { 111 | case "subscribe": 112 | if err = suber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: s.Offer}); err != nil { 113 | WebRTCPlugin.Error("Signal SetRemoteDescription", zap.Error(err)) 114 | return 115 | } 116 | for _, streamPath := range s.StreamList { 117 | if suber.subscribers.Has(streamPath) { 118 | continue 119 | } 120 | sub := &WebRTCBatchSubscriber{} 121 | sub.ID = fmt.Sprintf("%s_%s", suber.ID, streamPath) 122 | sub.WebRTCIO = suber.WebRTCIO 123 | if err = WebRTCPlugin.SubscribeExist(streamPath, sub); err == nil { 124 | suber.subscribers.Add(streamPath, sub) 125 | go func(streamPath string) { 126 | if sub.DC == nil { 127 | sub.PlayRTP() 128 | if sub.audio.RTPSender != nil { 129 | suber.RemoveTrack(sub.audio.RTPSender) 130 | } 131 | if sub.video.RTPSender != nil { 132 | suber.RemoveTrack(sub.video.RTPSender) 133 | } 134 | suber.RemoveSubscribe(streamPath) 135 | } else { 136 | sub.DC.OnOpen(func() { 137 | sub.DC.Send(codec.FLVHeader) 138 | go func() { 139 | sub.PlayFLV() 140 | sub.DC.Close() 141 | suber.RemoveSubscribe(streamPath) 142 | }() 143 | }) 144 | } 145 | }(streamPath) 146 | } else { 147 | WebRTCPlugin.Error("subscribe", zap.String("streamPath", streamPath), zap.Error(err)) 148 | suber.RemoveSubscribe(streamPath) 149 | } 150 | } 151 | err = suber.Answer() 152 | // if offer, err = suber.CreateOffer(nil); err == nil { 153 | // b, _ := json.Marshal(offer) 154 | // err = suber.signalChannel.SendText(string(b)) 155 | // suber.SetLocalDescription(offer) 156 | // } 157 | case "publish", "unpublish": 158 | if err = suber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: s.Offer}); err != nil { 159 | WebRTCPlugin.Error("Signal SetRemoteDescription", zap.Error(err)) 160 | return 161 | } 162 | if err = suber.Answer(); err == nil { 163 | switch s.Type { 164 | case "publish": 165 | WebRTCPlugin.Publish(s.StreamPath, suber) 166 | case "unpublish": 167 | suber.Stop() 168 | } 169 | } 170 | case "answer": 171 | if err = suber.SetRemoteDescription(SessionDescription{Type: SDPTypeAnswer, SDP: s.Answer}); err != nil { 172 | WebRTCPlugin.Error("Signal SetRemoteDescription", zap.Error(err)) 173 | return 174 | } 175 | } 176 | WebRTCPlugin.Info(s.Type) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module m7s.live/plugin/webrtc/v4 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/pion/interceptor v0.1.25 7 | github.com/pion/rtcp v1.2.12 8 | github.com/pion/webrtc/v3 v3.2.20 9 | go.uber.org/zap v1.26.0 10 | m7s.live/engine/v4 v4.15.2 11 | ) 12 | 13 | require ( 14 | github.com/bluenviron/gortsplib/v4 v4.6.2 // indirect 15 | github.com/bluenviron/mediacommon v1.5.1 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/deepch/vdk v0.0.27 // indirect 18 | github.com/denisbrodbeck/machineid v1.0.1 // indirect 19 | github.com/go-ole/go-ole v1.2.6 // indirect 20 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 21 | github.com/golang/mock v1.6.0 // indirect 22 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect 23 | github.com/google/uuid v1.4.0 // indirect 24 | github.com/logrusorgru/aurora/v4 v4.0.0 // indirect 25 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect 26 | github.com/mcuadros/go-defaults v1.2.0 // indirect 27 | github.com/onsi/ginkgo/v2 v2.9.5 // indirect 28 | github.com/pion/datachannel v1.5.5 // indirect 29 | github.com/pion/dtls/v2 v2.2.7 // indirect 30 | github.com/pion/ice/v2 v2.3.11 // indirect 31 | github.com/pion/logging v0.2.2 // indirect 32 | github.com/pion/mdns v0.0.9 // indirect 33 | github.com/pion/randutil v0.1.0 // indirect 34 | github.com/pion/rtp v1.8.3 // indirect 35 | github.com/pion/sctp v1.8.9 // indirect 36 | github.com/pion/sdp/v3 v3.0.6 // indirect 37 | github.com/pion/srtp/v2 v2.0.17 // indirect 38 | github.com/pion/stun v0.6.1 // indirect 39 | github.com/pion/transport/v2 v2.2.4 // indirect 40 | github.com/pion/turn/v2 v2.1.3 // indirect 41 | github.com/pkg/errors v0.9.1 // indirect 42 | github.com/pmezard/go-difflib v1.0.0 // indirect 43 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect 44 | github.com/q191201771/naza v0.30.48 // indirect 45 | github.com/quic-go/qtls-go1-20 v0.3.3 // indirect 46 | github.com/quic-go/quic-go v0.38.1 // indirect 47 | github.com/shirou/gopsutil/v3 v3.23.8 // indirect 48 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 49 | github.com/stretchr/testify v1.8.4 // indirect 50 | github.com/tklauser/go-sysconf v0.3.12 // indirect 51 | github.com/tklauser/numcpus v0.6.1 // indirect 52 | github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1 // indirect 53 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 54 | go.uber.org/multierr v1.11.0 // indirect 55 | golang.org/x/crypto v0.16.0 // indirect 56 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 57 | golang.org/x/mod v0.12.0 // indirect 58 | golang.org/x/net v0.19.0 // indirect 59 | golang.org/x/sync v0.3.0 // indirect 60 | golang.org/x/sys v0.15.0 // indirect 61 | golang.org/x/tools v0.13.0 // indirect 62 | gopkg.in/yaml.v3 v3.0.1 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bluenviron/gortsplib/v4 v4.6.2 h1:CGIsxpnUFvSlIxnSFS0oFSSfwsHMmBCmYcrGAtIcwXc= 2 | github.com/bluenviron/gortsplib/v4 v4.6.2/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo= 3 | github.com/bluenviron/mediacommon v1.5.1 h1:yYVF+ebqZOJh8yH+EeuPcAtTmWR66BqbJGmStxkScoI= 4 | github.com/bluenviron/mediacommon v1.5.1/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/deepch/vdk v0.0.27 h1:j/SHaTiZhA47wRpaue8NRp7P9xwOOO/lunxrDJBwcao= 9 | github.com/deepch/vdk v0.0.27/go.mod h1:JlgGyR2ld6+xOIHa7XAxJh+stSDBAkdNvIPkUIdIywk= 10 | github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= 11 | github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 14 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 15 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 16 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 17 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 18 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 19 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 20 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 21 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 22 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 23 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 24 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 25 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 26 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 27 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 28 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 29 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 30 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 31 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 32 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 33 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 36 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 37 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 38 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 39 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ= 40 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= 41 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 42 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 43 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 44 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 45 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 46 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 49 | github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= 50 | github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= 51 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 52 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= 53 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= 54 | github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= 55 | github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= 56 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 57 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 58 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 59 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 60 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 61 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 62 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 63 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 64 | github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= 65 | github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= 66 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 67 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 68 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 69 | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= 70 | github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= 71 | github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= 72 | github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= 73 | github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= 74 | github.com/pion/ice/v2 v2.3.11 h1:rZjVmUwyT55cmN8ySMpL7rsS8KYsJERsrxJLLxpKhdw= 75 | github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E= 76 | github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I= 77 | github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc= 78 | github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y= 79 | github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= 80 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= 81 | github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI= 82 | github.com/pion/mdns v0.0.9 h1:7Ue5KZsqq8EuqStnpPWV33vYYEH0+skdDN5L7EiEsI4= 83 | github.com/pion/mdns v0.0.9/go.mod h1:2JA5exfxwzXiCihmxpTKgFUpiQws2MnipoPK09vecIc= 84 | github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= 85 | github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 86 | github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= 87 | github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM= 88 | github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= 89 | github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= 90 | github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= 91 | github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8= 92 | github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= 93 | github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= 94 | github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs= 95 | github.com/pion/sctp v1.8.9 h1:TP5ZVxV5J7rz7uZmbyvnUvsn7EJ2x/5q9uhsTtXbI3g= 96 | github.com/pion/sctp v1.8.9/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI= 97 | github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= 98 | github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= 99 | github.com/pion/srtp/v2 v2.0.17 h1:ECuOk+7uIpY6HUlTb0nXhfvu4REG2hjtC4ronYFCZE4= 100 | github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc= 101 | github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= 102 | github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= 103 | github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= 104 | github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= 105 | github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= 106 | github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= 107 | github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= 108 | github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= 109 | github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= 110 | github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= 111 | github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= 112 | github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA= 113 | github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= 114 | github.com/pion/webrtc/v3 v3.2.20 h1:BQJiXQsJq9LgLp3op7rLy1y8d2WD+LtiS9cpY0uQ22A= 115 | github.com/pion/webrtc/v3 v3.2.20/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg= 116 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 117 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 118 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 119 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 120 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 121 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= 122 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 123 | github.com/q191201771/naza v0.30.48 h1:lbYUaa7A15kJKYwOiU4AbFS1Zo8oQwppl2tLEbJTqnw= 124 | github.com/q191201771/naza v0.30.48/go.mod h1:n+dpJjQSh90PxBwxBNuifOwQttywvSIN5TkWSSYCeBk= 125 | github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= 126 | github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= 127 | github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= 128 | github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= 129 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= 130 | github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= 131 | github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= 132 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 133 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 134 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 135 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 136 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 137 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 138 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 139 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 140 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 141 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 142 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 143 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 144 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 145 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 146 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 147 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 148 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 149 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 150 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 151 | github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1 h1:ilNIuDBR+UKA3qefiyWRoFufIFn3E4tgEXbBM4ILH28= 152 | github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= 153 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 154 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 155 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 156 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 157 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 158 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 159 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 160 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 161 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 162 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 163 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 164 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 165 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 166 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 167 | golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= 168 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 169 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 170 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 171 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 172 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= 173 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 174 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 175 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 176 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 177 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 178 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 179 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 180 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 181 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 182 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 183 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 184 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 185 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 186 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 187 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 188 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 189 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 190 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 191 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 192 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 193 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 194 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 195 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= 196 | golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 197 | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 198 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 199 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 200 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 201 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 203 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 204 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 205 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 206 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 207 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 208 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 209 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 210 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 211 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 213 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 217 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 218 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 221 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 222 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 224 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 225 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 226 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 227 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 228 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 229 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 230 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 231 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 232 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 233 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 234 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 235 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 236 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 237 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 238 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 239 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 240 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 241 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 242 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 243 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 244 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= 245 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 246 | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 247 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 248 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 249 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 250 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 251 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 252 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 253 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 254 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 255 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 256 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 257 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 258 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 259 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 260 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 261 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 262 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 263 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 264 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 265 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 266 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 267 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 268 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 269 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 270 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 271 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 272 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 273 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 274 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 275 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 276 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 277 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 278 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 279 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 280 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 281 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 282 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 283 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 284 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 285 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 286 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 287 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 288 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 289 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 290 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 291 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 292 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 293 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 294 | m7s.live/engine/v4 v4.15.2 h1:Uws658Ict2B8JojBG7fNmd2G2i63MlomsQ4npgNzF3g= 295 | m7s.live/engine/v4 v4.15.2/go.mod h1:uKxjmsjU1WARUNowEkP83BSrJMUjGwkJrX5nPi6DGmE= 296 | -------------------------------------------------------------------------------- /io.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | . "github.com/pion/webrtc/v3" 5 | ) 6 | 7 | type WebRTCIO struct { 8 | *PeerConnection 9 | SDP string 10 | // LocalSDP *sdp.SessionDescription 11 | } 12 | 13 | func (IO *WebRTCIO) GetAnswer() (string, error) { 14 | // Sets the LocalDescription, and starts our UDP listeners 15 | answer, err := IO.CreateAnswer(nil) 16 | if err != nil { 17 | return "", err 18 | } 19 | // IO.LocalSDP, err = answer.Unmarshal() 20 | // if err != nil { 21 | // return "", err 22 | // } 23 | gatherComplete := GatheringCompletePromise(IO.PeerConnection) 24 | if err := IO.SetLocalDescription(answer); err != nil { 25 | return "", err 26 | } 27 | <-gatherComplete 28 | return IO.LocalDescription().SDP, nil 29 | } 30 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | "go.uber.org/zap" 13 | "m7s.live/engine/v4" 14 | 15 | _ "embed" 16 | 17 | "github.com/pion/interceptor" 18 | . "github.com/pion/webrtc/v3" 19 | "m7s.live/engine/v4/config" 20 | "m7s.live/engine/v4/util" 21 | "m7s.live/plugin/webrtc/v4/webrtc" 22 | ) 23 | 24 | // }{[]string{ 25 | // "stun:stun.ekiga.net", 26 | // "stun:stun.ideasip.com", 27 | // "stun:stun.schlund.de", 28 | // "stun:stun.stunprotocol.org:3478", 29 | // "stun:stun.voiparound.com", 30 | // "stun:stun.voipbuster.com", 31 | // "stun:stun.voipstunt.com", 32 | // "stun:stun.voxgratia.org", 33 | // "stun:stun.services.mozilla.com", 34 | // "stun:stun.xten.com", 35 | // "stun:stun.softjoys.com", 36 | // "stun:stunserver.org", 37 | // "stun:stun.schlund.de", 38 | // "stun:stun.rixtelecom.se", 39 | // "stun:stun.iptel.org", 40 | // "stun:stun.ideasip.com", 41 | // "stun:stun.fwdnet.net", 42 | // "stun:stun.ekiga.net", 43 | // "stun:stun01.sipphone.com", 44 | // }} 45 | 46 | // type udpConn struct { 47 | // conn *net.UDPConn 48 | // port int 49 | // } 50 | 51 | var ( 52 | //go:embed publish.html 53 | publishHTML []byte 54 | 55 | //go:embed subscribe.html 56 | subscribeHTML []byte 57 | webrtcConfig WebRTCConfig 58 | reg_level = regexp.MustCompile("profile-level-id=(4.+f)") 59 | WebRTCPlugin = engine.InstallPlugin(&webrtcConfig) 60 | ) 61 | 62 | type WebRTCConfig struct { 63 | config.Publish 64 | config.Subscribe 65 | ICEServers []ICEServer `desc:"ice服务器配置"` 66 | PublicIP string `desc:"公网IP"` 67 | PublicIPv6 string `desc:"公网IPv6"` 68 | Port string `default:"tcp:9000" desc:"监听端口"` 69 | PLI time.Duration `default:"2s" desc:"发送PLI请求间隔"` // 视频流丢包后,发送PLI请求 70 | EnableOpus bool `default:"true" desc:"是否启用opus编码"` // 是否启用opus编码 71 | EnableAv1 bool `default:"true" desc:"是否启用av1编码"` // 是否启用av1编码 72 | m MediaEngine 73 | s SettingEngine 74 | api *API 75 | } 76 | 77 | func (conf *WebRTCConfig) OnEvent(event any) { 78 | switch event.(type) { 79 | case engine.FirstConfig: 80 | if len(conf.ICEServers) > 0 { 81 | for i := range conf.ICEServers { 82 | b, _ := conf.ICEServers[i].MarshalJSON() 83 | conf.ICEServers[i].UnmarshalJSON(b) 84 | } 85 | } 86 | webrtc.RegisterCodecs(&conf.m) 87 | if conf.EnableOpus { 88 | conf.m.RegisterCodec(RTPCodecParameters{ 89 | RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil}, 90 | PayloadType: 111, 91 | }, RTPCodecTypeAudio) 92 | } 93 | if conf.EnableAv1 { 94 | conf.m.RegisterCodec(RTPCodecParameters{ 95 | RTPCodecCapability: RTPCodecCapability{MimeTypeAV1, 90000, 0, "profile=2;level-idx=8;tier=1", nil}, 96 | PayloadType: 45, 97 | }, RTPCodecTypeVideo) 98 | } 99 | i := &interceptor.Registry{} 100 | if conf.PublicIP != "" { 101 | ips := []string{conf.PublicIP} 102 | if conf.PublicIPv6 != "" { 103 | ips = append(ips, conf.PublicIPv6) 104 | } 105 | conf.s.SetNAT1To1IPs(ips, ICECandidateTypeHost) 106 | } 107 | protocol, ports := util.Conf2Listener(conf.Port) 108 | if len(ports) == 0 { 109 | WebRTCPlugin.Fatal("webrtc port config error") 110 | } 111 | if protocol == "tcp" { 112 | tcpport := int(ports[0]) 113 | tcpl, err := net.ListenTCP("tcp", &net.TCPAddr{ 114 | IP: net.IP{0, 0, 0, 0}, 115 | Port: tcpport, 116 | }) 117 | if err != nil { 118 | WebRTCPlugin.Fatal("webrtc listener tcp", zap.Error(err)) 119 | } 120 | WebRTCPlugin.Info("webrtc start listen", zap.Int("port", tcpport)) 121 | conf.s.SetICETCPMux(NewICETCPMux(nil, tcpl, 4096)) 122 | conf.s.SetNetworkTypes([]NetworkType{NetworkTypeTCP4, NetworkTypeTCP6}) 123 | } else if len(ports) == 2 { 124 | conf.s.SetEphemeralUDPPortRange(ports[0], ports[1]) 125 | } else { 126 | // 创建共享WEBRTC端口 默认9000 127 | udpListener, err := net.ListenUDP("udp", &net.UDPAddr{ 128 | IP: net.IP{0, 0, 0, 0}, 129 | Port: int(ports[0]), 130 | }) 131 | if err != nil { 132 | WebRTCPlugin.Fatal("webrtc listener udp", zap.Error(err)) 133 | } 134 | WebRTCPlugin.Info("webrtc start listen", zap.Uint16("port", ports[0])) 135 | conf.s.SetICEUDPMux(NewICEUDPMux(nil, udpListener)) 136 | conf.s.SetNetworkTypes([]NetworkType{NetworkTypeUDP4, NetworkTypeUDP6}) 137 | } 138 | 139 | if err := RegisterDefaultInterceptors(&conf.m, i); err != nil { 140 | panic(err) 141 | } 142 | conf.api = NewAPI(WithMediaEngine(&conf.m), 143 | WithInterceptorRegistry(i), WithSettingEngine(conf.s)) 144 | } 145 | } 146 | 147 | func (conf *WebRTCConfig) Play_(w http.ResponseWriter, r *http.Request) { 148 | w.Header().Set("Content-Type", "application/sdp") 149 | streamPath := r.URL.Path[len("/play/"):] 150 | rawQuery := r.URL.RawQuery 151 | bytes, err := io.ReadAll(r.Body) 152 | var suber WebRTCSubscriber 153 | suber.SDP = string(bytes) 154 | suber.RemoteAddr = r.RemoteAddr 155 | if suber.PeerConnection, err = conf.api.NewPeerConnection(Configuration{ 156 | ICEServers: conf.ICEServers, 157 | }); err != nil { 158 | http.Error(w, err.Error(), http.StatusInternalServerError) 159 | return 160 | } 161 | suber.OnICECandidate(func(ice *ICECandidate) { 162 | if ice != nil { 163 | suber.Info(ice.ToJSON().Candidate) 164 | } 165 | }) 166 | if err = suber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: suber.SDP}); err != nil { 167 | http.Error(w, err.Error(), http.StatusInternalServerError) 168 | return 169 | } 170 | if rawQuery != "" { 171 | streamPath += "?" + rawQuery 172 | } 173 | if err = WebRTCPlugin.Subscribe(streamPath, &suber); err != nil { 174 | http.Error(w, err.Error(), http.StatusBadRequest) 175 | return 176 | } 177 | if sdp, err := suber.GetAnswer(); err == nil { 178 | w.Write([]byte(sdp)) 179 | } else { 180 | http.Error(w, err.Error(), http.StatusBadRequest) 181 | } 182 | } 183 | 184 | // https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip 185 | func (conf *WebRTCConfig) Push_(w http.ResponseWriter, r *http.Request) { 186 | streamPath := r.URL.Path[len("/push/"):] 187 | rawQuery := r.URL.RawQuery 188 | auth := r.Header.Get("Authorization") 189 | if strings.HasPrefix(auth, "Bearer ") { 190 | auth = auth[len("Bearer "):] 191 | if rawQuery != "" { 192 | rawQuery += "&bearer=" + auth 193 | } else { 194 | rawQuery = "bearer=" + auth 195 | } 196 | WebRTCPlugin.Info("push", zap.String("stream", streamPath), zap.String("bearer", auth)) 197 | } 198 | w.Header().Set("Content-Type", "application/sdp") 199 | w.Header().Set("Location", "/webrtc/api/stop/push/"+streamPath) 200 | if rawQuery != "" { 201 | streamPath += "?" + rawQuery 202 | } 203 | bytes, err := io.ReadAll(r.Body) 204 | var puber WebRTCPublisher 205 | puber.SDP = string(bytes) 206 | if puber.PeerConnection, err = conf.api.NewPeerConnection(Configuration{ 207 | ICEServers: conf.ICEServers, 208 | }); err != nil { 209 | http.Error(w, err.Error(), http.StatusInternalServerError) 210 | return 211 | } 212 | puber.SetIO(puber.PeerConnection) //TODO: 单PC需要注释掉 213 | puber.OnICECandidate(func(ice *ICECandidate) { 214 | if ice != nil { 215 | puber.Info(ice.ToJSON().Candidate) 216 | } 217 | }) 218 | puber.OnDataChannel(func(d *DataChannel) { 219 | puber.Info("OnDataChannel", zap.String("label", d.Label())) 220 | d.OnMessage(func(msg DataChannelMessage) { 221 | puber.SDP = string(msg.Data[1:]) 222 | puber.Debug("dc message", zap.String("sdp", puber.SDP)) 223 | if err := puber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: puber.SDP}); err != nil { 224 | return 225 | } 226 | if answer, err := puber.GetAnswer(); err == nil { 227 | d.SendText(answer) 228 | } else { 229 | return 230 | } 231 | switch msg.Data[0] { 232 | case '0': 233 | puber.Stop() 234 | case '1': 235 | 236 | } 237 | }) 238 | }) 239 | // if _, err = puber.AddTransceiverFromKind(RTPCodecTypeVideo); err != nil { 240 | // http.Error(w, err.Error(), http.StatusInternalServerError) 241 | // return 242 | // } 243 | // if _, err = puber.AddTransceiverFromKind(RTPCodecTypeAudio); err != nil { 244 | // http.Error(w, err.Error(), http.StatusInternalServerError) 245 | // return 246 | // } 247 | if err = WebRTCPlugin.Publish(streamPath, &puber); err != nil { 248 | http.Error(w, err.Error(), http.StatusBadRequest) 249 | return 250 | } 251 | puber.OnConnectionStateChange(func(state PeerConnectionState) { 252 | puber.Info("Connection State has changed:" + state.String()) 253 | switch state { 254 | case PeerConnectionStateConnected: 255 | 256 | case PeerConnectionStateDisconnected, PeerConnectionStateFailed, PeerConnectionStateClosed: 257 | puber.Stop() 258 | } 259 | }) 260 | if err := puber.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: puber.SDP}); err != nil { 261 | http.Error(w, err.Error(), http.StatusBadRequest) 262 | return 263 | } 264 | if answer, err := puber.GetAnswer(); err == nil { 265 | w.WriteHeader(http.StatusCreated) 266 | fmt.Fprint(w, answer) 267 | } else { 268 | http.Error(w, err.Error(), http.StatusBadRequest) 269 | return 270 | } 271 | } 272 | 273 | func (conf *WebRTCConfig) Test_Publish(w http.ResponseWriter, r *http.Request) { 274 | w.Write(publishHTML) 275 | } 276 | func (conf *WebRTCConfig) Test_ScreenShare(w http.ResponseWriter, r *http.Request) { 277 | w.Write(publishHTML) 278 | } 279 | func (conf *WebRTCConfig) Test_Subscribe(w http.ResponseWriter, r *http.Request) { 280 | w.Write(subscribeHTML) 281 | } 282 | 283 | func (conf *WebRTCConfig) Batch(w http.ResponseWriter, r *http.Request) { 284 | bytes, err := io.ReadAll(r.Body) 285 | var suber WebRTCBatcher 286 | suber.RemoteAddr = r.RemoteAddr 287 | suber.SDP = string(bytes) 288 | if suber.PeerConnection, err = conf.api.NewPeerConnection(Configuration{ 289 | ICEServers: conf.ICEServers, 290 | }); err != nil { 291 | http.Error(w, err.Error(), http.StatusInternalServerError) 292 | return 293 | } 294 | if err = suber.Start(); err != nil { 295 | http.Error(w, err.Error(), http.StatusInternalServerError) 296 | return 297 | } 298 | if sdp, err := suber.GetAnswer(); err == nil { 299 | w.Header().Set("Content-Type", "application/sdp") 300 | fmt.Fprintf(w, "%s", sdp) 301 | } else { 302 | http.Error(w, err.Error(), http.StatusBadRequest) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /publish.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 测试WebRTC推流 9 | 10 | 11 | 12 | 14 | 15 |
16 |
 17 |   
 18 | 
 19 |   
 20 | 
21 | 22 | 111 | 112 | -------------------------------------------------------------------------------- /publisher.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | 7 | "github.com/pion/rtcp" 8 | . "github.com/pion/webrtc/v3" 9 | "go.uber.org/zap" 10 | . "m7s.live/engine/v4" 11 | "m7s.live/engine/v4/codec" 12 | ) 13 | 14 | type WebRTCPublisher struct { 15 | Publisher 16 | WebRTCIO 17 | audioTrack atomic.Pointer[TrackRemote] 18 | videoTrack atomic.Pointer[TrackRemote] 19 | } 20 | 21 | func (puber *WebRTCPublisher) OnEvent(event any) { 22 | switch event.(type) { 23 | case IPublisher: 24 | puber.OnTrack(puber.onTrack) 25 | } 26 | puber.Publisher.OnEvent(event) 27 | } 28 | 29 | func (puber *WebRTCPublisher) onTrack(track *TrackRemote, receiver *RTPReceiver) { 30 | puber.Info("onTrack", zap.String("kind", track.Kind().String()), zap.Uint8("payloadType", uint8(track.Codec().PayloadType))) 31 | if codecP := track.Codec(); track.Kind() == RTPCodecTypeAudio { 32 | puber.audioTrack.Store(track) 33 | if puber.AudioTrack == nil { 34 | switch codecP.PayloadType { 35 | case 111: 36 | puber.CreateAudioTrack(codec.CodecID_OPUS) 37 | case 8: 38 | puber.CreateAudioTrack(codec.CodecID_PCMA) 39 | case 0: 40 | puber.CreateAudioTrack(codec.CodecID_PCMU) 41 | default: 42 | puber.AudioTrack = nil 43 | puber.Config.PubAudio = false 44 | return 45 | } 46 | } 47 | for { 48 | if puber.audioTrack.Load() != track { 49 | return 50 | } 51 | rtpItem := puber.AudioTrack.GetRTPFromPool() 52 | if i, _, err := track.Read(rtpItem.Value.Raw); err == nil { 53 | rtpItem.Value.Unmarshal(rtpItem.Value.Raw[:i]) 54 | puber.AudioTrack.WriteRTP(rtpItem) 55 | } else { 56 | puber.Info("track stop", zap.String("kind", track.Kind().String()), zap.Error(err)) 57 | rtpItem.Recycle() 58 | return 59 | } 60 | } 61 | } else { 62 | puber.videoTrack.Store(track) 63 | if puber.VideoTrack == nil { 64 | switch codecP.PayloadType { 65 | case 45: 66 | puber.CreateVideoTrack(codec.CodecID_AV1, byte(codecP.PayloadType)) 67 | default: 68 | puber.CreateVideoTrack(codec.CodecID_H264, byte(codecP.PayloadType)) 69 | } 70 | } 71 | go puber.writeRTCP(track) 72 | for { 73 | if puber.videoTrack.Load() != track { 74 | return 75 | } 76 | rtpItem := puber.VideoTrack.GetRTPFromPool() 77 | if i, _, err := track.Read(rtpItem.Value.Raw); err == nil { 78 | rtpItem.Value.Unmarshal(rtpItem.Value.Raw[:i]) 79 | if rtpItem.Value.Extension { 80 | for _, id := range rtpItem.Value.GetExtensionIDs() { 81 | puber.Debug("extension", zap.Uint8("id", id), zap.Binary("value", rtpItem.Value.GetExtension(id))) 82 | } 83 | } 84 | puber.VideoTrack.WriteRTP(rtpItem) 85 | } else { 86 | puber.Info("track stop", zap.String("kind", track.Kind().String()), zap.Error(err)) 87 | rtpItem.Recycle() 88 | return 89 | } 90 | } 91 | } 92 | } 93 | 94 | func (puber *WebRTCPublisher) writeRTCP(track *TrackRemote) { 95 | ticker := time.NewTicker(webrtcConfig.PLI) 96 | defer ticker.Stop() 97 | for { 98 | select { 99 | case <-ticker.C: 100 | if puber.videoTrack.Load() != track { 101 | return 102 | } 103 | if rtcpErr := puber.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil { 104 | puber.Error("writeRTCP", zap.Error(rtcpErr)) 105 | return 106 | } 107 | case <-puber.Done(): 108 | return 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /subscribe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 测试WebRTC拉流 9 | 10 | 11 | 12 | 14 | 15 |
16 |   
17 | 
18 |   
19 | 
20 | 21 | 90 | 91 | -------------------------------------------------------------------------------- /subscriber.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/pion/rtcp" 8 | . "github.com/pion/webrtc/v3" 9 | "go.uber.org/zap" 10 | . "m7s.live/engine/v4" 11 | "m7s.live/engine/v4/codec" 12 | "m7s.live/engine/v4/track" 13 | "m7s.live/engine/v4/util" 14 | ) 15 | 16 | type trackSender struct { 17 | *TrackLocalStaticRTP 18 | *RTPSender 19 | // seq uint32 20 | } 21 | 22 | type WebRTCSubscriber struct { 23 | Subscriber 24 | WebRTCIO 25 | audio trackSender 26 | video trackSender 27 | DC *DataChannel 28 | videoTracks []*track.Video 29 | audioTracks []*track.Audio 30 | // flvHeadCache []byte 31 | } 32 | 33 | func (suber *WebRTCSubscriber) queueDCData(data ...[]byte) (err error) { 34 | for _, d := range data { 35 | if err = suber.DC.Send(d); err != nil { 36 | return 37 | } 38 | } 39 | return 40 | } 41 | 42 | func (suber *WebRTCSubscriber) createDataChannel() { 43 | if suber.DC != nil { 44 | return 45 | } 46 | suber.DC, _ = suber.PeerConnection.CreateDataChannel(suber.Subscriber.Stream.Path, nil) 47 | // suber.flvHeadCache = make([]byte, 15) 48 | } 49 | 50 | // func (suber *WebRTCSubscriber) sendAvByDatachannel(t byte, reader *track.AVRingReader) { 51 | // suber.flvHeadCache[0] = t 52 | // frame := reader.Frame 53 | // dataSize := uint32(frame.AVCC.ByteLength) 54 | // result := net.Buffers{suber.flvHeadCache[:11]} 55 | // result = append(result, frame.AVCC.ToBuffers()...) 56 | // ts := reader.AbsTime 57 | // util.PutBE(suber.flvHeadCache[1:4], dataSize) 58 | // util.PutBE(suber.flvHeadCache[4:7], ts) 59 | // suber.flvHeadCache[7] = byte(ts >> 24) 60 | // result = append(result, util.PutBE(suber.flvHeadCache[11:15], dataSize+11)) 61 | // for _, data := range util.SplitBuffers(result, 65535) { 62 | // for _, d := range data { 63 | // suber.queueDCData(d) 64 | // } 65 | // } 66 | // } 67 | func (suber *WebRTCSubscriber) OnSubscribe() { 68 | vm := make(map[codec.VideoCodecID]*track.Video) 69 | am := make(map[codec.AudioCodecID]*track.Audio) 70 | for _, track := range suber.videoTracks { 71 | vm[track.CodecID] = track 72 | } 73 | for _, track := range suber.audioTracks { 74 | am[track.CodecID] = track 75 | } 76 | if (vm[codec.CodecID_H264] != nil || vm[codec.CodecID_AV1] != nil || vm[codec.CodecID_H265] == nil) && (am[codec.CodecID_PCMA] != nil || am[codec.CodecID_PCMU] != nil || am[codec.CodecID_AAC] == nil) { 77 | video := vm[codec.CodecID_H264] 78 | if video != nil { 79 | suber.Subscriber.AddTrack(video) 80 | pli := fmt.Sprintf("%x", video.SPS[1:4]) 81 | // pli := "42001f" 82 | if !strings.Contains(suber.SDP, pli) { 83 | list := reg_level.FindAllStringSubmatch(suber.SDP, -1) 84 | if len(list) > 0 { 85 | pli = list[0][1] 86 | } 87 | } 88 | suber.video.TrackLocalStaticRTP, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, video.Name, suber.Subscriber.Stream.Path) 89 | } else if video = vm[codec.CodecID_AV1]; video != nil { 90 | suber.Subscriber.AddTrack(video) 91 | suber.video.TrackLocalStaticRTP, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeAV1, SDPFmtpLine: fmt.Sprintf("profile=%d;level-idx=%d;tier=%d", video.ParamaterSets[1][1], video.ParamaterSets[1][0], video.ParamaterSets[1][2])}, video.Name, suber.Subscriber.Stream.Path) 92 | } 93 | if suber.video.TrackLocalStaticRTP != nil { 94 | suber.video.RTPSender, _ = suber.PeerConnection.AddTrack(suber.video.TrackLocalStaticRTP) 95 | go func() { 96 | rtcpBuf := make([]byte, 1500) 97 | for { 98 | if n, _, rtcpErr := suber.video.Read(rtcpBuf); rtcpErr != nil { 99 | suber.Warn("rtcp read error", zap.Error(rtcpErr)) 100 | return 101 | } else { 102 | if p, err := rtcp.Unmarshal(rtcpBuf[:n]); err == nil { 103 | for _, pp := range p { 104 | switch pp.(type) { 105 | case *rtcp.PictureLossIndication: 106 | // fmt.Println("PictureLossIndication") 107 | } 108 | } 109 | } 110 | } 111 | } 112 | }() 113 | } 114 | var audio *track.Audio 115 | audioMimeType := MimeTypePCMA 116 | if am[codec.CodecID_PCMA] != nil { 117 | audio = am[codec.CodecID_PCMA] 118 | } else if am[codec.CodecID_PCMU] != nil { 119 | audioMimeType = MimeTypePCMU 120 | audio = am[codec.CodecID_PCMU] 121 | } else if am[codec.CodecID_OPUS] != nil { 122 | audioMimeType = MimeTypeOpus 123 | audio = am[codec.CodecID_OPUS] 124 | } 125 | if audio != nil { 126 | suber.Subscriber.AddTrack(audio) 127 | suber.audio.TrackLocalStaticRTP, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: audioMimeType}, audio.Name, suber.Subscriber.Stream.Path) 128 | if suber.audio.TrackLocalStaticRTP != nil { 129 | suber.audio.RTPSender, _ = suber.PeerConnection.AddTrack(suber.audio.TrackLocalStaticRTP) 130 | } 131 | } 132 | } else { 133 | suber.createDataChannel() 134 | if len(suber.videoTracks) > 0 { 135 | suber.Subscriber.AddTrack(suber.videoTracks[0]) 136 | } 137 | if len(suber.audioTracks) > 0 { 138 | suber.Subscriber.AddTrack(suber.audioTracks[0]) 139 | } 140 | 141 | } 142 | } 143 | 144 | func (suber *WebRTCSubscriber) OnEvent(event any) { 145 | var err error 146 | switch v := event.(type) { 147 | case *track.Video: 148 | suber.videoTracks = append(suber.videoTracks, v) 149 | // switch v.CodecID { 150 | // case codec.CodecID_H264: 151 | // pli := fmt.Sprintf("%x", v.SPS[1:4]) 152 | // // pli := "42001f" 153 | // if !strings.Contains(suber.SDP, pli) { 154 | // list := reg_level.FindAllStringSubmatch(suber.SDP, -1) 155 | // if len(list) > 0 { 156 | // pli = list[0][1] 157 | // } 158 | // } 159 | // suber.video.TrackLocalStaticRTP, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, v.Name, suber.Subscriber.Stream.Path) 160 | // case codec.CodecID_H265: 161 | // suber.createDataChannel() 162 | // // suber.videoTrack, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeH265, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, "video", suber.Subscriber.Stream.Path) 163 | // default: 164 | // return 165 | // } 166 | // suber.Subscriber.AddTrack(v) //接受这个track 167 | case *track.Audio: 168 | // audioMimeType := MimeTypePCMA 169 | // if v.CodecID == codec.CodecID_PCMU { 170 | // audioMimeType = MimeTypePCMU 171 | // } 172 | // switch v.CodecID { 173 | // case codec.CodecID_AAC: 174 | // suber.createDataChannel() 175 | // case codec.CodecID_PCMA, codec.CodecID_PCMU: 176 | // suber.audio.TrackLocalStaticRTP, _ = NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: audioMimeType}, v.Name, suber.Subscriber.Stream.Path) 177 | // //suber.audio.RTPSender, _ = suber.PeerConnection.AddTrack(suber.audio.TrackLocalStaticRTP) 178 | // } 179 | // suber.Subscriber.AddTrack(v) //接受这个track 180 | suber.audioTracks = append(suber.audioTracks, v) 181 | // case VideoDeConf: 182 | // if suber.DC != nil { 183 | // suber.queueDCData(codec.VideoAVCC2FLV(0, v)...) 184 | // } 185 | // case AudioDeConf: 186 | // if suber.DC != nil { 187 | // suber.queueDCData(codec.AudioAVCC2FLV(0, v)...) 188 | // } 189 | case VideoRTP: 190 | // if suber.video.TrackLocalStaticRTP != nil { 191 | if err = suber.video.WriteRTP(v.Packet); err != nil { 192 | suber.Stop(zap.Error(err)) 193 | return 194 | } 195 | // } else if suber.DC != nil && suber.VideoReader.Frame.Sequence != suber.video.seq { 196 | // suber.video.seq = suber.VideoReader.Frame.Sequence 197 | // suber.sendAvByDatachannel(9, suber.VideoReader) 198 | // } 199 | case AudioRTP: 200 | // if suber.audio.TrackLocalStaticRTP != nil { 201 | if err = suber.audio.WriteRTP(v.Packet); err != nil { 202 | suber.Stop(zap.Error(err)) 203 | return 204 | } 205 | // } else if suber.DC != nil && suber.AudioReader.Frame.Sequence != suber.audio.seq { 206 | // suber.audio.seq = suber.AudioReader.Frame.Sequence 207 | // suber.sendAvByDatachannel(8, suber.AudioReader) 208 | // } 209 | case FLVFrame: 210 | for _, data := range util.SplitBuffers(v, 65535) { 211 | if err = suber.queueDCData(data...); err != nil { 212 | suber.Stop(zap.Error(err)) 213 | return 214 | } 215 | } 216 | case ISubscriber: 217 | suber.OnSubscribe() 218 | if suber.DC != nil { 219 | suber.DC.OnOpen(func() { 220 | suber.DC.Send(codec.FLVHeader) 221 | go func() { 222 | suber.PlayFLV() 223 | suber.DC.Close() 224 | suber.PeerConnection.Close() 225 | }() 226 | }) 227 | } 228 | suber.OnConnectionStateChange(func(pcs PeerConnectionState) { 229 | suber.Info("Connection State has changed:" + pcs.String()) 230 | switch pcs { 231 | case PeerConnectionStateConnected: 232 | if suber.DC == nil { 233 | go func() { 234 | suber.PlayRTP() 235 | suber.PeerConnection.Close() 236 | }() 237 | } 238 | case PeerConnectionStateDisconnected, PeerConnectionStateFailed, PeerConnectionStateClosed: 239 | suber.Stop(zap.String("reason", pcs.String())) 240 | } 241 | }) 242 | default: 243 | suber.Subscriber.OnEvent(event) 244 | } 245 | } 246 | 247 | type WebRTCBatchSubscriber struct { 248 | WebRTCSubscriber 249 | OnPlayDone func() 250 | } 251 | 252 | func (suber *WebRTCBatchSubscriber) OnEvent(event any) { 253 | switch event.(type) { 254 | case ISubscriber: 255 | suber.OnSubscribe() 256 | default: 257 | suber.WebRTCSubscriber.OnEvent(event) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /webrtc/config.go: -------------------------------------------------------------------------------- 1 | package webrtc 2 | 3 | import ( 4 | . "github.com/pion/webrtc/v3" 5 | ) 6 | 7 | func RegisterCodecs(m *MediaEngine) error { 8 | for _, codec := range []RTPCodecParameters{ 9 | { 10 | RTPCodecCapability: RTPCodecCapability{MimeTypePCMU, 8000, 0, "", nil}, 11 | PayloadType: 0, 12 | }, 13 | { 14 | RTPCodecCapability: RTPCodecCapability{MimeTypePCMA, 8000, 0, "", nil}, 15 | PayloadType: 8, 16 | }, 17 | } { 18 | if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil { 19 | return err 20 | } 21 | } 22 | videoRTCPFeedback := []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}} 23 | for _, codec := range []RTPCodecParameters{ 24 | // { 25 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, 26 | // PayloadType: 97, 27 | // }, 28 | 29 | // { 30 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil}, 31 | // PayloadType: 99, 32 | // }, 33 | 34 | // { 35 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil}, 36 | // PayloadType: 101, 37 | // }, 38 | { 39 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback}, 40 | PayloadType: 102, 41 | }, 42 | // { 43 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil}, 44 | // PayloadType: 121, 45 | // }, 46 | 47 | { 48 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback}, 49 | PayloadType: 127, 50 | }, 51 | // { 52 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, 53 | // PayloadType: 120, 54 | // }, 55 | 56 | { 57 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback}, 58 | PayloadType: 125, 59 | }, 60 | // { 61 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=125", nil}, 62 | // PayloadType: 107, 63 | // }, 64 | 65 | { 66 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback}, 67 | PayloadType: 108, 68 | }, 69 | // { 70 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil}, 71 | // PayloadType: 109, 72 | // }, 73 | 74 | { 75 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback}, 76 | PayloadType: 127, 77 | }, 78 | // { 79 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, 80 | // PayloadType: 120, 81 | // }, 82 | 83 | { 84 | RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", videoRTCPFeedback}, 85 | PayloadType: 123, 86 | }, 87 | // { 88 | // RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=123", nil}, 89 | // PayloadType: 118, 90 | // }, 91 | } { 92 | if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil { 93 | return err 94 | } 95 | } 96 | return nil 97 | } 98 | --------------------------------------------------------------------------------