├── .gitignore ├── README.md ├── config.go ├── config.json ├── fakeAudio.go ├── go.mod ├── go.sum ├── main.go ├── renovate.json └── stream.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTSPtoRTMP 2 | 3 | RTSP Stream to YouTube or Twitch 4 | 5 | full native! not use ffmpeg or gstreamer 6 | 7 | 8 | ### Download Source 9 | 10 | 1. Download source 11 | ```bash 12 | $ git clone https://github.com/deepch/RTSPtoRTMP 13 | ``` 14 | 3. CD to Directory 15 | ```bash 16 | $ cd RTSPtoRTMP/ 17 | ``` 18 | 4. Test Run 19 | ```bash 20 | $ GO111MODULE=on go run *.go 21 | ``` 22 | 5. Edit config.json 23 | 24 | 25 | ## Configuration 26 | 27 | ### Edit file config.json 28 | 29 | format: 30 | 31 | ```bash 32 | { 33 | "streams": { 34 | "H264_AAC": { 35 | "on_demand": false, 36 | "url": "YOU_RTSP_CAMERA_URL", 37 | "broadcast": { 38 | "enable": true, 39 | "url": "rtmp://a.rtmp.youtube.com/live2/YOU_YOUTUBE_KEY" 40 | } 41 | } 42 | } 43 | } 44 | 45 | ``` 46 | 47 | ## Limitations 48 | 49 | Video Codecs Supported: H264 all profiles 50 | 51 | Audio Codecs Supported: AAC or NONE 52 | 53 | ## Test 54 | 55 | CPU usage 0.2% one core cpu intel core i7 / stream 56 | 57 | ## Team 58 | 59 | Deepch - https://github.com/deepch streaming developer 60 | 61 | Dmitry - https://github.com/vdalex25 web developer 62 | 63 | ## Other Example 64 | 65 | Examples of working with video on golang 66 | 67 | - [RTSPtoWeb](https://github.com/deepch/RTSPtoWeb) 68 | - [RTSPtoWebRTC](https://github.com/deepch/RTSPtoWebRTC) 69 | - [RTSPtoWSMP4f](https://github.com/deepch/RTSPtoWSMP4f) 70 | - [RTSPtoImage](https://github.com/deepch/RTSPtoImage) 71 | - [RTSPtoHLS](https://github.com/deepch/RTSPtoHLS) 72 | - [RTSPtoHLSLL](https://github.com/deepch/RTSPtoHLSLL) 73 | - [RTSPtoRTMP](https://github.com/deepch/RTSPtoRTMP) 74 | - 75 | [![paypal.me/AndreySemochkin](https://ionicabizau.github.io/badges/paypal.svg)](https://www.paypal.me/AndreySemochkin) - You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "sync" 11 | "time" 12 | 13 | "github.com/deepch/vdk/av" 14 | ) 15 | 16 | var ( 17 | Success = "success" 18 | ErrorStreamNotFound = errors.New("stream not found") 19 | ErrorStreamAlreadyExists = errors.New("stream already exists") 20 | ErrorStreamChannelAlreadyExists = errors.New("stream channel already exists") 21 | ErrorStreamNotHLSSegments = errors.New("stream hls not ts seq found") 22 | ErrorStreamNoVideo = errors.New("stream no video") 23 | ErrorStreamNoClients = errors.New("stream no clients") 24 | ErrorStreamRestart = errors.New("stream restart") 25 | ErrorStreamStopCoreSignal = errors.New("stream stop core signal") 26 | ErrorStreamStopRTSPSignal = errors.New("stream stop rtsp signal") 27 | ErrorStreamChannelNotFound = errors.New("stream channel not found") 28 | ErrorStreamChannelCodecNotFound = errors.New("stream channel codec not ready, possible stream offline") 29 | ErrorStreamsLen0 = errors.New("streams len zero") 30 | ) 31 | 32 | //Config global 33 | var Config = loadConfig() 34 | 35 | //ConfigST struct 36 | type ConfigST struct { 37 | mutex sync.RWMutex 38 | Server ServerST `json:"server"` 39 | Streams map[string]StreamST `json:"streams"` 40 | } 41 | 42 | //ServerST struct 43 | type ServerST struct { 44 | HTTPPort string `json:"http_port"` 45 | } 46 | 47 | //StreamST struct 48 | type StreamST struct { 49 | URL string `json:"url"` 50 | Broadcast BroadcastST `json:"broadcast"` 51 | Status bool `json:"status"` 52 | OnDemand bool `json:"on_demand"` 53 | RunLock bool `json:"-"` 54 | Codecs []av.CodecData 55 | Cl map[string]viewer 56 | } 57 | type BroadcastST struct { 58 | Enable bool `json:"enable"` 59 | URL string `json:"url"` 60 | } 61 | 62 | //Segment HLS cache section 63 | type Segment struct { 64 | dur time.Duration 65 | data []*av.Packet 66 | } 67 | 68 | type viewer struct { 69 | c chan av.Packet 70 | } 71 | 72 | func (element *ConfigST) RunIFNotRun(uuid string) { 73 | element.mutex.Lock() 74 | defer element.mutex.Unlock() 75 | if tmp, ok := element.Streams[uuid]; ok { 76 | if tmp.OnDemand && !tmp.RunLock { 77 | tmp.RunLock = true 78 | element.Streams[uuid] = tmp 79 | go RTSPWorkerLoop(uuid, tmp.URL, tmp.OnDemand) 80 | } 81 | } 82 | } 83 | 84 | func (element *ConfigST) RunUnlock(uuid string) { 85 | element.mutex.Lock() 86 | defer element.mutex.Unlock() 87 | if tmp, ok := element.Streams[uuid]; ok { 88 | if tmp.OnDemand && tmp.RunLock { 89 | tmp.RunLock = false 90 | element.Streams[uuid] = tmp 91 | } 92 | } 93 | } 94 | 95 | func (element *ConfigST) HasViewer(uuid string) bool { 96 | element.mutex.Lock() 97 | defer element.mutex.Unlock() 98 | if tmp, ok := element.Streams[uuid]; ok && len(tmp.Cl) > 0 { 99 | return true 100 | } 101 | return false 102 | } 103 | 104 | func loadConfig() *ConfigST { 105 | var tmp ConfigST 106 | data, err := ioutil.ReadFile("config.json") 107 | if err != nil { 108 | log.Fatalln(err) 109 | } 110 | err = json.Unmarshal(data, &tmp) 111 | if err != nil { 112 | log.Fatalln(err) 113 | } 114 | for i, v := range tmp.Streams { 115 | v.Cl = make(map[string]viewer) 116 | tmp.Streams[i] = v 117 | } 118 | return &tmp 119 | } 120 | 121 | func (element *ConfigST) cast(uuid string, pck av.Packet) { 122 | element.mutex.Lock() 123 | defer element.mutex.Unlock() 124 | for _, v := range element.Streams[uuid].Cl { 125 | if len(v.c) < cap(v.c) { 126 | v.c <- pck 127 | } 128 | } 129 | } 130 | 131 | func (element *ConfigST) ext(suuid string) bool { 132 | element.mutex.Lock() 133 | defer element.mutex.Unlock() 134 | _, ok := element.Streams[suuid] 135 | return ok 136 | } 137 | 138 | func (element *ConfigST) coAd(suuid string, codecs []av.CodecData) { 139 | element.mutex.Lock() 140 | defer element.mutex.Unlock() 141 | t := element.Streams[suuid] 142 | t.Codecs = codecs 143 | element.Streams[suuid] = t 144 | } 145 | 146 | func (element *ConfigST) coGe(suuid string) []av.CodecData { 147 | for i := 0; i < 100; i++ { 148 | element.mutex.RLock() 149 | tmp, ok := element.Streams[suuid] 150 | element.mutex.RUnlock() 151 | if !ok { 152 | return nil 153 | } 154 | if tmp.Codecs != nil { 155 | return tmp.Codecs 156 | } 157 | time.Sleep(50 * time.Millisecond) 158 | } 159 | return nil 160 | } 161 | 162 | func (element *ConfigST) clAd(suuid string) (string, chan av.Packet) { 163 | element.mutex.Lock() 164 | defer element.mutex.Unlock() 165 | cuuid := pseudoUUID() 166 | ch := make(chan av.Packet, 100) 167 | element.Streams[suuid].Cl[cuuid] = viewer{c: ch} 168 | return cuuid, ch 169 | } 170 | 171 | func (element *ConfigST) list() (string, []string) { 172 | element.mutex.Lock() 173 | defer element.mutex.Unlock() 174 | var res []string 175 | var fist string 176 | for k := range element.Streams { 177 | if fist == "" { 178 | fist = k 179 | } 180 | res = append(res, k) 181 | } 182 | return fist, res 183 | } 184 | 185 | func (element *ConfigST) clDe(suuid, cuuid string) { 186 | element.mutex.Lock() 187 | defer element.mutex.Unlock() 188 | delete(element.Streams[suuid].Cl, cuuid) 189 | } 190 | 191 | func (element *ConfigST) bcConfig(suuid string) (bool, string) { 192 | element.mutex.RLock() 193 | defer element.mutex.RUnlock() 194 | return element.Streams[suuid].Broadcast.Enable, element.Streams[suuid].Broadcast.URL 195 | } 196 | 197 | func pseudoUUID() (uuid string) { 198 | b := make([]byte, 16) 199 | _, err := rand.Read(b) 200 | if err != nil { 201 | fmt.Println("Error: ", err) 202 | return 203 | } 204 | uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 205 | return 206 | } 207 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "streams": { 3 | "H264_AAC": { 4 | "on_demand": false, 5 | "url": "YOU_RTSP_CAMERA_URL", 6 | "broadcast": { 7 | "enable": true, 8 | "url": "rtmp://a.rtmp.youtube.com/live2/YOU_YOUTUBE_KEY" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fakeAudio.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/deepch/vdk/codec/aacparser" 7 | 8 | "github.com/deepch/vdk/av" 9 | ) 10 | 11 | type FakeAAC struct { 12 | closer chan bool 13 | OutgoingPacketQueue string 14 | } 15 | 16 | func NewFakeAudio() *FakeAAC { 17 | return &FakeAAC{closer: make(chan bool)} 18 | } 19 | 20 | func (obj *FakeAAC) Start(CodecData []av.CodecData, OutgoingPacketQueue chan *av.Packet) ([]av.CodecData, error) { 21 | go func() { 22 | ticker := time.NewTicker(128 * time.Millisecond) 23 | for { 24 | select { 25 | case <-obj.closer: 26 | return 27 | case <-ticker.C: 28 | OutgoingPacketQueue <- &av.Packet{Duration: 128 * time.Millisecond, Idx: 1, CompositionTime: 1 * time.Millisecond, Data: []byte{1, 2, 52, 24, 67, 243, 0, 69, 107, 122, 169, 68, 50, 47, 46, 147, 26, 82, 161, 42, 80, 119, 97, 77, 199, 109, 20, 8, 26, 99, 53, 19, 85, 4, 186, 150, 69, 121, 208, 72, 42, 74, 224, 144, 172, 132, 146, 70, 246, 74, 13, 98, 76, 226, 5, 214, 169, 73, 18, 154, 33, 43, 11, 196, 2, 132, 43, 2, 240, 10, 14, 162, 64, 149, 243, 217, 96, 225, 85, 233, 90, 11, 19, 41, 67, 32, 5, 45, 5, 74, 72, 138, 215, 107, 33, 96, 49, 66, 85, 152, 150, 222, 87, 151, 89, 46, 80, 1, 172, 66, 150, 150, 150, 150, 150, 150, 150, 150, 150, 151, 7}} 29 | 30 | } 31 | } 32 | }() 33 | audioCodec, err := aacparser.NewCodecDataFromMPEG4AudioConfig(aacparser.MPEG4AudioConfig{SampleRate: 8000, SampleRateIndex: 11, ObjectType: 2, ChannelLayout: 1, ChannelConfig: 1}) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return append(CodecData, audioCodec), nil 38 | } 39 | 40 | func (obj *FakeAAC) Close() { 41 | obj.closer <- true 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deepch/RTSPtoWSMP4f 2 | 3 | go 1.15 4 | 5 | require github.com/deepch/vdk v0.0.0-20220402115557-02504b116966 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/deepch/vdk v0.0.0-20210829103406-e99c71546c52 h1:4BU3M5FytIXd60lSNOQqshYpse1W8qi4NMvF30pLcgw= 5 | github.com/deepch/vdk v0.0.0-20210829103406-e99c71546c52/go.mod h1:LMmoiqSkkiwvrIpGiw+ZMsiw40xq3L7FadfAScgsVZE= 6 | github.com/deepch/vdk v0.0.0-20220303214059-7a7f15e1a59a h1:cZpn/rhSYt/4rz2YBCbzXJe2yStK3GafoFncY8jz3HQ= 7 | github.com/deepch/vdk v0.0.0-20220303214059-7a7f15e1a59a/go.mod h1:9rAZ6GWQMM5y9J58Efr1uh+YYTuzojAnvUH1XxS8OJ8= 8 | github.com/deepch/vdk v0.0.0-20220309163430-c6529706436c h1:ElPbczQd4gu6bAiBYdffk4d2KoGG1qolXVfDJkijpok= 9 | github.com/deepch/vdk v0.0.0-20220309163430-c6529706436c/go.mod h1:9rAZ6GWQMM5y9J58Efr1uh+YYTuzojAnvUH1XxS8OJ8= 10 | github.com/deepch/vdk v0.0.0-20220313222842-9b5af91694e2 h1:cgjCpUErEz6b/jO/0szpPckET14JXQ1JIFkLOWGcEL0= 11 | github.com/deepch/vdk v0.0.0-20220313222842-9b5af91694e2/go.mod h1:9rAZ6GWQMM5y9J58Efr1uh+YYTuzojAnvUH1XxS8OJ8= 12 | github.com/deepch/vdk v0.0.0-20220317152139-68ae487ae8a6 h1:McEsLiEE3E8ZyfLX5pj+SB1XwgvRQ8QXEW8keC9xIFY= 13 | github.com/deepch/vdk v0.0.0-20220317152139-68ae487ae8a6/go.mod h1:9rAZ6GWQMM5y9J58Efr1uh+YYTuzojAnvUH1XxS8OJ8= 14 | github.com/deepch/vdk v0.0.0-20220402115557-02504b116966 h1:FlEAUD1Xn82Up/jezt32nQUbYkEwVkkDYLuNOo9DVWw= 15 | github.com/deepch/vdk v0.0.0-20220402115557-02504b116966/go.mod h1:9rAZ6GWQMM5y9J58Efr1uh+YYTuzojAnvUH1XxS8OJ8= 16 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 17 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 18 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 19 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 20 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 22 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 23 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 24 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 25 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 26 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 27 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 28 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 29 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 30 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 31 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 32 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 34 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 36 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 37 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 38 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 39 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 40 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 41 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 42 | github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= 43 | github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= 44 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 45 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 46 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 47 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 48 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 49 | github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= 50 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 51 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 52 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 53 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 54 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 55 | github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= 56 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 57 | github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= 58 | github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= 59 | github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U= 60 | github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I= 61 | github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho= 62 | github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= 63 | github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= 64 | github.com/pion/ice v0.7.18/go.mod h1:+Bvnm3nYC6Nnp7VV6glUkuOfToB/AtMRZpOU8ihuf4c= 65 | github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0= 66 | github.com/pion/ice/v2 v2.2.1/go.mod h1:Op8jlPtjeiycsXh93Cs4jK82C9j/kh7vef6ztIOvtIQ= 67 | github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4= 68 | github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U= 69 | github.com/pion/interceptor v0.1.8/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U= 70 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= 71 | github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= 72 | github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= 73 | github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k= 74 | github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 75 | github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 76 | github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I= 77 | github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= 78 | github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= 79 | github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI= 80 | github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= 81 | github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= 82 | github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= 83 | github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= 84 | github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= 85 | github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= 86 | github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= 87 | github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= 88 | github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E= 89 | github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= 90 | github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA= 91 | github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0= 92 | github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A= 93 | github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= 94 | github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= 95 | github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= 96 | github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= 97 | github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= 98 | github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= 99 | github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= 100 | github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= 101 | github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog= 102 | github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= 103 | github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= 104 | github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= 105 | github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= 106 | github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc= 107 | github.com/pion/webrtc/v3 v3.0.29/go.mod h1:XFQeLYBf++bWWA0sJqh6zF1ouWluosxwTOMOoTZGaD0= 108 | github.com/pion/webrtc/v3 v3.1.24/go.mod h1:mO/yv7fBN3Lp7YNlnYcTj1jtpvNvssJG+7eh6itZ4xM= 109 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 110 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 111 | github.com/quadrifoglio/go-mkv v0.0.0-20180620161916-e7a1fc70199c/go.mod h1:Mtv+LuzkLR5r7fwBlvqI/N/5d4nOaH/7fpgQFXwxnFk= 112 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 115 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 116 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 117 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 118 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 119 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 121 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 122 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 123 | golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 124 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 125 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 126 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 127 | golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 128 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 129 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 131 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 132 | golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 133 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 134 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 135 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 136 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 137 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 138 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 139 | golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 140 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 141 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 142 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 143 | golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 144 | golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 145 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 146 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 147 | golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 148 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 149 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 150 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 151 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 152 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 153 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 154 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 155 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 156 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 169 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 170 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 171 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 172 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 173 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 174 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 175 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 176 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 177 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 178 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 179 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 180 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 181 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 183 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 184 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 185 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 186 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 187 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 188 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 189 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 190 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 191 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 192 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 193 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 194 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 195 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 196 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 197 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 198 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 199 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 200 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | func main() { 11 | go serveStreams() 12 | sigs := make(chan os.Signal, 1) 13 | done := make(chan bool, 1) 14 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 15 | go func() { 16 | sig := <-sigs 17 | log.Println(sig) 18 | done <- true 19 | }() 20 | log.Println("Server Start Awaiting Signal") 21 | <-done 22 | log.Println("Exiting") 23 | } 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "time" 7 | 8 | "github.com/deepch/vdk/av" 9 | 10 | "github.com/deepch/vdk/format/rtmp" 11 | 12 | "github.com/deepch/vdk/format/rtspv2" 13 | ) 14 | 15 | var ( 16 | ErrorStreamExitNoVideoOnStream = errors.New("Stream Exit No Video On Stream") 17 | ErrorStreamExitRtspDisconnect = errors.New("Stream Exit Rtsp Disconnect") 18 | ErrorStreamExitNoViewer = errors.New("Stream Exit On Demand No Viewer") 19 | ) 20 | 21 | func serveStreams() { 22 | for k, v := range Config.Streams { 23 | if v.OnDemand { 24 | log.Println("OnDemand not supported") 25 | v.OnDemand = false 26 | } 27 | if !v.OnDemand { 28 | go RTSPWorkerLoop(k, v.URL, v.OnDemand) 29 | } 30 | } 31 | } 32 | 33 | func RTSPWorkerLoop(name, url string, OnDemand bool) { 34 | defer Config.RunUnlock(name) 35 | for { 36 | log.Println(name, "Stream Try Connect") 37 | err := RTSPWorker(name, url, OnDemand) 38 | if err != nil { 39 | log.Println(err) 40 | } 41 | if OnDemand && !Config.HasViewer(name) { 42 | log.Println(name, ErrorStreamExitNoViewer) 43 | return 44 | } 45 | time.Sleep(1 * time.Second) 46 | } 47 | } 48 | 49 | func RTSPWorker(name, url string, OnDemand bool) error { 50 | keyTest := time.NewTimer(20 * time.Second) 51 | clientTest := time.NewTimer(20 * time.Second) 52 | RTSPClient, err := rtspv2.Dial(rtspv2.RTSPClientOptions{URL: url, DisableAudio: false, DialTimeout: 3 * time.Second, ReadWriteTimeout: 3 * time.Second, Debug: false}) 53 | if err != nil { 54 | return err 55 | } 56 | defer RTSPClient.Close() 57 | var connRTMP *rtmp.Conn 58 | broadCastEnable, broadCastURL := Config.bcConfig(name) 59 | if broadCastEnable { 60 | connRTMP, err = rtmp.DialTimeout(broadCastURL, 5*time.Second) 61 | if err != nil { 62 | return err 63 | } 64 | defer connRTMP.Close() 65 | } 66 | if len(RTSPClient.CodecData) < 2 { 67 | AudioStreamFake := NewFakeAudio() 68 | RTSPClient.CodecData, err = AudioStreamFake.Start(RTSPClient.CodecData, RTSPClient.OutgoingPacketQueue) 69 | defer AudioStreamFake.Close() 70 | } else if RTSPClient.CodecData[1].Type() != av.AAC { 71 | log.Fatalln("Only AAC or Disable Audio") 72 | } 73 | if broadCastEnable { 74 | err = connRTMP.WriteHeader(RTSPClient.CodecData) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | if RTSPClient.CodecData != nil { 80 | Config.coAd(name, RTSPClient.CodecData) 81 | } 82 | var start bool 83 | var lineA time.Duration 84 | var lineV time.Duration 85 | for { 86 | select { 87 | case <-clientTest.C: 88 | if OnDemand && !Config.HasViewer(name) { 89 | return ErrorStreamExitNoViewer 90 | } 91 | case <-keyTest.C: 92 | return ErrorStreamExitNoVideoOnStream 93 | case signals := <-RTSPClient.Signals: 94 | switch signals { 95 | case rtspv2.SignalCodecUpdate: 96 | Config.coAd(name, RTSPClient.CodecData) 97 | case rtspv2.SignalStreamRTPStop: 98 | return ErrorStreamExitRtspDisconnect 99 | } 100 | case packetAV := <-RTSPClient.OutgoingPacketQueue: 101 | if packetAV.IsKeyFrame { 102 | keyTest.Reset(20 * time.Second) 103 | } 104 | if packetAV.IsKeyFrame && !start { 105 | start = true 106 | } 107 | if packetAV.Idx == 0 { 108 | lineV += packetAV.Duration 109 | packetAV.Time = lineV 110 | } 111 | if packetAV.Idx == 1 { 112 | lineA += packetAV.Duration 113 | packetAV.Time = lineA 114 | } 115 | if broadCastEnable && start { 116 | if err = connRTMP.WritePacket(*packetAV); err != nil { 117 | return err 118 | } 119 | } 120 | } 121 | } 122 | } 123 | --------------------------------------------------------------------------------