Browse Source

Merge pull request #1 from gwuhaolin/master

更新
pull/142/head
BerryCol 5 years ago committed by GitHub
parent
commit
02babea932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      .github/workflows/release.yml
  2. 22
      .github/workflows/test.yml
  3. 4
      .gitignore
  4. 16
      .goreleaser.yml
  5. 52
      CHANGELOG.md
  6. 21
      Dockerfile
  7. 21
      LICENSE
  8. 36
      Makefile
  9. 107
      README.md
  10. 67
      README_cn.md
  11. 6
      av/av.go
  12. 6
      av/rwbase.go
  13. 134
      configure/channel.go
  14. 153
      configure/liveconfig.go
  15. 8
      container/flv/demuxer.go
  16. 53
      container/flv/muxer.go
  17. 5
      container/flv/tag.go
  18. 1
      container/ts/muxer.go
  19. 2
      container/ts/muxer_test.go
  20. 19
      go.mod
  21. 200
      go.sum
  22. 11
      goreleaser.yml
  23. 10
      livego.cfg
  24. 130
      livego.go
  25. 21
      livego.yaml
  26. BIN
      logo.png
  27. 143
      main.go
  28. 7
      parser/aac/parser.go
  29. 20
      parser/h264/parser.go
  30. 5
      parser/h264/parser_test.go
  31. 8
      parser/mp3/parser.go
  32. 9
      parser/parser.go
  33. 5
      protocol/amf/amf.go
  34. 9
      protocol/amf/amf_test.go
  35. 43
      protocol/amf/decoder_amf0.go
  36. 63
      protocol/amf/decoder_amf3.go
  37. 20
      protocol/amf/decoder_amf3_external.go
  38. 6
      protocol/amf/decoder_amf3_test.go
  39. 31
      protocol/amf/encoder_amf0.go
  40. 41
      protocol/amf/encoder_amf3.go
  41. 3
      protocol/amf/metadata.go
  42. 9
      protocol/amf/util.go
  43. 425
      protocol/api/api.go
  44. 3
      protocol/hls/cache.go
  45. 50
      protocol/hls/hls.go
  46. 41
      protocol/hls/source.go
  47. 39
      protocol/httpflv/server.go
  48. 34
      protocol/httpflv/writer.go
  49. 239
      protocol/httpopera/http_opera.go
  50. 8
      protocol/rtmp/cache/cache.go
  51. 5
      protocol/rtmp/cache/gop.go
  52. 5
      protocol/rtmp/cache/special.go
  53. 2
      protocol/rtmp/core/chunk_stream.go
  54. 3
      protocol/rtmp/core/chunk_stream_test.go
  55. 3
      protocol/rtmp/core/conn.go
  56. 44
      protocol/rtmp/core/conn_client.go
  57. 17
      protocol/rtmp/core/conn_server.go
  58. 4
      protocol/rtmp/core/conn_test.go
  59. 96
      protocol/rtmp/rtmp.go
  60. 42
      protocol/rtmp/rtmprelay/rtmprelay.go
  61. 46
      protocol/rtmp/rtmprelay/staticrelay.go
  62. 172
      protocol/rtmp/stream.go
  63. 1
      test.go
  64. 1
      utils/queue/queue.go
  65. 13
      utils/uid/rand.go
  66. 3
      utils/uid/uuid.go

27
.github/workflows/release.yml

@ -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 }}

22
.github/workflows/test.yml

@ -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 ./...

4
.gitignore vendored

@ -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

16
.goreleaser.yml

@ -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

52
CHANGELOG.md

@ -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.

21
Dockerfile

@ -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"]

21
LICENSE

@ -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.

36
Makefile

@ -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)

107
README.md

@ -1,40 +1,67 @@
# livego <p align='center'>
简单高效的直播服务器: <img src='./logo.png' width='200px' height='80px'/>
- 安装和使用非常简单; </p>
- 纯 Golang 编写,性能高,跨平台;
- 支持常用的传输协议、文件格式、编码格式; [中文](./README_cn.md)
#### 支持的传输协议 [![Test](https://github.com/gwuhaolin/livego/workflows/Test/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest)
- [x] RTMP [![Release](https://github.com/gwuhaolin/livego/workflows/Release/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease)
- [x] AMF
- [x] HLS Simple and efficient live broadcast server:
- [x] HTTP-FLV - Very simple to install and use;
- Pure Golang, high performance, and cross-platform;
#### 支持的容器格式 - Supports commonly used transmission protocols, file formats, and encoding formats;
- [x] FLV
- [x] TS #### Supported transport protocols
- RTMP
#### 支持的编码格式 - AMF
- [x] H264 - HLS
- [x] AAC - HTTP-FLV
- [x] MP3
#### Supported container formats
## 安装 - FLV
直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后,在命令行中执行。 - TS
#### 从源码编译 #### Supported encoding formats
1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git` - H264
2. 去 livego 目录中 执行 `go build` - AAC
- MP3
## 使用
2. 启动服务:执行 `livego` 二进制文件启动 livego 服务; ## Installation
3. 上行推流:通过 `RTMP` 协议把视频流推送到 `rtmp://localhost:1935/live/movie`,例如使用 `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/live/movie` 推送; After directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line.
4. 下行播放:支持以下三种播放协议,播放地址如下:
- `RTMP`:`rtmp://localhost:1935/live/movie` #### Boot from Docker
- `FLV`:`http://127.0.0.1:7001/live/movie.flv` Run `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start
- `HLS`:`http://127.0.0.1:7002/live/movie.m3u8`
#### Compile from source
1. Download the source code `git clone https://github.com/gwuhaolin/livego.git`
### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3) 2. Go to the livego directory and execute `go build` or `make build`
对Golang感兴趣?请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/) ## Use
1. Start the service: execute the livego binary file or `make run` to start the livego service;
2. Get a channelkey(used for push the video stream) from `http://localhost:8090/control/get?room=movie` and copy data like your channelkey.
3. Upstream push: Push the video stream to `rtmp://localhost:1935/{appname}/{channelkey}` through the` RTMP` protocol(default appname is `live`), for example, use `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` push([download demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv));
4. Downstream playback: The following three playback protocols are supported, and the playback address is as follows:
- `RTMP`:`rtmp://localhost:1935/{appname}/movie`
- `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`
- `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`
all options:
```bash
./livego -h
Usage of ./livego:
--api_addr string HTTP manage interface server listen address (default ":8090")
--config_file string configure filename (default "livego.yaml")
--flv_dir string output flv file at flvDir/APP/KEY_TIME.flv (default "tmp")
--gop_num int gop num (default 1)
--hls_addr string HLS server listen address (default ":7002")
--hls_keep_after_end Maintains the HLS after the stream ends
--httpflv_addr string HTTP-FLV server listen address (default ":7001")
--level string Log level (default "info")
--read_timeout int read time out (default 10)
--rtmp_addr string RTMP server listen address
```
### [Use with flv.js](https://github.com/gwuhaolin/blog/issues/3)
Interested in Golang? Please see [Golang Chinese Learning Materials Summary](http://go.wuhaolin.cn/)

67
README_cn.md

@ -0,0 +1,67 @@
<p align='center'>
<img src='./logo.png' width='200px' height='80px'/>
</p>
[![Test](https://github.com/gwuhaolin/livego/workflows/Test/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest)
[![Release](https://github.com/gwuhaolin/livego/workflows/Release/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease)
简单高效的直播服务器:
- 安装和使用非常简单;
- 纯 Golang 编写,性能高,跨平台;
- 支持常用的传输协议、文件格式、编码格式;
#### 支持的传输协议
- RTMP
- AMF
- HLS
- HTTP-FLV
#### 支持的容器格式
- FLV
- TS
#### 支持的编码格式
- H264
- AAC
- MP3
## 安装
直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后,在命令行中执行。
#### 从 Docker 启动
执行`docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego`启动
#### 从源码编译
1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git`
2. 去 livego 目录中 执行 `go build`
## 使用
1. 启动服务:执行 `livego` 二进制文件启动 livego 服务;
2. 访问 `http://localhost:8090/control/get?room=movie` 获取一个房间的 channelkey(channelkey用于推流,movie用于播放).
3. 推流: 通过`RTMP`协议推送视频流到地址 `rtmp://localhost:1935/{appname}/{channelkey}` (appname默认是`live`), 例如: 使用 `ffmpeg -re -i demo.flv -c copy -f flv rtmp://localhost:1935/{appname}/{channelkey}` 推流([下载demo flv](https://s3plus.meituan.net/v1/mss_7e425c4d9dcb4bb4918bbfa2779e6de1/mpack/default/demo.flv));
4. 播放: 支持多种播放协议,播放地址如下:
- `RTMP`:`rtmp://localhost:1935/{appname}/movie`
- `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`
- `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`
所有配置项:
```bash
./livego -h
Usage of ./livego:
--api_addr string HTTP管理访问监听地址 (default ":8090")
--config_file string 配置文件路径 (默认 "livego.yaml")
--flv_dir string 输出的 flv 文件路径 flvDir/APP/KEY_TIME.flv (默认 "tmp")
--gop_num int gop 数量 (default 1)
--hls_addr string HLS 服务监听地址 (默认 ":7002")
--hls_keep_after_end Maintains the HLS after the stream ends
--httpflv_addr string HTTP-FLV server listen address (默认 ":7001")
--level string 日志等级 (默认 "info")
--read_timeout int 读超时时间 (默认 10)
--rtmp_addr string RTMP 服务监听地址 (默认 ":1935")
--write_timeout int 写超时时间 (默认 10)
```
### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3)
对Golang感兴趣?请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/)

6
av/av.go

@ -1,7 +1,9 @@
package av package av
import "io" import (
import "fmt" "fmt"
"io"
)
const ( const (
TAG_AUDIO = 8 TAG_AUDIO = 8

6
av/rwbase.go

@ -1,7 +1,9 @@
package av package av
import "time" import (
import "sync" "sync"
"time"
)
type RWBaser struct { type RWBaser struct {
lock sync.Mutex lock sync.Mutex

134
configure/channel.go

@ -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
}

153
configure/liveconfig.go

@ -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"`
} }
var RtmpServercfg ServerCfg // 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,
}},
}
func LoadConfig(configfilename string) error { var Config = viper.New()
log.Printf("starting load configure file(%s)......", configfilename)
data, err := ioutil.ReadFile(configfilename) func initLog() {
if err != nil { if l, err := log.ParseLevel(Config.GetString("level")); err == nil {
log.Printf("ReadFile %s error:%v", configfilename, err) log.SetLevel(l)
return err log.SetReportCaller(l == log.DebugLevel)
}
} }
log.Printf("loadconfig: \r\n%s", string(data)) func init() {
defer Init()
err = json.Unmarshal(data, &RtmpServercfg) // Default config
b, _ := json.Marshal(defaultConf)
defaultConfig := bytes.NewReader(b)
viper.SetConfigType("json")
viper.ReadConfig(defaultConfig)
Config.MergeConfigMap(viper.AllSettings())
// 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
} }

8
container/flv/demuxer.go

@ -1,12 +1,12 @@
package flv package flv
import ( import (
"errors" "fmt"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )
var ( var (
ErrAvcEndSEQ = errors.New("avc end sequence") ErrAvcEndSEQ = fmt.Errorf("avc end sequence")
) )
type Demuxer struct { type Demuxer struct {
@ -18,7 +18,7 @@ func NewDemuxer() *Demuxer {
func (d *Demuxer) DemuxH(p *av.Packet) error { func (d *Demuxer) DemuxH(p *av.Packet) error {
var tag Tag var tag Tag
_, err := tag.ParseMeidaTagHeader(p.Data, p.IsVideo) _, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
if err != nil { if err != nil {
return err return err
} }
@ -29,7 +29,7 @@ func (d *Demuxer) DemuxH(p *av.Packet) error {
func (d *Demuxer) Demux(p *av.Packet) error { func (d *Demuxer) Demux(p *av.Packet) error {
var tag Tag var tag Tag
n, err := tag.ParseMeidaTagHeader(p.Data, p.IsVideo) n, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
if err != nil { if err != nil {
return err return err
} }

53
container/flv/muxer.go

@ -1,33 +1,37 @@
package flv package flv
import ( import (
"fmt"
"os"
"path"
"strings" "strings"
"time" "time"
"flag"
"os"
"log"
"github.com/gwuhaolin/livego/utils/uid"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/uid"
log "github.com/sirupsen/logrus"
) )
var ( var (
flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09} flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}
flvFile = flag.String("filFile", "./out.flv", "output flv file name")
) )
/*
func NewFlv(handler av.Handler, info av.Info) { func NewFlv(handler av.Handler, info av.Info) {
patths := strings.SplitN(info.Key, "/", 2) patths := strings.SplitN(info.Key, "/", 2)
if len(patths) != 2 { if len(patths) != 2 {
log.Println("invalid info") log.Warning("invalid info")
return return
} }
w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755) w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755)
if err != nil { if err != nil {
log.Println("open file error: ", err) log.Error("open file error: ", err)
} }
writer := NewFLVWriter(patths[0], patths[1], info.URL, w) writer := NewFLVWriter(patths[0], patths[1], info.URL, w)
@ -36,9 +40,10 @@ func NewFlv(handler av.Handler, info av.Info) {
writer.Wait() writer.Wait()
// close flv file // close flv file
log.Println("close flv file") log.Debug("close flv file")
writer.ctx.Close() writer.ctx.Close()
} }
*/
const ( const (
headerLen = 11 headerLen = 11
@ -136,3 +141,33 @@ func (writer *FLVWriter) Info() (ret av.Info) {
ret.Key = writer.app + "/" + writer.title ret.Key = writer.app + "/" + writer.title
return return
} }
type FlvDvr struct{}
func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser {
paths := strings.SplitN(info.Key, "/", 2)
if len(paths) != 2 {
log.Warning("invalid info")
return nil
}
flvDir := configure.Config.GetString("flv_dir")
err := os.MkdirAll(path.Join(flvDir, paths[0]), 0755)
if err != nil {
log.Error("mkdir error: ", err)
return nil
}
fileName := fmt.Sprintf("%s_%d.%s", path.Join(flvDir, info.Key), time.Now().Unix(), "flv")
log.Debug("flv dvr save stream to: ", fileName)
w, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
log.Error("open file error: ", err)
return nil
}
writer := NewFLVWriter(paths[0], paths[1], info.URL, w)
log.Debug("new flv dvr: ", writer.Info())
return writer
}

5
container/flv/tag.go

@ -2,6 +2,7 @@ package flv
import ( import (
"fmt" "fmt"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )
@ -130,8 +131,8 @@ func (tag *Tag) CompositionTime() int32 {
return tag.mediat.compositionTime return tag.mediat.compositionTime
} }
// ParseMeidaTagHeader, parse video, audio, tag header // ParseMediaTagHeader, parse video, audio, tag header
func (tag *Tag) ParseMeidaTagHeader(b []byte, isVideo bool) (n int, err error) { func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) {
switch isVideo { switch isVideo {
case false: case false:
n, err = tag.parseAudioHeader(b) n, err = tag.parseAudioHeader(b)

1
container/ts/muxer.go

@ -2,6 +2,7 @@ package ts
import ( import (
"io" "io"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )

2
container/ts/muxer_test.go

@ -2,7 +2,9 @@ package ts
import ( import (
"testing" "testing"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

19
go.mod

@ -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
)

200
go.sum

@ -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=

11
goreleaser.yml

@ -1,11 +0,0 @@
build:
binary: livego
goos:
- windows
- darwin
- linux
- freebsd
goarch:
- amd64
- 386
- arm

10
livego.cfg

@ -1,10 +0,0 @@
{
"server": [
{
"appname":"live",
"liveon":"on",
"hlson":"on"
}
]
}

130
livego.go

@ -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)
}

21
livego.yaml

@ -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

BIN
logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

143
main.go

@ -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)
}

7
parser/aac/parser.go

@ -1,8 +1,9 @@
package aac package aac
import ( import (
"errors" "fmt"
"io" "io"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )
@ -25,8 +26,8 @@ type mpegCfgInfo struct {
var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350} var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}
var ( var (
specificBufInvalid = errors.New("audio mpegspecific error") specificBufInvalid = fmt.Errorf("audio mpegspecific error")
audioBufInvalid = errors.New("audiodata invalid") audioBufInvalid = fmt.Errorf("audiodata invalid")
) )
const ( const (

20
parser/h264/parser.go

@ -2,7 +2,7 @@ package h264
import ( import (
"bytes" "bytes"
"errors" "fmt"
"io" "io"
) )
@ -34,14 +34,14 @@ const (
) )
var ( var (
decDataNil = errors.New("dec buf is nil") decDataNil = fmt.Errorf("dec buf is nil")
spsDataError = errors.New("sps data error") spsDataError = fmt.Errorf("sps data error")
ppsHeaderError = errors.New("pps header error") ppsHeaderError = fmt.Errorf("pps header error")
ppsDataError = errors.New("pps data error") ppsDataError = fmt.Errorf("pps data error")
naluHeaderInvalid = errors.New("nalu header invalid") naluHeaderInvalid = fmt.Errorf("nalu header invalid")
videoDataInvalid = errors.New("video data not match") videoDataInvalid = fmt.Errorf("video data not match")
dataSizeNotMatch = errors.New("data size not match") dataSizeNotMatch = fmt.Errorf("data size not match")
naluBodyLenError = errors.New("nalu body len error") naluBodyLenError = fmt.Errorf("nalu body len error")
) )
var startCode = []byte{0x00, 0x00, 0x00, 0x01} var startCode = []byte{0x00, 0x00, 0x00, 0x01}
@ -132,7 +132,7 @@ func (parser *Parser) isNaluHeader(src []byte) bool {
func (parser *Parser) naluSize(src []byte) (int, error) { func (parser *Parser) naluSize(src []byte) (int, error) {
if len(src) < naluBytesLen { if len(src) < naluBytesLen {
return 0, errors.New("nalusizedata invalid") return 0, fmt.Errorf("nalusizedata invalid")
} }
buf := src[:naluBytesLen] buf := src[:naluBytesLen]
size := int(0) size := int(0)

5
parser/h264/parser_test.go

@ -2,8 +2,9 @@ package h264
import ( import (
"bytes" "bytes"
"errors" "fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -46,7 +47,7 @@ func TestH264NalueSizeException(t *testing.T) {
d := NewParser() d := NewParser()
w := bytes.NewBuffer(nil) w := bytes.NewBuffer(nil)
err := d.Parse(nalu, false, w) err := d.Parse(nalu, false, w)
at.Equal(err, errors.New("video data not match")) at.Equal(err, fmt.Errorf("video data not match"))
} }
func TestH264Mp4Demux(t *testing.T) { func TestH264Mp4Demux(t *testing.T) {

8
parser/mp3/parser.go

@ -1,6 +1,8 @@
package mp3 package mp3
import "errors" import (
"fmt"
)
type Parser struct { type Parser struct {
samplingFrequency int samplingFrequency int
@ -17,8 +19,8 @@ func NewParser() *Parser {
// '11' reserved // '11' reserved
var mp3Rates = []int{44100, 48000, 32000} var mp3Rates = []int{44100, 48000, 32000}
var ( var (
errMp3DataInvalid = errors.New("mp3data invalid") errMp3DataInvalid = fmt.Errorf("mp3data invalid")
errIndexInvalid = errors.New("invalid rate index") errIndexInvalid = fmt.Errorf("invalid rate index")
) )
func (parser *Parser) Parse(src []byte) error { func (parser *Parser) Parse(src []byte) error {

9
parser/parser.go

@ -1,16 +1,17 @@
package parser package parser
import ( import (
"errors" "fmt"
"io" "io"
"github.com/gwuhaolin/livego/parser/mp3"
"github.com/gwuhaolin/livego/parser/aac"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/parser/aac"
"github.com/gwuhaolin/livego/parser/h264" "github.com/gwuhaolin/livego/parser/h264"
"github.com/gwuhaolin/livego/parser/mp3"
) )
var ( var (
errNoAudio = errors.New("demuxer no audio") errNoAudio = fmt.Errorf("demuxer no audio")
) )
type CodecParser struct { type CodecParser struct {

5
protocol/amf/amf.go

@ -1,7 +1,6 @@
package amf package amf
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
) )
@ -26,7 +25,7 @@ func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {
return d.DecodeAmf3(r) return d.DecodeAmf3(r)
} }
return nil, errors.New(fmt.Sprintf("decode amf: unsupported version %d", ver)) return nil, fmt.Errorf("decode amf: unsupported version %d", ver)
} }
func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) { func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) {
@ -46,5 +45,5 @@ func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error)
return e.EncodeAmf3(w, val) return e.EncodeAmf3(w, val)
} }
return 0, Error("encode amf: unsupported version %d", ver) return 0, fmt.Errorf("encode amf: unsupported version %d", ver)
} }

9
protocol/amf/amf_test.go

@ -2,7 +2,6 @@ package amf
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
@ -17,12 +16,12 @@ func EncodeAndDecode(val interface{}, ver Version) (result interface{}, err erro
_, err = enc.Encode(buf, val, ver) _, err = enc.Encode(buf, val, ver)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("error in encode: %s", err)) return nil, fmt.Errorf("error in encode: %s", err)
} }
result, err = dec.Decode(buf, ver) result, err = dec.Decode(buf, ver)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("error in decode: %s", err)) return nil, fmt.Errorf("error in decode: %s", err)
} }
return return
@ -108,7 +107,7 @@ func TestAmf0Array(t *testing.T) {
res, err := EncodeAndDecode(arr, 0) res, err := EncodeAndDecode(arr, 0)
if err != nil { if err != nil {
t.Error("amf0 object: %s", err) t.Errorf("amf0 object: %s", err)
} }
result, ok := res.(Array) result, ok := res.(Array)
@ -170,7 +169,7 @@ func TestAmf3Array(t *testing.T) {
res, err := EncodeAndDecode(arr, 3) res, err := EncodeAndDecode(arr, 3)
if err != nil { if err != nil {
t.Error("amf3 object: %s", err) t.Errorf("amf3 object: %s", err)
} }
result, ok := res.(Array) result, ok := res.(Array)

43
protocol/amf/decoder_amf0.go

@ -2,6 +2,7 @@ package amf
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
) )
@ -22,13 +23,13 @@ func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
case AMF0_OBJECT_MARKER: case AMF0_OBJECT_MARKER:
return d.DecodeAmf0Object(r, false) return d.DecodeAmf0Object(r, false)
case AMF0_MOVIECLIP_MARKER: case AMF0_MOVIECLIP_MARKER:
return nil, Error("decode amf0: unsupported type movieclip") return nil, fmt.Errorf("decode amf0: unsupported type movieclip")
case AMF0_NULL_MARKER: case AMF0_NULL_MARKER:
return d.DecodeAmf0Null(r, false) return d.DecodeAmf0Null(r, false)
case AMF0_UNDEFINED_MARKER: case AMF0_UNDEFINED_MARKER:
return d.DecodeAmf0Undefined(r, false) return d.DecodeAmf0Undefined(r, false)
case AMF0_REFERENCE_MARKER: case AMF0_REFERENCE_MARKER:
return nil, Error("decode amf0: unsupported type reference") return nil, fmt.Errorf("decode amf0: unsupported type reference")
case AMF0_ECMA_ARRAY_MARKER: case AMF0_ECMA_ARRAY_MARKER:
return d.DecodeAmf0EcmaArray(r, false) return d.DecodeAmf0EcmaArray(r, false)
case AMF0_STRICT_ARRAY_MARKER: case AMF0_STRICT_ARRAY_MARKER:
@ -40,7 +41,7 @@ func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
case AMF0_UNSUPPORTED_MARKER: case AMF0_UNSUPPORTED_MARKER:
return d.DecodeAmf0Unsupported(r, false) return d.DecodeAmf0Unsupported(r, false)
case AMF0_RECORDSET_MARKER: case AMF0_RECORDSET_MARKER:
return nil, Error("decode amf0: unsupported type recordset") return nil, fmt.Errorf("decode amf0: unsupported type recordset")
case AMF0_XML_DOCUMENT_MARKER: case AMF0_XML_DOCUMENT_MARKER:
return d.DecodeAmf0XmlDocument(r, false) return d.DecodeAmf0XmlDocument(r, false)
case AMF0_TYPED_OBJECT_MARKER: case AMF0_TYPED_OBJECT_MARKER:
@ -49,7 +50,7 @@ func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
return d.DecodeAmf3(r) return d.DecodeAmf3(r)
} }
return nil, Error("decode amf0: unsupported type %d", marker) return nil, fmt.Errorf("decode amf0: unsupported type %d", marker)
} }
// marker: 1 byte 0x00 // marker: 1 byte 0x00
@ -61,7 +62,7 @@ func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float
err = binary.Read(r, binary.BigEndian, &result) err = binary.Read(r, binary.BigEndian, &result)
if err != nil { if err != nil {
return float64(0), Error("amf0 decode: unable to read number: %s", err) return float64(0), fmt.Errorf("amf0 decode: unable to read number: %s", err)
} }
return return
@ -85,7 +86,7 @@ func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool
return true, nil return true, nil
} }
return false, Error("decode amf0: unexpected value %v for boolean", b) return false, fmt.Errorf("decode amf0: unexpected value %v for boolean", b)
} }
// marker: 1 byte 0x02 // marker: 1 byte 0x02
@ -100,12 +101,12 @@ func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result strin
var length uint16 var length uint16
err = binary.Read(r, binary.BigEndian, &length) err = binary.Read(r, binary.BigEndian, &length)
if err != nil { if err != nil {
return "", Error("decode amf0: unable to decode string length: %s", err) return "", fmt.Errorf("decode amf0: unable to decode string length: %s", err)
} }
var bytes = make([]byte, length) var bytes = make([]byte, length)
if bytes, err = ReadBytes(r, int(length)); err != nil { if bytes, err = ReadBytes(r, int(length)); err != nil {
return "", Error("decode amf0: unable to decode string value: %s", err) return "", fmt.Errorf("decode amf0: unable to decode string value: %s", err)
} }
return string(bytes), nil return string(bytes), nil
@ -131,7 +132,7 @@ func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, erro
if key == "" { if key == "" {
if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil { if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {
return nil, Error("decode amf0: expected object end marker: %s", err) return nil, fmt.Errorf("decode amf0: expected object end marker: %s", err)
} }
break break
@ -139,7 +140,7 @@ func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, erro
value, err := d.DecodeAmf0(r) value, err := d.DecodeAmf0(r)
if err != nil { if err != nil {
return nil, Error("decode amf0: unable to decode object value: %s", err) return nil, fmt.Errorf("decode amf0: unable to decode object value: %s", err)
} }
result[key] = value result[key] = value
@ -176,11 +177,11 @@ func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface
err = binary.Read(r, binary.BigEndian, &ref) err = binary.Read(r, binary.BigEndian, &ref)
if err != nil { if err != nil {
return nil, Error("decode amf0: unable to decode reference id: %s", err) return nil, fmt.Errorf("decode amf0: unable to decode reference id: %s", err)
} }
if int(ref) > len(d.refCache) { if int(ref) > len(d.refCache) {
return nil, Error("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache)) return nil, fmt.Errorf("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache))
} }
result := d.refCache[ref] result := d.refCache[ref]
@ -205,7 +206,7 @@ func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (Object, e
result, err := d.DecodeAmf0Object(r, false) result, err := d.DecodeAmf0Object(r, false)
if err != nil { if err != nil {
return nil, Error("decode amf0: unable to decode ecma array object: %s", err) return nil, fmt.Errorf("decode amf0: unable to decode ecma array object: %s", err)
} }
return result, nil return result, nil
@ -223,7 +224,7 @@ func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result
var length uint32 var length uint32
err = binary.Read(r, binary.BigEndian, &length) err = binary.Read(r, binary.BigEndian, &length)
if err != nil { if err != nil {
return nil, Error("decode amf0: unable to decode strict array length: %s", err) return nil, fmt.Errorf("decode amf0: unable to decode strict array length: %s", err)
} }
d.refCache = append(d.refCache, result) d.refCache = append(d.refCache, result)
@ -231,7 +232,7 @@ func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result
for i := uint32(0); i < length; i++ { for i := uint32(0); i < length; i++ {
tmp, err := d.DecodeAmf0(r) tmp, err := d.DecodeAmf0(r)
if err != nil { if err != nil {
return nil, Error("decode amf0: unable to decode strict array object: %s", err) return nil, fmt.Errorf("decode amf0: unable to decode strict array object: %s", err)
} }
result = append(result, tmp) result = append(result, tmp)
} }
@ -250,11 +251,11 @@ func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64
} }
if result, err = d.DecodeAmf0Number(r, false); err != nil { if result, err = d.DecodeAmf0Number(r, false); err != nil {
return float64(0), Error("decode amf0: unable to decode float in date: %s", err) return float64(0), fmt.Errorf("decode amf0: unable to decode float in date: %s", err)
} }
if _, err = ReadBytes(r, 2); err != nil { if _, err = ReadBytes(r, 2); err != nil {
return float64(0), Error("decode amf0: unable to read 2 trail bytes in date: %s", err) return float64(0), fmt.Errorf("decode amf0: unable to read 2 trail bytes in date: %s", err)
} }
return return
@ -272,12 +273,12 @@ func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result s
var length uint32 var length uint32
err = binary.Read(r, binary.BigEndian, &length) err = binary.Read(r, binary.BigEndian, &length)
if err != nil { if err != nil {
return "", Error("decode amf0: unable to decode long string length: %s", err) return "", fmt.Errorf("decode amf0: unable to decode long string length: %s", err)
} }
var bytes = make([]byte, length) var bytes = make([]byte, length)
if bytes, err = ReadBytes(r, int(length)); err != nil { if bytes, err = ReadBytes(r, int(length)); err != nil {
return "", Error("decode amf0: unable to decode long string value: %s", err) return "", fmt.Errorf("decode amf0: unable to decode long string value: %s", err)
} }
return string(bytes), nil return string(bytes), nil
@ -323,12 +324,12 @@ func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (TypedOb
result.Type, err = d.DecodeAmf0String(r, false) result.Type, err = d.DecodeAmf0String(r, false)
if err != nil { if err != nil {
return result, Error("decode amf0: typed object unable to determine type: %s", err) return result, fmt.Errorf("decode amf0: typed object unable to determine type: %s", err)
} }
result.Object, err = d.DecodeAmf0Object(r, false) result.Object, err = d.DecodeAmf0Object(r, false)
if err != nil { if err != nil {
return result, Error("decode amf0: typed object unable to determine object: %s", err) return result, fmt.Errorf("decode amf0: typed object unable to determine object: %s", err)
} }
return result, nil return result, nil

63
protocol/amf/decoder_amf3.go

@ -2,6 +2,7 @@ package amf
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"time" "time"
) )
@ -42,7 +43,7 @@ func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) {
return d.DecodeAmf3ByteArray(r, false) return d.DecodeAmf3ByteArray(r, false)
} }
return nil, Error("decode amf3: unsupported type %d", marker) return nil, fmt.Errorf("decode amf3: unsupported type %d", marker)
} }
// marker: 1 byte 0x00 // marker: 1 byte 0x00
@ -103,7 +104,7 @@ func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (result float
err = binary.Read(r, binary.BigEndian, &result) err = binary.Read(r, binary.BigEndian, &result)
if err != nil { if err != nil {
return float64(0), Error("amf3 decode: unable to read double: %s", err) return float64(0), fmt.Errorf("amf3 decode: unable to read double: %s", err)
} }
return return
@ -122,7 +123,7 @@ func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result strin
var refVal uint32 var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r) isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil { if err != nil {
return "", Error("amf3 decode: unable to decode string reference and length: %s", err) return "", fmt.Errorf("amf3 decode: unable to decode string reference and length: %s", err)
} }
if isRef { if isRef {
@ -133,7 +134,7 @@ func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result strin
buf := make([]byte, refVal) buf := make([]byte, refVal)
_, err = r.Read(buf) _, err = r.Read(buf)
if err != nil { if err != nil {
return "", Error("amf3 decode: unable to read string: %s", err) return "", fmt.Errorf("amf3 decode: unable to read string: %s", err)
} }
result = string(buf) result = string(buf)
@ -157,13 +158,13 @@ func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Ti
var refVal uint32 var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r) isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode date reference and length: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode date reference and length: %s", err)
} }
if isRef { if isRef {
res, ok := d.objectRefs[refVal].(time.Time) res, ok := d.objectRefs[refVal].(time.Time)
if ok != true { if ok != true {
return result, Error("amf3 decode: unable to extract time from date object references") return result, fmt.Errorf("amf3 decode: unable to extract time from date object references")
} }
return res, err return res, err
@ -172,7 +173,7 @@ func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Ti
var u64 float64 var u64 float64
err = binary.Read(r, binary.BigEndian, &u64) err = binary.Read(r, binary.BigEndian, &u64)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to read double: %s", err) return result, fmt.Errorf("amf3 decode: unable to read double: %s", err)
} }
result = time.Unix(int64(u64/1000), 0).UTC() result = time.Unix(int64(u64/1000), 0).UTC()
@ -196,7 +197,7 @@ func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array,
var refVal uint32 var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r) isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode array reference and length: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode array reference and length: %s", err)
} }
if isRef { if isRef {
@ -204,7 +205,7 @@ func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array,
res, ok := d.objectRefs[objRefId].(Array) res, ok := d.objectRefs[objRefId].(Array)
if ok != true { if ok != true {
return result, Error("amf3 decode: unable to extract array from object references") return result, fmt.Errorf("amf3 decode: unable to extract array from object references")
} }
return res, err return res, err
@ -213,17 +214,17 @@ func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array,
var key string var key string
key, err = d.DecodeAmf3String(r, false) key, err = d.DecodeAmf3String(r, false)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to read key for array: %s", err) return result, fmt.Errorf("amf3 decode: unable to read key for array: %s", err)
} }
if key != "" { if key != "" {
return result, Error("amf3 decode: array key is not empty, can't handle associative array") return result, fmt.Errorf("amf3 decode: array key is not empty, can't handle associative array")
} }
for i := uint32(0); i < refVal; i++ { for i := uint32(0); i < refVal; i++ {
tmp, err := d.DecodeAmf3(r) tmp, err := d.DecodeAmf3(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: array element could not be decoded: %s", err) return result, fmt.Errorf("amf3 decode: array element could not be decoded: %s", err)
} }
result = append(result, tmp) result = append(result, tmp)
} }
@ -243,7 +244,7 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
// decode the initial u29 // decode the initial u29
isRef, refVal, err := d.decodeReferenceInt(r) isRef, refVal, err := d.decodeReferenceInt(r)
if err != nil { if err != nil {
return nil, Error("amf3 decode: unable to decode object reference and length: %s", err) return nil, fmt.Errorf("amf3 decode: unable to decode object reference and length: %s", err)
} }
// if this is a object reference only, grab it and return it // if this is a object reference only, grab it and return it
@ -272,7 +273,7 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
var cls string var cls string
cls, err = d.DecodeAmf3String(r, false) cls, err = d.DecodeAmf3String(r, false)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to read trait type for object: %s", err) return result, fmt.Errorf("amf3 decode: unable to read trait type for object: %s", err)
} }
trait.Type = cls trait.Type = cls
@ -281,7 +282,7 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
for i := uint32(0); i < propLength; i++ { for i := uint32(0); i < propLength; i++ {
tmp, err := d.DecodeAmf3String(r, false) tmp, err := d.DecodeAmf3String(r, false)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to read trait property for object: %s", err) return result, fmt.Errorf("amf3 decode: unable to read trait property for object: %s", err)
} }
trait.Properties = append(trait.Properties, tmp) trait.Properties = append(trait.Properties, tmp)
} }
@ -299,17 +300,17 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
case "DSA": // AsyncMessageExt case "DSA": // AsyncMessageExt
result, err = d.decodeAsyncMessageExt(r) result, err = d.decodeAsyncMessageExt(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode dsa: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode dsa: %s", err)
} }
case "DSK": // AcknowledgeMessageExt case "DSK": // AcknowledgeMessageExt
result, err = d.decodeAcknowledgeMessageExt(r) result, err = d.decodeAcknowledgeMessageExt(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode dsk: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode dsk: %s", err)
} }
case "flex.messaging.io.ArrayCollection": case "flex.messaging.io.ArrayCollection":
result, err = d.decodeArrayCollection(r) result, err = d.decodeArrayCollection(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode ac: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode ac: %s", err)
} }
// store an extra reference to array collection container // store an extra reference to array collection container
@ -320,10 +321,10 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
if ok { if ok {
result, err = fn(d, r) result, err = fn(d, r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to call external decoder for type %s: %s", trait.Type, err) return result, fmt.Errorf("amf3 decode: unable to call external decoder for type %s: %s", trait.Type, err)
} }
} else { } else {
return result, Error("amf3 decode: unable to decode external type %s, no handler", trait.Type) return result, fmt.Errorf("amf3 decode: unable to decode external type %s, no handler", trait.Type)
} }
} }
@ -341,7 +342,7 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
for _, key = range trait.Properties { for _, key = range trait.Properties {
val, err = d.DecodeAmf3(r) val, err = d.DecodeAmf3(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode object property: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode object property: %s", err)
} }
obj[key] = val obj[key] = val
@ -353,14 +354,14 @@ func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result inter
for { for {
key, err = d.DecodeAmf3String(r, false) key, err = d.DecodeAmf3String(r, false)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode dynamic key: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode dynamic key: %s", err)
} }
if key == "" { if key == "" {
break break
} }
val, err = d.DecodeAmf3(r) val, err = d.DecodeAmf3(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode dynamic value: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode dynamic value: %s", err)
} }
obj[key] = val obj[key] = val
@ -385,7 +386,7 @@ func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string,
} }
if (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) { if (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) {
return "", Error("decode assert marker failed: expected %v or %v, got %v", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker) return "", fmt.Errorf("decode assert marker failed: expected %v or %v, got %v", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker)
} }
} }
@ -393,7 +394,7 @@ func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string,
var refVal uint32 var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r) isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil { if err != nil {
return "", Error("amf3 decode: unable to decode xml reference and length: %s", err) return "", fmt.Errorf("amf3 decode: unable to decode xml reference and length: %s", err)
} }
if isRef { if isRef {
@ -401,7 +402,7 @@ func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string,
buf := d.objectRefs[refVal] buf := d.objectRefs[refVal]
result, ok = buf.(string) result, ok = buf.(string)
if ok != true { if ok != true {
return "", Error("amf3 decode: cannot coerce object reference into xml string") return "", fmt.Errorf("amf3 decode: cannot coerce object reference into xml string")
} }
return return
@ -410,7 +411,7 @@ func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string,
buf := make([]byte, refVal) buf := make([]byte, refVal)
_, err = r.Read(buf) _, err = r.Read(buf)
if err != nil { if err != nil {
return "", Error("amf3 decode: unable to read xml string: %s", err) return "", fmt.Errorf("amf3 decode: unable to read xml string: %s", err)
} }
result = string(buf) result = string(buf)
@ -435,14 +436,14 @@ func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []
var refVal uint32 var refVal uint32
isRef, refVal, err = d.decodeReferenceInt(r) isRef, refVal, err = d.decodeReferenceInt(r)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to decode byte array reference and length: %s", err) return result, fmt.Errorf("amf3 decode: unable to decode byte array reference and length: %s", err)
} }
if isRef { if isRef {
var ok bool var ok bool
result, ok = d.objectRefs[refVal].([]byte) result, ok = d.objectRefs[refVal].([]byte)
if ok != true { if ok != true {
return result, Error("amf3 decode: unable to convert object ref to bytes") return result, fmt.Errorf("amf3 decode: unable to convert object ref to bytes")
} }
return return
@ -451,7 +452,7 @@ func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []
result = make([]byte, refVal) result = make([]byte, refVal)
_, err = r.Read(result) _, err = r.Read(result)
if err != nil { if err != nil {
return result, Error("amf3 decode: unable to read bytearray: %s", err) return result, fmt.Errorf("amf3 decode: unable to read bytearray: %s", err)
} }
d.objectRefs = append(d.objectRefs, result) d.objectRefs = append(d.objectRefs, result)
@ -486,7 +487,7 @@ func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {
func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) { func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) {
u29, err := d.decodeU29(r) u29, err := d.decodeU29(r)
if err != nil { if err != nil {
return false, 0, Error("amf3 decode: unable to decode reference int: %s", err) return false, 0, fmt.Errorf("amf3 decode: unable to decode reference int: %s", err)
} }
isRef = u29&0x01 == 0 isRef = u29&0x01 == 0

20
protocol/amf/decoder_amf3_external.go

@ -13,7 +13,7 @@ func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error)
if err = d.decodeExternal(r, &result, if err = d.decodeExternal(r, &result,
[]string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"}, []string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"},
[]string{"clientIdBytes", "messageIdBytes"}); err != nil { []string{"clientIdBytes", "messageIdBytes"}); err != nil {
return result, Error("unable to decode abstract external: %s", err) return result, fmt.Errorf("unable to decode abstract external: %s", err)
} }
return return
@ -26,11 +26,11 @@ func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error)
func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) { func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) {
result, err = d.decodeAbstractMessage(r) result, err = d.decodeAbstractMessage(r)
if err != nil { if err != nil {
return result, Error("unable to decode abstract for async: %s", err) return result, fmt.Errorf("unable to decode abstract for async: %s", err)
} }
if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil { if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil {
return result, Error("unable to decode async external: %s", err) return result, fmt.Errorf("unable to decode async external: %s", err)
} }
return return
@ -43,11 +43,11 @@ func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err e
func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) { func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) {
result, err = d.decodeAsyncMessage(r) result, err = d.decodeAsyncMessage(r)
if err != nil { if err != nil {
return result, Error("unable to decode async for ack: %s", err) return result, fmt.Errorf("unable to decode async for ack: %s", err)
} }
if err = d.decodeExternal(r, &result); err != nil { if err = d.decodeExternal(r, &result); err != nil {
return result, Error("unable to decode ack external: %s", err) return result, fmt.Errorf("unable to decode ack external: %s", err)
} }
return return
@ -57,7 +57,7 @@ func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err erro
func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) { func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {
result, err := d.DecodeAmf3(r) result, err := d.DecodeAmf3(r)
if err != nil { if err != nil {
return result, Error("cannot decode child of array collection: %s", err) return result, fmt.Errorf("cannot decode child of array collection: %s", err)
} }
return result, nil return result, nil
@ -70,7 +70,7 @@ func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string
flagSet, err = readFlags(r) flagSet, err = readFlags(r)
if err != nil { if err != nil {
return Error("unable to read flags: %s", err) return fmt.Errorf("unable to read flags: %s", err)
} }
for i, flags := range flagSet { for i, flags := range flagSet {
@ -87,7 +87,7 @@ func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string
if (flags & flagBit) != 0 { if (flags & flagBit) != 0 {
tmp, err := d.DecodeAmf3(r) tmp, err := d.DecodeAmf3(r)
if err != nil { if err != nil {
return Error("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err) return fmt.Errorf("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err)
} }
(*obj)[field] = tmp (*obj)[field] = tmp
} }
@ -99,7 +99,7 @@ func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string
field := fmt.Sprintf("extra_%d_%d", i, j) field := fmt.Sprintf("extra_%d_%d", i, j)
tmp, err := d.DecodeAmf3(r) tmp, err := d.DecodeAmf3(r)
if err != nil { if err != nil {
return Error("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err) return fmt.Errorf("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err)
} }
(*obj)[field] = tmp (*obj)[field] = tmp
} }
@ -114,7 +114,7 @@ func readFlags(r io.Reader) (result []uint8, err error) {
for { for {
flag, err := ReadByte(r) flag, err := ReadByte(r)
if err != nil { if err != nil {
return result, Error("unable to read flags: %s", err) return result, fmt.Errorf("unable to read flags: %s", err)
} }
result = append(result, flag) result = append(result, flag)

6
protocol/amf/decoder_amf3_test.go

@ -186,7 +186,7 @@ func TestDecodeAmf3Array(t *testing.T) {
for i, v := range expect { for i, v := range expect {
if got[i] != v { if got[i] != v {
t.Error("expected array element %d to be %v, got %v", i, v, got[i]) t.Errorf("expected array element %d to be %v, got %v", i, v, got[i])
} }
} }
} }
@ -211,10 +211,10 @@ func TestDecodeAmf3Object(t *testing.T) {
} }
if to["foo"] != "bar" { if to["foo"] != "bar" {
t.Error("expected foo to be bar, got: %+v", to["foo"]) t.Errorf("expected foo to be bar, got: %+v", to["foo"])
} }
if to["baz"] != nil { if to["baz"] != nil {
t.Error("expected baz to be nil, got: %+v", to["baz"]) t.Errorf("expected baz to be nil, got: %+v", to["baz"])
} }
} }

31
protocol/amf/encoder_amf0.go

@ -2,6 +2,7 @@ package amf
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"reflect" "reflect"
) )
@ -43,16 +44,16 @@ func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) {
case reflect.Map: case reflect.Map:
obj, ok := val.(Object) obj, ok := val.(Object)
if ok != true { if ok != true {
return 0, Error("encode amf0: unable to create object from map") return 0, fmt.Errorf("encode amf0: unable to create object from map")
} }
return e.EncodeAmf0Object(w, obj, true) return e.EncodeAmf0Object(w, obj, true)
} }
if _, ok := val.(TypedObject); ok { if _, ok := val.(TypedObject); ok {
return 0, Error("encode amf0: unsupported type typed object") return 0, fmt.Errorf("encode amf0: unsupported type typed object")
} }
return 0, Error("encode amf0: unsupported type %s", v.Type()) return 0, fmt.Errorf("encode amf0: unsupported type %s", v.Type())
} }
// marker: 1 byte 0x00 // marker: 1 byte 0x00
@ -117,13 +118,13 @@ func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (
length := uint16(len(val)) length := uint16(len(val))
err = binary.Write(w, binary.BigEndian, length) err = binary.Write(w, binary.BigEndian, length)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode string length: %s", err) return n, fmt.Errorf("encode amf0: unable to encode string length: %s", err)
} }
n += 2 n += 2
m, err = w.Write([]byte(val)) m, err = w.Write([]byte(val))
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode string value: %s", err) return n, fmt.Errorf("encode amf0: unable to encode string value: %s", err)
} }
n += m n += m
@ -146,26 +147,26 @@ func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (
for k, v := range val { for k, v := range val {
m, err = e.EncodeAmf0String(w, k, false) m, err = e.EncodeAmf0String(w, k, false)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode object key: %s", err) return n, fmt.Errorf("encode amf0: unable to encode object key: %s", err)
} }
n += m n += m
m, err = e.EncodeAmf0(w, v) m, err = e.EncodeAmf0(w, v)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode object value: %s", err) return n, fmt.Errorf("encode amf0: unable to encode object value: %s", err)
} }
n += m n += m
} }
m, err = e.EncodeAmf0String(w, "", false) m, err = e.EncodeAmf0String(w, "", false)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode object empty string: %s", err) return n, fmt.Errorf("encode amf0: unable to encode object empty string: %s", err)
} }
n += m n += m
err = WriteMarker(w, AMF0_OBJECT_END_MARKER) err = WriteMarker(w, AMF0_OBJECT_END_MARKER)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to object end marker: %s", err) return n, fmt.Errorf("encode amf0: unable to object end marker: %s", err)
} }
n += 1 n += 1
@ -216,13 +217,13 @@ func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool
length := uint32(len(val)) length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length) err = binary.Write(w, binary.BigEndian, length)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode ecma array length: %s", err) return n, fmt.Errorf("encode amf0: unable to encode ecma array length: %s", err)
} }
n += 4 n += 4
m, err = e.EncodeAmf0Object(w, val, false) m, err = e.EncodeAmf0Object(w, val, false)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode ecma array object: %s", err) return n, fmt.Errorf("encode amf0: unable to encode ecma array object: %s", err)
} }
n += m n += m
@ -245,14 +246,14 @@ func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker boo
length := uint32(len(val)) length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length) err = binary.Write(w, binary.BigEndian, length)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode strict array length: %s", err) return n, fmt.Errorf("encode amf0: unable to encode strict array length: %s", err)
} }
n += 4 n += 4
for _, v := range val { for _, v := range val {
m, err = e.EncodeAmf0(w, v) m, err = e.EncodeAmf0(w, v)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode strict array element: %s", err) return n, fmt.Errorf("encode amf0: unable to encode strict array element: %s", err)
} }
n += m n += m
} }
@ -276,13 +277,13 @@ func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker boo
length := uint32(len(val)) length := uint32(len(val))
err = binary.Write(w, binary.BigEndian, length) err = binary.Write(w, binary.BigEndian, length)
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode long string length: %s", err) return n, fmt.Errorf("encode amf0: unable to encode long string length: %s", err)
} }
n += 4 n += 4
m, err = w.Write([]byte(val)) m, err = w.Write([]byte(val))
if err != nil { if err != nil {
return n, Error("encode amf0: unable to encode long string value: %s", err) return n, fmt.Errorf("encode amf0: unable to encode long string value: %s", err)
} }
n += m n += m

41
protocol/amf/encoder_amf3.go

@ -2,6 +2,7 @@ package amf
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"reflect" "reflect"
"sort" "sort"
@ -59,7 +60,7 @@ func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
case reflect.Map: case reflect.Map:
obj, ok := val.(Object) obj, ok := val.(Object)
if ok != true { if ok != true {
return 0, Error("encode amf3: unable to create object from map") return 0, fmt.Errorf("encode amf3: unable to create object from map")
} }
to := *new(TypedObject) to := *new(TypedObject)
@ -76,7 +77,7 @@ func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
return e.EncodeAmf3Object(w, to, true) return e.EncodeAmf3Object(w, to, true)
} }
return 0, Error("encode amf3: unsupported type %s", v.Type()) return 0, fmt.Errorf("encode amf3: unsupported type %s", v.Type())
} }
// marker: 1 byte 0x00 // marker: 1 byte 0x00
@ -204,14 +205,14 @@ func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarker bool)
} }
if err = WriteMarker(w, 0x01); err != nil { if err = WriteMarker(w, 0x01); err != nil {
return n, Error("amf3 encode: cannot encode u29 for array: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
} }
n += 1 n += 1
u64 := float64(val.Unix()) * 1000.0 u64 := float64(val.Unix()) * 1000.0
err = binary.Write(w, binary.BigEndian, &u64) err = binary.Write(w, binary.BigEndian, &u64)
if err != nil { if err != nil {
return n, Error("amf3 encode: unable to write date double: %s", err) return n, fmt.Errorf("amf3 encode: unable to write date double: %s", err)
} }
n += 8 n += 8
@ -237,20 +238,20 @@ func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker bool) (n
m, err = e.encodeAmf3Uint29(w, u29) m, err = e.encodeAmf3Uint29(w, u29)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode u29 for array: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
} }
n += m n += m
m, err = e.encodeAmf3Utf8(w, "") m, err = e.encodeAmf3Utf8(w, "")
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode empty string for array: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode empty string for array: %s", err)
} }
n += m n += m
for _, v := range val { for _, v := range val {
m, err := e.EncodeAmf3(w, v) m, err := e.EncodeAmf3(w, v)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode array element: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode array element: %s", err)
} }
n += m n += m
} }
@ -294,32 +295,32 @@ func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bo
m, err = e.encodeAmf3Uint29(w, u29) m, err = e.encodeAmf3Uint29(w, u29)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode trait header for object: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode trait header for object: %s", err)
} }
n += m n += m
m, err = e.encodeAmf3Utf8(w, trait.Type) m, err = e.encodeAmf3Utf8(w, trait.Type)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode trait type for object: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode trait type for object: %s", err)
} }
n += m n += m
for _, prop := range trait.Properties { for _, prop := range trait.Properties {
m, err = e.encodeAmf3Utf8(w, prop) m, err = e.encodeAmf3Utf8(w, prop)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode trait property for object: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode trait property for object: %s", err)
} }
n += m n += m
} }
if trait.Externalizable { if trait.Externalizable {
return n, Error("amf3 encode: cannot encode externalizable object") return n, fmt.Errorf("amf3 encode: cannot encode externalizable object")
} }
for _, prop := range trait.Properties { for _, prop := range trait.Properties {
m, err = e.EncodeAmf3(w, val.Object[prop]) m, err = e.EncodeAmf3(w, val.Object[prop])
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode sealed object value: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode sealed object value: %s", err)
} }
n += m n += m
} }
@ -337,20 +338,20 @@ func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bo
if foundProp != true { if foundProp != true {
m, err = e.encodeAmf3Utf8(w, k) m, err = e.encodeAmf3Utf8(w, k)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode dynamic object property key: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode dynamic object property key: %s", err)
} }
n += m n += m
m, err = e.EncodeAmf3(w, v) m, err = e.EncodeAmf3(w, v)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode dynamic object value: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode dynamic object value: %s", err)
} }
n += m n += m
} }
m, err = e.encodeAmf3Utf8(w, "") m, err = e.encodeAmf3Utf8(w, "")
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode dynamic object ending marker string: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode dynamic object ending marker string: %s", err)
} }
n += m n += m
} }
@ -378,13 +379,13 @@ func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMarker bool
m, err = e.encodeAmf3Uint29(w, u29) m, err = e.encodeAmf3Uint29(w, u29)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode u29 for bytearray: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode u29 for bytearray: %s", err)
} }
n += m n += m
m, err = w.Write(val) m, err = w.Write(val)
if err != nil { if err != nil {
return n, Error("encode amf3: unable to encode bytearray value: %s", err) return n, fmt.Errorf("encode amf3: unable to encode bytearray value: %s", err)
} }
n += m n += m
@ -398,13 +399,13 @@ func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err error) {
var m int var m int
m, err = e.encodeAmf3Uint29(w, u29) m, err = e.encodeAmf3Uint29(w, u29)
if err != nil { if err != nil {
return n, Error("amf3 encode: cannot encode u29 for string: %s", err) return n, fmt.Errorf("amf3 encode: cannot encode u29 for string: %s", err)
} }
n += m n += m
m, err = w.Write([]byte(val)) m, err = w.Write([]byte(val))
if err != nil { if err != nil {
return n, Error("encode amf3: unable to encode string value: %s", err) return n, fmt.Errorf("encode amf3: unable to encode string value: %s", err)
} }
n += m n += m
@ -424,7 +425,7 @@ func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err error) {
} else if val <= 0x1FFFFFFF { } else if val <= 0x1FFFFFFF {
n, err = w.Write([]byte{byte(val>>22 | 0x80), byte(val>>15&0x7F | 0x80), byte(val>>8&0x7F | 0x80), byte(val)}) n, err = w.Write([]byte{byte(val>>22 | 0x80), byte(val>>15&0x7F | 0x80), byte(val>>8&0x7F | 0x80), byte(val)})
} else { } else {
return n, Error("amf3 encode: cannot encode u29 with value %d (out of range)", val) return n, fmt.Errorf("amf3 encode: cannot encode u29 with value %d (out of range)", val)
} }
return return

3
protocol/amf/metadata.go

@ -3,7 +3,8 @@ package amf
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
log "github.com/sirupsen/logrus"
) )
const ( const (

9
protocol/amf/util.go

@ -2,7 +2,6 @@ package amf
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
) )
@ -18,17 +17,13 @@ func DumpBytes(label string, buf []byte, size int) {
func Dump(label string, val interface{}) error { func Dump(label string, val interface{}) error {
json, err := json.MarshalIndent(val, "", " ") json, err := json.MarshalIndent(val, "", " ")
if err != nil { if err != nil {
return Error("Error dumping %s: %s", label, err) return fmt.Errorf("Error dumping %s: %s", label, err)
} }
fmt.Printf("Dumping %s:\n%s\n", label, json) fmt.Printf("Dumping %s:\n%s\n", label, json)
return nil return nil
} }
func Error(f string, v ...interface{}) error {
return errors.New(fmt.Sprintf(f, v...))
}
func WriteByte(w io.Writer, b byte) (err error) { func WriteByte(w io.Writer, b byte) (err error) {
bytes := make([]byte, 1) bytes := make([]byte, 1)
bytes[0] = b bytes[0] = b
@ -85,7 +80,7 @@ func AssertMarker(r io.Reader, checkMarker bool, m byte) error {
} }
if marker != m { if marker != m {
return Error("decode assert marker failed: expected %v got %v", m, marker) return fmt.Errorf("decode assert marker failed: expected %v got %v", m, marker)
} }
return nil return nil

425
protocol/api/api.go

@ -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"
}

3
protocol/hls/cache.go

@ -3,7 +3,6 @@ package hls
import ( import (
"bytes" "bytes"
"container/list" "container/list"
"errors"
"fmt" "fmt"
"sync" "sync"
) )
@ -13,7 +12,7 @@ const (
) )
var ( var (
ErrNoKey = errors.New("No key for cache") ErrNoKey = fmt.Errorf("No key for cache")
) )
type TSCacheItem struct { type TSCacheItem struct {

50
protocol/hls/hls.go

@ -1,17 +1,20 @@
package hls package hls
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"log"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/orcaman/concurrent-map"
log "github.com/sirupsen/logrus"
) )
const ( const (
@ -19,10 +22,10 @@ const (
) )
var ( var (
ErrNoPublisher = errors.New("No publisher") ErrNoPublisher = fmt.Errorf("no publisher")
ErrInvalidReq = errors.New("invalid req url path") ErrInvalidReq = fmt.Errorf("invalid req url path")
ErrNoSupportVideoCodec = errors.New("no support video codec") ErrNoSupportVideoCodec = fmt.Errorf("no support video codec")
ErrNoSupportAudioCodec = errors.New("no support audio codec") ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec")
) )
var crossdomainxml = []byte(`<?xml version="1.0" ?> var crossdomainxml = []byte(`<?xml version="1.0" ?>
@ -33,12 +36,12 @@ var crossdomainxml = []byte(`<?xml version="1.0" ?>
type Server struct { type Server struct {
listener net.Listener listener net.Listener
conns cmap.ConcurrentMap conns *sync.Map
} }
func NewServer() *Server { func NewServer() *Server {
ret := &Server{ ret := &Server{
conns: cmap.New(), conns: &sync.Map{},
} }
go ret.checkStop() go ret.checkStop()
return ret return ret
@ -56,20 +59,19 @@ func (server *Server) Serve(listener net.Listener) error {
func (server *Server) GetWriter(info av.Info) av.WriteCloser { func (server *Server) GetWriter(info av.Info) av.WriteCloser {
var s *Source var s *Source
ok := server.conns.Has(info.Key) v, ok := server.conns.Load(info.Key)
if !ok { if !ok {
log.Println("new hls source") log.Debug("new hls source")
s = NewSource(info) s = NewSource(info)
server.conns.Set(info.Key, s) server.conns.Store(info.Key, s)
} else { } else {
v, _ := server.conns.Get(info.Key)
s = v.(*Source) s = v.(*Source)
} }
return s return s
} }
func (server *Server) getConn(key string) *Source { func (server *Server) getConn(key string) *Source {
v, ok := server.conns.Get(key) v, ok := server.conns.Load(key)
if !ok { if !ok {
return nil return nil
} }
@ -79,13 +81,15 @@ func (server *Server) getConn(key string) *Source {
func (server *Server) checkStop() { func (server *Server) checkStop() {
for { for {
<-time.After(5 * time.Second) <-time.After(5 * time.Second)
for item := range server.conns.IterBuffered() {
v := item.Val.(*Source) server.conns.Range(func(key, val interface{}) bool {
if !v.Alive() { v := val.(*Source)
log.Println("check stop and remove: ", v.Info()) if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") {
server.conns.Remove(item.Key) log.Debug("check stop and remove: ", v.Info())
} server.conns.Delete(key)
} }
return true
})
} }
} }
@ -110,7 +114,7 @@ func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
} }
body, err := tsCache.GenM3U8PlayList() body, err := tsCache.GenM3U8PlayList()
if err != nil { if err != nil {
log.Println("GenM3U8PlayList error: ", err) log.Debug("GenM3U8PlayList error: ", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@ -130,7 +134,7 @@ func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
tsCache := conn.GetCacheInc() tsCache := conn.GetCacheInc()
item, err := tsCache.GetItem(r.URL.Path) item, err := tsCache.GetItem(r.URL.Path)
if err != nil { if err != nil {
log.Println("GetItem error: ", err) log.Debug("GetItem error: ", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@ -143,7 +147,7 @@ func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
func (server *Server) parseM3u8(pathstr string) (key string, err error) { func (server *Server) parseM3u8(pathstr string) (key string, err error) {
pathstr = strings.TrimLeft(pathstr, "/") pathstr = strings.TrimLeft(pathstr, "/")
key = strings.TrimRight(pathstr, path.Ext(pathstr)) key = strings.Split(pathstr, path.Ext(pathstr))[0]
return return
} }

41
protocol/hls/source.go

@ -1,15 +1,17 @@
package hls package hls
import ( import (
"bytes"
"fmt" "fmt"
"github.com/gwuhaolin/livego/configure"
"time" "time"
"bytes"
"log"
"errors"
"github.com/gwuhaolin/livego/parser"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/container/flv" "github.com/gwuhaolin/livego/container/flv"
"github.com/gwuhaolin/livego/container/ts" "github.com/gwuhaolin/livego/container/ts"
"github.com/gwuhaolin/livego/parser"
log "github.com/sirupsen/logrus"
) )
const ( const (
@ -56,7 +58,7 @@ func NewSource(info av.Info) *Source {
go func() { go func() {
err := s.SendPacket() err := s.SendPacket()
if err != nil { if err != nil {
log.Println("send packet error: ", err) log.Warning("send packet error: ", err)
s.closed = true s.closed = true
} }
}() }()
@ -68,7 +70,7 @@ func (source *Source) GetCacheInc() *TSCacheItem {
} }
func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) { func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Printf("[%v] packet queue max!!!", info) log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ { for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue tmpPkt, ok := <-pktQue
// try to don't drop audio // try to don't drop audio
@ -92,20 +94,19 @@ func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
} }
} }
log.Println("packet queue len: ", len(pktQue)) log.Debug("packet queue len: ", len(pktQue))
} }
func (source *Source) Write(p *av.Packet) (err error) { func (source *Source) Write(p *av.Packet) (err error) {
err = nil err = nil
if source.closed { if source.closed {
err = errors.New("hls source closed") err = fmt.Errorf("hls source closed")
return return
} }
source.SetPreTime() source.SetPreTime()
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
errString := fmt.Sprintf("hls source has already been closed:%v", e) err = fmt.Errorf("hls source has already been closed:%v", e)
err = errors.New(errString)
} }
}() }()
if len(source.packetQueue) >= maxQueueNum-24 { if len(source.packetQueue) >= maxQueueNum-24 {
@ -120,16 +121,16 @@ func (source *Source) Write(p *av.Packet) (err error) {
func (source *Source) SendPacket() error { func (source *Source) SendPacket() error {
defer func() { defer func() {
log.Printf("[%v] hls sender stop", source.info) log.Debugf("[%v] hls sender stop", source.info)
if r := recover(); r != nil { if r := recover(); r != nil {
log.Println("hls SendPacket panic: ", r) log.Warning("hls SendPacket panic: ", r)
} }
}() }()
log.Printf("[%v] hls sender start", source.info) log.Debugf("[%v] hls sender start", source.info)
for { for {
if source.closed { if source.closed {
return errors.New("closed") return fmt.Errorf("closed")
} }
p, ok := <-source.packetQueue p, ok := <-source.packetQueue
@ -140,17 +141,17 @@ func (source *Source) SendPacket() error {
err := source.demuxer.Demux(p) err := source.demuxer.Demux(p)
if err == flv.ErrAvcEndSEQ { if err == flv.ErrAvcEndSEQ {
log.Println(err) log.Warning(err)
continue continue
} else { } else {
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
return err return err
} }
} }
compositionTime, isSeq, err := source.parse(p) compositionTime, isSeq, err := source.parse(p)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
} }
if err != nil || isSeq { if err != nil || isSeq {
continue continue
@ -161,7 +162,7 @@ func (source *Source) SendPacket() error {
source.tsMux(p) source.tsMux(p)
} }
} else { } else {
return errors.New("closed") return fmt.Errorf("closed")
} }
} }
} }
@ -179,8 +180,8 @@ func (source *Source) cleanup() {
} }
func (source *Source) Close(err error) { func (source *Source) Close(err error) {
log.Println("hls source closed: ", source.info) log.Debug("hls source closed: ", source.info)
if !source.closed { if !source.closed && !configure.Config.GetBool("hls_keep_after_end") {
source.cleanup() source.cleanup()
} }
source.closed = true source.closed = true

39
protocol/httpflv/server.go

@ -2,12 +2,14 @@ package httpflv
import ( import (
"encoding/json" "encoding/json"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/rtmp"
"log"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/rtmp"
log "github.com/sirupsen/logrus"
) )
type Server struct { type Server struct {
@ -49,26 +51,31 @@ func (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *stream
return nil return nil
} }
msgs := new(streams) msgs := new(streams)
for item := range rtmpStream.GetStreams().IterBuffered() {
if s, ok := item.Val.(*rtmp.Stream); ok { rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
if s, ok := val.(*rtmp.Stream); ok {
if s.GetReader() != nil { if s.GetReader() != nil {
msg := stream{item.Key, s.GetReader().Info().UID} msg := stream{key.(string), s.GetReader().Info().UID}
msgs.Publishers = append(msgs.Publishers, msg) msgs.Publishers = append(msgs.Publishers, msg)
} }
} }
} return true
})
for item := range rtmpStream.GetStreams().IterBuffered() { rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
ws := item.Val.(*rtmp.Stream).GetWs() ws := val.(*rtmp.Stream).GetWs()
for s := range ws.IterBuffered() {
if pw, ok := s.Val.(*rtmp.PackWriterCloser); ok { ws.Range(func(k, v interface{}) bool {
if pw, ok := v.(*rtmp.PackWriterCloser); ok {
if pw.GetWriter() != nil { if pw.GetWriter() != nil {
msg := stream{item.Key, pw.GetWriter().Info().UID} msg := stream{key.(string), pw.GetWriter().Info().UID}
msgs.Players = append(msgs.Players, msg) msgs.Players = append(msgs.Players, msg)
} }
} }
} return true
} })
return true
})
return msgs return msgs
} }
@ -86,7 +93,7 @@ func (server *Server) getStream(w http.ResponseWriter, r *http.Request) {
func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) { func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Println("http flv handleConn panic: ", r) log.Error("http flv handleConn panic: ", r)
} }
}() }()
@ -98,7 +105,7 @@ func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) {
} }
path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv") path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv")
paths := strings.SplitN(path, "/", 2) paths := strings.SplitN(path, "/", 2)
log.Println("url:", u, "path:", path, "paths:", paths) log.Debug("url:", u, "path:", path, "paths:", paths)
if len(paths) != 2 { if len(paths) != 2 {
http.Error(w, "invalid path", http.StatusBadRequest) http.Error(w, "invalid path", http.StatusBadRequest)

34
protocol/httpflv/writer.go

@ -1,15 +1,16 @@
package httpflv package httpflv
import ( import (
"time"
"errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"github.com/gwuhaolin/livego/utils/uid" "time"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/uid"
log "github.com/sirupsen/logrus"
) )
const ( const (
@ -47,7 +48,7 @@ func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter {
go func() { go func() {
err := ret.SendPacket() err := ret.SendPacket()
if err != nil { if err != nil {
log.Println("SendPacket error:", err) log.Error("SendPacket error: ", err)
ret.closed = true ret.closed = true
} }
}() }()
@ -55,14 +56,14 @@ func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter {
} }
func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) { func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Printf("[%v] packet queue max!!!", info) log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ { for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue tmpPkt, ok := <-pktQue
if ok && tmpPkt.IsVideo { if ok && tmpPkt.IsVideo {
videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)
// dont't drop sps config and dont't drop key frame // dont't drop sps config and dont't drop key frame
if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {
log.Println("insert keyframe to queue") log.Debug("insert keyframe to queue")
pktQue <- tmpPkt pktQue <- tmpPkt
} }
@ -74,25 +75,26 @@ func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
} }
// try to don't drop audio // try to don't drop audio
if ok && tmpPkt.IsAudio { if ok && tmpPkt.IsAudio {
log.Println("insert audio to queue") log.Debug("insert audio to queue")
pktQue <- tmpPkt pktQue <- tmpPkt
} }
} }
log.Println("packet queue len: ", len(pktQue)) log.Debug("packet queue len: ", len(pktQue))
} }
func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) { func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) {
err = nil err = nil
if flvWriter.closed { if flvWriter.closed {
err = errors.New("flvwrite source closed") err = fmt.Errorf("flvwrite source closed")
return return
} }
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
errString := fmt.Sprintf("FLVWriter has already been closed:%v", e) err = fmt.Errorf("FLVWriter has already been closed:%v", e)
err = errors.New(errString)
} }
}() }()
if len(flvWriter.packetQueue) >= maxQueueNum-24 { if len(flvWriter.packetQueue) >= maxQueueNum-24 {
flvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info()) flvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info())
} else { } else {
@ -148,12 +150,10 @@ func (flvWriter *FLVWriter) SendPacket() error {
return err return err
} }
} else { } else {
return errors.New("closed") return fmt.Errorf("closed")
} }
} }
return nil
} }
func (flvWriter *FLVWriter) Wait() { func (flvWriter *FLVWriter) Wait() {
@ -164,7 +164,7 @@ func (flvWriter *FLVWriter) Wait() {
} }
func (flvWriter *FLVWriter) Close(error) { func (flvWriter *FLVWriter) Close(error) {
log.Println("http flv closed") log.Debug("http flv closed")
if !flvWriter.closed { if !flvWriter.closed {
close(flvWriter.packetQueue) close(flvWriter.packetQueue)
close(flvWriter.closedChan) close(flvWriter.closedChan)

239
protocol/httpopera/http_opera.go

@ -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)
}
}

8
protocol/rtmp/cache/cache.go vendored

@ -1,12 +1,8 @@
package cache package cache
import ( import (
"flag"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) "github.com/gwuhaolin/livego/configure"
var (
gopNum = flag.Int("gopNum", 1, "gop num")
) )
type Cache struct { type Cache struct {
@ -18,7 +14,7 @@ type Cache struct {
func NewCache() *Cache { func NewCache() *Cache {
return &Cache{ return &Cache{
gop: NewGopCache(*gopNum), gop: NewGopCache(configure.Config.GetInt("gop_num")),
videoSeq: NewSpecialCache(), videoSeq: NewSpecialCache(),
audioSeq: NewSpecialCache(), audioSeq: NewSpecialCache(),
metadata: NewSpecialCache(), metadata: NewSpecialCache(),

5
protocol/rtmp/cache/gop.go vendored

@ -1,13 +1,14 @@
package cache package cache
import ( import (
"errors" "fmt"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )
var ( var (
maxGOPCap int = 1024 maxGOPCap int = 1024
ErrGopTooBig = errors.New("gop to big") ErrGopTooBig = fmt.Errorf("gop to big")
) )
type array struct { type array struct {

5
protocol/rtmp/cache/special.go vendored

@ -2,10 +2,11 @@ package cache
import ( import (
"bytes" "bytes"
"log"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
) )
const ( const (

2
protocol/rtmp/core/chunk_stream.go

@ -122,7 +122,7 @@ func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize int) error {
func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error { func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error {
if chunkStream.remain != 0 && chunkStream.tmpFromat != 3 { if chunkStream.remain != 0 && chunkStream.tmpFromat != 3 {
return fmt.Errorf("inlaid remin = %d", chunkStream.remain) return fmt.Errorf("invalid remain = %d", chunkStream.remain)
} }
switch chunkStream.CSID { switch chunkStream.CSID {
case 0: case 0:

3
protocol/rtmp/core/chunk_stream_test.go

@ -4,8 +4,9 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/gwuhaolin/livego/utils/pool" "github.com/gwuhaolin/livego/utils/pool"
"github.com/stretchr/testify/assert"
) )
func TestChunkRead1(t *testing.T) { func TestChunkRead1(t *testing.T) {

3
protocol/rtmp/core/conn.go

@ -4,8 +4,9 @@ import (
"encoding/binary" "encoding/binary"
"net" "net"
"time" "time"
"github.com/gwuhaolin/livego/utils/pool"
"github.com/gwuhaolin/livego/utils/pio" "github.com/gwuhaolin/livego/utils/pio"
"github.com/gwuhaolin/livego/utils/pool"
) )
const ( const (

44
protocol/rtmp/core/conn_client.go

@ -2,7 +2,6 @@ package core
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
@ -10,9 +9,10 @@ import (
neturl "net/url" neturl "net/url"
"strings" "strings"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"log" "github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
) )
var ( var (
@ -26,7 +26,7 @@ var (
) )
var ( var (
ErrFail = errors.New("respone err") ErrFail = fmt.Errorf("respone err")
) )
type ConnClient struct { type ConnClient struct {
@ -74,14 +74,14 @@ func (connClient *ConnClient) readRespMsg() error {
r := bytes.NewReader(rc.Data) r := bytes.NewReader(rc.Data)
vs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0) vs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0)
log.Printf("readRespMsg: vs=%v", vs) log.Debugf("readRespMsg: vs=%v", vs)
for k, v := range vs { for k, v := range vs {
switch v.(type) { switch v.(type) {
case string: case string:
switch connClient.curcmdName { switch connClient.curcmdName {
case cmdConnect, cmdCreateStream: case cmdConnect, cmdCreateStream:
if v.(string) != respResult { if v.(string) != respResult {
return errors.New(v.(string)) return fmt.Errorf(v.(string))
} }
case cmdPublish: case cmdPublish:
@ -157,7 +157,7 @@ func (connClient *ConnClient) writeConnectMsg() error {
event["tcUrl"] = connClient.tcurl event["tcUrl"] = connClient.tcurl
connClient.curcmdName = cmdConnect connClient.curcmdName = cmdConnect
log.Printf("writeConnectMsg: connClient.transID=%d, event=%v", connClient.transID, event) log.Debugf("writeConnectMsg: connClient.transID=%d, event=%v", connClient.transID, event)
if err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil { if err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil {
return err return err
} }
@ -168,7 +168,7 @@ func (connClient *ConnClient) writeCreateStreamMsg() error {
connClient.transID++ connClient.transID++
connClient.curcmdName = cmdCreateStream connClient.curcmdName = cmdCreateStream
log.Printf("writeCreateStreamMsg: connClient.transID=%d", connClient.transID) log.Debugf("writeCreateStreamMsg: connClient.transID=%d", connClient.transID)
if err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil { if err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil {
return err return err
} }
@ -180,7 +180,7 @@ func (connClient *ConnClient) writeCreateStreamMsg() error {
} }
if err == ErrFail { if err == ErrFail {
log.Println("writeCreateStreamMsg readRespMsg err=%v", err) log.Debugf("writeCreateStreamMsg readRespMsg err=%v", err)
return err return err
} }
} }
@ -199,7 +199,7 @@ func (connClient *ConnClient) writePublishMsg() error {
func (connClient *ConnClient) writePlayMsg() error { func (connClient *ConnClient) writePlayMsg() error {
connClient.transID++ connClient.transID++
connClient.curcmdName = cmdPlay connClient.curcmdName = cmdPlay
log.Printf("writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v", log.Debugf("writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v",
connClient.transID, cmdPlay, connClient.title) connClient.transID, cmdPlay, connClient.title)
if err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil { if err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil {
@ -235,9 +235,9 @@ func (connClient *ConnClient) Start(url string, method string) error {
port = ":" + port port = ":" + port
} }
ips, err := net.LookupIP(host) ips, err := net.LookupIP(host)
log.Printf("ips: %v, host: %v", ips, host) log.Debugf("ips: %v, host: %v", ips, host)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
return err return err
} }
remoteIP = ips[rand.Intn(len(ips))].String() remoteIP = ips[rand.Intn(len(ips))].String()
@ -247,41 +247,41 @@ func (connClient *ConnClient) Start(url string, method string) error {
local, err := net.ResolveTCPAddr("tcp", localIP) local, err := net.ResolveTCPAddr("tcp", localIP)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
return err return err
} }
log.Println("remoteIP: ", remoteIP) log.Debug("remoteIP: ", remoteIP)
remote, err := net.ResolveTCPAddr("tcp", remoteIP) remote, err := net.ResolveTCPAddr("tcp", remoteIP)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
return err return err
} }
conn, err := net.DialTCP("tcp", local, remote) conn, err := net.DialTCP("tcp", local, remote)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
return err return err
} }
log.Println("connection:", "local:", conn.LocalAddr(), "remote:", conn.RemoteAddr()) log.Debug("connection:", "local:", conn.LocalAddr(), "remote:", conn.RemoteAddr())
connClient.conn = NewConn(conn, 4*1024) connClient.conn = NewConn(conn, 4*1024)
log.Println("HandshakeClient....") log.Debug("HandshakeClient....")
if err := connClient.conn.HandshakeClient(); err != nil { if err := connClient.conn.HandshakeClient(); err != nil {
return err return err
} }
log.Println("writeConnectMsg....") log.Debug("writeConnectMsg....")
if err := connClient.writeConnectMsg(); err != nil { if err := connClient.writeConnectMsg(); err != nil {
return err return err
} }
log.Println("writeCreateStreamMsg....") log.Debug("writeCreateStreamMsg....")
if err := connClient.writeCreateStreamMsg(); err != nil { if err := connClient.writeCreateStreamMsg(); err != nil {
log.Println("writeCreateStreamMsg error", err) log.Debug("writeCreateStreamMsg error", err)
return err return err
} }
log.Println("method control:", method, av.PUBLISH, av.PLAY) log.Debug("method control:", method, av.PUBLISH, av.PLAY)
if method == av.PUBLISH { if method == av.PUBLISH {
if err := connClient.writePublishMsg(); err != nil { if err := connClient.writePublishMsg(); err != nil {
return err return err

17
protocol/rtmp/core/conn_server.go

@ -2,12 +2,13 @@ package core
import ( import (
"bytes" "bytes"
"errors" "fmt"
"io" "io"
"github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"log" "github.com/gwuhaolin/livego/protocol/amf"
log "github.com/sirupsen/logrus"
) )
var ( var (
@ -17,7 +18,7 @@ var (
) )
var ( var (
ErrReq = errors.New("req error") ErrReq = fmt.Errorf("req error")
) )
var ( var (
@ -250,7 +251,7 @@ func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return err return err
} }
// log.Printf("rtmp req: %#v", vs) // log.Debugf("rtmp req: %#v", vs)
switch vs[0].(type) { switch vs[0].(type) {
case string: case string:
switch vs[0].(string) { switch vs[0].(string) {
@ -277,7 +278,7 @@ func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
} }
connServer.done = true connServer.done = true
connServer.isPublisher = true connServer.isPublisher = true
log.Println("handle publish req done") log.Debug("handle publish req done")
case cmdPlay: case cmdPlay:
if err = connServer.publishOrPlay(vs[1:]); err != nil { if err = connServer.publishOrPlay(vs[1:]); err != nil {
return err return err
@ -287,7 +288,7 @@ func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
} }
connServer.done = true connServer.done = true
connServer.isPublisher = false connServer.isPublisher = false
log.Println("handle play req done") log.Debug("handle play req done")
case cmdFcpublish: case cmdFcpublish:
connServer.fcPublish(vs) connServer.fcPublish(vs)
case cmdReleaseStream: case cmdReleaseStream:
@ -295,7 +296,7 @@ func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
case cmdFCUnpublish: case cmdFCUnpublish:
case cmdDeleteStream: case cmdDeleteStream:
default: default:
log.Println("no support command=", vs[0].(string)) log.Debug("no support command=", vs[0].(string))
} }
} }

4
protocol/rtmp/core/conn_test.go

@ -4,8 +4,10 @@ import (
"bytes" "bytes"
"io" "io"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/gwuhaolin/livego/utils/pool" "github.com/gwuhaolin/livego/utils/pool"
"github.com/stretchr/testify/assert"
) )
func TestConnReadNormal(t *testing.T) { func TestConnReadNormal(t *testing.T) {

96
protocol/rtmp/rtmp.go

@ -1,20 +1,21 @@
package rtmp package rtmp
import ( import (
"errors"
"flag"
"fmt" "fmt"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/container/flv"
"github.com/gwuhaolin/livego/protocol/rtmp/core"
"github.com/gwuhaolin/livego/utils/uid"
"log"
"net" "net"
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
"time" "time"
"github.com/gwuhaolin/livego/utils/uid"
"github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/container/flv"
"github.com/gwuhaolin/livego/protocol/rtmp/core"
log "github.com/sirupsen/logrus"
) )
const ( const (
@ -23,8 +24,8 @@ const (
) )
var ( var (
readTimeout = flag.Int("readTimeout", 10, "read time out") readTimeout = configure.Config.GetInt("read_timeout")
writeTimeout = flag.Int("writeTimeout", 10, "write time out") writeTimeout = configure.Config.GetInt("write_timeout")
) )
type Client struct { type Client struct {
@ -46,11 +47,11 @@ func (c *Client) Dial(url string, method string) error {
} }
if method == av.PUBLISH { if method == av.PUBLISH {
writer := NewVirWriter(connClient) writer := NewVirWriter(connClient)
log.Printf("client Dial call NewVirWriter url=%s, method=%s", url, method) log.Debugf("client Dial call NewVirWriter url=%s, method=%s", url, method)
c.handler.HandleWriter(writer) c.handler.HandleWriter(writer)
} else if method == av.PLAY { } else if method == av.PLAY {
reader := NewVirReader(connClient) reader := NewVirReader(connClient)
log.Printf("client Dial call NewVirReader url=%s, method=%s", url, method) log.Debugf("client Dial call NewVirReader url=%s, method=%s", url, method)
c.handler.HandleReader(reader) c.handler.HandleReader(reader)
if c.getter != nil { if c.getter != nil {
writer := c.getter.GetWriter(reader.Info()) writer := c.getter.GetWriter(reader.Info())
@ -79,7 +80,7 @@ func NewRtmpServer(h av.Handler, getter av.GetWriter) *Server {
func (s *Server) Serve(listener net.Listener) (err error) { func (s *Server) Serve(listener net.Listener) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Println("rtmp serve panic: ", r) log.Error("rtmp serve panic: ", r)
} }
}() }()
@ -90,7 +91,7 @@ func (s *Server) Serve(listener net.Listener) (err error) {
return return
} }
conn := core.NewConn(netconn, 4*1024) conn := core.NewConn(netconn, 4*1024)
log.Println("new client, connect remote:", conn.RemoteAddr().String(), log.Debug("new client, connect remote: ", conn.RemoteAddr().String(),
"local:", conn.LocalAddr().String()) "local:", conn.LocalAddr().String())
go s.handleConn(conn) go s.handleConn(conn)
} }
@ -99,44 +100,55 @@ func (s *Server) Serve(listener net.Listener) (err error) {
func (s *Server) handleConn(conn *core.Conn) error { func (s *Server) handleConn(conn *core.Conn) error {
if err := conn.HandshakeServer(); err != nil { if err := conn.HandshakeServer(); err != nil {
conn.Close() conn.Close()
log.Println("handleConn HandshakeServer err:", err) log.Error("handleConn HandshakeServer err: ", err)
return err return err
} }
connServer := core.NewConnServer(conn) connServer := core.NewConnServer(conn)
if err := connServer.ReadMsg(); err != nil { if err := connServer.ReadMsg(); err != nil {
conn.Close() conn.Close()
log.Println("handleConn read msg err:", err) log.Error("handleConn read msg err: ", err)
return err return err
} }
appname, _, _ := connServer.GetInfo() appname, name, _ := connServer.GetInfo()
if ret := configure.CheckAppName(appname); !ret { if ret := configure.CheckAppName(appname); !ret {
err := errors.New(fmt.Sprintf("application name=%s is not configured", appname)) err := fmt.Errorf("application name=%s is not configured", appname)
conn.Close() conn.Close()
log.Println("CheckAppName err:", err) log.Error("CheckAppName err: ", err)
return err return err
} }
log.Printf("handleConn: IsPublisher=%v", connServer.IsPublisher()) log.Debugf("handleConn: IsPublisher=%v", connServer.IsPublisher())
if connServer.IsPublisher() { if connServer.IsPublisher() {
channel, err := configure.RoomKeys.GetChannel(name)
if err != nil {
err := fmt.Errorf("invalid key err=%s", err.Error())
conn.Close()
log.Error("CheckKey err: ", err)
return err
}
connServer.PublishInfo.Name = channel
if pushlist, ret := configure.GetStaticPushUrlList(appname); ret && (pushlist != nil) { if pushlist, ret := configure.GetStaticPushUrlList(appname); ret && (pushlist != nil) {
log.Printf("GetStaticPushUrlList: %v", pushlist) log.Debugf("GetStaticPushUrlList: %v", pushlist)
} }
reader := NewVirReader(connServer) reader := NewVirReader(connServer)
s.handler.HandleReader(reader) s.handler.HandleReader(reader)
log.Printf("new publisher: %+v", reader.Info()) log.Debugf("new publisher: %+v", reader.Info())
if s.getter != nil { if s.getter != nil {
writeType := reflect.TypeOf(s.getter) writeType := reflect.TypeOf(s.getter)
log.Printf("handleConn:writeType=%v", writeType) log.Debugf("handleConn:writeType=%v", writeType)
writer := s.getter.GetWriter(reader.Info()) writer := s.getter.GetWriter(reader.Info())
s.handler.HandleWriter(writer) s.handler.HandleWriter(writer)
} }
//FIXME: should flv should be configurable, not always on -gs
flvWriter := new(flv.FlvDvr)
s.handler.HandleWriter(flvWriter.GetWriter(reader.Info()))
} else { } else {
writer := NewVirWriter(connServer) writer := NewVirWriter(connServer)
log.Printf("new player: %+v", writer.Info()) log.Debugf("new player: %+v", writer.Info())
s.handler.HandleWriter(writer) s.handler.HandleWriter(writer)
} }
@ -180,7 +192,7 @@ func NewVirWriter(conn StreamReadWriteCloser) *VirWriter {
ret := &VirWriter{ ret := &VirWriter{
Uid: uid.NewId(), Uid: uid.NewId(),
conn: conn, conn: conn,
RWBaser: av.NewRWBaser(time.Second * time.Duration(*writeTimeout)), RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)),
packetQueue: make(chan *av.Packet, maxQueueNum), packetQueue: make(chan *av.Packet, maxQueueNum),
WriteBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0}, WriteBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},
} }
@ -189,7 +201,7 @@ func NewVirWriter(conn StreamReadWriteCloser) *VirWriter {
go func() { go func() {
err := ret.SendPacket() err := ret.SendPacket()
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
} }
}() }()
return ret return ret
@ -230,13 +242,13 @@ func (v *VirWriter) Check() {
} }
func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) { func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
log.Printf("[%v] packet queue max!!!", info) log.Warningf("[%v] packet queue max!!!", info)
for i := 0; i < maxQueueNum-84; i++ { for i := 0; i < maxQueueNum-84; i++ {
tmpPkt, ok := <-pktQue tmpPkt, ok := <-pktQue
// try to don't drop audio // try to don't drop audio
if ok && tmpPkt.IsAudio { if ok && tmpPkt.IsAudio {
if len(pktQue) > maxQueueNum-2 { if len(pktQue) > maxQueueNum-2 {
log.Println("drop audio pkt") log.Debug("drop audio pkt")
<-pktQue <-pktQue
} else { } else {
pktQue <- tmpPkt pktQue <- tmpPkt
@ -251,13 +263,13 @@ func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
pktQue <- tmpPkt pktQue <- tmpPkt
} }
if len(pktQue) > maxQueueNum-10 { if len(pktQue) > maxQueueNum-10 {
log.Println("drop video pkt") log.Debug("drop video pkt")
<-pktQue <-pktQue
} }
} }
} }
log.Println("packet queue len: ", len(pktQue)) log.Debug("packet queue len: ", len(pktQue))
} }
// //
@ -265,13 +277,12 @@ func (v *VirWriter) Write(p *av.Packet) (err error) {
err = nil err = nil
if v.closed { if v.closed {
err = errors.New("VirWriter closed") err = fmt.Errorf("VirWriter closed")
return return
} }
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
errString := fmt.Sprintf("VirWriter has already been closed:%v", e) err = fmt.Errorf("VirWriter has already been closed:%v", e)
err = errors.New(errString)
} }
}() }()
if len(v.packetQueue) >= maxQueueNum-24 { if len(v.packetQueue) >= maxQueueNum-24 {
@ -284,7 +295,7 @@ func (v *VirWriter) Write(p *av.Packet) (err error) {
} }
func (v *VirWriter) SendPacket() error { func (v *VirWriter) SendPacket() error {
Flush := reflect.ValueOf(v.conn).MethodByName("Flush"); Flush := reflect.ValueOf(v.conn).MethodByName("Flush")
var cs core.ChunkStream var cs core.ChunkStream
for { for {
p, ok := <-v.packetQueue p, ok := <-v.packetQueue
@ -313,13 +324,12 @@ func (v *VirWriter) SendPacket() error {
v.closed = true v.closed = true
return err return err
} }
Flush.Call(nil); Flush.Call(nil)
} else { } else {
return errors.New("closed") return fmt.Errorf("closed")
} }
} }
return nil
} }
func (v *VirWriter) Info() (ret av.Info) { func (v *VirWriter) Info() (ret av.Info) {
@ -328,7 +338,7 @@ func (v *VirWriter) Info() (ret av.Info) {
ret.URL = URL ret.URL = URL
_url, err := url.Parse(URL) _url, err := url.Parse(URL)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
} }
ret.Key = strings.TrimLeft(_url.Path, "/") ret.Key = strings.TrimLeft(_url.Path, "/")
ret.Inter = true ret.Inter = true
@ -336,7 +346,7 @@ func (v *VirWriter) Info() (ret av.Info) {
} }
func (v *VirWriter) Close(err error) { func (v *VirWriter) Close(err error) {
log.Println("player ", v.Info(), "closed: "+err.Error()) log.Warning("player ", v.Info(), "closed: "+err.Error())
if !v.closed { if !v.closed {
close(v.packetQueue) close(v.packetQueue)
} }
@ -356,7 +366,7 @@ func NewVirReader(conn StreamReadWriteCloser) *VirReader {
return &VirReader{ return &VirReader{
Uid: uid.NewId(), Uid: uid.NewId(),
conn: conn, conn: conn,
RWBaser: av.NewRWBaser(time.Second * time.Duration(*writeTimeout)), RWBaser: av.NewRWBaser(time.Second * time.Duration(writeTimeout)),
demuxer: flv.NewDemuxer(), demuxer: flv.NewDemuxer(),
ReadBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0}, ReadBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},
} }
@ -390,7 +400,7 @@ func (v *VirReader) SaveStatics(streamid uint32, length uint64, isVideoFlag bool
func (v *VirReader) Read(p *av.Packet) (err error) { func (v *VirReader) Read(p *av.Packet) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Println("rtmp read packet panic: ", r) log.Warning("rtmp read packet panic: ", r)
} }
}() }()
@ -427,13 +437,13 @@ func (v *VirReader) Info() (ret av.Info) {
ret.URL = URL ret.URL = URL
_url, err := url.Parse(URL) _url, err := url.Parse(URL)
if err != nil { if err != nil {
log.Println(err) log.Warning(err)
} }
ret.Key = strings.TrimLeft(_url.Path, "/") ret.Key = strings.TrimLeft(_url.Path, "/")
return return
} }
func (v *VirReader) Close(err error) { func (v *VirReader) Close(err error) {
log.Println("publisher ", v.Info(), "closed: "+err.Error()) log.Debug("publisher ", v.Info(), "closed: "+err.Error())
v.conn.Close(err) v.conn.Close(err)
} }

42
protocol/rtmp/rtmprelay/rtmprelay.go

@ -2,12 +2,14 @@ package rtmprelay
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"github.com/gwuhaolin/livego/av"
"io"
"github.com/gwuhaolin/livego/protocol/amf" "github.com/gwuhaolin/livego/protocol/amf"
"github.com/gwuhaolin/livego/protocol/rtmp/core" "github.com/gwuhaolin/livego/protocol/rtmp/core"
"io"
"log" log "github.com/sirupsen/logrus"
) )
var ( var (
@ -37,13 +39,13 @@ func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay {
} }
func (self *RtmpRelay) rcvPlayChunkStream() { func (self *RtmpRelay) rcvPlayChunkStream() {
log.Println("rcvPlayRtmpMediaPacket connectClient.Read...") log.Debug("rcvPlayRtmpMediaPacket connectClient.Read...")
for { for {
var rc core.ChunkStream var rc core.ChunkStream
if self.startflag == false { if self.startflag == false {
self.connectPlayClient.Close(nil) self.connectPlayClient.Close(nil)
log.Printf("rcvPlayChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) log.Debugf("rcvPlayChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
break break
} }
err := self.connectPlayClient.Read(&rc) err := self.connectPlayClient.Read(&rc)
@ -51,15 +53,15 @@ func (self *RtmpRelay) rcvPlayChunkStream() {
if err != nil && err == io.EOF { if err != nil && err == io.EOF {
break break
} }
//log.Printf("connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v", rc.TypeID, len(rc.Data), err) //log.Debugf("connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v", rc.TypeID, len(rc.Data), err)
switch rc.TypeID { switch rc.TypeID {
case 20, 17: case 20, 17:
r := bytes.NewReader(rc.Data) r := bytes.NewReader(rc.Data)
vs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0) vs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0)
log.Printf("rcvPlayRtmpMediaPacket: vs=%v, err=%v", vs, err) log.Debugf("rcvPlayRtmpMediaPacket: vs=%v, err=%v", vs, err)
case 18: case 18:
log.Printf("rcvPlayRtmpMediaPacket: metadata....") log.Debug("rcvPlayRtmpMediaPacket: metadata....")
case 8, 9: case 8, 9:
self.cs_chan <- rc self.cs_chan <- rc
} }
@ -70,13 +72,13 @@ func (self *RtmpRelay) sendPublishChunkStream() {
for { for {
select { select {
case rc := <-self.cs_chan: case rc := <-self.cs_chan:
//log.Printf("sendPublishChunkStream: rc.TypeID=%v length=%d", rc.TypeID, len(rc.Data)) //log.Debugf("sendPublishChunkStream: rc.TypeID=%v length=%d", rc.TypeID, len(rc.Data))
self.connectPublishClient.Write(rc) self.connectPublishClient.Write(rc)
case ctrlcmd := <-self.sndctrl_chan: case ctrlcmd := <-self.sndctrl_chan:
if ctrlcmd == STOP_CTRL { if ctrlcmd == STOP_CTRL {
self.connectPublishClient.Close(nil) self.connectPublishClient.Close(nil)
log.Printf("sendPublishChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) log.Debugf("sendPublishChunkStream close: playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
break return
} }
} }
} }
@ -84,24 +86,23 @@ func (self *RtmpRelay) sendPublishChunkStream() {
func (self *RtmpRelay) Start() error { func (self *RtmpRelay) Start() error {
if self.startflag { if self.startflag {
err := errors.New(fmt.Sprintf("The rtmprelay already started, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)) return fmt.Errorf("The rtmprelay already started, playurl=%s, publishurl=%s\n", self.PlayUrl, self.PublishUrl)
return err
} }
self.connectPlayClient = core.NewConnClient() self.connectPlayClient = core.NewConnClient()
self.connectPublishClient = core.NewConnClient() self.connectPublishClient = core.NewConnClient()
log.Printf("play server addr:%v starting....", self.PlayUrl) log.Debugf("play server addr:%v starting....", self.PlayUrl)
err := self.connectPlayClient.Start(self.PlayUrl, "play") err := self.connectPlayClient.Start(self.PlayUrl, av.PLAY)
if err != nil { if err != nil {
log.Printf("connectPlayClient.Start url=%v error", self.PlayUrl) log.Debugf("connectPlayClient.Start url=%v error", self.PlayUrl)
return err return err
} }
log.Printf("publish server addr:%v starting....", self.PublishUrl) log.Debugf("publish server addr:%v starting....", self.PublishUrl)
err = self.connectPublishClient.Start(self.PublishUrl, "publish") err = self.connectPublishClient.Start(self.PublishUrl, av.PUBLISH)
if err != nil { if err != nil {
log.Printf("connectPublishClient.Start url=%v error", self.PublishUrl) log.Debugf("connectPublishClient.Start url=%v error", self.PublishUrl)
self.connectPlayClient.Close(nil) self.connectPlayClient.Close(nil)
return err return err
} }
@ -115,11 +116,10 @@ func (self *RtmpRelay) Start() error {
func (self *RtmpRelay) Stop() { func (self *RtmpRelay) Stop() {
if !self.startflag { if !self.startflag {
log.Printf("The rtmprelay already stoped, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl) log.Debugf("The rtmprelay already stoped, playurl=%s, publishurl=%s", self.PlayUrl, self.PublishUrl)
return return
} }
self.startflag = false self.startflag = false
self.sndctrl_chan <- STOP_CTRL self.sndctrl_chan <- STOP_CTRL
} }

46
protocol/rtmp/rtmprelay/staticrelay.go

@ -1,13 +1,14 @@
package rtmprelay package rtmprelay
import ( import (
"errors"
"fmt" "fmt"
"sync"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/configure"
"github.com/gwuhaolin/livego/protocol/rtmp/core" "github.com/gwuhaolin/livego/protocol/rtmp/core"
"log"
"sync" log "github.com/sirupsen/logrus"
) )
type StaticPush struct { type StaticPush struct {
@ -20,25 +21,34 @@ type StaticPush struct {
var G_StaticPushMap = make(map[string](*StaticPush)) var G_StaticPushMap = make(map[string](*StaticPush))
var g_MapLock = new(sync.RWMutex) var g_MapLock = new(sync.RWMutex)
var G_PushUrlList []string = nil
var ( var (
STATIC_RELAY_STOP_CTRL = "STATIC_RTMPRELAY_STOP" STATIC_RELAY_STOP_CTRL = "STATIC_RTMPRELAY_STOP"
) )
func GetStaticPushList(appname string) ([]string, error) { func GetStaticPushList(appname string) ([]string, error) {
if G_PushUrlList == nil {
// Do not unmarshel the config every time, lots of reflect works -gs
pushurlList, ok := configure.GetStaticPushUrlList(appname) pushurlList, ok := configure.GetStaticPushUrlList(appname)
if !ok { if !ok {
return nil, errors.New("no static push url") G_PushUrlList = []string{}
} else {
G_PushUrlList = pushurlList
}
} }
return pushurlList, nil if len(G_PushUrlList) == 0 {
return nil, fmt.Errorf("no static push url")
}
return G_PushUrlList, nil
} }
func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush { func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush {
g_MapLock.RLock() g_MapLock.RLock()
staticpush, ok := G_StaticPushMap[rtmpurl] staticpush, ok := G_StaticPushMap[rtmpurl]
log.Printf("GetAndCreateStaticPushObject: %s, return %v", rtmpurl, ok) log.Debugf("GetAndCreateStaticPushObject: %s, return %v", rtmpurl, ok)
if !ok { if !ok {
g_MapLock.RUnlock() g_MapLock.RUnlock()
newStaticpush := NewStaticPush(rtmpurl) newStaticpush := NewStaticPush(rtmpurl)
@ -62,7 +72,7 @@ func GetStaticPushObject(rtmpurl string) (*StaticPush, error) {
} }
g_MapLock.RUnlock() g_MapLock.RUnlock()
return nil, errors.New(fmt.Sprintf("G_StaticPushMap[%s] not exist....")) return nil, fmt.Errorf("G_StaticPushMap[%s] not exist....", rtmpurl)
} }
func ReleaseStaticPushObject(rtmpurl string) { func ReleaseStaticPushObject(rtmpurl string) {
@ -70,13 +80,13 @@ func ReleaseStaticPushObject(rtmpurl string) {
if _, ok := G_StaticPushMap[rtmpurl]; ok { if _, ok := G_StaticPushMap[rtmpurl]; ok {
g_MapLock.RUnlock() g_MapLock.RUnlock()
log.Printf("ReleaseStaticPushObject %s ok", rtmpurl) log.Debugf("ReleaseStaticPushObject %s ok", rtmpurl)
g_MapLock.Lock() g_MapLock.Lock()
delete(G_StaticPushMap, rtmpurl) delete(G_StaticPushMap, rtmpurl)
g_MapLock.Unlock() g_MapLock.Unlock()
} else { } else {
g_MapLock.RUnlock() g_MapLock.RUnlock()
log.Printf("ReleaseStaticPushObject: not find %s", rtmpurl) log.Debugf("ReleaseStaticPushObject: not find %s", rtmpurl)
} }
} }
@ -92,18 +102,18 @@ func NewStaticPush(rtmpurl string) *StaticPush {
func (self *StaticPush) Start() error { func (self *StaticPush) Start() error {
if self.startflag { if self.startflag {
return errors.New(fmt.Sprintf("StaticPush already start %s", self.RtmpUrl)) return fmt.Errorf("StaticPush already start %s", self.RtmpUrl)
} }
self.connectClient = core.NewConnClient() self.connectClient = core.NewConnClient()
log.Printf("static publish server addr:%v starting....", self.RtmpUrl) log.Debugf("static publish server addr:%v starting....", self.RtmpUrl)
err := self.connectClient.Start(self.RtmpUrl, "publish") err := self.connectClient.Start(self.RtmpUrl, "publish")
if err != nil { if err != nil {
log.Printf("connectClient.Start url=%v error", self.RtmpUrl) log.Debugf("connectClient.Start url=%v error", self.RtmpUrl)
return err return err
} }
log.Printf("static publish server addr:%v started, streamid=%d", self.RtmpUrl, self.connectClient.GetStreamId()) log.Debugf("static publish server addr:%v started, streamid=%d", self.RtmpUrl, self.connectClient.GetStreamId())
go self.HandleAvPacket() go self.HandleAvPacket()
self.startflag = true self.startflag = true
@ -115,7 +125,7 @@ func (self *StaticPush) Stop() {
return return
} }
log.Printf("StaticPush Stop: %s", self.RtmpUrl) log.Debugf("StaticPush Stop: %s", self.RtmpUrl)
self.sndctrl_chan <- STATIC_RELAY_STOP_CTRL self.sndctrl_chan <- STATIC_RELAY_STOP_CTRL
self.startflag = false self.startflag = false
} }
@ -157,7 +167,7 @@ func (self *StaticPush) sendPacket(p *av.Packet) {
func (self *StaticPush) HandleAvPacket() { func (self *StaticPush) HandleAvPacket() {
if !self.IsStart() { if !self.IsStart() {
log.Printf("static push %s not started", self.RtmpUrl) log.Debugf("static push %s not started", self.RtmpUrl)
return return
} }
@ -168,8 +178,8 @@ func (self *StaticPush) HandleAvPacket() {
case ctrlcmd := <-self.sndctrl_chan: case ctrlcmd := <-self.sndctrl_chan:
if ctrlcmd == STATIC_RELAY_STOP_CTRL { if ctrlcmd == STATIC_RELAY_STOP_CTRL {
self.connectClient.Close(nil) self.connectClient.Close(nil)
log.Printf("Static HandleAvPacket close: publishurl=%s", self.RtmpUrl) log.Debugf("Static HandleAvPacket close: publishurl=%s", self.RtmpUrl)
break return
} }
} }
} }

172
protocol/rtmp/stream.go

@ -1,14 +1,16 @@
package rtmp package rtmp
import ( import (
"errors" "fmt"
"strings"
"sync"
"time"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
"github.com/gwuhaolin/livego/protocol/rtmp/cache" "github.com/gwuhaolin/livego/protocol/rtmp/cache"
"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay" "github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay"
"github.com/orcaman/concurrent-map"
"log" log "github.com/sirupsen/logrus"
"strings"
"time"
) )
var ( var (
@ -16,12 +18,12 @@ var (
) )
type RtmpStream struct { type RtmpStream struct {
streams cmap.ConcurrentMap //key streams *sync.Map //key
} }
func NewRtmpStream() *RtmpStream { func NewRtmpStream() *RtmpStream {
ret := &RtmpStream{ ret := &RtmpStream{
streams: cmap.New(), streams: &sync.Map{},
} }
go ret.CheckAlive() go ret.CheckAlive()
return ret return ret
@ -29,10 +31,10 @@ func NewRtmpStream() *RtmpStream {
func (rs *RtmpStream) HandleReader(r av.ReadCloser) { func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
info := r.Info() info := r.Info()
log.Printf("HandleReader: info[%v]", info) log.Debugf("HandleReader: info[%v]", info)
var stream *Stream var stream *Stream
i, ok := rs.streams.Get(info.Key) i, ok := rs.streams.Load(info.Key)
if stream, ok = i.(*Stream); ok { if stream, ok = i.(*Stream); ok {
stream.TransStop() stream.TransStop()
id := stream.ID() id := stream.ID()
@ -40,11 +42,11 @@ func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
ns := NewStream() ns := NewStream()
stream.Copy(ns) stream.Copy(ns)
stream = ns stream = ns
rs.streams.Set(info.Key, ns) rs.streams.Store(info.Key, ns)
} }
} else { } else {
stream = NewStream() stream = NewStream()
rs.streams.Set(info.Key, stream) rs.streams.Store(info.Key, stream)
stream.info = info stream.info = info
} }
@ -53,36 +55,35 @@ func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
func (rs *RtmpStream) HandleWriter(w av.WriteCloser) { func (rs *RtmpStream) HandleWriter(w av.WriteCloser) {
info := w.Info() info := w.Info()
log.Printf("HandleWriter: info[%v]", info) log.Debugf("HandleWriter: info[%v]", info)
var s *Stream var s *Stream
ok := rs.streams.Has(info.Key) item, ok := rs.streams.Load(info.Key)
if !ok { if !ok {
log.Debugf("HandleWriter: not found create new info[%v]", info)
s = NewStream() s = NewStream()
rs.streams.Set(info.Key, s) rs.streams.Store(info.Key, s)
s.info = info s.info = info
} else { } else {
item, ok := rs.streams.Get(info.Key)
if ok {
s = item.(*Stream) s = item.(*Stream)
s.AddWriter(w) s.AddWriter(w)
} }
} }
}
func (rs *RtmpStream) GetStreams() cmap.ConcurrentMap { func (rs *RtmpStream) GetStreams() *sync.Map {
return rs.streams return rs.streams
} }
func (rs *RtmpStream) CheckAlive() { func (rs *RtmpStream) CheckAlive() {
for { for {
<-time.After(5 * time.Second) <-time.After(5 * time.Second)
for item := range rs.streams.IterBuffered() { rs.streams.Range(func(key, val interface{}) bool {
v := item.Val.(*Stream) v := val.(*Stream)
if v.CheckAlive() == 0 { if v.CheckAlive() == 0 {
rs.streams.Remove(item.Key) rs.streams.Delete(key)
}
} }
return true
})
} }
} }
@ -90,7 +91,7 @@ type Stream struct {
isStart bool isStart bool
cache *cache.Cache cache *cache.Cache
r av.ReadCloser r av.ReadCloser
ws cmap.ConcurrentMap ws *sync.Map
info av.Info info av.Info
} }
@ -106,7 +107,7 @@ func (p *PackWriterCloser) GetWriter() av.WriteCloser {
func NewStream() *Stream { func NewStream() *Stream {
return &Stream{ return &Stream{
cache: cache.NewCache(), cache: cache.NewCache(),
ws: cmap.New(), ws: &sync.Map{},
} }
} }
@ -121,17 +122,19 @@ func (s *Stream) GetReader() av.ReadCloser {
return s.r return s.r
} }
func (s *Stream) GetWs() cmap.ConcurrentMap { func (s *Stream) GetWs() *sync.Map {
return s.ws return s.ws
} }
func (s *Stream) Copy(dst *Stream) { func (s *Stream) Copy(dst *Stream) {
for item := range s.ws.IterBuffered() { dst.info = s.info
v := item.Val.(*PackWriterCloser) s.ws.Range(func(key, val interface{}) bool {
s.ws.Remove(item.Key) v := val.(*PackWriterCloser)
s.ws.Delete(key)
v.w.CalcBaseTimestamp() v.w.CalcBaseTimestamp()
dst.AddWriter(v.w) dst.AddWriter(v.w)
} return true
})
} }
func (s *Stream) AddReader(r av.ReadCloser) { func (s *Stream) AddReader(r av.ReadCloser) {
@ -142,7 +145,7 @@ func (s *Stream) AddReader(r av.ReadCloser) {
func (s *Stream) AddWriter(w av.WriteCloser) { func (s *Stream) AddWriter(w av.WriteCloser) {
info := w.Info() info := w.Info()
pw := &PackWriterCloser{w: w} pw := &PackWriterCloser{w: w}
s.ws.Set(info.UID, pw) s.ws.Store(info.UID, pw)
} }
/*检测本application下是否配置static_push, /*检测本application下是否配置static_push,
@ -163,26 +166,26 @@ func (s *Stream) StartStaticPush() {
streamname := key[index+1:] streamname := key[index+1:]
appname := dscr[0] appname := dscr[0]
log.Printf("StartStaticPush: current streamname=%s, appname=%s", streamname, appname) log.Debugf("StartStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname) pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 { if err != nil || len(pushurllist) < 1 {
log.Printf("StartStaticPush: GetStaticPushList error=%v", err) log.Debugf("StartStaticPush: GetStaticPushList error=%v", err)
return return
} }
for _, pushurl := range pushurllist { for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname pushurl := pushurl + "/" + streamname
log.Printf("StartStaticPush: static pushurl=%s", pushurl) log.Debugf("StartStaticPush: static pushurl=%s", pushurl)
staticpushObj := rtmprelay.GetAndCreateStaticPushObject(pushurl) staticpushObj := rtmprelay.GetAndCreateStaticPushObject(pushurl)
if staticpushObj != nil { if staticpushObj != nil {
if err := staticpushObj.Start(); err != nil { if err := staticpushObj.Start(); err != nil {
log.Printf("StartStaticPush: staticpushObj.Start %s error=%v", pushurl, err) log.Debugf("StartStaticPush: staticpushObj.Start %s error=%v", pushurl, err)
} else { } else {
log.Printf("StartStaticPush: staticpushObj.Start %s ok", pushurl) log.Debugf("StartStaticPush: staticpushObj.Start %s ok", pushurl)
} }
} else { } else {
log.Printf("StartStaticPush GetStaticPushObject %s error", pushurl) log.Debugf("StartStaticPush GetStaticPushObject %s error", pushurl)
} }
} }
} }
@ -190,7 +193,7 @@ func (s *Stream) StartStaticPush() {
func (s *Stream) StopStaticPush() { func (s *Stream) StopStaticPush() {
key := s.info.Key key := s.info.Key
log.Printf("StopStaticPush......%s", key) log.Debugf("StopStaticPush......%s", key)
dscr := strings.Split(key, "/") dscr := strings.Split(key, "/")
if len(dscr) < 1 { if len(dscr) < 1 {
return return
@ -204,24 +207,24 @@ func (s *Stream) StopStaticPush() {
streamname := key[index+1:] streamname := key[index+1:]
appname := dscr[0] appname := dscr[0]
log.Printf("StopStaticPush: current streamname=%s, appname=%s", streamname, appname) log.Debugf("StopStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname) pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 { if err != nil || len(pushurllist) < 1 {
log.Printf("StopStaticPush: GetStaticPushList error=%v", err) log.Debugf("StopStaticPush: GetStaticPushList error=%v", err)
return return
} }
for _, pushurl := range pushurllist { for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname pushurl := pushurl + "/" + streamname
log.Printf("StopStaticPush: static pushurl=%s", pushurl) log.Debugf("StopStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) { if (staticpushObj != nil) && (err == nil) {
staticpushObj.Stop() staticpushObj.Stop()
rtmprelay.ReleaseStaticPushObject(pushurl) rtmprelay.ReleaseStaticPushObject(pushurl)
log.Printf("StopStaticPush: staticpushObj.Stop %s ", pushurl) log.Debugf("StopStaticPush: staticpushObj.Stop %s ", pushurl)
} else { } else {
log.Printf("StopStaticPush GetStaticPushObject %s error", pushurl) log.Debugf("StopStaticPush GetStaticPushObject %s error", pushurl)
} }
} }
} }
@ -236,10 +239,10 @@ func (s *Stream) IsSendStaticPush() bool {
appname := dscr[0] appname := dscr[0]
//log.Printf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname) //log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname) pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 { if err != nil || len(pushurllist) < 1 {
//log.Printf("SendStaticPush: GetStaticPushList error=%v", err) //log.Debugf("SendStaticPush: GetStaticPushList error=%v", err)
return false return false
} }
@ -252,15 +255,15 @@ func (s *Stream) IsSendStaticPush() bool {
for _, pushurl := range pushurllist { for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname pushurl := pushurl + "/" + streamname
//log.Printf("SendStaticPush: static pushurl=%s", pushurl) //log.Debugf("SendStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) { if (staticpushObj != nil) && (err == nil) {
return true return true
//staticpushObj.WriteAvPacket(&packet) //staticpushObj.WriteAvPacket(&packet)
//log.Printf("SendStaticPush: WriteAvPacket %s ", pushurl) //log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl)
} else { } else {
log.Printf("SendStaticPush GetStaticPushObject %s error", pushurl) log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl)
} }
} }
return false return false
@ -282,23 +285,23 @@ func (s *Stream) SendStaticPush(packet av.Packet) {
streamname := key[index+1:] streamname := key[index+1:]
appname := dscr[0] appname := dscr[0]
//log.Printf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname) //log.Debugf("SendStaticPush: current streamname=%s, appname=%s", streamname, appname)
pushurllist, err := rtmprelay.GetStaticPushList(appname) pushurllist, err := rtmprelay.GetStaticPushList(appname)
if err != nil || len(pushurllist) < 1 { if err != nil || len(pushurllist) < 1 {
//log.Printf("SendStaticPush: GetStaticPushList error=%v", err) //log.Debugf("SendStaticPush: GetStaticPushList error=%v", err)
return return
} }
for _, pushurl := range pushurllist { for _, pushurl := range pushurllist {
pushurl := pushurl + "/" + streamname pushurl := pushurl + "/" + streamname
//log.Printf("SendStaticPush: static pushurl=%s", pushurl) //log.Debugf("SendStaticPush: static pushurl=%s", pushurl)
staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl) staticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)
if (staticpushObj != nil) && (err == nil) { if (staticpushObj != nil) && (err == nil) {
staticpushObj.WriteAvPacket(&packet) staticpushObj.WriteAvPacket(&packet)
//log.Printf("SendStaticPush: WriteAvPacket %s ", pushurl) //log.Debugf("SendStaticPush: WriteAvPacket %s ", pushurl)
} else { } else {
log.Printf("SendStaticPush GetStaticPushObject %s error", pushurl) log.Debugf("SendStaticPush GetStaticPushObject %s error", pushurl)
} }
} }
} }
@ -307,7 +310,7 @@ func (s *Stream) TransStart() {
s.isStart = true s.isStart = true
var p av.Packet var p av.Packet
log.Printf("TransStart:%v", s.info) log.Debugf("TransStart: %v", s.info)
s.StartStaticPush() s.StartStaticPush()
@ -329,34 +332,35 @@ func (s *Stream) TransStart() {
s.cache.Write(p) s.cache.Write(p)
for item := range s.ws.IterBuffered() { s.ws.Range(func(key, val interface{}) bool {
v := item.Val.(*PackWriterCloser) v := val.(*PackWriterCloser)
if !v.init { if !v.init {
//log.Printf("cache.send: %v", v.w.Info()) //log.Debugf("cache.send: %v", v.w.Info())
if err = s.cache.Send(v.w); err != nil { if err = s.cache.Send(v.w); err != nil {
log.Printf("[%s] send cache packet error: %v, remove", v.w.Info(), err) log.Debugf("[%s] send cache packet error: %v, remove", v.w.Info(), err)
s.ws.Remove(item.Key) s.ws.Delete(key)
continue return true
} }
v.init = true v.init = true
} else { } else {
new_packet := p newPacket := p
//writeType := reflect.TypeOf(v.w) //writeType := reflect.TypeOf(v.w)
//log.Printf("w.Write: type=%v, %v", writeType, v.w.Info()) //log.Debugf("w.Write: type=%v, %v", writeType, v.w.Info())
if err = v.w.Write(&new_packet); err != nil { if err = v.w.Write(&newPacket); err != nil {
log.Printf("[%s] write packet error: %v, remove", v.w.Info(), err) log.Debugf("[%s] write packet error: %v, remove", v.w.Info(), err)
s.ws.Remove(item.Key) s.ws.Delete(key)
}
} }
} }
return true
})
} }
} }
func (s *Stream) TransStop() { func (s *Stream) TransStop() {
log.Printf("TransStop: %s", s.info.Key) log.Debugf("TransStop: %s", s.info.Key)
if s.isStart && s.r != nil { if s.isStart && s.r != nil {
s.r.Close(errors.New("stop old")) s.r.Close(fmt.Errorf("stop old"))
} }
s.isStart = false s.isStart = false
@ -367,39 +371,43 @@ func (s *Stream) CheckAlive() (n int) {
if s.r.Alive() { if s.r.Alive() {
n++ n++
} else { } else {
s.r.Close(errors.New("read timeout")) s.r.Close(fmt.Errorf("read timeout"))
} }
} }
for item := range s.ws.IterBuffered() {
v := item.Val.(*PackWriterCloser) s.ws.Range(func(key, val interface{}) bool {
v := val.(*PackWriterCloser)
if v.w != nil { if v.w != nil {
//Alive from RWBaser, check last frame now - timestamp, if > timeout then Remove it
if !v.w.Alive() && s.isStart { if !v.w.Alive() && s.isStart {
s.ws.Remove(item.Key) log.Infof("write timeout remove")
v.w.Close(errors.New("write timeout")) s.ws.Delete(key)
continue v.w.Close(fmt.Errorf("write timeout"))
return true
} }
n++ n++
} }
return true
})
}
return return
} }
func (s *Stream) closeInter() { func (s *Stream) closeInter() {
if s.r != nil { if s.r != nil {
s.StopStaticPush() s.StopStaticPush()
log.Printf("[%v] publisher closed", s.r.Info()) log.Debugf("[%v] publisher closed", s.r.Info())
} }
for item := range s.ws.IterBuffered() { s.ws.Range(func(key, val interface{}) bool {
v := item.Val.(*PackWriterCloser) v := val.(*PackWriterCloser)
if v.w != nil { if v.w != nil {
if v.w.Info().IsInterval() { if v.w.Info().IsInterval() {
v.w.Close(errors.New("closed")) v.w.Close(fmt.Errorf("closed"))
s.ws.Remove(item.Key) s.ws.Delete(key)
log.Printf("[%v] player closed and remove\n", v.w.Info()) log.Debugf("[%v] player closed and remove\n", v.w.Info())
} }
} }
return true
} })
} }

1
test.go

@ -0,0 +1 @@
package main

1
utils/queue/queue.go

@ -2,6 +2,7 @@ package queue
import ( import (
"sync" "sync"
"github.com/gwuhaolin/livego/av" "github.com/gwuhaolin/livego/av"
) )

13
utils/uid/rand.go

@ -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)
}

3
utils/uid/uuid.go

@ -2,11 +2,12 @@ package uid
import ( import (
"encoding/base64" "encoding/base64"
"github.com/satori/go.uuid" "github.com/satori/go.uuid"
) )
func NewId() string { func NewId() string {
id, _ := uuid.NewV4() id := uuid.NewV4()
b64 := base64.URLEncoding.EncodeToString(id.Bytes()[:12]) b64 := base64.URLEncoding.EncodeToString(id.Bytes()[:12])
return b64 return b64
} }

Loading…
Cancel
Save