mirror of https://github.com/gwuhaolin/livego.git
69 changed files with 2000 additions and 964 deletions
@ -0,0 +1,27 @@ |
|||||||
|
name: Release |
||||||
|
on: |
||||||
|
release: |
||||||
|
types: [published] |
||||||
|
jobs: |
||||||
|
goreleaser: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
env: |
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||||
|
steps: |
||||||
|
- name: Set up Go 1.13 |
||||||
|
uses: actions/setup-go@v1 |
||||||
|
with: |
||||||
|
go-version: 1.13 |
||||||
|
id: go |
||||||
|
- name: Check out code into the Go module directory |
||||||
|
uses: actions/checkout@v1 |
||||||
|
- name: Get dependencies |
||||||
|
run: go mod tidy |
||||||
|
- name: Go release |
||||||
|
uses: goreleaser/goreleaser-action@v1 |
||||||
|
- name: Docker release |
||||||
|
uses: elgohr/Publish-Docker-Github-Action@master |
||||||
|
with: |
||||||
|
name: gwuhaolin/livego |
||||||
|
username: ${{ secrets.DOCKER_USERNAME }} |
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }} |
@ -0,0 +1,22 @@ |
|||||||
|
name: Test |
||||||
|
on: [push] |
||||||
|
jobs: |
||||||
|
test: |
||||||
|
name: Build |
||||||
|
runs-on: ${{ matrix.os }} |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
os: [ubuntu-latest, windows-latest, macOS-latest] |
||||||
|
|
||||||
|
steps: |
||||||
|
- name: Set up Go 1.13 |
||||||
|
uses: actions/setup-go@v1 |
||||||
|
with: |
||||||
|
go-version: 1.13 |
||||||
|
id: go |
||||||
|
- name: Check out code into the Go module directory |
||||||
|
uses: actions/checkout@v1 |
||||||
|
- name: Get dependencies |
||||||
|
run: go mod tidy |
||||||
|
- name: Test |
||||||
|
run: go test ./... |
@ -1,3 +1,7 @@ |
|||||||
# Created by .ignore support plugin (hsz.mobi) |
# Created by .ignore support plugin (hsz.mobi) |
||||||
.idea |
.idea |
||||||
dist |
dist |
||||||
|
.vscode |
||||||
|
tmp |
||||||
|
vendor |
||||||
|
livego |
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
before: |
||||||
|
hooks: |
||||||
|
- go mod tidy |
||||||
|
builds: |
||||||
|
- binary: livego |
||||||
|
id: livego |
||||||
|
main: ./main.go |
||||||
|
goos: |
||||||
|
- windows |
||||||
|
- darwin |
||||||
|
- linux |
||||||
|
- freebsd |
||||||
|
goarch: |
||||||
|
- amd64 |
||||||
|
- 386 |
||||||
|
- arm |
@ -0,0 +1,52 @@ |
|||||||
|
# Changelog |
||||||
|
All notable changes to this project will be documented in this file. |
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
||||||
|
|
||||||
|
## [Unreleased] |
||||||
|
|
||||||
|
### Added |
||||||
|
- JSON Web Token support. |
||||||
|
``` json |
||||||
|
// livego.json |
||||||
|
{ |
||||||
|
"jwt": { |
||||||
|
"secret": "testing", |
||||||
|
"algorithm": "HS256" |
||||||
|
}, |
||||||
|
"server": [ |
||||||
|
{ |
||||||
|
"appname": "live", |
||||||
|
"live": true, |
||||||
|
"hls": true |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
``` |
||||||
|
- Use redis for store room keys |
||||||
|
``` json |
||||||
|
// livego.json |
||||||
|
{ |
||||||
|
"redis_addr": "localhost:6379", |
||||||
|
"server": [ |
||||||
|
{ |
||||||
|
"appname": "live", |
||||||
|
"live": true, |
||||||
|
"hls": true |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
``` |
||||||
|
- Makefile |
||||||
|
|
||||||
|
### Changed |
||||||
|
- Show `players`. |
||||||
|
- Show `stream_id`. |
||||||
|
- Deleted keys saved in physical file, now the keys are in cached using `go-cache` by default. |
||||||
|
- Using `logrus` like log system. |
||||||
|
- Using method `.Get(queryParamName)` to get an url query param. |
||||||
|
- Replaced `errors.New(...)` to `fmt.Errorf(...)`. |
||||||
|
- Replaced types string on config params `liveon` and `hlson` to booleans `live: true/false` and `hls: true/false` |
||||||
|
- Using viper for config, allow use file, cloud providers, environment vars or flags. |
||||||
|
- Using yaml config by default. |
@ -0,0 +1,21 @@ |
|||||||
|
FROM golang:latest as builder |
||||||
|
WORKDIR /app |
||||||
|
ENV GOPROXY https://goproxy.io |
||||||
|
COPY go.mod go.sum ./ |
||||||
|
RUN go mod download |
||||||
|
COPY . . |
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego . |
||||||
|
|
||||||
|
FROM alpine:latest |
||||||
|
RUN mkdir -p /app/config |
||||||
|
WORKDIR /app |
||||||
|
ENV RTMP_PORT 1935 |
||||||
|
ENV HTTP_FLV_PORT 7001 |
||||||
|
ENV HLS_PORT 7002 |
||||||
|
ENV HTTP_OPERATION_PORT 8090 |
||||||
|
COPY --from=builder /app/livego . |
||||||
|
EXPOSE ${RTMP_PORT} |
||||||
|
EXPOSE ${HTTP_FLV_PORT} |
||||||
|
EXPOSE ${HLS_PORT} |
||||||
|
EXPOSE ${HTTP_OPERATION_PORT} |
||||||
|
ENTRYPOINT ["./livego"] |
@ -0,0 +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,36 @@ |
|||||||
|
GOCMD ?= go |
||||||
|
GOBUILD = $(GOCMD) build |
||||||
|
GOCLEAN = $(GOCMD) clean |
||||||
|
GOTEST = $(GOCMD) test |
||||||
|
GOGET = $(GOCMD) get |
||||||
|
BINARY_NAME = livego |
||||||
|
BINARY_UNIX = $(BINARY_NAME)_unix |
||||||
|
|
||||||
|
DOCKER_ACC ?= gwuhaolin |
||||||
|
DOCKER_REPO ?= livego |
||||||
|
|
||||||
|
TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null) |
||||||
|
|
||||||
|
default: all |
||||||
|
|
||||||
|
all: test build dockerize |
||||||
|
build: |
||||||
|
$(GOBUILD) -o $(BINARY_NAME) -v -ldflags="-X main.VERSION=$(TAG)" |
||||||
|
|
||||||
|
test: |
||||||
|
$(GOTEST) -v ./... |
||||||
|
|
||||||
|
clean: |
||||||
|
$(GOCLEAN) |
||||||
|
rm -f $(BINARY_NAME) |
||||||
|
rm -f $(BINARY_UNIX) |
||||||
|
|
||||||
|
run: build |
||||||
|
./$(BINARY_NAME) |
||||||
|
|
||||||
|
build-linux: |
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v |
||||||
|
|
||||||
|
dockerize: |
||||||
|
docker build -t $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) . |
||||||
|
docker push $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) |
@ -0,0 +1,134 @@ |
|||||||
|
package configure |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/gwuhaolin/livego/utils/uid" |
||||||
|
|
||||||
|
"github.com/go-redis/redis/v7" |
||||||
|
"github.com/patrickmn/go-cache" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
type RoomKeysType struct { |
||||||
|
redisCli *redis.Client |
||||||
|
localCache *cache.Cache |
||||||
|
} |
||||||
|
|
||||||
|
var RoomKeys = &RoomKeysType{ |
||||||
|
localCache: cache.New(cache.NoExpiration, 0), |
||||||
|
} |
||||||
|
|
||||||
|
var saveInLocal = true |
||||||
|
|
||||||
|
func Init() { |
||||||
|
saveInLocal = len(Config.GetString("redis_addr")) == 0 |
||||||
|
if saveInLocal { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
RoomKeys.redisCli = redis.NewClient(&redis.Options{ |
||||||
|
Addr: Config.GetString("redis_addr"), |
||||||
|
Password: Config.GetString("redis_pwd"), |
||||||
|
DB: 0, |
||||||
|
}) |
||||||
|
|
||||||
|
_, err := RoomKeys.redisCli.Ping().Result() |
||||||
|
if err != nil { |
||||||
|
log.Panic("Redis: ", err) |
||||||
|
} |
||||||
|
|
||||||
|
log.Info("Redis connected") |
||||||
|
} |
||||||
|
|
||||||
|
// set/reset a random key for channel
|
||||||
|
func (r *RoomKeysType) SetKey(channel string) (key string, err error) { |
||||||
|
if !saveInLocal { |
||||||
|
for { |
||||||
|
key = uid.RandStringRunes(48) |
||||||
|
if _, err = r.redisCli.Get(key).Result(); err == redis.Nil { |
||||||
|
err = r.redisCli.Set(channel, key, 0).Err() |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
err = r.redisCli.Set(key, channel, 0).Err() |
||||||
|
return |
||||||
|
} else if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
key = uid.RandStringRunes(48) |
||||||
|
if _, found := r.localCache.Get(key); !found { |
||||||
|
r.localCache.SetDefault(channel, key) |
||||||
|
r.localCache.SetDefault(key, channel) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RoomKeysType) GetKey(channel string) (newKey string, err error) { |
||||||
|
if !saveInLocal { |
||||||
|
if newKey, err = r.redisCli.Get(channel).Result(); err == redis.Nil { |
||||||
|
newKey, err = r.SetKey(channel) |
||||||
|
log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
var key interface{} |
||||||
|
var found bool |
||||||
|
if key, found = r.localCache.Get(channel); found { |
||||||
|
return key.(string), nil |
||||||
|
} |
||||||
|
newKey, err = r.SetKey(channel) |
||||||
|
log.Debugf("[KEY] new channel [%s]: %s", channel, newKey) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RoomKeysType) GetChannel(key string) (channel string, err error) { |
||||||
|
if !saveInLocal { |
||||||
|
return r.redisCli.Get(key).Result() |
||||||
|
} |
||||||
|
|
||||||
|
chann, found := r.localCache.Get(key) |
||||||
|
if found { |
||||||
|
return chann.(string), nil |
||||||
|
} else { |
||||||
|
return "", fmt.Errorf("%s does not exists", key) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RoomKeysType) DeleteChannel(channel string) bool { |
||||||
|
if !saveInLocal { |
||||||
|
return r.redisCli.Del(channel).Err() != nil |
||||||
|
} |
||||||
|
|
||||||
|
key, ok := r.localCache.Get(channel) |
||||||
|
if ok { |
||||||
|
r.localCache.Delete(channel) |
||||||
|
r.localCache.Delete(key.(string)) |
||||||
|
return true |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RoomKeysType) DeleteKey(key string) bool { |
||||||
|
if !saveInLocal { |
||||||
|
return r.redisCli.Del(key).Err() != nil |
||||||
|
} |
||||||
|
|
||||||
|
channel, ok := r.localCache.Get(key) |
||||||
|
if ok { |
||||||
|
r.localCache.Delete(channel.(string)) |
||||||
|
r.localCache.Delete(key) |
||||||
|
return true |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
@ -1,74 +1,161 @@ |
|||||||
package configure |
package configure |
||||||
|
|
||||||
import ( |
import ( |
||||||
|
"bytes" |
||||||
"encoding/json" |
"encoding/json" |
||||||
"io/ioutil" |
"strings" |
||||||
"log" |
|
||||||
|
"github.com/kr/pretty" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
"github.com/spf13/pflag" |
||||||
|
"github.com/spf13/viper" |
||||||
) |
) |
||||||
|
|
||||||
/* |
/* |
||||||
{ |
{ |
||||||
[ |
"server": [ |
||||||
{ |
{ |
||||||
"application":"live", |
"appname": "live", |
||||||
"live":"on", |
"live": true, |
||||||
"hls":"on", |
"hls": true, |
||||||
"static_push":["rtmp://xx/live"] |
"static_push": [] |
||||||
} |
} |
||||||
] |
] |
||||||
} |
} |
||||||
*/ |
*/ |
||||||
|
|
||||||
type Application struct { |
type Application struct { |
||||||
Appname string |
Appname string `mapstructure:"appname"` |
||||||
Liveon string |
Live bool `mapstructure:"live"` |
||||||
Hlson string |
Hls bool `mapstructure:"hls"` |
||||||
Static_push []string |
StaticPush []string `mapstructure:"static_push"` |
||||||
} |
} |
||||||
|
|
||||||
|
type Applications []Application |
||||||
|
|
||||||
|
type JWT struct { |
||||||
|
Secret string `mapstructure:"secret"` |
||||||
|
Algorithm string `mapstructure:"algorithm"` |
||||||
|
} |
||||||
type ServerCfg struct { |
type ServerCfg struct { |
||||||
Server []Application |
Level string `mapstructure:"level"` |
||||||
|
ConfigFile string `mapstructure:"config_file"` |
||||||
|
FLVDir string `mapstructure:"flv_dir"` |
||||||
|
RTMPAddr string `mapstructure:"rtmp_addr"` |
||||||
|
HTTPFLVAddr string `mapstructure:"httpflv_addr"` |
||||||
|
HLSAddr string `mapstructure:"hls_addr"` |
||||||
|
HLSKeepAfterEnd bool `mapstructure:"hls_keep_after_end"` |
||||||
|
APIAddr string `mapstructure:"api_addr"` |
||||||
|
RedisAddr string `mapstructure:"redis_addr"` |
||||||
|
RedisPwd string `mapstructure:"redis_pwd"` |
||||||
|
ReadTimeout int `mapstructure:"read_timeout"` |
||||||
|
WriteTimeout int `mapstructure:"write_timeout"` |
||||||
|
GopNum int `mapstructure:"gop_num"` |
||||||
|
JWT JWT `mapstructure:"jwt"` |
||||||
|
Server Applications `mapstructure:"server"` |
||||||
|
} |
||||||
|
|
||||||
|
// default config
|
||||||
|
var defaultConf = ServerCfg{ |
||||||
|
ConfigFile: "livego.yaml", |
||||||
|
RTMPAddr: ":1935", |
||||||
|
HTTPFLVAddr: ":7001", |
||||||
|
HLSAddr: ":7002", |
||||||
|
HLSKeepAfterEnd: false, |
||||||
|
APIAddr: ":8090", |
||||||
|
WriteTimeout: 10, |
||||||
|
ReadTimeout: 10, |
||||||
|
GopNum: 1, |
||||||
|
Server: Applications{{ |
||||||
|
Appname: "live", |
||||||
|
Live: true, |
||||||
|
Hls: true, |
||||||
|
StaticPush: nil, |
||||||
|
}}, |
||||||
} |
} |
||||||
|
|
||||||
var RtmpServercfg ServerCfg |
var Config = viper.New() |
||||||
|
|
||||||
func LoadConfig(configfilename string) error { |
func initLog() { |
||||||
log.Printf("starting load configure file(%s)......", configfilename) |
if l, err := log.ParseLevel(Config.GetString("level")); err == nil { |
||||||
data, err := ioutil.ReadFile(configfilename) |
log.SetLevel(l) |
||||||
if err != nil { |
log.SetReportCaller(l == log.DebugLevel) |
||||||
log.Printf("ReadFile %s error:%v", configfilename, err) |
|
||||||
return err |
|
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
defer Init() |
||||||
|
|
||||||
log.Printf("loadconfig: \r\n%s", string(data)) |
// Default config
|
||||||
|
b, _ := json.Marshal(defaultConf) |
||||||
|
defaultConfig := bytes.NewReader(b) |
||||||
|
viper.SetConfigType("json") |
||||||
|
viper.ReadConfig(defaultConfig) |
||||||
|
Config.MergeConfigMap(viper.AllSettings()) |
||||||
|
|
||||||
err = json.Unmarshal(data, &RtmpServercfg) |
// Flags
|
||||||
|
pflag.String("rtmp_addr", ":1935", "RTMP server listen address") |
||||||
|
pflag.String("httpflv_addr", ":7001", "HTTP-FLV server listen address") |
||||||
|
pflag.String("hls_addr", ":7002", "HLS server listen address") |
||||||
|
pflag.String("api_addr", ":8090", "HTTP manage interface server listen address") |
||||||
|
pflag.String("config_file", "livego.yaml", "configure filename") |
||||||
|
pflag.String("level", "info", "Log level") |
||||||
|
pflag.Bool("hls_keep_after_end", false, "Maintains the HLS after the stream ends") |
||||||
|
pflag.String("flv_dir", "tmp", "output flv file at flvDir/APP/KEY_TIME.flv") |
||||||
|
pflag.Int("read_timeout", 10, "read time out") |
||||||
|
pflag.Int("write_timeout", 10, "write time out") |
||||||
|
pflag.Int("gop_num", 1, "gop num") |
||||||
|
pflag.Parse() |
||||||
|
Config.BindPFlags(pflag.CommandLine) |
||||||
|
|
||||||
|
// File
|
||||||
|
Config.SetConfigFile(Config.GetString("config_file")) |
||||||
|
Config.AddConfigPath(".") |
||||||
|
err := Config.ReadInConfig() |
||||||
if err != nil { |
if err != nil { |
||||||
log.Printf("json.Unmarshal error:%v", err) |
log.Warning(err) |
||||||
return err |
log.Info("Using default config") |
||||||
|
} else { |
||||||
|
Config.MergeInConfig() |
||||||
} |
} |
||||||
log.Printf("get config json data:%v", RtmpServercfg) |
|
||||||
return nil |
// Environment
|
||||||
|
replacer := strings.NewReplacer(".", "_") |
||||||
|
Config.SetEnvKeyReplacer(replacer) |
||||||
|
Config.AllowEmptyEnv(true) |
||||||
|
Config.AutomaticEnv() |
||||||
|
|
||||||
|
// Log
|
||||||
|
initLog() |
||||||
|
|
||||||
|
// Print final config
|
||||||
|
c := ServerCfg{} |
||||||
|
Config.Unmarshal(&c) |
||||||
|
log.Debugf("Current configurations: \n%# v", pretty.Formatter(c)) |
||||||
} |
} |
||||||
|
|
||||||
func CheckAppName(appname string) bool { |
func CheckAppName(appname string) bool { |
||||||
for _, app := range RtmpServercfg.Server { |
apps := Applications{} |
||||||
if (app.Appname == appname) && (app.Liveon == "on") { |
Config.UnmarshalKey("server", &apps) |
||||||
return true |
for _, app := range apps { |
||||||
|
if app.Appname == appname { |
||||||
|
return app.Live |
||||||
} |
} |
||||||
} |
} |
||||||
return false |
return false |
||||||
} |
} |
||||||
|
|
||||||
func GetStaticPushUrlList(appname string) ([]string, bool) { |
func GetStaticPushUrlList(appname string) ([]string, bool) { |
||||||
for _, app := range RtmpServercfg.Server { |
apps := Applications{} |
||||||
if (app.Appname == appname) && (app.Liveon == "on") { |
Config.UnmarshalKey("server", &apps) |
||||||
if len(app.Static_push) > 0 { |
for _, app := range apps { |
||||||
return app.Static_push, true |
if (app.Appname == appname) && app.Live { |
||||||
|
if len(app.StaticPush) > 0 { |
||||||
|
return app.StaticPush, true |
||||||
} else { |
} else { |
||||||
return nil, false |
return nil, false |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
return nil, false |
return nil, false |
||||||
} |
} |
||||||
|
@ -0,0 +1,19 @@ |
|||||||
|
module github.com/gwuhaolin/livego |
||||||
|
|
||||||
|
go 1.13 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b |
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible |
||||||
|
github.com/go-redis/redis/v7 v7.2.0 |
||||||
|
github.com/gorilla/mux v1.7.4 // indirect |
||||||
|
github.com/kr/pretty v0.1.0 |
||||||
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 |
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible |
||||||
|
github.com/satori/go.uuid v1.2.0 |
||||||
|
github.com/sirupsen/logrus v1.5.0 |
||||||
|
github.com/spf13/pflag v1.0.3 |
||||||
|
github.com/spf13/viper v1.6.3 |
||||||
|
github.com/stretchr/testify v1.4.0 |
||||||
|
github.com/urfave/negroni v1.0.0 // indirect |
||||||
|
) |
@ -0,0 +1,200 @@ |
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= |
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= |
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
||||||
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= |
||||||
|
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw= |
||||||
|
github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= |
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= |
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= |
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= |
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= |
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= |
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= |
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= |
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= |
||||||
|
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= |
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= |
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= |
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= |
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= |
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
||||||
|
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= |
||||||
|
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= |
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= |
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= |
||||||
|
github.com/golang/mock v1.1.1/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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= |
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= |
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= |
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= |
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= |
||||||
|
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= |
||||||
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= |
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= |
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= |
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= |
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= |
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= |
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= |
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= |
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= |
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= |
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= |
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= |
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= |
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= |
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= |
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= |
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= |
||||||
|
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/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= |
||||||
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= |
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= |
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= |
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= |
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= |
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= |
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
||||||
|
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= |
||||||
|
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
||||||
|
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= |
||||||
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= |
||||||
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= |
||||||
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= |
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= |
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= |
||||||
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= |
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= |
||||||
|
github.com/pkg/errors v0.8.0/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= |
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= |
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= |
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
||||||
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= |
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= |
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= |
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= |
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= |
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= |
||||||
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= |
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= |
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= |
||||||
|
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= |
||||||
|
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= |
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= |
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= |
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= |
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= |
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= |
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |
||||||
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= |
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= |
||||||
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= |
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= |
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= |
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= |
||||||
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= |
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
||||||
|
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= |
||||||
|
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= |
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= |
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= |
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= |
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= |
||||||
|
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= |
||||||
|
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= |
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= |
||||||
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= |
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= |
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= |
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= |
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= |
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= |
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||||
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= |
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= |
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= |
||||||
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= |
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= |
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= |
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= |
||||||
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= |
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= |
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= |
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= |
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= |
||||||
|
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 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= |
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
@ -1,11 +0,0 @@ |
|||||||
build: |
|
||||||
binary: livego |
|
||||||
goos: |
|
||||||
- windows |
|
||||||
- darwin |
|
||||||
- linux |
|
||||||
- freebsd |
|
||||||
goarch: |
|
||||||
- amd64 |
|
||||||
- 386 |
|
||||||
- arm |
|
@ -1,10 +0,0 @@ |
|||||||
{ |
|
||||||
"server": [ |
|
||||||
{ |
|
||||||
"appname":"live", |
|
||||||
"liveon":"on", |
|
||||||
"hlson":"on" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
@ -1,130 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"flag" |
|
||||||
"github.com/gwuhaolin/livego/configure" |
|
||||||
"github.com/gwuhaolin/livego/protocol/hls" |
|
||||||
"github.com/gwuhaolin/livego/protocol/httpflv" |
|
||||||
"github.com/gwuhaolin/livego/protocol/httpopera" |
|
||||||
"github.com/gwuhaolin/livego/protocol/rtmp" |
|
||||||
"log" |
|
||||||
"net" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
version = "master" |
|
||||||
rtmpAddr = flag.String("rtmp-addr", ":1935", "RTMP server listen address") |
|
||||||
httpFlvAddr = flag.String("httpflv-addr", ":7001", "HTTP-FLV server listen address") |
|
||||||
hlsAddr = flag.String("hls-addr", ":7002", "HLS server listen address") |
|
||||||
operaAddr = flag.String("manage-addr", ":8090", "HTTP manage interface server listen address") |
|
||||||
configfilename = flag.String("cfgfile", "livego.cfg", "live configure filename") |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
log.SetFlags(log.Lshortfile | log.Ltime | log.Ldate) |
|
||||||
flag.Parse() |
|
||||||
} |
|
||||||
|
|
||||||
func startHls() *hls.Server { |
|
||||||
hlsListen, err := net.Listen("tcp", *hlsAddr) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
hlsServer := hls.NewServer() |
|
||||||
go func() { |
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
log.Println("HLS server panic: ", r) |
|
||||||
} |
|
||||||
}() |
|
||||||
log.Println("HLS listen On", *hlsAddr) |
|
||||||
hlsServer.Serve(hlsListen) |
|
||||||
}() |
|
||||||
return hlsServer |
|
||||||
} |
|
||||||
|
|
||||||
func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) { |
|
||||||
rtmpListen, err := net.Listen("tcp", *rtmpAddr) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
var rtmpServer *rtmp.Server |
|
||||||
|
|
||||||
if hlsServer == nil { |
|
||||||
rtmpServer = rtmp.NewRtmpServer(stream, nil) |
|
||||||
log.Printf("hls server disable....") |
|
||||||
} else { |
|
||||||
rtmpServer = rtmp.NewRtmpServer(stream, hlsServer) |
|
||||||
log.Printf("hls server enable....") |
|
||||||
} |
|
||||||
|
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
log.Println("RTMP server panic: ", r) |
|
||||||
} |
|
||||||
}() |
|
||||||
log.Println("RTMP Listen On", *rtmpAddr) |
|
||||||
rtmpServer.Serve(rtmpListen) |
|
||||||
} |
|
||||||
|
|
||||||
func startHTTPFlv(stream *rtmp.RtmpStream) { |
|
||||||
flvListen, err := net.Listen("tcp", *httpFlvAddr) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
hdlServer := httpflv.NewServer(stream) |
|
||||||
go func() { |
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
log.Println("HTTP-FLV server panic: ", r) |
|
||||||
} |
|
||||||
}() |
|
||||||
log.Println("HTTP-FLV listen On", *httpFlvAddr) |
|
||||||
hdlServer.Serve(flvListen) |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
func startHTTPOpera(stream *rtmp.RtmpStream) { |
|
||||||
if *operaAddr != "" { |
|
||||||
opListen, err := net.Listen("tcp", *operaAddr) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
opServer := httpopera.NewServer(stream, *rtmpAddr) |
|
||||||
go func() { |
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
log.Println("HTTP-Operation server panic: ", r) |
|
||||||
} |
|
||||||
}() |
|
||||||
log.Println("HTTP-Operation listen On", *operaAddr) |
|
||||||
opServer.Serve(opListen) |
|
||||||
}() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func main() { |
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
log.Println("livego panic: ", r) |
|
||||||
time.Sleep(1 * time.Second) |
|
||||||
} |
|
||||||
}() |
|
||||||
log.Println("start livego, version", version) |
|
||||||
err := configure.LoadConfig(*configfilename) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
stream := rtmp.NewRtmpStream() |
|
||||||
hlsServer := startHls() |
|
||||||
startHTTPFlv(stream) |
|
||||||
startHTTPOpera(stream) |
|
||||||
|
|
||||||
startRtmp(stream, hlsServer) |
|
||||||
//startRtmp(stream, nil)
|
|
||||||
} |
|
@ -0,0 +1,21 @@ |
|||||||
|
# # Logger level |
||||||
|
# level: info |
||||||
|
|
||||||
|
# # FLV Options |
||||||
|
# flv_dir: "./tmp" |
||||||
|
# httpflv_addr: ":7001" |
||||||
|
|
||||||
|
# # RTMP Options |
||||||
|
# rtmp_addr: ":1935" |
||||||
|
# read_timeout: 10 |
||||||
|
# write_timeout: 10 |
||||||
|
|
||||||
|
# # HLS Options |
||||||
|
# hls_addr: ":7002" |
||||||
|
|
||||||
|
# # API Options |
||||||
|
# api_addr: ":8090" |
||||||
|
server: |
||||||
|
- appname: live |
||||||
|
live: true |
||||||
|
hls: true |
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1,143 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/gwuhaolin/livego/configure" |
||||||
|
"github.com/gwuhaolin/livego/protocol/api" |
||||||
|
"github.com/gwuhaolin/livego/protocol/hls" |
||||||
|
"github.com/gwuhaolin/livego/protocol/httpflv" |
||||||
|
"github.com/gwuhaolin/livego/protocol/rtmp" |
||||||
|
"net" |
||||||
|
"path" |
||||||
|
"runtime" |
||||||
|
"time" |
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
var VERSION = "master" |
||||||
|
|
||||||
|
func startHls() *hls.Server { |
||||||
|
hlsAddr := configure.Config.GetString("hls_addr") |
||||||
|
hlsListen, err := net.Listen("tcp", hlsAddr) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
hlsServer := hls.NewServer() |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
log.Error("HLS server panic: ", r) |
||||||
|
} |
||||||
|
}() |
||||||
|
log.Info("HLS listen On ", hlsAddr) |
||||||
|
hlsServer.Serve(hlsListen) |
||||||
|
}() |
||||||
|
return hlsServer |
||||||
|
} |
||||||
|
|
||||||
|
var rtmpAddr string |
||||||
|
|
||||||
|
func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) { |
||||||
|
rtmpAddr = configure.Config.GetString("rtmp_addr") |
||||||
|
|
||||||
|
rtmpListen, err := net.Listen("tcp", rtmpAddr) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
var rtmpServer *rtmp.Server |
||||||
|
|
||||||
|
if hlsServer == nil { |
||||||
|
rtmpServer = rtmp.NewRtmpServer(stream, nil) |
||||||
|
log.Info("HLS server disable....") |
||||||
|
} else { |
||||||
|
rtmpServer = rtmp.NewRtmpServer(stream, hlsServer) |
||||||
|
log.Info("HLS server enable....") |
||||||
|
} |
||||||
|
|
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
log.Error("RTMP server panic: ", r) |
||||||
|
} |
||||||
|
}() |
||||||
|
log.Info("RTMP Listen On ", rtmpAddr) |
||||||
|
rtmpServer.Serve(rtmpListen) |
||||||
|
} |
||||||
|
|
||||||
|
func startHTTPFlv(stream *rtmp.RtmpStream) { |
||||||
|
httpflvAddr := configure.Config.GetString("httpflv_addr") |
||||||
|
|
||||||
|
flvListen, err := net.Listen("tcp", httpflvAddr) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
hdlServer := httpflv.NewServer(stream) |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
log.Error("HTTP-FLV server panic: ", r) |
||||||
|
} |
||||||
|
}() |
||||||
|
log.Info("HTTP-FLV listen On ", httpflvAddr) |
||||||
|
hdlServer.Serve(flvListen) |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
func startAPI(stream *rtmp.RtmpStream) { |
||||||
|
apiAddr := configure.Config.GetString("api_addr") |
||||||
|
|
||||||
|
if apiAddr != "" { |
||||||
|
opListen, err := net.Listen("tcp", apiAddr) |
||||||
|
if err != nil { |
||||||
|
log.Fatal(err) |
||||||
|
} |
||||||
|
opServer := api.NewServer(stream, rtmpAddr) |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
log.Error("HTTP-API server panic: ", r) |
||||||
|
} |
||||||
|
}() |
||||||
|
log.Info("HTTP-API listen On ", apiAddr) |
||||||
|
opServer.Serve(opListen) |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
log.SetFormatter(&log.TextFormatter{ |
||||||
|
FullTimestamp: true, |
||||||
|
CallerPrettyfier: func(f *runtime.Frame) (string, string) { |
||||||
|
filename := path.Base(f.File) |
||||||
|
return fmt.Sprintf("%s()", f.Function), fmt.Sprintf(" %s:%d", filename, f.Line) |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
log.Error("livego panic: ", r) |
||||||
|
time.Sleep(1 * time.Second) |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
log.Infof(` |
||||||
|
_ _ ____ |
||||||
|
| | (_)_ _____ / ___| ___ |
||||||
|
| | | \ \ / / _ \ | _ / _ \ |
||||||
|
| |___| |\ V / __/ |_| | (_) | |
||||||
|
|_____|_| \_/ \___|\____|\___/ |
||||||
|
version: %s |
||||||
|
`, VERSION) |
||||||
|
|
||||||
|
stream := rtmp.NewRtmpStream() |
||||||
|
hlsServer := startHls() |
||||||
|
startHTTPFlv(stream) |
||||||
|
startAPI(stream) |
||||||
|
|
||||||
|
startRtmp(stream, hlsServer) |
||||||
|
} |
@ -0,0 +1,425 @@ |
|||||||
|
package api |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"github.com/gwuhaolin/livego/av" |
||||||
|
"github.com/gwuhaolin/livego/configure" |
||||||
|
"github.com/gwuhaolin/livego/protocol/rtmp" |
||||||
|
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay" |
||||||
|
|
||||||
|
jwtmiddleware "github.com/auth0/go-jwt-middleware" |
||||||
|
"github.com/dgrijalva/jwt-go" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
type Response struct { |
||||||
|
w http.ResponseWriter |
||||||
|
Status int `json:"status"` |
||||||
|
Data interface{} `json:"data"` |
||||||
|
} |
||||||
|
|
||||||
|
func (r *Response) SendJson() (int, error) { |
||||||
|
resp, _ := json.Marshal(r) |
||||||
|
r.w.Header().Set("Content-Type", "application/json") |
||||||
|
r.w.WriteHeader(r.Status) |
||||||
|
return r.w.Write(resp) |
||||||
|
} |
||||||
|
|
||||||
|
type Operation struct { |
||||||
|
Method string `json:"method"` |
||||||
|
URL string `json:"url"` |
||||||
|
Stop bool `json:"stop"` |
||||||
|
} |
||||||
|
|
||||||
|
type OperationChange struct { |
||||||
|
Method string `json:"method"` |
||||||
|
SourceURL string `json:"source_url"` |
||||||
|
TargetURL string `json:"target_url"` |
||||||
|
Stop bool `json:"stop"` |
||||||
|
} |
||||||
|
|
||||||
|
type ClientInfo struct { |
||||||
|
url string |
||||||
|
rtmpRemoteClient *rtmp.Client |
||||||
|
rtmpLocalClient *rtmp.Client |
||||||
|
} |
||||||
|
|
||||||
|
type Server struct { |
||||||
|
handler av.Handler |
||||||
|
session map[string]*rtmprelay.RtmpRelay |
||||||
|
rtmpAddr string |
||||||
|
} |
||||||
|
|
||||||
|
func NewServer(h av.Handler, rtmpAddr string) *Server { |
||||||
|
return &Server{ |
||||||
|
handler: h, |
||||||
|
session: make(map[string]*rtmprelay.RtmpRelay), |
||||||
|
rtmpAddr: rtmpAddr, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func JWTMiddleware(next http.Handler) http.Handler { |
||||||
|
isJWT := len(configure.Config.GetString("jwt.secret")) > 0 |
||||||
|
if !isJWT { |
||||||
|
return next |
||||||
|
} |
||||||
|
|
||||||
|
log.Info("Using JWT middleware") |
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||||
|
var algorithm jwt.SigningMethod |
||||||
|
if len(configure.Config.GetString("jwt.algorithm")) > 0 { |
||||||
|
algorithm = jwt.GetSigningMethod(configure.Config.GetString("jwt.algorithm")) |
||||||
|
} |
||||||
|
|
||||||
|
if algorithm == nil { |
||||||
|
algorithm = jwt.SigningMethodHS256 |
||||||
|
} |
||||||
|
|
||||||
|
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ |
||||||
|
Extractor: jwtmiddleware.FromFirst(jwtmiddleware.FromAuthHeader, jwtmiddleware.FromParameter("jwt")), |
||||||
|
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { |
||||||
|
return []byte(configure.Config.GetString("jwt.secret")), nil |
||||||
|
}, |
||||||
|
SigningMethod: algorithm, |
||||||
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) { |
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Status: 403, |
||||||
|
Data: err, |
||||||
|
} |
||||||
|
res.SendJson() |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
jwtMiddleware.HandlerWithNext(w, r, next.ServeHTTP) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Server) Serve(l net.Listener) error { |
||||||
|
mux := http.NewServeMux() |
||||||
|
|
||||||
|
mux.Handle("/statics/", http.StripPrefix("/statics/", http.FileServer(http.Dir("statics")))) |
||||||
|
|
||||||
|
mux.HandleFunc("/control/push", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.handlePush(w, r) |
||||||
|
}) |
||||||
|
mux.HandleFunc("/control/pull", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.handlePull(w, r) |
||||||
|
}) |
||||||
|
mux.HandleFunc("/control/get", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.handleGet(w, r) |
||||||
|
}) |
||||||
|
mux.HandleFunc("/control/reset", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.handleReset(w, r) |
||||||
|
}) |
||||||
|
mux.HandleFunc("/control/delete", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.handleDelete(w, r) |
||||||
|
}) |
||||||
|
mux.HandleFunc("/stat/livestat", func(w http.ResponseWriter, r *http.Request) { |
||||||
|
s.GetLiveStatics(w, r) |
||||||
|
}) |
||||||
|
http.Serve(l, JWTMiddleware(mux)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
type stream struct { |
||||||
|
Key string `json:"key"` |
||||||
|
Url string `json:"url"` |
||||||
|
StreamId uint32 `json:"stream_id"` |
||||||
|
VideoTotalBytes uint64 `json:"video_total_bytes"` |
||||||
|
VideoSpeed uint64 `json:"video_speed"` |
||||||
|
AudioTotalBytes uint64 `json:"audio_total_bytes"` |
||||||
|
AudioSpeed uint64 `json:"audio_speed"` |
||||||
|
} |
||||||
|
|
||||||
|
type streams struct { |
||||||
|
Publishers []stream `json:"publishers"` |
||||||
|
Players []stream `json:"players"` |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/stat/livestat
|
||||||
|
func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http.Request) { |
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
|
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
rtmpStream := server.handler.(*rtmp.RtmpStream) |
||||||
|
if rtmpStream == nil { |
||||||
|
res.Status = 500 |
||||||
|
res.Data = "Get rtmp stream information error" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
msgs := new(streams) |
||||||
|
|
||||||
|
rtmpStream.GetStreams().Range(func(key, val interface{}) bool { |
||||||
|
if s, ok := val.(*rtmp.Stream); ok { |
||||||
|
if s.GetReader() != nil { |
||||||
|
switch s.GetReader().(type) { |
||||||
|
case *rtmp.VirReader: |
||||||
|
v := s.GetReader().(*rtmp.VirReader) |
||||||
|
msg := stream{key.(string), v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS, |
||||||
|
v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS} |
||||||
|
msgs.Publishers = append(msgs.Publishers, msg) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
}) |
||||||
|
|
||||||
|
rtmpStream.GetStreams().Range(func(key, val interface{}) bool { |
||||||
|
ws := val.(*rtmp.Stream).GetWs() |
||||||
|
ws.Range(func(k, v interface{}) bool { |
||||||
|
if pw, ok := v.(*rtmp.PackWriterCloser); ok { |
||||||
|
if pw.GetWriter() != nil { |
||||||
|
switch pw.GetWriter().(type) { |
||||||
|
case *rtmp.VirWriter: |
||||||
|
v := pw.GetWriter().(*rtmp.VirWriter) |
||||||
|
msg := stream{key.(string), v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS, |
||||||
|
v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS} |
||||||
|
msgs.Players = append(msgs.Players, msg) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
}) |
||||||
|
return true |
||||||
|
}) |
||||||
|
|
||||||
|
resp, _ := json.Marshal(msgs) |
||||||
|
res.Data = resp |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
|
||||||
|
func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) { |
||||||
|
var retString string |
||||||
|
var err error |
||||||
|
|
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
|
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
if req.ParseForm() != nil { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
oper := req.Form.Get("oper") |
||||||
|
app := req.Form.Get("app") |
||||||
|
name := req.Form.Get("name") |
||||||
|
url := req.Form.Get("url") |
||||||
|
|
||||||
|
log.Debugf("control pull: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) |
||||||
|
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "control push parameter error, please check them." |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
remoteurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name |
||||||
|
localurl := url |
||||||
|
|
||||||
|
keyString := "pull:" + app + "/" + name |
||||||
|
if oper == "stop" { |
||||||
|
pullRtmprelay, found := s.session[keyString] |
||||||
|
|
||||||
|
if !found { |
||||||
|
retString = fmt.Sprintf("session key[%s] not exist, please check it again.", keyString) |
||||||
|
res.Status = 400 |
||||||
|
res.Data = retString |
||||||
|
return |
||||||
|
} |
||||||
|
log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl) |
||||||
|
pullRtmprelay.Stop() |
||||||
|
|
||||||
|
delete(s.session, keyString) |
||||||
|
retString = fmt.Sprintf("<h1>push url stop %s ok</h1></br>", url) |
||||||
|
res.Status = 400 |
||||||
|
res.Data = retString |
||||||
|
log.Debugf("pull stop return %s", retString) |
||||||
|
} else { |
||||||
|
pullRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) |
||||||
|
log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl) |
||||||
|
err = pullRtmprelay.Start() |
||||||
|
if err != nil { |
||||||
|
retString = fmt.Sprintf("push error=%v", err) |
||||||
|
} else { |
||||||
|
s.session[keyString] = pullRtmprelay |
||||||
|
retString = fmt.Sprintf("<h1>push url start %s ok</h1></br>", url) |
||||||
|
} |
||||||
|
res.Status = 400 |
||||||
|
res.Data = retString |
||||||
|
log.Debugf("pull start return %s", retString) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
|
||||||
|
func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) { |
||||||
|
var retString string |
||||||
|
var err error |
||||||
|
|
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
|
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
if req.ParseForm() != nil { |
||||||
|
res.Data = "url: /control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
oper := req.Form.Get("oper") |
||||||
|
app := req.Form.Get("app") |
||||||
|
name := req.Form.Get("name") |
||||||
|
url := req.Form.Get("url") |
||||||
|
|
||||||
|
log.Debugf("control push: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) |
||||||
|
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { |
||||||
|
res.Data = "control push parameter error, please check them." |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
localurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app + "/" + name |
||||||
|
remoteurl := url |
||||||
|
|
||||||
|
keyString := "push:" + app + "/" + name |
||||||
|
if oper == "stop" { |
||||||
|
pushRtmprelay, found := s.session[keyString] |
||||||
|
if !found { |
||||||
|
retString = fmt.Sprintf("<h1>session key[%s] not exist, please check it again.</h1>", keyString) |
||||||
|
res.Data = retString |
||||||
|
return |
||||||
|
} |
||||||
|
log.Debugf("rtmprelay stop push %s from %s", remoteurl, localurl) |
||||||
|
pushRtmprelay.Stop() |
||||||
|
|
||||||
|
delete(s.session, keyString) |
||||||
|
retString = fmt.Sprintf("<h1>push url stop %s ok</h1></br>", url) |
||||||
|
res.Data = retString |
||||||
|
log.Debugf("push stop return %s", retString) |
||||||
|
} else { |
||||||
|
pushRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) |
||||||
|
log.Debugf("rtmprelay start push %s from %s", remoteurl, localurl) |
||||||
|
err = pushRtmprelay.Start() |
||||||
|
if err != nil { |
||||||
|
retString = fmt.Sprintf("push error=%v", err) |
||||||
|
} else { |
||||||
|
retString = fmt.Sprintf("<h1>push url start %s ok</h1></br>", url) |
||||||
|
s.session[keyString] = pushRtmprelay |
||||||
|
} |
||||||
|
|
||||||
|
res.Data = retString |
||||||
|
log.Debugf("push start return %s", retString) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/control/reset?room=ROOM_NAME
|
||||||
|
func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) { |
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/reset?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
room := r.Form.Get("room") |
||||||
|
|
||||||
|
if len(room) == 0 { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/reset?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
msg, err := configure.RoomKeys.SetKey(room) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
msg = err.Error() |
||||||
|
res.Status = 400 |
||||||
|
} |
||||||
|
|
||||||
|
res.Data = msg |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/control/get?room=ROOM_NAME
|
||||||
|
func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) { |
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/get?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
room := r.Form.Get("room") |
||||||
|
|
||||||
|
if len(room) == 0 { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/get?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
msg, err := configure.RoomKeys.GetKey(room) |
||||||
|
if err != nil { |
||||||
|
msg = err.Error() |
||||||
|
res.Status = 400 |
||||||
|
} |
||||||
|
res.Data = msg |
||||||
|
} |
||||||
|
|
||||||
|
//http://127.0.0.1:8090/control/delete?room=ROOM_NAME
|
||||||
|
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) { |
||||||
|
res := &Response{ |
||||||
|
w: w, |
||||||
|
Data: nil, |
||||||
|
Status: 200, |
||||||
|
} |
||||||
|
defer res.SendJson() |
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/delete?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
room := r.Form.Get("room") |
||||||
|
|
||||||
|
if len(room) == 0 { |
||||||
|
res.Status = 400 |
||||||
|
res.Data = "url: /control/delete?room=<ROOM_NAME>" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if configure.RoomKeys.DeleteChannel(room) { |
||||||
|
res.Data = "Ok" |
||||||
|
return |
||||||
|
} |
||||||
|
res.Status = 404 |
||||||
|
res.Data = "room not found" |
||||||
|
} |
@ -1,239 +0,0 @@ |
|||||||
package httpopera |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay" |
|
||||||
"io" |
|
||||||
"net" |
|
||||||
"net/http" |
|
||||||
"log" |
|
||||||
"github.com/gwuhaolin/livego/av" |
|
||||||
"github.com/gwuhaolin/livego/protocol/rtmp" |
|
||||||
) |
|
||||||
|
|
||||||
type Response struct { |
|
||||||
w http.ResponseWriter |
|
||||||
Status int `json:"status"` |
|
||||||
Message string `json:"message"` |
|
||||||
} |
|
||||||
|
|
||||||
func (r *Response) SendJson() (int, error) { |
|
||||||
resp, _ := json.Marshal(r) |
|
||||||
r.w.Header().Set("Content-Type", "application/json") |
|
||||||
return r.w.Write(resp) |
|
||||||
} |
|
||||||
|
|
||||||
type Operation struct { |
|
||||||
Method string `json:"method"` |
|
||||||
URL string `json:"url"` |
|
||||||
Stop bool `json:"stop"` |
|
||||||
} |
|
||||||
|
|
||||||
type OperationChange struct { |
|
||||||
Method string `json:"method"` |
|
||||||
SourceURL string `json:"source_url"` |
|
||||||
TargetURL string `json:"target_url"` |
|
||||||
Stop bool `json:"stop"` |
|
||||||
} |
|
||||||
|
|
||||||
type ClientInfo struct { |
|
||||||
url string |
|
||||||
rtmpRemoteClient *rtmp.Client |
|
||||||
rtmpLocalClient *rtmp.Client |
|
||||||
} |
|
||||||
|
|
||||||
type Server struct { |
|
||||||
handler av.Handler |
|
||||||
session map[string]*rtmprelay.RtmpRelay |
|
||||||
rtmpAddr string |
|
||||||
} |
|
||||||
|
|
||||||
func NewServer(h av.Handler, rtmpAddr string) *Server { |
|
||||||
return &Server{ |
|
||||||
handler: h, |
|
||||||
session: make(map[string]*rtmprelay.RtmpRelay), |
|
||||||
rtmpAddr: rtmpAddr, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Server) Serve(l net.Listener) error { |
|
||||||
mux := http.NewServeMux() |
|
||||||
|
|
||||||
mux.Handle("/statics", http.FileServer(http.Dir("statics"))) |
|
||||||
|
|
||||||
mux.HandleFunc("/control/push", func(w http.ResponseWriter, r *http.Request) { |
|
||||||
s.handlePush(w, r) |
|
||||||
}) |
|
||||||
mux.HandleFunc("/control/pull", func(w http.ResponseWriter, r *http.Request) { |
|
||||||
s.handlePull(w, r) |
|
||||||
}) |
|
||||||
mux.HandleFunc("/stat/livestat", func(w http.ResponseWriter, r *http.Request) { |
|
||||||
s.GetLiveStatics(w, r) |
|
||||||
}) |
|
||||||
http.Serve(l, mux) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
type stream struct { |
|
||||||
Key string `json:"key"` |
|
||||||
Url string `json:"Url"` |
|
||||||
StreamId uint32 `json:"StreamId"` |
|
||||||
VideoTotalBytes uint64 `json:123456` |
|
||||||
VideoSpeed uint64 `json:123456` |
|
||||||
AudioTotalBytes uint64 `json:123456` |
|
||||||
AudioSpeed uint64 `json:123456` |
|
||||||
} |
|
||||||
|
|
||||||
type streams struct { |
|
||||||
Publishers []stream `json:"publishers"` |
|
||||||
Players []stream `json:"players"` |
|
||||||
} |
|
||||||
|
|
||||||
//http://127.0.0.1:8090/stat/livestat
|
|
||||||
func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http.Request) { |
|
||||||
rtmpStream := server.handler.(*rtmp.RtmpStream) |
|
||||||
if rtmpStream == nil { |
|
||||||
io.WriteString(w, "<h1>Get rtmp stream information error</h1>") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
msgs := new(streams) |
|
||||||
for item := range rtmpStream.GetStreams().IterBuffered() { |
|
||||||
if s, ok := item.Val.(*rtmp.Stream); ok { |
|
||||||
if s.GetReader() != nil { |
|
||||||
switch s.GetReader().(type) { |
|
||||||
case *rtmp.VirReader: |
|
||||||
v := s.GetReader().(*rtmp.VirReader) |
|
||||||
msg := stream{item.Key, v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS, |
|
||||||
v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS} |
|
||||||
msgs.Publishers = append(msgs.Publishers, msg) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for item := range rtmpStream.GetStreams().IterBuffered() { |
|
||||||
ws := item.Val.(*rtmp.Stream).GetWs() |
|
||||||
for s := range ws.IterBuffered() { |
|
||||||
if pw, ok := s.Val.(*rtmp.PackWriterCloser); ok { |
|
||||||
if pw.GetWriter() != nil { |
|
||||||
switch pw.GetWriter().(type) { |
|
||||||
case *rtmp.VirWriter: |
|
||||||
v := pw.GetWriter().(*rtmp.VirWriter) |
|
||||||
msg := stream{item.Key, v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS, |
|
||||||
v.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS} |
|
||||||
msgs.Players = append(msgs.Players, msg) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
resp, _ := json.Marshal(msgs) |
|
||||||
w.Header().Set("Content-Type", "application/json") |
|
||||||
w.Write(resp) |
|
||||||
} |
|
||||||
|
|
||||||
//http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
|
|
||||||
func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) { |
|
||||||
var retString string |
|
||||||
var err error |
|
||||||
|
|
||||||
req.ParseForm() |
|
||||||
|
|
||||||
oper := req.Form["oper"] |
|
||||||
app := req.Form["app"] |
|
||||||
name := req.Form["name"] |
|
||||||
url := req.Form["url"] |
|
||||||
|
|
||||||
log.Printf("control pull: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) |
|
||||||
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { |
|
||||||
io.WriteString(w, "control push parameter error, please check them.</br>") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
remoteurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app[0] + "/" + name[0] |
|
||||||
localurl := url[0] |
|
||||||
|
|
||||||
keyString := "pull:" + app[0] + "/" + name[0] |
|
||||||
if oper[0] == "stop" { |
|
||||||
pullRtmprelay, found := s.session[keyString] |
|
||||||
|
|
||||||
if !found { |
|
||||||
retString = fmt.Sprintf("session key[%s] not exist, please check it again.", keyString) |
|
||||||
io.WriteString(w, retString) |
|
||||||
return |
|
||||||
} |
|
||||||
log.Printf("rtmprelay stop push %s from %s", remoteurl, localurl) |
|
||||||
pullRtmprelay.Stop() |
|
||||||
|
|
||||||
delete(s.session, keyString) |
|
||||||
retString = fmt.Sprintf("<h1>push url stop %s ok</h1></br>", url[0]) |
|
||||||
io.WriteString(w, retString) |
|
||||||
log.Printf("pull stop return %s", retString) |
|
||||||
} else { |
|
||||||
pullRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) |
|
||||||
log.Printf("rtmprelay start push %s from %s", remoteurl, localurl) |
|
||||||
err = pullRtmprelay.Start() |
|
||||||
if err != nil { |
|
||||||
retString = fmt.Sprintf("push error=%v", err) |
|
||||||
} else { |
|
||||||
s.session[keyString] = pullRtmprelay |
|
||||||
retString = fmt.Sprintf("<h1>push url start %s ok</h1></br>", url[0]) |
|
||||||
} |
|
||||||
io.WriteString(w, retString) |
|
||||||
log.Printf("pull start return %s", retString) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456
|
|
||||||
func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) { |
|
||||||
var retString string |
|
||||||
var err error |
|
||||||
|
|
||||||
req.ParseForm() |
|
||||||
|
|
||||||
oper := req.Form["oper"] |
|
||||||
app := req.Form["app"] |
|
||||||
name := req.Form["name"] |
|
||||||
url := req.Form["url"] |
|
||||||
|
|
||||||
log.Printf("control push: oper=%v, app=%v, name=%v, url=%v", oper, app, name, url) |
|
||||||
if (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) { |
|
||||||
io.WriteString(w, "control push parameter error, please check them.</br>") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
localurl := "rtmp://127.0.0.1" + s.rtmpAddr + "/" + app[0] + "/" + name[0] |
|
||||||
remoteurl := url[0] |
|
||||||
|
|
||||||
keyString := "push:" + app[0] + "/" + name[0] |
|
||||||
if oper[0] == "stop" { |
|
||||||
pushRtmprelay, found := s.session[keyString] |
|
||||||
if !found { |
|
||||||
retString = fmt.Sprintf("<h1>session key[%s] not exist, please check it again.</h1>", keyString) |
|
||||||
io.WriteString(w, retString) |
|
||||||
return |
|
||||||
} |
|
||||||
log.Printf("rtmprelay stop push %s from %s", remoteurl, localurl) |
|
||||||
pushRtmprelay.Stop() |
|
||||||
|
|
||||||
delete(s.session, keyString) |
|
||||||
retString = fmt.Sprintf("<h1>push url stop %s ok</h1></br>", url[0]) |
|
||||||
io.WriteString(w, retString) |
|
||||||
log.Printf("push stop return %s", retString) |
|
||||||
} else { |
|
||||||
pushRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl) |
|
||||||
log.Printf("rtmprelay start push %s from %s", remoteurl, localurl) |
|
||||||
err = pushRtmprelay.Start() |
|
||||||
if err != nil { |
|
||||||
retString = fmt.Sprintf("push error=%v", err) |
|
||||||
} else { |
|
||||||
retString = fmt.Sprintf("<h1>push url start %s ok</h1></br>", url[0]) |
|
||||||
s.session[keyString] = pushRtmprelay |
|
||||||
} |
|
||||||
|
|
||||||
io.WriteString(w, retString) |
|
||||||
log.Printf("push start return %s", retString) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,13 @@ |
|||||||
|
package uid |
||||||
|
|
||||||
|
import "math/rand" |
||||||
|
|
||||||
|
var letterRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") |
||||||
|
|
||||||
|
func RandStringRunes(n int) string { |
||||||
|
b := make([]rune, n) |
||||||
|
for i := range b { |
||||||
|
b[i] = letterRunes[rand.Intn(len(letterRunes))] |
||||||
|
} |
||||||
|
return string(b) |
||||||
|
} |
Loading…
Reference in new issue