Full Code of gwuhaolin/livego for AI

master 16c6af5d9031 cached
79 files
272.9 KB
99.1k tokens
690 symbols
1 requests
Download .txt
Showing preview only (292K chars total). Download the full file or copy to clipboard to get everything.
Repository: gwuhaolin/livego
Branch: master
Commit: 16c6af5d9031
Files: 79
Total size: 272.9 KB

Directory structure:
gitextract_je20vge0/

├── .github/
│   └── workflows/
│       ├── codeql.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── README_cn.md
├── SECURITY.md
├── av/
│   ├── av.go
│   └── rwbase.go
├── configure/
│   ├── channel.go
│   └── liveconfig.go
├── container/
│   ├── flv/
│   │   ├── demuxer.go
│   │   ├── muxer.go
│   │   └── tag.go
│   └── ts/
│       ├── crc32.go
│       ├── muxer.go
│       └── muxer_test.go
├── go.mod
├── go.sum
├── livego.yaml
├── main.go
├── parser/
│   ├── aac/
│   │   └── parser.go
│   ├── h264/
│   │   ├── parser.go
│   │   └── parser_test.go
│   ├── mp3/
│   │   └── parser.go
│   └── parser.go
├── protocol/
│   ├── amf/
│   │   ├── amf.go
│   │   ├── amf_test.go
│   │   ├── const.go
│   │   ├── decoder_amf0.go
│   │   ├── decoder_amf0_test.go
│   │   ├── decoder_amf3.go
│   │   ├── decoder_amf3_external.go
│   │   ├── decoder_amf3_test.go
│   │   ├── encoder_amf0.go
│   │   ├── encoder_amf0_test.go
│   │   ├── encoder_amf3.go
│   │   ├── encoder_amf3_test.go
│   │   ├── metadata.go
│   │   └── util.go
│   ├── api/
│   │   └── api.go
│   ├── hls/
│   │   ├── align.go
│   │   ├── audio_cache.go
│   │   ├── cache.go
│   │   ├── hls.go
│   │   ├── item.go
│   │   ├── source.go
│   │   └── status.go
│   ├── httpflv/
│   │   ├── server.go
│   │   └── writer.go
│   └── rtmp/
│       ├── cache/
│       │   ├── cache.go
│       │   ├── gop.go
│       │   └── special.go
│       ├── core/
│       │   ├── chunk_stream.go
│       │   ├── chunk_stream_test.go
│       │   ├── conn.go
│       │   ├── conn_client.go
│       │   ├── conn_server.go
│       │   ├── conn_test.go
│       │   ├── handshake.go
│       │   ├── read_writer.go
│       │   └── read_writer_test.go
│       ├── rtmp.go
│       ├── rtmprelay/
│       │   ├── rtmprelay.go
│       │   └── staticrelay.go
│       └── stream.go
├── test.go
└── utils/
    ├── pio/
    │   ├── pio.go
    │   ├── reader.go
    │   └── writer.go
    ├── pool/
    │   └── pool.go
    ├── queue/
    │   └── queue.go
    └── uid/
        ├── rand.go
        └── uuid.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/codeql.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
  schedule:
    - cron: '30 23 * * 3'

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    # Runner size impacts CodeQL analysis time. To learn more, please see:
    #   - https://gh.io/recommended-hardware-resources-for-running-codeql
    #   - https://gh.io/supported-runners-and-hardware-resources
    #   - https://gh.io/using-larger-runners (GitHub.com only)
    # Consider using larger runners or machines with greater resources for possible analysis time improvements.
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    permissions:
      # required for all workflows
      security-events: write

      # required to fetch internal or private CodeQL packs
      packages: read

      # only required for workflows in private repositories
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        include:
        - language: go
          build-mode: autobuild
        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
        # Use `c-cpp` to analyze code written in C, C++ or both
        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        build-mode: ${{ matrix.build-mode }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality

    # If the analyze step fails for one of the languages you are analyzing with
    # "We were unable to automatically build your code", modify the matrix above
    # to set the build mode to "manual" for that language. Then modify this step
    # to build your code.
    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
    - if: matrix.build-mode == 'manual'
      shell: bash
      run: |
        echo 'If you are using a "manual" build mode for one or more of the' \
          'languages you are analyzing, replace this with the commands to build' \
          'your code, for example:'
        echo '  make bootstrap'
        echo '  make release'
        exit 1

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        category: "/language:${{matrix.language}}"


================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  release:
    types: [published]
jobs:
  goreleaser:
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '>=1.18.0'
      - name: Check out code into the Go module directory
        uses: actions/checkout@v4
      - name: Get dependencies
        run: go get
      - 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 }}


================================================
FILE: .github/workflows/test.yml
================================================
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
        uses: actions/setup-go@v5
        with:
          go-version: '>=1.18.0'
      - name: Check out code into the Go module directory
        uses: actions/checkout@v4
      - name: Get dependencies
        run: go get
      - name: Test
        run: go test ./...


================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
.idea
dist
.vscode
tmp
vendor
livego
livego.exe


================================================
FILE: .goreleaser.yml
================================================
before:
  hooks:
    - go mod tidy
builds:
  - binary: livego
    id: livego
    main: ./main.go
    goos:
      - windows
      - darwin
      - linux
      - freebsd
    goarch:
      - amd64
      - 386
      - arm

================================================
FILE: CHANGELOG.md
================================================
# 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.


================================================
FILE: Dockerfile
================================================
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"]


================================================
FILE: LICENSE
================================================
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.


================================================
FILE: Makefile
================================================
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)


================================================
FILE: README.md
================================================
<p align='center'>
    <img src='./logo.png' width='200px' height='80px'/>
</p>

[中文](./README_cn.md)

[![Test](https://github.com/gwuhaolin/livego/actions/workflows/test.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/test.yml)
[![Release](https://github.com/gwuhaolin/livego/actions/workflows/release.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/release.yml)
[![CodeQL Advanced](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml)

Simple and efficient live broadcast server:
- Very simple to install and use;
- Pure Golang, high performance, and cross-platform;
- Supports commonly used transmission protocols, file formats, and encoding formats;

#### Supported transport protocols
- RTMP
- AMF
- HLS
- HTTP-FLV

#### Supported container formats
- FLV
- TS

#### Supported encoding formats
- H264
- AAC
- MP3

## Installation
After directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line.

#### Boot from Docker
Run `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start

#### Compile from source
1. Download the source code `git clone https://github.com/gwuhaolin/livego.git`
2. Go to the livego directory and execute `go build` or `make build`

## 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`
5. Use hls via https: generate ssl certificate(server.key, server.crt files), place them in directory with executable file, change "use_hls_https" option in livego.yaml to true (false by default)

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


================================================
FILE: README_cn.md
================================================
<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/)



================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

Use this section to tell people about which versions of your project are
currently being supported with security updates.

| Version | Supported          |
| ------- | ------------------ |
| 5.1.x   | :white_check_mark: |
| 5.0.x   | :x:                |
| 4.0.x   | :white_check_mark: |
| < 4.0   | :x:                |

## Reporting a Vulnerability

Use this section to tell people how to report a vulnerability.

Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.


================================================
FILE: av/av.go
================================================
package av

import (
	"fmt"
	"io"
)

const (
	TAG_AUDIO          = 8
	TAG_VIDEO          = 9
	TAG_SCRIPTDATAAMF0 = 18
	TAG_SCRIPTDATAAMF3 = 0xf
)

const (
	MetadatAMF0  = 0x12
	MetadataAMF3 = 0xf
)

const (
	SOUND_MP3                   = 2
	SOUND_NELLYMOSER_16KHZ_MONO = 4
	SOUND_NELLYMOSER_8KHZ_MONO  = 5
	SOUND_NELLYMOSER            = 6
	SOUND_ALAW                  = 7
	SOUND_MULAW                 = 8
	SOUND_AAC                   = 10
	SOUND_SPEEX                 = 11

	SOUND_5_5Khz = 0
	SOUND_11Khz  = 1
	SOUND_22Khz  = 2
	SOUND_44Khz  = 3

	SOUND_8BIT  = 0
	SOUND_16BIT = 1

	SOUND_MONO   = 0
	SOUND_STEREO = 1

	AAC_SEQHDR = 0
	AAC_RAW    = 1
)

const (
	AVC_SEQHDR = 0
	AVC_NALU   = 1
	AVC_EOS    = 2

	FRAME_KEY   = 1
	FRAME_INTER = 2

	VIDEO_H264 = 7
)

var (
	PUBLISH = "publish"
	PLAY    = "play"
)

// Header can be converted to AudioHeaderInfo or VideoHeaderInfo
type Packet struct {
	IsAudio    bool
	IsVideo    bool
	IsMetadata bool
	TimeStamp  uint32 // dts
	StreamID   uint32
	Header     PacketHeader
	Data       []byte
}

type PacketHeader interface {
}

type AudioPacketHeader interface {
	PacketHeader
	SoundFormat() uint8
	AACPacketType() uint8
}

type VideoPacketHeader interface {
	PacketHeader
	IsKeyFrame() bool
	IsSeq() bool
	CodecID() uint8
	CompositionTime() int32
}

type Demuxer interface {
	Demux(*Packet) (ret *Packet, err error)
}

type Muxer interface {
	Mux(*Packet, io.Writer) error
}

type SampleRater interface {
	SampleRate() (int, error)
}

type CodecParser interface {
	SampleRater
	Parse(*Packet, io.Writer) error
}

type GetWriter interface {
	GetWriter(Info) WriteCloser
}

type Handler interface {
	HandleReader(ReadCloser)
	HandleWriter(WriteCloser)
}

type Alive interface {
	Alive() bool
}

type Closer interface {
	Info() Info
	Close(error)
}

type CalcTime interface {
	CalcBaseTimestamp()
}

type Info struct {
	Key   string
	URL   string
	UID   string
	Inter bool
}

func (info Info) IsInterval() bool {
	return info.Inter
}

func (info Info) String() string {
	return fmt.Sprintf("<key: %s, URL: %s, UID: %s, Inter: %v>",
		info.Key, info.URL, info.UID, info.Inter)
}

type ReadCloser interface {
	Closer
	Alive
	Read(*Packet) error
}

type WriteCloser interface {
	Closer
	Alive
	CalcTime
	Write(*Packet) error
}


================================================
FILE: av/rwbase.go
================================================
package av

import (
	"sync"
	"time"
)

type RWBaser struct {
	lock               sync.Mutex
	timeout            time.Duration
	PreTime            time.Time
	BaseTimestamp      uint32
	LastVideoTimestamp uint32
	LastAudioTimestamp uint32
}

func NewRWBaser(duration time.Duration) RWBaser {
	return RWBaser{
		timeout: duration,
		PreTime: time.Now(),
	}
}

func (rw *RWBaser) BaseTimeStamp() uint32 {
	return rw.BaseTimestamp
}

func (rw *RWBaser) CalcBaseTimestamp() {
	if rw.LastAudioTimestamp > rw.LastVideoTimestamp {
		rw.BaseTimestamp = rw.LastAudioTimestamp
	} else {
		rw.BaseTimestamp = rw.LastVideoTimestamp
	}
}

func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) {
	if typeID == TAG_VIDEO {
		rw.LastVideoTimestamp = timestamp
	} else if typeID == TAG_AUDIO {
		rw.LastAudioTimestamp = timestamp
	}
}

func (rw *RWBaser) SetPreTime() {
	rw.lock.Lock()
	rw.PreTime = time.Now()
	rw.lock.Unlock()
}

func (rw *RWBaser) Alive() bool {
	rw.lock.Lock()
	b := !(time.Now().Sub(rw.PreTime) >= rw.timeout)
	rw.lock.Unlock()
	return b
}


================================================
FILE: configure/channel.go
================================================
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
}


================================================
FILE: configure/liveconfig.go
================================================
package configure

import (
	"bytes"
	"encoding/json"
	"strings"

	"github.com/kr/pretty"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

/*
{
  "server": [
    {
      "appname": "live",
      "live": true,
	  "hls": true,
	  "static_push": []
    }
  ]
}
*/

type Application struct {
	Appname    string   `mapstructure:"appname"`
	Live       bool     `mapstructure:"live"`
	Hls        bool     `mapstructure:"hls"`
	Flv        bool     `mapstructure:"flv"`
	Api        bool     `mapstructure:"api"`
	StaticPush []string `mapstructure:"static_push"`
}

type Applications []Application

type JWT struct {
	Secret    string `mapstructure:"secret"`
	Algorithm string `mapstructure:"algorithm"`
}
type ServerCfg struct {
	Level           string       `mapstructure:"level"`
	ConfigFile      string       `mapstructure:"config_file"`
	FLVArchive      bool         `mapstructure:"flv_archive"`
	FLVDir          string       `mapstructure:"flv_dir"`
	RTMPNoAuth      bool         `mapstructure:"rtmp_noauth"`
	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"`
	EnableTLSVerify bool         `mapstructure:"enable_tls_verify"`
	GopNum          int          `mapstructure:"gop_num"`
	JWT             JWT          `mapstructure:"jwt"`
	Server          Applications `mapstructure:"server"`
}

// default config
var defaultConf = ServerCfg{
	ConfigFile:      "livego.yaml",
	FLVArchive:      false,
	RTMPNoAuth:      false,
	RTMPAddr:        ":1935",
	HTTPFLVAddr:     ":7001",
	HLSAddr:         ":7002",
	HLSKeepAfterEnd: false,
	APIAddr:         ":8090",
	WriteTimeout:    10,
	ReadTimeout:     10,
	EnableTLSVerify: true,
	GopNum:          1,
	Server: Applications{{
		Appname:    "live",
		Live:       true,
		Hls:        true,
		Flv:        true,
		Api:        true,
		StaticPush: nil,
	}},
}

var (
	Config = viper.New()

	// BypassInit can be used to bypass the init() function by setting this
	// value to True at compile time.
	//
	// go build -ldflags "-X 'github.com/gwuhaolin/livego/configure.BypassInit=true'" -o livego main.go
	BypassInit string = ""
)

func initLog() {
	if l, err := log.ParseLevel(Config.GetString("level")); err == nil {
		log.SetLevel(l)
		log.SetReportCaller(l == log.DebugLevel)
	}
}

func init() {
	if BypassInit == "" {
		initDefault()
	}
}

func initDefault() {
	defer Init()

	// 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.Bool("enable_rtmps", false, "enable server session RTMPS")
	pflag.String("rtmps_cert", "server.crt", "cert file path required for RTMPS")
	pflag.String("rtmps_key", "server.key", "key file path required for RTMPS")
	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.Bool("enable_tls_verify", true, "Use system root CA to verify RTMPS connection, set this flag to false on Windows")
	pflag.Parse()
	Config.BindPFlags(pflag.CommandLine)

	// File
	Config.SetConfigFile(Config.GetString("config_file"))
	Config.AddConfigPath(".")
	err := Config.ReadInConfig()
	if err != nil {
		log.Warning(err)
		log.Info("Using default config")
	} else {
		Config.MergeInConfig()
	}

	// 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 {
	apps := Applications{}
	Config.UnmarshalKey("server", &apps)
	for _, app := range apps {
		if app.Appname == appname {
			return app.Live
		}
	}
	return false
}

func GetStaticPushUrlList(appname string) ([]string, bool) {
	apps := Applications{}
	Config.UnmarshalKey("server", &apps)
	for _, app := range apps {
		if (app.Appname == appname) && app.Live {
			if len(app.StaticPush) > 0 {
				return app.StaticPush, true
			} else {
				return nil, false
			}
		}
	}
	return nil, false
}


================================================
FILE: container/flv/demuxer.go
================================================
package flv

import (
	"fmt"
	"github.com/gwuhaolin/livego/av"
)

var (
	ErrAvcEndSEQ = fmt.Errorf("avc end sequence")
)

type Demuxer struct {
}

func NewDemuxer() *Demuxer {
	return &Demuxer{}
}

func (d *Demuxer) DemuxH(p *av.Packet) error {
	var tag Tag
	_, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
	if err != nil {
		return err
	}
	p.Header = &tag

	return nil
}

func (d *Demuxer) Demux(p *av.Packet) error {
	var tag Tag
	n, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)
	if err != nil {
		return err
	}
	if tag.CodecID() == av.VIDEO_H264 &&
		p.Data[0] == 0x17 && p.Data[1] == 0x02 {
		return ErrAvcEndSEQ
	}
	p.Header = &tag
	p.Data = p.Data[n:]

	return nil
}


================================================
FILE: container/flv/muxer.go
================================================
package flv

import (
	"fmt"
	"os"
	"path"
	"strings"
	"time"

	"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/uid"

	log "github.com/sirupsen/logrus"
)

var (
	flvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}
)

/*
func NewFlv(handler av.Handler, info av.Info) {
	patths := strings.SplitN(info.Key, "/", 2)

	if len(patths) != 2 {
		log.Warning("invalid info")
		return
	}

	w, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755)
	if err != nil {
		log.Error("open file error: ", err)
	}

	writer := NewFLVWriter(patths[0], patths[1], info.URL, w)

	handler.HandleWriter(writer)

	writer.Wait()
	// close flv file
	log.Debug("close flv file")
	writer.ctx.Close()
}
*/

const (
	headerLen = 11
)

type FLVWriter struct {
	Uid string
	av.RWBaser
	app, title, url string
	buf             []byte
	closed          chan struct{}
	ctx             *os.File
	closedWriter    bool
}

func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter {
	ret := &FLVWriter{
		Uid:     uid.NewId(),
		app:     app,
		title:   title,
		url:     url,
		ctx:     ctx,
		RWBaser: av.NewRWBaser(time.Second * 10),
		closed:  make(chan struct{}),
		buf:     make([]byte, headerLen),
	}

	ret.ctx.Write(flvHeader)
	pio.PutI32BE(ret.buf[:4], 0)
	ret.ctx.Write(ret.buf[:4])

	return ret
}

func (writer *FLVWriter) Write(p *av.Packet) error {
	writer.RWBaser.SetPreTime()
	h := writer.buf[:headerLen]
	typeID := av.TAG_VIDEO
	if !p.IsVideo {
		if p.IsMetadata {
			var err error
			typeID = av.TAG_SCRIPTDATAAMF0
			p.Data, err = amf.MetaDataReform(p.Data, amf.DEL)
			if err != nil {
				return err
			}
		} else {
			typeID = av.TAG_AUDIO
		}
	}
	dataLen := len(p.Data)
	timestamp := p.TimeStamp
	timestamp += writer.BaseTimeStamp()
	writer.RWBaser.RecTimeStamp(timestamp, uint32(typeID))

	preDataLen := dataLen + headerLen
	timestampbase := timestamp & 0xffffff
	timestampExt := timestamp >> 24 & 0xff

	pio.PutU8(h[0:1], uint8(typeID))
	pio.PutI24BE(h[1:4], int32(dataLen))
	pio.PutI24BE(h[4:7], int32(timestampbase))
	pio.PutU8(h[7:8], uint8(timestampExt))

	if _, err := writer.ctx.Write(h); err != nil {
		return err
	}

	if _, err := writer.ctx.Write(p.Data); err != nil {
		return err
	}

	pio.PutI32BE(h[:4], int32(preDataLen))
	if _, err := writer.ctx.Write(h[:4]); err != nil {
		return err
	}

	return nil
}

func (writer *FLVWriter) Wait() {
	select {
	case <-writer.closed:
		return
	}
}

func (writer *FLVWriter) Close(error) {
	if writer.closedWriter {
		return
	}
	writer.closedWriter = true
	writer.ctx.Close()
	close(writer.closed)
}

func (writer *FLVWriter) Info() (ret av.Info) {
	ret.UID = writer.Uid
	ret.URL = writer.url
	ret.Key = writer.app + "/" + writer.title
	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
}


================================================
FILE: container/flv/tag.go
================================================
package flv

import (
	"fmt"

	"github.com/gwuhaolin/livego/av"
)

type flvTag struct {
	fType     uint8
	dataSize  uint32
	timeStamp uint32
	streamID  uint32 // always 0
}

type mediaTag struct {
	/*
		SoundFormat: UB[4]
		0 = Linear PCM, platform endian
		1 = ADPCM
		2 = MP3
		3 = Linear PCM, little endian
		4 = Nellymoser 16-kHz mono
		5 = Nellymoser 8-kHz mono
		6 = Nellymoser
		7 = G.711 A-law logarithmic PCM
		8 = G.711 mu-law logarithmic PCM
		9 = reserved
		10 = AAC
		11 = Speex
		14 = MP3 8-Khz
		15 = Device-specific sound
		Formats 7, 8, 14, and 15 are reserved for internal use
		AAC is supported in Flash Player 9,0,115,0 and higher.
		Speex is supported in Flash Player 10 and higher.
	*/
	soundFormat uint8

	/*
		SoundRate: UB[2]
		Sampling rate
		0 = 5.5-kHz For AAC: always 3
		1 = 11-kHz
		2 = 22-kHz
		3 = 44-kHz
	*/
	soundRate uint8

	/*
		SoundSize: UB[1]
		0 = snd8Bit
		1 = snd16Bit
		Size of each sample.
		This parameter only pertains to uncompressed formats.
		Compressed formats always decode to 16 bits internally
	*/
	soundSize uint8

	/*
		SoundType: UB[1]
		0 = sndMono
		1 = sndStereo
		Mono or stereo sound For Nellymoser: always 0
		For AAC: always 1
	*/
	soundType uint8

	/*
		0: AAC sequence header
		1: AAC raw
	*/
	aacPacketType uint8

	/*
		1: keyframe (for AVC, a seekable frame)
		2: inter frame (for AVC, a non- seekable frame)
		3: disposable inter frame (H.263 only)
		4: generated keyframe (reserved for server use only)
		5: video info/command frame
	*/
	frameType uint8

	/*
		1: JPEG (currently unused)
		2: Sorenson H.263
		3: Screen video
		4: On2 VP6
		5: On2 VP6 with alpha channel
		6: Screen video version 2
		7: AVC
	*/
	codecID uint8

	/*
		0: AVC sequence header
		1: AVC NALU
		2: AVC end of sequence (lower level NALU sequence ender is not required or supported)
	*/
	avcPacketType uint8

	compositionTime int32
}

type Tag struct {
	flvt   flvTag
	mediat mediaTag
}

func (tag *Tag) SoundFormat() uint8 {
	return tag.mediat.soundFormat
}

func (tag *Tag) AACPacketType() uint8 {
	return tag.mediat.aacPacketType
}

func (tag *Tag) IsKeyFrame() bool {
	return tag.mediat.frameType == av.FRAME_KEY
}

func (tag *Tag) IsSeq() bool {
	return tag.mediat.frameType == av.FRAME_KEY &&
		tag.mediat.avcPacketType == av.AVC_SEQHDR
}

func (tag *Tag) CodecID() uint8 {
	return tag.mediat.codecID
}

func (tag *Tag) CompositionTime() int32 {
	return tag.mediat.compositionTime
}

// ParseMediaTagHeader, parse video, audio, tag header
func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) {
	switch isVideo {
	case false:
		n, err = tag.parseAudioHeader(b)
	case true:
		n, err = tag.parseVideoHeader(b)
	}
	return
}

func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) {
	if len(b) < n+1 {
		err = fmt.Errorf("invalid audiodata len=%d", len(b))
		return
	}
	flags := b[0]
	tag.mediat.soundFormat = flags >> 4
	tag.mediat.soundRate = (flags >> 2) & 0x3
	tag.mediat.soundSize = (flags >> 1) & 0x1
	tag.mediat.soundType = flags & 0x1
	n++
	switch tag.mediat.soundFormat {
	case av.SOUND_AAC:
		tag.mediat.aacPacketType = b[1]
		n++
	}
	return
}

func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) {
	if len(b) < n+5 {
		err = fmt.Errorf("invalid videodata len=%d", len(b))
		return
	}
	flags := b[0]
	tag.mediat.frameType = flags >> 4
	tag.mediat.codecID = flags & 0xf
	n++
	if tag.mediat.frameType == av.FRAME_INTER || tag.mediat.frameType == av.FRAME_KEY {
		tag.mediat.avcPacketType = b[1]
		for i := 2; i < 5; i++ {
			tag.mediat.compositionTime = tag.mediat.compositionTime<<8 + int32(b[i])
		}
		n += 4
	}
	return
}


================================================
FILE: container/ts/crc32.go
================================================
package ts

func GenCrc32(src []byte) uint32 {
	crcTable := []uint32{
		0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
		0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
		0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
		0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
		0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
		0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
		0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
		0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
		0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
		0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
		0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
		0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
		0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
		0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
		0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
		0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
		0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
		0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
		0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
		0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
		0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
		0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
		0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
		0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
		0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
		0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
		0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
		0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
		0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
		0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
		0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
		0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
		0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
		0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
		0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
		0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
		0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
		0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
		0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
		0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
		0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
		0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
		0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
		0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
		0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
		0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
		0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
		0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
		0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
		0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
		0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
		0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
		0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
		0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
		0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
		0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
		0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
		0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
		0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
		0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
		0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
		0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
		0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
		0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}

	j := byte(0)
	crc32 := uint32(0xFFFFFFFF)
	for i := 0; i < len(src); i++ {
		j = (byte(crc32>>24) ^ src[i]) & 0xff
		crc32 = uint32(uint32(crc32<<8) ^ uint32(crcTable[j]))
	}

	return crc32
}


================================================
FILE: container/ts/muxer.go
================================================
package ts

import (
	"io"

	"github.com/gwuhaolin/livego/av"
)

const (
	tsDefaultDataLen = 184
	tsPacketLen      = 188
	h264DefaultHZ    = 90

	videoPID = 0x100
	audioPID = 0x101
	videoSID = 0xe0
	audioSID = 0xc0
)

type Muxer struct {
	videoCc  byte
	audioCc  byte
	patCc    byte
	pmtCc    byte
	pat      [tsPacketLen]byte
	pmt      [tsPacketLen]byte
	tsPacket [tsPacketLen]byte
}

func NewMuxer() *Muxer {
	return &Muxer{}
}

func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error {
	first := true
	wBytes := 0
	pesIndex := 0
	tmpLen := byte(0)
	dataLen := byte(0)

	var pes pesHeader
	dts := int64(p.TimeStamp) * int64(h264DefaultHZ)
	pts := dts
	pid := audioPID
	var videoH av.VideoPacketHeader
	if p.IsVideo {
		pid = videoPID
		videoH, _ = p.Header.(av.VideoPacketHeader)
		pts = dts + int64(videoH.CompositionTime())*int64(h264DefaultHZ)
	}
	err := pes.packet(p, pts, dts)
	if err != nil {
		return err
	}
	pesHeaderLen := pes.len
	packetBytesLen := len(p.Data) + int(pesHeaderLen)

	for {
		if packetBytesLen <= 0 {
			break
		}
		if p.IsVideo {
			muxer.videoCc++
			if muxer.videoCc > 0xf {
				muxer.videoCc = 0
			}
		} else {
			muxer.audioCc++
			if muxer.audioCc > 0xf {
				muxer.audioCc = 0
			}
		}

		i := byte(0)

		//sync byte
		muxer.tsPacket[i] = 0x47
		i++

		//error indicator, unit start indicator,ts priority,pid
		muxer.tsPacket[i] = byte(pid >> 8) //pid high 5 bits
		if first {
			muxer.tsPacket[i] = muxer.tsPacket[i] | 0x40 //unit start indicator
		}
		i++

		//pid low 8 bits
		muxer.tsPacket[i] = byte(pid)
		i++

		//scram control, adaptation control, counter
		if p.IsVideo {
			muxer.tsPacket[i] = 0x10 | byte(muxer.videoCc&0x0f)
		} else {
			muxer.tsPacket[i] = 0x10 | byte(muxer.audioCc&0x0f)
		}
		i++

		//关键帧需要加pcr
		if first && p.IsVideo && videoH.IsKeyFrame() {
			muxer.tsPacket[3] |= 0x20
			muxer.tsPacket[i] = 7
			i++
			muxer.tsPacket[i] = 0x50
			i++
			muxer.writePcr(muxer.tsPacket[0:], i, dts)
			i += 6
		}

		//frame data
		if packetBytesLen >= tsDefaultDataLen {
			dataLen = tsDefaultDataLen
			if first {
				dataLen -= (i - 4)
			}
		} else {
			muxer.tsPacket[3] |= 0x20 //have adaptation
			remainBytes := byte(0)
			dataLen = byte(packetBytesLen)
			if first {
				remainBytes = tsDefaultDataLen - dataLen - (i - 4)
			} else {
				remainBytes = tsDefaultDataLen - dataLen
			}
			muxer.adaptationBufInit(muxer.tsPacket[i:], byte(remainBytes))
			i += remainBytes
		}
		if first && i < tsPacketLen && pesHeaderLen > 0 {
			tmpLen = tsPacketLen - i
			if pesHeaderLen <= tmpLen {
				tmpLen = pesHeaderLen
			}
			copy(muxer.tsPacket[i:], pes.data[pesIndex:pesIndex+int(tmpLen)])
			i += tmpLen
			packetBytesLen -= int(tmpLen)
			dataLen -= tmpLen
			pesHeaderLen -= tmpLen
			pesIndex += int(tmpLen)
		}

		if i < tsPacketLen {
			tmpLen = tsPacketLen - i
			if tmpLen <= dataLen {
				dataLen = tmpLen
			}
			copy(muxer.tsPacket[i:], p.Data[wBytes:wBytes+int(dataLen)])
			wBytes += int(dataLen)
			packetBytesLen -= int(dataLen)
		}
		if w != nil {
			if _, err := w.Write(muxer.tsPacket[0:]); err != nil {
				return err
			}
		}
		first = false
	}

	return nil
}

//PAT return pat data
func (muxer *Muxer) PAT() []byte {
	i := 0
	remainByte := 0
	tsHeader := []byte{0x47, 0x40, 0x00, 0x10, 0x00}
	patHeader := []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x01}

	if muxer.patCc > 0xf {
		muxer.patCc = 0
	}
	tsHeader[3] |= muxer.patCc & 0x0f
	muxer.patCc++

	copy(muxer.pat[i:], tsHeader)
	i += len(tsHeader)

	copy(muxer.pat[i:], patHeader)
	i += len(patHeader)

	crc32Value := GenCrc32(patHeader)
	muxer.pat[i] = byte(crc32Value >> 24)
	i++
	muxer.pat[i] = byte(crc32Value >> 16)
	i++
	muxer.pat[i] = byte(crc32Value >> 8)
	i++
	muxer.pat[i] = byte(crc32Value)
	i++

	remainByte = int(tsPacketLen - i)
	for j := 0; j < remainByte; j++ {
		muxer.pat[i+j] = 0xff
	}

	return muxer.pat[0:]
}

// PMT return pmt data
func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte {
	i := int(0)
	j := int(0)
	var progInfo []byte
	remainBytes := int(0)
	tsHeader := []byte{0x47, 0x50, 0x01, 0x10, 0x00}
	pmtHeader := []byte{0x02, 0xb0, 0xff, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00}
	if !hasVideo {
		pmtHeader[9] = 0x01
		progInfo = []byte{0x0f, 0xe1, 0x01, 0xf0, 0x00}
	} else {
		progInfo = []byte{0x1b, 0xe1, 0x00, 0xf0, 0x00, //h264 or h265*
			0x0f, 0xe1, 0x01, 0xf0, 0x00, //mp3 or aac
		}
	}
	pmtHeader[2] = byte(len(progInfo) + 9 + 4)

	if muxer.pmtCc > 0xf {
		muxer.pmtCc = 0
	}
	tsHeader[3] |= muxer.pmtCc & 0x0f
	muxer.pmtCc++

	if soundFormat == 2 ||
		soundFormat == 14 {
		if hasVideo {
			progInfo[5] = 0x4
		} else {
			progInfo[0] = 0x4
		}
	}

	copy(muxer.pmt[i:], tsHeader)
	i += len(tsHeader)

	copy(muxer.pmt[i:], pmtHeader)
	i += len(pmtHeader)

	copy(muxer.pmt[i:], progInfo[0:])
	i += len(progInfo)

	crc32Value := GenCrc32(muxer.pmt[5 : 5+len(pmtHeader)+len(progInfo)])
	muxer.pmt[i] = byte(crc32Value >> 24)
	i++
	muxer.pmt[i] = byte(crc32Value >> 16)
	i++
	muxer.pmt[i] = byte(crc32Value >> 8)
	i++
	muxer.pmt[i] = byte(crc32Value)
	i++

	remainBytes = int(tsPacketLen - i)
	for j = 0; j < remainBytes; j++ {
		muxer.pmt[i+j] = 0xff
	}

	return muxer.pmt[0:]
}

func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) {
	src[0] = byte(remainBytes - 1)
	if remainBytes == 1 {
	} else {
		src[1] = 0x00
		for i := 2; i < len(src); i++ {
			src[i] = 0xff
		}
	}
	return
}

func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error {
	b[i] = byte(pcr >> 25)
	i++
	b[i] = byte((pcr >> 17) & 0xff)
	i++
	b[i] = byte((pcr >> 9) & 0xff)
	i++
	b[i] = byte((pcr >> 1) & 0xff)
	i++
	b[i] = byte(((pcr & 0x1) << 7) | 0x7e)
	i++
	b[i] = 0x00

	return nil
}

type pesHeader struct {
	len  byte
	data [tsPacketLen]byte
}

// packet return pes packet
func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error {
	//PES header
	i := 0
	header.data[i] = 0x00
	i++
	header.data[i] = 0x00
	i++
	header.data[i] = 0x01
	i++

	sid := audioSID
	if p.IsVideo {
		sid = videoSID
	}
	header.data[i] = byte(sid)
	i++

	flag := 0x80
	ptslen := 5
	dtslen := ptslen
	headerSize := ptslen
	if p.IsVideo && pts != dts {
		flag |= 0x40
		headerSize += 5 //add dts
	}
	size := len(p.Data) + headerSize + 3
	if size > 0xffff {
		size = 0
	}
	header.data[i] = byte(size >> 8)
	i++
	header.data[i] = byte(size)
	i++

	header.data[i] = 0x80
	i++
	header.data[i] = byte(flag)
	i++
	header.data[i] = byte(headerSize)
	i++

	header.writeTs(header.data[0:], i, flag>>6, pts)
	i += ptslen
	if p.IsVideo && pts != dts {
		header.writeTs(header.data[0:], i, 1, dts)
		i += dtslen
	}

	header.len = byte(i)

	return nil
}

func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) {
	val := uint32(0)
	if ts > 0x1ffffffff {
		ts -= 0x1ffffffff
	}
	val = uint32(fb<<4) | ((uint32(ts>>30) & 0x07) << 1) | 1
	src[i] = byte(val)
	i++

	val = ((uint32(ts>>15) & 0x7fff) << 1) | 1
	src[i] = byte(val >> 8)
	i++
	src[i] = byte(val)
	i++

	val = (uint32(ts&0x7fff) << 1) | 1
	src[i] = byte(val >> 8)
	i++
	src[i] = byte(val)
}


================================================
FILE: container/ts/muxer_test.go
================================================
package ts

import (
	"testing"

	"github.com/gwuhaolin/livego/av"

	"github.com/stretchr/testify/assert"
)

type TestWriter struct {
	buf   []byte
	count int
}

//Write write p to w.buf
func (w *TestWriter) Write(p []byte) (int, error) {
	w.count++
	w.buf = p
	return len(p), nil
}

func TestTSEncoder(t *testing.T) {
	at := assert.New(t)
	m := NewMuxer()

	w := &TestWriter{}
	data := []byte{0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81,
		0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7,
		0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38,
	}
	p := av.Packet{
		IsVideo: false,
		Data:    data,
	}
	err := m.Mux(&p, w)
	at.Equal(err, nil)
	at.Equal(w.count, 1)
	at.Equal(w.buf, []byte{0x47, 0x41, 0x01, 0x31, 0x81, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x30,
		0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01, 0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d,
		0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04,
		0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00,
		0x06, 0x00, 0x38})
}


================================================
FILE: go.mod
================================================
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/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
)


================================================
FILE: go.sum
================================================
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/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=


================================================
FILE: livego.yaml
================================================
# # Logger level
# level: info

# # FLV Options
# flv_archive: false
# flv_dir: "./tmp"
# httpflv_addr: ":7001"

# # RTMP Options
# rtmp_noauth: false
# rtmp_addr: ":1935"
# enable_rtmps: true
# rtmps_cert: server.crt
# rtmps_key: server.key
# read_timeout: 10
# write_timeout: 10

# # HLS Options
# hls_addr: ":7002"
#use_hls_https: true

# # API Options
# api_addr: ":8090"
server:
- appname: live
  live: true
  hls: true
  api: true
  flv: true


================================================
FILE: main.go
================================================
package main

import (
	"crypto/tls"
	"fmt"
	"net"
	"path"
	"runtime"
	"time"

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

	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
}

func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) {
	rtmpAddr := configure.Config.GetString("rtmp_addr")
	isRtmps := configure.Config.GetBool("enable_rtmps")

	var rtmpListen net.Listener
	if isRtmps {
		certPath := configure.Config.GetString("rtmps_cert")
		keyPath := configure.Config.GetString("rtmps_key")
		cert, err := tls.LoadX509KeyPair(certPath, keyPath)
		if err != nil {
			log.Fatal(err)
		}

		rtmpListen, err = tls.Listen("tcp", rtmpAddr, &tls.Config{
			Certificates: []tls.Certificate{cert},
		})
		if err != nil {
			log.Fatal(err)
		}
	} else {
		var err error
		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)
		}
	}()
	if isRtmps {
		log.Info("RTMPS Listen On ", rtmpAddr)
	} else {
		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")
	rtmpAddr := configure.Config.GetString("rtmp_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)

	apps := configure.Applications{}
	configure.Config.UnmarshalKey("server", &apps)
	for _, app := range apps {
		stream := rtmp.NewRtmpStream()
		var hlsServer *hls.Server
		if app.Hls {
			hlsServer = startHls()
		}
		if app.Flv {
			startHTTPFlv(stream)
		}
		if app.Api {
			startAPI(stream)
		}

		startRtmp(stream, hlsServer)
	}
}


================================================
FILE: parser/aac/parser.go
================================================
package aac

import (
	"fmt"
	"io"

	"github.com/gwuhaolin/livego/av"
)

type mpegExtension struct {
	objectType byte
	sampleRate byte
}

type mpegCfgInfo struct {
	objectType     byte
	sampleRate     byte
	channel        byte
	sbr            byte
	ps             byte
	frameLen       byte
	exceptionLogTs int64
	extension      *mpegExtension
}

var aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}

var (
	specificBufInvalid = fmt.Errorf("audio mpegspecific error")
	audioBufInvalid    = fmt.Errorf("audiodata  invalid")
)

const (
	adtsHeaderLen = 7
)

type Parser struct {
	gettedSpecific bool
	adtsHeader     []byte
	cfgInfo        *mpegCfgInfo
}

func NewParser() *Parser {
	return &Parser{
		gettedSpecific: false,
		cfgInfo:        &mpegCfgInfo{},
		adtsHeader:     make([]byte, adtsHeaderLen),
	}
}

func (parser *Parser) specificInfo(src []byte) error {
	if len(src) < 2 {
		return specificBufInvalid
	}
	parser.gettedSpecific = true
	parser.cfgInfo.objectType = (src[0] >> 3) & 0xff
	parser.cfgInfo.sampleRate = ((src[0] & 0x07) << 1) | src[1]>>7
	parser.cfgInfo.channel = (src[1] >> 3) & 0x0f
	return nil
}

func (parser *Parser) adts(src []byte, w io.Writer) error {
	if len(src) <= 0 || !parser.gettedSpecific {
		return audioBufInvalid
	}

	frameLen := uint16(len(src)) + 7

	//first write adts header
	parser.adtsHeader[0] = 0xff
	parser.adtsHeader[1] = 0xf1

	parser.adtsHeader[2] &= 0x00
	parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.objectType-1)<<6
	parser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.sampleRate)<<2

	parser.adtsHeader[3] &= 0x00
	parser.adtsHeader[3] = parser.adtsHeader[3] | (parser.cfgInfo.channel<<2)<<4
	parser.adtsHeader[3] = parser.adtsHeader[3] | byte((frameLen<<3)>>14)

	parser.adtsHeader[4] &= 0x00
	parser.adtsHeader[4] = parser.adtsHeader[4] | byte((frameLen<<5)>>8)

	parser.adtsHeader[5] &= 0x00
	parser.adtsHeader[5] = parser.adtsHeader[5] | byte(((frameLen<<13)>>13)<<5)
	parser.adtsHeader[5] = parser.adtsHeader[5] | (0x7C<<1)>>3
	parser.adtsHeader[6] = 0xfc

	if _, err := w.Write(parser.adtsHeader[0:]); err != nil {
		return err
	}
	if _, err := w.Write(src); err != nil {
		return err
	}
	return nil
}

func (parser *Parser) SampleRate() int {
	rate := 44100
	if parser.cfgInfo.sampleRate <= byte(len(aacRates)-1) {
		rate = aacRates[parser.cfgInfo.sampleRate]
	}
	return rate
}

func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (err error) {
	switch packetType {
	case av.AAC_SEQHDR:
		err = parser.specificInfo(b)
	case av.AAC_RAW:
		err = parser.adts(b, w)
	}
	return
}


================================================
FILE: parser/h264/parser.go
================================================
package h264

import (
	"bytes"
	"fmt"
	"io"
)

const (
	i_frame byte = 0
	p_frame byte = 1
	b_frame byte = 2
)

const (
	nalu_type_not_define byte = 0
	nalu_type_slice      byte = 1  //slice_layer_without_partioning_rbsp() sliceheader
	nalu_type_dpa        byte = 2  // slice_data_partition_a_layer_rbsp( ), slice_header
	nalu_type_dpb        byte = 3  // slice_data_partition_b_layer_rbsp( )
	nalu_type_dpc        byte = 4  // slice_data_partition_c_layer_rbsp( )
	nalu_type_idr        byte = 5  // slice_layer_without_partitioning_rbsp( ),sliceheader
	nalu_type_sei        byte = 6  //sei_rbsp( )
	nalu_type_sps        byte = 7  //seq_parameter_set_rbsp( )
	nalu_type_pps        byte = 8  //pic_parameter_set_rbsp( )
	nalu_type_aud        byte = 9  // access_unit_delimiter_rbsp( )
	nalu_type_eoesq      byte = 10 //end_of_seq_rbsp( )
	nalu_type_eostream   byte = 11 //end_of_stream_rbsp( )
	nalu_type_filler     byte = 12 //filler_data_rbsp( )
)

const (
	naluBytesLen int = 4
	maxSpsPpsLen int = 2 * 1024
)

var (
	decDataNil        = fmt.Errorf("dec buf is nil")
	spsDataError      = fmt.Errorf("sps data error")
	ppsHeaderError    = fmt.Errorf("pps header error")
	ppsDataError      = fmt.Errorf("pps data error")
	naluHeaderInvalid = fmt.Errorf("nalu header invalid")
	videoDataInvalid  = fmt.Errorf("video data not match")
	dataSizeNotMatch  = fmt.Errorf("data size not match")
	naluBodyLenError  = fmt.Errorf("nalu body len error")
)

var startCode = []byte{0x00, 0x00, 0x00, 0x01}
var naluAud = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0}

type Parser struct {
	frameType    byte
	specificInfo []byte
	pps          *bytes.Buffer
}

type sequenceHeader struct {
	configVersion        byte //8bits
	avcProfileIndication byte //8bits
	profileCompatility   byte //8bits
	avcLevelIndication   byte //8bits
	reserved1            byte //6bits
	naluLen              byte //2bits
	reserved2            byte //3bits
	spsNum               byte //5bits
	ppsNum               byte //8bits
	spsLen               int
	ppsLen               int
}

func NewParser() *Parser {
	return &Parser{
		pps: bytes.NewBuffer(make([]byte, maxSpsPpsLen)),
	}
}

//return value 1:sps, value2 :pps
func (parser *Parser) parseSpecificInfo(src []byte) error {
	if len(src) < 9 {
		return decDataNil
	}
	sps := []byte{}
	pps := []byte{}

	var seq sequenceHeader
	seq.configVersion = src[0]
	seq.avcProfileIndication = src[1]
	seq.profileCompatility = src[2]
	seq.avcLevelIndication = src[3]
	seq.reserved1 = src[4] & 0xfc
	seq.naluLen = src[4]&0x03 + 1
	seq.reserved2 = src[5] >> 5

	//get sps
	seq.spsNum = src[5] & 0x1f
	seq.spsLen = int(src[6])<<8 | int(src[7])

	if len(src[8:]) < seq.spsLen || seq.spsLen <= 0 {
		return spsDataError
	}
	sps = append(sps, startCode...)
	sps = append(sps, src[8:(8+seq.spsLen)]...)

	//get pps
	tmpBuf := src[(8 + seq.spsLen):]
	if len(tmpBuf) < 4 {
		return ppsHeaderError
	}
	seq.ppsNum = tmpBuf[0]
	seq.ppsLen = int(0)<<16 | int(tmpBuf[1])<<8 | int(tmpBuf[2])
	if len(tmpBuf[3:]) < seq.ppsLen || seq.ppsLen <= 0 {
		return ppsDataError
	}

	pps = append(pps, startCode...)
	pps = append(pps, tmpBuf[3:]...)

	parser.specificInfo = append(parser.specificInfo, sps...)
	parser.specificInfo = append(parser.specificInfo, pps...)

	return nil
}

func (parser *Parser) isNaluHeader(src []byte) bool {
	if len(src) < naluBytesLen {
		return false
	}
	return src[0] == 0x00 &&
		src[1] == 0x00 &&
		src[2] == 0x00 &&
		src[3] == 0x01
}

func (parser *Parser) naluSize(src []byte) (int, error) {
	if len(src) < naluBytesLen {
		return 0, fmt.Errorf("nalusizedata invalid")
	}
	buf := src[:naluBytesLen]
	size := int(0)
	for i := 0; i < len(buf); i++ {
		size = size<<8 + int(buf[i])
	}
	return size, nil
}

func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error {
	dataSize := len(src)
	if dataSize < naluBytesLen {
		return videoDataInvalid
	}
	parser.pps.Reset()
	_, err := w.Write(naluAud)
	if err != nil {
		return err
	}

	index := 0
	nalLen := 0
	hasSpsPps := false
	hasWriteSpsPps := false

	for dataSize > 0 {
		nalLen, err = parser.naluSize(src[index:])
		if err != nil {
			return dataSizeNotMatch
		}
		index += naluBytesLen
		dataSize -= naluBytesLen
		if dataSize >= nalLen && len(src[index:]) >= nalLen && nalLen > 0 {
			nalType := src[index] & 0x1f
			switch nalType {
			case nalu_type_aud:
			case nalu_type_idr:
				if !hasWriteSpsPps {
					hasWriteSpsPps = true
					if !hasSpsPps {
						if _, err := w.Write(parser.specificInfo); err != nil {
							return err
						}
					} else {
						if _, err := w.Write(parser.pps.Bytes()); err != nil {
							return err
						}
					}
				}
				fallthrough
			case nalu_type_slice:
				fallthrough
			case nalu_type_sei:
				_, err := w.Write(startCode)
				if err != nil {
					return err
				}
				_, err = w.Write(src[index : index+nalLen])
				if err != nil {
					return err
				}
			case nalu_type_sps:
				fallthrough
			case nalu_type_pps:
				hasSpsPps = true
				_, err := parser.pps.Write(startCode)
				if err != nil {
					return err
				}
				_, err = parser.pps.Write(src[index : index+nalLen])
				if err != nil {
					return err
				}
			}
			index += nalLen
			dataSize -= nalLen
		} else {
			return naluBodyLenError
		}
	}
	return nil
}

func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err error) {
	switch isSeq {
	case true:
		err = parser.parseSpecificInfo(b)
	case false:
		// is annexb
		if parser.isNaluHeader(b) {
			_, err = w.Write(b)
		} else {
			err = parser.getAnnexbH264(b, w)
		}
	}
	return
}


================================================
FILE: parser/h264/parser_test.go
================================================
package h264

import (
	"bytes"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestH264SeqDemux(t *testing.T) {
	at := assert.New(t)
	seq := []byte{
		0x01, 0x4d, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x4d, 0x00,
		0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,
		0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x01, 0x00,
		0x04, 0x68, 0xde, 0x31, 0x12,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)
	err := d.Parse(seq, true, w)
	at.Equal(err, nil)
	at.Equal(d.specificInfo, []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00,
		0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,
		0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12})
}

func TestH264AnnexbDemux(t *testing.T) {
	at := assert.New(t)
	nalu := []byte{
		0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
		0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,
		0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)
	err := d.Parse(nalu, false, w)
	at.Equal(err, nil)
	at.Equal(w.Len(), 41)
}

func TestH264NalueSizeException(t *testing.T) {
	at := assert.New(t)
	nalu := []byte{
		0x00, 0x00, 0x10,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)
	err := d.Parse(nalu, false, w)
	at.Equal(err, fmt.Errorf("video data not match"))
}

func TestH264Mp4Demux(t *testing.T) {
	at := assert.New(t)
	nalu := []byte{
		0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
		0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x04, 0x68,
		0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x02, 0x65, 0x23,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)
	err := d.Parse(nalu, false, w)
	at.Equal(err, nil)
	at.Equal(w.Len(), 47)
	at.Equal(w.Bytes(), []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
		0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,
		0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23})
}

func TestH264Mp4DemuxException1(t *testing.T) {
	at := assert.New(t)
	nalu := []byte{
		0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)

	err := d.Parse(nalu, false, w)
	at.Equal(err, naluBodyLenError)
}

func TestH264Mp4DemuxException2(t *testing.T) {
	at := assert.New(t)
	nalu := []byte{
		0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,
		0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00,
	}
	d := NewParser()
	w := bytes.NewBuffer(nil)
	err := d.Parse(nalu, false, w)
	at.Equal(err, naluBodyLenError)
}


================================================
FILE: parser/mp3/parser.go
================================================
package mp3

import (
	"fmt"
)

type Parser struct {
	samplingFrequency int
}

func NewParser() *Parser {
	return &Parser{}
}

// sampling_frequency - indicates the sampling frequency, according to the following table.
// '00' 44.1 kHz
// '01' 48 kHz
// '10' 32 kHz
// '11' reserved
var mp3Rates = []int{44100, 48000, 32000}
var (
	errMp3DataInvalid = fmt.Errorf("mp3data  invalid")
	errIndexInvalid   = fmt.Errorf("invalid rate index")
)

func (parser *Parser) Parse(src []byte) error {
	if len(src) < 3 {
		return errMp3DataInvalid
	}
	index := (src[2] >> 2) & 0x3
	if index <= byte(len(mp3Rates)-1) {
		parser.samplingFrequency = mp3Rates[index]
		return nil
	}
	return errIndexInvalid
}

func (parser *Parser) SampleRate() int {
	if parser.samplingFrequency == 0 {
		parser.samplingFrequency = 44100
	}
	return parser.samplingFrequency
}


================================================
FILE: parser/parser.go
================================================
package parser

import (
	"fmt"
	"io"

	"github.com/gwuhaolin/livego/av"
	"github.com/gwuhaolin/livego/parser/aac"
	"github.com/gwuhaolin/livego/parser/h264"
	"github.com/gwuhaolin/livego/parser/mp3"
)

var (
	errNoAudio = fmt.Errorf("demuxer no audio")
)

type CodecParser struct {
	aac  *aac.Parser
	mp3  *mp3.Parser
	h264 *h264.Parser
}

func NewCodecParser() *CodecParser {
	return &CodecParser{}
}

func (codeParser *CodecParser) SampleRate() (int, error) {
	if codeParser.aac == nil && codeParser.mp3 == nil {
		return 0, errNoAudio
	}
	if codeParser.aac != nil {
		return codeParser.aac.SampleRate(), nil
	}
	return codeParser.mp3.SampleRate(), nil
}

func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err error) {

	switch p.IsVideo {
	case true:
		f, ok := p.Header.(av.VideoPacketHeader)
		if ok {
			if f.CodecID() == av.VIDEO_H264 {
				if codeParser.h264 == nil {
					codeParser.h264 = h264.NewParser()
				}
				err = codeParser.h264.Parse(p.Data, f.IsSeq(), w)
			}
		}
	case false:
		f, ok := p.Header.(av.AudioPacketHeader)
		if ok {
			switch f.SoundFormat() {
			case av.SOUND_AAC:
				if codeParser.aac == nil {
					codeParser.aac = aac.NewParser()
				}
				err = codeParser.aac.Parse(p.Data, f.AACPacketType(), w)
			case av.SOUND_MP3:
				if codeParser.mp3 == nil {
					codeParser.mp3 = mp3.NewParser()
				}
				err = codeParser.mp3.Parse(p.Data)
			}
		}

	}
	return
}


================================================
FILE: protocol/amf/amf.go
================================================
package amf

import (
	"fmt"
	"io"
)

func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err error) {
	var v interface{}
	for {
		v, err = d.Decode(r, ver)
		if err != nil {
			break
		}
		ret = append(ret, v)
	}
	return
}

func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {
	switch ver {
	case 0:
		return d.DecodeAmf0(r)
	case 3:
		return d.DecodeAmf3(r)
	}

	return nil, fmt.Errorf("decode amf: unsupported version %d", ver)
}

func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) {
	for _, v := range val {
		if _, err := e.Encode(w, v, ver); err != nil {
			return 0, err
		}
	}
	return 0, nil
}

func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error) {
	switch ver {
	case AMF0:
		return e.EncodeAmf0(w, val)
	case AMF3:
		return e.EncodeAmf3(w, val)
	}

	return 0, fmt.Errorf("encode amf: unsupported version %d", ver)
}


================================================
FILE: protocol/amf/amf_test.go
================================================
package amf

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"
	"time"
)

func EncodeAndDecode(val interface{}, ver Version) (result interface{}, err error) {
	enc := new(Encoder)
	dec := new(Decoder)

	buf := new(bytes.Buffer)

	_, err = enc.Encode(buf, val, ver)
	if err != nil {
		return nil, fmt.Errorf("error in encode: %s", err)
	}

	result, err = dec.Decode(buf, ver)
	if err != nil {
		return nil, fmt.Errorf("error in decode: %s", err)
	}

	return
}

func Compare(val interface{}, ver Version, name string, t *testing.T) {
	result, err := EncodeAndDecode(val, ver)
	if err != nil {
		t.Errorf("%s: %s", name, err)
	}

	if !reflect.DeepEqual(val, result) {
		val_v := reflect.ValueOf(val)
		result_v := reflect.ValueOf(result)

		t.Errorf("%s: comparison failed between %+v (%s) and %+v (%s)", name, val, val_v.Type(), result, result_v.Type())

		Dump("expected", val)
		Dump("got", result)
	}

	// if val != result {
	// 	t.Errorf("%s: comparison failed between %+v and %+v", name, val, result)
	// }
}

func TestAmf0Number(t *testing.T) {
	Compare(float64(3.14159), 0, "amf0 number float", t)
	Compare(float64(124567890), 0, "amf0 number high", t)
	Compare(float64(-34.2), 0, "amf0 number negative", t)
}

func TestAmf0String(t *testing.T) {
	Compare("a pup!", 0, "amf0 string simple", t)
	Compare("日本語", 0, "amf0 string utf8", t)
}

func TestAmf0Boolean(t *testing.T) {
	Compare(true, 0, "amf0 boolean true", t)
	Compare(false, 0, "amf0 boolean false", t)
}

func TestAmf0Null(t *testing.T) {
	Compare(nil, 0, "amf0 boolean nil", t)
}

func TestAmf0Object(t *testing.T) {
	obj := make(Object)
	obj["dog"] = "alfie"
	obj["coffee"] = true
	obj["drugs"] = false
	obj["pi"] = 3.14159

	res, err := EncodeAndDecode(obj, 0)
	if err != nil {
		t.Errorf("amf0 object: %s", err)
	}

	result, ok := res.(Object)
	if ok != true {
		t.Errorf("amf0 object conversion failed")
	}

	if result["dog"] != "alfie" {
		t.Errorf("amf0 object string: comparison failed")
	}

	if result["coffee"] != true {
		t.Errorf("amf0 object true: comparison failed")
	}

	if result["drugs"] != false {
		t.Errorf("amf0 object false: comparison failed")
	}

	if result["pi"] != float64(3.14159) {
		t.Errorf("amf0 object float: comparison failed")
	}
}

func TestAmf0Array(t *testing.T) {
	arr := [5]float64{1, 2, 3, 4, 5}

	res, err := EncodeAndDecode(arr, 0)
	if err != nil {
		t.Errorf("amf0 object: %s", err)
	}

	result, ok := res.(Array)
	if ok != true {
		t.Errorf("amf0 array conversion failed")
	}

	for i := 0; i < len(arr); i++ {
		if arr[i] != result[i] {
			t.Errorf("amf0 array %d comparison failed: %v / %v", i, arr[i], result[i])
		}
	}
}

func TestAmf3Integer(t *testing.T) {
	Compare(int32(0), 3, "amf3 integer zero", t)
	Compare(int32(1245), 3, "amf3 integer low", t)
	Compare(int32(123456), 3, "amf3 integer high", t)
}

func TestAmf3Double(t *testing.T) {
	Compare(float64(3.14159), 3, "amf3 double float", t)
	Compare(float64(1234567890), 3, "amf3 double high", t)
	Compare(float64(-12345), 3, "amf3 double negative", t)
}

func TestAmf3String(t *testing.T) {
	Compare("a pup!", 0, "amf0 string simple", t)
	Compare("日本語", 0, "amf0 string utf8", t)
}

func TestAmf3Boolean(t *testing.T) {
	Compare(true, 3, "amf3 boolean true", t)
	Compare(false, 3, "amf3 boolean false", t)
}

func TestAmf3Null(t *testing.T) {
	Compare(nil, 3, "amf3 boolean nil", t)
}

func TestAmf3Date(t *testing.T) {
	t1 := time.Unix(time.Now().Unix(), 0).UTC() // nanoseconds discarded
	t2 := time.Date(1983, 9, 4, 12, 4, 8, 0, time.UTC)

	Compare(t1, 3, "amf3 date now", t)
	Compare(t2, 3, "amf3 date earlier", t)
}

func TestAmf3Array(t *testing.T) {
	obj := make(Object)
	obj["key"] = "val"

	var arr Array
	arr = append(arr, "amf")
	arr = append(arr, float64(2))
	arr = append(arr, -34.95)
	arr = append(arr, true)
	arr = append(arr, false)

	res, err := EncodeAndDecode(arr, 3)
	if err != nil {
		t.Errorf("amf3 object: %s", err)
	}

	result, ok := res.(Array)
	if ok != true {
		t.Errorf("amf3 array conversion failed: %+v", res)
	}

	for i := 0; i < len(arr); i++ {
		if arr[i] != result[i] {
			t.Errorf("amf3 array %d comparison failed: %v / %v", i, arr[i], result[i])
		}
	}
}

func TestAmf3ByteArray(t *testing.T) {
	enc := new(Encoder)
	dec := new(Decoder)

	buf := new(bytes.Buffer)

	expect := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x00}

	enc.EncodeAmf3ByteArray(buf, expect, true)

	result, err := dec.DecodeAmf3ByteArray(buf, true)
	if err != nil {
		t.Errorf("err: %s", err)
	}

	if bytes.Compare(result, expect) != 0 {
		t.Errorf("expected: %+v, got %+v", expect, buf)
	}
}


================================================
FILE: protocol/amf/const.go
================================================
package amf

import (
	"io"
)

const (
	AMF0 = 0x00
	AMF3 = 0x03
)

const (
	AMF0_NUMBER_MARKER         = 0x00
	AMF0_BOOLEAN_MARKER        = 0x01
	AMF0_STRING_MARKER         = 0x02
	AMF0_OBJECT_MARKER         = 0x03
	AMF0_MOVIECLIP_MARKER      = 0x04
	AMF0_NULL_MARKER           = 0x05
	AMF0_UNDEFINED_MARKER      = 0x06
	AMF0_REFERENCE_MARKER      = 0x07
	AMF0_ECMA_ARRAY_MARKER     = 0x08
	AMF0_OBJECT_END_MARKER     = 0x09
	AMF0_STRICT_ARRAY_MARKER   = 0x0a
	AMF0_DATE_MARKER           = 0x0b
	AMF0_LONG_STRING_MARKER    = 0x0c
	AMF0_UNSUPPORTED_MARKER    = 0x0d
	AMF0_RECORDSET_MARKER      = 0x0e
	AMF0_XML_DOCUMENT_MARKER   = 0x0f
	AMF0_TYPED_OBJECT_MARKER   = 0x10
	AMF0_ACMPLUS_OBJECT_MARKER = 0x11
)

const (
	AMF0_BOOLEAN_FALSE = 0x00
	AMF0_BOOLEAN_TRUE  = 0x01
	AMF0_STRING_MAX    = 65535
	AMF3_INTEGER_MAX   = 536870911
)

const (
	AMF3_UNDEFINED_MARKER = 0x00
	AMF3_NULL_MARKER      = 0x01
	AMF3_FALSE_MARKER     = 0x02
	AMF3_TRUE_MARKER      = 0x03
	AMF3_INTEGER_MARKER   = 0x04
	AMF3_DOUBLE_MARKER    = 0x05
	AMF3_STRING_MARKER    = 0x06
	AMF3_XMLDOC_MARKER    = 0x07
	AMF3_DATE_MARKER      = 0x08
	AMF3_ARRAY_MARKER     = 0x09
	AMF3_OBJECT_MARKER    = 0x0a
	AMF3_XMLSTRING_MARKER = 0x0b
	AMF3_BYTEARRAY_MARKER = 0x0c
)

type ExternalHandler func(*Decoder, io.Reader) (interface{}, error)

type Decoder struct {
	refCache         []interface{}
	stringRefs       []string
	objectRefs       []interface{}
	traitRefs        []Trait
	externalHandlers map[string]ExternalHandler
}

func NewDecoder() *Decoder {
	return &Decoder{
		externalHandlers: make(map[string]ExternalHandler),
	}
}

func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandler) {
	d.externalHandlers[name] = f
}

type Encoder struct {
}

type Version uint8

type Array []interface{}
type Object map[string]interface{}

type TypedObject struct {
	Type   string
	Object Object
}

type Trait struct {
	Type           string
	Externalizable bool
	Dynamic        bool
	Properties     []string
}

func NewTrait() *Trait {
	return &Trait{}
}

func NewTypedObject() *TypedObject {
	return &TypedObject{
		Type:   "",
		Object: make(Object),
	}
}


================================================
FILE: protocol/amf/decoder_amf0.go
================================================
package amf

import (
	"encoding/binary"
	"fmt"
	"io"
)

// amf0 polymorphic router
func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
	marker, err := ReadMarker(r)
	if err != nil {
		return nil, err
	}

	switch marker {
	case AMF0_NUMBER_MARKER:
		return d.DecodeAmf0Number(r, false)
	case AMF0_BOOLEAN_MARKER:
		return d.DecodeAmf0Boolean(r, false)
	case AMF0_STRING_MARKER:
		return d.DecodeAmf0String(r, false)
	case AMF0_OBJECT_MARKER:
		return d.DecodeAmf0Object(r, false)
	case AMF0_MOVIECLIP_MARKER:
		return nil, fmt.Errorf("decode amf0: unsupported type movieclip")
	case AMF0_NULL_MARKER:
		return d.DecodeAmf0Null(r, false)
	case AMF0_UNDEFINED_MARKER:
		return d.DecodeAmf0Undefined(r, false)
	case AMF0_REFERENCE_MARKER:
		return nil, fmt.Errorf("decode amf0: unsupported type reference")
	case AMF0_ECMA_ARRAY_MARKER:
		return d.DecodeAmf0EcmaArray(r, false)
	case AMF0_STRICT_ARRAY_MARKER:
		return d.DecodeAmf0StrictArray(r, false)
	case AMF0_DATE_MARKER:
		return d.DecodeAmf0Date(r, false)
	case AMF0_LONG_STRING_MARKER:
		return d.DecodeAmf0LongString(r, false)
	case AMF0_UNSUPPORTED_MARKER:
		return d.DecodeAmf0Unsupported(r, false)
	case AMF0_RECORDSET_MARKER:
		return nil, fmt.Errorf("decode amf0: unsupported type recordset")
	case AMF0_XML_DOCUMENT_MARKER:
		return d.DecodeAmf0XmlDocument(r, false)
	case AMF0_TYPED_OBJECT_MARKER:
		return d.DecodeAmf0TypedObject(r, false)
	case AMF0_ACMPLUS_OBJECT_MARKER:
		return d.DecodeAmf3(r)
	}

	return nil, fmt.Errorf("decode amf0: unsupported type %d", marker)
}

// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil {
		return
	}

	err = binary.Read(r, binary.BigEndian, &result)
	if err != nil {
		return float64(0), fmt.Errorf("amf0 decode: unable to read number: %s", err)
	}

	return
}

// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil {
		return
	}

	var b byte
	if b, err = ReadByte(r); err != nil {
		return
	}

	if b == AMF0_BOOLEAN_FALSE {
		return false, nil
	} else if b == AMF0_BOOLEAN_TRUE {
		return true, nil
	}

	return false, fmt.Errorf("decode amf0: unexpected value %v for boolean", b)
}

// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil {
		return
	}

	var length uint16
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return "", fmt.Errorf("decode amf0: unable to decode string length: %s", err)
	}

	var bytes = make([]byte, length)
	if bytes, err = ReadBytes(r, int(length)); err != nil {
		return "", fmt.Errorf("decode amf0: unable to decode string value: %s", err)
	}

	return string(bytes), nil
}

// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil {
		return nil, err
	}

	result := make(Object)
	d.refCache = append(d.refCache, result)

	for {
		key, err := d.DecodeAmf0String(r, false)
		if err != nil {
			return nil, err
		}

		if key == "" {
			if err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {
				return nil, fmt.Errorf("decode amf0: expected object end marker: %s", err)
			}

			break
		}

		value, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, fmt.Errorf("decode amf0: unable to decode object value: %s", err)
		}

		result[key] = value
	}

	return result, nil

}

// marker: 1 byte 0x05
// no additional data
func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER)
	return
}

// marker: 1 byte 0x06
// no additional data
func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER)
	return
}

// marker: 1 byte 0x07
// format: 2 byte big endian uint16
/*
func (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil {
		return nil, err
	}

	var err error
	var ref uint16

	err = binary.Read(r, binary.BigEndian, &ref)
	if err != nil {
		return nil, fmt.Errorf("decode amf0: unable to decode reference id: %s", err)
	}

	if int(ref) > len(d.refCache) {
		return nil, fmt.Errorf("decode amf0: bad reference %d (current length %d)", ref, len(d.refCache))
	}

	result := d.refCache[ref]

	return result, nil
}
*/

// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (Object, error) {
	if err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil {
		return nil, err
	}

	var length uint32
	err := binary.Read(r, binary.BigEndian, &length)

	result, err := d.DecodeAmf0Object(r, false)
	if err != nil {
		return nil, fmt.Errorf("decode amf0: unable to decode ecma array object: %s", err)
	}

	return result, nil
}

// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result Array, err error) {
	if err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil {
		return nil, err
	}

	var length uint32
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return nil, fmt.Errorf("decode amf0: unable to decode strict array length: %s", err)
	}

	d.refCache = append(d.refCache, result)

	for i := uint32(0); i < length; i++ {
		tmp, err := d.DecodeAmf0(r)
		if err != nil {
			return nil, fmt.Errorf("decode amf0: unable to decode strict array object: %s", err)
		}
		result = append(result, tmp)
	}

	return result, nil
}

// marker: 1 byte 0x0b
// format:
// - normal number format:
//   - 8 byte big endian float64
// - 2 byte unused
func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil {
		return
	}

	if result, err = d.DecodeAmf0Number(r, false); err != nil {
		return float64(0), fmt.Errorf("decode amf0: unable to decode float in date: %s", err)
	}

	if _, err = ReadBytes(r, 2); err != nil {
		return float64(0), fmt.Errorf("decode amf0: unable to read 2 trail bytes in date: %s", err)
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil {
		return
	}

	var length uint32
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return "", fmt.Errorf("decode amf0: unable to decode long string length: %s", err)
	}

	var bytes = make([]byte, length)
	if bytes, err = ReadBytes(r, int(length)); err != nil {
		return "", fmt.Errorf("decode amf0: unable to decode long string value: %s", err)
	}

	return string(bytes), nil
}

// marker: 1 byte 0x0d
// no additional data
func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER)
	return
}

// marker: 1 byte 0x0f
// format:
// - normal long string format
//   - 4 byte big endian uint32 header to determine size
//   - n (size) byte utf8 string
func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil {
		return
	}

	return d.DecodeAmf0LongString(r, false)
}

// marker: 1 byte 0x10
// format:
// - normal string format:
//   - 2 byte big endian uint16 header to determine size
//   - n (size) byte utf8 string
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (TypedObject, error) {
	result := *new(TypedObject)

	err := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER)
	if err != nil {
		return result, err
	}

	d.refCache = append(d.refCache, result)

	result.Type, err = d.DecodeAmf0String(r, false)
	if err != nil {
		return result, fmt.Errorf("decode amf0: typed object unable to determine type: %s", err)
	}

	result.Object, err = d.DecodeAmf0Object(r, false)
	if err != nil {
		return result, fmt.Errorf("decode amf0: typed object unable to determine object: %s", err)
	}

	return result, nil
}


================================================
FILE: protocol/amf/decoder_amf0_test.go
================================================
package amf

import (
	"bytes"
	"testing"
)

func TestDecodeAmf0Number(t *testing.T) {
	buf := bytes.NewReader([]byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})
	expect := float64(1.2)

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test number interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Number(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test number interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0Number(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0BooleanTrue(t *testing.T) {
	buf := bytes.NewReader([]byte{0x01, 0x01})
	expect := true

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test boolean interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Boolean(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test boolean interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0Boolean(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0BooleanFalse(t *testing.T) {
	buf := bytes.NewReader([]byte{0x01, 0x00})
	expect := false

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test boolean interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Boolean(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test boolean interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0Boolean(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0String(t *testing.T) {
	buf := bytes.NewReader([]byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f})
	expect := "foo"

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test string interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0String(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test string interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0String(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0Object(t *testing.T) {
	buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok := got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}

	// Test object interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Object(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok = got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}

	// Test object interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0Object(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok = got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}
}

func TestDecodeAmf0Null(t *testing.T) {
	buf := bytes.NewReader([]byte{0x05})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}

	// Test null interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Null(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}
}

func TestDecodeAmf0Undefined(t *testing.T) {
	buf := bytes.NewReader([]byte{0x06})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}

	// Test undefined interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Undefined(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}
}

/*
func TestDecodeReference(t *testing.T) {
	buf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x09})

	dec := &Decoder{}

	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok := got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}

	_, ok2 := obj["foo"].(Object)
	if ok2 != true {
		t.Errorf("expected foo value to cast to object")
	}
}
*/

func TestDecodeAmf0EcmaArray(t *testing.T) {
	buf := bytes.NewReader([]byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok := got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}

	// Test ecma array interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0EcmaArray(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok = got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to object")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}

	// Test ecma array interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0EcmaArray(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	obj, ok = got.(Object)
	if ok != true {
		t.Errorf("expected result to cast to ecma array")
	}
	if obj["foo"] != "bar" {
		t.Errorf("expected {'foo'='bar'}, got %v", obj)
	}
}

func TestDecodeAmf0StrictArray(t *testing.T) {
	buf := bytes.NewReader([]byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	arr, ok := got.(Array)
	if ok != true {
		t.Errorf("expected result to cast to strict array")
	}
	if arr[0] != float64(5) {
		t.Errorf("expected array[0] to be 5, got %v", arr[0])
	}
	if arr[1] != "foo" {
		t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
	}
	if arr[2] != nil {
		t.Errorf("expected array[2] to be nil, got %v", arr[2])
	}

	// Test strict array interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0StrictArray(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	arr, ok = got.(Array)
	if ok != true {
		t.Errorf("expected result to cast to strict array")
	}
	if arr[0] != float64(5) {
		t.Errorf("expected array[0] to be 5, got %v", arr[0])
	}
	if arr[1] != "foo" {
		t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
	}
	if arr[2] != nil {
		t.Errorf("expected array[2] to be nil, got %v", arr[2])
	}

	// Test strict array interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0StrictArray(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	arr, ok = got.(Array)
	if ok != true {
		t.Errorf("expected result to cast to strict array")
	}
	if arr[0] != float64(5) {
		t.Errorf("expected array[0] to be 5, got %v", arr[0])
	}
	if arr[1] != "foo" {
		t.Errorf("expected array[1] to be 'foo', got %v", arr[1])
	}
	if arr[2] != nil {
		t.Errorf("expected array[2] to be nil, got %v", arr[2])
	}
}

func TestDecodeAmf0Date(t *testing.T) {
	buf := bytes.NewReader([]byte{0x0b, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
	expect := float64(5)

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test date interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Date(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test date interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0Date(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0LongString(t *testing.T) {
	buf := bytes.NewReader([]byte{0x0c, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})
	expect := "foo"

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test long string interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0LongString(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test long string interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0LongString(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0Unsupported(t *testing.T) {
	buf := bytes.NewReader([]byte{0x0d})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}

	// Test unsupported interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0Unsupported(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}
}

func TestDecodeAmf0XmlDocument(t *testing.T) {
	buf := bytes.NewReader([]byte{0x0f, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})
	expect := "foo"

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test long string interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0XmlDocument(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	// Test long string interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0XmlDocument(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf0TypedObject(t *testing.T) {

	buf := bytes.NewReader([]byte{
		0x10, 0x00, 0x0F, 'o', 'r', 'g',
		'.', 'a', 'm', 'f', '.', 'A',
		'S', 'C', 'l', 'a', 's', 's',
		0x00, 0x03, 'b', 'a', 'z', 0x05,
		0x00, 0x03, 'f', 'o', 'o', 0x02,
		0x00, 0x03, 'b', 'a', 'r', 0x00,
		0x00, 0x09,
	})

	dec := &Decoder{}

	// Test main interface
	got, err := dec.DecodeAmf0(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	tobj, ok := got.(TypedObject)
	if ok != true {
		t.Errorf("expected result to cast to typed object, got %+v", tobj)
	}
	if tobj.Type != "org.amf.ASClass" {
		t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
	}
	if tobj.Object["foo"] != "bar" {
		t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
	}
	if tobj.Object["baz"] != nil {
		t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
	}

	// Test typed object interface with marker
	buf.Seek(0, 0)
	got, err = dec.DecodeAmf0TypedObject(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	tobj, ok = got.(TypedObject)
	if ok != true {
		t.Errorf("expected result to cast to typed object, got %+v", tobj)
	}
	if tobj.Type != "org.amf.ASClass" {
		t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
	}
	if tobj.Object["foo"] != "bar" {
		t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
	}
	if tobj.Object["baz"] != nil {
		t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
	}

	// Test typed object interface without marker
	buf.Seek(1, 0)
	got, err = dec.DecodeAmf0TypedObject(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	tobj, ok = got.(TypedObject)
	if ok != true {
		t.Errorf("expected result to cast to typed object, got %+v", tobj)
	}
	if tobj.Type != "org.amf.ASClass" {
		t.Errorf("expected typed object type to be 'class', got %v", tobj.Type)
	}
	if tobj.Object["foo"] != "bar" {
		t.Errorf("expected typed object object foo to eql bar, got %v", tobj.Object["foo"])
	}
	if tobj.Object["baz"] != nil {
		t.Errorf("expected typed object object baz to nil, got %v", tobj.Object["baz"])
	}
}


================================================
FILE: protocol/amf/decoder_amf3.go
================================================
package amf

import (
	"encoding/binary"
	"fmt"
	"io"
	"time"
)

// amf3 polymorphic router
func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) {
	marker, err := ReadMarker(r)
	if err != nil {
		return nil, err
	}

	switch marker {
	case AMF3_UNDEFINED_MARKER:
		return d.DecodeAmf3Undefined(r, false)
	case AMF3_NULL_MARKER:
		return d.DecodeAmf3Null(r, false)
	case AMF3_FALSE_MARKER:
		return d.DecodeAmf3False(r, false)
	case AMF3_TRUE_MARKER:
		return d.DecodeAmf3True(r, false)
	case AMF3_INTEGER_MARKER:
		return d.DecodeAmf3Integer(r, false)
	case AMF3_DOUBLE_MARKER:
		return d.DecodeAmf3Double(r, false)
	case AMF3_STRING_MARKER:
		return d.DecodeAmf3String(r, false)
	case AMF3_XMLDOC_MARKER:
		return d.DecodeAmf3Xml(r, false)
	case AMF3_DATE_MARKER:
		return d.DecodeAmf3Date(r, false)
	case AMF3_ARRAY_MARKER:
		return d.DecodeAmf3Array(r, false)
	case AMF3_OBJECT_MARKER:
		return d.DecodeAmf3Object(r, false)
	case AMF3_XMLSTRING_MARKER:
		return d.DecodeAmf3Xml(r, false)
	case AMF3_BYTEARRAY_MARKER:
		return d.DecodeAmf3ByteArray(r, false)
	}

	return nil, fmt.Errorf("decode amf3: unsupported type %d", marker)
}

// marker: 1 byte 0x00
// no additional data
func (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF3_UNDEFINED_MARKER)
	return
}

// marker: 1 byte 0x01
// no additional data
func (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	err = AssertMarker(r, decodeMarker, AMF3_NULL_MARKER)
	return
}

// marker: 1 byte 0x02
// no additional data
func (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (result bool, err error) {
	err = AssertMarker(r, decodeMarker, AMF3_FALSE_MARKER)
	result = false
	return
}

// marker: 1 byte 0x03
// no additional data
func (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result bool, err error) {
	err = AssertMarker(r, decodeMarker, AMF3_TRUE_MARKER)
	result = true
	return
}

// marker: 1 byte 0x04
func (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (result int32, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_INTEGER_MARKER); err != nil {
		return
	}

	var u29 uint32
	u29, err = d.decodeU29(r)
	if err != nil {
		return
	}

	result = int32(u29)
	if result > 0xfffffff {
		result = int32(u29 - 0x20000000)
	}

	return
}

// marker: 1 byte 0x05
func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (result float64, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_DOUBLE_MARKER); err != nil {
		return
	}

	err = binary.Read(r, binary.BigEndian, &result)
	if err != nil {
		return float64(0), fmt.Errorf("amf3 decode: unable to read double: %s", err)
	}

	return
}

// marker: 1 byte 0x06
// format:
// - u29 reference int. if reference, no more data. if not reference,
//   length value of bytes to read to complete string.
func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result string, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_STRING_MARKER); err != nil {
		return
	}

	var isRef bool
	var refVal uint32
	isRef, refVal, err = d.decodeReferenceInt(r)
	if err != nil {
		return "", fmt.Errorf("amf3 decode: unable to decode string reference and length: %s", err)
	}

	if isRef {
		result = d.stringRefs[refVal]
		return
	}

	buf := make([]byte, refVal)
	_, err = r.Read(buf)
	if err != nil {
		return "", fmt.Errorf("amf3 decode: unable to read string: %s", err)
	}

	result = string(buf)
	if result != "" {
		d.stringRefs = append(d.stringRefs, result)
	}

	return
}

// marker: 1 byte 0x08
// format:
// - u29 reference int, if reference, no more data
// - timestamp double
func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Time, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_DATE_MARKER); err != nil {
		return
	}

	var isRef bool
	var refVal uint32
	isRef, refVal, err = d.decodeReferenceInt(r)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to decode date reference and length: %s", err)
	}

	if isRef {
		res, ok := d.objectRefs[refVal].(time.Time)
		if ok != true {
			return result, fmt.Errorf("amf3 decode: unable to extract time from date object references")
		}

		return res, err
	}

	var u64 float64
	err = binary.Read(r, binary.BigEndian, &u64)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to read double: %s", err)
	}

	result = time.Unix(int64(u64/1000), 0).UTC()

	d.objectRefs = append(d.objectRefs, result)

	return
}

// marker: 1 byte 0x09
// format:
// - u29 reference int. if reference, no more data.
// - string representing associative array if present
// - n values (length of u29)
func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_ARRAY_MARKER); err != nil {
		return
	}

	var isRef bool
	var refVal uint32
	isRef, refVal, err = d.decodeReferenceInt(r)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to decode array reference and length: %s", err)
	}

	if isRef {
		objRefId := refVal >> 1

		res, ok := d.objectRefs[objRefId].(Array)
		if ok != true {
			return result, fmt.Errorf("amf3 decode: unable to extract array from object references")
		}

		return res, err
	}

	var key string
	key, err = d.DecodeAmf3String(r, false)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to read key for array: %s", err)
	}

	if key != "" {
		return result, fmt.Errorf("amf3 decode: array key is not empty, can't handle associative array")
	}

	for i := uint32(0); i < refVal; i++ {
		tmp, err := d.DecodeAmf3(r)
		if err != nil {
			return result, fmt.Errorf("amf3 decode: array element could not be decoded: %s", err)
		}
		result = append(result, tmp)
	}

	d.objectRefs = append(d.objectRefs, result)

	return
}

// marker: 1 byte 0x09
// format: oh dear god
func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result interface{}, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_OBJECT_MARKER); err != nil {
		return nil, err
	}

	// decode the initial u29
	isRef, refVal, err := d.decodeReferenceInt(r)
	if err != nil {
		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 isRef {
		objRefId := refVal >> 1

		return d.objectRefs[objRefId], nil
	}

	// each type has traits that are cached, if the peer sent a reference
	// then we'll need to look it up and use it.
	var trait Trait

	traitIsRef := (refVal & 0x01) == 0

	if traitIsRef {
		traitRef := refVal >> 1
		trait = d.traitRefs[traitRef]

	} else {
		// build a new trait from what's left of the given u29
		trait = *NewTrait()
		trait.Externalizable = (refVal & 0x02) != 0
		trait.Dynamic = (refVal & 0x04) != 0

		var cls string
		cls, err = d.DecodeAmf3String(r, false)
		if err != nil {
			return result, fmt.Errorf("amf3 decode: unable to read trait type for object: %s", err)
		}
		trait.Type = cls

		// traits have property keys, encoded as amf3 strings
		propLength := refVal >> 3
		for i := uint32(0); i < propLength; i++ {
			tmp, err := d.DecodeAmf3String(r, false)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to read trait property for object: %s", err)
			}
			trait.Properties = append(trait.Properties, tmp)
		}

		d.traitRefs = append(d.traitRefs, trait)
	}

	d.objectRefs = append(d.objectRefs, result)

	// objects can be externalizable, meaning that the system has no concrete understanding of
	// their properties or how they are encoded. in that case, we need to find and delegate behavior
	// to the right object.
	if trait.Externalizable {
		switch trait.Type {
		case "DSA": // AsyncMessageExt
			result, err = d.decodeAsyncMessageExt(r)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to decode dsa: %s", err)
			}
		case "DSK": // AcknowledgeMessageExt
			result, err = d.decodeAcknowledgeMessageExt(r)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to decode dsk: %s", err)
			}
		case "flex.messaging.io.ArrayCollection":
			result, err = d.decodeArrayCollection(r)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to decode ac: %s", err)
			}

			// store an extra reference to array collection container
			d.objectRefs = append(d.objectRefs, result)

		default:
			fn, ok := d.externalHandlers[trait.Type]
			if ok {
				result, err = fn(d, r)
				if err != nil {
					return result, fmt.Errorf("amf3 decode: unable to call external decoder for type %s: %s", trait.Type, err)
				}
			} else {
				return result, fmt.Errorf("amf3 decode: unable to decode external type %s, no handler", trait.Type)
			}
		}

		return result, err
	}

	var key string
	var val interface{}
	var obj Object

	obj = make(Object)

	// non-externalizable objects have property keys in traits, iterate through them
	// and add the read values to the object
	for _, key = range trait.Properties {
		val, err = d.DecodeAmf3(r)
		if err != nil {
			return result, fmt.Errorf("amf3 decode: unable to decode object property: %s", err)
		}

		obj[key] = val
	}

	// if an object is dynamic, it can have extra key/value data at the end. in this case,
	// read keys until we get an empty one.
	if trait.Dynamic {
		for {
			key, err = d.DecodeAmf3String(r, false)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to decode dynamic key: %s", err)
			}
			if key == "" {
				break
			}
			val, err = d.DecodeAmf3(r)
			if err != nil {
				return result, fmt.Errorf("amf3 decode: unable to decode dynamic value: %s", err)
			}

			obj[key] = val
		}
	}

	result = obj

	return
}

// marker: 1 byte 0x07 or 0x0b
// format:
// - u29 reference int. if reference, no more data. if not reference,
//   length value of bytes to read to complete string.
func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string, err error) {
	if decodeMarker {
		var marker byte
		marker, err = ReadMarker(r)
		if err != nil {
			return "", err
		}

		if (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) {
			return "", fmt.Errorf("decode assert marker failed: expected %v or %v, got %v", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker)
		}
	}

	var isRef bool
	var refVal uint32
	isRef, refVal, err = d.decodeReferenceInt(r)
	if err != nil {
		return "", fmt.Errorf("amf3 decode: unable to decode xml reference and length: %s", err)
	}

	if isRef {
		var ok bool
		buf := d.objectRefs[refVal]
		result, ok = buf.(string)
		if ok != true {
			return "", fmt.Errorf("amf3 decode: cannot coerce object reference into xml string")
		}

		return
	}

	buf := make([]byte, refVal)
	_, err = r.Read(buf)
	if err != nil {
		return "", fmt.Errorf("amf3 decode: unable to read xml string: %s", err)
	}

	result = string(buf)

	if result != "" {
		d.objectRefs = append(d.objectRefs, result)
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - u29 reference int. if reference, no more data. if not reference,
//   length value of bytes to read.
func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []byte, err error) {
	if err = AssertMarker(r, decodeMarker, AMF3_BYTEARRAY_MARKER); err != nil {
		return
	}

	var isRef bool
	var refVal uint32
	isRef, refVal, err = d.decodeReferenceInt(r)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to decode byte array reference and length: %s", err)
	}

	if isRef {
		var ok bool
		result, ok = d.objectRefs[refVal].([]byte)
		if ok != true {
			return result, fmt.Errorf("amf3 decode: unable to convert object ref to bytes")
		}

		return
	}

	result = make([]byte, refVal)
	_, err = r.Read(result)
	if err != nil {
		return result, fmt.Errorf("amf3 decode: unable to read bytearray: %s", err)
	}

	d.objectRefs = append(d.objectRefs, result)

	return
}

func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {
	var b byte

	for i := 0; i < 3; i++ {
		b, err = ReadByte(r)
		if err != nil {
			return
		}
		result = (result << 7) + uint32(b&0x7F)
		if (b & 0x80) == 0 {
			return
		}
	}

	b, err = ReadByte(r)
	if err != nil {
		return
	}

	result = ((result << 8) + uint32(b))

	return
}

func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) {
	u29, err := d.decodeU29(r)
	if err != nil {
		return false, 0, fmt.Errorf("amf3 decode: unable to decode reference int: %s", err)
	}

	isRef = u29&0x01 == 0
	refVal = u29 >> 1

	return
}


================================================
FILE: protocol/amf/decoder_amf3_external.go
================================================
package amf

import (
	"fmt"
	"io"
	"math"
)

// Abstract external boilerplate
func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error) {
	result = make(Object)

	if err = d.decodeExternal(r, &result,
		[]string{"body", "clientId", "destination", "headers", "messageId", "timeStamp", "timeToLive"},
		[]string{"clientIdBytes", "messageIdBytes"}); err != nil {
		return result, fmt.Errorf("unable to decode abstract external: %s", err)
	}

	return
}

// DSA
func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error) {
	return d.decodeAsyncMessage(r)
}
func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) {
	result, err = d.decodeAbstractMessage(r)
	if err != nil {
		return result, fmt.Errorf("unable to decode abstract for async: %s", err)
	}

	if err = d.decodeExternal(r, &result, []string{"correlationId", "correlationIdBytes"}); err != nil {
		return result, fmt.Errorf("unable to decode async external: %s", err)
	}

	return
}

// DSK
func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err error) {
	return d.decodeAcknowledgeMessage(r)
}
func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) {
	result, err = d.decodeAsyncMessage(r)
	if err != nil {
		return result, fmt.Errorf("unable to decode async for ack: %s", err)
	}

	if err = d.decodeExternal(r, &result); err != nil {
		return result, fmt.Errorf("unable to decode ack external: %s", err)
	}

	return
}

// flex.messaging.io.ArrayCollection
func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {
	result, err := d.DecodeAmf3(r)
	if err != nil {
		return result, fmt.Errorf("cannot decode child of array collection: %s", err)
	}

	return result, nil
}

func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string) (err error) {
	var flagSet []uint8
	var reservedPosition uint8
	var fieldNames []string

	flagSet, err = readFlags(r)
	if err != nil {
		return fmt.Errorf("unable to read flags: %s", err)
	}

	for i, flags := range flagSet {
		if i < len(fieldSets) {
			fieldNames = fieldSets[i]
		} else {
			fieldNames = []string{}
		}

		reservedPosition = uint8(len(fieldNames))

		for p, field := range fieldNames {
			flagBit := uint8(math.Exp2(float64(p)))
			if (flags & flagBit) != 0 {
				tmp, err := d.DecodeAmf3(r)
				if err != nil {
					return fmt.Errorf("unable to decode external field %s %d %d (%#v): %s", field, i, p, flagSet, err)
				}
				(*obj)[field] = tmp
			}
		}

		if (flags >> reservedPosition) != 0 {
			for j := reservedPosition; j < 6; j++ {
				if ((flags >> j) & 0x01) != 0 {
					field := fmt.Sprintf("extra_%d_%d", i, j)
					tmp, err := d.DecodeAmf3(r)
					if err != nil {
						return fmt.Errorf("unable to decode post-external field %d %d (%#v): %s", i, j, flagSet, err)
					}
					(*obj)[field] = tmp
				}
			}
		}
	}

	return
}

func readFlags(r io.Reader) (result []uint8, err error) {
	for {
		flag, err := ReadByte(r)
		if err != nil {
			return result, fmt.Errorf("unable to read flags: %s", err)
		}

		result = append(result, flag)
		if (flag & 0x80) == 0 {
			break
		}
	}

	return
}


================================================
FILE: protocol/amf/decoder_amf3_test.go
================================================
package amf

import (
	"bytes"
	"testing"
)

type u29TestCase struct {
	value  uint32
	expect []byte
}

var u29TestCases = []u29TestCase{
	{1, []byte{0x01}},
	{2, []byte{0x02}},
	{127, []byte{0x7F}},
	{128, []byte{0x81, 0x00}},
	{255, []byte{0x81, 0x7F}},
	{256, []byte{0x82, 0x00}},
	{0x3FFF, []byte{0xFF, 0x7F}},
	{0x4000, []byte{0x81, 0x80, 0x00}},
	{0x7FFF, []byte{0x81, 0xFF, 0x7F}},
	{0x8000, []byte{0x82, 0x80, 0x00}},
	{0x1FFFFF, []byte{0xFF, 0xFF, 0x7F}},
	{0x200000, []byte{0x80, 0xC0, 0x80, 0x00}},
	{0x3FFFFF, []byte{0x80, 0xFF, 0xFF, 0xFF}},
	{0x400000, []byte{0x81, 0x80, 0x80, 0x00}},
	{0x0FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF}},
}

func TestDecodeAmf3Undefined(t *testing.T) {
	buf := bytes.NewReader([]byte{0x00})

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}
}

func TestDecodeAmf3Null(t *testing.T) {
	buf := bytes.NewReader([]byte{0x01})

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if got != nil {
		t.Errorf("expect nil got %v", got)
	}
}

func TestDecodeAmf3False(t *testing.T) {
	buf := bytes.NewReader([]byte{0x02})
	expect := false

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf3True(t *testing.T) {
	buf := bytes.NewReader([]byte{0x03})
	expect := true

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeU29(t *testing.T) {
	dec := new(Decoder)

	for _, tc := range u29TestCases {
		buf := bytes.NewBuffer(tc.expect)
		n, err := dec.decodeU29(buf)
		if err != nil {
			t.Errorf("DecodeAmf3Integer error: %s", err)
		}
		if n != tc.value {
			t.Errorf("DecodeAmf3Integer expect n %x got %x", tc.value, n)
		}
	}
}

func TestDecodeAmf3Integer(t *testing.T) {
	dec := new(Decoder)

	buf := bytes.NewReader([]byte{0x04, 0xFF, 0xFF, 0x7F})
	expect := int32(2097151)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	buf.Seek(0, 0)
	got, err = dec.DecodeAmf3Integer(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}

	buf.Seek(1, 0)
	got, err = dec.DecodeAmf3Integer(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf3Double(t *testing.T) {
	buf := bytes.NewReader([]byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})
	expect := float64(1.2)

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf3String(t *testing.T) {
	buf := bytes.NewReader([]byte{0x06, 0x07, 'f', 'o', 'o'})
	expect := "foo"

	dec := new(Decoder)

	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("%s", err)
	}
	if expect != got {
		t.Errorf("expect %v got %v", expect, got)
	}
}

func TestDecodeAmf3Array(t *testing.T) {
	buf := bytes.NewReader([]byte{0x09, 0x13, 0x01,
		0x06, 0x03, '1',
		0x06, 0x03, '2',
		0x06, 0x03, '3',
		0x06, 0x03, '4',
		0x06, 0x03, '5',
		0x06, 0x03, '6',
		0x06, 0x03, '7',
		0x06, 0x03, '8',
		0x06, 0x03, '9',
	})

	dec := new(Decoder)
	expect := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}
	got, err := dec.DecodeAmf3Array(buf, true)
	if err != nil {
		t.Errorf("err: %s", err)
	}

	for i, v := range expect {
		if got[i] != v {
			t.Errorf("expected array element %d to be %v, got %v", i, v, got[i])
		}
	}
}

func TestDecodeAmf3Object(t *testing.T) {
	buf := bytes.NewReader([]byte{
		0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',
		'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',
		's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',
		'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',
	})

	dec := new(Decoder)
	got, err := dec.DecodeAmf3(buf)
	if err != nil {
		t.Errorf("err: %s", err)
	}

	to, ok := got.(Object)
	if ok != true {
		t.Error("unable to cast object as typed object")
	}

	if to["foo"] != "bar" {
		t.Errorf("expected foo to be bar, got: %+v", to["foo"])
	}

	if to["baz"] != nil {
		t.Errorf("expected baz to be nil, got: %+v", to["baz"])
	}
}


================================================
FILE: protocol/amf/encoder_amf0.go
================================================
package amf

import (
	"encoding/binary"
	"fmt"
	"io"
	"reflect"
)

// amf0 polymorphic router
func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) {
	if val == nil {
		return e.EncodeAmf0Null(w, true)
	}

	v := reflect.ValueOf(val)
	if !v.IsValid() {
		return e.EncodeAmf0Null(w, true)
	}

	switch v.Kind() {
	case reflect.String:
		str := v.String()
		if len(str) <= AMF0_STRING_MAX {
			return e.EncodeAmf0String(w, str, true)
		} else {
			return e.EncodeAmf0LongString(w, str, true)
		}
	case reflect.Bool:
		return e.EncodeAmf0Boolean(w, v.Bool(), true)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return e.EncodeAmf0Number(w, float64(v.Int()), true)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return e.EncodeAmf0Number(w, float64(v.Uint()), true)
	case reflect.Float32, reflect.Float64:
		return e.EncodeAmf0Number(w, float64(v.Float()), true)
	case reflect.Array, reflect.Slice:
		length := v.Len()
		arr := make(Array, length)
		for i := 0; i < length; i++ {
			arr[i] = v.Index(int(i)).Interface()
		}
		return e.EncodeAmf0StrictArray(w, arr, true)
	case reflect.Map:
		obj, ok := val.(Object)
		if ok != true {
			return 0, fmt.Errorf("encode amf0: unable to create object from map")
		}
		return e.EncodeAmf0Object(w, obj, true)
	}

	if _, ok := val.(TypedObject); ok {
		return 0, fmt.Errorf("encode amf0: unsupported type typed object")
	}

	return 0, fmt.Errorf("encode amf0: unsupported type %s", v.Type())
}

// marker: 1 byte 0x00
// format: 8 byte big endian float64
func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_NUMBER_MARKER); err != nil {
			return
		}
		n += 1
	}

	err = binary.Write(w, binary.BigEndian, &val)
	if err != nil {
		return
	}
	n += 8

	return
}

// marker: 1 byte 0x01
// format: 1 byte, 0x00 = false, 0x01 = true
func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_BOOLEAN_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	buf := make([]byte, 1)
	if val {
		buf[0] = AMF0_BOOLEAN_TRUE
	} else {
		buf[0] = AMF0_BOOLEAN_FALSE
	}

	m, err = w.Write(buf)
	if err != nil {
		return
	}
	n += m

	return
}

// marker: 1 byte 0x02
// format:
// - 2 byte big endian uint16 header to determine size
// - n (size) byte utf8 string
func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_STRING_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	length := uint16(len(val))
	err = binary.Write(w, binary.BigEndian, length)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode string length: %s", err)
	}
	n += 2

	m, err = w.Write([]byte(val))
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode string value: %s", err)
	}
	n += m

	return
}

// marker: 1 byte 0x03
// format:
// - loop encoded string followed by encoded value
// - terminated with empty string followed by 1 byte 0x09
func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_OBJECT_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	for k, v := range val {
		m, err = e.EncodeAmf0String(w, k, false)
		if err != nil {
			return n, fmt.Errorf("encode amf0: unable to encode object key: %s", err)
		}
		n += m

		m, err = e.EncodeAmf0(w, v)
		if err != nil {
			return n, fmt.Errorf("encode amf0: unable to encode object value: %s", err)
		}
		n += m
	}

	m, err = e.EncodeAmf0String(w, "", false)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode object empty string: %s", err)
	}
	n += m

	err = WriteMarker(w, AMF0_OBJECT_END_MARKER)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to object end marker: %s", err)
	}
	n += 1

	return
}

// marker: 1 byte 0x05
// no additional data
func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_NULL_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x06
// no additional data
func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_UNDEFINED_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x08
// format:
// - 4 byte big endian uint32 with length of associative array
// - normal object format:
//   - loop encoded string followed by encoded value
//   - terminated with empty string followed by 1 byte 0x09
func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_ECMA_ARRAY_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	length := uint32(len(val))
	err = binary.Write(w, binary.BigEndian, length)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode ecma array length: %s", err)
	}
	n += 4

	m, err = e.EncodeAmf0Object(w, val, false)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode ecma array object: %s", err)
	}
	n += m

	return
}

// marker: 1 byte 0x0a
// format:
// - 4 byte big endian uint32 to determine length of associative array
// - n (length) encoded values
func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_STRICT_ARRAY_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	length := uint32(len(val))
	err = binary.Write(w, binary.BigEndian, length)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode strict array length: %s", err)
	}
	n += 4

	for _, v := range val {
		m, err = e.EncodeAmf0(w, v)
		if err != nil {
			return n, fmt.Errorf("encode amf0: unable to encode strict array element: %s", err)
		}
		n += m
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - 4 byte big endian uint32 header to determine size
// - n (size) byte utf8 string
func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_LONG_STRING_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	length := uint32(len(val))
	err = binary.Write(w, binary.BigEndian, length)
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode long string length: %s", err)
	}
	n += 4

	m, err = w.Write([]byte(val))
	if err != nil {
		return n, fmt.Errorf("encode amf0: unable to encode long string value: %s", err)
	}
	n += m

	return
}

// marker: 1 byte 0x0d
// no additional data
func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF0_UNSUPPORTED_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x11
func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error {
	return WriteMarker(w, AMF0_ACMPLUS_OBJECT_MARKER)
}


================================================
FILE: protocol/amf/encoder_amf0_test.go
================================================
package amf

import (
	"bytes"
	"encoding/binary"
	"testing"
)

func TestEncodeAmf0Number(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}

	enc := new(Encoder)

	n, err := enc.EncodeAmf0(buf, float64(1.2))
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 9 {
		t.Errorf("expected to write 9 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0BooleanTrue(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x01, 0x01}

	enc := new(Encoder)

	n, err := enc.EncodeAmf0(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 2 {
		t.Errorf("expected to write 2 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0BooleanFalse(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x01, 0x00}

	enc := new(Encoder)

	n, err := enc.EncodeAmf0(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 2 {
		t.Errorf("expected to write 2 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0String(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f}

	enc := new(Encoder)

	n, err := enc.EncodeAmf0(buf, "foo")
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 6 {
		t.Errorf("expected to write 6 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0Object(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}

	enc := new(Encoder)

	obj := make(Object)
	obj["foo"] = "bar"

	n, err := enc.EncodeAmf0(buf, obj)
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 15 {
		t.Errorf("expected to write 15 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0EcmaArray(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}

	enc := new(Encoder)

	obj := make(Object)
	obj["foo"] = "bar"

	_, err := enc.EncodeAmf0EcmaArray(buf, obj, true)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0StrictArray(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05}

	enc := new(Encoder)

	arr := make(Array, 3)
	arr[0] = float64(5)
	arr[1] = "foo"
	arr[2] = nil

	_, err := enc.EncodeAmf0StrictArray(buf, arr, true)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0Null(t *testing.T) {
	buf := new(bytes.Buffer)
	expect := []byte{0x05}

	enc := new(Encoder)

	n, err := enc.EncodeAmf0(buf, nil)
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 1 {
		t.Errorf("expected to write 1 byte, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf0LongString(t *testing.T) {
	buf := new(bytes.Buffer)

	testBytes := []byte("12345678")

	tbuf := new(bytes.Buffer)
	for i := 0; i < 65536; i++ {
		tbuf.Write(testBytes)
	}

	enc := new(Encoder)

	_, err := enc.EncodeAmf0(buf, string(tbuf.Bytes()))
	if err != nil {
		t.Errorf("%s", err)
	}

	mbuf := make([]byte, 1)
	_, err = buf.Read(mbuf)
	if err != nil {
		t.Errorf("error reading header")
	}

	if mbuf[0] != 0x0c {
		t.Errorf("marker mismatch")
	}

	var length uint32
	err = binary.Read(buf, binary.BigEndian, &length)
	if err != nil {
		t.Errorf("error reading buffer")
	}
	if length != (65536 * 8) {
		t.Errorf("expected length to be %d, got %d", (65536 * 8), length)
	}

	tmpBuf := make([]byte, 8)
	counter := 0
	for buf.Len() > 0 {
		n, err := buf.Read(tmpBuf)
		if err != nil {
			t.Fatalf("test long string result check, read data(%d) error: %s, n: %d", counter, err, n)
		}
		if n != 8 {
			t.Fatalf("test long string result check, read data(%d) n: %d", counter, n)
		}
		if !bytes.Equal(testBytes, tmpBuf) {
			t.Fatalf("test long string result check, read data % x", tmpBuf)
		}

		counter++
	}
}


================================================
FILE: protocol/amf/encoder_amf3.go
================================================
package amf

import (
	"encoding/binary"
	"fmt"
	"io"
	"reflect"
	"sort"
	"time"
)

// amf3 polymorphic router

func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
	if val == nil {
		return e.EncodeAmf3Null(w, true)
	}

	v := reflect.ValueOf(val)
	if !v.IsValid() {
		return e.EncodeAmf3Null(w, true)
	}

	switch v.Kind() {
	case reflect.String:
		return e.EncodeAmf3String(w, v.String(), true)
	case reflect.Bool:
		if v.Bool() {
			return e.EncodeAmf3True(w, true)
		} else {
			return e.EncodeAmf3False(w, true)
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
		n := v.Int()
		if n >= 0 && n <= AMF3_INTEGER_MAX {
			return e.EncodeAmf3Integer(w, uint32(n), true)
		} else {
			return e.EncodeAmf3Double(w, float64(n), true)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
		n := v.Uint()
		if n <= AMF3_INTEGER_MAX {
			return e.EncodeAmf3Integer(w, uint32(n), true)
		} else {
			return e.EncodeAmf3Double(w, float64(n), true)
		}
	case reflect.Int64:
		return e.EncodeAmf3Double(w, float64(v.Int()), true)
	case reflect.Uint64:
		return e.EncodeAmf3Double(w, float64(v.Uint()), true)
	case reflect.Float32, reflect.Float64:
		return e.EncodeAmf3Double(w, float64(v.Float()), true)
	case reflect.Array, reflect.Slice:
		length := v.Len()
		arr := make(Array, length)
		for i := 0; i < length; i++ {
			arr[i] = v.Index(int(i)).Interface()
		}
		return e.EncodeAmf3Array(w, arr, true)
	case reflect.Map:
		obj, ok := val.(Object)
		if ok != true {
			return 0, fmt.Errorf("encode amf3: unable to create object from map")
		}

		to := *new(TypedObject)
		to.Object = obj

		return e.EncodeAmf3Object(w, to, true)
	}

	if tm, ok := val.(time.Time); ok {
		return e.EncodeAmf3Date(w, tm, true)
	}

	if to, ok := val.(TypedObject); ok {
		return e.EncodeAmf3Object(w, to, true)
	}

	return 0, fmt.Errorf("encode amf3: unsupported type %s", v.Type())
}

// marker: 1 byte 0x00
// no additional data
func (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_UNDEFINED_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x01
// no additional data
func (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_NULL_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x02
// no additional data
func (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_FALSE_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x03
// no additional data
func (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_TRUE_MARKER); err != nil {
			return
		}
		n += 1
	}

	return
}

// marker: 1 byte 0x04
func (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_INTEGER_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	m, err = e.encodeAmf3Uint29(w, val)
	if err != nil {
		return
	}
	n += m

	return
}

// marker: 1 byte 0x05
func (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_DOUBLE_MARKER); err != nil {
			return
		}
		n += 1
	}

	err = binary.Write(w, binary.BigEndian, &val)
	if err != nil {
		return
	}
	n += 8

	return
}

// marker: 1 byte 0x06
// format:
// - u29 reference int. if reference, no more data. if not reference,
//   length value of bytes to read to complete string.
func (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_STRING_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int

	m, err = e.encodeAmf3Utf8(w, val)
	if err != nil {
		return
	}
	n += m

	return
}

// marker: 1 byte 0x08
// format:
// - u29 reference int, if reference, no more data
// - timestamp double
func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_DATE_MARKER); err != nil {
			return
		}
		n += 1
	}

	if err = WriteMarker(w, 0x01); err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
	}
	n += 1

	u64 := float64(val.Unix()) * 1000.0
	err = binary.Write(w, binary.BigEndian, &u64)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: unable to write date double: %s", err)
	}
	n += 8

	return
}

// marker: 1 byte 0x09
// format:
// - u29 reference int. if reference, no more data.
// - string representing associative array if present
// - n values (length of u29)
func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_ARRAY_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int
	length := uint32(len(val))
	u29 := uint32(length<<1) | 0x01

	m, err = e.encodeAmf3Uint29(w, u29)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode u29 for array: %s", err)
	}
	n += m

	m, err = e.encodeAmf3Utf8(w, "")
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode empty string for array: %s", err)
	}
	n += m

	for _, v := range val {
		m, err := e.EncodeAmf3(w, v)
		if err != nil {
			return n, fmt.Errorf("amf3 encode: cannot encode array element: %s", err)
		}
		n += m
	}

	return
}

// marker: 1 byte 0x0a
// format: ugh
func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_OBJECT_MARKER); err != nil {
			return
		}
		n += 1
	}

	m := 0

	trait := *NewTrait()
	trait.Type = val.Type
	trait.Dynamic = false
	trait.Externalizable = false

	for k, _ := range val.Object {
		trait.Properties = append(trait.Properties, k)
	}

	sort.Strings(trait.Properties)

	var u29 uint32 = 0x03
	if trait.Dynamic {
		u29 |= 0x02 << 2
	}

	if trait.Externalizable {
		u29 |= 0x01 << 2
	}

	u29 |= uint32(len(trait.Properties)) << 4

	m, err = e.encodeAmf3Uint29(w, u29)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode trait header for object: %s", err)
	}
	n += m

	m, err = e.encodeAmf3Utf8(w, trait.Type)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode trait type for object: %s", err)
	}
	n += m

	for _, prop := range trait.Properties {
		m, err = e.encodeAmf3Utf8(w, prop)
		if err != nil {
			return n, fmt.Errorf("amf3 encode: cannot encode trait property for object: %s", err)
		}
		n += m
	}

	if trait.Externalizable {
		return n, fmt.Errorf("amf3 encode: cannot encode externalizable object")
	}

	for _, prop := range trait.Properties {
		m, err = e.EncodeAmf3(w, val.Object[prop])
		if err != nil {
			return n, fmt.Errorf("amf3 encode: cannot encode sealed object value: %s", err)
		}
		n += m
	}

	if trait.Dynamic {
		for k, v := range val.Object {
			var foundProp bool = false
			for _, prop := range trait.Properties {
				if prop == k {
					foundProp = true
					break
				}
			}

			if foundProp != true {
				m, err = e.encodeAmf3Utf8(w, k)
				if err != nil {
					return n, fmt.Errorf("amf3 encode: cannot encode dynamic object property key: %s", err)
				}
				n += m

				m, err = e.EncodeAmf3(w, v)
				if err != nil {
					return n, fmt.Errorf("amf3 encode: cannot encode dynamic object value: %s", err)
				}
				n += m
			}

			m, err = e.encodeAmf3Utf8(w, "")
			if err != nil {
				return n, fmt.Errorf("amf3 encode: cannot encode dynamic object ending marker string: %s", err)
			}
			n += m
		}
	}

	return
}

// marker: 1 byte 0x0c
// format:
// - u29 reference int. if reference, no more data. if not reference,
//   length value of bytes to read .
func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMarker bool) (n int, err error) {
	if encodeMarker {
		if err = WriteMarker(w, AMF3_BYTEARRAY_MARKER); err != nil {
			return
		}
		n += 1
	}

	var m int

	length := uint32(len(val))
	u29 := (length << 1) | 1

	m, err = e.encodeAmf3Uint29(w, u29)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode u29 for bytearray: %s", err)
	}
	n += m

	m, err = w.Write(val)
	if err != nil {
		return n, fmt.Errorf("encode amf3: unable to encode bytearray value: %s", err)
	}
	n += m

	return
}

func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err error) {
	length := uint32(len(val))
	u29 := uint32(length<<1) | 0x01

	var m int
	m, err = e.encodeAmf3Uint29(w, u29)
	if err != nil {
		return n, fmt.Errorf("amf3 encode: cannot encode u29 for string: %s", err)
	}
	n += m

	m, err = w.Write([]byte(val))
	if err != nil {
		return n, fmt.Errorf("encode amf3: unable to encode string value: %s", err)
	}
	n += m

	return
}

func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err error) {
	if val <= 0x0000007F {
		err = WriteByte(w, byte(val))
		if err == nil {
			n += 1
		}
	} else if val <= 0x00003FFF {
		n, err = w.Write([]byte{byte(val>>7 | 0x80), byte(val & 0x7F)})
	} else if val <= 0x001FFFFF {
		n, err = w.Write([]byte{byte(val>>14 | 0x80), byte(val>>7&0x7F | 0x80), byte(val & 0x7F)})
	} 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)})
	} else {
		return n, fmt.Errorf("amf3 encode: cannot encode u29 with value %d (out of range)", val)
	}

	return
}


================================================
FILE: protocol/amf/encoder_amf3_test.go
================================================
package amf

import (
	"bytes"
	"testing"
)

func TestEncodeAmf3EmptyString(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x01}

	_, err := enc.EncodeAmf3String(buf, "", false)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Undefined(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x00}

	_, err := enc.EncodeAmf3Undefined(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Null(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x01}

	_, err := enc.EncodeAmf3(buf, nil)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3False(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x02}

	_, err := enc.EncodeAmf3(buf, false)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3True(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x03}

	_, err := enc.EncodeAmf3(buf, true)
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Integer(t *testing.T) {
	enc := new(Encoder)

	for _, tc := range u29TestCases {
		buf := new(bytes.Buffer)
		_, err := enc.EncodeAmf3Integer(buf, tc.value, false)
		if err != nil {
			t.Errorf("EncodeAmf3Integer error: %s", err)
		}
		got := buf.Bytes()
		if !bytes.Equal(tc.expect, got) {
			t.Errorf("EncodeAmf3Integer expect n %x got %x", tc.value, got)
		}
	}

	buf := new(bytes.Buffer)
	expect := []byte{0x04, 0x80, 0xFF, 0xFF, 0xFF}

	n, err := enc.EncodeAmf3(buf, uint32(4194303))
	if err != nil {
		t.Errorf("%s", err)
	}
	if n != 5 {
		t.Errorf("expected to write 5 bytes, actual %d", n)
	}
	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Double(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}

	_, err := enc.EncodeAmf3(buf, float64(1.2))
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3String(t *testing.T) {
	enc := new(Encoder)

	buf := new(bytes.Buffer)
	expect := []byte{0x06, 0x07, 'f', 'o', 'o'}

	_, err := enc.EncodeAmf3(buf, "foo")
	if err != nil {
		t.Errorf("%s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Array(t *testing.T) {
	enc := new(Encoder)
	buf := new(bytes.Buffer)
	expect := []byte{0x09, 0x13, 0x01,
		0x06, 0x03, '1',
		0x06, 0x03, '2',
		0x06, 0x03, '3',
		0x06, 0x03, '4',
		0x06, 0x03, '5',
		0x06, 0x03, '6',
		0x06, 0x03, '7',
		0x06, 0x03, '8',
		0x06, 0x03, '9',
	}

	arr := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}
	_, err := enc.EncodeAmf3(buf, arr)
	if err != nil {
		t.Errorf("err: %s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer: %+v, got: %+v", expect, buf.Bytes())
	}
}

func TestEncodeAmf3Object(t *testing.T) {
	enc := new(Encoder)
	buf := new(bytes.Buffer)
	expect := []byte{
		0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',
		'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',
		's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',
		'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',
	}

	to := *NewTypedObject()
	to.Type = "org.amf.ASClass"
	to.Object["foo"] = "bar"
	to.Object["baz"] = nil

	_, err := enc.EncodeAmf3(buf, to)
	if err != nil {
		t.Errorf("err: %s", err)
	}

	if bytes.Compare(buf.Bytes(), expect) != 0 {
		t.Errorf("expected buffer:\n%#v\ngot:\n%#v", expect, buf.Bytes())
	}
}


================================================
FILE: protocol/amf/metadata.go
================================================
package amf

import (
	"bytes"
	"fmt"

	log "github.com/sirupsen/logrus"
)

const (
	ADD = 0x0
	DEL = 0x3
)

const (
	SetDataFrame string = "@setDataFrame"
	OnMetaData   string = "onMetaData"
)

var setFrameFrame []byte

func init() {
	b := bytes.NewBuffer(nil)
	encoder := &Encoder{}
	if _, err := encoder.Encode(b, SetDataFrame, AMF0); err != nil {
		log.Fatal(err)
	}
	setFrameFrame = b.Bytes()
}

func MetaDataReform(p []byte, flag uint8) ([]byte, error) {
	r := bytes.NewReader(p)
	decoder := &Decoder{}
	switch flag {
	case ADD:
		v, err := decoder.Decode(r, AMF0)
		if err != nil {
			return nil, err
		}
		switch v.(type) {
		case string:
			vv := v.(string)
			if vv != SetDataFrame {
				tmplen := len(setFrameFrame)
				b := make([]byte, tmplen+len(p))
				copy(b, setFrameFrame)
				copy(b[tmplen:], p)
				p = b
			}
		default:
			return nil, fmt.Errorf("setFrameFrame error")
		}
	case DEL:
		v, err := decoder.Decode(r, AMF0)
		if err != nil {
			return nil, err
		}
		switch v.(type) {
		case string:
			vv := v.(string)
			if vv == SetDataFrame {
				p = p[len(setFrameFrame):]
			}
		default:
			return nil, fmt.Errorf("metadata error")
		}
	default:
		return nil, fmt.Errorf("invalid flag:%d", flag)
	}
	return p, nil
}


================================================
FILE: protocol/amf/util.go
================================================
package amf

import (
	"encoding/json"
	"fmt"
	"io"
)

func DumpBytes(label string, buf []byte, size int) {
	fmt.Printf("Dumping %s (%d bytes):\n", label, size)
	for i := 0; i < size; i++ {
		fmt.Printf("0x%02x ", buf[i])
	}
	fmt.Printf("\n")
}

func Dump(label string, val interface{}) error {
	json, err := json.MarshalIndent(val, "", "  ")
	if err != nil {
		return fmt.Errorf("Error dumping %s: %s", label, err)
	}

	fmt.Printf("Dumping %s:\n%s\n", label, json)
	return nil
}

func WriteByte(w io.Writer, b byte) (err error) {
	bytes := make([]byte, 1)
	bytes[0] = b

	_, err = WriteBytes(w, bytes)

	return
}

func WriteBytes(w io.Writer, bytes []byte) (int, error) {
	return w.Write(bytes)
}

func ReadByte(r io.Reader) (byte, error) {
	bytes, err := ReadBytes(r, 1)
	if err != nil {
		return 0x00, err
	}

	return bytes[0], nil
}

func ReadBytes(r io.Reader, n int) ([]byte, error) {
	bytes := make([]byte, n)

	m, err := r.Read(bytes)
	if err != nil {
		return bytes, err
	}

	if m != n {
		return bytes, fmt.Errorf("decode read bytes failed: expected %d got %d", m, n)
	}

	return bytes, nil
}

func WriteMarker(w io.Writer, m byte) error {
	return WriteByte(w, m)
}

func ReadMarker(r io.Reader) (byte, error) {
	return ReadByte(r)
}

func AssertMarker(r io.Reader, checkMarker bool, m byte) error {
	if checkMarker == false {
		return nil
	}

	marker, err := ReadMarker(r)
	if err != nil {
		return err
	}

	if marker != m {
		return fmt.Errorf("decode assert marker failed: expected %v got %v", m, marker)
	}

	return nil
}


================================================
FILE: protocol/api/api.go
================================================
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()

	room := ""

	if err := req.ParseForm(); err == nil {
		room = req.Form.Get("room")
	}

	rtmpStream := server.handler.(*rtmp.RtmpStream)
	if rtmpStream == nil {
		res.Status = 500
		res.Data = "Get rtmp stream information error"
		return
	}

	msgs := new(streams)

	if room == "" {
		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
		})
	} else {
		// Warning: The room should be in the "live/stream" format!
		roomInfo, exists := (rtmpStream.GetStreams()).Load(room)
		if exists == false {
			res.Status = 404
			res.Data = "room not found or inactive"
			return
		}

		if s, ok := roomInfo.(*rtmp.Stream); ok {
			if s.GetReader() != nil {
				switch s.GetReader().(type) {
				case *rtmp.VirReader:
					v := s.GetReader().(*rtmp.VirReader)
					msg := stream{room, v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS,
						v.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS}
					msgs.Publishers = append(msgs.Publishers, msg)
				}
			}

			s.GetWs().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{room, 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
			})
		}
	}

	//resp, _ := json.Marshal(msgs)
	res.Data = msgs
}

//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 {
			res.Status = 400
			retString = fmt.Sprintf("push error=%v", err)
		} else {
			s.session[keyString] = pullRtmprelay
			retString = fmt.Sprintf("<h1>pull url start %s ok</h1></br>", url)
		}

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


================================================
FILE: protocol/hls/align.go
================================================
package hls

const (
	syncms = 2 // ms
)

type align struct {
	frameNum  uint64
	frameBase uint64
}

func (a *align) align(dts *uint64, inc uint32) {
	aFrameDts := *dts
	estPts := a.frameBase + a.frameNum*uint64(inc)
	var dPts uint64
	if estPts >= aFrameDts {
		dPts = estPts - aFrameDts
	} else {
		dPts = aFrameDts - estPts
	}

	if dPts <= uint64(syncms)*h264_default_hz {
		a.frameNum++
		*dts = estPts
		return
	}
	a.frameNum = 1
	a.frameBase = aFrameDts
}


================================================
FILE: protocol/hls/audio_cache.go
================================================
package hls

import "bytes"

const (
	cache_max_frames byte = 6
	audio_cache_len  int  = 10 * 1024
)

type audioCache struct {
	soundFormat byte
	num         byte
	offset      int
	pts         uint64
	buf         *bytes.Buffer
}

func newAudioCache() *audioCache {
	return &audioCache{
		buf: bytes.NewBuffer(make([]byte, audio_cache_len)),
	}
}

func (a *audioCache) Cache(src []byte, pts uint64) bool {
	if a.num == 0 {
		a.offset = 0
		a.pts = pts
		a.buf.Reset()
	}
	a.buf.Write(src)
	a.offset += len(src)
	a.num++

	return false
}

func (a *audioCache) GetFrame() (int, uint64, []byte) {
	a.num = 0
	return a.offset, a.pts, a.buf.Bytes()
}

func (a *audioCache) CacheNum() byte {
	return a.num
}


================================================
FILE: protocol/hls/cache.go
================================================
package hls

import (
	"bytes"
	"container/list"
	"fmt"
	"sync"
)

const (
	maxTSCacheNum = 3
)

var (
	ErrNoKey = fmt.Errorf("No key for cache")
)

type TSCacheItem struct {
	id   string
	num  int
	lock sync.RWMutex
	ll   *list.List
	lm   map[string]TSItem
}

func NewTSCacheItem(id string) *TSCacheItem {
	return &TSCacheItem{
		id:  id,
		ll:  list.New(),
		num: maxTSCacheNum,
		lm:  make(map[string]TSItem),
	}
}

func (tcCacheItem *TSCacheItem) ID() string {
	return tcCacheItem.id
}

// TODO: found data race, fix it
func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) {
	var seq int
	var getSeq bool
	var maxDuration int
	m3u8body := bytes.NewBuffer(nil)
	for e := tcCacheItem.ll.Front(); e != nil; e = e.Next() {
		key := e.Value.(string)
		v, ok := tcCacheItem.lm[key]
		if ok {
			if v.Duration > maxDuration {
				maxDuration = v.Duration
			}
			if !getSeq {
				getSeq = true
				seq = v.SeqNum
			}
			fmt.Fprintf(m3u8body, "#EXTINF:%.3f,\n%s\n", float64(v.Duration)/float64(1000), v.Name)
		}
	}
	w := bytes.NewBuffer(nil)
	fmt.Fprintf(w,
		"#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:NO\n#EXT-X-TARGETDURATION:%d\n#EXT-X-MEDIA-SEQUENCE:%d\n\n",
		maxDuration/1000+1, seq)
	w.Write(m3u8body.Bytes())
	return w.Bytes(), nil
}

func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) {
	if tcCacheItem.ll.Len() == tcCacheItem.num {
		e := tcCacheItem.ll.Front()
		tcCacheItem.ll.Remove(e)
		k := e.Value.(string)
		delete(tcCacheItem.lm, k)
	}
	tcCacheItem.lm[key] = item
	tcCacheItem.ll.PushBack(key)
}

func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) {
	item, ok := tcCacheItem.lm[key]
	if !ok {
		return item, ErrNoKey
	}
	return item, nil
}


================================================
FILE: protocol/hls/hls.go
================================================
package hls

import (
	"fmt"
	"net"
	"net/http"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gwuhaolin/livego/configure"

	"github.com/gwuhaolin/livego/av"

	log "github.com/sirupsen/logrus"
)

const (
	duration = 3000
)

var (
	ErrNoPublisher         = fmt.Errorf("no publisher")
	ErrInvalidReq          = fmt.Errorf("invalid req url path")
	ErrNoSupportVideoCodec = fmt.Errorf("no support video codec")
	ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec")
)

var crossdomainxml = []byte(`<?xml version="1.0" ?>
<cross-domain-policy>
	<allow-access-from domain="*" />
	<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>`)

type Server struct {
	listener net.Listener
	conns    *sync.Map
}

func NewServer() *Server {
	ret := &Server{
		conns: &sync.Map{},
	}
	go ret.checkStop()
	return ret
}

func (server *Server) Serve(listener net.Listener) error {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		server.handle(w, r)
	})
	server.listener = listener

	if configure.Config.GetBool("use_hls_https") {
		http.ServeTLS(listener, mux, "server.crt", "server.key")
	} else {
		http.Serve(listener, mux)
	}

	return nil
}

func (server *Server) GetWriter(info av.Info) av.WriteCloser {
	var s *Source
	v, ok := server.conns.Load(info.Key)
	if !ok {
		log.Debug("new hls source")
		s = NewSource(info)
		server.conns.Store(info.Key, s)
	} else {
		s = v.(*Source)
	}
	return s
}

func (server *Server) getConn(key string) *Source {
	v, ok := server.conns.Load(key)
	if !ok {
		return nil
	}
	return v.(*Source)
}

func (server *Server) checkStop() {
	for {
		<-time.After(5 * time.Second)

		server.conns.Range(func(key, val interface{}) bool {
			v := val.(*Source)
			if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") {
				log.Debug("check stop and remove: ", v.Info())
				server.conns.Delete(key)
			}
			return true
		})
	}
}

func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
	if path.Base(r.URL.Path) == "crossdomain.xml" {
		w.Header().Set("Content-Type", "application/xml")
		w.Write(crossdomainxml)
		return
	}
	switch path.Ext(r.URL.Path) {
	case ".m3u8":
		key, _ := server.parseM3u8(r.URL.Path)
		conn := server.getConn(key)
		if conn == nil {
			http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
			return
		}
		tsCache := conn.GetCacheInc()
		if tsCache == nil {
			http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
			return
		}
		body, err := tsCache.GenM3U8PlayList()
		if err != nil {
			log.Debug("GenM3U8PlayList error: ", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Content-Type", "application/x-mpegURL")
		w.Header().Set("Content-Length", strconv.Itoa(len(body)))
		w.Write(body)
	case ".ts":
		key, _ := server.parseTs(r.URL.Path)
		conn := server.getConn(key)
		if conn == nil {
			http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)
			return
		}
		tsCache := conn.GetCacheInc()
		item, err := tsCache.GetItem(r.URL.Path)
		if err != nil {
			log.Debug("GetItem error: ", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "video/mp2ts")
		w.Header().Set("Content-Length", strconv.Itoa(len(item.Data)))
		w.Write(item.Data)
	}
}

func (server *Server) parseM3u8(pathstr string) (key string, err error) {
	pathstr = strings.TrimLeft(pathstr, "/")
	key = strings.Split(pathstr, path.Ext(pathstr))[0]
	return
}

func (server *Server) parseTs(pathstr string) (key string, err error) {
	pathstr = strings.TrimLeft(pathstr, "/")
	paths := strings.SplitN(pathstr, "/", 3)
	if len(paths) != 3 {
		err = fmt.Errorf("invalid path=%s", pathstr)
		return
	}
	key = paths[0] + "/" + paths[1]

	return
}


================================================
FILE: protocol/hls/item.go
================================================
package hls

type TSItem struct {
	Name     string
	SeqNum   int
	Duration int
	Data     []byte
}

func NewTSItem(name string, duration, seqNum int, b []byte) TSItem {
	var item TSItem
	item.Name = name
	item.SeqNum = seqNum
	item.Duration = duration
	item.Data = make([]byte, len(b))
	copy(item.Data, b)
	return item
}


================================================
FILE: protocol/hls/source.go
================================================
package hls

import (
	"bytes"
	"fmt"
	"github.com/gwuhaolin/livego/configure"
	"time"

	"github.com/gwuhaolin/livego/av"
	"github.com/gwuhaolin/livego/container/flv"
	"github.com/gwuhaolin/livego/container/ts"
	"github.com/gwuhaolin/livego/parser"

	log "github.com/sirupsen/logrus"
)

const (
	videoHZ      = 90000
	aacSampleLen = 1024
	maxQueueNum  = 512

	h264_default_hz uint64 = 90
)

type Source struct {
	av.RWBaser
	seq         int
	info        av.Info
	bwriter     *bytes.Buffer
	btswriter   *bytes.Buffer
	demuxer     *flv.Demuxer
	muxer       *ts.Muxer
	pts, dts    uint64
	stat        *status
	align       *align
	cache       *audioCache
	tsCache     *TSCacheItem
	tsparser    *parser.CodecParser
	closed      bool
	packetQueue chan *av.Packet
}

func NewSource(info av.Info) *Source {
	info.Inter = true
	s := &Source{
		info:        info,
		align:       &align{},
		stat:        newStatus(),
		RWBaser:     av.NewRWBaser(time.Second * 10),
		cache:       newAudioCache(),
		demuxer:     flv.NewDemuxer(),
		muxer:       ts.NewMuxer(),
		tsCache:     NewTSCacheItem(info.Key),
		tsparser:    parser.NewCodecParser(),
		bwriter:     bytes.NewBuffer(make([]byte, 100*1024)),
		packetQueue: make(chan *av.Packet, maxQueueNum),
	}
	go func() {
		err := s.SendPacket()
		if err != nil {
			log.Debug("send packet error: ", err)
			s.closed = true
		}
	}()
	return s
}

func (source *Source) GetCacheInc() *TSCacheItem {
	return source.tsCache
}

func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
	log.Warningf("[%v] packet queue max!!!", info)
	for i := 0; i < maxQueueNum-84; i++ {
		tmpPkt, ok := <-pktQue
		// try to don't drop audio
		if ok && tmpPkt.IsAudio {
			if len(pktQue) > maxQueueNum-2 {
				<-pktQue
			} else {
				pktQue <- tmpPkt
			}
		}

		if ok && tmpPkt.IsVideo {
			videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)
			// dont't drop sps config and dont't drop key frame
			if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {
				pktQue <- tmpPkt
			}
			if len(pktQue) > maxQueueNum-10 {
				<-pktQue
			}
		}

	}
	log.Debug("packet queue len: ", len(pktQue))
}

func (source *Source) Write(p *av.Packet) (err error) {
	err = nil
	if source.closed {
		err = fmt.Errorf("hls source closed")
		return
	}
	source.SetPreTime()
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("hls source has already been closed:%v", e)
		}
	}()
	if len(source.packetQueue) >= maxQueueNum-24 {
		source.DropPacket(source.packetQueue, source.info)
	} else {
		if !source.closed {
			source.packetQueue <- p
		}
	}
	return
}

func (source *Source) SendPacket() error {
	defer func() {
		log.Debugf("[%v] hls sender stop", source.info)
		if r := recover(); r != nil {
			log.Warning("hls SendPacket panic: ", r)
		}
	}()

	log.Debugf("[%v] hls sender start", source.info)
	for {
		if source.closed {
			return fmt.Errorf("closed")
		}

		p, ok := <-source.packetQueue
		if ok {
			if p.IsMetadata {
				continue
			}

			err := source.demuxer.Demux(p)
			if err == flv.ErrAvcEndSEQ {
				log.Warning(err)
				continue
			} else {
				if err != nil {
					log.Warning(err)
					return err
				}
			}
			compositionTime, isSeq, err := source.parse(p)
			if err != nil {
				log.Warning(err)
			}
			if err != nil || isSeq {
				continue
			}
			if source.btswriter != nil {
				source.stat.update(p.IsVideo, p.TimeStamp)
				source.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime))
				source.tsMux(p)
			}
		} else {
			return fmt.Errorf("closed")
		}
	}
}

func (source *Source) Info() (ret av.Info) {
	return source.info
}

func (source *Source) cleanup() {
	close(source.packetQueue)
	source.bwriter = nil
	source.btswriter = nil
	source.cache = nil
	source.tsCache = nil
}

func (source *Source) Close(err error) {
	log.Debug("hls source closed: ", source.info)
	if !source.closed && !configure.Config.GetBool("hls_keep_after_end") {
		source.cleanup()
	}
	source.closed = true
}

func (source *Source) cut() {
	newf := true
	if source.btswriter == nil {
		source.btswriter = bytes.NewBuffer(nil)
	} else if source.btswriter != nil && source.stat.durationMs() >= duration {
		source.flushAudio()

		source.seq++
		filename := fmt.Sprintf("/%s/%d.ts", source.info.Key, time.Now().Unix())
		item := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes())
		source.tsCache.SetItem(filename, item)

		source.btswriter.Reset()
		source.stat.resetAndNew()
	} else {
		newf = false
	}
	if newf {
		source.btswriter.Write(source.muxer.PAT())
		source.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true))
	}
}

func (source *Source) parse(p *av.Packet) (int32, bool, error) {
	var compositionTime int32
	var ah av.AudioPacketHeader
	var vh av.VideoPacketHeader
	if p.IsVideo {
		vh = p.Header.(av.VideoPacketHeader)
		if vh.CodecID() != av.VIDEO_H264 {
			return compositionTime, false, ErrNoSupportVideoCodec
		}
		compositionTime = vh.CompositionTime()
		if vh.IsKeyFrame() && vh.IsSeq() {
			return compositionTime, true, source.tsparser.Parse(p, source.bwriter)
		}
	} else {
		ah = p.Header.(av.AudioPacketHeader)
		if ah.SoundFormat() != av.SOUND_AAC {
			return compositionTime, false, ErrNoSupportAudioCodec
		}
		if ah.AACPacketType() == av.AAC_SEQHDR {
			return compositionTime, true, source.tsparser.Parse(p, source.bwriter)
		}
	}
	source.bwriter.Reset()
	if err := source.tsparser.Parse(p, source.bwriter); err != nil {
		return compositionTime, false, err
	}
	p.Data = source.bwriter.Bytes()

	if p.IsVideo && vh.IsKeyFrame() {
		source.cut()
	}
	return compositionTime, false, nil
}

func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) {
	source.dts = uint64(ts) * h264_default_hz
	if isVideo {
		source.pts = source.dts + uint64(compositionTs)*h264_default_hz
	} else {
		sampleRate, _ := source.tsparser.SampleRate()
		source.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate))
		source.pts = source.dts
	}
}
func (source *Source) flushAudio() error {
	return source.muxAudio(1)
}

func (source *Source) muxAudio(limit byte) error {
	if source.cache.CacheNum() < limit {
		return nil
	}
	var p av.Packet
	_, pts, buf := source.cache.GetFrame()
	p.Data = buf
	p.TimeStamp = uint32(pts / h264_default_hz)
	return source.muxer.Mux(&p, source.btswriter)
}

func (source *Source) tsMux(p *av.Packet) error {
	if p.IsVideo {
		return source.muxer.Mux(p, source.btswriter)
	} else {
		source.cache.Cache(p.Data, source.pts)
		return source.muxAudio(cache_max_frames)
	}
}


================================================
FILE: protocol/hls/status.go
================================================
package hls

import "time"

type status struct {
	hasVideo       bool
	seqId          int64
	createdAt      time.Time
	segBeginAt     time.Time
	hasSetFirstTs  bool
	firstTimestamp int64
	lastTimestamp  int64
}

func newStatus() *status {
	return &status{
		seqId:         0,
		hasSetFirstTs: false,
		segBeginAt:    time.Now(),
	}
}

func (t *status) update(isVideo bool, timestamp uint32) {
	if isVideo {
		t.hasVideo = true
	}
	if !t.hasSetFirstTs {
		t.hasSetFirstTs = true
		t.firstTimestamp = int64(timestamp)
	}
	t.lastTimestamp = int64(timestamp)
}

func (t *status) resetAndNew() {
	t.seqId++
	t.hasVideo = false
	t.createdAt = time.Now()
	t.hasSetFirstTs = false
}

func (t *status) durationMs() int64 {
	return t.lastTimestamp - t.firstTimestamp
}


================================================
FILE: protocol/httpflv/server.go
================================================
package httpflv

import (
	"encoding/json"
	"net"
	"net/http"
	"strings"

	"github.com/gwuhaolin/livego/av"
	"github.com/gwuhaolin/livego/protocol/rtmp"

	log "github.com/sirupsen/logrus"
)

type Server struct {
	handler av.Handler
}

type stream struct {
	Key string `json:"key"`
	Id  string `json:"id"`
}

type streams struct {
	Publishers []stream `json:"publishers"`
	Players    []stream `json:"players"`
}

func NewServer(h av.Handler) *Server {
	return &Server{
		handler: h,
	}
}

func (server *Server) Serve(l net.Listener) error {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		server.handleConn(w, r)
	})
	mux.HandleFunc("/streams", func(w http.ResponseWriter, r *http.Request) {
		server.getStream(w, r)
	})
	if err := http.Serve(l, mux); err != nil {
		return err
	}
	return nil
}

// 获取发布和播放器的信息
func (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *streams {
	rtmpStream := server.handler.(*rtmp.RtmpStream)
	if rtmpStream == nil {
		return nil
	}
	msgs := new(streams)

	rtmpStream.GetStreams().Range(func(key, val interface{}) bool {
		if s, ok := val.(*rtmp.Stream); ok {
			if s.GetReader() != nil {
				msg := stream{key.(string), s.GetReader().Info().UID}
				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 {
					msg := stream{key.(string), pw.GetWriter().Info().UID}
					msgs.Players = append(msgs.Players, msg)
				}
			}
			return true
		})
		return true
	})

	return msgs
}

func (server *Server) getStream(w http.ResponseWriter, r *http.Request) {
	msgs := server.getStreams(w, r)
	if msgs == nil {
		return
	}
	resp, _ := json.Marshal(msgs)
	w.Header().Set("Content-Type", "application/json")
	w.Write(resp)
}

func (server *Server) handleConn(w http.ResponseWriter, r *http.Request) {
	defer func() {
		if r := recover(); r != nil {
			log.Error("http flv handleConn panic: ", r)
		}
	}()

	url := r.URL.String()
	u := r.URL.Path
	if pos := strings.LastIndex(u, "."); pos < 0 || u[pos:] != ".flv" {
		http.Error(w, "invalid path", http.StatusBadRequest)
		return
	}
	path := strings.TrimSuffix(strings.TrimLeft(u, "/"), ".flv")
	paths := strings.SplitN(path, "/", 2)
	log.Debug("url:", u, "path:", path, "paths:", paths)

	if len(paths) != 2 {
		http.Error(w, "invalid path", http.StatusBadRequest)
		return
	}

	// 判断视屏流是否发布,如果没有发布,直接返回404
	msgs := server.getStreams(w, r)
	if msgs == nil || len(msgs.Publishers) == 0 {
		http.Error(w, "invalid path", http.StatusNotFound)
		return
	} else {
		include := false
		for _, item := range msgs.Publishers {
			if item.Key == path {
				include = true
				break
			}
		}
		if include == false {
			http.Error(w, "invalid path", http.StatusNotFound)
			return
		}
	}

	w.Header().Set("Access-Control-Allow-Origin", "*")
	writer := NewFLVWriter(paths[0], paths[1], url, w)

	server.handler.HandleWriter(writer)
	writer.Wait()
}


================================================
FILE: protocol/httpflv/writer.go
=================
Download .txt
gitextract_je20vge0/

├── .github/
│   └── workflows/
│       ├── codeql.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── README_cn.md
├── SECURITY.md
├── av/
│   ├── av.go
│   └── rwbase.go
├── configure/
│   ├── channel.go
│   └── liveconfig.go
├── container/
│   ├── flv/
│   │   ├── demuxer.go
│   │   ├── muxer.go
│   │   └── tag.go
│   └── ts/
│       ├── crc32.go
│       ├── muxer.go
│       └── muxer_test.go
├── go.mod
├── go.sum
├── livego.yaml
├── main.go
├── parser/
│   ├── aac/
│   │   └── parser.go
│   ├── h264/
│   │   ├── parser.go
│   │   └── parser_test.go
│   ├── mp3/
│   │   └── parser.go
│   └── parser.go
├── protocol/
│   ├── amf/
│   │   ├── amf.go
│   │   ├── amf_test.go
│   │   ├── const.go
│   │   ├── decoder_amf0.go
│   │   ├── decoder_amf0_test.go
│   │   ├── decoder_amf3.go
│   │   ├── decoder_amf3_external.go
│   │   ├── decoder_amf3_test.go
│   │   ├── encoder_amf0.go
│   │   ├── encoder_amf0_test.go
│   │   ├── encoder_amf3.go
│   │   ├── encoder_amf3_test.go
│   │   ├── metadata.go
│   │   └── util.go
│   ├── api/
│   │   └── api.go
│   ├── hls/
│   │   ├── align.go
│   │   ├── audio_cache.go
│   │   ├── cache.go
│   │   ├── hls.go
│   │   ├── item.go
│   │   ├── source.go
│   │   └── status.go
│   ├── httpflv/
│   │   ├── server.go
│   │   └── writer.go
│   └── rtmp/
│       ├── cache/
│       │   ├── cache.go
│       │   ├── gop.go
│       │   └── special.go
│       ├── core/
│       │   ├── chunk_stream.go
│       │   ├── chunk_stream_test.go
│       │   ├── conn.go
│       │   ├── conn_client.go
│       │   ├── conn_server.go
│       │   ├── conn_test.go
│       │   ├── handshake.go
│       │   ├── read_writer.go
│       │   └── read_writer_test.go
│       ├── rtmp.go
│       ├── rtmprelay/
│       │   ├── rtmprelay.go
│       │   └── staticrelay.go
│       └── stream.go
├── test.go
└── utils/
    ├── pio/
    │   ├── pio.go
    │   ├── reader.go
    │   └── writer.go
    ├── pool/
    │   └── pool.go
    ├── queue/
    │   └── queue.go
    └── uid/
        ├── rand.go
        └── uuid.go
Download .txt
SYMBOL INDEX (690 symbols across 62 files)

FILE: av/av.go
  constant TAG_AUDIO (line 9) | TAG_AUDIO          = 8
  constant TAG_VIDEO (line 10) | TAG_VIDEO          = 9
  constant TAG_SCRIPTDATAAMF0 (line 11) | TAG_SCRIPTDATAAMF0 = 18
  constant TAG_SCRIPTDATAAMF3 (line 12) | TAG_SCRIPTDATAAMF3 = 0xf
  constant MetadatAMF0 (line 16) | MetadatAMF0  = 0x12
  constant MetadataAMF3 (line 17) | MetadataAMF3 = 0xf
  constant SOUND_MP3 (line 21) | SOUND_MP3                   = 2
  constant SOUND_NELLYMOSER_16KHZ_MONO (line 22) | SOUND_NELLYMOSER_16KHZ_MONO = 4
  constant SOUND_NELLYMOSER_8KHZ_MONO (line 23) | SOUND_NELLYMOSER_8KHZ_MONO  = 5
  constant SOUND_NELLYMOSER (line 24) | SOUND_NELLYMOSER            = 6
  constant SOUND_ALAW (line 25) | SOUND_ALAW                  = 7
  constant SOUND_MULAW (line 26) | SOUND_MULAW                 = 8
  constant SOUND_AAC (line 27) | SOUND_AAC                   = 10
  constant SOUND_SPEEX (line 28) | SOUND_SPEEX                 = 11
  constant SOUND_5_5Khz (line 30) | SOUND_5_5Khz = 0
  constant SOUND_11Khz (line 31) | SOUND_11Khz  = 1
  constant SOUND_22Khz (line 32) | SOUND_22Khz  = 2
  constant SOUND_44Khz (line 33) | SOUND_44Khz  = 3
  constant SOUND_8BIT (line 35) | SOUND_8BIT  = 0
  constant SOUND_16BIT (line 36) | SOUND_16BIT = 1
  constant SOUND_MONO (line 38) | SOUND_MONO   = 0
  constant SOUND_STEREO (line 39) | SOUND_STEREO = 1
  constant AAC_SEQHDR (line 41) | AAC_SEQHDR = 0
  constant AAC_RAW (line 42) | AAC_RAW    = 1
  constant AVC_SEQHDR (line 46) | AVC_SEQHDR = 0
  constant AVC_NALU (line 47) | AVC_NALU   = 1
  constant AVC_EOS (line 48) | AVC_EOS    = 2
  constant FRAME_KEY (line 50) | FRAME_KEY   = 1
  constant FRAME_INTER (line 51) | FRAME_INTER = 2
  constant VIDEO_H264 (line 53) | VIDEO_H264 = 7
  type Packet (line 62) | type Packet struct
  type PacketHeader (line 72) | type PacketHeader interface
  type AudioPacketHeader (line 75) | type AudioPacketHeader interface
  type VideoPacketHeader (line 81) | type VideoPacketHeader interface
  type Demuxer (line 89) | type Demuxer interface
  type Muxer (line 93) | type Muxer interface
  type SampleRater (line 97) | type SampleRater interface
  type CodecParser (line 101) | type CodecParser interface
  type GetWriter (line 106) | type GetWriter interface
  type Handler (line 110) | type Handler interface
  type Alive (line 115) | type Alive interface
  type Closer (line 119) | type Closer interface
  type CalcTime (line 124) | type CalcTime interface
  type Info (line 128) | type Info struct
    method IsInterval (line 135) | func (info Info) IsInterval() bool {
    method String (line 139) | func (info Info) String() string {
  type ReadCloser (line 144) | type ReadCloser interface
  type WriteCloser (line 150) | type WriteCloser interface

FILE: av/rwbase.go
  type RWBaser (line 8) | type RWBaser struct
    method BaseTimeStamp (line 24) | func (rw *RWBaser) BaseTimeStamp() uint32 {
    method CalcBaseTimestamp (line 28) | func (rw *RWBaser) CalcBaseTimestamp() {
    method RecTimeStamp (line 36) | func (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) {
    method SetPreTime (line 44) | func (rw *RWBaser) SetPreTime() {
    method Alive (line 50) | func (rw *RWBaser) Alive() bool {
  function NewRWBaser (line 17) | func NewRWBaser(duration time.Duration) RWBaser {

FILE: configure/channel.go
  type RoomKeysType (line 13) | type RoomKeysType struct
    method SetKey (line 45) | func (r *RoomKeysType) SetKey(channel string) (key string, err error) {
    method GetKey (line 74) | func (r *RoomKeysType) GetKey(channel string) (newKey string, err erro...
    method GetChannel (line 95) | func (r *RoomKeysType) GetChannel(key string) (channel string, err err...
    method DeleteChannel (line 108) | func (r *RoomKeysType) DeleteChannel(channel string) bool {
    method DeleteKey (line 122) | func (r *RoomKeysType) DeleteKey(key string) bool {
  function Init (line 24) | func Init() {

FILE: configure/liveconfig.go
  type Application (line 27) | type Application struct
  type Applications (line 36) | type Applications
  type JWT (line 38) | type JWT struct
  type ServerCfg (line 42) | type ServerCfg struct
  function initLog (line 97) | func initLog() {
  function init (line 104) | func init() {
  function initDefault (line 110) | func initDefault() {
  function CheckAppName (line 165) | func CheckAppName(appname string) bool {
  function GetStaticPushUrlList (line 176) | func GetStaticPushUrlList(appname string) ([]string, bool) {

FILE: container/flv/demuxer.go
  type Demuxer (line 12) | type Demuxer struct
    method DemuxH (line 19) | func (d *Demuxer) DemuxH(p *av.Packet) error {
    method Demux (line 30) | func (d *Demuxer) Demux(p *av.Packet) error {
  function NewDemuxer (line 15) | func NewDemuxer() *Demuxer {

FILE: container/flv/muxer.go
  constant headerLen (line 49) | headerLen = 11
  type FLVWriter (line 52) | type FLVWriter struct
    method Write (line 81) | func (writer *FLVWriter) Write(p *av.Packet) error {
    method Wait (line 127) | func (writer *FLVWriter) Wait() {
    method Close (line 134) | func (writer *FLVWriter) Close(error) {
    method Info (line 143) | func (writer *FLVWriter) Info() (ret av.Info) {
  function NewFLVWriter (line 62) | func NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter {
  type FlvDvr (line 150) | type FlvDvr struct
    method GetWriter (line 152) | func (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser {

FILE: container/flv/tag.go
  type flvTag (line 9) | type flvTag struct
  type mediaTag (line 16) | type mediaTag struct
  type Tag (line 104) | type Tag struct
    method SoundFormat (line 109) | func (tag *Tag) SoundFormat() uint8 {
    method AACPacketType (line 113) | func (tag *Tag) AACPacketType() uint8 {
    method IsKeyFrame (line 117) | func (tag *Tag) IsKeyFrame() bool {
    method IsSeq (line 121) | func (tag *Tag) IsSeq() bool {
    method CodecID (line 126) | func (tag *Tag) CodecID() uint8 {
    method CompositionTime (line 130) | func (tag *Tag) CompositionTime() int32 {
    method ParseMediaTagHeader (line 135) | func (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, er...
    method parseAudioHeader (line 145) | func (tag *Tag) parseAudioHeader(b []byte) (n int, err error) {
    method parseVideoHeader (line 164) | func (tag *Tag) parseVideoHeader(b []byte) (n int, err error) {

FILE: container/ts/crc32.go
  function GenCrc32 (line 3) | func GenCrc32(src []byte) uint32 {

FILE: container/ts/muxer.go
  constant tsDefaultDataLen (line 10) | tsDefaultDataLen = 184
  constant tsPacketLen (line 11) | tsPacketLen      = 188
  constant h264DefaultHZ (line 12) | h264DefaultHZ    = 90
  constant videoPID (line 14) | videoPID = 0x100
  constant audioPID (line 15) | audioPID = 0x101
  constant videoSID (line 16) | videoSID = 0xe0
  constant audioSID (line 17) | audioSID = 0xc0
  type Muxer (line 20) | type Muxer struct
    method Mux (line 34) | func (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error {
    method PAT (line 162) | func (muxer *Muxer) PAT() []byte {
    method PMT (line 199) | func (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte {
    method adaptationBufInit (line 258) | func (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) {
    method writePcr (line 270) | func (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error {
  function NewMuxer (line 30) | func NewMuxer() *Muxer {
  type pesHeader (line 286) | type pesHeader struct
    method packet (line 292) | func (header *pesHeader) packet(p *av.Packet, pts, dts int64) error {
    method writeTs (line 345) | func (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) {

FILE: container/ts/muxer_test.go
  type TestWriter (line 11) | type TestWriter struct
    method Write (line 17) | func (w *TestWriter) Write(p []byte) (int, error) {
  function TestTSEncoder (line 23) | func TestTSEncoder(t *testing.T) {

FILE: main.go
  function startHls (line 22) | func startHls() *hls.Server {
  function startRtmp (line 42) | func startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) {
  function startHTTPFlv (line 92) | func startHTTPFlv(stream *rtmp.RtmpStream) {
  function startAPI (line 112) | func startAPI(stream *rtmp.RtmpStream) {
  function init (line 134) | func init() {
  function main (line 144) | func main() {

FILE: parser/aac/parser.go
  type mpegExtension (line 10) | type mpegExtension struct
  type mpegCfgInfo (line 15) | type mpegCfgInfo struct
  constant adtsHeaderLen (line 34) | adtsHeaderLen = 7
  type Parser (line 37) | type Parser struct
    method specificInfo (line 51) | func (parser *Parser) specificInfo(src []byte) error {
    method adts (line 62) | func (parser *Parser) adts(src []byte, w io.Writer) error {
    method SampleRate (line 98) | func (parser *Parser) SampleRate() int {
    method Parse (line 106) | func (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (...
  function NewParser (line 43) | func NewParser() *Parser {

FILE: parser/h264/parser.go
  constant i_frame (line 10) | i_frame byte = 0
  constant p_frame (line 11) | p_frame byte = 1
  constant b_frame (line 12) | b_frame byte = 2
  constant nalu_type_not_define (line 16) | nalu_type_not_define byte = 0
  constant nalu_type_slice (line 17) | nalu_type_slice      byte = 1
  constant nalu_type_dpa (line 18) | nalu_type_dpa        byte = 2
  constant nalu_type_dpb (line 19) | nalu_type_dpb        byte = 3
  constant nalu_type_dpc (line 20) | nalu_type_dpc        byte = 4
  constant nalu_type_idr (line 21) | nalu_type_idr        byte = 5
  constant nalu_type_sei (line 22) | nalu_type_sei        byte = 6
  constant nalu_type_sps (line 23) | nalu_type_sps        byte = 7
  constant nalu_type_pps (line 24) | nalu_type_pps        byte = 8
  constant nalu_type_aud (line 25) | nalu_type_aud        byte = 9
  constant nalu_type_eoesq (line 26) | nalu_type_eoesq      byte = 10
  constant nalu_type_eostream (line 27) | nalu_type_eostream   byte = 11
  constant nalu_type_filler (line 28) | nalu_type_filler     byte = 12
  constant naluBytesLen (line 32) | naluBytesLen int = 4
  constant maxSpsPpsLen (line 33) | maxSpsPpsLen int = 2 * 1024
  type Parser (line 50) | type Parser struct
    method parseSpecificInfo (line 77) | func (parser *Parser) parseSpecificInfo(src []byte) error {
    method isNaluHeader (line 123) | func (parser *Parser) isNaluHeader(src []byte) bool {
    method naluSize (line 133) | func (parser *Parser) naluSize(src []byte) (int, error) {
    method getAnnexbH264 (line 145) | func (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error {
    method Parse (line 219) | func (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err er...
  type sequenceHeader (line 56) | type sequenceHeader struct
  function NewParser (line 70) | func NewParser() *Parser {

FILE: parser/h264/parser_test.go
  function TestH264SeqDemux (line 11) | func TestH264SeqDemux(t *testing.T) {
  function TestH264AnnexbDemux (line 28) | func TestH264AnnexbDemux(t *testing.T) {
  function TestH264NalueSizeException (line 42) | func TestH264NalueSizeException(t *testing.T) {
  function TestH264Mp4Demux (line 53) | func TestH264Mp4Demux(t *testing.T) {
  function TestH264Mp4DemuxException1 (line 70) | func TestH264Mp4DemuxException1(t *testing.T) {
  function TestH264Mp4DemuxException2 (line 82) | func TestH264Mp4DemuxException2(t *testing.T) {

FILE: parser/mp3/parser.go
  type Parser (line 7) | type Parser struct
    method Parse (line 26) | func (parser *Parser) Parse(src []byte) error {
    method SampleRate (line 38) | func (parser *Parser) SampleRate() int {
  function NewParser (line 11) | func NewParser() *Parser {

FILE: parser/parser.go
  type CodecParser (line 17) | type CodecParser struct
    method SampleRate (line 27) | func (codeParser *CodecParser) SampleRate() (int, error) {
    method Parse (line 37) | func (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err e...
  function NewCodecParser (line 23) | func NewCodecParser() *CodecParser {

FILE: protocol/amf/amf.go
  method DecodeBatch (line 8) | func (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface...
  method Decode (line 20) | func (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {
  method EncodeBatch (line 31) | func (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface...
  method Encode (line 40) | func (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int...

FILE: protocol/amf/amf_test.go
  function EncodeAndDecode (line 11) | func EncodeAndDecode(val interface{}, ver Version) (result interface{}, ...
  function Compare (line 30) | func Compare(val interface{}, ver Version, name string, t *testing.T) {
  function TestAmf0Number (line 51) | func TestAmf0Number(t *testing.T) {
  function TestAmf0String (line 57) | func TestAmf0String(t *testing.T) {
  function TestAmf0Boolean (line 62) | func TestAmf0Boolean(t *testing.T) {
  function TestAmf0Null (line 67) | func TestAmf0Null(t *testing.T) {
  function TestAmf0Object (line 71) | func TestAmf0Object(t *testing.T) {
  function TestAmf0Array (line 105) | func TestAmf0Array(t *testing.T) {
  function TestAmf3Integer (line 125) | func TestAmf3Integer(t *testing.T) {
  function TestAmf3Double (line 131) | func TestAmf3Double(t *testing.T) {
  function TestAmf3String (line 137) | func TestAmf3String(t *testing.T) {
  function TestAmf3Boolean (line 142) | func TestAmf3Boolean(t *testing.T) {
  function TestAmf3Null (line 147) | func TestAmf3Null(t *testing.T) {
  function TestAmf3Date (line 151) | func TestAmf3Date(t *testing.T) {
  function TestAmf3Array (line 159) | func TestAmf3Array(t *testing.T) {
  function TestAmf3ByteArray (line 187) | func TestAmf3ByteArray(t *testing.T) {

FILE: protocol/amf/const.go
  constant AMF0 (line 8) | AMF0 = 0x00
  constant AMF3 (line 9) | AMF3 = 0x03
  constant AMF0_NUMBER_MARKER (line 13) | AMF0_NUMBER_MARKER         = 0x00
  constant AMF0_BOOLEAN_MARKER (line 14) | AMF0_BOOLEAN_MARKER        = 0x01
  constant AMF0_STRING_MARKER (line 15) | AMF0_STRING_MARKER         = 0x02
  constant AMF0_OBJECT_MARKER (line 16) | AMF0_OBJECT_MARKER         = 0x03
  constant AMF0_MOVIECLIP_MARKER (line 17) | AMF0_MOVIECLIP_MARKER      = 0x04
  constant AMF0_NULL_MARKER (line 18) | AMF0_NULL_MARKER           = 0x05
  constant AMF0_UNDEFINED_MARKER (line 19) | AMF0_UNDEFINED_MARKER      = 0x06
  constant AMF0_REFERENCE_MARKER (line 20) | AMF0_REFERENCE_MARKER      = 0x07
  constant AMF0_ECMA_ARRAY_MARKER (line 21) | AMF0_ECMA_ARRAY_MARKER     = 0x08
  constant AMF0_OBJECT_END_MARKER (line 22) | AMF0_OBJECT_END_MARKER     = 0x09
  constant AMF0_STRICT_ARRAY_MARKER (line 23) | AMF0_STRICT_ARRAY_MARKER   = 0x0a
  constant AMF0_DATE_MARKER (line 24) | AMF0_DATE_MARKER           = 0x0b
  constant AMF0_LONG_STRING_MARKER (line 25) | AMF0_LONG_STRING_MARKER    = 0x0c
  constant AMF0_UNSUPPORTED_MARKER (line 26) | AMF0_UNSUPPORTED_MARKER    = 0x0d
  constant AMF0_RECORDSET_MARKER (line 27) | AMF0_RECORDSET_MARKER      = 0x0e
  constant AMF0_XML_DOCUMENT_MARKER (line 28) | AMF0_XML_DOCUMENT_MARKER   = 0x0f
  constant AMF0_TYPED_OBJECT_MARKER (line 29) | AMF0_TYPED_OBJECT_MARKER   = 0x10
  constant AMF0_ACMPLUS_OBJECT_MARKER (line 30) | AMF0_ACMPLUS_OBJECT_MARKER = 0x11
  constant AMF0_BOOLEAN_FALSE (line 34) | AMF0_BOOLEAN_FALSE = 0x00
  constant AMF0_BOOLEAN_TRUE (line 35) | AMF0_BOOLEAN_TRUE  = 0x01
  constant AMF0_STRING_MAX (line 36) | AMF0_STRING_MAX    = 65535
  constant AMF3_INTEGER_MAX (line 37) | AMF3_INTEGER_MAX   = 536870911
  constant AMF3_UNDEFINED_MARKER (line 41) | AMF3_UNDEFINED_MARKER = 0x00
  constant AMF3_NULL_MARKER (line 42) | AMF3_NULL_MARKER      = 0x01
  constant AMF3_FALSE_MARKER (line 43) | AMF3_FALSE_MARKER     = 0x02
  constant AMF3_TRUE_MARKER (line 44) | AMF3_TRUE_MARKER      = 0x03
  constant AMF3_INTEGER_MARKER (line 45) | AMF3_INTEGER_MARKER   = 0x04
  constant AMF3_DOUBLE_MARKER (line 46) | AMF3_DOUBLE_MARKER    = 0x05
  constant AMF3_STRING_MARKER (line 47) | AMF3_STRING_MARKER    = 0x06
  constant AMF3_XMLDOC_MARKER (line 48) | AMF3_XMLDOC_MARKER    = 0x07
  constant AMF3_DATE_MARKER (line 49) | AMF3_DATE_MARKER      = 0x08
  constant AMF3_ARRAY_MARKER (line 50) | AMF3_ARRAY_MARKER     = 0x09
  constant AMF3_OBJECT_MARKER (line 51) | AMF3_OBJECT_MARKER    = 0x0a
  constant AMF3_XMLSTRING_MARKER (line 52) | AMF3_XMLSTRING_MARKER = 0x0b
  constant AMF3_BYTEARRAY_MARKER (line 53) | AMF3_BYTEARRAY_MARKER = 0x0c
  type ExternalHandler (line 56) | type ExternalHandler
  type Decoder (line 58) | type Decoder struct
    method RegisterExternalHandler (line 72) | func (d *Decoder) RegisterExternalHandler(name string, f ExternalHandl...
  function NewDecoder (line 66) | func NewDecoder() *Decoder {
  type Encoder (line 76) | type Encoder struct
  type Version (line 79) | type Version
  type Array (line 81) | type Array
  type Object (line 82) | type Object
  type TypedObject (line 84) | type TypedObject struct
  type Trait (line 89) | type Trait struct
  function NewTrait (line 96) | func NewTrait() *Trait {
  function NewTypedObject (line 100) | func NewTypedObject() *TypedObject {

FILE: protocol/amf/decoder_amf0.go
  method DecodeAmf0 (line 10) | func (d *Decoder) DecodeAmf0(r io.Reader) (interface{}, error) {
  method DecodeAmf0Number (line 58) | func (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (resu...
  method DecodeAmf0Boolean (line 73) | func (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (res...
  method DecodeAmf0String (line 96) | func (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (resu...
  method DecodeAmf0Object (line 119) | func (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Obje...
  method DecodeAmf0Null (line 155) | func (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result...
  method DecodeAmf0Undefined (line 162) | func (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (r...
  method DecodeAmf0EcmaArray (line 199) | func (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (O...
  method DecodeAmf0StrictArray (line 219) | func (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) ...
  method DecodeAmf0Date (line 248) | func (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result...
  method DecodeAmf0LongString (line 268) | func (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (...
  method DecodeAmf0Unsupported (line 289) | func (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) ...
  method DecodeAmf0XmlDocument (line 299) | func (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) ...
  method DecodeAmf0TypedObject (line 315) | func (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) ...

FILE: protocol/amf/decoder_amf0_test.go
  function TestDecodeAmf0Number (line 8) | func TestDecodeAmf0Number(t *testing.T) {
  function TestDecodeAmf0BooleanTrue (line 44) | func TestDecodeAmf0BooleanTrue(t *testing.T) {
  function TestDecodeAmf0BooleanFalse (line 80) | func TestDecodeAmf0BooleanFalse(t *testing.T) {
  function TestDecodeAmf0String (line 116) | func TestDecodeAmf0String(t *testing.T) {
  function TestDecodeAmf0Object (line 152) | func TestDecodeAmf0Object(t *testing.T) {
  function TestDecodeAmf0Null (line 199) | func TestDecodeAmf0Null(t *testing.T) {
  function TestDecodeAmf0Undefined (line 224) | func TestDecodeAmf0Undefined(t *testing.T) {
  function TestDecodeAmf0EcmaArray (line 271) | func TestDecodeAmf0EcmaArray(t *testing.T) {
  function TestDecodeAmf0StrictArray (line 318) | func TestDecodeAmf0StrictArray(t *testing.T) {
  function TestDecodeAmf0Date (line 383) | func TestDecodeAmf0Date(t *testing.T) {
  function TestDecodeAmf0LongString (line 419) | func TestDecodeAmf0LongString(t *testing.T) {
  function TestDecodeAmf0Unsupported (line 455) | func TestDecodeAmf0Unsupported(t *testing.T) {
  function TestDecodeAmf0XmlDocument (line 480) | func TestDecodeAmf0XmlDocument(t *testing.T) {
  function TestDecodeAmf0TypedObject (line 516) | func TestDecodeAmf0TypedObject(t *testing.T) {

FILE: protocol/amf/decoder_amf3.go
  method DecodeAmf3 (line 11) | func (d *Decoder) DecodeAmf3(r io.Reader) (interface{}, error) {
  method DecodeAmf3Undefined (line 51) | func (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (r...
  method DecodeAmf3Null (line 58) | func (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result...
  method DecodeAmf3False (line 65) | func (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (resul...
  method DecodeAmf3True (line 73) | func (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result...
  method DecodeAmf3Integer (line 80) | func (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (res...
  method DecodeAmf3Double (line 100) | func (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (resu...
  method DecodeAmf3String (line 117) | func (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (resu...
  method DecodeAmf3Date (line 152) | func (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result...
  method DecodeAmf3Array (line 191) | func (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (resul...
  method DecodeAmf3Object (line 239) | func (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (resu...
  method DecodeAmf3Xml (line 380) | func (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result ...
  method DecodeAmf3ByteArray (line 430) | func (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (r...
  method decodeU29 (line 463) | func (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {
  method decodeReferenceInt (line 487) | func (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal ui...

FILE: protocol/amf/decoder_amf3_external.go
  method decodeAbstractMessage (line 10) | func (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err...
  method decodeAsyncMessageExt (line 23) | func (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err...
  method decodeAsyncMessage (line 26) | func (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err er...
  method decodeAcknowledgeMessageExt (line 40) | func (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Objec...
  method decodeAcknowledgeMessage (line 43) | func (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, ...
  method decodeArrayCollection (line 57) | func (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {
  method decodeExternal (line 66) | func (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ......
  function readFlags (line 113) | func readFlags(r io.Reader) (result []uint8, err error) {

FILE: protocol/amf/decoder_amf3_test.go
  type u29TestCase (line 8) | type u29TestCase struct
  function TestDecodeAmf3Undefined (line 31) | func TestDecodeAmf3Undefined(t *testing.T) {
  function TestDecodeAmf3Null (line 45) | func TestDecodeAmf3Null(t *testing.T) {
  function TestDecodeAmf3False (line 59) | func TestDecodeAmf3False(t *testing.T) {
  function TestDecodeAmf3True (line 74) | func TestDecodeAmf3True(t *testing.T) {
  function TestDecodeU29 (line 89) | func TestDecodeU29(t *testing.T) {
  function TestDecodeAmf3Integer (line 104) | func TestDecodeAmf3Integer(t *testing.T) {
  function TestDecodeAmf3Double (line 137) | func TestDecodeAmf3Double(t *testing.T) {
  function TestDecodeAmf3String (line 152) | func TestDecodeAmf3String(t *testing.T) {
  function TestDecodeAmf3Array (line 167) | func TestDecodeAmf3Array(t *testing.T) {
  function TestDecodeAmf3Object (line 194) | func TestDecodeAmf3Object(t *testing.T) {

FILE: protocol/amf/encoder_amf0.go
  method EncodeAmf0 (line 11) | func (e *Encoder) EncodeAmf0(w io.Writer, val interface{}) (int, error) {
  method EncodeAmf0Number (line 61) | func (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarke...
  method EncodeAmf0Boolean (line 80) | func (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker ...
  method EncodeAmf0String (line 109) | func (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker...
  method EncodeAmf0Object (line 138) | func (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker...
  method EncodeAmf0Null (line 178) | func (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int,...
  method EncodeAmf0Undefined (line 191) | func (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n...
  method EncodeAmf0EcmaArray (line 208) | func (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMar...
  method EncodeAmf0StrictArray (line 237) | func (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMa...
  method EncodeAmf0LongString (line 268) | func (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMa...
  method EncodeAmf0Unsupported (line 295) | func (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) ...
  method EncodeAmf0Amf3Marker (line 307) | func (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error {

FILE: protocol/amf/encoder_amf0_test.go
  function TestEncodeAmf0Number (line 9) | func TestEncodeAmf0Number(t *testing.T) {
  function TestEncodeAmf0BooleanTrue (line 27) | func TestEncodeAmf0BooleanTrue(t *testing.T) {
  function TestEncodeAmf0BooleanFalse (line 45) | func TestEncodeAmf0BooleanFalse(t *testing.T) {
  function TestEncodeAmf0String (line 63) | func TestEncodeAmf0String(t *testing.T) {
  function TestEncodeAmf0Object (line 81) | func TestEncodeAmf0Object(t *testing.T) {
  function TestEncodeAmf0EcmaArray (line 102) | func TestEncodeAmf0EcmaArray(t *testing.T) {
  function TestEncodeAmf0StrictArray (line 121) | func TestEncodeAmf0StrictArray(t *testing.T) {
  function TestEncodeAmf0Null (line 142) | func TestEncodeAmf0Null(t *testing.T) {
  function TestEncodeAmf0LongString (line 160) | func TestEncodeAmf0LongString(t *testing.T) {

FILE: protocol/amf/encoder_amf3.go
  method EncodeAmf3 (line 14) | func (e *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {
  method EncodeAmf3Undefined (line 85) | func (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n...
  method EncodeAmf3Null (line 98) | func (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int,...
  method EncodeAmf3False (line 111) | func (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int...
  method EncodeAmf3True (line 124) | func (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int,...
  method EncodeAmf3Integer (line 136) | func (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarke...
  method EncodeAmf3Double (line 155) | func (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarke...
  method EncodeAmf3String (line 176) | func (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker...
  method EncodeAmf3Date (line 199) | func (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarke...
  method EncodeAmf3Array (line 227) | func (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker b...
  method EncodeAmf3Object (line 264) | func (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeM...
  method EncodeAmf3ByteArray (line 367) | func (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMar...
  method encodeAmf3Utf8 (line 395) | func (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err er...
  method encodeAmf3Uint29 (line 415) | func (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err ...

FILE: protocol/amf/encoder_amf3_test.go
  function TestEncodeAmf3EmptyString (line 8) | func TestEncodeAmf3EmptyString(t *testing.T) {
  function TestEncodeAmf3Undefined (line 24) | func TestEncodeAmf3Undefined(t *testing.T) {
  function TestEncodeAmf3Null (line 40) | func TestEncodeAmf3Null(t *testing.T) {
  function TestEncodeAmf3False (line 56) | func TestEncodeAmf3False(t *testing.T) {
  function TestEncodeAmf3True (line 72) | func TestEncodeAmf3True(t *testing.T) {
  function TestEncodeAmf3Integer (line 88) | func TestEncodeAmf3Integer(t *testing.T) {
  function TestEncodeAmf3Double (line 118) | func TestEncodeAmf3Double(t *testing.T) {
  function TestEncodeAmf3String (line 134) | func TestEncodeAmf3String(t *testing.T) {
  function TestEncodeAmf3Array (line 150) | func TestEncodeAmf3Array(t *testing.T) {
  function TestEncodeAmf3Object (line 176) | func TestEncodeAmf3Object(t *testing.T) {

FILE: protocol/amf/metadata.go
  constant ADD (line 11) | ADD = 0x0
  constant DEL (line 12) | DEL = 0x3
  constant SetDataFrame (line 16) | SetDataFrame string = "@setDataFrame"
  constant OnMetaData (line 17) | OnMetaData   string = "onMetaData"
  function init (line 22) | func init() {
  function MetaDataReform (line 31) | func MetaDataReform(p []byte, flag uint8) ([]byte, error) {

FILE: protocol/amf/util.go
  function DumpBytes (line 9) | func DumpBytes(label string, buf []byte, size int) {
  function Dump (line 17) | func Dump(label string, val interface{}) error {
  function WriteByte (line 27) | func WriteByte(w io.Writer, b byte) (err error) {
  function WriteBytes (line 36) | func WriteBytes(w io.Writer, bytes []byte) (int, error) {
  function ReadByte (line 40) | func ReadByte(r io.Reader) (byte, error) {
  function ReadBytes (line 49) | func ReadBytes(r io.Reader, n int) ([]byte, error) {
  function WriteMarker (line 64) | func WriteMarker(w io.Writer, m byte) error {
  function ReadMarker (line 68) | func ReadMarker(r io.Reader) (byte, error) {
  function AssertMarker (line 72) | func AssertMarker(r io.Reader, checkMarker bool, m byte) error {

FILE: protocol/api/api.go
  type Response (line 19) | type Response struct
    method SendJson (line 25) | func (r *Response) SendJson() (int, error) {
  type Operation (line 32) | type Operation struct
  type OperationChange (line 38) | type OperationChange struct
  type ClientInfo (line 45) | type ClientInfo struct
  type Server (line 51) | type Server struct
    method Serve (line 103) | func (s *Server) Serve(l net.Listener) error {
    method GetLiveStatics (line 146) | func (server *Server) GetLiveStatics(w http.ResponseWriter, req *http....
    method handlePull (line 246) | func (s *Server) handlePull(w http.ResponseWriter, req *http.Request) {
    method handlePush (line 315) | func (s *Server) handlePush(w http.ResponseWriter, req *http.Request) {
    method handleReset (line 378) | func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
    method handleGet (line 410) | func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
    method handleDelete (line 441) | func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {
  function NewServer (line 57) | func NewServer(h av.Handler, rtmpAddr string) *Server {
  function JWTMiddleware (line 65) | func JWTMiddleware(next http.Handler) http.Handler {
  type stream (line 130) | type stream struct
  type streams (line 140) | type streams struct

FILE: protocol/hls/align.go
  constant syncms (line 4) | syncms = 2
  type align (line 7) | type align struct
    method align (line 12) | func (a *align) align(dts *uint64, inc uint32) {

FILE: protocol/hls/audio_cache.go
  constant cache_max_frames (line 6) | cache_max_frames byte = 6
  constant audio_cache_len (line 7) | audio_cache_len  int  = 10 * 1024
  type audioCache (line 10) | type audioCache struct
    method Cache (line 24) | func (a *audioCache) Cache(src []byte, pts uint64) bool {
    method GetFrame (line 37) | func (a *audioCache) GetFrame() (int, uint64, []byte) {
    method CacheNum (line 42) | func (a *audioCache) CacheNum() byte {
  function newAudioCache (line 18) | func newAudioCache() *audioCache {

FILE: protocol/hls/cache.go
  constant maxTSCacheNum (line 11) | maxTSCacheNum = 3
  type TSCacheItem (line 18) | type TSCacheItem struct
    method ID (line 35) | func (tcCacheItem *TSCacheItem) ID() string {
    method GenM3U8PlayList (line 40) | func (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) {
    method SetItem (line 67) | func (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) {
    method GetItem (line 78) | func (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) {
  function NewTSCacheItem (line 26) | func NewTSCacheItem(id string) *TSCacheItem {

FILE: protocol/hls/hls.go
  constant duration (line 21) | duration = 3000
  type Server (line 37) | type Server struct
    method Serve (line 50) | func (server *Server) Serve(listener net.Listener) error {
    method GetWriter (line 66) | func (server *Server) GetWriter(info av.Info) av.WriteCloser {
    method getConn (line 79) | func (server *Server) getConn(key string) *Source {
    method checkStop (line 87) | func (server *Server) checkStop() {
    method handle (line 102) | func (server *Server) handle(w http.ResponseWriter, r *http.Request) {
    method parseM3u8 (line 154) | func (server *Server) parseM3u8(pathstr string) (key string, err error) {
    method parseTs (line 160) | func (server *Server) parseTs(pathstr string) (key string, err error) {
  function NewServer (line 42) | func NewServer() *Server {

FILE: protocol/hls/item.go
  type TSItem (line 3) | type TSItem struct
  function NewTSItem (line 10) | func NewTSItem(name string, duration, seqNum int, b []byte) TSItem {

FILE: protocol/hls/source.go
  constant videoHZ (line 18) | videoHZ      = 90000
  constant aacSampleLen (line 19) | aacSampleLen = 1024
  constant maxQueueNum (line 20) | maxQueueNum  = 512
  constant h264_default_hz (line 22) | h264_default_hz uint64 = 90
  type Source (line 25) | type Source struct
    method GetCacheInc (line 68) | func (source *Source) GetCacheInc() *TSCacheItem {
    method DropPacket (line 72) | func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {
    method Write (line 100) | func (source *Source) Write(p *av.Packet) (err error) {
    method SendPacket (line 122) | func (source *Source) SendPacket() error {
    method Info (line 170) | func (source *Source) Info() (ret av.Info) {
    method cleanup (line 174) | func (source *Source) cleanup() {
    method Close (line 182) | func (source *Source) Close(err error) {
    method cut (line 190) | func (source *Source) cut() {
    method parse (line 213) | func (source *Source) parse(p *av.Packet) (int32, bool, error) {
    method calcPtsDts (line 247) | func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint3...
    method flushAudio (line 257) | func (source *Source) flushAudio() error {
    method muxAudio (line 261) | func (source *Source) muxAudio(limit byte) error {
    method tsMux (line 272) | func (source *Source) tsMux(p *av.Packet) error {
  function NewSource (line 43) | func NewSource(info av.Info) *Source {

FILE: protocol/hls/status.go
  type status (line 5) | type status struct
    method update (line 23) | func (t *status) update(isVideo bool, timestamp uint32) {
    method resetAndNew (line 34) | func (t *status) resetAndNew() {
    method durationMs (line 41) | func (t *status) durationMs() int64 {
  function newStatus (line 15) | func newStatus() *status {

FILE: protocol/httpflv/server.go
  type Server (line 15) | type Server struct
    method Serve (line 35) | func (server *Server) Serve(l net.Listener) error {
    method getStreams (line 50) | func (server *Server) getStreams(w http.ResponseWriter, r *http.Reques...
    method getStream (line 85) | func (server *Server) getStream(w http.ResponseWriter, r *http.Request) {
    method handleConn (line 95) | func (server *Server) handleConn(w http.ResponseWriter, r *http.Reques...
  type stream (line 19) | type stream struct
  type streams (line 24) | type streams struct
  function NewServer (line 29) | func NewServer(h av.Handler) *Server {

FILE: protocol/httpflv/writer.go
  constant headerLen (line 17) | headerLen   = 11
  constant maxQueueNum (line 18) | maxQueueNum = 1024
  type FLVWriter (line 21) | type FLVWriter struct
    method DropPacket (line 65) | func (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av...
    method Write (line 92) | func (flvWriter *FLVWriter) Write(p *av.Packet) (err error) {
    method SendPacket (line 114) | func (flvWriter *FLVWriter) SendPacket() error {
    method Wait (line 166) | func (flvWriter *FLVWriter) Wait() {
    method Close (line 173) | func (flvWriter *FLVWriter) Close(error) {
    method Info (line 182) | func (flvWriter *FLVWriter) Info() (ret av.Info) {
  function NewFLVWriter (line 32) | func NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVW...

FILE: protocol/rtmp/cache/cache.go
  type Cache (line 8) | type Cache struct
    method Write (line 24) | func (cache *Cache) Write(p av.Packet) {
    method Send (line 57) | func (cache *Cache) Send(w av.WriteCloser) error {
  function NewCache (line 15) | func NewCache() *Cache {

FILE: protocol/rtmp/cache/gop.go
  type array (line 14) | type array struct
    method reset (line 27) | func (array *array) reset() {
    method write (line 32) | func (array *array) write(packet *av.Packet) error {
    method send (line 41) | func (array *array) send(w av.WriteCloser) error {
  function newArray (line 19) | func newArray() *array {
  type GopCache (line 52) | type GopCache struct
    method writeToArray (line 67) | func (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool...
    method Write (line 87) | func (gopCache *GopCache) Write(p *av.Packet) {
    method sendTo (line 101) | func (gopCache *GopCache) sendTo(w av.WriteCloser) error {
    method Send (line 118) | func (gopCache *GopCache) Send(w av.WriteCloser) error {
  function NewGopCache (line 60) | func NewGopCache(num int) *GopCache {

FILE: protocol/rtmp/cache/special.go
  constant SetDataFrame (line 13) | SetDataFrame string = "@setDataFrame"
  constant OnMetaData (line 14) | OnMetaData   string = "onMetaData"
  function init (line 19) | func init() {
  type SpecialCache (line 28) | type SpecialCache struct
    method Write (line 37) | func (specialCache *SpecialCache) Write(p *av.Packet) {
    method Send (line 42) | func (specialCache *SpecialCache) Send(w av.WriteCloser) error {
  function NewSpecialCache (line 33) | func NewSpecialCache() *SpecialCache {

FILE: protocol/rtmp/core/chunk_stream.go
  type ChunkStream (line 11) | type ChunkStream struct
    method full (line 27) | func (chunkStream *ChunkStream) full() bool {
    method new (line 31) | func (chunkStream *ChunkStream) new(pool *pool.Pool) {
    method writeHeader (line 38) | func (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error {
    method writeChunk (line 83) | func (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize in...
    method readChunk (line 123) | func (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uin...

FILE: protocol/rtmp/core/chunk_stream_test.go
  function TestChunkRead1 (line 12) | func TestChunkRead1(t *testing.T) {
  function TestWriteChunk (line 82) | func TestWriteChunk(t *testing.T) {

FILE: protocol/rtmp/core/conn.go
  constant _ (line 13) | _ = iota
  constant idSetChunkSize (line 14) | idSetChunkSize
  constant idAbortMessage (line 15) | idAbortMessage
  constant idAck (line 16) | idAck
  constant idUserControlMessages (line 17) | idUserControlMessages
  constant idWindowAckSize (line 18) | idWindowAckSize
  constant idSetPeerBandwidth (line 19) | idSetPeerBandwidth
  type Conn (line 22) | type Conn struct
    method Read (line 48) | func (conn *Conn) Read(c *ChunkStream) error {
    method Write (line 82) | func (conn *Conn) Write(c *ChunkStream) error {
    method Flush (line 89) | func (conn *Conn) Flush() error {
    method Close (line 93) | func (conn *Conn) Close() error {
    method RemoteAddr (line 97) | func (conn *Conn) RemoteAddr() net.Addr {
    method LocalAddr (line 101) | func (conn *Conn) LocalAddr() net.Addr {
    method SetDeadline (line 105) | func (conn *Conn) SetDeadline(t time.Time) error {
    method NewAck (line 109) | func (conn *Conn) NewAck(size uint32) ChunkStream {
    method NewSetChunkSize (line 113) | func (conn *Conn) NewSetChunkSize(size uint32) ChunkStream {
    method NewWindowAckSize (line 117) | func (conn *Conn) NewWindowAckSize(size uint32) ChunkStream {
    method NewSetPeerBandwidth (line 121) | func (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream {
    method handleControlMsg (line 127) | func (conn *Conn) handleControlMsg(c *ChunkStream) {
    method ack (line 135) | func (conn *Conn) ack(size uint32) {
    method userControlMsg (line 177) | func (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream {
    method SetBegin (line 193) | func (conn *Conn) SetBegin() {
    method SetRecorded (line 201) | func (conn *Conn) SetRecorded() {
  function NewConn (line 35) | func NewConn(c net.Conn, bufferSize int) *Conn {
  function initControlMsg (line 148) | func initControlMsg(id, size, value uint32) ChunkStream {
  constant streamBegin (line 162) | streamBegin      uint32 = 0
  constant streamEOF (line 163) | streamEOF        uint32 = 1
  constant streamDry (line 164) | streamDry        uint32 = 2
  constant setBufferLen (line 165) | setBufferLen     uint32 = 3
  constant streamIsRecorded (line 166) | streamIsRecorded uint32 = 4
  constant pingRequest (line 167) | pingRequest      uint32 = 6
  constant pingResponse (line 168) | pingResponse     uint32 = 7

FILE: protocol/rtmp/core/conn_client.go
  type ConnClient (line 35) | type ConnClient struct
    method DecodeBatch (line 60) | func (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version...
    method readRespMsg (line 65) | func (connClient *ConnClient) readRespMsg() error {
    method writeMsg (line 134) | func (connClient *ConnClient) writeMsg(args ...interface{}) error {
    method writeConnectMsg (line 155) | func (connClient *ConnClient) writeConnectMsg() error {
    method writeCreateStreamMsg (line 170) | func (connClient *ConnClient) writeCreateStreamMsg() error {
    method writePublishMsg (line 193) | func (connClient *ConnClient) writePublishMsg() error {
    method writePlayMsg (line 202) | func (connClient *ConnClient) writePlayMsg() error {
    method Start (line 214) | func (connClient *ConnClient) Start(url string, method string) error {
    method Write (line 334) | func (connClient *ConnClient) Write(c ChunkStream) error {
    method Flush (line 346) | func (connClient *ConnClient) Flush() error {
    method Read (line 350) | func (connClient *ConnClient) Read(c *ChunkStream) (err error) {
    method GetInfo (line 354) | func (connClient *ConnClient) GetInfo() (app string, name string, url ...
    method GetStreamId (line 361) | func (connClient *ConnClient) GetStreamId() uint32 {
    method Close (line 365) | func (connClient *ConnClient) Close(err error) {
  function NewConnClient (line 51) | func NewConnClient() *ConnClient {

FILE: protocol/rtmp/core/conn_server.go
  type ConnectInfo (line 35) | type ConnectInfo struct
  type ConnectResp (line 48) | type ConnectResp struct
  type ConnectEvent (line 53) | type ConnectEvent struct
  type PublishInfo (line 60) | type PublishInfo struct
  type ConnServer (line 65) | type ConnServer struct
    method writeMsg (line 88) | func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ......
    method connect (line 109) | func (connServer *ConnServer) connect(vs []interface{}) error {
    method releaseStream (line 138) | func (connServer *ConnServer) releaseStream(vs []interface{}) error {
    method fcPublish (line 142) | func (connServer *ConnServer) fcPublish(vs []interface{}) error {
    method connectResp (line 146) | func (connServer *ConnServer) connectResp(cur *ChunkStream) error {
    method createStream (line 166) | func (connServer *ConnServer) createStream(vs []interface{}) error {
    method createStreamResp (line 178) | func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error {
    method publishOrPlay (line 182) | func (connServer *ConnServer) publishOrPlay(vs []interface{}) error {
    method publishResp (line 201) | func (connServer *ConnServer) publishResp(cur *ChunkStream) error {
    method playResp (line 209) | func (connServer *ConnServer) playResp(cur *ChunkStream) error {
    method handleCmdMsg (line 244) | func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {
    method ReadMsg (line 306) | func (connServer *ConnServer) ReadMsg() error {
    method IsPublisher (line 325) | func (connServer *ConnServer) IsPublisher() bool {
    method Write (line 329) | func (connServer *ConnServer) Write(c ChunkStream) error {
    method Flush (line 341) | func (connServer *ConnServer) Flush() error {
    method Read (line 345) | func (connServer *ConnServer) Read(c *ChunkStream) (err error) {
    method GetInfo (line 349) | func (connServer *ConnServer) GetInfo() (app string, name string, url ...
    method Close (line 356) | func (connServer *ConnServer) Close(err error) {
  function NewConnServer (line 78) | func NewConnServer(conn *Conn) *ConnServer {

FILE: protocol/rtmp/core/conn_test.go
  function TestConnReadNormal (line 13) | func TestConnReadNormal(t *testing.T) {
  function TestConnCrossReading (line 42) | func TestConnCrossReading(t *testing.T) {
  function TestSetChunksizeForWrite (line 96) | func TestSetChunksizeForWrite(t *testing.T) {
  function TestSetChunksize (line 148) | func TestSetChunksize(t *testing.T) {
  function TestConnWrite (line 205) | func TestConnWrite(t *testing.T) {

FILE: protocol/rtmp/core/handshake.go
  function hsMakeDigest (line 42) | func hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) {
  function hsCalcDigestPos (line 53) | func hsCalcDigestPos(p []byte, base int) (pos int) {
  function hsFindDigest (line 61) | func hsFindDigest(p []byte, key []byte, base int) int {
  function hsParse1 (line 70) | func hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []b...
  function hsCreate01 (line 82) | func hsCreate01(p []byte, time uint32, ver uint32, key []byte) {
  function hsCreate2 (line 93) | func hsCreate2(p []byte, key []byte) {
  method HandshakeClient (line 100) | func (conn *Conn) HandshakeClient() (err error) {
  method HandshakeServer (line 143) | func (conn *Conn) HandshakeServer() (err error) {

FILE: protocol/rtmp/core/read_writer.go
  type ReadWriter (line 8) | type ReadWriter struct
    method Read (line 20) | func (rw *ReadWriter) Read(p []byte) (int, error) {
    method ReadError (line 29) | func (rw *ReadWriter) ReadError() error {
    method ReadUintBE (line 33) | func (rw *ReadWriter) ReadUintBE(n int) (uint32, error) {
    method ReadUintLE (line 49) | func (rw *ReadWriter) ReadUintLE(n int) (uint32, error) {
    method Flush (line 65) | func (rw *ReadWriter) Flush() error {
    method Write (line 76) | func (rw *ReadWriter) Write(p []byte) (int, error) {
    method WriteError (line 83) | func (rw *ReadWriter) WriteError() error {
    method WriteUintBE (line 87) | func (rw *ReadWriter) WriteUintBE(v uint32, n int) error {
    method WriteUintLE (line 101) | func (rw *ReadWriter) WriteUintLE(v uint32, n int) error {
  function NewReadWriter (line 14) | func NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter {

FILE: protocol/rtmp/core/read_writer_test.go
  function TestReader (line 11) | func TestReader(t *testing.T) {
  function TestReaderUintBE (line 30) | func TestReaderUintBE(t *testing.T) {
  function TestReaderUintLE (line 52) | func TestReaderUintLE(t *testing.T) {
  function TestWriter (line 74) | func TestWriter(t *testing.T) {
  function TestWriteUintBE (line 90) | func TestWriteUintBE(t *testing.T) {
  function TestWriteUintLE (line 114) | func TestWriteUintLE(t *testing.T) {

FILE: protocol/rtmp/rtmp.go
  constant maxQueueNum (line 22) | maxQueueNum           = 1024
  constant SAVE_STATICS_INTERVAL (line 23) | SAVE_STATICS_INTERVAL = 5000
  type Client (line 31) | type Client struct
    method Dial (line 43) | func (c *Client) Dial(url string, method string) error {
    method GetHandle (line 64) | func (c *Client) GetHandle() av.Handler {
  function NewRtmpClient (line 36) | func NewRtmpClient(h av.Handler, getter av.GetWriter) *Client {
  type Server (line 68) | type Server struct
    method Serve (line 80) | func (s *Server) Serve(listener net.Listener) (err error) {
    method handleConn (line 100) | func (s *Server) handleConn(conn *core.Conn) error {
  function NewRtmpServer (line 73) | func NewRtmpServer(h av.Handler, getter av.GetWriter) *Server {
  type GetInFo (line 169) | type GetInFo interface
  type StreamReadWriteCloser (line 173) | type StreamReadWriteCloser interface
  type StaticsBW (line 180) | type StaticsBW struct
  type VirWriter (line 193) | type VirWriter struct
    method SaveStatics (line 221) | func (v *VirWriter) SaveStatics(streamid uint32, length uint64, isVide...
    method Check (line 245) | func (v *VirWriter) Check() {
    method DropPacket (line 255) | func (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {
    method Write (line 287) | func (v *VirWriter) Write(p *av.Packet) (err error) {
    method SendPacket (line 308) | func (v *VirWriter) SendPacket() error {
    method Info (line 346) | func (v *VirWriter) Info() (ret av.Info) {
    method Close (line 359) | func (v *VirWriter) Close(err error) {
  function NewVirWriter (line 202) | func NewVirWriter(conn StreamReadWriteCloser) *VirWriter {
  type VirReader (line 368) | type VirReader struct
    method SaveStatics (line 386) | func (v *VirReader) SaveStatics(streamid uint32, length uint64, isVide...
    method Read (line 411) | func (v *VirReader) Read(p *av.Packet) (err error) {
    method Info (line 445) | func (v *VirReader) Info() (ret av.Info) {
    method Close (line 457) | func (v *VirReader) Close(err error) {
  function NewVirReader (line 376) | func NewVirReader(conn StreamReadWriteCloser) *VirReader {

FILE: protocol/rtmp/rtmprelay/rtmprelay.go
  type RtmpRelay (line 19) | type RtmpRelay struct
    method rcvPlayChunkStream (line 41) | func (self *RtmpRelay) rcvPlayChunkStream() {
    method sendPublishChunkStream (line 72) | func (self *RtmpRelay) sendPublishChunkStream() {
    method Start (line 88) | func (self *RtmpRelay) Start() error {
    method Stop (line 118) | func (self *RtmpRelay) Stop() {
  function NewRtmpRelay (line 29) | func NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay {

FILE: protocol/rtmp/rtmprelay/staticrelay.go
  type StaticPush (line 14) | type StaticPush struct
    method Start (line 103) | func (self *StaticPush) Start() error {
    method Stop (line 123) | func (self *StaticPush) Stop() {
    method WriteAvPacket (line 133) | func (self *StaticPush) WriteAvPacket(packet *av.Packet) {
    method sendPacket (line 141) | func (self *StaticPush) sendPacket(p *av.Packet) {
    method HandleAvPacket (line 168) | func (self *StaticPush) HandleAvPacket() {
    method IsStart (line 188) | func (self *StaticPush) IsStart() bool {
  function GetStaticPushList (line 30) | func GetStaticPushList(appname string) ([]string, error) {
  function GetAndCreateStaticPushObject (line 48) | func GetAndCreateStaticPushObject(rtmpurl string) *StaticPush {
  function GetStaticPushObject (line 67) | func GetStaticPushObject(rtmpurl string) (*StaticPush, error) {
  function ReleaseStaticPushObject (line 78) | func ReleaseStaticPushObject(rtmpurl string) {
  function NewStaticPush (line 93) | func NewStaticPush(rtmpurl string) *StaticPush {

FILE: protocol/rtmp/stream.go
  type RtmpStream (line 20) | type RtmpStream struct
    method HandleReader (line 32) | func (rs *RtmpStream) HandleReader(r av.ReadCloser) {
    method HandleWriter (line 56) | func (rs *RtmpStream) HandleWriter(w av.WriteCloser) {
    method GetStreams (line 73) | func (rs *RtmpStream) GetStreams() *sync.Map {
    method CheckAlive (line 77) | func (rs *RtmpStream) CheckAlive() {
  function NewRtmpStream (line 24) | func NewRtmpStream() *RtmpStream {
  type Stream (line 90) | type Stream struct
    method ID (line 114) | func (s *Stream) ID() string {
    method GetReader (line 121) | func (s *Stream) GetReader() av.ReadCloser {
    method GetWs (line 125) | func (s *Stream) GetWs() *sync.Map {
    method Copy (line 129) | func (s *Stream) Copy(dst *Stream) {
    method AddReader (line 140) | func (s *Stream) AddReader(r av.ReadCloser) {
    method AddWriter (line 145) | func (s *Stream) AddWriter(w av.WriteCloser) {
    method StartStaticPush (line 153) | func (s *Stream) StartStaticPush() {
    method StopStaticPush (line 193) | func (s *Stream) StopStaticPush() {
    method IsSendStaticPush (line 232) | func (s *Stream) IsSendStaticPush() bool {
    method SendStaticPush (line 272) | func (s *Stream) SendStaticPush(packet av.Packet) {
    method TransStart (line 309) | func (s *Stream) TransStart() {
    method TransStop (line 359) | func (s *Stream) TransStop() {
    method CheckAlive (line 369) | func (s *Stream) CheckAlive() (n int) {
    method closeInter (line 396) | func (s *Stream) closeInter() {
  type PackWriterCloser (line 98) | type PackWriterCloser struct
    method GetWriter (line 103) | func (p *PackWriterCloser) GetWriter() av.WriteCloser {
  function NewStream (line 107) | func NewStream() *Stream {

FILE: utils/pio/reader.go
  function U8 (line 3) | func U8(b []byte) (i uint8) {
  function U16BE (line 7) | func U16BE(b []byte) (i uint16) {
  function I16BE (line 14) | func I16BE(b []byte) (i int16) {
  function I24BE (line 21) | func I24BE(b []byte) (i int32) {
  function U24BE (line 30) | func U24BE(b []byte) (i uint32) {
  function I32BE (line 39) | func I32BE(b []byte) (i int32) {
  function U32LE (line 50) | func U32LE(b []byte) (i uint32) {
  function U32BE (line 61) | func U32BE(b []byte) (i uint32) {
  function U40BE (line 72) | func U40BE(b []byte) (i uint64) {
  function U64BE (line 85) | func U64BE(b []byte) (i uint64) {
  function I64BE (line 104) | func I64BE(b []byte) (i int64) {

FILE: utils/pio/writer.go
  function PutU8 (line 3) | func PutU8(b []byte, v uint8) {
  function PutI16BE (line 7) | func PutI16BE(b []byte, v int16) {
  function PutU16BE (line 12) | func PutU16BE(b []byte, v uint16) {
  function PutI24BE (line 17) | func PutI24BE(b []byte, v int32) {
  function PutU24BE (line 23) | func PutU24BE(b []byte, v uint32) {
  function PutI32BE (line 29) | func PutI32BE(b []byte, v int32) {
  function PutU32BE (line 36) | func PutU32BE(b []byte, v uint32) {
  function PutU32LE (line 43) | func PutU32LE(b []byte, v uint32) {
  function PutU40BE (line 50) | func PutU40BE(b []byte, v uint64) {
  function PutU48BE (line 58) | func PutU48BE(b []byte, v uint64) {
  function PutU64BE (line 67) | func PutU64BE(b []byte, v uint64) {
  function PutI64BE (line 78) | func PutI64BE(b []byte, v int64) {

FILE: utils/pool/pool.go
  type Pool (line 3) | type Pool struct
    method Get (line 10) | func (pool *Pool) Get(size int) []byte {
  constant maxpoolsize (line 8) | maxpoolsize = 500 * 1024
  function NewPool (line 20) | func NewPool() *Pool {

FILE: utils/queue/queue.go
  type Queue (line 10) | type Queue struct
    method Push (line 26) | func (q *Queue) Push(msg *av.Packet) {
    method Pop (line 38) | func (q *Queue) Pop() *av.Packet {
    method pop (line 49) | func (q *Queue) pop() *av.Packet {
    method Len (line 57) | func (q *Queue) Len() int {
    method All (line 65) | func (q *Queue) All() []*av.Packet {
  function NewQueue (line 19) | func NewQueue(maxSize int) *Queue {

FILE: utils/uid/rand.go
  function RandStringRunes (line 7) | func RandStringRunes(n int) string {

FILE: utils/uid/uuid.go
  function NewId (line 9) | func NewId() string {
Condensed preview — 79 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (310K chars).
[
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 4300,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 721,
    "preview": "name: Release\non:\n  release:\n    types: [published]\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    env:\n      GITHUB"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 474,
    "preview": "name: Test\non: [push]\njobs:\n  test:\n    name: Build\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os"
  },
  {
    "path": ".gitignore",
    "chars": 95,
    "preview": "# Created by .ignore support plugin (hsz.mobi)\n.idea\ndist\n.vscode\ntmp\nvendor\nlivego\nlivego.exe\n"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 217,
    "preview": "before:\n  hooks:\n    - go mod tidy\nbuilds:\n  - binary: livego\n    id: livego\n    main: ./main.go\n    goos:\n      - windo"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1404,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "Dockerfile",
    "chars": 495,
    "preview": "FROM golang:latest as builder\nWORKDIR /app\nENV GOPROXY https://goproxy.io\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "MIT License\n\nCopyright (c) 2017 吴浩麟\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "Makefile",
    "chars": 705,
    "preview": "GOCMD ?= go\nGOBUILD = $(GOCMD) build\nGOCLEAN = $(GOCMD) clean\nGOTEST = $(GOCMD) test\nGOGET = $(GOCMD) get\nBINARY_NAME = "
  },
  {
    "path": "README.md",
    "chars": 3413,
    "preview": "<p align='center'>\n    <img src='./logo.png' width='200px' height='80px'/>\n</p>\n\n[中文](./README_cn.md)\n\n[![Test](https://"
  },
  {
    "path": "README_cn.md",
    "chars": 2266,
    "preview": "<p align='center'>\n    <img src='./logo.png' width='200px' height='80px'/>\n</p>\n\n[![Test](https://github.com/gwuhaolin/l"
  },
  {
    "path": "SECURITY.md",
    "chars": 619,
    "preview": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurre"
  },
  {
    "path": "av/av.go",
    "chars": 2269,
    "preview": "package av\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nconst (\n\tTAG_AUDIO          = 8\n\tTAG_VIDEO          = 9\n\tTAG_SCRIPTDATAAMF0 = 18\n\tT"
  },
  {
    "path": "av/rwbase.go",
    "chars": 1048,
    "preview": "package av\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype RWBaser struct {\n\tlock               sync.Mutex\n\ttimeout            time.Du"
  },
  {
    "path": "configure/channel.go",
    "chars": 2716,
    "preview": "package configure\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n\n\t\"github.com/go-redis/redis/v7\"\n\t\"github.c"
  },
  {
    "path": "configure/liveconfig.go",
    "chars": 5186,
    "preview": "package configure\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/kr/pretty\"\n\tlog \"github.com/sirupsen/logr"
  },
  {
    "path": "container/flv/demuxer.go",
    "chars": 683,
    "preview": "package flv\n\nimport (\n\t\"fmt\"\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nvar (\n\tErrAvcEndSEQ = fmt.Errorf(\"avc end sequence\")\n)"
  },
  {
    "path": "container/flv/muxer.go",
    "chars": 3617,
    "preview": "package flv\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/"
  },
  {
    "path": "container/flv/tag.go",
    "chars": 3622,
    "preview": "package flv\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\ntype flvTag struct {\n\tfType     uint8\n\tdataSize  uint"
  },
  {
    "path": "container/ts/crc32.go",
    "chars": 3464,
    "preview": "package ts\n\nfunc GenCrc32(src []byte) uint32 {\n\tcrcTable := []uint32{\n\t\t0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,\n"
  },
  {
    "path": "container/ts/muxer.go",
    "chars": 7053,
    "preview": "package ts\n\nimport (\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nconst (\n\ttsDefaultDataLen = 184\n\ttsPacketLen      = 188"
  },
  {
    "path": "container/ts/muxer_test.go",
    "chars": 1940,
    "preview": "package ts\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype TestWr"
  },
  {
    "path": "go.mod",
    "chars": 550,
    "preview": "module github.com/gwuhaolin/livego\n\ngo 1.13\n\nrequire (\n\tgithub.com/auth0/go-jwt-middleware v0.0.0-20190805220309-3608124"
  },
  {
    "path": "go.sum",
    "chars": 19008,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1 h1:"
  },
  {
    "path": "livego.yaml",
    "chars": 449,
    "preview": "# # Logger level\n# level: info\n\n# # FLV Options\n# flv_archive: false\n# flv_dir: \"./tmp\"\n# httpflv_addr: \":7001\"\n\n# # RTM"
  },
  {
    "path": "main.go",
    "chars": 3830,
    "preview": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"path\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/configure\"\n"
  },
  {
    "path": "parser/aac/parser.go",
    "chars": 2636,
    "preview": "package aac\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\ntype mpegExtension struct {\n\tobjectType byte\n\ts"
  },
  {
    "path": "parser/h264/parser.go",
    "chars": 5532,
    "preview": "package h264\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n)\n\nconst (\n\ti_frame byte = 0\n\tp_frame byte = 1\n\tb_frame byte = 2\n)\n\nconst ("
  },
  {
    "path": "parser/h264/parser_test.go",
    "chars": 2874,
    "preview": "package h264\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestH264SeqDemux(t *tes"
  },
  {
    "path": "parser/mp3/parser.go",
    "chars": 842,
    "preview": "package mp3\n\nimport (\n\t\"fmt\"\n)\n\ntype Parser struct {\n\tsamplingFrequency int\n}\n\nfunc NewParser() *Parser {\n\treturn &Parse"
  },
  {
    "path": "parser/parser.go",
    "chars": 1412,
    "preview": "package parser\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/parser/aac\"\n\t\"git"
  },
  {
    "path": "protocol/amf/amf.go",
    "chars": 938,
    "preview": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nfunc (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err er"
  },
  {
    "path": "protocol/amf/amf_test.go",
    "chars": 4562,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc EncodeAndDecode(val interface{}, ver Version"
  },
  {
    "path": "protocol/amf/const.go",
    "chars": 2132,
    "preview": "package amf\n\nimport (\n\t\"io\"\n)\n\nconst (\n\tAMF0 = 0x00\n\tAMF3 = 0x03\n)\n\nconst (\n\tAMF0_NUMBER_MARKER         = 0x00\n\tAMF0_BOO"
  },
  {
    "path": "protocol/amf/decoder_amf0.go",
    "chars": 9333,
    "preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// amf0 polymorphic router\nfunc (d *Decoder) DecodeAmf0(r io.Re"
  },
  {
    "path": "protocol/amf/decoder_amf0_test.go",
    "chars": 13189,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestDecodeAmf0Number(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x"
  },
  {
    "path": "protocol/amf/decoder_amf3.go",
    "chars": 12538,
    "preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// amf3 polymorphic router\nfunc (d *Decoder) DecodeAmf3"
  },
  {
    "path": "protocol/amf/decoder_amf3_external.go",
    "chars": 3162,
    "preview": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n)\n\n// Abstract external boilerplate\nfunc (d *Decoder) decodeAbstractMessage(r"
  },
  {
    "path": "protocol/amf/decoder_amf3_test.go",
    "chars": 4419,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\ntype u29TestCase struct {\n\tvalue  uint32\n\texpect []byte\n}\n\nvar u29TestCases"
  },
  {
    "path": "protocol/amf/encoder_amf0.go",
    "chars": 7168,
    "preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n)\n\n// amf0 polymorphic router\nfunc (e *Encoder) EncodeA"
  },
  {
    "path": "protocol/amf/encoder_amf0_test.go",
    "chars": 4728,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf0Number(t *testing.T) {\n\tbuf := new(by"
  },
  {
    "path": "protocol/amf/encoder_amf3.go",
    "chars": 9578,
    "preview": "package amf\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"sort\"\n\t\"time\"\n)\n\n// amf3 polymorphic router\n\nfunc (e "
  },
  {
    "path": "protocol/amf/encoder_amf3_test.go",
    "chars": 4233,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf3EmptyString(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf :"
  },
  {
    "path": "protocol/amf/metadata.go",
    "chars": 1239,
    "preview": "package amf\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tADD = 0x0\n\tDEL = 0x3\n)\n\nconst (\n\tSe"
  },
  {
    "path": "protocol/amf/util.go",
    "chars": 1536,
    "preview": "package amf\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n)\n\nfunc DumpBytes(label string, buf []byte, size int) {\n\tfmt.Printf("
  },
  {
    "path": "protocol/api/api.go",
    "chars": 12265,
    "preview": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaol"
  },
  {
    "path": "protocol/hls/align.go",
    "chars": 461,
    "preview": "package hls\n\nconst (\n\tsyncms = 2 // ms\n)\n\ntype align struct {\n\tframeNum  uint64\n\tframeBase uint64\n}\n\nfunc (a *align) ali"
  },
  {
    "path": "protocol/hls/audio_cache.go",
    "chars": 701,
    "preview": "package hls\n\nimport \"bytes\"\n\nconst (\n\tcache_max_frames byte = 6\n\taudio_cache_len  int  = 10 * 1024\n)\n\ntype audioCache st"
  },
  {
    "path": "protocol/hls/cache.go",
    "chars": 1705,
    "preview": "package hls\n\nimport (\n\t\"bytes\"\n\t\"container/list\"\n\t\"fmt\"\n\t\"sync\"\n)\n\nconst (\n\tmaxTSCacheNum = 3\n)\n\nvar (\n\tErrNoKey = fmt.E"
  },
  {
    "path": "protocol/hls/hls.go",
    "chars": 3934,
    "preview": "package hls\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/li"
  },
  {
    "path": "protocol/hls/item.go",
    "chars": 320,
    "preview": "package hls\n\ntype TSItem struct {\n\tName     string\n\tSeqNum   int\n\tDuration int\n\tData     []byte\n}\n\nfunc NewTSItem(name s"
  },
  {
    "path": "protocol/hls/source.go",
    "chars": 6512,
    "preview": "package hls\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av"
  },
  {
    "path": "protocol/hls/status.go",
    "chars": 759,
    "preview": "package hls\n\nimport \"time\"\n\ntype status struct {\n\thasVideo       bool\n\tseqId          int64\n\tcreatedAt      time.Time\n\ts"
  },
  {
    "path": "protocol/httpflv/server.go",
    "chars": 3118,
    "preview": "package httpflv\n\nimport (\n\t\"encoding/json\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com"
  },
  {
    "path": "protocol/httpflv/writer.go",
    "chars": 4209,
    "preview": "package httpflv\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/pr"
  },
  {
    "path": "protocol/rtmp/cache/cache.go",
    "chars": 1278,
    "preview": "package cache\n\nimport (\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n)\n\ntype Cache struct "
  },
  {
    "path": "protocol/rtmp/cache/gop.go",
    "chars": 2178,
    "preview": "package cache\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nvar (\n\tmaxGOPCap    int = 1024\n\tErrGopTooBig     = "
  },
  {
    "path": "protocol/rtmp/cache/special.go",
    "chars": 905,
    "preview": "package cache\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/protocol/amf\"\n\n\tlog \"g"
  },
  {
    "path": "protocol/rtmp/core/chunk_stream.go",
    "chars": 5283,
    "preview": "package core\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/utils/"
  },
  {
    "path": "protocol/rtmp/core/chunk_stream_test.go",
    "chars": 2322,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/utils/pool\"\n\n\t\"github.com/stretchr/testify/ass"
  },
  {
    "path": "protocol/rtmp/core/conn.go",
    "chars": 4492,
    "preview": "package core\n\nimport (\n\t\"encoding/binary\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/utils/pio\"\n\t\"github.com/gwuhaoli"
  },
  {
    "path": "protocol/rtmp/core/conn_client.go",
    "chars": 8271,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\tneturl \"net/url\"\n\t\"string"
  },
  {
    "path": "protocol/rtmp/core/conn_server.go",
    "chars": 8958,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/protocol/a"
  },
  {
    "path": "protocol/rtmp/core/conn_test.go",
    "chars": 6081,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/gwuhaolin/livego/utils/pool\"\n\n\t\"github.com/stretchr/testi"
  },
  {
    "path": "protocol/rtmp/core/handshake.go",
    "chars": 4584,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"time\"\n\n\t\"github.com/gwuha"
  },
  {
    "path": "protocol/rtmp/core/read_writer.go",
    "chars": 2122,
    "preview": "package core\n\nimport (\n\t\"bufio\"\n\t\"io\"\n)\n\ntype ReadWriter struct {\n\t*bufio.ReadWriter\n\treadError  error\n\twriteError error"
  },
  {
    "path": "protocol/rtmp/core/read_writer_test.go",
    "chars": 3095,
    "preview": "package core\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestReader(t *testing.T)"
  },
  {
    "path": "protocol/rtmp/rtmp.go",
    "chars": 11177,
    "preview": "package rtmp\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n"
  },
  {
    "path": "protocol/rtmp/rtmprelay/rtmprelay.go",
    "chars": 3301,
    "preview": "package rtmprelay\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/proto"
  },
  {
    "path": "protocol/rtmp/rtmprelay/staticrelay.go",
    "chars": 4241,
    "preview": "package rtmprelay\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t"
  },
  {
    "path": "protocol/rtmp/stream.go",
    "chars": 8745,
    "preview": "package rtmp\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/liveg"
  },
  {
    "path": "test.go",
    "chars": 13,
    "preview": "package main\n"
  },
  {
    "path": "utils/pio/pio.go",
    "chars": 48,
    "preview": "package pio\n\nvar RecommendBufioSize = 1024 * 64\n"
  },
  {
    "path": "utils/pio/reader.go",
    "chars": 1609,
    "preview": "package pio\n\nfunc U8(b []byte) (i uint8) {\n\treturn b[0]\n}\n\nfunc U16BE(b []byte) (i uint16) {\n\ti = uint16(b[0])\n\ti <<= 8\n"
  },
  {
    "path": "utils/pio/writer.go",
    "chars": 1483,
    "preview": "package pio\n\nfunc PutU8(b []byte, v uint8) {\n\tb[0] = v\n}\n\nfunc PutI16BE(b []byte, v int16) {\n\tb[0] = byte(v >> 8)\n\tb[1] "
  },
  {
    "path": "utils/pool/pool.go",
    "chars": 370,
    "preview": "package pool\n\ntype Pool struct {\n\tpos int\n\tbuf []byte\n}\n\nconst maxpoolsize = 500 * 1024\n\nfunc (pool *Pool) Get(size int)"
  },
  {
    "path": "utils/queue/queue.go",
    "chars": 1218,
    "preview": "package queue\n\nimport (\n\t\"sync\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\n// Queue is a basic FIFO queue for Messages.\ntype "
  },
  {
    "path": "utils/uid/rand.go",
    "chars": 277,
    "preview": "package uid\n\nimport \"math/rand\"\n\nvar letterRunes = []rune(\"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY"
  },
  {
    "path": "utils/uid/uuid.go",
    "chars": 189,
    "preview": "package uid\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/satori/go.uuid\"\n)\n\nfunc NewId() string {\n\tid := uuid.NewV4()\n\tb64"
  }
]

About this extraction

This page contains the full source code of the gwuhaolin/livego GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 79 files (272.9 KB), approximately 99.1k tokens, and a symbol index with 690 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!