init
parent
5219ce4f1f
commit
cdd255edda
@ -0,0 +1,4 @@
|
||||
|
||||
.idea/
|
||||
|
||||
bin/
|
||||
@ -0,0 +1,14 @@
|
||||
FROM golang:1.16
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY . .
|
||||
|
||||
RUN go get -d -v ./...
|
||||
RUN go install -v ./...
|
||||
|
||||
EXPOSE 8083
|
||||
|
||||
ENV GO111MODULE=on
|
||||
ENV GIN_MODE=release
|
||||
|
||||
CMD go run *.go
|
||||
@ -1 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
# RTSPtoWebRTC
|
||||
|
||||
RTSP Stream to WebBrowser over WebRTC based on Pion
|
||||
|
||||
full native! not use ffmpeg or gstreamer
|
||||
|
||||
if you need RTSPtoWSMP4f use https://github.com/deepch/RTSPtoWSMP4f
|
||||
|
||||
|
||||

|
||||
|
||||
### Download Source
|
||||
|
||||
1. Download source
|
||||
```bash
|
||||
$ git clone https://github.com/deepch/RTSPtoWebRTC
|
||||
```
|
||||
3. CD to Directory
|
||||
```bash
|
||||
$ cd RTSPtoWebRTC/
|
||||
```
|
||||
4. Test Run
|
||||
```bash
|
||||
$ GO111MODULE=on go run *.go
|
||||
```
|
||||
5. Open Browser
|
||||
```bash
|
||||
open web browser http://127.0.0.1:8083 work chrome, safari, firefox
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Edit file config.json
|
||||
|
||||
format:
|
||||
|
||||
```bash
|
||||
{
|
||||
"server": {
|
||||
"http_port": ":8083"
|
||||
},
|
||||
"streams": {
|
||||
"demo1": {
|
||||
"on_demand" : false
|
||||
"url": "rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa"
|
||||
},
|
||||
"demo2": {
|
||||
"on_demand" : true
|
||||
"url": "rtsp://admin:admin123@10.128.18.224/mpeg4"
|
||||
},
|
||||
"demo3": {
|
||||
"on_demand" : false
|
||||
"url": "rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Livestreams
|
||||
|
||||
Use option ``` "on_demand": false ``` otherwise you will get choppy jerky streams and performance issues when multiple clients connect.
|
||||
|
||||
## Limitations
|
||||
|
||||
Video Codecs Supported: H264
|
||||
|
||||
Audio Codecs Supported: pcm alaw and pcm mulaw
|
||||
|
||||
## Team
|
||||
|
||||
Deepch - https://github.com/deepch streaming developer
|
||||
|
||||
Dmitry - https://github.com/vdalex25 web developer
|
||||
|
||||
Now test work on (chrome, safari, firefox) no MAC OS
|
||||
|
||||
## Other Example
|
||||
|
||||
Examples of working with video on golang
|
||||
|
||||
- [RTSPtoWeb](https://github.com/deepch/RTSPtoWeb)
|
||||
- [RTSPtoWebRTC](https://github.com/deepch/RTSPtoWebRTC)
|
||||
- [RTSPtoWSMP4f](https://github.com/deepch/RTSPtoWSMP4f)
|
||||
- [RTSPtoImage](https://github.com/deepch/RTSPtoImage)
|
||||
- [RTSPtoHLS](https://github.com/deepch/RTSPtoHLS)
|
||||
- [RTSPtoHLSLL](https://github.com/deepch/RTSPtoHLSLL)
|
||||
|
||||
[](https://www.paypal.me/AndreySemochkin) - You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea:
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,26 @@
|
||||
@REM https://habr.com/ru/post/249449/
|
||||
|
||||
@SET GOOS=windows
|
||||
@SET GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_amd64.exe
|
||||
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=386
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_i386
|
||||
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_amd64
|
||||
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=arm
|
||||
@SET GOARM=7
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_armv7
|
||||
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=arm64
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_aarch64
|
||||
|
||||
@SET GOOS=darwin
|
||||
@SET GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o bin/rtsp2webrtc_darwin
|
||||
@ -0,0 +1,241 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/codec/h264parser"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
)
|
||||
|
||||
//Config global
|
||||
var Config = loadConfig()
|
||||
|
||||
//ConfigST struct
|
||||
type ConfigST struct {
|
||||
mutex sync.RWMutex
|
||||
Server ServerST `json:"server"`
|
||||
Streams map[string]StreamST `json:"streams"`
|
||||
LastError error
|
||||
}
|
||||
|
||||
//ServerST struct
|
||||
type ServerST struct {
|
||||
HTTPPort string `json:"http_port"`
|
||||
ICEServers []string `json:"ice_servers"`
|
||||
ICEUsername string `json:"ice_username"`
|
||||
ICECredential string `json:"ice_credential"`
|
||||
WebRTCPortMin uint16 `json:"webrtc_port_min"`
|
||||
WebRTCPortMax uint16 `json:"webrtc_port_max"`
|
||||
}
|
||||
|
||||
//StreamST struct
|
||||
type StreamST struct {
|
||||
URL string `json:"url"`
|
||||
Status bool `json:"status"`
|
||||
OnDemand bool `json:"on_demand"`
|
||||
DisableAudio bool `json:"disable_audio"`
|
||||
Debug bool `json:"debug"`
|
||||
RunLock bool `json:"-"`
|
||||
Codecs []av.CodecData
|
||||
Cl map[string]viewer
|
||||
}
|
||||
|
||||
type viewer struct {
|
||||
c chan av.Packet
|
||||
}
|
||||
|
||||
func (element *ConfigST) RunIFNotRun(uuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok {
|
||||
if tmp.OnDemand && !tmp.RunLock {
|
||||
tmp.RunLock = true
|
||||
element.Streams[uuid] = tmp
|
||||
go RTSPWorkerLoop(uuid, tmp.URL, tmp.OnDemand, tmp.DisableAudio, tmp.Debug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) RunUnlock(uuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok {
|
||||
if tmp.OnDemand && tmp.RunLock {
|
||||
tmp.RunLock = false
|
||||
element.Streams[uuid] = tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) HasViewer(uuid string) bool {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
if tmp, ok := element.Streams[uuid]; ok && len(tmp.Cl) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (element *ConfigST) GetICEServers() []string {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
return element.Server.ICEServers
|
||||
}
|
||||
|
||||
func (element *ConfigST) GetICEUsername() string {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
return element.Server.ICEUsername
|
||||
}
|
||||
|
||||
func (element *ConfigST) GetICECredential() string {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
return element.Server.ICECredential
|
||||
}
|
||||
|
||||
func (element *ConfigST) GetWebRTCPortMin() uint16 {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
return element.Server.WebRTCPortMin
|
||||
}
|
||||
|
||||
func (element *ConfigST) GetWebRTCPortMax() uint16 {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
return element.Server.WebRTCPortMax
|
||||
}
|
||||
|
||||
func loadConfig() *ConfigST {
|
||||
var tmp ConfigST
|
||||
data, err := ioutil.ReadFile("config.json")
|
||||
if err == nil {
|
||||
err = json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for i, v := range tmp.Streams {
|
||||
v.Cl = make(map[string]viewer)
|
||||
tmp.Streams[i] = v
|
||||
}
|
||||
} else {
|
||||
addr := flag.String("listen", "8083", "HTTP host:port")
|
||||
udpMin := flag.Int("udp_min", 0, "WebRTC UDP port min")
|
||||
udpMax := flag.Int("udp_max", 0, "WebRTC UDP port max")
|
||||
iceServer := flag.String("ice_server", "", "ICE Server")
|
||||
flag.Parse()
|
||||
|
||||
tmp.Server.HTTPPort = *addr
|
||||
tmp.Server.WebRTCPortMin = uint16(*udpMin)
|
||||
tmp.Server.WebRTCPortMax = uint16(*udpMax)
|
||||
if len(*iceServer) > 0 {
|
||||
tmp.Server.ICEServers = []string{*iceServer}
|
||||
}
|
||||
|
||||
tmp.Streams = make(map[string]StreamST)
|
||||
}
|
||||
return &tmp
|
||||
}
|
||||
|
||||
func (element *ConfigST) cast(uuid string, pck av.Packet) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
for _, v := range element.Streams[uuid].Cl {
|
||||
if len(v.c) < cap(v.c) {
|
||||
v.c <- pck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ConfigST) ext(suuid string) bool {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
_, ok := element.Streams[suuid]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (element *ConfigST) coAd(suuid string, codecs []av.CodecData) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
t := element.Streams[suuid]
|
||||
t.Codecs = codecs
|
||||
element.Streams[suuid] = t
|
||||
}
|
||||
|
||||
func (element *ConfigST) coGe(suuid string) []av.CodecData {
|
||||
for i := 0; i < 100; i++ {
|
||||
element.mutex.RLock()
|
||||
tmp, ok := element.Streams[suuid]
|
||||
element.mutex.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if tmp.Codecs != nil {
|
||||
//TODO Delete test
|
||||
for _, codec := range tmp.Codecs {
|
||||
if codec.Type() == av.H264 {
|
||||
codecVideo := codec.(h264parser.CodecData)
|
||||
if codecVideo.SPS() != nil && codecVideo.PPS() != nil && len(codecVideo.SPS()) > 0 && len(codecVideo.PPS()) > 0 {
|
||||
//ok
|
||||
//log.Println("Ok Video Ready to play")
|
||||
} else {
|
||||
//video codec not ok
|
||||
log.Println("Bad Video Codec SPS or PPS Wait")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmp.Codecs
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (element *ConfigST) clAd(suuid string) (string, chan av.Packet) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
cuuid := pseudoUUID()
|
||||
ch := make(chan av.Packet, 100)
|
||||
element.Streams[suuid].Cl[cuuid] = viewer{c: ch}
|
||||
return cuuid, ch
|
||||
}
|
||||
|
||||
func (element *ConfigST) list() (string, []string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
var res []string
|
||||
var fist string
|
||||
for k := range element.Streams {
|
||||
if fist == "" {
|
||||
fist = k
|
||||
}
|
||||
res = append(res, k)
|
||||
}
|
||||
return fist, res
|
||||
}
|
||||
func (element *ConfigST) clDe(suuid, cuuid string) {
|
||||
element.mutex.Lock()
|
||||
defer element.mutex.Unlock()
|
||||
delete(element.Streams[suuid].Cl, cuuid)
|
||||
}
|
||||
|
||||
func pseudoUUID() (uuid string) {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
fmt.Println("Error: ", err)
|
||||
return
|
||||
}
|
||||
uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
return
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"http_port": ":8083",
|
||||
"ice_servers": ["stun:stun.l.google.com:19302"],
|
||||
"ice_username": "",
|
||||
"ice_credential": ""
|
||||
},
|
||||
"streams": {
|
||||
"camera1": {
|
||||
"on_demand": false,
|
||||
"disable_audio": true,
|
||||
"url": "rtsp://admin:a1234567@172.16.0.118:554/cam/realmonitor?channel=1&subtype=0"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 403 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 315 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
@ -0,0 +1,8 @@
|
||||
module github.com/deepch/RTSPtoWebRTC
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/deepch/vdk v0.0.0-20210508200759-5adbbcc01f89
|
||||
github.com/gin-gonic/gin v1.7.0
|
||||
)
|
||||
@ -0,0 +1,197 @@
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepch/vdk v0.0.0-20210508200759-5adbbcc01f89 h1:LU4kMpxvMVnqyIU5iB0va7loq01eIl1cVeJpOhHoCX4=
|
||||
github.com/deepch/vdk v0.0.0-20210508200759-5adbbcc01f89/go.mod h1:EZi580zgIZQOVwMcApOE/rTxuWCHvP/9yAFkUro14LI=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU=
|
||||
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
|
||||
github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
|
||||
github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
|
||||
github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
|
||||
github.com/pion/ice v0.7.18 h1:KbAWlzWRUdX9SmehBh3gYpIFsirjhSQsCw6K2MjYMK0=
|
||||
github.com/pion/ice v0.7.18/go.mod h1:+Bvnm3nYC6Nnp7VV6glUkuOfToB/AtMRZpOU8ihuf4c=
|
||||
github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ=
|
||||
github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI=
|
||||
github.com/pion/interceptor v0.0.9 h1:fk5hTdyLO3KURQsf/+RjMpEm4NE3yeTY9Kh97b5BvwA=
|
||||
github.com/pion/interceptor v0.0.9/go.mod h1:dHgEP5dtxOTf21MObuBAjJeAayPxLUAZjerGH8Xr07c=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
||||
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
|
||||
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
|
||||
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
|
||||
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
|
||||
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
|
||||
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
|
||||
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
|
||||
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE=
|
||||
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
|
||||
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
|
||||
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw=
|
||||
github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
|
||||
github.com/pion/srtp/v2 v2.0.1 h1:kgfh65ob3EcnFYA4kUBvU/menCp9u7qaJLXwWgpobzs=
|
||||
github.com/pion/srtp/v2 v2.0.1/go.mod h1:c8NWHhhkFf/drmHTAblkdu8++lsISEBBdAuiyxgqIsE=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
|
||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
|
||||
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
|
||||
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
|
||||
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc=
|
||||
github.com/pion/webrtc/v3 v3.0.5 h1:utennp7RwX+2mtyMzoOXE03IUIckiHBigjarRJZ2DqY=
|
||||
github.com/pion/webrtc/v3 v3.0.5/go.mod h1:/EDCREM8y+JrJSkoCRHpoz//qtuBCOYV4E96vEK3bz0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -0,0 +1,268 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/av"
|
||||
|
||||
webrtc "github.com/deepch/vdk/format/webrtcv3"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type JCodec struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func serveHTTP() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(CORSMiddleware())
|
||||
|
||||
if _, err := os.Stat("./web"); !os.IsNotExist(err) {
|
||||
router.LoadHTMLGlob("web/templates/*")
|
||||
router.GET("/", HTTPAPIServerIndex)
|
||||
router.GET("/stream/player/:uuid", HTTPAPIServerStreamPlayer)
|
||||
}
|
||||
router.POST("/stream/receiver/:uuid", HTTPAPIServerStreamWebRTC)
|
||||
router.GET("/stream/codec/:uuid", HTTPAPIServerStreamCodec)
|
||||
router.POST("/stream", HTTPAPIServerStreamWebRTC2)
|
||||
|
||||
router.StaticFS("/static", http.Dir("web/static"))
|
||||
err := router.Run(Config.Server.HTTPPort)
|
||||
if err != nil {
|
||||
log.Fatalln("Start HTTP Server error", err)
|
||||
}
|
||||
}
|
||||
|
||||
//HTTPAPIServerIndex index
|
||||
func HTTPAPIServerIndex(c *gin.Context) {
|
||||
_, all := Config.list()
|
||||
if len(all) > 0 {
|
||||
c.Header("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Redirect(http.StatusMovedPermanently, "stream/player/"+all[0])
|
||||
} else {
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"port": Config.Server.HTTPPort,
|
||||
"version": time.Now().String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//HTTPAPIServerStreamPlayer stream player
|
||||
func HTTPAPIServerStreamPlayer(c *gin.Context) {
|
||||
_, all := Config.list()
|
||||
sort.Strings(all)
|
||||
c.HTML(http.StatusOK, "player.tmpl", gin.H{
|
||||
"port": Config.Server.HTTPPort,
|
||||
"suuid": c.Param("uuid"),
|
||||
"suuidMap": all,
|
||||
"version": time.Now().String(),
|
||||
})
|
||||
}
|
||||
|
||||
//HTTPAPIServerStreamCodec stream codec
|
||||
func HTTPAPIServerStreamCodec(c *gin.Context) {
|
||||
if Config.ext(c.Param("uuid")) {
|
||||
Config.RunIFNotRun(c.Param("uuid"))
|
||||
codecs := Config.coGe(c.Param("uuid"))
|
||||
if codecs == nil {
|
||||
return
|
||||
}
|
||||
var tmpCodec []JCodec
|
||||
for _, codec := range codecs {
|
||||
if codec.Type() != av.H264 && codec.Type() != av.PCM_ALAW && codec.Type() != av.PCM_MULAW && codec.Type() != av.OPUS {
|
||||
log.Println("Codec Not Supported WebRTC ignore this track", codec.Type())
|
||||
continue
|
||||
}
|
||||
if codec.Type().IsVideo() {
|
||||
tmpCodec = append(tmpCodec, JCodec{Type: "video"})
|
||||
} else {
|
||||
tmpCodec = append(tmpCodec, JCodec{Type: "audio"})
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(tmpCodec)
|
||||
if err == nil {
|
||||
_, err = c.Writer.Write(b)
|
||||
if err != nil {
|
||||
log.Println("Write Codec Info error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//HTTPAPIServerStreamWebRTC stream video over WebRTC
|
||||
func HTTPAPIServerStreamWebRTC(c *gin.Context) {
|
||||
if !Config.ext(c.PostForm("suuid")) {
|
||||
log.Println("Stream Not Found")
|
||||
return
|
||||
}
|
||||
Config.RunIFNotRun(c.PostForm("suuid"))
|
||||
codecs := Config.coGe(c.PostForm("suuid"))
|
||||
if codecs == nil {
|
||||
log.Println("Stream Codec Not Found")
|
||||
return
|
||||
}
|
||||
var AudioOnly bool
|
||||
if len(codecs) == 1 && codecs[0].Type().IsAudio() {
|
||||
AudioOnly = true
|
||||
}
|
||||
muxerWebRTC := webrtc.NewMuxer(webrtc.Options{ICEServers: Config.GetICEServers(), ICEUsername: Config.GetICEUsername(), ICECredential: Config.GetICECredential(), PortMin: Config.GetWebRTCPortMin(), PortMax: Config.GetWebRTCPortMax()})
|
||||
answer, err := muxerWebRTC.WriteHeader(codecs, c.PostForm("data"))
|
||||
if err != nil {
|
||||
log.Println("WriteHeader", err)
|
||||
return
|
||||
}
|
||||
_, err = c.Writer.Write([]byte(answer))
|
||||
if err != nil {
|
||||
log.Println("Write", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
cid, ch := Config.clAd(c.PostForm("suuid"))
|
||||
defer Config.clDe(c.PostForm("suuid"), cid)
|
||||
defer muxerWebRTC.Close()
|
||||
var videoStart bool
|
||||
noVideo := time.NewTimer(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-noVideo.C:
|
||||
log.Println("noVideo")
|
||||
return
|
||||
case pck := <-ch:
|
||||
if pck.IsKeyFrame || AudioOnly {
|
||||
noVideo.Reset(10 * time.Second)
|
||||
videoStart = true
|
||||
}
|
||||
if !videoStart && !AudioOnly {
|
||||
continue
|
||||
}
|
||||
err = muxerWebRTC.WritePacket(pck)
|
||||
if err != nil {
|
||||
log.Println("WritePacket", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, x-access-token")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
|
||||
c.Header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Tracks []string `json:"tracks"`
|
||||
Sdp64 string `json:"sdp64"`
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func HTTPAPIServerStreamWebRTC2(c *gin.Context) {
|
||||
url := c.PostForm("url")
|
||||
if _, ok := Config.Streams[url]; !ok {
|
||||
Config.Streams[url] = StreamST{
|
||||
URL: url,
|
||||
OnDemand: true,
|
||||
Cl: make(map[string]viewer),
|
||||
}
|
||||
}
|
||||
|
||||
Config.RunIFNotRun(url)
|
||||
|
||||
codecs := Config.coGe(url)
|
||||
if codecs == nil {
|
||||
log.Println("Stream Codec Not Found")
|
||||
c.JSON(500, ResponseError{Error: Config.LastError.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
muxerWebRTC := webrtc.NewMuxer(
|
||||
webrtc.Options{
|
||||
ICEServers: Config.GetICEServers(),
|
||||
PortMin: Config.GetWebRTCPortMin(),
|
||||
PortMax: Config.GetWebRTCPortMax(),
|
||||
},
|
||||
)
|
||||
|
||||
sdp64 := c.PostForm("sdp64")
|
||||
answer, err := muxerWebRTC.WriteHeader(codecs, sdp64)
|
||||
if err != nil {
|
||||
log.Println("Muxer WriteHeader", err)
|
||||
c.JSON(500, ResponseError{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := Response{
|
||||
Sdp64: answer,
|
||||
}
|
||||
|
||||
for _, codec := range codecs {
|
||||
if codec.Type() != av.H264 &&
|
||||
codec.Type() != av.PCM_ALAW &&
|
||||
codec.Type() != av.PCM_MULAW &&
|
||||
codec.Type() != av.OPUS {
|
||||
log.Println("Codec Not Supported WebRTC ignore this track", codec.Type())
|
||||
continue
|
||||
}
|
||||
if codec.Type().IsVideo() {
|
||||
response.Tracks = append(response.Tracks, "video")
|
||||
} else {
|
||||
response.Tracks = append(response.Tracks, "audio")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, response)
|
||||
|
||||
AudioOnly := len(codecs) == 1 && codecs[0].Type().IsAudio()
|
||||
|
||||
go func() {
|
||||
cid, ch := Config.clAd(url)
|
||||
defer Config.clDe(url, cid)
|
||||
defer muxerWebRTC.Close()
|
||||
var videoStart bool
|
||||
noVideo := time.NewTimer(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-noVideo.C:
|
||||
log.Println("noVideo")
|
||||
return
|
||||
case pck := <-ch:
|
||||
if pck.IsKeyFrame || AudioOnly {
|
||||
noVideo.Reset(10 * time.Second)
|
||||
videoStart = true
|
||||
}
|
||||
if !videoStart && !AudioOnly {
|
||||
continue
|
||||
}
|
||||
err = muxerWebRTC.WritePacket(pck)
|
||||
if err != nil {
|
||||
log.Println("WritePacket", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
go serveHTTP()
|
||||
go serveStreams()
|
||||
sigs := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
log.Println(sig)
|
||||
done <- true
|
||||
}()
|
||||
log.Println("Server Start Awaiting Signal")
|
||||
<-done
|
||||
log.Println("Exiting")
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/deepch/vdk/format/rtspv2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorStreamExitNoVideoOnStream = errors.New("Stream Exit No Video On Stream")
|
||||
ErrorStreamExitRtspDisconnect = errors.New("Stream Exit Rtsp Disconnect")
|
||||
ErrorStreamExitNoViewer = errors.New("Stream Exit On Demand No Viewer")
|
||||
)
|
||||
|
||||
func serveStreams() {
|
||||
for k, v := range Config.Streams {
|
||||
if !v.OnDemand {
|
||||
go RTSPWorkerLoop(k, v.URL, v.OnDemand, v.DisableAudio, v.Debug)
|
||||
}
|
||||
}
|
||||
}
|
||||
func RTSPWorkerLoop(name, url string, OnDemand, DisableAudio, Debug bool) {
|
||||
defer Config.RunUnlock(name)
|
||||
for {
|
||||
log.Println("Stream Try Connect", name)
|
||||
err := RTSPWorker(name, url, OnDemand, DisableAudio, Debug)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
Config.LastError = err
|
||||
}
|
||||
if OnDemand && !Config.HasViewer(name) {
|
||||
log.Println(ErrorStreamExitNoViewer)
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
func RTSPWorker(name, url string, OnDemand, DisableAudio, Debug bool) error {
|
||||
keyTest := time.NewTimer(20 * time.Second)
|
||||
clientTest := time.NewTimer(20 * time.Second)
|
||||
//add next TimeOut
|
||||
RTSPClient, err := rtspv2.Dial(rtspv2.RTSPClientOptions{URL: url, DisableAudio: DisableAudio, DialTimeout: 3 * time.Second, ReadWriteTimeout: 3 * time.Second, Debug: Debug})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer RTSPClient.Close()
|
||||
if RTSPClient.CodecData != nil {
|
||||
Config.coAd(name, RTSPClient.CodecData)
|
||||
}
|
||||
var AudioOnly bool
|
||||
if len(RTSPClient.CodecData) == 1 && RTSPClient.CodecData[0].Type().IsAudio() {
|
||||
AudioOnly = true
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-clientTest.C:
|
||||
if OnDemand {
|
||||
if !Config.HasViewer(name) {
|
||||
return ErrorStreamExitNoViewer
|
||||
} else {
|
||||
clientTest.Reset(20 * time.Second)
|
||||
}
|
||||
}
|
||||
case <-keyTest.C:
|
||||
return ErrorStreamExitNoVideoOnStream
|
||||
case signals := <-RTSPClient.Signals:
|
||||
switch signals {
|
||||
case rtspv2.SignalCodecUpdate:
|
||||
Config.coAd(name, RTSPClient.CodecData)
|
||||
case rtspv2.SignalStreamRTPStop:
|
||||
return ErrorStreamExitRtspDisconnect
|
||||
}
|
||||
case packetAV := <-RTSPClient.OutgoingPacketQueue:
|
||||
if AudioOnly || packetAV.IsKeyFrame {
|
||||
keyTest.Reset(20 * time.Second)
|
||||
}
|
||||
Config.cast(name, *packetAV)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,331 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="reset"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,69 @@
|
||||
let stream = new MediaStream();
|
||||
let suuid = $('#suuid').val();
|
||||
|
||||
let config = {
|
||||
iceServers: [{
|
||||
urls: ["stun:stun.l.google.com:19302"]
|
||||
}]
|
||||
};
|
||||
|
||||
const pc = new RTCPeerConnection(config);
|
||||
pc.onnegotiationneeded = handleNegotiationNeededEvent;
|
||||
|
||||
let log = msg => {
|
||||
document.getElementById('div').innerHTML += msg + '<br>'
|
||||
}
|
||||
|
||||
pc.ontrack = function(event) {
|
||||
stream.addTrack(event.track);
|
||||
videoElem.srcObject = stream;
|
||||
log(event.streams.length + ' track is delivered')
|
||||
}
|
||||
|
||||
pc.oniceconnectionstatechange = e => log('here '+pc.iceConnectionState)
|
||||
|
||||
async function handleNegotiationNeededEvent() {
|
||||
let offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
getRemoteSdp();
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#' + suuid).addClass('active');
|
||||
getCodecInfo();
|
||||
});
|
||||
|
||||
|
||||
function getCodecInfo() {
|
||||
$.get("../codec/" + suuid, function(data) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
$.each(data,function(index,value){
|
||||
pc.addTransceiver(value.Type, {
|
||||
'direction': 'sendrecv'
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let sendChannel = null;
|
||||
|
||||
function getRemoteSdp() {
|
||||
$.post("../receiver/"+ suuid, {
|
||||
suuid: suuid,
|
||||
data: btoa(pc.localDescription.sdp)
|
||||
}, function(data) {
|
||||
try {
|
||||
pc.setRemoteDescription(new RTCSessionDescription({
|
||||
type: 'answer',
|
||||
sdp: atob(data)
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta http-equiv="Last-Modified" content="0">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, mustrevalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
||||
<script type="text/javascript" src="static/js/jquery-3.4.1.min.js"></script>
|
||||
<script src="static/js/bootstrap.min.js"></script>
|
||||
|
||||
<h2 align=center>
|
||||
No Stream Found (add stream and reload page)
|
||||
</h2>
|
||||
</html>
|
||||
@ -0,0 +1,36 @@
|
||||
<html>
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta http-equiv="Last-Modified" content="0">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, mustrevalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<link rel="stylesheet" href="../../static/css/bootstrap.min.css">
|
||||
<script type="text/javascript" src="../../static/js/jquery-3.4.1.min.js"></script>
|
||||
<script src="../../static/js/bootstrap.min.js"></script>
|
||||
<script src="../../static/js/adapter-latest.js"></script>
|
||||
|
||||
<h2 align=center>
|
||||
Play Stream {{ .suuid }}
|
||||
</h2>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="list-group">
|
||||
{{ range .suuidMap }}
|
||||
<a href="{{ . }}" id="{{ . }}" name="{{ . }}" class="list-group-item list-group-item-action">{{ . }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="hidden" name="suuid" id="suuid" value="{{ .suuid }}">
|
||||
<input type="hidden" name="port" id="port" value="{{ .port }}">
|
||||
<input type="hidden" id="localSessionDescription" readonly="true">
|
||||
<input type="hidden" id="remoteSessionDescription">
|
||||
<div id="remoteVideos">
|
||||
<video style="width:600px" id="videoElem" autoplay muted controls></video>
|
||||
</div>
|
||||
<div id="div"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../static/js/app.js?ver={{ .version }}"></script>
|
||||
</html>
|
||||
Loading…
Reference in New Issue