[
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL Advanced\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n  schedule:\n    - cron: '30 23 * * 3'\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: go\n          build-mode: autobuild\n        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # 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\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n        # 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\n        # queries: security-extended,security-and-quality\n\n    # If the analyze step fails for one of the languages you are analyzing with\n    # \"We were unable to automatically build your code\", modify the matrix above\n    # to set the build mode to \"manual\" for that language. Then modify this step\n    # to build your code.\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n    - if: matrix.build-mode == 'manual'\n      shell: bash\n      run: |\n        echo 'If you are using a \"manual\" build mode for one or more of the' \\\n          'languages you are analyzing, replace this with the commands to build' \\\n          'your code, for example:'\n        echo '  make bootstrap'\n        echo '  make release'\n        exit 1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  release:\n    types: [published]\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    env:\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    steps:\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '>=1.18.0'\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@v4\n      - name: Get dependencies\n        run: go get\n      - name: Go release\n        uses: goreleaser/goreleaser-action@v1\n      - name: Docker release\n        uses: elgohr/Publish-Docker-Github-Action@master\n        with:\n          name: gwuhaolin/livego\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non: [push]\njobs:\n  test:\n    name: Build\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n\n    steps:\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '>=1.18.0'\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@v4\n      - name: Get dependencies\n        run: go get\n      - name: Test\n        run: go test ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n.idea\ndist\n.vscode\ntmp\nvendor\nlivego\nlivego.exe\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "before:\n  hooks:\n    - go mod tidy\nbuilds:\n  - binary: livego\n    id: livego\n    main: ./main.go\n    goos:\n      - windows\n      - darwin\n      - linux\n      - freebsd\n    goarch:\n      - amd64\n      - 386\n      - arm"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n- JSON Web Token support.\n``` json \n    // livego.json\n    {\n        \"jwt\": {\n            \"secret\": \"testing\",\n            \"algorithm\": \"HS256\"\n        },\n        \"server\": [\n            {\n                \"appname\": \"live\",\n                \"live\": true,\n                \"hls\": true\n            }\n        ]\n    }\n```\n- Use redis for store room keys\n``` json \n    // livego.json\n    {\n        \"redis_addr\": \"localhost:6379\", \n        \"server\": [\n            {\n                \"appname\": \"live\",\n                \"live\": true,\n                \"hls\": true\n            }\n        ]\n    }\n```\n- Makefile\n\n### Changed\n- Show `players`.\n- Show `stream_id`.\n- Deleted keys saved in physical file, now the keys are in cached using `go-cache` by default.\n- Using `logrus` like log system.\n- Using method `.Get(queryParamName)` to get an url query param.\n- Replaced `errors.New(...)` to `fmt.Errorf(...)`.\n- Replaced types string on config params `liveon` and `hlson` to booleans `live: true/false` and `hls: true/false`\n- Using viper for config, allow use file, cloud providers, environment vars or flags.\n- Using yaml config by default.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:latest as builder\nWORKDIR /app\nENV GOPROXY https://goproxy.io\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego .\n\nFROM alpine:latest\nRUN mkdir -p /app/config\nWORKDIR /app\nENV RTMP_PORT 1935\nENV HTTP_FLV_PORT 7001\nENV HLS_PORT 7002\nENV HTTP_OPERATION_PORT 8090\nCOPY --from=builder /app/livego .\nEXPOSE ${RTMP_PORT}\nEXPOSE ${HTTP_FLV_PORT}\nEXPOSE ${HLS_PORT}\nEXPOSE ${HTTP_OPERATION_PORT}\nENTRYPOINT [\"./livego\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 吴浩麟\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "GOCMD ?= go\nGOBUILD = $(GOCMD) build\nGOCLEAN = $(GOCMD) clean\nGOTEST = $(GOCMD) test\nGOGET = $(GOCMD) get\nBINARY_NAME = livego\nBINARY_UNIX = $(BINARY_NAME)_unix\n\nDOCKER_ACC ?= gwuhaolin\nDOCKER_REPO ?= livego\n\nTAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null)\n\ndefault: all\n\nall: test build dockerize\nbuild:\n\t$(GOBUILD) -o $(BINARY_NAME) -v -ldflags=\"-X main.VERSION=$(TAG)\"\n\ntest:\n\t$(GOTEST) -v ./...\n\nclean:\n\t$(GOCLEAN)\n\trm -f $(BINARY_NAME)\n\trm -f $(BINARY_UNIX)\n\nrun: build\n\t./$(BINARY_NAME)\n\nbuild-linux:\n\tCGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v\n\ndockerize:\n\tdocker build -t $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG) .\n\tdocker push $(DOCKER_ACC)/$(DOCKER_REPO):$(TAG)\n"
  },
  {
    "path": "README.md",
    "content": "<p align='center'>\n    <img src='./logo.png' width='200px' height='80px'/>\n</p>\n\n[中文](./README_cn.md)\n\n[![Test](https://github.com/gwuhaolin/livego/actions/workflows/test.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/test.yml)\n[![Release](https://github.com/gwuhaolin/livego/actions/workflows/release.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/release.yml)\n[![CodeQL Advanced](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml/badge.svg)](https://github.com/gwuhaolin/livego/actions/workflows/codeql.yml)\n\nSimple and efficient live broadcast server:\n- Very simple to install and use;\n- Pure Golang, high performance, and cross-platform;\n- Supports commonly used transmission protocols, file formats, and encoding formats;\n\n#### Supported transport protocols\n- RTMP\n- AMF\n- HLS\n- HTTP-FLV\n\n#### Supported container formats\n- FLV\n- TS\n\n#### Supported encoding formats\n- H264\n- AAC\n- MP3\n\n## Installation\nAfter directly downloading the compiled [binary file](https://github.com/gwuhaolin/livego/releases), execute it on the command line.\n\n#### Boot from Docker\nRun `docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego` to start\n\n#### Compile from source\n1. Download the source code `git clone https://github.com/gwuhaolin/livego.git`\n2. Go to the livego directory and execute `go build` or `make build`\n\n## Use\n1. Start the service: execute the livego binary file or `make run` to start the livego service;\n2. Get a channelkey(used for push the video stream) from `http://localhost:8090/control/get?room=movie` and copy data like your channelkey.\n3. 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));\n4. Downstream playback: The following three playback protocols are supported, and the playback address is as follows:\n    - `RTMP`:`rtmp://localhost:1935/{appname}/movie`\n    - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`\n    - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`\n5. 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)\n\nall options: \n```bash\n./livego  -h\nUsage of ./livego:\n      --api_addr string       HTTP manage interface server listen address (default \":8090\")\n      --config_file string    configure filename (default \"livego.yaml\")\n      --flv_dir string        output flv file at flvDir/APP/KEY_TIME.flv (default \"tmp\")\n      --gop_num int           gop num (default 1)\n      --hls_addr string       HLS server listen address (default \":7002\")\n      --hls_keep_after_end    Maintains the HLS after the stream ends\n      --httpflv_addr string   HTTP-FLV server listen address (default \":7001\")\n      --level string          Log level (default \"info\")\n      --read_timeout int      read time out (default 10)\n      --rtmp_addr string      RTMP server listen address\n```\n\n### [Use with flv.js](https://github.com/gwuhaolin/blog/issues/3)\n\nInterested in Golang? Please see [Golang Chinese Learning Materials Summary](http://go.wuhaolin.cn/)\n"
  },
  {
    "path": "README_cn.md",
    "content": "<p align='center'>\n    <img src='./logo.png' width='200px' height='80px'/>\n</p>\n\n[![Test](https://github.com/gwuhaolin/livego/workflows/Test/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ATest)\n[![Release](https://github.com/gwuhaolin/livego/workflows/Release/badge.svg)](https://github.com/gwuhaolin/livego/actions?query=workflow%3ARelease)\n\n简单高效的直播服务器：\n- 安装和使用非常简单；\n- 纯 Golang 编写，性能高，跨平台；\n- 支持常用的传输协议、文件格式、编码格式；\n\n#### 支持的传输协议\n- RTMP\n- AMF\n- HLS\n- HTTP-FLV\n\n#### 支持的容器格式\n- FLV\n- TS\n\n#### 支持的编码格式\n- H264\n- AAC\n- MP3\n\n## 安装\n直接下载编译好的[二进制文件](https://github.com/gwuhaolin/livego/releases)后，在命令行中执行。\n\n#### 从 Docker 启动\n执行`docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego`启动\n\n#### 从源码编译\n1. 下载源码 `git clone https://github.com/gwuhaolin/livego.git`\n2. 去 livego 目录中 执行 `go build`\n\n## 使用\n1. 启动服务：执行 `livego` 二进制文件启动 livego 服务；\n2. 访问 `http://localhost:8090/control/get?room=movie` 获取一个房间的 channelkey(channelkey用于推流，movie用于播放).\n3. 推流: 通过`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));\n4. 播放: 支持多种播放协议，播放地址如下:\n    - `RTMP`:`rtmp://localhost:1935/{appname}/movie`\n    - `FLV`:`http://127.0.0.1:7001/{appname}/movie.flv`\n    - `HLS`:`http://127.0.0.1:7002/{appname}/movie.m3u8`\n\n所有配置项: \n```bash\n./livego  -h\nUsage of ./livego:\n      --api_addr string       HTTP管理访问监听地址 (default \":8090\")\n      --config_file string    配置文件路径 (默认 \"livego.yaml\")\n      --flv_dir string        输出的 flv 文件路径 flvDir/APP/KEY_TIME.flv (默认 \"tmp\")\n      --gop_num int           gop 数量 (default 1)\n      --hls_addr string       HLS 服务监听地址 (默认 \":7002\")\n      --hls_keep_after_end    Maintains the HLS after the stream ends\n      --httpflv_addr string   HTTP-FLV server listen address (默认 \":7001\")\n      --level string          日志等级 (默认 \"info\")\n      --read_timeout int      读超时时间 (默认 10)\n      --rtmp_addr string      RTMP 服务监听地址 (默认 \":1935\")\n      --write_timeout int     写超时时间 (默认 10)\n```\n\n### [和 flv.js 搭配使用](https://github.com/gwuhaolin/blog/issues/3)\n\n对Golang感兴趣？请看[Golang 中文学习资料汇总](http://go.wuhaolin.cn/)\n\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurrently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 5.1.x   | :white_check_mark: |\n| 5.0.x   | :x:                |\n| 4.0.x   | :white_check_mark: |\n| < 4.0   | :x:                |\n\n## Reporting a Vulnerability\n\nUse this section to tell people how to report a vulnerability.\n\nTell them where to go, how often they can expect to get an update on a\nreported vulnerability, what to expect if the vulnerability is accepted or\ndeclined, etc.\n"
  },
  {
    "path": "av/av.go",
    "content": "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\tTAG_SCRIPTDATAAMF3 = 0xf\n)\n\nconst (\n\tMetadatAMF0  = 0x12\n\tMetadataAMF3 = 0xf\n)\n\nconst (\n\tSOUND_MP3                   = 2\n\tSOUND_NELLYMOSER_16KHZ_MONO = 4\n\tSOUND_NELLYMOSER_8KHZ_MONO  = 5\n\tSOUND_NELLYMOSER            = 6\n\tSOUND_ALAW                  = 7\n\tSOUND_MULAW                 = 8\n\tSOUND_AAC                   = 10\n\tSOUND_SPEEX                 = 11\n\n\tSOUND_5_5Khz = 0\n\tSOUND_11Khz  = 1\n\tSOUND_22Khz  = 2\n\tSOUND_44Khz  = 3\n\n\tSOUND_8BIT  = 0\n\tSOUND_16BIT = 1\n\n\tSOUND_MONO   = 0\n\tSOUND_STEREO = 1\n\n\tAAC_SEQHDR = 0\n\tAAC_RAW    = 1\n)\n\nconst (\n\tAVC_SEQHDR = 0\n\tAVC_NALU   = 1\n\tAVC_EOS    = 2\n\n\tFRAME_KEY   = 1\n\tFRAME_INTER = 2\n\n\tVIDEO_H264 = 7\n)\n\nvar (\n\tPUBLISH = \"publish\"\n\tPLAY    = \"play\"\n)\n\n// Header can be converted to AudioHeaderInfo or VideoHeaderInfo\ntype Packet struct {\n\tIsAudio    bool\n\tIsVideo    bool\n\tIsMetadata bool\n\tTimeStamp  uint32 // dts\n\tStreamID   uint32\n\tHeader     PacketHeader\n\tData       []byte\n}\n\ntype PacketHeader interface {\n}\n\ntype AudioPacketHeader interface {\n\tPacketHeader\n\tSoundFormat() uint8\n\tAACPacketType() uint8\n}\n\ntype VideoPacketHeader interface {\n\tPacketHeader\n\tIsKeyFrame() bool\n\tIsSeq() bool\n\tCodecID() uint8\n\tCompositionTime() int32\n}\n\ntype Demuxer interface {\n\tDemux(*Packet) (ret *Packet, err error)\n}\n\ntype Muxer interface {\n\tMux(*Packet, io.Writer) error\n}\n\ntype SampleRater interface {\n\tSampleRate() (int, error)\n}\n\ntype CodecParser interface {\n\tSampleRater\n\tParse(*Packet, io.Writer) error\n}\n\ntype GetWriter interface {\n\tGetWriter(Info) WriteCloser\n}\n\ntype Handler interface {\n\tHandleReader(ReadCloser)\n\tHandleWriter(WriteCloser)\n}\n\ntype Alive interface {\n\tAlive() bool\n}\n\ntype Closer interface {\n\tInfo() Info\n\tClose(error)\n}\n\ntype CalcTime interface {\n\tCalcBaseTimestamp()\n}\n\ntype Info struct {\n\tKey   string\n\tURL   string\n\tUID   string\n\tInter bool\n}\n\nfunc (info Info) IsInterval() bool {\n\treturn info.Inter\n}\n\nfunc (info Info) String() string {\n\treturn fmt.Sprintf(\"<key: %s, URL: %s, UID: %s, Inter: %v>\",\n\t\tinfo.Key, info.URL, info.UID, info.Inter)\n}\n\ntype ReadCloser interface {\n\tCloser\n\tAlive\n\tRead(*Packet) error\n}\n\ntype WriteCloser interface {\n\tCloser\n\tAlive\n\tCalcTime\n\tWrite(*Packet) error\n}\n"
  },
  {
    "path": "av/rwbase.go",
    "content": "package av\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype RWBaser struct {\n\tlock               sync.Mutex\n\ttimeout            time.Duration\n\tPreTime            time.Time\n\tBaseTimestamp      uint32\n\tLastVideoTimestamp uint32\n\tLastAudioTimestamp uint32\n}\n\nfunc NewRWBaser(duration time.Duration) RWBaser {\n\treturn RWBaser{\n\t\ttimeout: duration,\n\t\tPreTime: time.Now(),\n\t}\n}\n\nfunc (rw *RWBaser) BaseTimeStamp() uint32 {\n\treturn rw.BaseTimestamp\n}\n\nfunc (rw *RWBaser) CalcBaseTimestamp() {\n\tif rw.LastAudioTimestamp > rw.LastVideoTimestamp {\n\t\trw.BaseTimestamp = rw.LastAudioTimestamp\n\t} else {\n\t\trw.BaseTimestamp = rw.LastVideoTimestamp\n\t}\n}\n\nfunc (rw *RWBaser) RecTimeStamp(timestamp, typeID uint32) {\n\tif typeID == TAG_VIDEO {\n\t\trw.LastVideoTimestamp = timestamp\n\t} else if typeID == TAG_AUDIO {\n\t\trw.LastAudioTimestamp = timestamp\n\t}\n}\n\nfunc (rw *RWBaser) SetPreTime() {\n\trw.lock.Lock()\n\trw.PreTime = time.Now()\n\trw.lock.Unlock()\n}\n\nfunc (rw *RWBaser) Alive() bool {\n\trw.lock.Lock()\n\tb := !(time.Now().Sub(rw.PreTime) >= rw.timeout)\n\trw.lock.Unlock()\n\treturn b\n}\n"
  },
  {
    "path": "configure/channel.go",
    "content": "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.com/patrickmn/go-cache\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype RoomKeysType struct {\n\tredisCli   *redis.Client\n\tlocalCache *cache.Cache\n}\n\nvar RoomKeys = &RoomKeysType{\n\tlocalCache: cache.New(cache.NoExpiration, 0),\n}\n\nvar saveInLocal = true\n\nfunc Init() {\n\tsaveInLocal = len(Config.GetString(\"redis_addr\")) == 0\n\tif saveInLocal {\n\t\treturn\n\t}\n\n\tRoomKeys.redisCli = redis.NewClient(&redis.Options{\n\t\tAddr:     Config.GetString(\"redis_addr\"),\n\t\tPassword: Config.GetString(\"redis_pwd\"),\n\t\tDB:       0,\n\t})\n\n\t_, err := RoomKeys.redisCli.Ping().Result()\n\tif err != nil {\n\t\tlog.Panic(\"Redis: \", err)\n\t}\n\n\tlog.Info(\"Redis connected\")\n}\n\n// set/reset a random key for channel\nfunc (r *RoomKeysType) SetKey(channel string) (key string, err error) {\n\tif !saveInLocal {\n\t\tfor {\n\t\t\tkey = uid.RandStringRunes(48)\n\t\t\tif _, err = r.redisCli.Get(key).Result(); err == redis.Nil {\n\t\t\t\terr = r.redisCli.Set(channel, key, 0).Err()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\terr = r.redisCli.Set(key, channel, 0).Err()\n\t\t\t\treturn\n\t\t\t} else if err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tfor {\n\t\tkey = uid.RandStringRunes(48)\n\t\tif _, found := r.localCache.Get(key); !found {\n\t\t\tr.localCache.SetDefault(channel, key)\n\t\t\tr.localCache.SetDefault(key, channel)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn\n}\n\nfunc (r *RoomKeysType) GetKey(channel string) (newKey string, err error) {\n\tif !saveInLocal {\n\t\tif newKey, err = r.redisCli.Get(channel).Result(); err == redis.Nil {\n\t\t\tnewKey, err = r.SetKey(channel)\n\t\t\tlog.Debugf(\"[KEY] new channel [%s]: %s\", channel, newKey)\n\t\t\treturn\n\t\t}\n\n\t\treturn\n\t}\n\n\tvar key interface{}\n\tvar found bool\n\tif key, found = r.localCache.Get(channel); found {\n\t\treturn key.(string), nil\n\t}\n\tnewKey, err = r.SetKey(channel)\n\tlog.Debugf(\"[KEY] new channel [%s]: %s\", channel, newKey)\n\treturn\n}\n\nfunc (r *RoomKeysType) GetChannel(key string) (channel string, err error) {\n\tif !saveInLocal {\n\t\treturn r.redisCli.Get(key).Result()\n\t}\n\n\tchann, found := r.localCache.Get(key)\n\tif found {\n\t\treturn chann.(string), nil\n\t} else {\n\t\treturn \"\", fmt.Errorf(\"%s does not exists\", key)\n\t}\n}\n\nfunc (r *RoomKeysType) DeleteChannel(channel string) bool {\n\tif !saveInLocal {\n\t\treturn r.redisCli.Del(channel).Err() != nil\n\t}\n\n\tkey, ok := r.localCache.Get(channel)\n\tif ok {\n\t\tr.localCache.Delete(channel)\n\t\tr.localCache.Delete(key.(string))\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (r *RoomKeysType) DeleteKey(key string) bool {\n\tif !saveInLocal {\n\t\treturn r.redisCli.Del(key).Err() != nil\n\t}\n\n\tchannel, ok := r.localCache.Get(key)\n\tif ok {\n\t\tr.localCache.Delete(channel.(string))\n\t\tr.localCache.Delete(key)\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "configure/liveconfig.go",
    "content": "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/logrus\"\n\t\"github.com/spf13/pflag\"\n\t\"github.com/spf13/viper\"\n)\n\n/*\n{\n  \"server\": [\n    {\n      \"appname\": \"live\",\n      \"live\": true,\n\t  \"hls\": true,\n\t  \"static_push\": []\n    }\n  ]\n}\n*/\n\ntype Application struct {\n\tAppname    string   `mapstructure:\"appname\"`\n\tLive       bool     `mapstructure:\"live\"`\n\tHls        bool     `mapstructure:\"hls\"`\n\tFlv        bool     `mapstructure:\"flv\"`\n\tApi        bool     `mapstructure:\"api\"`\n\tStaticPush []string `mapstructure:\"static_push\"`\n}\n\ntype Applications []Application\n\ntype JWT struct {\n\tSecret    string `mapstructure:\"secret\"`\n\tAlgorithm string `mapstructure:\"algorithm\"`\n}\ntype ServerCfg struct {\n\tLevel           string       `mapstructure:\"level\"`\n\tConfigFile      string       `mapstructure:\"config_file\"`\n\tFLVArchive      bool         `mapstructure:\"flv_archive\"`\n\tFLVDir          string       `mapstructure:\"flv_dir\"`\n\tRTMPNoAuth      bool         `mapstructure:\"rtmp_noauth\"`\n\tRTMPAddr        string       `mapstructure:\"rtmp_addr\"`\n\tHTTPFLVAddr     string       `mapstructure:\"httpflv_addr\"`\n\tHLSAddr         string       `mapstructure:\"hls_addr\"`\n\tHLSKeepAfterEnd bool         `mapstructure:\"hls_keep_after_end\"`\n\tAPIAddr         string       `mapstructure:\"api_addr\"`\n\tRedisAddr       string       `mapstructure:\"redis_addr\"`\n\tRedisPwd        string       `mapstructure:\"redis_pwd\"`\n\tReadTimeout     int          `mapstructure:\"read_timeout\"`\n\tWriteTimeout    int          `mapstructure:\"write_timeout\"`\n\tEnableTLSVerify bool         `mapstructure:\"enable_tls_verify\"`\n\tGopNum          int          `mapstructure:\"gop_num\"`\n\tJWT             JWT          `mapstructure:\"jwt\"`\n\tServer          Applications `mapstructure:\"server\"`\n}\n\n// default config\nvar defaultConf = ServerCfg{\n\tConfigFile:      \"livego.yaml\",\n\tFLVArchive:      false,\n\tRTMPNoAuth:      false,\n\tRTMPAddr:        \":1935\",\n\tHTTPFLVAddr:     \":7001\",\n\tHLSAddr:         \":7002\",\n\tHLSKeepAfterEnd: false,\n\tAPIAddr:         \":8090\",\n\tWriteTimeout:    10,\n\tReadTimeout:     10,\n\tEnableTLSVerify: true,\n\tGopNum:          1,\n\tServer: Applications{{\n\t\tAppname:    \"live\",\n\t\tLive:       true,\n\t\tHls:        true,\n\t\tFlv:        true,\n\t\tApi:        true,\n\t\tStaticPush: nil,\n\t}},\n}\n\nvar (\n\tConfig = viper.New()\n\n\t// BypassInit can be used to bypass the init() function by setting this\n\t// value to True at compile time.\n\t//\n\t// go build -ldflags \"-X 'github.com/gwuhaolin/livego/configure.BypassInit=true'\" -o livego main.go\n\tBypassInit string = \"\"\n)\n\nfunc initLog() {\n\tif l, err := log.ParseLevel(Config.GetString(\"level\")); err == nil {\n\t\tlog.SetLevel(l)\n\t\tlog.SetReportCaller(l == log.DebugLevel)\n\t}\n}\n\nfunc init() {\n\tif BypassInit == \"\" {\n\t\tinitDefault()\n\t}\n}\n\nfunc initDefault() {\n\tdefer Init()\n\n\t// Default config\n\tb, _ := json.Marshal(defaultConf)\n\tdefaultConfig := bytes.NewReader(b)\n\tviper.SetConfigType(\"json\")\n\tviper.ReadConfig(defaultConfig)\n\tConfig.MergeConfigMap(viper.AllSettings())\n\n\t// Flags\n\tpflag.String(\"rtmp_addr\", \":1935\", \"RTMP server listen address\")\n\tpflag.Bool(\"enable_rtmps\", false, \"enable server session RTMPS\")\n\tpflag.String(\"rtmps_cert\", \"server.crt\", \"cert file path required for RTMPS\")\n\tpflag.String(\"rtmps_key\", \"server.key\", \"key file path required for RTMPS\")\n\tpflag.String(\"httpflv_addr\", \":7001\", \"HTTP-FLV server listen address\")\n\tpflag.String(\"hls_addr\", \":7002\", \"HLS server listen address\")\n\tpflag.String(\"api_addr\", \":8090\", \"HTTP manage interface server listen address\")\n\tpflag.String(\"config_file\", \"livego.yaml\", \"configure filename\")\n\tpflag.String(\"level\", \"info\", \"Log level\")\n\tpflag.Bool(\"hls_keep_after_end\", false, \"Maintains the HLS after the stream ends\")\n\tpflag.String(\"flv_dir\", \"tmp\", \"output flv file at flvDir/APP/KEY_TIME.flv\")\n\tpflag.Int(\"read_timeout\", 10, \"read time out\")\n\tpflag.Int(\"write_timeout\", 10, \"write time out\")\n\tpflag.Int(\"gop_num\", 1, \"gop num\")\n\tpflag.Bool(\"enable_tls_verify\", true, \"Use system root CA to verify RTMPS connection, set this flag to false on Windows\")\n\tpflag.Parse()\n\tConfig.BindPFlags(pflag.CommandLine)\n\n\t// File\n\tConfig.SetConfigFile(Config.GetString(\"config_file\"))\n\tConfig.AddConfigPath(\".\")\n\terr := Config.ReadInConfig()\n\tif err != nil {\n\t\tlog.Warning(err)\n\t\tlog.Info(\"Using default config\")\n\t} else {\n\t\tConfig.MergeInConfig()\n\t}\n\n\t// Environment\n\treplacer := strings.NewReplacer(\".\", \"_\")\n\tConfig.SetEnvKeyReplacer(replacer)\n\tConfig.AllowEmptyEnv(true)\n\tConfig.AutomaticEnv()\n\n\t// Log\n\tinitLog()\n\n\t// Print final config\n\tc := ServerCfg{}\n\tConfig.Unmarshal(&c)\n\tlog.Debugf(\"Current configurations: \\n%# v\", pretty.Formatter(c))\n}\n\nfunc CheckAppName(appname string) bool {\n\tapps := Applications{}\n\tConfig.UnmarshalKey(\"server\", &apps)\n\tfor _, app := range apps {\n\t\tif app.Appname == appname {\n\t\t\treturn app.Live\n\t\t}\n\t}\n\treturn false\n}\n\nfunc GetStaticPushUrlList(appname string) ([]string, bool) {\n\tapps := Applications{}\n\tConfig.UnmarshalKey(\"server\", &apps)\n\tfor _, app := range apps {\n\t\tif (app.Appname == appname) && app.Live {\n\t\t\tif len(app.StaticPush) > 0 {\n\t\t\t\treturn app.StaticPush, true\n\t\t\t} else {\n\t\t\t\treturn nil, false\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, false\n}\n"
  },
  {
    "path": "container/flv/demuxer.go",
    "content": "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)\n\ntype Demuxer struct {\n}\n\nfunc NewDemuxer() *Demuxer {\n\treturn &Demuxer{}\n}\n\nfunc (d *Demuxer) DemuxH(p *av.Packet) error {\n\tvar tag Tag\n\t_, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.Header = &tag\n\n\treturn nil\n}\n\nfunc (d *Demuxer) Demux(p *av.Packet) error {\n\tvar tag Tag\n\tn, err := tag.ParseMediaTagHeader(p.Data, p.IsVideo)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif tag.CodecID() == av.VIDEO_H264 &&\n\t\tp.Data[0] == 0x17 && p.Data[1] == 0x02 {\n\t\treturn ErrAvcEndSEQ\n\t}\n\tp.Header = &tag\n\tp.Data = p.Data[n:]\n\n\treturn nil\n}\n"
  },
  {
    "path": "container/flv/muxer.go",
    "content": "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/livego/configure\"\n\t\"github.com/gwuhaolin/livego/protocol/amf\"\n\t\"github.com/gwuhaolin/livego/utils/pio\"\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tflvHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}\n)\n\n/*\nfunc NewFlv(handler av.Handler, info av.Info) {\n\tpatths := strings.SplitN(info.Key, \"/\", 2)\n\n\tif len(patths) != 2 {\n\t\tlog.Warning(\"invalid info\")\n\t\treturn\n\t}\n\n\tw, err := os.OpenFile(*flvFile, os.O_CREATE|os.O_RDWR, 0755)\n\tif err != nil {\n\t\tlog.Error(\"open file error: \", err)\n\t}\n\n\twriter := NewFLVWriter(patths[0], patths[1], info.URL, w)\n\n\thandler.HandleWriter(writer)\n\n\twriter.Wait()\n\t// close flv file\n\tlog.Debug(\"close flv file\")\n\twriter.ctx.Close()\n}\n*/\n\nconst (\n\theaderLen = 11\n)\n\ntype FLVWriter struct {\n\tUid string\n\tav.RWBaser\n\tapp, title, url string\n\tbuf             []byte\n\tclosed          chan struct{}\n\tctx             *os.File\n\tclosedWriter    bool\n}\n\nfunc NewFLVWriter(app, title, url string, ctx *os.File) *FLVWriter {\n\tret := &FLVWriter{\n\t\tUid:     uid.NewId(),\n\t\tapp:     app,\n\t\ttitle:   title,\n\t\turl:     url,\n\t\tctx:     ctx,\n\t\tRWBaser: av.NewRWBaser(time.Second * 10),\n\t\tclosed:  make(chan struct{}),\n\t\tbuf:     make([]byte, headerLen),\n\t}\n\n\tret.ctx.Write(flvHeader)\n\tpio.PutI32BE(ret.buf[:4], 0)\n\tret.ctx.Write(ret.buf[:4])\n\n\treturn ret\n}\n\nfunc (writer *FLVWriter) Write(p *av.Packet) error {\n\twriter.RWBaser.SetPreTime()\n\th := writer.buf[:headerLen]\n\ttypeID := av.TAG_VIDEO\n\tif !p.IsVideo {\n\t\tif p.IsMetadata {\n\t\t\tvar err error\n\t\t\ttypeID = av.TAG_SCRIPTDATAAMF0\n\t\t\tp.Data, err = amf.MetaDataReform(p.Data, amf.DEL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\ttypeID = av.TAG_AUDIO\n\t\t}\n\t}\n\tdataLen := len(p.Data)\n\ttimestamp := p.TimeStamp\n\ttimestamp += writer.BaseTimeStamp()\n\twriter.RWBaser.RecTimeStamp(timestamp, uint32(typeID))\n\n\tpreDataLen := dataLen + headerLen\n\ttimestampbase := timestamp & 0xffffff\n\ttimestampExt := timestamp >> 24 & 0xff\n\n\tpio.PutU8(h[0:1], uint8(typeID))\n\tpio.PutI24BE(h[1:4], int32(dataLen))\n\tpio.PutI24BE(h[4:7], int32(timestampbase))\n\tpio.PutU8(h[7:8], uint8(timestampExt))\n\n\tif _, err := writer.ctx.Write(h); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := writer.ctx.Write(p.Data); err != nil {\n\t\treturn err\n\t}\n\n\tpio.PutI32BE(h[:4], int32(preDataLen))\n\tif _, err := writer.ctx.Write(h[:4]); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (writer *FLVWriter) Wait() {\n\tselect {\n\tcase <-writer.closed:\n\t\treturn\n\t}\n}\n\nfunc (writer *FLVWriter) Close(error) {\n\tif writer.closedWriter {\n\t\treturn\n\t}\n\twriter.closedWriter = true\n\twriter.ctx.Close()\n\tclose(writer.closed)\n}\n\nfunc (writer *FLVWriter) Info() (ret av.Info) {\n\tret.UID = writer.Uid\n\tret.URL = writer.url\n\tret.Key = writer.app + \"/\" + writer.title\n\treturn\n}\n\ntype FlvDvr struct{}\n\nfunc (f *FlvDvr) GetWriter(info av.Info) av.WriteCloser {\n\tpaths := strings.SplitN(info.Key, \"/\", 2)\n\tif len(paths) != 2 {\n\t\tlog.Warning(\"invalid info\")\n\t\treturn nil\n\t}\n\n\tflvDir := configure.Config.GetString(\"flv_dir\")\n\n\terr := os.MkdirAll(path.Join(flvDir, paths[0]), 0755)\n\tif err != nil {\n\t\tlog.Error(\"mkdir error: \", err)\n\t\treturn nil\n\t}\n\n\tfileName := fmt.Sprintf(\"%s_%d.%s\", path.Join(flvDir, info.Key), time.Now().Unix(), \"flv\")\n\tlog.Debug(\"flv dvr save stream to: \", fileName)\n\tw, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0755)\n\tif err != nil {\n\t\tlog.Error(\"open file error: \", err)\n\t\treturn nil\n\t}\n\n\twriter := NewFLVWriter(paths[0], paths[1], info.URL, w)\n\tlog.Debug(\"new flv dvr: \", writer.Info())\n\treturn writer\n}\n"
  },
  {
    "path": "container/flv/tag.go",
    "content": "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  uint32\n\ttimeStamp uint32\n\tstreamID  uint32 // always 0\n}\n\ntype mediaTag struct {\n\t/*\n\t\tSoundFormat: UB[4]\n\t\t0 = Linear PCM, platform endian\n\t\t1 = ADPCM\n\t\t2 = MP3\n\t\t3 = Linear PCM, little endian\n\t\t4 = Nellymoser 16-kHz mono\n\t\t5 = Nellymoser 8-kHz mono\n\t\t6 = Nellymoser\n\t\t7 = G.711 A-law logarithmic PCM\n\t\t8 = G.711 mu-law logarithmic PCM\n\t\t9 = reserved\n\t\t10 = AAC\n\t\t11 = Speex\n\t\t14 = MP3 8-Khz\n\t\t15 = Device-specific sound\n\t\tFormats 7, 8, 14, and 15 are reserved for internal use\n\t\tAAC is supported in Flash Player 9,0,115,0 and higher.\n\t\tSpeex is supported in Flash Player 10 and higher.\n\t*/\n\tsoundFormat uint8\n\n\t/*\n\t\tSoundRate: UB[2]\n\t\tSampling rate\n\t\t0 = 5.5-kHz For AAC: always 3\n\t\t1 = 11-kHz\n\t\t2 = 22-kHz\n\t\t3 = 44-kHz\n\t*/\n\tsoundRate uint8\n\n\t/*\n\t\tSoundSize: UB[1]\n\t\t0 = snd8Bit\n\t\t1 = snd16Bit\n\t\tSize of each sample.\n\t\tThis parameter only pertains to uncompressed formats.\n\t\tCompressed formats always decode to 16 bits internally\n\t*/\n\tsoundSize uint8\n\n\t/*\n\t\tSoundType: UB[1]\n\t\t0 = sndMono\n\t\t1 = sndStereo\n\t\tMono or stereo sound For Nellymoser: always 0\n\t\tFor AAC: always 1\n\t*/\n\tsoundType uint8\n\n\t/*\n\t\t0: AAC sequence header\n\t\t1: AAC raw\n\t*/\n\taacPacketType uint8\n\n\t/*\n\t\t1: keyframe (for AVC, a seekable frame)\n\t\t2: inter frame (for AVC, a non- seekable frame)\n\t\t3: disposable inter frame (H.263 only)\n\t\t4: generated keyframe (reserved for server use only)\n\t\t5: video info/command frame\n\t*/\n\tframeType uint8\n\n\t/*\n\t\t1: JPEG (currently unused)\n\t\t2: Sorenson H.263\n\t\t3: Screen video\n\t\t4: On2 VP6\n\t\t5: On2 VP6 with alpha channel\n\t\t6: Screen video version 2\n\t\t7: AVC\n\t*/\n\tcodecID uint8\n\n\t/*\n\t\t0: AVC sequence header\n\t\t1: AVC NALU\n\t\t2: AVC end of sequence (lower level NALU sequence ender is not required or supported)\n\t*/\n\tavcPacketType uint8\n\n\tcompositionTime int32\n}\n\ntype Tag struct {\n\tflvt   flvTag\n\tmediat mediaTag\n}\n\nfunc (tag *Tag) SoundFormat() uint8 {\n\treturn tag.mediat.soundFormat\n}\n\nfunc (tag *Tag) AACPacketType() uint8 {\n\treturn tag.mediat.aacPacketType\n}\n\nfunc (tag *Tag) IsKeyFrame() bool {\n\treturn tag.mediat.frameType == av.FRAME_KEY\n}\n\nfunc (tag *Tag) IsSeq() bool {\n\treturn tag.mediat.frameType == av.FRAME_KEY &&\n\t\ttag.mediat.avcPacketType == av.AVC_SEQHDR\n}\n\nfunc (tag *Tag) CodecID() uint8 {\n\treturn tag.mediat.codecID\n}\n\nfunc (tag *Tag) CompositionTime() int32 {\n\treturn tag.mediat.compositionTime\n}\n\n// ParseMediaTagHeader, parse video, audio, tag header\nfunc (tag *Tag) ParseMediaTagHeader(b []byte, isVideo bool) (n int, err error) {\n\tswitch isVideo {\n\tcase false:\n\t\tn, err = tag.parseAudioHeader(b)\n\tcase true:\n\t\tn, err = tag.parseVideoHeader(b)\n\t}\n\treturn\n}\n\nfunc (tag *Tag) parseAudioHeader(b []byte) (n int, err error) {\n\tif len(b) < n+1 {\n\t\terr = fmt.Errorf(\"invalid audiodata len=%d\", len(b))\n\t\treturn\n\t}\n\tflags := b[0]\n\ttag.mediat.soundFormat = flags >> 4\n\ttag.mediat.soundRate = (flags >> 2) & 0x3\n\ttag.mediat.soundSize = (flags >> 1) & 0x1\n\ttag.mediat.soundType = flags & 0x1\n\tn++\n\tswitch tag.mediat.soundFormat {\n\tcase av.SOUND_AAC:\n\t\ttag.mediat.aacPacketType = b[1]\n\t\tn++\n\t}\n\treturn\n}\n\nfunc (tag *Tag) parseVideoHeader(b []byte) (n int, err error) {\n\tif len(b) < n+5 {\n\t\terr = fmt.Errorf(\"invalid videodata len=%d\", len(b))\n\t\treturn\n\t}\n\tflags := b[0]\n\ttag.mediat.frameType = flags >> 4\n\ttag.mediat.codecID = flags & 0xf\n\tn++\n\tif tag.mediat.frameType == av.FRAME_INTER || tag.mediat.frameType == av.FRAME_KEY {\n\t\ttag.mediat.avcPacketType = b[1]\n\t\tfor i := 2; i < 5; i++ {\n\t\t\ttag.mediat.compositionTime = tag.mediat.compositionTime<<8 + int32(b[i])\n\t\t}\n\t\tn += 4\n\t}\n\treturn\n}\n"
  },
  {
    "path": "container/ts/crc32.go",
    "content": "package ts\n\nfunc GenCrc32(src []byte) uint32 {\n\tcrcTable := []uint32{\n\t\t0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,\n\t\t0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,\n\t\t0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,\n\t\t0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,\n\t\t0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,\n\t\t0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,\n\t\t0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,\n\t\t0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,\n\t\t0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,\n\t\t0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,\n\t\t0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,\n\t\t0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,\n\t\t0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,\n\t\t0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,\n\t\t0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,\n\t\t0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,\n\t\t0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,\n\t\t0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,\n\t\t0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,\n\t\t0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,\n\t\t0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,\n\t\t0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,\n\t\t0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,\n\t\t0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,\n\t\t0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,\n\t\t0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,\n\t\t0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,\n\t\t0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,\n\t\t0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,\n\t\t0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,\n\t\t0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,\n\t\t0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,\n\t\t0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,\n\t\t0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,\n\t\t0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,\n\t\t0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,\n\t\t0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,\n\t\t0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,\n\t\t0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,\n\t\t0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,\n\t\t0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,\n\t\t0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,\n\t\t0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,\n\t\t0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,\n\t\t0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,\n\t\t0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,\n\t\t0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,\n\t\t0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,\n\t\t0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,\n\t\t0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,\n\t\t0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,\n\t\t0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,\n\t\t0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,\n\t\t0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,\n\t\t0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,\n\t\t0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,\n\t\t0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,\n\t\t0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,\n\t\t0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,\n\t\t0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,\n\t\t0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,\n\t\t0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,\n\t\t0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,\n\t\t0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}\n\n\tj := byte(0)\n\tcrc32 := uint32(0xFFFFFFFF)\n\tfor i := 0; i < len(src); i++ {\n\t\tj = (byte(crc32>>24) ^ src[i]) & 0xff\n\t\tcrc32 = uint32(uint32(crc32<<8) ^ uint32(crcTable[j]))\n\t}\n\n\treturn crc32\n}\n"
  },
  {
    "path": "container/ts/muxer.go",
    "content": "package ts\n\nimport (\n\t\"io\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nconst (\n\ttsDefaultDataLen = 184\n\ttsPacketLen      = 188\n\th264DefaultHZ    = 90\n\n\tvideoPID = 0x100\n\taudioPID = 0x101\n\tvideoSID = 0xe0\n\taudioSID = 0xc0\n)\n\ntype Muxer struct {\n\tvideoCc  byte\n\taudioCc  byte\n\tpatCc    byte\n\tpmtCc    byte\n\tpat      [tsPacketLen]byte\n\tpmt      [tsPacketLen]byte\n\ttsPacket [tsPacketLen]byte\n}\n\nfunc NewMuxer() *Muxer {\n\treturn &Muxer{}\n}\n\nfunc (muxer *Muxer) Mux(p *av.Packet, w io.Writer) error {\n\tfirst := true\n\twBytes := 0\n\tpesIndex := 0\n\ttmpLen := byte(0)\n\tdataLen := byte(0)\n\n\tvar pes pesHeader\n\tdts := int64(p.TimeStamp) * int64(h264DefaultHZ)\n\tpts := dts\n\tpid := audioPID\n\tvar videoH av.VideoPacketHeader\n\tif p.IsVideo {\n\t\tpid = videoPID\n\t\tvideoH, _ = p.Header.(av.VideoPacketHeader)\n\t\tpts = dts + int64(videoH.CompositionTime())*int64(h264DefaultHZ)\n\t}\n\terr := pes.packet(p, pts, dts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpesHeaderLen := pes.len\n\tpacketBytesLen := len(p.Data) + int(pesHeaderLen)\n\n\tfor {\n\t\tif packetBytesLen <= 0 {\n\t\t\tbreak\n\t\t}\n\t\tif p.IsVideo {\n\t\t\tmuxer.videoCc++\n\t\t\tif muxer.videoCc > 0xf {\n\t\t\t\tmuxer.videoCc = 0\n\t\t\t}\n\t\t} else {\n\t\t\tmuxer.audioCc++\n\t\t\tif muxer.audioCc > 0xf {\n\t\t\t\tmuxer.audioCc = 0\n\t\t\t}\n\t\t}\n\n\t\ti := byte(0)\n\n\t\t//sync byte\n\t\tmuxer.tsPacket[i] = 0x47\n\t\ti++\n\n\t\t//error indicator, unit start indicator,ts priority,pid\n\t\tmuxer.tsPacket[i] = byte(pid >> 8) //pid high 5 bits\n\t\tif first {\n\t\t\tmuxer.tsPacket[i] = muxer.tsPacket[i] | 0x40 //unit start indicator\n\t\t}\n\t\ti++\n\n\t\t//pid low 8 bits\n\t\tmuxer.tsPacket[i] = byte(pid)\n\t\ti++\n\n\t\t//scram control, adaptation control, counter\n\t\tif p.IsVideo {\n\t\t\tmuxer.tsPacket[i] = 0x10 | byte(muxer.videoCc&0x0f)\n\t\t} else {\n\t\t\tmuxer.tsPacket[i] = 0x10 | byte(muxer.audioCc&0x0f)\n\t\t}\n\t\ti++\n\n\t\t//关键帧需要加pcr\n\t\tif first && p.IsVideo && videoH.IsKeyFrame() {\n\t\t\tmuxer.tsPacket[3] |= 0x20\n\t\t\tmuxer.tsPacket[i] = 7\n\t\t\ti++\n\t\t\tmuxer.tsPacket[i] = 0x50\n\t\t\ti++\n\t\t\tmuxer.writePcr(muxer.tsPacket[0:], i, dts)\n\t\t\ti += 6\n\t\t}\n\n\t\t//frame data\n\t\tif packetBytesLen >= tsDefaultDataLen {\n\t\t\tdataLen = tsDefaultDataLen\n\t\t\tif first {\n\t\t\t\tdataLen -= (i - 4)\n\t\t\t}\n\t\t} else {\n\t\t\tmuxer.tsPacket[3] |= 0x20 //have adaptation\n\t\t\tremainBytes := byte(0)\n\t\t\tdataLen = byte(packetBytesLen)\n\t\t\tif first {\n\t\t\t\tremainBytes = tsDefaultDataLen - dataLen - (i - 4)\n\t\t\t} else {\n\t\t\t\tremainBytes = tsDefaultDataLen - dataLen\n\t\t\t}\n\t\t\tmuxer.adaptationBufInit(muxer.tsPacket[i:], byte(remainBytes))\n\t\t\ti += remainBytes\n\t\t}\n\t\tif first && i < tsPacketLen && pesHeaderLen > 0 {\n\t\t\ttmpLen = tsPacketLen - i\n\t\t\tif pesHeaderLen <= tmpLen {\n\t\t\t\ttmpLen = pesHeaderLen\n\t\t\t}\n\t\t\tcopy(muxer.tsPacket[i:], pes.data[pesIndex:pesIndex+int(tmpLen)])\n\t\t\ti += tmpLen\n\t\t\tpacketBytesLen -= int(tmpLen)\n\t\t\tdataLen -= tmpLen\n\t\t\tpesHeaderLen -= tmpLen\n\t\t\tpesIndex += int(tmpLen)\n\t\t}\n\n\t\tif i < tsPacketLen {\n\t\t\ttmpLen = tsPacketLen - i\n\t\t\tif tmpLen <= dataLen {\n\t\t\t\tdataLen = tmpLen\n\t\t\t}\n\t\t\tcopy(muxer.tsPacket[i:], p.Data[wBytes:wBytes+int(dataLen)])\n\t\t\twBytes += int(dataLen)\n\t\t\tpacketBytesLen -= int(dataLen)\n\t\t}\n\t\tif w != nil {\n\t\t\tif _, err := w.Write(muxer.tsPacket[0:]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tfirst = false\n\t}\n\n\treturn nil\n}\n\n//PAT return pat data\nfunc (muxer *Muxer) PAT() []byte {\n\ti := 0\n\tremainByte := 0\n\ttsHeader := []byte{0x47, 0x40, 0x00, 0x10, 0x00}\n\tpatHeader := []byte{0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x01}\n\n\tif muxer.patCc > 0xf {\n\t\tmuxer.patCc = 0\n\t}\n\ttsHeader[3] |= muxer.patCc & 0x0f\n\tmuxer.patCc++\n\n\tcopy(muxer.pat[i:], tsHeader)\n\ti += len(tsHeader)\n\n\tcopy(muxer.pat[i:], patHeader)\n\ti += len(patHeader)\n\n\tcrc32Value := GenCrc32(patHeader)\n\tmuxer.pat[i] = byte(crc32Value >> 24)\n\ti++\n\tmuxer.pat[i] = byte(crc32Value >> 16)\n\ti++\n\tmuxer.pat[i] = byte(crc32Value >> 8)\n\ti++\n\tmuxer.pat[i] = byte(crc32Value)\n\ti++\n\n\tremainByte = int(tsPacketLen - i)\n\tfor j := 0; j < remainByte; j++ {\n\t\tmuxer.pat[i+j] = 0xff\n\t}\n\n\treturn muxer.pat[0:]\n}\n\n// PMT return pmt data\nfunc (muxer *Muxer) PMT(soundFormat byte, hasVideo bool) []byte {\n\ti := int(0)\n\tj := int(0)\n\tvar progInfo []byte\n\tremainBytes := int(0)\n\ttsHeader := []byte{0x47, 0x50, 0x01, 0x10, 0x00}\n\tpmtHeader := []byte{0x02, 0xb0, 0xff, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x00}\n\tif !hasVideo {\n\t\tpmtHeader[9] = 0x01\n\t\tprogInfo = []byte{0x0f, 0xe1, 0x01, 0xf0, 0x00}\n\t} else {\n\t\tprogInfo = []byte{0x1b, 0xe1, 0x00, 0xf0, 0x00, //h264 or h265*\n\t\t\t0x0f, 0xe1, 0x01, 0xf0, 0x00, //mp3 or aac\n\t\t}\n\t}\n\tpmtHeader[2] = byte(len(progInfo) + 9 + 4)\n\n\tif muxer.pmtCc > 0xf {\n\t\tmuxer.pmtCc = 0\n\t}\n\ttsHeader[3] |= muxer.pmtCc & 0x0f\n\tmuxer.pmtCc++\n\n\tif soundFormat == 2 ||\n\t\tsoundFormat == 14 {\n\t\tif hasVideo {\n\t\t\tprogInfo[5] = 0x4\n\t\t} else {\n\t\t\tprogInfo[0] = 0x4\n\t\t}\n\t}\n\n\tcopy(muxer.pmt[i:], tsHeader)\n\ti += len(tsHeader)\n\n\tcopy(muxer.pmt[i:], pmtHeader)\n\ti += len(pmtHeader)\n\n\tcopy(muxer.pmt[i:], progInfo[0:])\n\ti += len(progInfo)\n\n\tcrc32Value := GenCrc32(muxer.pmt[5 : 5+len(pmtHeader)+len(progInfo)])\n\tmuxer.pmt[i] = byte(crc32Value >> 24)\n\ti++\n\tmuxer.pmt[i] = byte(crc32Value >> 16)\n\ti++\n\tmuxer.pmt[i] = byte(crc32Value >> 8)\n\ti++\n\tmuxer.pmt[i] = byte(crc32Value)\n\ti++\n\n\tremainBytes = int(tsPacketLen - i)\n\tfor j = 0; j < remainBytes; j++ {\n\t\tmuxer.pmt[i+j] = 0xff\n\t}\n\n\treturn muxer.pmt[0:]\n}\n\nfunc (muxer *Muxer) adaptationBufInit(src []byte, remainBytes byte) {\n\tsrc[0] = byte(remainBytes - 1)\n\tif remainBytes == 1 {\n\t} else {\n\t\tsrc[1] = 0x00\n\t\tfor i := 2; i < len(src); i++ {\n\t\t\tsrc[i] = 0xff\n\t\t}\n\t}\n\treturn\n}\n\nfunc (muxer *Muxer) writePcr(b []byte, i byte, pcr int64) error {\n\tb[i] = byte(pcr >> 25)\n\ti++\n\tb[i] = byte((pcr >> 17) & 0xff)\n\ti++\n\tb[i] = byte((pcr >> 9) & 0xff)\n\ti++\n\tb[i] = byte((pcr >> 1) & 0xff)\n\ti++\n\tb[i] = byte(((pcr & 0x1) << 7) | 0x7e)\n\ti++\n\tb[i] = 0x00\n\n\treturn nil\n}\n\ntype pesHeader struct {\n\tlen  byte\n\tdata [tsPacketLen]byte\n}\n\n// packet return pes packet\nfunc (header *pesHeader) packet(p *av.Packet, pts, dts int64) error {\n\t//PES header\n\ti := 0\n\theader.data[i] = 0x00\n\ti++\n\theader.data[i] = 0x00\n\ti++\n\theader.data[i] = 0x01\n\ti++\n\n\tsid := audioSID\n\tif p.IsVideo {\n\t\tsid = videoSID\n\t}\n\theader.data[i] = byte(sid)\n\ti++\n\n\tflag := 0x80\n\tptslen := 5\n\tdtslen := ptslen\n\theaderSize := ptslen\n\tif p.IsVideo && pts != dts {\n\t\tflag |= 0x40\n\t\theaderSize += 5 //add dts\n\t}\n\tsize := len(p.Data) + headerSize + 3\n\tif size > 0xffff {\n\t\tsize = 0\n\t}\n\theader.data[i] = byte(size >> 8)\n\ti++\n\theader.data[i] = byte(size)\n\ti++\n\n\theader.data[i] = 0x80\n\ti++\n\theader.data[i] = byte(flag)\n\ti++\n\theader.data[i] = byte(headerSize)\n\ti++\n\n\theader.writeTs(header.data[0:], i, flag>>6, pts)\n\ti += ptslen\n\tif p.IsVideo && pts != dts {\n\t\theader.writeTs(header.data[0:], i, 1, dts)\n\t\ti += dtslen\n\t}\n\n\theader.len = byte(i)\n\n\treturn nil\n}\n\nfunc (header *pesHeader) writeTs(src []byte, i int, fb int, ts int64) {\n\tval := uint32(0)\n\tif ts > 0x1ffffffff {\n\t\tts -= 0x1ffffffff\n\t}\n\tval = uint32(fb<<4) | ((uint32(ts>>30) & 0x07) << 1) | 1\n\tsrc[i] = byte(val)\n\ti++\n\n\tval = ((uint32(ts>>15) & 0x7fff) << 1) | 1\n\tsrc[i] = byte(val >> 8)\n\ti++\n\tsrc[i] = byte(val)\n\ti++\n\n\tval = (uint32(ts&0x7fff) << 1) | 1\n\tsrc[i] = byte(val >> 8)\n\ti++\n\tsrc[i] = byte(val)\n}\n"
  },
  {
    "path": "container/ts/muxer_test.go",
    "content": "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 TestWriter struct {\n\tbuf   []byte\n\tcount int\n}\n\n//Write write p to w.buf\nfunc (w *TestWriter) Write(p []byte) (int, error) {\n\tw.count++\n\tw.buf = p\n\treturn len(p), nil\n}\n\nfunc TestTSEncoder(t *testing.T) {\n\tat := assert.New(t)\n\tm := NewMuxer()\n\n\tw := &TestWriter{}\n\tdata := []byte{0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d, 0x0b, 0x6d, 0x44, 0xae, 0x81,\n\t\t0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04, 0x80, 0x00, 0x5b, 0xb7,\n\t\t0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00, 0x06, 0x00, 0x38,\n\t}\n\tp := av.Packet{\n\t\tIsVideo: false,\n\t\tData:    data,\n\t}\n\terr := m.Mux(&p, w)\n\tat.Equal(err, nil)\n\tat.Equal(w.count, 1)\n\tat.Equal(w.buf, []byte{0x47, 0x41, 0x01, 0x31, 0x81, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x30,\n\t\t0x80, 0x80, 0x05, 0x21, 0x00, 0x01, 0x00, 0x01, 0xaf, 0x01, 0x21, 0x19, 0xd3, 0x40, 0x7d,\n\t\t0x0b, 0x6d, 0x44, 0xae, 0x81, 0x08, 0x00, 0x89, 0xa0, 0x3e, 0x85, 0xb6, 0x92, 0x57, 0x04,\n\t\t0x80, 0x00, 0x5b, 0xb7, 0x78, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x00,\n\t\t0x06, 0x00, 0x38})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gwuhaolin/livego\n\ngo 1.13\n\nrequire (\n\tgithub.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b\n\tgithub.com/dgrijalva/jwt-go v3.2.0+incompatible\n\tgithub.com/go-redis/redis/v7 v7.2.0\n\tgithub.com/gorilla/mux v1.7.4 // indirect\n\tgithub.com/kr/pretty v0.1.0\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible\n\tgithub.com/satori/go.uuid v1.2.0\n\tgithub.com/sirupsen/logrus v1.5.0\n\tgithub.com/spf13/pflag v1.0.3\n\tgithub.com/spf13/viper v1.6.3\n\tgithub.com/stretchr/testify v1.4.0\n\tgithub.com/urfave/negroni v1.0.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw=\ngithub.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=\ngithub.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=\ngithub.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=\ngithub.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=\ngithub.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=\ngithub.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=\ngithub.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "livego.yaml",
    "content": "# # Logger level\n# level: info\n\n# # FLV Options\n# flv_archive: false\n# flv_dir: \"./tmp\"\n# httpflv_addr: \":7001\"\n\n# # RTMP Options\n# rtmp_noauth: false\n# rtmp_addr: \":1935\"\n# enable_rtmps: true\n# rtmps_cert: server.crt\n# rtmps_key: server.key\n# read_timeout: 10\n# write_timeout: 10\n\n# # HLS Options\n# hls_addr: \":7002\"\n#use_hls_https: true\n\n# # API Options\n# api_addr: \":8090\"\nserver:\n- appname: live\n  live: true\n  hls: true\n  api: true\n  flv: true\n"
  },
  {
    "path": "main.go",
    "content": "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\t\"github.com/gwuhaolin/livego/protocol/api\"\n\t\"github.com/gwuhaolin/livego/protocol/hls\"\n\t\"github.com/gwuhaolin/livego/protocol/httpflv\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar VERSION = \"master\"\n\nfunc startHls() *hls.Server {\n\thlsAddr := configure.Config.GetString(\"hls_addr\")\n\thlsListen, err := net.Listen(\"tcp\", hlsAddr)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\thlsServer := hls.NewServer()\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlog.Error(\"HLS server panic: \", r)\n\t\t\t}\n\t\t}()\n\t\tlog.Info(\"HLS listen On \", hlsAddr)\n\t\thlsServer.Serve(hlsListen)\n\t}()\n\treturn hlsServer\n}\n\nfunc startRtmp(stream *rtmp.RtmpStream, hlsServer *hls.Server) {\n\trtmpAddr := configure.Config.GetString(\"rtmp_addr\")\n\tisRtmps := configure.Config.GetBool(\"enable_rtmps\")\n\n\tvar rtmpListen net.Listener\n\tif isRtmps {\n\t\tcertPath := configure.Config.GetString(\"rtmps_cert\")\n\t\tkeyPath := configure.Config.GetString(\"rtmps_key\")\n\t\tcert, err := tls.LoadX509KeyPair(certPath, keyPath)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\trtmpListen, err = tls.Listen(\"tcp\", rtmpAddr, &tls.Config{\n\t\t\tCertificates: []tls.Certificate{cert},\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t} else {\n\t\tvar err error\n\t\trtmpListen, err = net.Listen(\"tcp\", rtmpAddr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tvar rtmpServer *rtmp.Server\n\n\tif hlsServer == nil {\n\t\trtmpServer = rtmp.NewRtmpServer(stream, nil)\n\t\tlog.Info(\"HLS server disable....\")\n\t} else {\n\t\trtmpServer = rtmp.NewRtmpServer(stream, hlsServer)\n\t\tlog.Info(\"HLS server enable....\")\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Error(\"RTMP server panic: \", r)\n\t\t}\n\t}()\n\tif isRtmps {\n\t\tlog.Info(\"RTMPS Listen On \", rtmpAddr)\n\t} else {\n\t\tlog.Info(\"RTMP Listen On \", rtmpAddr)\n\t}\n\trtmpServer.Serve(rtmpListen)\n}\n\nfunc startHTTPFlv(stream *rtmp.RtmpStream) {\n\thttpflvAddr := configure.Config.GetString(\"httpflv_addr\")\n\n\tflvListen, err := net.Listen(\"tcp\", httpflvAddr)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\thdlServer := httpflv.NewServer(stream)\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlog.Error(\"HTTP-FLV server panic: \", r)\n\t\t\t}\n\t\t}()\n\t\tlog.Info(\"HTTP-FLV listen On \", httpflvAddr)\n\t\thdlServer.Serve(flvListen)\n\t}()\n}\n\nfunc startAPI(stream *rtmp.RtmpStream) {\n\tapiAddr := configure.Config.GetString(\"api_addr\")\n\trtmpAddr := configure.Config.GetString(\"rtmp_addr\")\n\n\tif apiAddr != \"\" {\n\t\topListen, err := net.Listen(\"tcp\", apiAddr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\topServer := api.NewServer(stream, rtmpAddr)\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Error(\"HTTP-API server panic: \", r)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tlog.Info(\"HTTP-API listen On \", apiAddr)\n\t\t\topServer.Serve(opListen)\n\t\t}()\n\t}\n}\n\nfunc init() {\n\tlog.SetFormatter(&log.TextFormatter{\n\t\tFullTimestamp: true,\n\t\tCallerPrettyfier: func(f *runtime.Frame) (string, string) {\n\t\t\tfilename := path.Base(f.File)\n\t\t\treturn fmt.Sprintf(\"%s()\", f.Function), fmt.Sprintf(\" %s:%d\", filename, f.Line)\n\t\t},\n\t})\n}\n\nfunc main() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Error(\"livego panic: \", r)\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}()\n\n\tlog.Infof(`\n     _     _            ____       \n    | |   (_)_   _____ / ___| ___  \n    | |   | \\ \\ / / _ \\ |  _ / _ \\ \n    | |___| |\\ V /  __/ |_| | (_) |\n    |_____|_| \\_/ \\___|\\____|\\___/ \n        version: %s\n\t`, VERSION)\n\n\tapps := configure.Applications{}\n\tconfigure.Config.UnmarshalKey(\"server\", &apps)\n\tfor _, app := range apps {\n\t\tstream := rtmp.NewRtmpStream()\n\t\tvar hlsServer *hls.Server\n\t\tif app.Hls {\n\t\t\thlsServer = startHls()\n\t\t}\n\t\tif app.Flv {\n\t\t\tstartHTTPFlv(stream)\n\t\t}\n\t\tif app.Api {\n\t\t\tstartAPI(stream)\n\t\t}\n\n\t\tstartRtmp(stream, hlsServer)\n\t}\n}\n"
  },
  {
    "path": "parser/aac/parser.go",
    "content": "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\tsampleRate byte\n}\n\ntype mpegCfgInfo struct {\n\tobjectType     byte\n\tsampleRate     byte\n\tchannel        byte\n\tsbr            byte\n\tps             byte\n\tframeLen       byte\n\texceptionLogTs int64\n\textension      *mpegExtension\n}\n\nvar aacRates = []int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}\n\nvar (\n\tspecificBufInvalid = fmt.Errorf(\"audio mpegspecific error\")\n\taudioBufInvalid    = fmt.Errorf(\"audiodata  invalid\")\n)\n\nconst (\n\tadtsHeaderLen = 7\n)\n\ntype Parser struct {\n\tgettedSpecific bool\n\tadtsHeader     []byte\n\tcfgInfo        *mpegCfgInfo\n}\n\nfunc NewParser() *Parser {\n\treturn &Parser{\n\t\tgettedSpecific: false,\n\t\tcfgInfo:        &mpegCfgInfo{},\n\t\tadtsHeader:     make([]byte, adtsHeaderLen),\n\t}\n}\n\nfunc (parser *Parser) specificInfo(src []byte) error {\n\tif len(src) < 2 {\n\t\treturn specificBufInvalid\n\t}\n\tparser.gettedSpecific = true\n\tparser.cfgInfo.objectType = (src[0] >> 3) & 0xff\n\tparser.cfgInfo.sampleRate = ((src[0] & 0x07) << 1) | src[1]>>7\n\tparser.cfgInfo.channel = (src[1] >> 3) & 0x0f\n\treturn nil\n}\n\nfunc (parser *Parser) adts(src []byte, w io.Writer) error {\n\tif len(src) <= 0 || !parser.gettedSpecific {\n\t\treturn audioBufInvalid\n\t}\n\n\tframeLen := uint16(len(src)) + 7\n\n\t//first write adts header\n\tparser.adtsHeader[0] = 0xff\n\tparser.adtsHeader[1] = 0xf1\n\n\tparser.adtsHeader[2] &= 0x00\n\tparser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.objectType-1)<<6\n\tparser.adtsHeader[2] = parser.adtsHeader[2] | (parser.cfgInfo.sampleRate)<<2\n\n\tparser.adtsHeader[3] &= 0x00\n\tparser.adtsHeader[3] = parser.adtsHeader[3] | (parser.cfgInfo.channel<<2)<<4\n\tparser.adtsHeader[3] = parser.adtsHeader[3] | byte((frameLen<<3)>>14)\n\n\tparser.adtsHeader[4] &= 0x00\n\tparser.adtsHeader[4] = parser.adtsHeader[4] | byte((frameLen<<5)>>8)\n\n\tparser.adtsHeader[5] &= 0x00\n\tparser.adtsHeader[5] = parser.adtsHeader[5] | byte(((frameLen<<13)>>13)<<5)\n\tparser.adtsHeader[5] = parser.adtsHeader[5] | (0x7C<<1)>>3\n\tparser.adtsHeader[6] = 0xfc\n\n\tif _, err := w.Write(parser.adtsHeader[0:]); err != nil {\n\t\treturn err\n\t}\n\tif _, err := w.Write(src); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (parser *Parser) SampleRate() int {\n\trate := 44100\n\tif parser.cfgInfo.sampleRate <= byte(len(aacRates)-1) {\n\t\trate = aacRates[parser.cfgInfo.sampleRate]\n\t}\n\treturn rate\n}\n\nfunc (parser *Parser) Parse(b []byte, packetType uint8, w io.Writer) (err error) {\n\tswitch packetType {\n\tcase av.AAC_SEQHDR:\n\t\terr = parser.specificInfo(b)\n\tcase av.AAC_RAW:\n\t\terr = parser.adts(b, w)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "parser/h264/parser.go",
    "content": "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 (\n\tnalu_type_not_define byte = 0\n\tnalu_type_slice      byte = 1  //\u0013\u0011slice_layer_without_partioning_rbsp() sliceheader\n\tnalu_type_dpa        byte = 2  // slice_data_partition_a_layer_rbsp( ), slice_header\n\tnalu_type_dpb        byte = 3  // slice_data_partition_b_layer_rbsp( )\n\tnalu_type_dpc        byte = 4  // slice_data_partition_c_layer_rbsp( )\n\tnalu_type_idr        byte = 5  // slice_layer_without_partitioning_rbsp( ),sliceheader\n\tnalu_type_sei        byte = 6  //sei_rbsp( )\n\tnalu_type_sps        byte = 7  //seq_parameter_set_rbsp( )\n\tnalu_type_pps        byte = 8  //pic_parameter_set_rbsp( )\n\tnalu_type_aud        byte = 9  // access_unit_delimiter_rbsp( )\n\tnalu_type_eoesq      byte = 10 //end_of_seq_rbsp( )\n\tnalu_type_eostream   byte = 11 //end_of_stream_rbsp( )\n\tnalu_type_filler     byte = 12 //filler_data_rbsp( )\n)\n\nconst (\n\tnaluBytesLen int = 4\n\tmaxSpsPpsLen int = 2 * 1024\n)\n\nvar (\n\tdecDataNil        = fmt.Errorf(\"dec buf is nil\")\n\tspsDataError      = fmt.Errorf(\"sps data error\")\n\tppsHeaderError    = fmt.Errorf(\"pps header error\")\n\tppsDataError      = fmt.Errorf(\"pps data error\")\n\tnaluHeaderInvalid = fmt.Errorf(\"nalu header invalid\")\n\tvideoDataInvalid  = fmt.Errorf(\"video data not match\")\n\tdataSizeNotMatch  = fmt.Errorf(\"data size not match\")\n\tnaluBodyLenError  = fmt.Errorf(\"nalu body len error\")\n)\n\nvar startCode = []byte{0x00, 0x00, 0x00, 0x01}\nvar naluAud = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xf0}\n\ntype Parser struct {\n\tframeType    byte\n\tspecificInfo []byte\n\tpps          *bytes.Buffer\n}\n\ntype sequenceHeader struct {\n\tconfigVersion        byte //8bits\n\tavcProfileIndication byte //8bits\n\tprofileCompatility   byte //8bits\n\tavcLevelIndication   byte //8bits\n\treserved1            byte //6bits\n\tnaluLen              byte //2bits\n\treserved2            byte //3bits\n\tspsNum               byte //5bits\n\tppsNum               byte //8bits\n\tspsLen               int\n\tppsLen               int\n}\n\nfunc NewParser() *Parser {\n\treturn &Parser{\n\t\tpps: bytes.NewBuffer(make([]byte, maxSpsPpsLen)),\n\t}\n}\n\n//return value 1:sps, value2 :pps\nfunc (parser *Parser) parseSpecificInfo(src []byte) error {\n\tif len(src) < 9 {\n\t\treturn decDataNil\n\t}\n\tsps := []byte{}\n\tpps := []byte{}\n\n\tvar seq sequenceHeader\n\tseq.configVersion = src[0]\n\tseq.avcProfileIndication = src[1]\n\tseq.profileCompatility = src[2]\n\tseq.avcLevelIndication = src[3]\n\tseq.reserved1 = src[4] & 0xfc\n\tseq.naluLen = src[4]&0x03 + 1\n\tseq.reserved2 = src[5] >> 5\n\n\t//get sps\n\tseq.spsNum = src[5] & 0x1f\n\tseq.spsLen = int(src[6])<<8 | int(src[7])\n\n\tif len(src[8:]) < seq.spsLen || seq.spsLen <= 0 {\n\t\treturn spsDataError\n\t}\n\tsps = append(sps, startCode...)\n\tsps = append(sps, src[8:(8+seq.spsLen)]...)\n\n\t//get pps\n\ttmpBuf := src[(8 + seq.spsLen):]\n\tif len(tmpBuf) < 4 {\n\t\treturn ppsHeaderError\n\t}\n\tseq.ppsNum = tmpBuf[0]\n\tseq.ppsLen = int(0)<<16 | int(tmpBuf[1])<<8 | int(tmpBuf[2])\n\tif len(tmpBuf[3:]) < seq.ppsLen || seq.ppsLen <= 0 {\n\t\treturn ppsDataError\n\t}\n\n\tpps = append(pps, startCode...)\n\tpps = append(pps, tmpBuf[3:]...)\n\n\tparser.specificInfo = append(parser.specificInfo, sps...)\n\tparser.specificInfo = append(parser.specificInfo, pps...)\n\n\treturn nil\n}\n\nfunc (parser *Parser) isNaluHeader(src []byte) bool {\n\tif len(src) < naluBytesLen {\n\t\treturn false\n\t}\n\treturn src[0] == 0x00 &&\n\t\tsrc[1] == 0x00 &&\n\t\tsrc[2] == 0x00 &&\n\t\tsrc[3] == 0x01\n}\n\nfunc (parser *Parser) naluSize(src []byte) (int, error) {\n\tif len(src) < naluBytesLen {\n\t\treturn 0, fmt.Errorf(\"nalusizedata invalid\")\n\t}\n\tbuf := src[:naluBytesLen]\n\tsize := int(0)\n\tfor i := 0; i < len(buf); i++ {\n\t\tsize = size<<8 + int(buf[i])\n\t}\n\treturn size, nil\n}\n\nfunc (parser *Parser) getAnnexbH264(src []byte, w io.Writer) error {\n\tdataSize := len(src)\n\tif dataSize < naluBytesLen {\n\t\treturn videoDataInvalid\n\t}\n\tparser.pps.Reset()\n\t_, err := w.Write(naluAud)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindex := 0\n\tnalLen := 0\n\thasSpsPps := false\n\thasWriteSpsPps := false\n\n\tfor dataSize > 0 {\n\t\tnalLen, err = parser.naluSize(src[index:])\n\t\tif err != nil {\n\t\t\treturn dataSizeNotMatch\n\t\t}\n\t\tindex += naluBytesLen\n\t\tdataSize -= naluBytesLen\n\t\tif dataSize >= nalLen && len(src[index:]) >= nalLen && nalLen > 0 {\n\t\t\tnalType := src[index] & 0x1f\n\t\t\tswitch nalType {\n\t\t\tcase nalu_type_aud:\n\t\t\tcase nalu_type_idr:\n\t\t\t\tif !hasWriteSpsPps {\n\t\t\t\t\thasWriteSpsPps = true\n\t\t\t\t\tif !hasSpsPps {\n\t\t\t\t\t\tif _, err := w.Write(parser.specificInfo); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif _, err := w.Write(parser.pps.Bytes()); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfallthrough\n\t\t\tcase nalu_type_slice:\n\t\t\t\tfallthrough\n\t\t\tcase nalu_type_sei:\n\t\t\t\t_, err := w.Write(startCode)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = w.Write(src[index : index+nalLen])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase nalu_type_sps:\n\t\t\t\tfallthrough\n\t\t\tcase nalu_type_pps:\n\t\t\t\thasSpsPps = true\n\t\t\t\t_, err := parser.pps.Write(startCode)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = parser.pps.Write(src[index : index+nalLen])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tindex += nalLen\n\t\t\tdataSize -= nalLen\n\t\t} else {\n\t\t\treturn naluBodyLenError\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (parser *Parser) Parse(b []byte, isSeq bool, w io.Writer) (err error) {\n\tswitch isSeq {\n\tcase true:\n\t\terr = parser.parseSpecificInfo(b)\n\tcase false:\n\t\t// is annexb\n\t\tif parser.isNaluHeader(b) {\n\t\t\t_, err = w.Write(b)\n\t\t} else {\n\t\t\terr = parser.getAnnexbH264(b, w)\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "parser/h264/parser_test.go",
    "content": "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 *testing.T) {\n\tat := assert.New(t)\n\tseq := []byte{\n\t\t0x01, 0x4d, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x4d, 0x00,\n\t\t0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,\n\t\t0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x01, 0x00,\n\t\t0x04, 0x68, 0xde, 0x31, 0x12,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\terr := d.Parse(seq, true, w)\n\tat.Equal(err, nil)\n\tat.Equal(d.specificInfo, []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00,\n\t\t0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28, 0x28, 0x2f,\n\t\t0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68, 0xde, 0x31, 0x12})\n}\n\nfunc TestH264AnnexbDemux(t *testing.T) {\n\tat := assert.New(t)\n\tnalu := []byte{\n\t\t0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,\n\t\t0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,\n\t\t0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\terr := d.Parse(nalu, false, w)\n\tat.Equal(err, nil)\n\tat.Equal(w.Len(), 41)\n}\n\nfunc TestH264NalueSizeException(t *testing.T) {\n\tat := assert.New(t)\n\tnalu := []byte{\n\t\t0x00, 0x00, 0x10,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\terr := d.Parse(nalu, false, w)\n\tat.Equal(err, fmt.Errorf(\"video data not match\"))\n}\n\nfunc TestH264Mp4Demux(t *testing.T) {\n\tat := assert.New(t)\n\tnalu := []byte{\n\t\t0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,\n\t\t0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x04, 0x68,\n\t\t0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x02, 0x65, 0x23,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\terr := d.Parse(nalu, false, w)\n\tat.Equal(err, nil)\n\tat.Equal(w.Len(), 47)\n\tat.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,\n\t\t0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x68,\n\t\t0xde, 0x31, 0x12, 0x00, 0x00, 0x00, 0x01, 0x65, 0x23})\n}\n\nfunc TestH264Mp4DemuxException1(t *testing.T) {\n\tat := assert.New(t)\n\tnalu := []byte{\n\t\t0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\n\terr := d.Parse(nalu, false, w)\n\tat.Equal(err, naluBodyLenError)\n}\n\nfunc TestH264Mp4DemuxException2(t *testing.T) {\n\tat := assert.New(t)\n\tnalu := []byte{\n\t\t0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x17, 0x67, 0x4d, 0x00, 0x1e, 0xab, 0x40, 0x5a, 0x12, 0x6c, 0x09, 0x28, 0x28,\n\t\t0x28, 0x2f, 0x80, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x61, 0xa8, 0x4a, 0x00, 0x00, 0x00,\n\t}\n\td := NewParser()\n\tw := bytes.NewBuffer(nil)\n\terr := d.Parse(nalu, false, w)\n\tat.Equal(err, naluBodyLenError)\n}\n"
  },
  {
    "path": "parser/mp3/parser.go",
    "content": "package mp3\n\nimport (\n\t\"fmt\"\n)\n\ntype Parser struct {\n\tsamplingFrequency int\n}\n\nfunc NewParser() *Parser {\n\treturn &Parser{}\n}\n\n// sampling_frequency - indicates the sampling frequency, according to the following table.\n// '00' 44.1 kHz\n// '01' 48 kHz\n// '10' 32 kHz\n// '11' reserved\nvar mp3Rates = []int{44100, 48000, 32000}\nvar (\n\terrMp3DataInvalid = fmt.Errorf(\"mp3data  invalid\")\n\terrIndexInvalid   = fmt.Errorf(\"invalid rate index\")\n)\n\nfunc (parser *Parser) Parse(src []byte) error {\n\tif len(src) < 3 {\n\t\treturn errMp3DataInvalid\n\t}\n\tindex := (src[2] >> 2) & 0x3\n\tif index <= byte(len(mp3Rates)-1) {\n\t\tparser.samplingFrequency = mp3Rates[index]\n\t\treturn nil\n\t}\n\treturn errIndexInvalid\n}\n\nfunc (parser *Parser) SampleRate() int {\n\tif parser.samplingFrequency == 0 {\n\t\tparser.samplingFrequency = 44100\n\t}\n\treturn parser.samplingFrequency\n}\n"
  },
  {
    "path": "parser/parser.go",
    "content": "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\"github.com/gwuhaolin/livego/parser/h264\"\n\t\"github.com/gwuhaolin/livego/parser/mp3\"\n)\n\nvar (\n\terrNoAudio = fmt.Errorf(\"demuxer no audio\")\n)\n\ntype CodecParser struct {\n\taac  *aac.Parser\n\tmp3  *mp3.Parser\n\th264 *h264.Parser\n}\n\nfunc NewCodecParser() *CodecParser {\n\treturn &CodecParser{}\n}\n\nfunc (codeParser *CodecParser) SampleRate() (int, error) {\n\tif codeParser.aac == nil && codeParser.mp3 == nil {\n\t\treturn 0, errNoAudio\n\t}\n\tif codeParser.aac != nil {\n\t\treturn codeParser.aac.SampleRate(), nil\n\t}\n\treturn codeParser.mp3.SampleRate(), nil\n}\n\nfunc (codeParser *CodecParser) Parse(p *av.Packet, w io.Writer) (err error) {\n\n\tswitch p.IsVideo {\n\tcase true:\n\t\tf, ok := p.Header.(av.VideoPacketHeader)\n\t\tif ok {\n\t\t\tif f.CodecID() == av.VIDEO_H264 {\n\t\t\t\tif codeParser.h264 == nil {\n\t\t\t\t\tcodeParser.h264 = h264.NewParser()\n\t\t\t\t}\n\t\t\t\terr = codeParser.h264.Parse(p.Data, f.IsSeq(), w)\n\t\t\t}\n\t\t}\n\tcase false:\n\t\tf, ok := p.Header.(av.AudioPacketHeader)\n\t\tif ok {\n\t\t\tswitch f.SoundFormat() {\n\t\t\tcase av.SOUND_AAC:\n\t\t\t\tif codeParser.aac == nil {\n\t\t\t\t\tcodeParser.aac = aac.NewParser()\n\t\t\t\t}\n\t\t\t\terr = codeParser.aac.Parse(p.Data, f.AACPacketType(), w)\n\t\t\tcase av.SOUND_MP3:\n\t\t\t\tif codeParser.mp3 == nil {\n\t\t\t\t\tcodeParser.mp3 = mp3.NewParser()\n\t\t\t\t}\n\t\t\t\terr = codeParser.mp3.Parse(p.Data)\n\t\t\t}\n\t\t}\n\n\t}\n\treturn\n}\n"
  },
  {
    "path": "protocol/amf/amf.go",
    "content": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nfunc (d *Decoder) DecodeBatch(r io.Reader, ver Version) (ret []interface{}, err error) {\n\tvar v interface{}\n\tfor {\n\t\tv, err = d.Decode(r, ver)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tret = append(ret, v)\n\t}\n\treturn\n}\n\nfunc (d *Decoder) Decode(r io.Reader, ver Version) (interface{}, error) {\n\tswitch ver {\n\tcase 0:\n\t\treturn d.DecodeAmf0(r)\n\tcase 3:\n\t\treturn d.DecodeAmf3(r)\n\t}\n\n\treturn nil, fmt.Errorf(\"decode amf: unsupported version %d\", ver)\n}\n\nfunc (e *Encoder) EncodeBatch(w io.Writer, ver Version, val ...interface{}) (int, error) {\n\tfor _, v := range val {\n\t\tif _, err := e.Encode(w, v, ver); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn 0, nil\n}\n\nfunc (e *Encoder) Encode(w io.Writer, val interface{}, ver Version) (int, error) {\n\tswitch ver {\n\tcase AMF0:\n\t\treturn e.EncodeAmf0(w, val)\n\tcase AMF3:\n\t\treturn e.EncodeAmf3(w, val)\n\t}\n\n\treturn 0, fmt.Errorf(\"encode amf: unsupported version %d\", ver)\n}\n"
  },
  {
    "path": "protocol/amf/amf_test.go",
    "content": "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) (result interface{}, err error) {\n\tenc := new(Encoder)\n\tdec := new(Decoder)\n\n\tbuf := new(bytes.Buffer)\n\n\t_, err = enc.Encode(buf, val, ver)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in encode: %s\", err)\n\t}\n\n\tresult, err = dec.Decode(buf, ver)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error in decode: %s\", err)\n\t}\n\n\treturn\n}\n\nfunc Compare(val interface{}, ver Version, name string, t *testing.T) {\n\tresult, err := EncodeAndDecode(val, ver)\n\tif err != nil {\n\t\tt.Errorf(\"%s: %s\", name, err)\n\t}\n\n\tif !reflect.DeepEqual(val, result) {\n\t\tval_v := reflect.ValueOf(val)\n\t\tresult_v := reflect.ValueOf(result)\n\n\t\tt.Errorf(\"%s: comparison failed between %+v (%s) and %+v (%s)\", name, val, val_v.Type(), result, result_v.Type())\n\n\t\tDump(\"expected\", val)\n\t\tDump(\"got\", result)\n\t}\n\n\t// if val != result {\n\t// \tt.Errorf(\"%s: comparison failed between %+v and %+v\", name, val, result)\n\t// }\n}\n\nfunc TestAmf0Number(t *testing.T) {\n\tCompare(float64(3.14159), 0, \"amf0 number float\", t)\n\tCompare(float64(124567890), 0, \"amf0 number high\", t)\n\tCompare(float64(-34.2), 0, \"amf0 number negative\", t)\n}\n\nfunc TestAmf0String(t *testing.T) {\n\tCompare(\"a pup!\", 0, \"amf0 string simple\", t)\n\tCompare(\"日本語\", 0, \"amf0 string utf8\", t)\n}\n\nfunc TestAmf0Boolean(t *testing.T) {\n\tCompare(true, 0, \"amf0 boolean true\", t)\n\tCompare(false, 0, \"amf0 boolean false\", t)\n}\n\nfunc TestAmf0Null(t *testing.T) {\n\tCompare(nil, 0, \"amf0 boolean nil\", t)\n}\n\nfunc TestAmf0Object(t *testing.T) {\n\tobj := make(Object)\n\tobj[\"dog\"] = \"alfie\"\n\tobj[\"coffee\"] = true\n\tobj[\"drugs\"] = false\n\tobj[\"pi\"] = 3.14159\n\n\tres, err := EncodeAndDecode(obj, 0)\n\tif err != nil {\n\t\tt.Errorf(\"amf0 object: %s\", err)\n\t}\n\n\tresult, ok := res.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"amf0 object conversion failed\")\n\t}\n\n\tif result[\"dog\"] != \"alfie\" {\n\t\tt.Errorf(\"amf0 object string: comparison failed\")\n\t}\n\n\tif result[\"coffee\"] != true {\n\t\tt.Errorf(\"amf0 object true: comparison failed\")\n\t}\n\n\tif result[\"drugs\"] != false {\n\t\tt.Errorf(\"amf0 object false: comparison failed\")\n\t}\n\n\tif result[\"pi\"] != float64(3.14159) {\n\t\tt.Errorf(\"amf0 object float: comparison failed\")\n\t}\n}\n\nfunc TestAmf0Array(t *testing.T) {\n\tarr := [5]float64{1, 2, 3, 4, 5}\n\n\tres, err := EncodeAndDecode(arr, 0)\n\tif err != nil {\n\t\tt.Errorf(\"amf0 object: %s\", err)\n\t}\n\n\tresult, ok := res.(Array)\n\tif ok != true {\n\t\tt.Errorf(\"amf0 array conversion failed\")\n\t}\n\n\tfor i := 0; i < len(arr); i++ {\n\t\tif arr[i] != result[i] {\n\t\t\tt.Errorf(\"amf0 array %d comparison failed: %v / %v\", i, arr[i], result[i])\n\t\t}\n\t}\n}\n\nfunc TestAmf3Integer(t *testing.T) {\n\tCompare(int32(0), 3, \"amf3 integer zero\", t)\n\tCompare(int32(1245), 3, \"amf3 integer low\", t)\n\tCompare(int32(123456), 3, \"amf3 integer high\", t)\n}\n\nfunc TestAmf3Double(t *testing.T) {\n\tCompare(float64(3.14159), 3, \"amf3 double float\", t)\n\tCompare(float64(1234567890), 3, \"amf3 double high\", t)\n\tCompare(float64(-12345), 3, \"amf3 double negative\", t)\n}\n\nfunc TestAmf3String(t *testing.T) {\n\tCompare(\"a pup!\", 0, \"amf0 string simple\", t)\n\tCompare(\"日本語\", 0, \"amf0 string utf8\", t)\n}\n\nfunc TestAmf3Boolean(t *testing.T) {\n\tCompare(true, 3, \"amf3 boolean true\", t)\n\tCompare(false, 3, \"amf3 boolean false\", t)\n}\n\nfunc TestAmf3Null(t *testing.T) {\n\tCompare(nil, 3, \"amf3 boolean nil\", t)\n}\n\nfunc TestAmf3Date(t *testing.T) {\n\tt1 := time.Unix(time.Now().Unix(), 0).UTC() // nanoseconds discarded\n\tt2 := time.Date(1983, 9, 4, 12, 4, 8, 0, time.UTC)\n\n\tCompare(t1, 3, \"amf3 date now\", t)\n\tCompare(t2, 3, \"amf3 date earlier\", t)\n}\n\nfunc TestAmf3Array(t *testing.T) {\n\tobj := make(Object)\n\tobj[\"key\"] = \"val\"\n\n\tvar arr Array\n\tarr = append(arr, \"amf\")\n\tarr = append(arr, float64(2))\n\tarr = append(arr, -34.95)\n\tarr = append(arr, true)\n\tarr = append(arr, false)\n\n\tres, err := EncodeAndDecode(arr, 3)\n\tif err != nil {\n\t\tt.Errorf(\"amf3 object: %s\", err)\n\t}\n\n\tresult, ok := res.(Array)\n\tif ok != true {\n\t\tt.Errorf(\"amf3 array conversion failed: %+v\", res)\n\t}\n\n\tfor i := 0; i < len(arr); i++ {\n\t\tif arr[i] != result[i] {\n\t\t\tt.Errorf(\"amf3 array %d comparison failed: %v / %v\", i, arr[i], result[i])\n\t\t}\n\t}\n}\n\nfunc TestAmf3ByteArray(t *testing.T) {\n\tenc := new(Encoder)\n\tdec := new(Decoder)\n\n\tbuf := new(bytes.Buffer)\n\n\texpect := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x00}\n\n\tenc.EncodeAmf3ByteArray(buf, expect, true)\n\n\tresult, err := dec.DecodeAmf3ByteArray(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"err: %s\", err)\n\t}\n\n\tif bytes.Compare(result, expect) != 0 {\n\t\tt.Errorf(\"expected: %+v, got %+v\", expect, buf)\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/const.go",
    "content": "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_BOOLEAN_MARKER        = 0x01\n\tAMF0_STRING_MARKER         = 0x02\n\tAMF0_OBJECT_MARKER         = 0x03\n\tAMF0_MOVIECLIP_MARKER      = 0x04\n\tAMF0_NULL_MARKER           = 0x05\n\tAMF0_UNDEFINED_MARKER      = 0x06\n\tAMF0_REFERENCE_MARKER      = 0x07\n\tAMF0_ECMA_ARRAY_MARKER     = 0x08\n\tAMF0_OBJECT_END_MARKER     = 0x09\n\tAMF0_STRICT_ARRAY_MARKER   = 0x0a\n\tAMF0_DATE_MARKER           = 0x0b\n\tAMF0_LONG_STRING_MARKER    = 0x0c\n\tAMF0_UNSUPPORTED_MARKER    = 0x0d\n\tAMF0_RECORDSET_MARKER      = 0x0e\n\tAMF0_XML_DOCUMENT_MARKER   = 0x0f\n\tAMF0_TYPED_OBJECT_MARKER   = 0x10\n\tAMF0_ACMPLUS_OBJECT_MARKER = 0x11\n)\n\nconst (\n\tAMF0_BOOLEAN_FALSE = 0x00\n\tAMF0_BOOLEAN_TRUE  = 0x01\n\tAMF0_STRING_MAX    = 65535\n\tAMF3_INTEGER_MAX   = 536870911\n)\n\nconst (\n\tAMF3_UNDEFINED_MARKER = 0x00\n\tAMF3_NULL_MARKER      = 0x01\n\tAMF3_FALSE_MARKER     = 0x02\n\tAMF3_TRUE_MARKER      = 0x03\n\tAMF3_INTEGER_MARKER   = 0x04\n\tAMF3_DOUBLE_MARKER    = 0x05\n\tAMF3_STRING_MARKER    = 0x06\n\tAMF3_XMLDOC_MARKER    = 0x07\n\tAMF3_DATE_MARKER      = 0x08\n\tAMF3_ARRAY_MARKER     = 0x09\n\tAMF3_OBJECT_MARKER    = 0x0a\n\tAMF3_XMLSTRING_MARKER = 0x0b\n\tAMF3_BYTEARRAY_MARKER = 0x0c\n)\n\ntype ExternalHandler func(*Decoder, io.Reader) (interface{}, error)\n\ntype Decoder struct {\n\trefCache         []interface{}\n\tstringRefs       []string\n\tobjectRefs       []interface{}\n\ttraitRefs        []Trait\n\texternalHandlers map[string]ExternalHandler\n}\n\nfunc NewDecoder() *Decoder {\n\treturn &Decoder{\n\t\texternalHandlers: make(map[string]ExternalHandler),\n\t}\n}\n\nfunc (d *Decoder) RegisterExternalHandler(name string, f ExternalHandler) {\n\td.externalHandlers[name] = f\n}\n\ntype Encoder struct {\n}\n\ntype Version uint8\n\ntype Array []interface{}\ntype Object map[string]interface{}\n\ntype TypedObject struct {\n\tType   string\n\tObject Object\n}\n\ntype Trait struct {\n\tType           string\n\tExternalizable bool\n\tDynamic        bool\n\tProperties     []string\n}\n\nfunc NewTrait() *Trait {\n\treturn &Trait{}\n}\n\nfunc NewTypedObject() *TypedObject {\n\treturn &TypedObject{\n\t\tType:   \"\",\n\t\tObject: make(Object),\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/decoder_amf0.go",
    "content": "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.Reader) (interface{}, error) {\n\tmarker, err := ReadMarker(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch marker {\n\tcase AMF0_NUMBER_MARKER:\n\t\treturn d.DecodeAmf0Number(r, false)\n\tcase AMF0_BOOLEAN_MARKER:\n\t\treturn d.DecodeAmf0Boolean(r, false)\n\tcase AMF0_STRING_MARKER:\n\t\treturn d.DecodeAmf0String(r, false)\n\tcase AMF0_OBJECT_MARKER:\n\t\treturn d.DecodeAmf0Object(r, false)\n\tcase AMF0_MOVIECLIP_MARKER:\n\t\treturn nil, fmt.Errorf(\"decode amf0: unsupported type movieclip\")\n\tcase AMF0_NULL_MARKER:\n\t\treturn d.DecodeAmf0Null(r, false)\n\tcase AMF0_UNDEFINED_MARKER:\n\t\treturn d.DecodeAmf0Undefined(r, false)\n\tcase AMF0_REFERENCE_MARKER:\n\t\treturn nil, fmt.Errorf(\"decode amf0: unsupported type reference\")\n\tcase AMF0_ECMA_ARRAY_MARKER:\n\t\treturn d.DecodeAmf0EcmaArray(r, false)\n\tcase AMF0_STRICT_ARRAY_MARKER:\n\t\treturn d.DecodeAmf0StrictArray(r, false)\n\tcase AMF0_DATE_MARKER:\n\t\treturn d.DecodeAmf0Date(r, false)\n\tcase AMF0_LONG_STRING_MARKER:\n\t\treturn d.DecodeAmf0LongString(r, false)\n\tcase AMF0_UNSUPPORTED_MARKER:\n\t\treturn d.DecodeAmf0Unsupported(r, false)\n\tcase AMF0_RECORDSET_MARKER:\n\t\treturn nil, fmt.Errorf(\"decode amf0: unsupported type recordset\")\n\tcase AMF0_XML_DOCUMENT_MARKER:\n\t\treturn d.DecodeAmf0XmlDocument(r, false)\n\tcase AMF0_TYPED_OBJECT_MARKER:\n\t\treturn d.DecodeAmf0TypedObject(r, false)\n\tcase AMF0_ACMPLUS_OBJECT_MARKER:\n\t\treturn d.DecodeAmf3(r)\n\t}\n\n\treturn nil, fmt.Errorf(\"decode amf0: unsupported type %d\", marker)\n}\n\n// marker: 1 byte 0x00\n// format: 8 byte big endian float64\nfunc (d *Decoder) DecodeAmf0Number(r io.Reader, decodeMarker bool) (result float64, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_NUMBER_MARKER); err != nil {\n\t\treturn\n\t}\n\n\terr = binary.Read(r, binary.BigEndian, &result)\n\tif err != nil {\n\t\treturn float64(0), fmt.Errorf(\"amf0 decode: unable to read number: %s\", err)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x01\n// format: 1 byte, 0x00 = false, 0x01 = true\nfunc (d *Decoder) DecodeAmf0Boolean(r io.Reader, decodeMarker bool) (result bool, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_BOOLEAN_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar b byte\n\tif b, err = ReadByte(r); err != nil {\n\t\treturn\n\t}\n\n\tif b == AMF0_BOOLEAN_FALSE {\n\t\treturn false, nil\n\t} else if b == AMF0_BOOLEAN_TRUE {\n\t\treturn true, nil\n\t}\n\n\treturn false, fmt.Errorf(\"decode amf0: unexpected value %v for boolean\", b)\n}\n\n// marker: 1 byte 0x02\n// format:\n// - 2 byte big endian uint16 header to determine size\n// - n (size) byte utf8 string\nfunc (d *Decoder) DecodeAmf0String(r io.Reader, decodeMarker bool) (result string, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_STRING_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar length uint16\n\terr = binary.Read(r, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode amf0: unable to decode string length: %s\", err)\n\t}\n\n\tvar bytes = make([]byte, length)\n\tif bytes, err = ReadBytes(r, int(length)); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode amf0: unable to decode string value: %s\", err)\n\t}\n\n\treturn string(bytes), nil\n}\n\n// marker: 1 byte 0x03\n// format:\n// - loop encoded string followed by encoded value\n// - terminated with empty string followed by 1 byte 0x09\nfunc (d *Decoder) DecodeAmf0Object(r io.Reader, decodeMarker bool) (Object, error) {\n\tif err := AssertMarker(r, decodeMarker, AMF0_OBJECT_MARKER); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult := make(Object)\n\td.refCache = append(d.refCache, result)\n\n\tfor {\n\t\tkey, err := d.DecodeAmf0String(r, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif key == \"\" {\n\t\t\tif err = AssertMarker(r, true, AMF0_OBJECT_END_MARKER); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"decode amf0: expected object end marker: %s\", err)\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\n\t\tvalue, err := d.DecodeAmf0(r)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"decode amf0: unable to decode object value: %s\", err)\n\t\t}\n\n\t\tresult[key] = value\n\t}\n\n\treturn result, nil\n\n}\n\n// marker: 1 byte 0x05\n// no additional data\nfunc (d *Decoder) DecodeAmf0Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF0_NULL_MARKER)\n\treturn\n}\n\n// marker: 1 byte 0x06\n// no additional data\nfunc (d *Decoder) DecodeAmf0Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF0_UNDEFINED_MARKER)\n\treturn\n}\n\n// marker: 1 byte 0x07\n// format: 2 byte big endian uint16\n/*\nfunc (d *Decoder) DecodeAmf0Reference(r io.Reader, decodeMarker bool) (interface{}, error) {\n\tif err := AssertMarker(r, decodeMarker, AMF0_REFERENCE_MARKER); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar err error\n\tvar ref uint16\n\n\terr = binary.Read(r, binary.BigEndian, &ref)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode amf0: unable to decode reference id: %s\", err)\n\t}\n\n\tif int(ref) > len(d.refCache) {\n\t\treturn nil, fmt.Errorf(\"decode amf0: bad reference %d (current length %d)\", ref, len(d.refCache))\n\t}\n\n\tresult := d.refCache[ref]\n\n\treturn result, nil\n}\n*/\n\n// marker: 1 byte 0x08\n// format:\n// - 4 byte big endian uint32 with length of associative array\n// - normal object format:\n//   - loop encoded string followed by encoded value\n//   - terminated with empty string followed by 1 byte 0x09\nfunc (d *Decoder) DecodeAmf0EcmaArray(r io.Reader, decodeMarker bool) (Object, error) {\n\tif err := AssertMarker(r, decodeMarker, AMF0_ECMA_ARRAY_MARKER); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar length uint32\n\terr := binary.Read(r, binary.BigEndian, &length)\n\n\tresult, err := d.DecodeAmf0Object(r, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode amf0: unable to decode ecma array object: %s\", err)\n\t}\n\n\treturn result, nil\n}\n\n// marker: 1 byte 0x0a\n// format:\n// - 4 byte big endian uint32 to determine length of associative array\n// - n (length) encoded values\nfunc (d *Decoder) DecodeAmf0StrictArray(r io.Reader, decodeMarker bool) (result Array, err error) {\n\tif err := AssertMarker(r, decodeMarker, AMF0_STRICT_ARRAY_MARKER); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar length uint32\n\terr = binary.Read(r, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode amf0: unable to decode strict array length: %s\", err)\n\t}\n\n\td.refCache = append(d.refCache, result)\n\n\tfor i := uint32(0); i < length; i++ {\n\t\ttmp, err := d.DecodeAmf0(r)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"decode amf0: unable to decode strict array object: %s\", err)\n\t\t}\n\t\tresult = append(result, tmp)\n\t}\n\n\treturn result, nil\n}\n\n// marker: 1 byte 0x0b\n// format:\n// - normal number format:\n//   - 8 byte big endian float64\n// - 2 byte unused\nfunc (d *Decoder) DecodeAmf0Date(r io.Reader, decodeMarker bool) (result float64, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_DATE_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tif result, err = d.DecodeAmf0Number(r, false); err != nil {\n\t\treturn float64(0), fmt.Errorf(\"decode amf0: unable to decode float in date: %s\", err)\n\t}\n\n\tif _, err = ReadBytes(r, 2); err != nil {\n\t\treturn float64(0), fmt.Errorf(\"decode amf0: unable to read 2 trail bytes in date: %s\", err)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x0c\n// format:\n// - 4 byte big endian uint32 header to determine size\n// - n (size) byte utf8 string\nfunc (d *Decoder) DecodeAmf0LongString(r io.Reader, decodeMarker bool) (result string, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_LONG_STRING_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar length uint32\n\terr = binary.Read(r, binary.BigEndian, &length)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode amf0: unable to decode long string length: %s\", err)\n\t}\n\n\tvar bytes = make([]byte, length)\n\tif bytes, err = ReadBytes(r, int(length)); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode amf0: unable to decode long string value: %s\", err)\n\t}\n\n\treturn string(bytes), nil\n}\n\n// marker: 1 byte 0x0d\n// no additional data\nfunc (d *Decoder) DecodeAmf0Unsupported(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF0_UNSUPPORTED_MARKER)\n\treturn\n}\n\n// marker: 1 byte 0x0f\n// format:\n// - normal long string format\n//   - 4 byte big endian uint32 header to determine size\n//   - n (size) byte utf8 string\nfunc (d *Decoder) DecodeAmf0XmlDocument(r io.Reader, decodeMarker bool) (result string, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF0_XML_DOCUMENT_MARKER); err != nil {\n\t\treturn\n\t}\n\n\treturn d.DecodeAmf0LongString(r, false)\n}\n\n// marker: 1 byte 0x10\n// format:\n// - normal string format:\n//   - 2 byte big endian uint16 header to determine size\n//   - n (size) byte utf8 string\n// - normal object format:\n//   - loop encoded string followed by encoded value\n//   - terminated with empty string followed by 1 byte 0x09\nfunc (d *Decoder) DecodeAmf0TypedObject(r io.Reader, decodeMarker bool) (TypedObject, error) {\n\tresult := *new(TypedObject)\n\n\terr := AssertMarker(r, decodeMarker, AMF0_TYPED_OBJECT_MARKER)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\td.refCache = append(d.refCache, result)\n\n\tresult.Type, err = d.DecodeAmf0String(r, false)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"decode amf0: typed object unable to determine type: %s\", err)\n\t}\n\n\tresult.Object, err = d.DecodeAmf0Object(r, false)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"decode amf0: typed object unable to determine object: %s\", err)\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "protocol/amf/decoder_amf0_test.go",
    "content": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestDecodeAmf0Number(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})\n\texpect := float64(1.2)\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test number interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Number(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test number interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0Number(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0BooleanTrue(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x01, 0x01})\n\texpect := true\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test boolean interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Boolean(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test boolean interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0Boolean(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0BooleanFalse(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x01, 0x00})\n\texpect := false\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test boolean interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Boolean(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test boolean interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0Boolean(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0String(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f})\n\texpect := \"foo\"\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test string interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0String(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test string interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0String(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0Object(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok := got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n\n\t// Test object interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Object(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok = got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n\n\t// Test object interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0Object(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok = got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n}\n\nfunc TestDecodeAmf0Null(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x05})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n\n\t// Test null interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Null(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n}\n\nfunc TestDecodeAmf0Undefined(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x06})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n\n\t// Test undefined interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Undefined(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n}\n\n/*\nfunc TestDecodeReference(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x09})\n\n\tdec := &Decoder{}\n\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok := got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\n\t_, ok2 := obj[\"foo\"].(Object)\n\tif ok2 != true {\n\t\tt.Errorf(\"expected foo value to cast to object\")\n\t}\n}\n*/\n\nfunc TestDecodeAmf0EcmaArray(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok := got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n\n\t// Test ecma array interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0EcmaArray(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok = got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to object\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n\n\t// Test ecma array interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0EcmaArray(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tobj, ok = got.(Object)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to ecma array\")\n\t}\n\tif obj[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected {'foo'='bar'}, got %v\", obj)\n\t}\n}\n\nfunc TestDecodeAmf0StrictArray(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tarr, ok := got.(Array)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to strict array\")\n\t}\n\tif arr[0] != float64(5) {\n\t\tt.Errorf(\"expected array[0] to be 5, got %v\", arr[0])\n\t}\n\tif arr[1] != \"foo\" {\n\t\tt.Errorf(\"expected array[1] to be 'foo', got %v\", arr[1])\n\t}\n\tif arr[2] != nil {\n\t\tt.Errorf(\"expected array[2] to be nil, got %v\", arr[2])\n\t}\n\n\t// Test strict array interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0StrictArray(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tarr, ok = got.(Array)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to strict array\")\n\t}\n\tif arr[0] != float64(5) {\n\t\tt.Errorf(\"expected array[0] to be 5, got %v\", arr[0])\n\t}\n\tif arr[1] != \"foo\" {\n\t\tt.Errorf(\"expected array[1] to be 'foo', got %v\", arr[1])\n\t}\n\tif arr[2] != nil {\n\t\tt.Errorf(\"expected array[2] to be nil, got %v\", arr[2])\n\t}\n\n\t// Test strict array interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0StrictArray(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tarr, ok = got.(Array)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to strict array\")\n\t}\n\tif arr[0] != float64(5) {\n\t\tt.Errorf(\"expected array[0] to be 5, got %v\", arr[0])\n\t}\n\tif arr[1] != \"foo\" {\n\t\tt.Errorf(\"expected array[1] to be 'foo', got %v\", arr[1])\n\t}\n\tif arr[2] != nil {\n\t\tt.Errorf(\"expected array[2] to be nil, got %v\", arr[2])\n\t}\n}\n\nfunc TestDecodeAmf0Date(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x0b, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})\n\texpect := float64(5)\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test date interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Date(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test date interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0Date(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0LongString(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x0c, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})\n\texpect := \"foo\"\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test long string interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0LongString(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test long string interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0LongString(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0Unsupported(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x0d})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n\n\t// Test unsupported interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0Unsupported(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n}\n\nfunc TestDecodeAmf0XmlDocument(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x0f, 0x00, 0x00, 0x00, 0x03, 0x66, 0x6f, 0x6f})\n\texpect := \"foo\"\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test long string interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0XmlDocument(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\t// Test long string interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0XmlDocument(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf0TypedObject(t *testing.T) {\n\n\tbuf := bytes.NewReader([]byte{\n\t\t0x10, 0x00, 0x0F, 'o', 'r', 'g',\n\t\t'.', 'a', 'm', 'f', '.', 'A',\n\t\t'S', 'C', 'l', 'a', 's', 's',\n\t\t0x00, 0x03, 'b', 'a', 'z', 0x05,\n\t\t0x00, 0x03, 'f', 'o', 'o', 0x02,\n\t\t0x00, 0x03, 'b', 'a', 'r', 0x00,\n\t\t0x00, 0x09,\n\t})\n\n\tdec := &Decoder{}\n\n\t// Test main interface\n\tgot, err := dec.DecodeAmf0(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\ttobj, ok := got.(TypedObject)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to typed object, got %+v\", tobj)\n\t}\n\tif tobj.Type != \"org.amf.ASClass\" {\n\t\tt.Errorf(\"expected typed object type to be 'class', got %v\", tobj.Type)\n\t}\n\tif tobj.Object[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected typed object object foo to eql bar, got %v\", tobj.Object[\"foo\"])\n\t}\n\tif tobj.Object[\"baz\"] != nil {\n\t\tt.Errorf(\"expected typed object object baz to nil, got %v\", tobj.Object[\"baz\"])\n\t}\n\n\t// Test typed object interface with marker\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf0TypedObject(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\ttobj, ok = got.(TypedObject)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to typed object, got %+v\", tobj)\n\t}\n\tif tobj.Type != \"org.amf.ASClass\" {\n\t\tt.Errorf(\"expected typed object type to be 'class', got %v\", tobj.Type)\n\t}\n\tif tobj.Object[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected typed object object foo to eql bar, got %v\", tobj.Object[\"foo\"])\n\t}\n\tif tobj.Object[\"baz\"] != nil {\n\t\tt.Errorf(\"expected typed object object baz to nil, got %v\", tobj.Object[\"baz\"])\n\t}\n\n\t// Test typed object interface without marker\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf0TypedObject(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\ttobj, ok = got.(TypedObject)\n\tif ok != true {\n\t\tt.Errorf(\"expected result to cast to typed object, got %+v\", tobj)\n\t}\n\tif tobj.Type != \"org.amf.ASClass\" {\n\t\tt.Errorf(\"expected typed object type to be 'class', got %v\", tobj.Type)\n\t}\n\tif tobj.Object[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected typed object object foo to eql bar, got %v\", tobj.Object[\"foo\"])\n\t}\n\tif tobj.Object[\"baz\"] != nil {\n\t\tt.Errorf(\"expected typed object object baz to nil, got %v\", tobj.Object[\"baz\"])\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/decoder_amf3.go",
    "content": "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(r io.Reader) (interface{}, error) {\n\tmarker, err := ReadMarker(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch marker {\n\tcase AMF3_UNDEFINED_MARKER:\n\t\treturn d.DecodeAmf3Undefined(r, false)\n\tcase AMF3_NULL_MARKER:\n\t\treturn d.DecodeAmf3Null(r, false)\n\tcase AMF3_FALSE_MARKER:\n\t\treturn d.DecodeAmf3False(r, false)\n\tcase AMF3_TRUE_MARKER:\n\t\treturn d.DecodeAmf3True(r, false)\n\tcase AMF3_INTEGER_MARKER:\n\t\treturn d.DecodeAmf3Integer(r, false)\n\tcase AMF3_DOUBLE_MARKER:\n\t\treturn d.DecodeAmf3Double(r, false)\n\tcase AMF3_STRING_MARKER:\n\t\treturn d.DecodeAmf3String(r, false)\n\tcase AMF3_XMLDOC_MARKER:\n\t\treturn d.DecodeAmf3Xml(r, false)\n\tcase AMF3_DATE_MARKER:\n\t\treturn d.DecodeAmf3Date(r, false)\n\tcase AMF3_ARRAY_MARKER:\n\t\treturn d.DecodeAmf3Array(r, false)\n\tcase AMF3_OBJECT_MARKER:\n\t\treturn d.DecodeAmf3Object(r, false)\n\tcase AMF3_XMLSTRING_MARKER:\n\t\treturn d.DecodeAmf3Xml(r, false)\n\tcase AMF3_BYTEARRAY_MARKER:\n\t\treturn d.DecodeAmf3ByteArray(r, false)\n\t}\n\n\treturn nil, fmt.Errorf(\"decode amf3: unsupported type %d\", marker)\n}\n\n// marker: 1 byte 0x00\n// no additional data\nfunc (d *Decoder) DecodeAmf3Undefined(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF3_UNDEFINED_MARKER)\n\treturn\n}\n\n// marker: 1 byte 0x01\n// no additional data\nfunc (d *Decoder) DecodeAmf3Null(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF3_NULL_MARKER)\n\treturn\n}\n\n// marker: 1 byte 0x02\n// no additional data\nfunc (d *Decoder) DecodeAmf3False(r io.Reader, decodeMarker bool) (result bool, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF3_FALSE_MARKER)\n\tresult = false\n\treturn\n}\n\n// marker: 1 byte 0x03\n// no additional data\nfunc (d *Decoder) DecodeAmf3True(r io.Reader, decodeMarker bool) (result bool, err error) {\n\terr = AssertMarker(r, decodeMarker, AMF3_TRUE_MARKER)\n\tresult = true\n\treturn\n}\n\n// marker: 1 byte 0x04\nfunc (d *Decoder) DecodeAmf3Integer(r io.Reader, decodeMarker bool) (result int32, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_INTEGER_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar u29 uint32\n\tu29, err = d.decodeU29(r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresult = int32(u29)\n\tif result > 0xfffffff {\n\t\tresult = int32(u29 - 0x20000000)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x05\nfunc (d *Decoder) DecodeAmf3Double(r io.Reader, decodeMarker bool) (result float64, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_DOUBLE_MARKER); err != nil {\n\t\treturn\n\t}\n\n\terr = binary.Read(r, binary.BigEndian, &result)\n\tif err != nil {\n\t\treturn float64(0), fmt.Errorf(\"amf3 decode: unable to read double: %s\", err)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x06\n// format:\n// - u29 reference int. if reference, no more data. if not reference,\n//   length value of bytes to read to complete string.\nfunc (d *Decoder) DecodeAmf3String(r io.Reader, decodeMarker bool) (result string, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_STRING_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar isRef bool\n\tvar refVal uint32\n\tisRef, refVal, err = d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"amf3 decode: unable to decode string reference and length: %s\", err)\n\t}\n\n\tif isRef {\n\t\tresult = d.stringRefs[refVal]\n\t\treturn\n\t}\n\n\tbuf := make([]byte, refVal)\n\t_, err = r.Read(buf)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"amf3 decode: unable to read string: %s\", err)\n\t}\n\n\tresult = string(buf)\n\tif result != \"\" {\n\t\td.stringRefs = append(d.stringRefs, result)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x08\n// format:\n// - u29 reference int, if reference, no more data\n// - timestamp double\nfunc (d *Decoder) DecodeAmf3Date(r io.Reader, decodeMarker bool) (result time.Time, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_DATE_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar isRef bool\n\tvar refVal uint32\n\tisRef, refVal, err = d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode date reference and length: %s\", err)\n\t}\n\n\tif isRef {\n\t\tres, ok := d.objectRefs[refVal].(time.Time)\n\t\tif ok != true {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to extract time from date object references\")\n\t\t}\n\n\t\treturn res, err\n\t}\n\n\tvar u64 float64\n\terr = binary.Read(r, binary.BigEndian, &u64)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to read double: %s\", err)\n\t}\n\n\tresult = time.Unix(int64(u64/1000), 0).UTC()\n\n\td.objectRefs = append(d.objectRefs, result)\n\n\treturn\n}\n\n// marker: 1 byte 0x09\n// format:\n// - u29 reference int. if reference, no more data.\n// - string representing associative array if present\n// - n values (length of u29)\nfunc (d *Decoder) DecodeAmf3Array(r io.Reader, decodeMarker bool) (result Array, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_ARRAY_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar isRef bool\n\tvar refVal uint32\n\tisRef, refVal, err = d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode array reference and length: %s\", err)\n\t}\n\n\tif isRef {\n\t\tobjRefId := refVal >> 1\n\n\t\tres, ok := d.objectRefs[objRefId].(Array)\n\t\tif ok != true {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to extract array from object references\")\n\t\t}\n\n\t\treturn res, err\n\t}\n\n\tvar key string\n\tkey, err = d.DecodeAmf3String(r, false)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to read key for array: %s\", err)\n\t}\n\n\tif key != \"\" {\n\t\treturn result, fmt.Errorf(\"amf3 decode: array key is not empty, can't handle associative array\")\n\t}\n\n\tfor i := uint32(0); i < refVal; i++ {\n\t\ttmp, err := d.DecodeAmf3(r)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: array element could not be decoded: %s\", err)\n\t\t}\n\t\tresult = append(result, tmp)\n\t}\n\n\td.objectRefs = append(d.objectRefs, result)\n\n\treturn\n}\n\n// marker: 1 byte 0x09\n// format: oh dear god\nfunc (d *Decoder) DecodeAmf3Object(r io.Reader, decodeMarker bool) (result interface{}, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_OBJECT_MARKER); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// decode the initial u29\n\tisRef, refVal, err := d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"amf3 decode: unable to decode object reference and length: %s\", err)\n\t}\n\n\t// if this is a object reference only, grab it and return it\n\tif isRef {\n\t\tobjRefId := refVal >> 1\n\n\t\treturn d.objectRefs[objRefId], nil\n\t}\n\n\t// each type has traits that are cached, if the peer sent a reference\n\t// then we'll need to look it up and use it.\n\tvar trait Trait\n\n\ttraitIsRef := (refVal & 0x01) == 0\n\n\tif traitIsRef {\n\t\ttraitRef := refVal >> 1\n\t\ttrait = d.traitRefs[traitRef]\n\n\t} else {\n\t\t// build a new trait from what's left of the given u29\n\t\ttrait = *NewTrait()\n\t\ttrait.Externalizable = (refVal & 0x02) != 0\n\t\ttrait.Dynamic = (refVal & 0x04) != 0\n\n\t\tvar cls string\n\t\tcls, err = d.DecodeAmf3String(r, false)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to read trait type for object: %s\", err)\n\t\t}\n\t\ttrait.Type = cls\n\n\t\t// traits have property keys, encoded as amf3 strings\n\t\tpropLength := refVal >> 3\n\t\tfor i := uint32(0); i < propLength; i++ {\n\t\t\ttmp, err := d.DecodeAmf3String(r, false)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to read trait property for object: %s\", err)\n\t\t\t}\n\t\t\ttrait.Properties = append(trait.Properties, tmp)\n\t\t}\n\n\t\td.traitRefs = append(d.traitRefs, trait)\n\t}\n\n\td.objectRefs = append(d.objectRefs, result)\n\n\t// objects can be externalizable, meaning that the system has no concrete understanding of\n\t// their properties or how they are encoded. in that case, we need to find and delegate behavior\n\t// to the right object.\n\tif trait.Externalizable {\n\t\tswitch trait.Type {\n\t\tcase \"DSA\": // AsyncMessageExt\n\t\t\tresult, err = d.decodeAsyncMessageExt(r)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode dsa: %s\", err)\n\t\t\t}\n\t\tcase \"DSK\": // AcknowledgeMessageExt\n\t\t\tresult, err = d.decodeAcknowledgeMessageExt(r)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode dsk: %s\", err)\n\t\t\t}\n\t\tcase \"flex.messaging.io.ArrayCollection\":\n\t\t\tresult, err = d.decodeArrayCollection(r)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode ac: %s\", err)\n\t\t\t}\n\n\t\t\t// store an extra reference to array collection container\n\t\t\td.objectRefs = append(d.objectRefs, result)\n\n\t\tdefault:\n\t\t\tfn, ok := d.externalHandlers[trait.Type]\n\t\t\tif ok {\n\t\t\t\tresult, err = fn(d, r)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to call external decoder for type %s: %s\", trait.Type, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode external type %s, no handler\", trait.Type)\n\t\t\t}\n\t\t}\n\n\t\treturn result, err\n\t}\n\n\tvar key string\n\tvar val interface{}\n\tvar obj Object\n\n\tobj = make(Object)\n\n\t// non-externalizable objects have property keys in traits, iterate through them\n\t// and add the read values to the object\n\tfor _, key = range trait.Properties {\n\t\tval, err = d.DecodeAmf3(r)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode object property: %s\", err)\n\t\t}\n\n\t\tobj[key] = val\n\t}\n\n\t// if an object is dynamic, it can have extra key/value data at the end. in this case,\n\t// read keys until we get an empty one.\n\tif trait.Dynamic {\n\t\tfor {\n\t\t\tkey, err = d.DecodeAmf3String(r, false)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode dynamic key: %s\", err)\n\t\t\t}\n\t\t\tif key == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tval, err = d.DecodeAmf3(r)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode dynamic value: %s\", err)\n\t\t\t}\n\n\t\t\tobj[key] = val\n\t\t}\n\t}\n\n\tresult = obj\n\n\treturn\n}\n\n// marker: 1 byte 0x07 or 0x0b\n// format:\n// - u29 reference int. if reference, no more data. if not reference,\n//   length value of bytes to read to complete string.\nfunc (d *Decoder) DecodeAmf3Xml(r io.Reader, decodeMarker bool) (result string, err error) {\n\tif decodeMarker {\n\t\tvar marker byte\n\t\tmarker, err = ReadMarker(r)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif (marker != AMF3_XMLDOC_MARKER) && (marker != AMF3_XMLSTRING_MARKER) {\n\t\t\treturn \"\", fmt.Errorf(\"decode assert marker failed: expected %v or %v, got %v\", AMF3_XMLDOC_MARKER, AMF3_XMLSTRING_MARKER, marker)\n\t\t}\n\t}\n\n\tvar isRef bool\n\tvar refVal uint32\n\tisRef, refVal, err = d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"amf3 decode: unable to decode xml reference and length: %s\", err)\n\t}\n\n\tif isRef {\n\t\tvar ok bool\n\t\tbuf := d.objectRefs[refVal]\n\t\tresult, ok = buf.(string)\n\t\tif ok != true {\n\t\t\treturn \"\", fmt.Errorf(\"amf3 decode: cannot coerce object reference into xml string\")\n\t\t}\n\n\t\treturn\n\t}\n\n\tbuf := make([]byte, refVal)\n\t_, err = r.Read(buf)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"amf3 decode: unable to read xml string: %s\", err)\n\t}\n\n\tresult = string(buf)\n\n\tif result != \"\" {\n\t\td.objectRefs = append(d.objectRefs, result)\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x0c\n// format:\n// - u29 reference int. if reference, no more data. if not reference,\n//   length value of bytes to read.\nfunc (d *Decoder) DecodeAmf3ByteArray(r io.Reader, decodeMarker bool) (result []byte, err error) {\n\tif err = AssertMarker(r, decodeMarker, AMF3_BYTEARRAY_MARKER); err != nil {\n\t\treturn\n\t}\n\n\tvar isRef bool\n\tvar refVal uint32\n\tisRef, refVal, err = d.decodeReferenceInt(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to decode byte array reference and length: %s\", err)\n\t}\n\n\tif isRef {\n\t\tvar ok bool\n\t\tresult, ok = d.objectRefs[refVal].([]byte)\n\t\tif ok != true {\n\t\t\treturn result, fmt.Errorf(\"amf3 decode: unable to convert object ref to bytes\")\n\t\t}\n\n\t\treturn\n\t}\n\n\tresult = make([]byte, refVal)\n\t_, err = r.Read(result)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"amf3 decode: unable to read bytearray: %s\", err)\n\t}\n\n\td.objectRefs = append(d.objectRefs, result)\n\n\treturn\n}\n\nfunc (d *Decoder) decodeU29(r io.Reader) (result uint32, err error) {\n\tvar b byte\n\n\tfor i := 0; i < 3; i++ {\n\t\tb, err = ReadByte(r)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tresult = (result << 7) + uint32(b&0x7F)\n\t\tif (b & 0x80) == 0 {\n\t\t\treturn\n\t\t}\n\t}\n\n\tb, err = ReadByte(r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresult = ((result << 8) + uint32(b))\n\n\treturn\n}\n\nfunc (d *Decoder) decodeReferenceInt(r io.Reader) (isRef bool, refVal uint32, err error) {\n\tu29, err := d.decodeU29(r)\n\tif err != nil {\n\t\treturn false, 0, fmt.Errorf(\"amf3 decode: unable to decode reference int: %s\", err)\n\t}\n\n\tisRef = u29&0x01 == 0\n\trefVal = u29 >> 1\n\n\treturn\n}\n"
  },
  {
    "path": "protocol/amf/decoder_amf3_external.go",
    "content": "package amf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n)\n\n// Abstract external boilerplate\nfunc (d *Decoder) decodeAbstractMessage(r io.Reader) (result Object, err error) {\n\tresult = make(Object)\n\n\tif err = d.decodeExternal(r, &result,\n\t\t[]string{\"body\", \"clientId\", \"destination\", \"headers\", \"messageId\", \"timeStamp\", \"timeToLive\"},\n\t\t[]string{\"clientIdBytes\", \"messageIdBytes\"}); err != nil {\n\t\treturn result, fmt.Errorf(\"unable to decode abstract external: %s\", err)\n\t}\n\n\treturn\n}\n\n// DSA\nfunc (d *Decoder) decodeAsyncMessageExt(r io.Reader) (result Object, err error) {\n\treturn d.decodeAsyncMessage(r)\n}\nfunc (d *Decoder) decodeAsyncMessage(r io.Reader) (result Object, err error) {\n\tresult, err = d.decodeAbstractMessage(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"unable to decode abstract for async: %s\", err)\n\t}\n\n\tif err = d.decodeExternal(r, &result, []string{\"correlationId\", \"correlationIdBytes\"}); err != nil {\n\t\treturn result, fmt.Errorf(\"unable to decode async external: %s\", err)\n\t}\n\n\treturn\n}\n\n// DSK\nfunc (d *Decoder) decodeAcknowledgeMessageExt(r io.Reader) (result Object, err error) {\n\treturn d.decodeAcknowledgeMessage(r)\n}\nfunc (d *Decoder) decodeAcknowledgeMessage(r io.Reader) (result Object, err error) {\n\tresult, err = d.decodeAsyncMessage(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"unable to decode async for ack: %s\", err)\n\t}\n\n\tif err = d.decodeExternal(r, &result); err != nil {\n\t\treturn result, fmt.Errorf(\"unable to decode ack external: %s\", err)\n\t}\n\n\treturn\n}\n\n// flex.messaging.io.ArrayCollection\nfunc (d *Decoder) decodeArrayCollection(r io.Reader) (interface{}, error) {\n\tresult, err := d.DecodeAmf3(r)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"cannot decode child of array collection: %s\", err)\n\t}\n\n\treturn result, nil\n}\n\nfunc (d *Decoder) decodeExternal(r io.Reader, obj *Object, fieldSets ...[]string) (err error) {\n\tvar flagSet []uint8\n\tvar reservedPosition uint8\n\tvar fieldNames []string\n\n\tflagSet, err = readFlags(r)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to read flags: %s\", err)\n\t}\n\n\tfor i, flags := range flagSet {\n\t\tif i < len(fieldSets) {\n\t\t\tfieldNames = fieldSets[i]\n\t\t} else {\n\t\t\tfieldNames = []string{}\n\t\t}\n\n\t\treservedPosition = uint8(len(fieldNames))\n\n\t\tfor p, field := range fieldNames {\n\t\t\tflagBit := uint8(math.Exp2(float64(p)))\n\t\t\tif (flags & flagBit) != 0 {\n\t\t\t\ttmp, err := d.DecodeAmf3(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unable to decode external field %s %d %d (%#v): %s\", field, i, p, flagSet, err)\n\t\t\t\t}\n\t\t\t\t(*obj)[field] = tmp\n\t\t\t}\n\t\t}\n\n\t\tif (flags >> reservedPosition) != 0 {\n\t\t\tfor j := reservedPosition; j < 6; j++ {\n\t\t\t\tif ((flags >> j) & 0x01) != 0 {\n\t\t\t\t\tfield := fmt.Sprintf(\"extra_%d_%d\", i, j)\n\t\t\t\t\ttmp, err := d.DecodeAmf3(r)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"unable to decode post-external field %d %d (%#v): %s\", i, j, flagSet, err)\n\t\t\t\t\t}\n\t\t\t\t\t(*obj)[field] = tmp\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc readFlags(r io.Reader) (result []uint8, err error) {\n\tfor {\n\t\tflag, err := ReadByte(r)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"unable to read flags: %s\", err)\n\t\t}\n\n\t\tresult = append(result, flag)\n\t\tif (flag & 0x80) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "protocol/amf/decoder_amf3_test.go",
    "content": "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 = []u29TestCase{\n\t{1, []byte{0x01}},\n\t{2, []byte{0x02}},\n\t{127, []byte{0x7F}},\n\t{128, []byte{0x81, 0x00}},\n\t{255, []byte{0x81, 0x7F}},\n\t{256, []byte{0x82, 0x00}},\n\t{0x3FFF, []byte{0xFF, 0x7F}},\n\t{0x4000, []byte{0x81, 0x80, 0x00}},\n\t{0x7FFF, []byte{0x81, 0xFF, 0x7F}},\n\t{0x8000, []byte{0x82, 0x80, 0x00}},\n\t{0x1FFFFF, []byte{0xFF, 0xFF, 0x7F}},\n\t{0x200000, []byte{0x80, 0xC0, 0x80, 0x00}},\n\t{0x3FFFFF, []byte{0x80, 0xFF, 0xFF, 0xFF}},\n\t{0x400000, []byte{0x81, 0x80, 0x80, 0x00}},\n\t{0x0FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF}},\n}\n\nfunc TestDecodeAmf3Undefined(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x00})\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n}\n\nfunc TestDecodeAmf3Null(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x01})\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"expect nil got %v\", got)\n\t}\n}\n\nfunc TestDecodeAmf3False(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x02})\n\texpect := false\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf3True(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x03})\n\texpect := true\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeU29(t *testing.T) {\n\tdec := new(Decoder)\n\n\tfor _, tc := range u29TestCases {\n\t\tbuf := bytes.NewBuffer(tc.expect)\n\t\tn, err := dec.decodeU29(buf)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"DecodeAmf3Integer error: %s\", err)\n\t\t}\n\t\tif n != tc.value {\n\t\t\tt.Errorf(\"DecodeAmf3Integer expect n %x got %x\", tc.value, n)\n\t\t}\n\t}\n}\n\nfunc TestDecodeAmf3Integer(t *testing.T) {\n\tdec := new(Decoder)\n\n\tbuf := bytes.NewReader([]byte{0x04, 0xFF, 0xFF, 0x7F})\n\texpect := int32(2097151)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\tbuf.Seek(0, 0)\n\tgot, err = dec.DecodeAmf3Integer(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n\n\tbuf.Seek(1, 0)\n\tgot, err = dec.DecodeAmf3Integer(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf3Double(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33})\n\texpect := float64(1.2)\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf3String(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x06, 0x07, 'f', 'o', 'o'})\n\texpect := \"foo\"\n\n\tdec := new(Decoder)\n\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif expect != got {\n\t\tt.Errorf(\"expect %v got %v\", expect, got)\n\t}\n}\n\nfunc TestDecodeAmf3Array(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{0x09, 0x13, 0x01,\n\t\t0x06, 0x03, '1',\n\t\t0x06, 0x03, '2',\n\t\t0x06, 0x03, '3',\n\t\t0x06, 0x03, '4',\n\t\t0x06, 0x03, '5',\n\t\t0x06, 0x03, '6',\n\t\t0x06, 0x03, '7',\n\t\t0x06, 0x03, '8',\n\t\t0x06, 0x03, '9',\n\t})\n\n\tdec := new(Decoder)\n\texpect := []string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"}\n\tgot, err := dec.DecodeAmf3Array(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"err: %s\", err)\n\t}\n\n\tfor i, v := range expect {\n\t\tif got[i] != v {\n\t\t\tt.Errorf(\"expected array element %d to be %v, got %v\", i, v, got[i])\n\t\t}\n\t}\n}\n\nfunc TestDecodeAmf3Object(t *testing.T) {\n\tbuf := bytes.NewReader([]byte{\n\t\t0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',\n\t\t'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',\n\t\t's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',\n\t\t'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',\n\t})\n\n\tdec := new(Decoder)\n\tgot, err := dec.DecodeAmf3(buf)\n\tif err != nil {\n\t\tt.Errorf(\"err: %s\", err)\n\t}\n\n\tto, ok := got.(Object)\n\tif ok != true {\n\t\tt.Error(\"unable to cast object as typed object\")\n\t}\n\n\tif to[\"foo\"] != \"bar\" {\n\t\tt.Errorf(\"expected foo to be bar, got: %+v\", to[\"foo\"])\n\t}\n\n\tif to[\"baz\"] != nil {\n\t\tt.Errorf(\"expected baz to be nil, got: %+v\", to[\"baz\"])\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/encoder_amf0.go",
    "content": "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) EncodeAmf0(w io.Writer, val interface{}) (int, error) {\n\tif val == nil {\n\t\treturn e.EncodeAmf0Null(w, true)\n\t}\n\n\tv := reflect.ValueOf(val)\n\tif !v.IsValid() {\n\t\treturn e.EncodeAmf0Null(w, true)\n\t}\n\n\tswitch v.Kind() {\n\tcase reflect.String:\n\t\tstr := v.String()\n\t\tif len(str) <= AMF0_STRING_MAX {\n\t\t\treturn e.EncodeAmf0String(w, str, true)\n\t\t} else {\n\t\t\treturn e.EncodeAmf0LongString(w, str, true)\n\t\t}\n\tcase reflect.Bool:\n\t\treturn e.EncodeAmf0Boolean(w, v.Bool(), true)\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\treturn e.EncodeAmf0Number(w, float64(v.Int()), true)\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:\n\t\treturn e.EncodeAmf0Number(w, float64(v.Uint()), true)\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn e.EncodeAmf0Number(w, float64(v.Float()), true)\n\tcase reflect.Array, reflect.Slice:\n\t\tlength := v.Len()\n\t\tarr := make(Array, length)\n\t\tfor i := 0; i < length; i++ {\n\t\t\tarr[i] = v.Index(int(i)).Interface()\n\t\t}\n\t\treturn e.EncodeAmf0StrictArray(w, arr, true)\n\tcase reflect.Map:\n\t\tobj, ok := val.(Object)\n\t\tif ok != true {\n\t\t\treturn 0, fmt.Errorf(\"encode amf0: unable to create object from map\")\n\t\t}\n\t\treturn e.EncodeAmf0Object(w, obj, true)\n\t}\n\n\tif _, ok := val.(TypedObject); ok {\n\t\treturn 0, fmt.Errorf(\"encode amf0: unsupported type typed object\")\n\t}\n\n\treturn 0, fmt.Errorf(\"encode amf0: unsupported type %s\", v.Type())\n}\n\n// marker: 1 byte 0x00\n// format: 8 byte big endian float64\nfunc (e *Encoder) EncodeAmf0Number(w io.Writer, val float64, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_NUMBER_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\terr = binary.Write(w, binary.BigEndian, &val)\n\tif err != nil {\n\t\treturn\n\t}\n\tn += 8\n\n\treturn\n}\n\n// marker: 1 byte 0x01\n// format: 1 byte, 0x00 = false, 0x01 = true\nfunc (e *Encoder) EncodeAmf0Boolean(w io.Writer, val bool, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_BOOLEAN_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tbuf := make([]byte, 1)\n\tif val {\n\t\tbuf[0] = AMF0_BOOLEAN_TRUE\n\t} else {\n\t\tbuf[0] = AMF0_BOOLEAN_FALSE\n\t}\n\n\tm, err = w.Write(buf)\n\tif err != nil {\n\t\treturn\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x02\n// format:\n// - 2 byte big endian uint16 header to determine size\n// - n (size) byte utf8 string\nfunc (e *Encoder) EncodeAmf0String(w io.Writer, val string, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_STRING_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tlength := uint16(len(val))\n\terr = binary.Write(w, binary.BigEndian, length)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode string length: %s\", err)\n\t}\n\tn += 2\n\n\tm, err = w.Write([]byte(val))\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode string value: %s\", err)\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x03\n// format:\n// - loop encoded string followed by encoded value\n// - terminated with empty string followed by 1 byte 0x09\nfunc (e *Encoder) EncodeAmf0Object(w io.Writer, val Object, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_OBJECT_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tfor k, v := range val {\n\t\tm, err = e.EncodeAmf0String(w, k, false)\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode object key: %s\", err)\n\t\t}\n\t\tn += m\n\n\t\tm, err = e.EncodeAmf0(w, v)\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode object value: %s\", err)\n\t\t}\n\t\tn += m\n\t}\n\n\tm, err = e.EncodeAmf0String(w, \"\", false)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode object empty string: %s\", err)\n\t}\n\tn += m\n\n\terr = WriteMarker(w, AMF0_OBJECT_END_MARKER)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to object end marker: %s\", err)\n\t}\n\tn += 1\n\n\treturn\n}\n\n// marker: 1 byte 0x05\n// no additional data\nfunc (e *Encoder) EncodeAmf0Null(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_NULL_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x06\n// no additional data\nfunc (e *Encoder) EncodeAmf0Undefined(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_UNDEFINED_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x08\n// format:\n// - 4 byte big endian uint32 with length of associative array\n// - normal object format:\n//   - loop encoded string followed by encoded value\n//   - terminated with empty string followed by 1 byte 0x09\nfunc (e *Encoder) EncodeAmf0EcmaArray(w io.Writer, val Object, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_ECMA_ARRAY_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tlength := uint32(len(val))\n\terr = binary.Write(w, binary.BigEndian, length)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode ecma array length: %s\", err)\n\t}\n\tn += 4\n\n\tm, err = e.EncodeAmf0Object(w, val, false)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode ecma array object: %s\", err)\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x0a\n// format:\n// - 4 byte big endian uint32 to determine length of associative array\n// - n (length) encoded values\nfunc (e *Encoder) EncodeAmf0StrictArray(w io.Writer, val Array, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_STRICT_ARRAY_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tlength := uint32(len(val))\n\terr = binary.Write(w, binary.BigEndian, length)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode strict array length: %s\", err)\n\t}\n\tn += 4\n\n\tfor _, v := range val {\n\t\tm, err = e.EncodeAmf0(w, v)\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode strict array element: %s\", err)\n\t\t}\n\t\tn += m\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x0c\n// format:\n// - 4 byte big endian uint32 header to determine size\n// - n (size) byte utf8 string\nfunc (e *Encoder) EncodeAmf0LongString(w io.Writer, val string, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_LONG_STRING_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tlength := uint32(len(val))\n\terr = binary.Write(w, binary.BigEndian, length)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode long string length: %s\", err)\n\t}\n\tn += 4\n\n\tm, err = w.Write([]byte(val))\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf0: unable to encode long string value: %s\", err)\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x0d\n// no additional data\nfunc (e *Encoder) EncodeAmf0Unsupported(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF0_UNSUPPORTED_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x11\nfunc (e *Encoder) EncodeAmf0Amf3Marker(w io.Writer) error {\n\treturn WriteMarker(w, AMF0_ACMPLUS_OBJECT_MARKER)\n}\n"
  },
  {
    "path": "protocol/amf/encoder_amf0_test.go",
    "content": "package amf\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf0Number(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x00, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}\n\n\tenc := new(Encoder)\n\n\tn, err := enc.EncodeAmf0(buf, float64(1.2))\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 9 {\n\t\tt.Errorf(\"expected to write 9 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0BooleanTrue(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x01, 0x01}\n\n\tenc := new(Encoder)\n\n\tn, err := enc.EncodeAmf0(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 2 {\n\t\tt.Errorf(\"expected to write 2 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0BooleanFalse(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x01, 0x00}\n\n\tenc := new(Encoder)\n\n\tn, err := enc.EncodeAmf0(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 2 {\n\t\tt.Errorf(\"expected to write 2 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0String(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f}\n\n\tenc := new(Encoder)\n\n\tn, err := enc.EncodeAmf0(buf, \"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 6 {\n\t\tt.Errorf(\"expected to write 6 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0Object(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}\n\n\tenc := new(Encoder)\n\n\tobj := make(Object)\n\tobj[\"foo\"] = \"bar\"\n\n\tn, err := enc.EncodeAmf0(buf, obj)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 15 {\n\t\tt.Errorf(\"expected to write 15 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0EcmaArray(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x09}\n\n\tenc := new(Encoder)\n\n\tobj := make(Object)\n\tobj[\"foo\"] = \"bar\"\n\n\t_, err := enc.EncodeAmf0EcmaArray(buf, obj, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0StrictArray(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x05}\n\n\tenc := new(Encoder)\n\n\tarr := make(Array, 3)\n\tarr[0] = float64(5)\n\tarr[1] = \"foo\"\n\tarr[2] = nil\n\n\t_, err := enc.EncodeAmf0StrictArray(buf, arr, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0Null(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x05}\n\n\tenc := new(Encoder)\n\n\tn, err := enc.EncodeAmf0(buf, nil)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 1 {\n\t\tt.Errorf(\"expected to write 1 byte, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf0LongString(t *testing.T) {\n\tbuf := new(bytes.Buffer)\n\n\ttestBytes := []byte(\"12345678\")\n\n\ttbuf := new(bytes.Buffer)\n\tfor i := 0; i < 65536; i++ {\n\t\ttbuf.Write(testBytes)\n\t}\n\n\tenc := new(Encoder)\n\n\t_, err := enc.EncodeAmf0(buf, string(tbuf.Bytes()))\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tmbuf := make([]byte, 1)\n\t_, err = buf.Read(mbuf)\n\tif err != nil {\n\t\tt.Errorf(\"error reading header\")\n\t}\n\n\tif mbuf[0] != 0x0c {\n\t\tt.Errorf(\"marker mismatch\")\n\t}\n\n\tvar length uint32\n\terr = binary.Read(buf, binary.BigEndian, &length)\n\tif err != nil {\n\t\tt.Errorf(\"error reading buffer\")\n\t}\n\tif length != (65536 * 8) {\n\t\tt.Errorf(\"expected length to be %d, got %d\", (65536 * 8), length)\n\t}\n\n\ttmpBuf := make([]byte, 8)\n\tcounter := 0\n\tfor buf.Len() > 0 {\n\t\tn, err := buf.Read(tmpBuf)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"test long string result check, read data(%d) error: %s, n: %d\", counter, err, n)\n\t\t}\n\t\tif n != 8 {\n\t\t\tt.Fatalf(\"test long string result check, read data(%d) n: %d\", counter, n)\n\t\t}\n\t\tif !bytes.Equal(testBytes, tmpBuf) {\n\t\t\tt.Fatalf(\"test long string result check, read data % x\", tmpBuf)\n\t\t}\n\n\t\tcounter++\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/encoder_amf3.go",
    "content": "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 *Encoder) EncodeAmf3(w io.Writer, val interface{}) (int, error) {\n\tif val == nil {\n\t\treturn e.EncodeAmf3Null(w, true)\n\t}\n\n\tv := reflect.ValueOf(val)\n\tif !v.IsValid() {\n\t\treturn e.EncodeAmf3Null(w, true)\n\t}\n\n\tswitch v.Kind() {\n\tcase reflect.String:\n\t\treturn e.EncodeAmf3String(w, v.String(), true)\n\tcase reflect.Bool:\n\t\tif v.Bool() {\n\t\t\treturn e.EncodeAmf3True(w, true)\n\t\t} else {\n\t\t\treturn e.EncodeAmf3False(w, true)\n\t\t}\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:\n\t\tn := v.Int()\n\t\tif n >= 0 && n <= AMF3_INTEGER_MAX {\n\t\t\treturn e.EncodeAmf3Integer(w, uint32(n), true)\n\t\t} else {\n\t\t\treturn e.EncodeAmf3Double(w, float64(n), true)\n\t\t}\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:\n\t\tn := v.Uint()\n\t\tif n <= AMF3_INTEGER_MAX {\n\t\t\treturn e.EncodeAmf3Integer(w, uint32(n), true)\n\t\t} else {\n\t\t\treturn e.EncodeAmf3Double(w, float64(n), true)\n\t\t}\n\tcase reflect.Int64:\n\t\treturn e.EncodeAmf3Double(w, float64(v.Int()), true)\n\tcase reflect.Uint64:\n\t\treturn e.EncodeAmf3Double(w, float64(v.Uint()), true)\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn e.EncodeAmf3Double(w, float64(v.Float()), true)\n\tcase reflect.Array, reflect.Slice:\n\t\tlength := v.Len()\n\t\tarr := make(Array, length)\n\t\tfor i := 0; i < length; i++ {\n\t\t\tarr[i] = v.Index(int(i)).Interface()\n\t\t}\n\t\treturn e.EncodeAmf3Array(w, arr, true)\n\tcase reflect.Map:\n\t\tobj, ok := val.(Object)\n\t\tif ok != true {\n\t\t\treturn 0, fmt.Errorf(\"encode amf3: unable to create object from map\")\n\t\t}\n\n\t\tto := *new(TypedObject)\n\t\tto.Object = obj\n\n\t\treturn e.EncodeAmf3Object(w, to, true)\n\t}\n\n\tif tm, ok := val.(time.Time); ok {\n\t\treturn e.EncodeAmf3Date(w, tm, true)\n\t}\n\n\tif to, ok := val.(TypedObject); ok {\n\t\treturn e.EncodeAmf3Object(w, to, true)\n\t}\n\n\treturn 0, fmt.Errorf(\"encode amf3: unsupported type %s\", v.Type())\n}\n\n// marker: 1 byte 0x00\n// no additional data\nfunc (e *Encoder) EncodeAmf3Undefined(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_UNDEFINED_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x01\n// no additional data\nfunc (e *Encoder) EncodeAmf3Null(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_NULL_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x02\n// no additional data\nfunc (e *Encoder) EncodeAmf3False(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_FALSE_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x03\n// no additional data\nfunc (e *Encoder) EncodeAmf3True(w io.Writer, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_TRUE_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x04\nfunc (e *Encoder) EncodeAmf3Integer(w io.Writer, val uint32, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_INTEGER_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tm, err = e.encodeAmf3Uint29(w, val)\n\tif err != nil {\n\t\treturn\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x05\nfunc (e *Encoder) EncodeAmf3Double(w io.Writer, val float64, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_DOUBLE_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\terr = binary.Write(w, binary.BigEndian, &val)\n\tif err != nil {\n\t\treturn\n\t}\n\tn += 8\n\n\treturn\n}\n\n// marker: 1 byte 0x06\n// format:\n// - u29 reference int. if reference, no more data. if not reference,\n//   length value of bytes to read to complete string.\nfunc (e *Encoder) EncodeAmf3String(w io.Writer, val string, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_STRING_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\n\tm, err = e.encodeAmf3Utf8(w, val)\n\tif err != nil {\n\t\treturn\n\t}\n\tn += m\n\n\treturn\n}\n\n// marker: 1 byte 0x08\n// format:\n// - u29 reference int, if reference, no more data\n// - timestamp double\nfunc (e *Encoder) EncodeAmf3Date(w io.Writer, val time.Time, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_DATE_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tif err = WriteMarker(w, 0x01); err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode u29 for array: %s\", err)\n\t}\n\tn += 1\n\n\tu64 := float64(val.Unix()) * 1000.0\n\terr = binary.Write(w, binary.BigEndian, &u64)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: unable to write date double: %s\", err)\n\t}\n\tn += 8\n\n\treturn\n}\n\n// marker: 1 byte 0x09\n// format:\n// - u29 reference int. if reference, no more data.\n// - string representing associative array if present\n// - n values (length of u29)\nfunc (e *Encoder) EncodeAmf3Array(w io.Writer, val Array, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_ARRAY_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\tlength := uint32(len(val))\n\tu29 := uint32(length<<1) | 0x01\n\n\tm, err = e.encodeAmf3Uint29(w, u29)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode u29 for array: %s\", err)\n\t}\n\tn += m\n\n\tm, err = e.encodeAmf3Utf8(w, \"\")\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode empty string for array: %s\", err)\n\t}\n\tn += m\n\n\tfor _, v := range val {\n\t\tm, err := e.EncodeAmf3(w, v)\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode array element: %s\", err)\n\t\t}\n\t\tn += m\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x0a\n// format: ugh\nfunc (e *Encoder) EncodeAmf3Object(w io.Writer, val TypedObject, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_OBJECT_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tm := 0\n\n\ttrait := *NewTrait()\n\ttrait.Type = val.Type\n\ttrait.Dynamic = false\n\ttrait.Externalizable = false\n\n\tfor k, _ := range val.Object {\n\t\ttrait.Properties = append(trait.Properties, k)\n\t}\n\n\tsort.Strings(trait.Properties)\n\n\tvar u29 uint32 = 0x03\n\tif trait.Dynamic {\n\t\tu29 |= 0x02 << 2\n\t}\n\n\tif trait.Externalizable {\n\t\tu29 |= 0x01 << 2\n\t}\n\n\tu29 |= uint32(len(trait.Properties)) << 4\n\n\tm, err = e.encodeAmf3Uint29(w, u29)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode trait header for object: %s\", err)\n\t}\n\tn += m\n\n\tm, err = e.encodeAmf3Utf8(w, trait.Type)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode trait type for object: %s\", err)\n\t}\n\tn += m\n\n\tfor _, prop := range trait.Properties {\n\t\tm, err = e.encodeAmf3Utf8(w, prop)\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode trait property for object: %s\", err)\n\t\t}\n\t\tn += m\n\t}\n\n\tif trait.Externalizable {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode externalizable object\")\n\t}\n\n\tfor _, prop := range trait.Properties {\n\t\tm, err = e.EncodeAmf3(w, val.Object[prop])\n\t\tif err != nil {\n\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode sealed object value: %s\", err)\n\t\t}\n\t\tn += m\n\t}\n\n\tif trait.Dynamic {\n\t\tfor k, v := range val.Object {\n\t\t\tvar foundProp bool = false\n\t\t\tfor _, prop := range trait.Properties {\n\t\t\t\tif prop == k {\n\t\t\t\t\tfoundProp = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif foundProp != true {\n\t\t\t\tm, err = e.encodeAmf3Utf8(w, k)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode dynamic object property key: %s\", err)\n\t\t\t\t}\n\t\t\t\tn += m\n\n\t\t\t\tm, err = e.EncodeAmf3(w, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode dynamic object value: %s\", err)\n\t\t\t\t}\n\t\t\t\tn += m\n\t\t\t}\n\n\t\t\tm, err = e.encodeAmf3Utf8(w, \"\")\n\t\t\tif err != nil {\n\t\t\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode dynamic object ending marker string: %s\", err)\n\t\t\t}\n\t\t\tn += m\n\t\t}\n\t}\n\n\treturn\n}\n\n// marker: 1 byte 0x0c\n// format:\n// - u29 reference int. if reference, no more data. if not reference,\n//   length value of bytes to read .\nfunc (e *Encoder) EncodeAmf3ByteArray(w io.Writer, val []byte, encodeMarker bool) (n int, err error) {\n\tif encodeMarker {\n\t\tif err = WriteMarker(w, AMF3_BYTEARRAY_MARKER); err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += 1\n\t}\n\n\tvar m int\n\n\tlength := uint32(len(val))\n\tu29 := (length << 1) | 1\n\n\tm, err = e.encodeAmf3Uint29(w, u29)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode u29 for bytearray: %s\", err)\n\t}\n\tn += m\n\n\tm, err = w.Write(val)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf3: unable to encode bytearray value: %s\", err)\n\t}\n\tn += m\n\n\treturn\n}\n\nfunc (e *Encoder) encodeAmf3Utf8(w io.Writer, val string) (n int, err error) {\n\tlength := uint32(len(val))\n\tu29 := uint32(length<<1) | 0x01\n\n\tvar m int\n\tm, err = e.encodeAmf3Uint29(w, u29)\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode u29 for string: %s\", err)\n\t}\n\tn += m\n\n\tm, err = w.Write([]byte(val))\n\tif err != nil {\n\t\treturn n, fmt.Errorf(\"encode amf3: unable to encode string value: %s\", err)\n\t}\n\tn += m\n\n\treturn\n}\n\nfunc (e *Encoder) encodeAmf3Uint29(w io.Writer, val uint32) (n int, err error) {\n\tif val <= 0x0000007F {\n\t\terr = WriteByte(w, byte(val))\n\t\tif err == nil {\n\t\t\tn += 1\n\t\t}\n\t} else if val <= 0x00003FFF {\n\t\tn, err = w.Write([]byte{byte(val>>7 | 0x80), byte(val & 0x7F)})\n\t} else if val <= 0x001FFFFF {\n\t\tn, err = w.Write([]byte{byte(val>>14 | 0x80), byte(val>>7&0x7F | 0x80), byte(val & 0x7F)})\n\t} else if val <= 0x1FFFFFFF {\n\t\tn, err = w.Write([]byte{byte(val>>22 | 0x80), byte(val>>15&0x7F | 0x80), byte(val>>8&0x7F | 0x80), byte(val)})\n\t} else {\n\t\treturn n, fmt.Errorf(\"amf3 encode: cannot encode u29 with value %d (out of range)\", val)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "protocol/amf/encoder_amf3_test.go",
    "content": "package amf\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestEncodeAmf3EmptyString(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x01}\n\n\t_, err := enc.EncodeAmf3String(buf, \"\", false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Undefined(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x00}\n\n\t_, err := enc.EncodeAmf3Undefined(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Null(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x01}\n\n\t_, err := enc.EncodeAmf3(buf, nil)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3False(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x02}\n\n\t_, err := enc.EncodeAmf3(buf, false)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3True(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x03}\n\n\t_, err := enc.EncodeAmf3(buf, true)\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Integer(t *testing.T) {\n\tenc := new(Encoder)\n\n\tfor _, tc := range u29TestCases {\n\t\tbuf := new(bytes.Buffer)\n\t\t_, err := enc.EncodeAmf3Integer(buf, tc.value, false)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"EncodeAmf3Integer error: %s\", err)\n\t\t}\n\t\tgot := buf.Bytes()\n\t\tif !bytes.Equal(tc.expect, got) {\n\t\t\tt.Errorf(\"EncodeAmf3Integer expect n %x got %x\", tc.value, got)\n\t\t}\n\t}\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x04, 0x80, 0xFF, 0xFF, 0xFF}\n\n\tn, err := enc.EncodeAmf3(buf, uint32(4194303))\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\tif n != 5 {\n\t\tt.Errorf(\"expected to write 5 bytes, actual %d\", n)\n\t}\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Double(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x05, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33}\n\n\t_, err := enc.EncodeAmf3(buf, float64(1.2))\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3String(t *testing.T) {\n\tenc := new(Encoder)\n\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x06, 0x07, 'f', 'o', 'o'}\n\n\t_, err := enc.EncodeAmf3(buf, \"foo\")\n\tif err != nil {\n\t\tt.Errorf(\"%s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Array(t *testing.T) {\n\tenc := new(Encoder)\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{0x09, 0x13, 0x01,\n\t\t0x06, 0x03, '1',\n\t\t0x06, 0x03, '2',\n\t\t0x06, 0x03, '3',\n\t\t0x06, 0x03, '4',\n\t\t0x06, 0x03, '5',\n\t\t0x06, 0x03, '6',\n\t\t0x06, 0x03, '7',\n\t\t0x06, 0x03, '8',\n\t\t0x06, 0x03, '9',\n\t}\n\n\tarr := []string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"}\n\t_, err := enc.EncodeAmf3(buf, arr)\n\tif err != nil {\n\t\tt.Errorf(\"err: %s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer: %+v, got: %+v\", expect, buf.Bytes())\n\t}\n}\n\nfunc TestEncodeAmf3Object(t *testing.T) {\n\tenc := new(Encoder)\n\tbuf := new(bytes.Buffer)\n\texpect := []byte{\n\t\t0x0a, 0x23, 0x1f, 'o', 'r', 'g', '.', 'a',\n\t\t'm', 'f', '.', 'A', 'S', 'C', 'l', 'a',\n\t\t's', 's', 0x07, 'b', 'a', 'z', 0x07, 'f',\n\t\t'o', 'o', 0x01, 0x06, 0x07, 'b', 'a', 'r',\n\t}\n\n\tto := *NewTypedObject()\n\tto.Type = \"org.amf.ASClass\"\n\tto.Object[\"foo\"] = \"bar\"\n\tto.Object[\"baz\"] = nil\n\n\t_, err := enc.EncodeAmf3(buf, to)\n\tif err != nil {\n\t\tt.Errorf(\"err: %s\", err)\n\t}\n\n\tif bytes.Compare(buf.Bytes(), expect) != 0 {\n\t\tt.Errorf(\"expected buffer:\\n%#v\\ngot:\\n%#v\", expect, buf.Bytes())\n\t}\n}\n"
  },
  {
    "path": "protocol/amf/metadata.go",
    "content": "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\tSetDataFrame string = \"@setDataFrame\"\n\tOnMetaData   string = \"onMetaData\"\n)\n\nvar setFrameFrame []byte\n\nfunc init() {\n\tb := bytes.NewBuffer(nil)\n\tencoder := &Encoder{}\n\tif _, err := encoder.Encode(b, SetDataFrame, AMF0); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tsetFrameFrame = b.Bytes()\n}\n\nfunc MetaDataReform(p []byte, flag uint8) ([]byte, error) {\n\tr := bytes.NewReader(p)\n\tdecoder := &Decoder{}\n\tswitch flag {\n\tcase ADD:\n\t\tv, err := decoder.Decode(r, AMF0)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\t\tvv := v.(string)\n\t\t\tif vv != SetDataFrame {\n\t\t\t\ttmplen := len(setFrameFrame)\n\t\t\t\tb := make([]byte, tmplen+len(p))\n\t\t\t\tcopy(b, setFrameFrame)\n\t\t\t\tcopy(b[tmplen:], p)\n\t\t\t\tp = b\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"setFrameFrame error\")\n\t\t}\n\tcase DEL:\n\t\tv, err := decoder.Decode(r, AMF0)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\t\tvv := v.(string)\n\t\t\tif vv == SetDataFrame {\n\t\t\t\tp = p[len(setFrameFrame):]\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"metadata error\")\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid flag:%d\", flag)\n\t}\n\treturn p, nil\n}\n"
  },
  {
    "path": "protocol/amf/util.go",
    "content": "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(\"Dumping %s (%d bytes):\\n\", label, size)\n\tfor i := 0; i < size; i++ {\n\t\tfmt.Printf(\"0x%02x \", buf[i])\n\t}\n\tfmt.Printf(\"\\n\")\n}\n\nfunc Dump(label string, val interface{}) error {\n\tjson, err := json.MarshalIndent(val, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error dumping %s: %s\", label, err)\n\t}\n\n\tfmt.Printf(\"Dumping %s:\\n%s\\n\", label, json)\n\treturn nil\n}\n\nfunc WriteByte(w io.Writer, b byte) (err error) {\n\tbytes := make([]byte, 1)\n\tbytes[0] = b\n\n\t_, err = WriteBytes(w, bytes)\n\n\treturn\n}\n\nfunc WriteBytes(w io.Writer, bytes []byte) (int, error) {\n\treturn w.Write(bytes)\n}\n\nfunc ReadByte(r io.Reader) (byte, error) {\n\tbytes, err := ReadBytes(r, 1)\n\tif err != nil {\n\t\treturn 0x00, err\n\t}\n\n\treturn bytes[0], nil\n}\n\nfunc ReadBytes(r io.Reader, n int) ([]byte, error) {\n\tbytes := make([]byte, n)\n\n\tm, err := r.Read(bytes)\n\tif err != nil {\n\t\treturn bytes, err\n\t}\n\n\tif m != n {\n\t\treturn bytes, fmt.Errorf(\"decode read bytes failed: expected %d got %d\", m, n)\n\t}\n\n\treturn bytes, nil\n}\n\nfunc WriteMarker(w io.Writer, m byte) error {\n\treturn WriteByte(w, m)\n}\n\nfunc ReadMarker(r io.Reader) (byte, error) {\n\treturn ReadByte(r)\n}\n\nfunc AssertMarker(r io.Reader, checkMarker bool, m byte) error {\n\tif checkMarker == false {\n\t\treturn nil\n\t}\n\n\tmarker, err := ReadMarker(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif marker != m {\n\t\treturn fmt.Errorf(\"decode assert marker failed: expected %v got %v\", m, marker)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "protocol/api/api.go",
    "content": "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/gwuhaolin/livego/configure\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay\"\n\n\tjwtmiddleware \"github.com/auth0/go-jwt-middleware\"\n\t\"github.com/dgrijalva/jwt-go\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype Response struct {\n\tw      http.ResponseWriter\n\tStatus int         `json:\"status\"`\n\tData   interface{} `json:\"data\"`\n}\n\nfunc (r *Response) SendJson() (int, error) {\n\tresp, _ := json.Marshal(r)\n\tr.w.Header().Set(\"Content-Type\", \"application/json\")\n\tr.w.WriteHeader(r.Status)\n\treturn r.w.Write(resp)\n}\n\ntype Operation struct {\n\tMethod string `json:\"method\"`\n\tURL    string `json:\"url\"`\n\tStop   bool   `json:\"stop\"`\n}\n\ntype OperationChange struct {\n\tMethod    string `json:\"method\"`\n\tSourceURL string `json:\"source_url\"`\n\tTargetURL string `json:\"target_url\"`\n\tStop      bool   `json:\"stop\"`\n}\n\ntype ClientInfo struct {\n\turl              string\n\trtmpRemoteClient *rtmp.Client\n\trtmpLocalClient  *rtmp.Client\n}\n\ntype Server struct {\n\thandler  av.Handler\n\tsession  map[string]*rtmprelay.RtmpRelay\n\trtmpAddr string\n}\n\nfunc NewServer(h av.Handler, rtmpAddr string) *Server {\n\treturn &Server{\n\t\thandler:  h,\n\t\tsession:  make(map[string]*rtmprelay.RtmpRelay),\n\t\trtmpAddr: rtmpAddr,\n\t}\n}\n\nfunc JWTMiddleware(next http.Handler) http.Handler {\n\tisJWT := len(configure.Config.GetString(\"jwt.secret\")) > 0\n\tif !isJWT {\n\t\treturn next\n\t}\n\n\tlog.Info(\"Using JWT middleware\")\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tvar algorithm jwt.SigningMethod\n\t\tif len(configure.Config.GetString(\"jwt.algorithm\")) > 0 {\n\t\t\talgorithm = jwt.GetSigningMethod(configure.Config.GetString(\"jwt.algorithm\"))\n\t\t}\n\n\t\tif algorithm == nil {\n\t\t\talgorithm = jwt.SigningMethodHS256\n\t\t}\n\n\t\tjwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{\n\t\t\tExtractor: jwtmiddleware.FromFirst(jwtmiddleware.FromAuthHeader, jwtmiddleware.FromParameter(\"jwt\")),\n\t\t\tValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {\n\t\t\t\treturn []byte(configure.Config.GetString(\"jwt.secret\")), nil\n\t\t\t},\n\t\t\tSigningMethod: algorithm,\n\t\t\tErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) {\n\t\t\t\tres := &Response{\n\t\t\t\t\tw:      w,\n\t\t\t\t\tStatus: 403,\n\t\t\t\t\tData:   err,\n\t\t\t\t}\n\t\t\t\tres.SendJson()\n\t\t\t},\n\t\t})\n\n\t\tjwtMiddleware.HandlerWithNext(w, r, next.ServeHTTP)\n\t})\n}\n\nfunc (s *Server) Serve(l net.Listener) error {\n\tmux := http.NewServeMux()\n\n\tmux.Handle(\"/statics/\", http.StripPrefix(\"/statics/\", http.FileServer(http.Dir(\"statics\"))))\n\n\tmux.HandleFunc(\"/control/push\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.handlePush(w, r)\n\t})\n\tmux.HandleFunc(\"/control/pull\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.handlePull(w, r)\n\t})\n\tmux.HandleFunc(\"/control/get\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.handleGet(w, r)\n\t})\n\tmux.HandleFunc(\"/control/reset\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.handleReset(w, r)\n\t})\n\tmux.HandleFunc(\"/control/delete\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.handleDelete(w, r)\n\t})\n\tmux.HandleFunc(\"/stat/livestat\", func(w http.ResponseWriter, r *http.Request) {\n\t\ts.GetLiveStatics(w, r)\n\t})\n\thttp.Serve(l, JWTMiddleware(mux))\n\treturn nil\n}\n\ntype stream struct {\n\tKey             string `json:\"key\"`\n\tUrl             string `json:\"url\"`\n\tStreamId        uint32 `json:\"stream_id\"`\n\tVideoTotalBytes uint64 `json:\"video_total_bytes\"`\n\tVideoSpeed      uint64 `json:\"video_speed\"`\n\tAudioTotalBytes uint64 `json:\"audio_total_bytes\"`\n\tAudioSpeed      uint64 `json:\"audio_speed\"`\n}\n\ntype streams struct {\n\tPublishers []stream `json:\"publishers\"`\n\tPlayers    []stream `json:\"players\"`\n}\n\n//http://127.0.0.1:8090/stat/livestat\nfunc (server *Server) GetLiveStatics(w http.ResponseWriter, req *http.Request) {\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\n\tdefer res.SendJson()\n\n\troom := \"\"\n\n\tif err := req.ParseForm(); err == nil {\n\t\troom = req.Form.Get(\"room\")\n\t}\n\n\trtmpStream := server.handler.(*rtmp.RtmpStream)\n\tif rtmpStream == nil {\n\t\tres.Status = 500\n\t\tres.Data = \"Get rtmp stream information error\"\n\t\treturn\n\t}\n\n\tmsgs := new(streams)\n\n\tif room == \"\" {\n\t\trtmpStream.GetStreams().Range(func(key, val interface{}) bool {\n\t\t\tif s, ok := val.(*rtmp.Stream); ok {\n\t\t\t\tif s.GetReader() != nil {\n\t\t\t\t\tswitch s.GetReader().(type) {\n\t\t\t\t\tcase *rtmp.VirReader:\n\t\t\t\t\t\tv := s.GetReader().(*rtmp.VirReader)\n\t\t\t\t\t\tmsg := stream{key.(string), v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS,\n\t\t\t\t\t\t\tv.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS}\n\t\t\t\t\t\tmsgs.Publishers = append(msgs.Publishers, msg)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\n\t\trtmpStream.GetStreams().Range(func(key, val interface{}) bool {\n\t\t\tws := val.(*rtmp.Stream).GetWs()\n\t\t\tws.Range(func(k, v interface{}) bool {\n\t\t\t\tif pw, ok := v.(*rtmp.PackWriterCloser); ok {\n\t\t\t\t\tif pw.GetWriter() != nil {\n\t\t\t\t\t\tswitch pw.GetWriter().(type) {\n\t\t\t\t\t\tcase *rtmp.VirWriter:\n\t\t\t\t\t\t\tv := pw.GetWriter().(*rtmp.VirWriter)\n\t\t\t\t\t\t\tmsg := stream{key.(string), v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS,\n\t\t\t\t\t\t\t\tv.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS}\n\t\t\t\t\t\t\tmsgs.Players = append(msgs.Players, msg)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\treturn true\n\t\t})\n\t} else {\n\t\t// Warning: The room should be in the \"live/stream\" format!\n\t\troomInfo, exists := (rtmpStream.GetStreams()).Load(room)\n\t\tif exists == false {\n\t\t\tres.Status = 404\n\t\t\tres.Data = \"room not found or inactive\"\n\t\t\treturn\n\t\t}\n\n\t\tif s, ok := roomInfo.(*rtmp.Stream); ok {\n\t\t\tif s.GetReader() != nil {\n\t\t\t\tswitch s.GetReader().(type) {\n\t\t\t\tcase *rtmp.VirReader:\n\t\t\t\t\tv := s.GetReader().(*rtmp.VirReader)\n\t\t\t\t\tmsg := stream{room, v.Info().URL, v.ReadBWInfo.StreamId, v.ReadBWInfo.VideoDatainBytes, v.ReadBWInfo.VideoSpeedInBytesperMS,\n\t\t\t\t\t\tv.ReadBWInfo.AudioDatainBytes, v.ReadBWInfo.AudioSpeedInBytesperMS}\n\t\t\t\t\tmsgs.Publishers = append(msgs.Publishers, msg)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ts.GetWs().Range(func(k, v interface{}) bool {\n\t\t\t\tif pw, ok := v.(*rtmp.PackWriterCloser); ok {\n\t\t\t\t\tif pw.GetWriter() != nil {\n\t\t\t\t\t\tswitch pw.GetWriter().(type) {\n\t\t\t\t\t\tcase *rtmp.VirWriter:\n\t\t\t\t\t\t\tv := pw.GetWriter().(*rtmp.VirWriter)\n\t\t\t\t\t\t\tmsg := stream{room, v.Info().URL, v.WriteBWInfo.StreamId, v.WriteBWInfo.VideoDatainBytes, v.WriteBWInfo.VideoSpeedInBytesperMS,\n\t\t\t\t\t\t\t\tv.WriteBWInfo.AudioDatainBytes, v.WriteBWInfo.AudioSpeedInBytesperMS}\n\t\t\t\t\t\t\tmsgs.Players = append(msgs.Players, msg)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\t//resp, _ := json.Marshal(msgs)\n\tres.Data = msgs\n}\n\n//http://127.0.0.1:8090/control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456\nfunc (s *Server) handlePull(w http.ResponseWriter, req *http.Request) {\n\tvar retString string\n\tvar err error\n\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\n\tdefer res.SendJson()\n\n\tif req.ParseForm() != nil {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/pull?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456\"\n\t\treturn\n\t}\n\n\toper := req.Form.Get(\"oper\")\n\tapp := req.Form.Get(\"app\")\n\tname := req.Form.Get(\"name\")\n\turl := req.Form.Get(\"url\")\n\n\tlog.Debugf(\"control pull: oper=%v, app=%v, name=%v, url=%v\", oper, app, name, url)\n\tif (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) {\n\t\tres.Status = 400\n\t\tres.Data = \"control push parameter error, please check them.\"\n\t\treturn\n\t}\n\n\tremoteurl := \"rtmp://127.0.0.1\" + s.rtmpAddr + \"/\" + app + \"/\" + name\n\tlocalurl := url\n\n\tkeyString := \"pull:\" + app + \"/\" + name\n\tif oper == \"stop\" {\n\t\tpullRtmprelay, found := s.session[keyString]\n\n\t\tif !found {\n\t\t\tretString = fmt.Sprintf(\"session key[%s] not exist, please check it again.\", keyString)\n\t\t\tres.Status = 400\n\t\t\tres.Data = retString\n\t\t\treturn\n\t\t}\n\t\tlog.Debugf(\"rtmprelay stop push %s from %s\", remoteurl, localurl)\n\t\tpullRtmprelay.Stop()\n\n\t\tdelete(s.session, keyString)\n\t\tretString = fmt.Sprintf(\"<h1>push url stop %s ok</h1></br>\", url)\n\t\tres.Status = 400\n\t\tres.Data = retString\n\t\tlog.Debugf(\"pull stop return %s\", retString)\n\t} else {\n\t\tpullRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl)\n\t\tlog.Debugf(\"rtmprelay start push %s from %s\", remoteurl, localurl)\n\t\terr = pullRtmprelay.Start()\n\t\tif err != nil {\n\t\t\tres.Status = 400\n\t\t\tretString = fmt.Sprintf(\"push error=%v\", err)\n\t\t} else {\n\t\t\ts.session[keyString] = pullRtmprelay\n\t\t\tretString = fmt.Sprintf(\"<h1>pull url start %s ok</h1></br>\", url)\n\t\t}\n\n\t\tres.Data = retString\n\t\tlog.Debugf(\"pull start return %s\", retString)\n\t}\n}\n\n//http://127.0.0.1:8090/control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456\nfunc (s *Server) handlePush(w http.ResponseWriter, req *http.Request) {\n\tvar retString string\n\tvar err error\n\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\n\tdefer res.SendJson()\n\n\tif req.ParseForm() != nil {\n\t\tres.Data = \"url: /control/push?&oper=start&app=live&name=123456&url=rtmp://192.168.16.136/live/123456\"\n\t\treturn\n\t}\n\n\toper := req.Form.Get(\"oper\")\n\tapp := req.Form.Get(\"app\")\n\tname := req.Form.Get(\"name\")\n\turl := req.Form.Get(\"url\")\n\n\tlog.Debugf(\"control push: oper=%v, app=%v, name=%v, url=%v\", oper, app, name, url)\n\tif (len(app) <= 0) || (len(name) <= 0) || (len(url) <= 0) {\n\t\tres.Data = \"control push parameter error, please check them.\"\n\t\treturn\n\t}\n\n\tlocalurl := \"rtmp://127.0.0.1\" + s.rtmpAddr + \"/\" + app + \"/\" + name\n\tremoteurl := url\n\n\tkeyString := \"push:\" + app + \"/\" + name\n\tif oper == \"stop\" {\n\t\tpushRtmprelay, found := s.session[keyString]\n\t\tif !found {\n\t\t\tretString = fmt.Sprintf(\"<h1>session key[%s] not exist, please check it again.</h1>\", keyString)\n\t\t\tres.Data = retString\n\t\t\treturn\n\t\t}\n\t\tlog.Debugf(\"rtmprelay stop push %s from %s\", remoteurl, localurl)\n\t\tpushRtmprelay.Stop()\n\n\t\tdelete(s.session, keyString)\n\t\tretString = fmt.Sprintf(\"<h1>push url stop %s ok</h1></br>\", url)\n\t\tres.Data = retString\n\t\tlog.Debugf(\"push stop return %s\", retString)\n\t} else {\n\t\tpushRtmprelay := rtmprelay.NewRtmpRelay(&localurl, &remoteurl)\n\t\tlog.Debugf(\"rtmprelay start push %s from %s\", remoteurl, localurl)\n\t\terr = pushRtmprelay.Start()\n\t\tif err != nil {\n\t\t\tretString = fmt.Sprintf(\"push error=%v\", err)\n\t\t} else {\n\t\t\tretString = fmt.Sprintf(\"<h1>push url start %s ok</h1></br>\", url)\n\t\t\ts.session[keyString] = pushRtmprelay\n\t\t}\n\n\t\tres.Data = retString\n\t\tlog.Debugf(\"push start return %s\", retString)\n\t}\n}\n\n//http://127.0.0.1:8090/control/reset?room=ROOM_NAME\nfunc (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\tdefer res.SendJson()\n\n\tif err := r.ParseForm(); err != nil {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/reset?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\troom := r.Form.Get(\"room\")\n\n\tif len(room) == 0 {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/reset?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\n\tmsg, err := configure.RoomKeys.SetKey(room)\n\n\tif err != nil {\n\t\tmsg = err.Error()\n\t\tres.Status = 400\n\t}\n\n\tres.Data = msg\n}\n\n//http://127.0.0.1:8090/control/get?room=ROOM_NAME\nfunc (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\tdefer res.SendJson()\n\n\tif err := r.ParseForm(); err != nil {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/get?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\n\troom := r.Form.Get(\"room\")\n\n\tif len(room) == 0 {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/get?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\n\tmsg, err := configure.RoomKeys.GetKey(room)\n\tif err != nil {\n\t\tmsg = err.Error()\n\t\tres.Status = 400\n\t}\n\tres.Data = msg\n}\n\n//http://127.0.0.1:8090/control/delete?room=ROOM_NAME\nfunc (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) {\n\tres := &Response{\n\t\tw:      w,\n\t\tData:   nil,\n\t\tStatus: 200,\n\t}\n\tdefer res.SendJson()\n\n\tif err := r.ParseForm(); err != nil {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/delete?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\n\troom := r.Form.Get(\"room\")\n\n\tif len(room) == 0 {\n\t\tres.Status = 400\n\t\tres.Data = \"url: /control/delete?room=<ROOM_NAME>\"\n\t\treturn\n\t}\n\n\tif configure.RoomKeys.DeleteChannel(room) {\n\t\tres.Data = \"Ok\"\n\t\treturn\n\t}\n\tres.Status = 404\n\tres.Data = \"room not found\"\n}\n"
  },
  {
    "path": "protocol/hls/align.go",
    "content": "package hls\n\nconst (\n\tsyncms = 2 // ms\n)\n\ntype align struct {\n\tframeNum  uint64\n\tframeBase uint64\n}\n\nfunc (a *align) align(dts *uint64, inc uint32) {\n\taFrameDts := *dts\n\testPts := a.frameBase + a.frameNum*uint64(inc)\n\tvar dPts uint64\n\tif estPts >= aFrameDts {\n\t\tdPts = estPts - aFrameDts\n\t} else {\n\t\tdPts = aFrameDts - estPts\n\t}\n\n\tif dPts <= uint64(syncms)*h264_default_hz {\n\t\ta.frameNum++\n\t\t*dts = estPts\n\t\treturn\n\t}\n\ta.frameNum = 1\n\ta.frameBase = aFrameDts\n}\n"
  },
  {
    "path": "protocol/hls/audio_cache.go",
    "content": "package hls\n\nimport \"bytes\"\n\nconst (\n\tcache_max_frames byte = 6\n\taudio_cache_len  int  = 10 * 1024\n)\n\ntype audioCache struct {\n\tsoundFormat byte\n\tnum         byte\n\toffset      int\n\tpts         uint64\n\tbuf         *bytes.Buffer\n}\n\nfunc newAudioCache() *audioCache {\n\treturn &audioCache{\n\t\tbuf: bytes.NewBuffer(make([]byte, audio_cache_len)),\n\t}\n}\n\nfunc (a *audioCache) Cache(src []byte, pts uint64) bool {\n\tif a.num == 0 {\n\t\ta.offset = 0\n\t\ta.pts = pts\n\t\ta.buf.Reset()\n\t}\n\ta.buf.Write(src)\n\ta.offset += len(src)\n\ta.num++\n\n\treturn false\n}\n\nfunc (a *audioCache) GetFrame() (int, uint64, []byte) {\n\ta.num = 0\n\treturn a.offset, a.pts, a.buf.Bytes()\n}\n\nfunc (a *audioCache) CacheNum() byte {\n\treturn a.num\n}\n"
  },
  {
    "path": "protocol/hls/cache.go",
    "content": "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.Errorf(\"No key for cache\")\n)\n\ntype TSCacheItem struct {\n\tid   string\n\tnum  int\n\tlock sync.RWMutex\n\tll   *list.List\n\tlm   map[string]TSItem\n}\n\nfunc NewTSCacheItem(id string) *TSCacheItem {\n\treturn &TSCacheItem{\n\t\tid:  id,\n\t\tll:  list.New(),\n\t\tnum: maxTSCacheNum,\n\t\tlm:  make(map[string]TSItem),\n\t}\n}\n\nfunc (tcCacheItem *TSCacheItem) ID() string {\n\treturn tcCacheItem.id\n}\n\n// TODO: found data race, fix it\nfunc (tcCacheItem *TSCacheItem) GenM3U8PlayList() ([]byte, error) {\n\tvar seq int\n\tvar getSeq bool\n\tvar maxDuration int\n\tm3u8body := bytes.NewBuffer(nil)\n\tfor e := tcCacheItem.ll.Front(); e != nil; e = e.Next() {\n\t\tkey := e.Value.(string)\n\t\tv, ok := tcCacheItem.lm[key]\n\t\tif ok {\n\t\t\tif v.Duration > maxDuration {\n\t\t\t\tmaxDuration = v.Duration\n\t\t\t}\n\t\t\tif !getSeq {\n\t\t\t\tgetSeq = true\n\t\t\t\tseq = v.SeqNum\n\t\t\t}\n\t\t\tfmt.Fprintf(m3u8body, \"#EXTINF:%.3f,\\n%s\\n\", float64(v.Duration)/float64(1000), v.Name)\n\t\t}\n\t}\n\tw := bytes.NewBuffer(nil)\n\tfmt.Fprintf(w,\n\t\t\"#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\",\n\t\tmaxDuration/1000+1, seq)\n\tw.Write(m3u8body.Bytes())\n\treturn w.Bytes(), nil\n}\n\nfunc (tcCacheItem *TSCacheItem) SetItem(key string, item TSItem) {\n\tif tcCacheItem.ll.Len() == tcCacheItem.num {\n\t\te := tcCacheItem.ll.Front()\n\t\ttcCacheItem.ll.Remove(e)\n\t\tk := e.Value.(string)\n\t\tdelete(tcCacheItem.lm, k)\n\t}\n\ttcCacheItem.lm[key] = item\n\ttcCacheItem.ll.PushBack(key)\n}\n\nfunc (tcCacheItem *TSCacheItem) GetItem(key string) (TSItem, error) {\n\titem, ok := tcCacheItem.lm[key]\n\tif !ok {\n\t\treturn item, ErrNoKey\n\t}\n\treturn item, nil\n}\n"
  },
  {
    "path": "protocol/hls/hls.go",
    "content": "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/livego/configure\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tduration = 3000\n)\n\nvar (\n\tErrNoPublisher         = fmt.Errorf(\"no publisher\")\n\tErrInvalidReq          = fmt.Errorf(\"invalid req url path\")\n\tErrNoSupportVideoCodec = fmt.Errorf(\"no support video codec\")\n\tErrNoSupportAudioCodec = fmt.Errorf(\"no support audio codec\")\n)\n\nvar crossdomainxml = []byte(`<?xml version=\"1.0\" ?>\n<cross-domain-policy>\n\t<allow-access-from domain=\"*\" />\n\t<allow-http-request-headers-from domain=\"*\" headers=\"*\"/>\n</cross-domain-policy>`)\n\ntype Server struct {\n\tlistener net.Listener\n\tconns    *sync.Map\n}\n\nfunc NewServer() *Server {\n\tret := &Server{\n\t\tconns: &sync.Map{},\n\t}\n\tgo ret.checkStop()\n\treturn ret\n}\n\nfunc (server *Server) Serve(listener net.Listener) error {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tserver.handle(w, r)\n\t})\n\tserver.listener = listener\n\n\tif configure.Config.GetBool(\"use_hls_https\") {\n\t\thttp.ServeTLS(listener, mux, \"server.crt\", \"server.key\")\n\t} else {\n\t\thttp.Serve(listener, mux)\n\t}\n\n\treturn nil\n}\n\nfunc (server *Server) GetWriter(info av.Info) av.WriteCloser {\n\tvar s *Source\n\tv, ok := server.conns.Load(info.Key)\n\tif !ok {\n\t\tlog.Debug(\"new hls source\")\n\t\ts = NewSource(info)\n\t\tserver.conns.Store(info.Key, s)\n\t} else {\n\t\ts = v.(*Source)\n\t}\n\treturn s\n}\n\nfunc (server *Server) getConn(key string) *Source {\n\tv, ok := server.conns.Load(key)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn v.(*Source)\n}\n\nfunc (server *Server) checkStop() {\n\tfor {\n\t\t<-time.After(5 * time.Second)\n\n\t\tserver.conns.Range(func(key, val interface{}) bool {\n\t\t\tv := val.(*Source)\n\t\t\tif !v.Alive() && !configure.Config.GetBool(\"hls_keep_after_end\") {\n\t\t\t\tlog.Debug(\"check stop and remove: \", v.Info())\n\t\t\t\tserver.conns.Delete(key)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\nfunc (server *Server) handle(w http.ResponseWriter, r *http.Request) {\n\tif path.Base(r.URL.Path) == \"crossdomain.xml\" {\n\t\tw.Header().Set(\"Content-Type\", \"application/xml\")\n\t\tw.Write(crossdomainxml)\n\t\treturn\n\t}\n\tswitch path.Ext(r.URL.Path) {\n\tcase \".m3u8\":\n\t\tkey, _ := server.parseM3u8(r.URL.Path)\n\t\tconn := server.getConn(key)\n\t\tif conn == nil {\n\t\t\thttp.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\ttsCache := conn.GetCacheInc()\n\t\tif tsCache == nil {\n\t\t\thttp.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\tbody, err := tsCache.GenM3U8PlayList()\n\t\tif err != nil {\n\t\t\tlog.Debug(\"GenM3U8PlayList error: \", err)\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Cache-Control\", \"no-cache\")\n\t\tw.Header().Set(\"Content-Type\", \"application/x-mpegURL\")\n\t\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(body)))\n\t\tw.Write(body)\n\tcase \".ts\":\n\t\tkey, _ := server.parseTs(r.URL.Path)\n\t\tconn := server.getConn(key)\n\t\tif conn == nil {\n\t\t\thttp.Error(w, ErrNoPublisher.Error(), http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\ttsCache := conn.GetCacheInc()\n\t\titem, err := tsCache.GetItem(r.URL.Path)\n\t\tif err != nil {\n\t\t\tlog.Debug(\"GetItem error: \", err)\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Content-Type\", \"video/mp2ts\")\n\t\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(item.Data)))\n\t\tw.Write(item.Data)\n\t}\n}\n\nfunc (server *Server) parseM3u8(pathstr string) (key string, err error) {\n\tpathstr = strings.TrimLeft(pathstr, \"/\")\n\tkey = strings.Split(pathstr, path.Ext(pathstr))[0]\n\treturn\n}\n\nfunc (server *Server) parseTs(pathstr string) (key string, err error) {\n\tpathstr = strings.TrimLeft(pathstr, \"/\")\n\tpaths := strings.SplitN(pathstr, \"/\", 3)\n\tif len(paths) != 3 {\n\t\terr = fmt.Errorf(\"invalid path=%s\", pathstr)\n\t\treturn\n\t}\n\tkey = paths[0] + \"/\" + paths[1]\n\n\treturn\n}\n"
  },
  {
    "path": "protocol/hls/item.go",
    "content": "package hls\n\ntype TSItem struct {\n\tName     string\n\tSeqNum   int\n\tDuration int\n\tData     []byte\n}\n\nfunc NewTSItem(name string, duration, seqNum int, b []byte) TSItem {\n\tvar item TSItem\n\titem.Name = name\n\titem.SeqNum = seqNum\n\titem.Duration = duration\n\titem.Data = make([]byte, len(b))\n\tcopy(item.Data, b)\n\treturn item\n}\n"
  },
  {
    "path": "protocol/hls/source.go",
    "content": "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\"\n\t\"github.com/gwuhaolin/livego/container/flv\"\n\t\"github.com/gwuhaolin/livego/container/ts\"\n\t\"github.com/gwuhaolin/livego/parser\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tvideoHZ      = 90000\n\taacSampleLen = 1024\n\tmaxQueueNum  = 512\n\n\th264_default_hz uint64 = 90\n)\n\ntype Source struct {\n\tav.RWBaser\n\tseq         int\n\tinfo        av.Info\n\tbwriter     *bytes.Buffer\n\tbtswriter   *bytes.Buffer\n\tdemuxer     *flv.Demuxer\n\tmuxer       *ts.Muxer\n\tpts, dts    uint64\n\tstat        *status\n\talign       *align\n\tcache       *audioCache\n\ttsCache     *TSCacheItem\n\ttsparser    *parser.CodecParser\n\tclosed      bool\n\tpacketQueue chan *av.Packet\n}\n\nfunc NewSource(info av.Info) *Source {\n\tinfo.Inter = true\n\ts := &Source{\n\t\tinfo:        info,\n\t\talign:       &align{},\n\t\tstat:        newStatus(),\n\t\tRWBaser:     av.NewRWBaser(time.Second * 10),\n\t\tcache:       newAudioCache(),\n\t\tdemuxer:     flv.NewDemuxer(),\n\t\tmuxer:       ts.NewMuxer(),\n\t\ttsCache:     NewTSCacheItem(info.Key),\n\t\ttsparser:    parser.NewCodecParser(),\n\t\tbwriter:     bytes.NewBuffer(make([]byte, 100*1024)),\n\t\tpacketQueue: make(chan *av.Packet, maxQueueNum),\n\t}\n\tgo func() {\n\t\terr := s.SendPacket()\n\t\tif err != nil {\n\t\t\tlog.Debug(\"send packet error: \", err)\n\t\t\ts.closed = true\n\t\t}\n\t}()\n\treturn s\n}\n\nfunc (source *Source) GetCacheInc() *TSCacheItem {\n\treturn source.tsCache\n}\n\nfunc (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) {\n\tlog.Warningf(\"[%v] packet queue max!!!\", info)\n\tfor i := 0; i < maxQueueNum-84; i++ {\n\t\ttmpPkt, ok := <-pktQue\n\t\t// try to don't drop audio\n\t\tif ok && tmpPkt.IsAudio {\n\t\t\tif len(pktQue) > maxQueueNum-2 {\n\t\t\t\t<-pktQue\n\t\t\t} else {\n\t\t\t\tpktQue <- tmpPkt\n\t\t\t}\n\t\t}\n\n\t\tif ok && tmpPkt.IsVideo {\n\t\t\tvideoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)\n\t\t\t// dont't drop sps config and dont't drop key frame\n\t\t\tif ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {\n\t\t\t\tpktQue <- tmpPkt\n\t\t\t}\n\t\t\tif len(pktQue) > maxQueueNum-10 {\n\t\t\t\t<-pktQue\n\t\t\t}\n\t\t}\n\n\t}\n\tlog.Debug(\"packet queue len: \", len(pktQue))\n}\n\nfunc (source *Source) Write(p *av.Packet) (err error) {\n\terr = nil\n\tif source.closed {\n\t\terr = fmt.Errorf(\"hls source closed\")\n\t\treturn\n\t}\n\tsource.SetPreTime()\n\tdefer func() {\n\t\tif e := recover(); e != nil {\n\t\t\terr = fmt.Errorf(\"hls source has already been closed:%v\", e)\n\t\t}\n\t}()\n\tif len(source.packetQueue) >= maxQueueNum-24 {\n\t\tsource.DropPacket(source.packetQueue, source.info)\n\t} else {\n\t\tif !source.closed {\n\t\t\tsource.packetQueue <- p\n\t\t}\n\t}\n\treturn\n}\n\nfunc (source *Source) SendPacket() error {\n\tdefer func() {\n\t\tlog.Debugf(\"[%v] hls sender stop\", source.info)\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Warning(\"hls SendPacket panic: \", r)\n\t\t}\n\t}()\n\n\tlog.Debugf(\"[%v] hls sender start\", source.info)\n\tfor {\n\t\tif source.closed {\n\t\t\treturn fmt.Errorf(\"closed\")\n\t\t}\n\n\t\tp, ok := <-source.packetQueue\n\t\tif ok {\n\t\t\tif p.IsMetadata {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr := source.demuxer.Demux(p)\n\t\t\tif err == flv.ErrAvcEndSEQ {\n\t\t\t\tlog.Warning(err)\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warning(err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tcompositionTime, isSeq, err := source.parse(p)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warning(err)\n\t\t\t}\n\t\t\tif err != nil || isSeq {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source.btswriter != nil {\n\t\t\t\tsource.stat.update(p.IsVideo, p.TimeStamp)\n\t\t\t\tsource.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime))\n\t\t\t\tsource.tsMux(p)\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"closed\")\n\t\t}\n\t}\n}\n\nfunc (source *Source) Info() (ret av.Info) {\n\treturn source.info\n}\n\nfunc (source *Source) cleanup() {\n\tclose(source.packetQueue)\n\tsource.bwriter = nil\n\tsource.btswriter = nil\n\tsource.cache = nil\n\tsource.tsCache = nil\n}\n\nfunc (source *Source) Close(err error) {\n\tlog.Debug(\"hls source closed: \", source.info)\n\tif !source.closed && !configure.Config.GetBool(\"hls_keep_after_end\") {\n\t\tsource.cleanup()\n\t}\n\tsource.closed = true\n}\n\nfunc (source *Source) cut() {\n\tnewf := true\n\tif source.btswriter == nil {\n\t\tsource.btswriter = bytes.NewBuffer(nil)\n\t} else if source.btswriter != nil && source.stat.durationMs() >= duration {\n\t\tsource.flushAudio()\n\n\t\tsource.seq++\n\t\tfilename := fmt.Sprintf(\"/%s/%d.ts\", source.info.Key, time.Now().Unix())\n\t\titem := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes())\n\t\tsource.tsCache.SetItem(filename, item)\n\n\t\tsource.btswriter.Reset()\n\t\tsource.stat.resetAndNew()\n\t} else {\n\t\tnewf = false\n\t}\n\tif newf {\n\t\tsource.btswriter.Write(source.muxer.PAT())\n\t\tsource.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true))\n\t}\n}\n\nfunc (source *Source) parse(p *av.Packet) (int32, bool, error) {\n\tvar compositionTime int32\n\tvar ah av.AudioPacketHeader\n\tvar vh av.VideoPacketHeader\n\tif p.IsVideo {\n\t\tvh = p.Header.(av.VideoPacketHeader)\n\t\tif vh.CodecID() != av.VIDEO_H264 {\n\t\t\treturn compositionTime, false, ErrNoSupportVideoCodec\n\t\t}\n\t\tcompositionTime = vh.CompositionTime()\n\t\tif vh.IsKeyFrame() && vh.IsSeq() {\n\t\t\treturn compositionTime, true, source.tsparser.Parse(p, source.bwriter)\n\t\t}\n\t} else {\n\t\tah = p.Header.(av.AudioPacketHeader)\n\t\tif ah.SoundFormat() != av.SOUND_AAC {\n\t\t\treturn compositionTime, false, ErrNoSupportAudioCodec\n\t\t}\n\t\tif ah.AACPacketType() == av.AAC_SEQHDR {\n\t\t\treturn compositionTime, true, source.tsparser.Parse(p, source.bwriter)\n\t\t}\n\t}\n\tsource.bwriter.Reset()\n\tif err := source.tsparser.Parse(p, source.bwriter); err != nil {\n\t\treturn compositionTime, false, err\n\t}\n\tp.Data = source.bwriter.Bytes()\n\n\tif p.IsVideo && vh.IsKeyFrame() {\n\t\tsource.cut()\n\t}\n\treturn compositionTime, false, nil\n}\n\nfunc (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) {\n\tsource.dts = uint64(ts) * h264_default_hz\n\tif isVideo {\n\t\tsource.pts = source.dts + uint64(compositionTs)*h264_default_hz\n\t} else {\n\t\tsampleRate, _ := source.tsparser.SampleRate()\n\t\tsource.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate))\n\t\tsource.pts = source.dts\n\t}\n}\nfunc (source *Source) flushAudio() error {\n\treturn source.muxAudio(1)\n}\n\nfunc (source *Source) muxAudio(limit byte) error {\n\tif source.cache.CacheNum() < limit {\n\t\treturn nil\n\t}\n\tvar p av.Packet\n\t_, pts, buf := source.cache.GetFrame()\n\tp.Data = buf\n\tp.TimeStamp = uint32(pts / h264_default_hz)\n\treturn source.muxer.Mux(&p, source.btswriter)\n}\n\nfunc (source *Source) tsMux(p *av.Packet) error {\n\tif p.IsVideo {\n\t\treturn source.muxer.Mux(p, source.btswriter)\n\t} else {\n\t\tsource.cache.Cache(p.Data, source.pts)\n\t\treturn source.muxAudio(cache_max_frames)\n\t}\n}\n"
  },
  {
    "path": "protocol/hls/status.go",
    "content": "package hls\n\nimport \"time\"\n\ntype status struct {\n\thasVideo       bool\n\tseqId          int64\n\tcreatedAt      time.Time\n\tsegBeginAt     time.Time\n\thasSetFirstTs  bool\n\tfirstTimestamp int64\n\tlastTimestamp  int64\n}\n\nfunc newStatus() *status {\n\treturn &status{\n\t\tseqId:         0,\n\t\thasSetFirstTs: false,\n\t\tsegBeginAt:    time.Now(),\n\t}\n}\n\nfunc (t *status) update(isVideo bool, timestamp uint32) {\n\tif isVideo {\n\t\tt.hasVideo = true\n\t}\n\tif !t.hasSetFirstTs {\n\t\tt.hasSetFirstTs = true\n\t\tt.firstTimestamp = int64(timestamp)\n\t}\n\tt.lastTimestamp = int64(timestamp)\n}\n\nfunc (t *status) resetAndNew() {\n\tt.seqId++\n\tt.hasVideo = false\n\tt.createdAt = time.Now()\n\tt.hasSetFirstTs = false\n}\n\nfunc (t *status) durationMs() int64 {\n\treturn t.lastTimestamp - t.firstTimestamp\n}\n"
  },
  {
    "path": "protocol/httpflv/server.go",
    "content": "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/gwuhaolin/livego/protocol/rtmp\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype Server struct {\n\thandler av.Handler\n}\n\ntype stream struct {\n\tKey string `json:\"key\"`\n\tId  string `json:\"id\"`\n}\n\ntype streams struct {\n\tPublishers []stream `json:\"publishers\"`\n\tPlayers    []stream `json:\"players\"`\n}\n\nfunc NewServer(h av.Handler) *Server {\n\treturn &Server{\n\t\thandler: h,\n\t}\n}\n\nfunc (server *Server) Serve(l net.Listener) error {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tserver.handleConn(w, r)\n\t})\n\tmux.HandleFunc(\"/streams\", func(w http.ResponseWriter, r *http.Request) {\n\t\tserver.getStream(w, r)\n\t})\n\tif err := http.Serve(l, mux); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// 获取发布和播放器的信息\nfunc (server *Server) getStreams(w http.ResponseWriter, r *http.Request) *streams {\n\trtmpStream := server.handler.(*rtmp.RtmpStream)\n\tif rtmpStream == nil {\n\t\treturn nil\n\t}\n\tmsgs := new(streams)\n\n\trtmpStream.GetStreams().Range(func(key, val interface{}) bool {\n\t\tif s, ok := val.(*rtmp.Stream); ok {\n\t\t\tif s.GetReader() != nil {\n\t\t\t\tmsg := stream{key.(string), s.GetReader().Info().UID}\n\t\t\t\tmsgs.Publishers = append(msgs.Publishers, msg)\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\trtmpStream.GetStreams().Range(func(key, val interface{}) bool {\n\t\tws := val.(*rtmp.Stream).GetWs()\n\n\t\tws.Range(func(k, v interface{}) bool {\n\t\t\tif pw, ok := v.(*rtmp.PackWriterCloser); ok {\n\t\t\t\tif pw.GetWriter() != nil {\n\t\t\t\t\tmsg := stream{key.(string), pw.GetWriter().Info().UID}\n\t\t\t\t\tmsgs.Players = append(msgs.Players, msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\treturn true\n\t})\n\n\treturn msgs\n}\n\nfunc (server *Server) getStream(w http.ResponseWriter, r *http.Request) {\n\tmsgs := server.getStreams(w, r)\n\tif msgs == nil {\n\t\treturn\n\t}\n\tresp, _ := json.Marshal(msgs)\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(resp)\n}\n\nfunc (server *Server) handleConn(w http.ResponseWriter, r *http.Request) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Error(\"http flv handleConn panic: \", r)\n\t\t}\n\t}()\n\n\turl := r.URL.String()\n\tu := r.URL.Path\n\tif pos := strings.LastIndex(u, \".\"); pos < 0 || u[pos:] != \".flv\" {\n\t\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tpath := strings.TrimSuffix(strings.TrimLeft(u, \"/\"), \".flv\")\n\tpaths := strings.SplitN(path, \"/\", 2)\n\tlog.Debug(\"url:\", u, \"path:\", path, \"paths:\", paths)\n\n\tif len(paths) != 2 {\n\t\thttp.Error(w, \"invalid path\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// 判断视屏流是否发布,如果没有发布,直接返回404\n\tmsgs := server.getStreams(w, r)\n\tif msgs == nil || len(msgs.Publishers) == 0 {\n\t\thttp.Error(w, \"invalid path\", http.StatusNotFound)\n\t\treturn\n\t} else {\n\t\tinclude := false\n\t\tfor _, item := range msgs.Publishers {\n\t\t\tif item.Key == path {\n\t\t\t\tinclude = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif include == false {\n\t\t\thttp.Error(w, \"invalid path\", http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\t}\n\n\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\twriter := NewFLVWriter(paths[0], paths[1], url, w)\n\n\tserver.handler.HandleWriter(writer)\n\twriter.Wait()\n}\n"
  },
  {
    "path": "protocol/httpflv/writer.go",
    "content": "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/protocol/amf\"\n\t\"github.com/gwuhaolin/livego/utils/pio\"\n\t\"github.com/gwuhaolin/livego/utils/uid\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\theaderLen   = 11\n\tmaxQueueNum = 1024\n)\n\ntype FLVWriter struct {\n\tUid string\n\tav.RWBaser\n\tapp, title, url string\n\tbuf             []byte\n\tclosed          bool\n\tclosedChan      chan struct{}\n\tctx             http.ResponseWriter\n\tpacketQueue     chan *av.Packet\n}\n\nfunc NewFLVWriter(app, title, url string, ctx http.ResponseWriter) *FLVWriter {\n\tret := &FLVWriter{\n\t\tUid:         uid.NewId(),\n\t\tapp:         app,\n\t\ttitle:       title,\n\t\turl:         url,\n\t\tctx:         ctx,\n\t\tRWBaser:     av.NewRWBaser(time.Second * 10),\n\t\tclosedChan:  make(chan struct{}),\n\t\tbuf:         make([]byte, headerLen),\n\t\tpacketQueue: make(chan *av.Packet, maxQueueNum),\n\t}\n\n\tif _, err := ret.ctx.Write([]byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09}); err != nil {\n\t\tlog.Errorf(\"Error on response writer\")\n\t\tret.closed = true\n\t}\n\tpio.PutI32BE(ret.buf[:4], 0)\n\tif _, err := ret.ctx.Write(ret.buf[:4]); err != nil {\n\t\tlog.Errorf(\"Error on response writer\")\n\t\tret.closed = true\n\t}\n\tgo func() {\n\t\terr := ret.SendPacket()\n\t\tif err != nil {\n\t\t\tlog.Debug(\"SendPacket error: \", err)\n\t\t\tret.closed = true\n\t\t}\n\n\t}()\n\treturn ret\n}\n\nfunc (flvWriter *FLVWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {\n\tlog.Warningf(\"[%v] packet queue max!!!\", info)\n\tfor i := 0; i < maxQueueNum-84; i++ {\n\t\ttmpPkt, ok := <-pktQue\n\t\tif ok && tmpPkt.IsVideo {\n\t\t\tvideoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)\n\t\t\t// dont't drop sps config and dont't drop key frame\n\t\t\tif ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {\n\t\t\t\tlog.Debug(\"insert keyframe to queue\")\n\t\t\t\tpktQue <- tmpPkt\n\t\t\t}\n\n\t\t\tif len(pktQue) > maxQueueNum-10 {\n\t\t\t\t<-pktQue\n\t\t\t}\n\t\t\t// drop other packet\n\t\t\t<-pktQue\n\t\t}\n\t\t// try to don't drop audio\n\t\tif ok && tmpPkt.IsAudio {\n\t\t\tlog.Debug(\"insert audio to queue\")\n\t\t\tpktQue <- tmpPkt\n\t\t}\n\t}\n\tlog.Debug(\"packet queue len: \", len(pktQue))\n}\n\nfunc (flvWriter *FLVWriter) Write(p *av.Packet) (err error) {\n\terr = nil\n\tif flvWriter.closed {\n\t\terr = fmt.Errorf(\"flvwrite source closed\")\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif e := recover(); e != nil {\n\t\t\terr = fmt.Errorf(\"FLVWriter has already been closed:%v\", e)\n\t\t}\n\t}()\n\n\tif len(flvWriter.packetQueue) >= maxQueueNum-24 {\n\t\tflvWriter.DropPacket(flvWriter.packetQueue, flvWriter.Info())\n\t} else {\n\t\tflvWriter.packetQueue <- p\n\t}\n\n\treturn\n}\n\nfunc (flvWriter *FLVWriter) SendPacket() error {\n\tfor {\n\t\tp, ok := <-flvWriter.packetQueue\n\t\tif ok {\n\t\t\tflvWriter.RWBaser.SetPreTime()\n\t\t\th := flvWriter.buf[:headerLen]\n\t\t\ttypeID := av.TAG_VIDEO\n\t\t\tif !p.IsVideo {\n\t\t\t\tif p.IsMetadata {\n\t\t\t\t\tvar err error\n\t\t\t\t\ttypeID = av.TAG_SCRIPTDATAAMF0\n\t\t\t\t\tp.Data, err = amf.MetaDataReform(p.Data, amf.DEL)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttypeID = av.TAG_AUDIO\n\t\t\t\t}\n\t\t\t}\n\t\t\tdataLen := len(p.Data)\n\t\t\ttimestamp := p.TimeStamp\n\t\t\ttimestamp += flvWriter.BaseTimeStamp()\n\t\t\tflvWriter.RWBaser.RecTimeStamp(timestamp, uint32(typeID))\n\n\t\t\tpreDataLen := dataLen + headerLen\n\t\t\ttimestampbase := timestamp & 0xffffff\n\t\t\ttimestampExt := timestamp >> 24 & 0xff\n\n\t\t\tpio.PutU8(h[0:1], uint8(typeID))\n\t\t\tpio.PutI24BE(h[1:4], int32(dataLen))\n\t\t\tpio.PutI24BE(h[4:7], int32(timestampbase))\n\t\t\tpio.PutU8(h[7:8], uint8(timestampExt))\n\n\t\t\tif _, err := flvWriter.ctx.Write(h); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif _, err := flvWriter.ctx.Write(p.Data); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tpio.PutI32BE(h[:4], int32(preDataLen))\n\t\t\tif _, err := flvWriter.ctx.Write(h[:4]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"closed\")\n\t\t}\n\n\t}\n}\n\nfunc (flvWriter *FLVWriter) Wait() {\n\tselect {\n\tcase <-flvWriter.closedChan:\n\t\treturn\n\t}\n}\n\nfunc (flvWriter *FLVWriter) Close(error) {\n\tlog.Debug(\"http flv closed\")\n\tif !flvWriter.closed {\n\t\tclose(flvWriter.packetQueue)\n\t\tclose(flvWriter.closedChan)\n\t}\n\tflvWriter.closed = true\n}\n\nfunc (flvWriter *FLVWriter) Info() (ret av.Info) {\n\tret.UID = flvWriter.Uid\n\tret.URL = flvWriter.url\n\tret.Key = flvWriter.app + \"/\" + flvWriter.title\n\tret.Inter = true\n\treturn\n}\n"
  },
  {
    "path": "protocol/rtmp/cache/cache.go",
    "content": "package cache\n\nimport (\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n)\n\ntype Cache struct {\n\tgop      *GopCache\n\tvideoSeq *SpecialCache\n\taudioSeq *SpecialCache\n\tmetadata *SpecialCache\n}\n\nfunc NewCache() *Cache {\n\treturn &Cache{\n\t\tgop:      NewGopCache(configure.Config.GetInt(\"gop_num\")),\n\t\tvideoSeq: NewSpecialCache(),\n\t\taudioSeq: NewSpecialCache(),\n\t\tmetadata: NewSpecialCache(),\n\t}\n}\n\nfunc (cache *Cache) Write(p av.Packet) {\n\tif p.IsMetadata {\n\t\tcache.metadata.Write(&p)\n\t\treturn\n\t} else {\n\t\tif !p.IsVideo {\n\t\t\tah, ok := p.Header.(av.AudioPacketHeader)\n\t\t\tif ok {\n\t\t\t\tif ah.SoundFormat() == av.SOUND_AAC &&\n\t\t\t\t\tah.AACPacketType() == av.AAC_SEQHDR {\n\t\t\t\t\tcache.audioSeq.Write(&p)\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\t\t\tvh, ok := p.Header.(av.VideoPacketHeader)\n\t\t\tif ok {\n\t\t\t\tif vh.IsSeq() {\n\t\t\t\t\tcache.videoSeq.Write(&p)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t}\n\t}\n\tcache.gop.Write(&p)\n}\n\nfunc (cache *Cache) Send(w av.WriteCloser) error {\n\tif err := cache.metadata.Send(w); err != nil {\n\t\treturn err\n\t}\n\n\tif err := cache.videoSeq.Send(w); err != nil {\n\t\treturn err\n\t}\n\n\tif err := cache.audioSeq.Send(w); err != nil {\n\t\treturn err\n\t}\n\n\tif err := cache.gop.Send(w); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "protocol/rtmp/cache/gop.go",
    "content": "package cache\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n)\n\nvar (\n\tmaxGOPCap    int = 1024\n\tErrGopTooBig     = fmt.Errorf(\"gop to big\")\n)\n\ntype array struct {\n\tindex   int\n\tpackets []*av.Packet\n}\n\nfunc newArray() *array {\n\tret := &array{\n\t\tindex:   0,\n\t\tpackets: make([]*av.Packet, 0, maxGOPCap),\n\t}\n\treturn ret\n}\n\nfunc (array *array) reset() {\n\tarray.index = 0\n\tarray.packets = array.packets[:0]\n}\n\nfunc (array *array) write(packet *av.Packet) error {\n\tif array.index >= maxGOPCap {\n\t\treturn ErrGopTooBig\n\t}\n\tarray.packets = append(array.packets, packet)\n\tarray.index++\n\treturn nil\n}\n\nfunc (array *array) send(w av.WriteCloser) error {\n\tvar err error\n\tfor i := 0; i < array.index; i++ {\n\t\tpacket := array.packets[i]\n\t\tif err = w.Write(packet); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn err\n}\n\ntype GopCache struct {\n\tstart     bool\n\tnum       int\n\tcount     int\n\tnextindex int\n\tgops      []*array\n}\n\nfunc NewGopCache(num int) *GopCache {\n\treturn &GopCache{\n\t\tcount: num,\n\t\tgops:  make([]*array, num),\n\t}\n}\n\nfunc (gopCache *GopCache) writeToArray(chunk *av.Packet, startNew bool) error {\n\tvar ginc *array\n\tif startNew {\n\t\tginc = gopCache.gops[gopCache.nextindex]\n\t\tif ginc == nil {\n\t\t\tginc = newArray()\n\t\t\tgopCache.num++\n\t\t\tgopCache.gops[gopCache.nextindex] = ginc\n\t\t} else {\n\t\t\tginc.reset()\n\t\t}\n\t\tgopCache.nextindex = (gopCache.nextindex + 1) % gopCache.count\n\t} else {\n\t\tginc = gopCache.gops[(gopCache.nextindex+1)%gopCache.count]\n\t}\n\tginc.write(chunk)\n\n\treturn nil\n}\n\nfunc (gopCache *GopCache) Write(p *av.Packet) {\n\tvar ok bool\n\tif p.IsVideo {\n\t\tvh := p.Header.(av.VideoPacketHeader)\n\t\tif vh.IsKeyFrame() && !vh.IsSeq() {\n\t\t\tok = true\n\t\t}\n\t}\n\tif ok || gopCache.start {\n\t\tgopCache.start = true\n\t\tgopCache.writeToArray(p, ok)\n\t}\n}\n\nfunc (gopCache *GopCache) sendTo(w av.WriteCloser) error {\n\tvar err error\n\tpos := (gopCache.nextindex + 1) % gopCache.count\n\tfor i := 0; i < gopCache.num; i++ {\n\t\tindex := (pos - gopCache.num + 1) + i\n\t\tif index < 0 {\n\t\t\tindex += gopCache.count\n\t\t}\n\t\tg := gopCache.gops[index]\n\t\terr = g.send(w)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (gopCache *GopCache) Send(w av.WriteCloser) error {\n\treturn gopCache.sendTo(w)\n}\n"
  },
  {
    "path": "protocol/rtmp/cache/special.go",
    "content": "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 \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tSetDataFrame string = \"@setDataFrame\"\n\tOnMetaData   string = \"onMetaData\"\n)\n\nvar setFrameFrame []byte\n\nfunc init() {\n\tb := bytes.NewBuffer(nil)\n\tencoder := &amf.Encoder{}\n\tif _, err := encoder.Encode(b, SetDataFrame, amf.AMF0); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tsetFrameFrame = b.Bytes()\n}\n\ntype SpecialCache struct {\n\tfull bool\n\tp    *av.Packet\n}\n\nfunc NewSpecialCache() *SpecialCache {\n\treturn &SpecialCache{}\n}\n\nfunc (specialCache *SpecialCache) Write(p *av.Packet) {\n\tspecialCache.p = p\n\tspecialCache.full = true\n}\n\nfunc (specialCache *SpecialCache) Send(w av.WriteCloser) error {\n\tif !specialCache.full {\n\t\treturn nil\n\t}\n\n\t// demux in hls will change p.Data, only send a copy here\n\tnewPacket := *specialCache.p\n\treturn w.Write(&newPacket)\n}\n"
  },
  {
    "path": "protocol/rtmp/core/chunk_stream.go",
    "content": "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/pool\"\n)\n\ntype ChunkStream struct {\n\tFormat    uint32\n\tCSID      uint32\n\tTimestamp uint32\n\tLength    uint32\n\tTypeID    uint32\n\tStreamID  uint32\n\ttimeDelta uint32\n\texted     bool\n\tindex     uint32\n\tremain    uint32\n\tgot       bool\n\ttmpFromat uint32\n\tData      []byte\n}\n\nfunc (chunkStream *ChunkStream) full() bool {\n\treturn chunkStream.got\n}\n\nfunc (chunkStream *ChunkStream) new(pool *pool.Pool) {\n\tchunkStream.got = false\n\tchunkStream.index = 0\n\tchunkStream.remain = chunkStream.Length\n\tchunkStream.Data = pool.Get(int(chunkStream.Length))\n}\n\nfunc (chunkStream *ChunkStream) writeHeader(w *ReadWriter) error {\n\t//Chunk Basic Header\n\th := chunkStream.Format << 6\n\tswitch {\n\tcase chunkStream.CSID < 64:\n\t\th |= chunkStream.CSID\n\t\tw.WriteUintBE(h, 1)\n\tcase chunkStream.CSID-64 < 256:\n\t\th |= 0\n\t\tw.WriteUintBE(h, 1)\n\t\tw.WriteUintLE(chunkStream.CSID-64, 1)\n\tcase chunkStream.CSID-64 < 65536:\n\t\th |= 1\n\t\tw.WriteUintBE(h, 1)\n\t\tw.WriteUintLE(chunkStream.CSID-64, 2)\n\t}\n\t//Chunk Message Header\n\tts := chunkStream.Timestamp\n\tif chunkStream.Format == 3 {\n\t\tgoto END\n\t}\n\tif chunkStream.Timestamp > 0xffffff {\n\t\tts = 0xffffff\n\t}\n\tw.WriteUintBE(ts, 3)\n\tif chunkStream.Format == 2 {\n\t\tgoto END\n\t}\n\tif chunkStream.Length > 0xffffff {\n\t\treturn fmt.Errorf(\"length=%d\", chunkStream.Length)\n\t}\n\tw.WriteUintBE(chunkStream.Length, 3)\n\tw.WriteUintBE(chunkStream.TypeID, 1)\n\tif chunkStream.Format == 1 {\n\t\tgoto END\n\t}\n\tw.WriteUintLE(chunkStream.StreamID, 4)\nEND:\n\t//Extended Timestamp\n\tif ts >= 0xffffff {\n\t\tw.WriteUintBE(chunkStream.Timestamp, 4)\n\t}\n\treturn w.WriteError()\n}\n\nfunc (chunkStream *ChunkStream) writeChunk(w *ReadWriter, chunkSize int) error {\n\tif chunkStream.TypeID == av.TAG_AUDIO {\n\t\tchunkStream.CSID = 4\n\t} else if chunkStream.TypeID == av.TAG_VIDEO ||\n\t\tchunkStream.TypeID == av.TAG_SCRIPTDATAAMF0 ||\n\t\tchunkStream.TypeID == av.TAG_SCRIPTDATAAMF3 {\n\t\tchunkStream.CSID = 6\n\t}\n\n\ttotalLen := uint32(0)\n\tnumChunks := (chunkStream.Length / uint32(chunkSize))\n\tfor i := uint32(0); i <= numChunks; i++ {\n\t\tif totalLen == chunkStream.Length {\n\t\t\tbreak\n\t\t}\n\t\tif i == 0 {\n\t\t\tchunkStream.Format = uint32(0)\n\t\t} else {\n\t\t\tchunkStream.Format = uint32(3)\n\t\t}\n\t\tif err := chunkStream.writeHeader(w); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinc := uint32(chunkSize)\n\t\tstart := uint32(i) * uint32(chunkSize)\n\t\tif uint32(len(chunkStream.Data))-start <= inc {\n\t\t\tinc = uint32(len(chunkStream.Data)) - start\n\t\t}\n\t\ttotalLen += inc\n\t\tend := start + inc\n\t\tbuf := chunkStream.Data[start:end]\n\t\tif _, err := w.Write(buf); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n\n}\n\nfunc (chunkStream *ChunkStream) readChunk(r *ReadWriter, chunkSize uint32, pool *pool.Pool) error {\n\tif chunkStream.remain != 0 && chunkStream.tmpFromat != 3 {\n\t\treturn fmt.Errorf(\"invalid remain = %d\", chunkStream.remain)\n\t}\n\tswitch chunkStream.CSID {\n\tcase 0:\n\t\tid, _ := r.ReadUintLE(1)\n\t\tchunkStream.CSID = id + 64\n\tcase 1:\n\t\tid, _ := r.ReadUintLE(2)\n\t\tchunkStream.CSID = id + 64\n\t}\n\n\tswitch chunkStream.tmpFromat {\n\tcase 0:\n\t\tchunkStream.Format = chunkStream.tmpFromat\n\t\tchunkStream.Timestamp, _ = r.ReadUintBE(3)\n\t\tchunkStream.Length, _ = r.ReadUintBE(3)\n\t\tchunkStream.TypeID, _ = r.ReadUintBE(1)\n\t\tchunkStream.StreamID, _ = r.ReadUintLE(4)\n\t\tif chunkStream.Timestamp == 0xffffff {\n\t\t\tchunkStream.Timestamp, _ = r.ReadUintBE(4)\n\t\t\tchunkStream.exted = true\n\t\t} else {\n\t\t\tchunkStream.exted = false\n\t\t}\n\t\tchunkStream.new(pool)\n\tcase 1:\n\t\tchunkStream.Format = chunkStream.tmpFromat\n\t\ttimeStamp, _ := r.ReadUintBE(3)\n\t\tchunkStream.Length, _ = r.ReadUintBE(3)\n\t\tchunkStream.TypeID, _ = r.ReadUintBE(1)\n\t\tif timeStamp == 0xffffff {\n\t\t\ttimeStamp, _ = r.ReadUintBE(4)\n\t\t\tchunkStream.exted = true\n\t\t} else {\n\t\t\tchunkStream.exted = false\n\t\t}\n\t\tchunkStream.timeDelta = timeStamp\n\t\tchunkStream.Timestamp += timeStamp\n\t\tchunkStream.new(pool)\n\tcase 2:\n\t\tchunkStream.Format = chunkStream.tmpFromat\n\t\ttimeStamp, _ := r.ReadUintBE(3)\n\t\tif timeStamp == 0xffffff {\n\t\t\ttimeStamp, _ = r.ReadUintBE(4)\n\t\t\tchunkStream.exted = true\n\t\t} else {\n\t\t\tchunkStream.exted = false\n\t\t}\n\t\tchunkStream.timeDelta = timeStamp\n\t\tchunkStream.Timestamp += timeStamp\n\t\tchunkStream.new(pool)\n\tcase 3:\n\t\tif chunkStream.remain == 0 {\n\t\t\tswitch chunkStream.Format {\n\t\t\tcase 0:\n\t\t\t\tif chunkStream.exted {\n\t\t\t\t\ttimestamp, _ := r.ReadUintBE(4)\n\t\t\t\t\tchunkStream.Timestamp = timestamp\n\t\t\t\t}\n\t\t\tcase 1, 2:\n\t\t\t\tvar timedet uint32\n\t\t\t\tif chunkStream.exted {\n\t\t\t\t\ttimedet, _ = r.ReadUintBE(4)\n\t\t\t\t} else {\n\t\t\t\t\ttimedet = chunkStream.timeDelta\n\t\t\t\t}\n\t\t\t\tchunkStream.Timestamp += timedet\n\t\t\t}\n\t\t\tchunkStream.new(pool)\n\t\t} else {\n\t\t\tif chunkStream.exted {\n\t\t\t\tb, err := r.Peek(4)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttmpts := binary.BigEndian.Uint32(b)\n\t\t\t\tif tmpts == chunkStream.Timestamp {\n\t\t\t\t\tr.Discard(4)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid format=%d\", chunkStream.Format)\n\t}\n\tsize := int(chunkStream.remain)\n\tif size > int(chunkSize) {\n\t\tsize = int(chunkSize)\n\t}\n\n\tbuf := chunkStream.Data[chunkStream.index : chunkStream.index+uint32(size)]\n\tif _, err := r.Read(buf); err != nil {\n\t\treturn err\n\t}\n\tchunkStream.index += uint32(size)\n\tchunkStream.remain -= uint32(size)\n\tif chunkStream.remain == 0 {\n\t\tchunkStream.got = true\n\t}\n\n\treturn r.readError\n}\n"
  },
  {
    "path": "protocol/rtmp/core/chunk_stream_test.go",
    "content": "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/assert\"\n)\n\nfunc TestChunkRead1(t *testing.T) {\n\tat := assert.New(t)\n\tdata := []byte{\n\t\t0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,\n\t}\n\tdata1 := make([]byte, 128)\n\tdata2 := make([]byte, 51)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data2...)\n\n\trw := NewReadWriter(bytes.NewBuffer(data), 1024)\n\tchunkinc := &ChunkStream{}\n\n\tfor {\n\t\th, _ := rw.ReadUintBE(1)\n\t\tchunkinc.tmpFromat = h >> 6\n\t\tchunkinc.CSID = h & 0x3f\n\t\tchunkinc.readChunk(rw, 128, pool.NewPool())\n\t\tif chunkinc.remain == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tat.Equal(int(chunkinc.Length), 307)\n\tat.Equal(int(chunkinc.TypeID), 9)\n\tat.Equal(int(chunkinc.StreamID), 1)\n\tat.Equal(len(chunkinc.Data), 307)\n\tat.Equal(int(chunkinc.remain), 0)\n\n\tdata = []byte{\n\t\t0x06, 0xff, 0xff, 0xff, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,\n\t}\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, []byte{0x00, 0x00, 0x00, 0x05}...)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data2...)\n\n\trw = NewReadWriter(bytes.NewBuffer(data), 1024)\n\tchunkinc = &ChunkStream{}\n\n\th, _ := rw.ReadUintBE(1)\n\tchunkinc.tmpFromat = h >> 6\n\tchunkinc.CSID = h & 0x3f\n\tchunkinc.readChunk(rw, 128, pool.NewPool())\n\n\th, _ = rw.ReadUintBE(1)\n\tchunkinc.tmpFromat = h >> 6\n\tchunkinc.CSID = h & 0x3f\n\tchunkinc.readChunk(rw, 128, pool.NewPool())\n\n\th, _ = rw.ReadUintBE(1)\n\tchunkinc.tmpFromat = h >> 6\n\tchunkinc.CSID = h & 0x3f\n\tchunkinc.readChunk(rw, 128, pool.NewPool())\n\n\tat.Equal(int(chunkinc.Length), 307)\n\tat.Equal(int(chunkinc.TypeID), 9)\n\tat.Equal(int(chunkinc.StreamID), 1)\n\tat.Equal(len(chunkinc.Data), 307)\n\tat.Equal(chunkinc.exted, true)\n\tat.Equal(int(chunkinc.Timestamp), 5)\n\tat.Equal(int(chunkinc.remain), 0)\n\n}\n\nfunc TestWriteChunk(t *testing.T) {\n\tat := assert.New(t)\n\tchunkinc := &ChunkStream{}\n\n\tchunkinc.Length = 307\n\tchunkinc.TypeID = 9\n\tchunkinc.CSID = 4\n\tchunkinc.Timestamp = 40\n\tchunkinc.Data = make([]byte, 307)\n\n\tbf := bytes.NewBuffer(nil)\n\tw := NewReadWriter(bf, 1024)\n\terr := chunkinc.writeChunk(w, 128)\n\tw.Flush()\n\tat.Equal(err, nil)\n\tat.Equal(len(bf.Bytes()), 321)\n}\n"
  },
  {
    "path": "protocol/rtmp/core/conn.go",
    "content": "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/gwuhaolin/livego/utils/pool\"\n)\n\nconst (\n\t_ = iota\n\tidSetChunkSize\n\tidAbortMessage\n\tidAck\n\tidUserControlMessages\n\tidWindowAckSize\n\tidSetPeerBandwidth\n)\n\ntype Conn struct {\n\tnet.Conn\n\tchunkSize           uint32\n\tremoteChunkSize     uint32\n\twindowAckSize       uint32\n\tremoteWindowAckSize uint32\n\treceived            uint32\n\tackReceived         uint32\n\trw                  *ReadWriter\n\tpool                *pool.Pool\n\tchunks              map[uint32]ChunkStream\n}\n\nfunc NewConn(c net.Conn, bufferSize int) *Conn {\n\treturn &Conn{\n\t\tConn:                c,\n\t\tchunkSize:           128,\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tpool:                pool.NewPool(),\n\t\trw:                  NewReadWriter(c, bufferSize),\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n}\n\nfunc (conn *Conn) Read(c *ChunkStream) error {\n\tfor {\n\t\th, _ := conn.rw.ReadUintBE(1)\n\t\t// if err != nil {\n\t\t// \tlog.Println(\"read from conn error: \", err)\n\t\t// \treturn err\n\t\t// }\n\t\tformat := h >> 6\n\t\tcsid := h & 0x3f\n\t\tcs, ok := conn.chunks[csid]\n\t\tif !ok {\n\t\t\tcs = ChunkStream{}\n\t\t\tconn.chunks[csid] = cs\n\t\t}\n\t\tcs.tmpFromat = format\n\t\tcs.CSID = csid\n\t\terr := cs.readChunk(conn.rw, conn.remoteChunkSize, conn.pool)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconn.chunks[csid] = cs\n\t\tif cs.full() {\n\t\t\t*c = cs\n\t\t\tbreak\n\t\t}\n\t}\n\n\tconn.handleControlMsg(c)\n\n\tconn.ack(c.Length)\n\n\treturn nil\n}\n\nfunc (conn *Conn) Write(c *ChunkStream) error {\n\tif c.TypeID == idSetChunkSize {\n\t\tconn.chunkSize = binary.BigEndian.Uint32(c.Data)\n\t}\n\treturn c.writeChunk(conn.rw, int(conn.chunkSize))\n}\n\nfunc (conn *Conn) Flush() error {\n\treturn conn.rw.Flush()\n}\n\nfunc (conn *Conn) Close() error {\n\treturn conn.Conn.Close()\n}\n\nfunc (conn *Conn) RemoteAddr() net.Addr {\n\treturn conn.Conn.RemoteAddr()\n}\n\nfunc (conn *Conn) LocalAddr() net.Addr {\n\treturn conn.Conn.LocalAddr()\n}\n\nfunc (conn *Conn) SetDeadline(t time.Time) error {\n\treturn conn.Conn.SetDeadline(t)\n}\n\nfunc (conn *Conn) NewAck(size uint32) ChunkStream {\n\treturn initControlMsg(idAck, 4, size)\n}\n\nfunc (conn *Conn) NewSetChunkSize(size uint32) ChunkStream {\n\treturn initControlMsg(idSetChunkSize, 4, size)\n}\n\nfunc (conn *Conn) NewWindowAckSize(size uint32) ChunkStream {\n\treturn initControlMsg(idWindowAckSize, 4, size)\n}\n\nfunc (conn *Conn) NewSetPeerBandwidth(size uint32) ChunkStream {\n\tret := initControlMsg(idSetPeerBandwidth, 5, size)\n\tret.Data[4] = 2\n\treturn ret\n}\n\nfunc (conn *Conn) handleControlMsg(c *ChunkStream) {\n\tif c.TypeID == idSetChunkSize {\n\t\tconn.remoteChunkSize = binary.BigEndian.Uint32(c.Data)\n\t} else if c.TypeID == idWindowAckSize {\n\t\tconn.remoteWindowAckSize = binary.BigEndian.Uint32(c.Data)\n\t}\n}\n\nfunc (conn *Conn) ack(size uint32) {\n\tconn.received += uint32(size)\n\tconn.ackReceived += uint32(size)\n\tif conn.received >= 0xf0000000 {\n\t\tconn.received = 0\n\t}\n\tif conn.ackReceived >= conn.remoteWindowAckSize {\n\t\tcs := conn.NewAck(conn.ackReceived)\n\t\tcs.writeChunk(conn.rw, int(conn.chunkSize))\n\t\tconn.ackReceived = 0\n\t}\n}\n\nfunc initControlMsg(id, size, value uint32) ChunkStream {\n\tret := ChunkStream{\n\t\tFormat:   0,\n\t\tCSID:     2,\n\t\tTypeID:   id,\n\t\tStreamID: 0,\n\t\tLength:   size,\n\t\tData:     make([]byte, size),\n\t}\n\tpio.PutU32BE(ret.Data[:size], value)\n\treturn ret\n}\n\nconst (\n\tstreamBegin      uint32 = 0\n\tstreamEOF        uint32 = 1\n\tstreamDry        uint32 = 2\n\tsetBufferLen     uint32 = 3\n\tstreamIsRecorded uint32 = 4\n\tpingRequest      uint32 = 6\n\tpingResponse     uint32 = 7\n)\n\n/*\n   +------------------------------+-------------------------\n   |     Event Type ( 2- bytes )  | Event Data\n   +------------------------------+-------------------------\n   Pay load for the ‘User Control Message’.\n*/\nfunc (conn *Conn) userControlMsg(eventType, buflen uint32) ChunkStream {\n\tvar ret ChunkStream\n\tbuflen += 2\n\tret = ChunkStream{\n\t\tFormat:   0,\n\t\tCSID:     2,\n\t\tTypeID:   4,\n\t\tStreamID: 1,\n\t\tLength:   buflen,\n\t\tData:     make([]byte, buflen),\n\t}\n\tret.Data[0] = byte(eventType >> 8 & 0xff)\n\tret.Data[1] = byte(eventType & 0xff)\n\treturn ret\n}\n\nfunc (conn *Conn) SetBegin() {\n\tret := conn.userControlMsg(streamBegin, 4)\n\tfor i := 0; i < 4; i++ {\n\t\tret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff)\n\t}\n\tconn.Write(&ret)\n}\n\nfunc (conn *Conn) SetRecorded() {\n\tret := conn.userControlMsg(streamIsRecorded, 4)\n\tfor i := 0; i < 4; i++ {\n\t\tret.Data[2+i] = byte(1 >> uint32((3-i)*8) & 0xff)\n\t}\n\tconn.Write(&ret)\n}\n"
  },
  {
    "path": "protocol/rtmp/core/conn_client.go",
    "content": "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\"strings\"\n\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t\"github.com/gwuhaolin/livego/protocol/amf\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\trespResult     = \"_result\"\n\trespError      = \"_error\"\n\tonStatus       = \"onStatus\"\n\tpublishStart   = \"NetStream.Publish.Start\"\n\tplayStart      = \"NetStream.Play.Start\"\n\tconnectSuccess = \"NetConnection.Connect.Success\"\n\tonBWDone       = \"onBWDone\"\n)\n\nvar (\n\tErrFail = fmt.Errorf(\"response err\")\n)\n\ntype ConnClient struct {\n\tdone       bool\n\ttransID    int\n\turl        string\n\ttcurl      string\n\tapp        string\n\ttitle      string\n\tcurcmdName string\n\tstreamid   uint32\n\tisRTMPS    bool\n\tconn       *Conn\n\tencoder    *amf.Encoder\n\tdecoder    *amf.Decoder\n\tbytesw     *bytes.Buffer\n}\n\nfunc NewConnClient() *ConnClient {\n\treturn &ConnClient{\n\t\ttransID: 1,\n\t\tbytesw:  bytes.NewBuffer(nil),\n\t\tencoder: &amf.Encoder{},\n\t\tdecoder: &amf.Decoder{},\n\t}\n}\n\nfunc (connClient *ConnClient) DecodeBatch(r io.Reader, ver amf.Version) (ret []interface{}, err error) {\n\tvs, err := connClient.decoder.DecodeBatch(r, ver)\n\treturn vs, err\n}\n\nfunc (connClient *ConnClient) readRespMsg() error {\n\tvar err error\n\tvar rc ChunkStream\n\tfor {\n\t\tif err = connClient.conn.Read(&rc); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t\tswitch rc.TypeID {\n\t\tcase 20, 17:\n\t\t\tr := bytes.NewReader(rc.Data)\n\t\t\tvs, _ := connClient.decoder.DecodeBatch(r, amf.AMF0)\n\n\t\t\tlog.Debugf(\"readRespMsg: vs=%v\", vs)\n\t\t\tfor k, v := range vs {\n\t\t\t\tswitch v.(type) {\n\t\t\t\tcase string:\n\t\t\t\t\tswitch connClient.curcmdName {\n\t\t\t\t\tcase cmdConnect, cmdCreateStream:\n\t\t\t\t\t\tif v.(string) != respResult {\n\t\t\t\t\t\t\treturn fmt.Errorf(v.(string))\n\t\t\t\t\t\t}\n\n\t\t\t\t\tcase cmdPublish:\n\t\t\t\t\t\tif v.(string) != onStatus {\n\t\t\t\t\t\t\treturn ErrFail\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase float64:\n\t\t\t\t\tswitch connClient.curcmdName {\n\t\t\t\t\tcase cmdConnect, cmdCreateStream:\n\t\t\t\t\t\tid := int(v.(float64))\n\n\t\t\t\t\t\tif k == 1 {\n\t\t\t\t\t\t\tif id != connClient.transID {\n\t\t\t\t\t\t\t\treturn ErrFail\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if k == 3 {\n\t\t\t\t\t\t\tconnClient.streamid = uint32(id)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase cmdPublish:\n\t\t\t\t\t\tif int(v.(float64)) != 0 {\n\t\t\t\t\t\t\treturn ErrFail\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase amf.Object:\n\t\t\t\t\tobjmap := v.(amf.Object)\n\t\t\t\t\tswitch connClient.curcmdName {\n\t\t\t\t\tcase cmdConnect:\n\t\t\t\t\t\tcode, ok := objmap[\"code\"]\n\t\t\t\t\t\tif ok && code.(string) != connectSuccess {\n\t\t\t\t\t\t\treturn ErrFail\n\t\t\t\t\t\t}\n\t\t\t\t\tcase cmdPublish:\n\t\t\t\t\t\tcode, ok := objmap[\"code\"]\n\t\t\t\t\t\tif ok && code.(string) != publishStart {\n\t\t\t\t\t\t\treturn ErrFail\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (connClient *ConnClient) writeMsg(args ...interface{}) error {\n\tconnClient.bytesw.Reset()\n\tfor _, v := range args {\n\t\tif _, err := connClient.encoder.Encode(connClient.bytesw, v, amf.AMF0); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tmsg := connClient.bytesw.Bytes()\n\tc := ChunkStream{\n\t\tFormat:    0,\n\t\tCSID:      3,\n\t\tTimestamp: 0,\n\t\tTypeID:    20,\n\t\tStreamID:  connClient.streamid,\n\t\tLength:    uint32(len(msg)),\n\t\tData:      msg,\n\t}\n\tconnClient.conn.Write(&c)\n\treturn connClient.conn.Flush()\n}\n\nfunc (connClient *ConnClient) writeConnectMsg() error {\n\tevent := make(amf.Object)\n\tevent[\"app\"] = connClient.app\n\tevent[\"type\"] = \"nonprivate\"\n\tevent[\"flashVer\"] = \"FMS.3.1\"\n\tevent[\"tcUrl\"] = connClient.tcurl\n\tconnClient.curcmdName = cmdConnect\n\n\tlog.Debugf(\"writeConnectMsg: connClient.transID=%d, event=%v\", connClient.transID, event)\n\tif err := connClient.writeMsg(cmdConnect, connClient.transID, event); err != nil {\n\t\treturn err\n\t}\n\treturn connClient.readRespMsg()\n}\n\nfunc (connClient *ConnClient) writeCreateStreamMsg() error {\n\tconnClient.transID++\n\tconnClient.curcmdName = cmdCreateStream\n\n\tlog.Debugf(\"writeCreateStreamMsg: connClient.transID=%d\", connClient.transID)\n\tif err := connClient.writeMsg(cmdCreateStream, connClient.transID, nil); err != nil {\n\t\treturn err\n\t}\n\n\tfor {\n\t\terr := connClient.readRespMsg()\n\t\tif err == nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err == ErrFail {\n\t\t\tlog.Debugf(\"writeCreateStreamMsg readRespMsg err=%v\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n}\n\nfunc (connClient *ConnClient) writePublishMsg() error {\n\tconnClient.transID++\n\tconnClient.curcmdName = cmdPublish\n\tif err := connClient.writeMsg(cmdPublish, connClient.transID, nil, connClient.title, publishLive); err != nil {\n\t\treturn err\n\t}\n\treturn connClient.readRespMsg()\n}\n\nfunc (connClient *ConnClient) writePlayMsg() error {\n\tconnClient.transID++\n\tconnClient.curcmdName = cmdPlay\n\tlog.Debugf(\"writePlayMsg: connClient.transID=%d, cmdPlay=%v, connClient.title=%v\",\n\t\tconnClient.transID, cmdPlay, connClient.title)\n\n\tif err := connClient.writeMsg(cmdPlay, 0, nil, connClient.title); err != nil {\n\t\treturn err\n\t}\n\treturn connClient.readRespMsg()\n}\n\nfunc (connClient *ConnClient) Start(url string, method string) error {\n\tu, err := neturl.Parse(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconnClient.url = url\n\tpath := strings.TrimLeft(u.Path, \"/\")\n\tps := strings.SplitN(path, \"/\", 2)\n\tif len(ps) != 2 {\n\t\treturn fmt.Errorf(\"u path err: %s\", path)\n\t}\n\tconnClient.app = ps[0]\n\tconnClient.title = ps[1]\n\tif u.RawQuery != \"\" {\n\t\tconnClient.title += \"?\" + u.RawQuery\n\t}\n\tconnClient.isRTMPS = strings.HasPrefix(url, \"rtmps://\")\n\n\tvar port string\n\tif connClient.isRTMPS {\n\t\tconnClient.tcurl = \"rtmps://\" + u.Host + \"/\" + connClient.app\n\t\tport = \":443\"\n\t} else {\n\t\tconnClient.tcurl = \"rtmp://\" + u.Host + \"/\" + connClient.app\n\t\tport = \":1935\"\n\t}\n\n\thost := u.Host\n\tlocalIP := \":0\"\n\tvar remoteIP string\n\tif strings.Index(host, \":\") != -1 {\n\t\thost, port, err = net.SplitHostPort(host)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tport = \":\" + port\n\t}\n\tips, err := net.LookupIP(host)\n\tlog.Debugf(\"ips: %v, host: %v\", ips, host)\n\tif err != nil {\n\t\tlog.Warning(err)\n\t\treturn err\n\t}\n\tremoteIP = ips[rand.Intn(len(ips))].String()\n\tif strings.Index(remoteIP, \":\") == -1 {\n\t\tremoteIP += port\n\t}\n\n\tlocal, err := net.ResolveTCPAddr(\"tcp\", localIP)\n\tif err != nil {\n\t\tlog.Warning(err)\n\t\treturn err\n\t}\n\tlog.Debug(\"remoteIP: \", remoteIP)\n\tremote, err := net.ResolveTCPAddr(\"tcp\", remoteIP)\n\tif err != nil {\n\t\tlog.Warning(err)\n\t\treturn err\n\t}\n\n\tvar conn net.Conn\n\tif connClient.isRTMPS {\n\t\tvar config tls.Config\n\t\tif configure.Config.GetBool(\"enable_tls_verify\") {\n\t\t\troots, err := x509.SystemCertPool()\n\t\t\tif err != nil {\n\t\t\t\tlog.Warning(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconfig.RootCAs = roots\n\t\t} else {\n\t\t\tconfig.InsecureSkipVerify = true\n\t\t}\n\n\t\tconn, err = tls.Dial(\"tcp\", remoteIP, &config)\n\t\tif err != nil {\n\t\t\tlog.Warning(err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tconn, err = net.DialTCP(\"tcp\", local, remote)\n\t\tif err != nil {\n\t\t\tlog.Warning(err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlog.Debug(\"connection:\", \"local:\", conn.LocalAddr(), \"remote:\", conn.RemoteAddr())\n\n\tconnClient.conn = NewConn(conn, 4*1024)\n\n\tlog.Debug(\"HandshakeClient....\")\n\tif err := connClient.conn.HandshakeClient(); err != nil {\n\t\treturn err\n\t}\n\n\tlog.Debug(\"writeConnectMsg....\")\n\tif err := connClient.writeConnectMsg(); err != nil {\n\t\treturn err\n\t}\n\tlog.Debug(\"writeCreateStreamMsg....\")\n\tif err := connClient.writeCreateStreamMsg(); err != nil {\n\t\tlog.Debug(\"writeCreateStreamMsg error\", err)\n\t\treturn err\n\t}\n\n\tlog.Debug(\"method control:\", method, av.PUBLISH, av.PLAY)\n\tif method == av.PUBLISH {\n\t\tif err := connClient.writePublishMsg(); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if method == av.PLAY {\n\t\tif err := connClient.writePlayMsg(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (connClient *ConnClient) Write(c ChunkStream) error {\n\tif c.TypeID == av.TAG_SCRIPTDATAAMF0 ||\n\t\tc.TypeID == av.TAG_SCRIPTDATAAMF3 {\n\t\tvar err error\n\t\tif c.Data, err = amf.MetaDataReform(c.Data, amf.ADD); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Length = uint32(len(c.Data))\n\t}\n\treturn connClient.conn.Write(&c)\n}\n\nfunc (connClient *ConnClient) Flush() error {\n\treturn connClient.conn.Flush()\n}\n\nfunc (connClient *ConnClient) Read(c *ChunkStream) (err error) {\n\treturn connClient.conn.Read(c)\n}\n\nfunc (connClient *ConnClient) GetInfo() (app string, name string, url string) {\n\tapp = connClient.app\n\tname = connClient.title\n\turl = connClient.url\n\treturn\n}\n\nfunc (connClient *ConnClient) GetStreamId() uint32 {\n\treturn connClient.streamid\n}\n\nfunc (connClient *ConnClient) Close(err error) {\n\tconnClient.conn.Close()\n}\n"
  },
  {
    "path": "protocol/rtmp/core/conn_server.go",
    "content": "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/amf\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tpublishLive   = \"live\"\n\tpublishRecord = \"record\"\n\tpublishAppend = \"append\"\n)\n\nvar (\n\tErrReq = fmt.Errorf(\"req error\")\n)\n\nvar (\n\tcmdConnect       = \"connect\"\n\tcmdFcpublish     = \"FCPublish\"\n\tcmdReleaseStream = \"releaseStream\"\n\tcmdCreateStream  = \"createStream\"\n\tcmdPublish       = \"publish\"\n\tcmdFCUnpublish   = \"FCUnpublish\"\n\tcmdDeleteStream  = \"deleteStream\"\n\tcmdPlay          = \"play\"\n)\n\ntype ConnectInfo struct {\n\tApp            string `amf:\"app\" json:\"app\"`\n\tFlashver       string `amf:\"flashVer\" json:\"flashVer\"`\n\tSwfUrl         string `amf:\"swfUrl\" json:\"swfUrl\"`\n\tTcUrl          string `amf:\"tcUrl\" json:\"tcUrl\"`\n\tFpad           bool   `amf:\"fpad\" json:\"fpad\"`\n\tAudioCodecs    int    `amf:\"audioCodecs\" json:\"audioCodecs\"`\n\tVideoCodecs    int    `amf:\"videoCodecs\" json:\"videoCodecs\"`\n\tVideoFunction  int    `amf:\"videoFunction\" json:\"videoFunction\"`\n\tPageUrl        string `amf:\"pageUrl\" json:\"pageUrl\"`\n\tObjectEncoding int    `amf:\"objectEncoding\" json:\"objectEncoding\"`\n}\n\ntype ConnectResp struct {\n\tFMSVer       string `amf:\"fmsVer\"`\n\tCapabilities int    `amf:\"capabilities\"`\n}\n\ntype ConnectEvent struct {\n\tLevel          string `amf:\"level\"`\n\tCode           string `amf:\"code\"`\n\tDescription    string `amf:\"description\"`\n\tObjectEncoding int    `amf:\"objectEncoding\"`\n}\n\ntype PublishInfo struct {\n\tName string\n\tType string\n}\n\ntype ConnServer struct {\n\tdone          bool\n\tstreamID      int\n\tisPublisher   bool\n\tconn          *Conn\n\ttransactionID int\n\tConnInfo      ConnectInfo\n\tPublishInfo   PublishInfo\n\tdecoder       *amf.Decoder\n\tencoder       *amf.Encoder\n\tbytesw        *bytes.Buffer\n}\n\nfunc NewConnServer(conn *Conn) *ConnServer {\n\treturn &ConnServer{\n\t\tconn:     conn,\n\t\tstreamID: 1,\n\t\tbytesw:   bytes.NewBuffer(nil),\n\t\tdecoder:  &amf.Decoder{},\n\t\tencoder:  &amf.Encoder{},\n\t}\n}\n\nfunc (connServer *ConnServer) writeMsg(csid, streamID uint32, args ...interface{}) error {\n\tconnServer.bytesw.Reset()\n\tfor _, v := range args {\n\t\tif _, err := connServer.encoder.Encode(connServer.bytesw, v, amf.AMF0); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tmsg := connServer.bytesw.Bytes()\n\tc := ChunkStream{\n\t\tFormat:    0,\n\t\tCSID:      csid,\n\t\tTimestamp: 0,\n\t\tTypeID:    20,\n\t\tStreamID:  streamID,\n\t\tLength:    uint32(len(msg)),\n\t\tData:      msg,\n\t}\n\tconnServer.conn.Write(&c)\n\treturn connServer.conn.Flush()\n}\n\nfunc (connServer *ConnServer) connect(vs []interface{}) error {\n\tfor _, v := range vs {\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\tcase float64:\n\t\t\tid := int(v.(float64))\n\t\t\tif id != 1 {\n\t\t\t\treturn ErrReq\n\t\t\t}\n\t\t\tconnServer.transactionID = id\n\t\tcase amf.Object:\n\t\t\tobimap := v.(amf.Object)\n\t\t\tif app, ok := obimap[\"app\"]; ok {\n\t\t\t\tconnServer.ConnInfo.App = app.(string)\n\t\t\t}\n\t\t\tif flashVer, ok := obimap[\"flashVer\"]; ok {\n\t\t\t\tconnServer.ConnInfo.Flashver = flashVer.(string)\n\t\t\t}\n\t\t\tif tcurl, ok := obimap[\"tcUrl\"]; ok {\n\t\t\t\tconnServer.ConnInfo.TcUrl = tcurl.(string)\n\t\t\t}\n\t\t\tif encoding, ok := obimap[\"objectEncoding\"]; ok {\n\t\t\t\tconnServer.ConnInfo.ObjectEncoding = int(encoding.(float64))\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (connServer *ConnServer) releaseStream(vs []interface{}) error {\n\treturn nil\n}\n\nfunc (connServer *ConnServer) fcPublish(vs []interface{}) error {\n\treturn nil\n}\n\nfunc (connServer *ConnServer) connectResp(cur *ChunkStream) error {\n\tc := connServer.conn.NewWindowAckSize(2500000)\n\tconnServer.conn.Write(&c)\n\tc = connServer.conn.NewSetPeerBandwidth(2500000)\n\tconnServer.conn.Write(&c)\n\tc = connServer.conn.NewSetChunkSize(uint32(1024))\n\tconnServer.conn.Write(&c)\n\n\tresp := make(amf.Object)\n\tresp[\"fmsVer\"] = \"FMS/3,0,1,123\"\n\tresp[\"capabilities\"] = 31\n\n\tevent := make(amf.Object)\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetConnection.Connect.Success\"\n\tevent[\"description\"] = \"Connection succeeded.\"\n\tevent[\"objectEncoding\"] = connServer.ConnInfo.ObjectEncoding\n\treturn connServer.writeMsg(cur.CSID, cur.StreamID, \"_result\", connServer.transactionID, resp, event)\n}\n\nfunc (connServer *ConnServer) createStream(vs []interface{}) error {\n\tfor _, v := range vs {\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\tcase float64:\n\t\t\tconnServer.transactionID = int(v.(float64))\n\t\tcase amf.Object:\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (connServer *ConnServer) createStreamResp(cur *ChunkStream) error {\n\treturn connServer.writeMsg(cur.CSID, cur.StreamID, \"_result\", connServer.transactionID, nil, connServer.streamID)\n}\n\nfunc (connServer *ConnServer) publishOrPlay(vs []interface{}) error {\n\tfor k, v := range vs {\n\t\tswitch v.(type) {\n\t\tcase string:\n\t\t\tif k == 2 {\n\t\t\t\tconnServer.PublishInfo.Name = v.(string)\n\t\t\t} else if k == 3 {\n\t\t\t\tconnServer.PublishInfo.Type = v.(string)\n\t\t\t}\n\t\tcase float64:\n\t\t\tid := int(v.(float64))\n\t\t\tconnServer.transactionID = id\n\t\tcase amf.Object:\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (connServer *ConnServer) publishResp(cur *ChunkStream) error {\n\tevent := make(amf.Object)\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetStream.Publish.Start\"\n\tevent[\"description\"] = \"Start publishing.\"\n\treturn connServer.writeMsg(cur.CSID, cur.StreamID, \"onStatus\", 0, nil, event)\n}\n\nfunc (connServer *ConnServer) playResp(cur *ChunkStream) error {\n\tconnServer.conn.SetRecorded()\n\tconnServer.conn.SetBegin()\n\n\tevent := make(amf.Object)\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetStream.Play.Reset\"\n\tevent[\"description\"] = \"Playing and resetting stream.\"\n\tif err := connServer.writeMsg(cur.CSID, cur.StreamID, \"onStatus\", 0, nil, event); err != nil {\n\t\treturn err\n\t}\n\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetStream.Play.Start\"\n\tevent[\"description\"] = \"Started playing stream.\"\n\tif err := connServer.writeMsg(cur.CSID, cur.StreamID, \"onStatus\", 0, nil, event); err != nil {\n\t\treturn err\n\t}\n\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetStream.Data.Start\"\n\tevent[\"description\"] = \"Started playing stream.\"\n\tif err := connServer.writeMsg(cur.CSID, cur.StreamID, \"onStatus\", 0, nil, event); err != nil {\n\t\treturn err\n\t}\n\n\tevent[\"level\"] = \"status\"\n\tevent[\"code\"] = \"NetStream.Play.PublishNotify\"\n\tevent[\"description\"] = \"Started playing notify.\"\n\tif err := connServer.writeMsg(cur.CSID, cur.StreamID, \"onStatus\", 0, nil, event); err != nil {\n\t\treturn err\n\t}\n\treturn connServer.conn.Flush()\n}\n\nfunc (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error {\n\tamfType := amf.AMF0\n\tif c.TypeID == 17 {\n\t\tc.Data = c.Data[1:]\n\t}\n\tr := bytes.NewReader(c.Data)\n\tvs, err := connServer.decoder.DecodeBatch(r, amf.Version(amfType))\n\tif err != nil && err != io.EOF {\n\t\treturn err\n\t}\n\t// log.Debugf(\"rtmp req: %#v\", vs)\n\tswitch vs[0].(type) {\n\tcase string:\n\t\tswitch vs[0].(string) {\n\t\tcase cmdConnect:\n\t\t\tif err = connServer.connect(vs[1:]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = connServer.connectResp(c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase cmdCreateStream:\n\t\t\tif err = connServer.createStream(vs[1:]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = connServer.createStreamResp(c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase cmdPublish:\n\t\t\tif err = connServer.publishOrPlay(vs[1:]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = connServer.publishResp(c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconnServer.done = true\n\t\t\tconnServer.isPublisher = true\n\t\t\tlog.Debug(\"handle publish req done\")\n\t\tcase cmdPlay:\n\t\t\tif err = connServer.publishOrPlay(vs[1:]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = connServer.playResp(c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconnServer.done = true\n\t\t\tconnServer.isPublisher = false\n\t\t\tlog.Debug(\"handle play req done\")\n\t\tcase cmdFcpublish:\n\t\t\tconnServer.fcPublish(vs)\n\t\tcase cmdReleaseStream:\n\t\t\tconnServer.releaseStream(vs)\n\t\tcase cmdFCUnpublish:\n\t\tcase cmdDeleteStream:\n\t\tdefault:\n\t\t\tlog.Debug(\"no support command=\", vs[0].(string))\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (connServer *ConnServer) ReadMsg() error {\n\tvar c ChunkStream\n\tfor {\n\t\tif err := connServer.conn.Read(&c); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch c.TypeID {\n\t\tcase 20, 17:\n\t\t\tif err := connServer.handleCmdMsg(&c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif connServer.done {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (connServer *ConnServer) IsPublisher() bool {\n\treturn connServer.isPublisher\n}\n\nfunc (connServer *ConnServer) Write(c ChunkStream) error {\n\tif c.TypeID == av.TAG_SCRIPTDATAAMF0 ||\n\t\tc.TypeID == av.TAG_SCRIPTDATAAMF3 {\n\t\tvar err error\n\t\tif c.Data, err = amf.MetaDataReform(c.Data, amf.DEL); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Length = uint32(len(c.Data))\n\t}\n\treturn connServer.conn.Write(&c)\n}\n\nfunc (connServer *ConnServer) Flush() error {\n\treturn connServer.conn.Flush()\n}\n\nfunc (connServer *ConnServer) Read(c *ChunkStream) (err error) {\n\treturn connServer.conn.Read(c)\n}\n\nfunc (connServer *ConnServer) GetInfo() (app string, name string, url string) {\n\tapp = connServer.ConnInfo.App\n\tname = connServer.PublishInfo.Name\n\turl = connServer.ConnInfo.TcUrl + \"/\" + connServer.PublishInfo.Name\n\treturn\n}\n\nfunc (connServer *ConnServer) Close(err error) {\n\tconnServer.conn.Close()\n}\n"
  },
  {
    "path": "protocol/rtmp/core/conn_test.go",
    "content": "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/testify/assert\"\n)\n\nfunc TestConnReadNormal(t *testing.T) {\n\tat := assert.New(t)\n\tdata := []byte{\n\t\t0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,\n\t}\n\tdata1 := make([]byte, 128)\n\tdata2 := make([]byte, 51)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data2...)\n\tconn := &Conn{\n\t\tpool:                pool.NewPool(),\n\t\trw:                  NewReadWriter(bytes.NewBuffer(data), 1024),\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n\tvar c ChunkStream\n\terr := conn.Read(&c)\n\tat.Equal(err, nil)\n\tat.Equal(int(c.CSID), 6)\n\tat.Equal(int(c.Length), 307)\n\tat.Equal(int(c.TypeID), 9)\n}\n\n//交叉读音视频数据\nfunc TestConnCrossReading(t *testing.T) {\n\tat := assert.New(t)\n\tdata1 := make([]byte, 128)\n\tdata2 := make([]byte, 51)\n\n\tvideoData := []byte{\n\t\t0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,\n\t}\n\taudioData := []byte{\n\t\t0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x08, 0x01, 0x00, 0x00, 0x00,\n\t}\n\t//video 1\n\tvideoData = append(videoData, data1...)\n\t//video 2\n\tvideoData = append(videoData, 0xc6)\n\tvideoData = append(videoData, data1...)\n\t//audio 1\n\tvideoData = append(videoData, audioData...)\n\tvideoData = append(videoData, data1...)\n\t//audio 2\n\tvideoData = append(videoData, 0xc4)\n\tvideoData = append(videoData, data1...)\n\t//video 3\n\tvideoData = append(videoData, 0xc6)\n\tvideoData = append(videoData, data2...)\n\t//audio 3\n\tvideoData = append(videoData, 0xc4)\n\tvideoData = append(videoData, data2...)\n\n\tconn := &Conn{\n\t\tpool:                pool.NewPool(),\n\t\trw:                  NewReadWriter(bytes.NewBuffer(videoData), 1024),\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n\tvar c ChunkStream\n\t//video 1\n\terr := conn.Read(&c)\n\tat.Equal(err, nil)\n\tat.Equal(int(c.TypeID), 9)\n\tat.Equal(len(c.Data), 307)\n\n\t//audio2\n\terr = conn.Read(&c)\n\tat.Equal(err, nil)\n\tat.Equal(int(c.TypeID), 8)\n\tat.Equal(len(c.Data), 307)\n\n\terr = conn.Read(&c)\n\tat.Equal(err, io.EOF)\n}\n\nfunc TestSetChunksizeForWrite(t *testing.T) {\n\tat := assert.New(t)\n\tchunk := ChunkStream{\n\t\tFormat:    0,\n\t\tCSID:      2,\n\t\tTimestamp: 0,\n\t\tLength:    4,\n\t\tStreamID:  1,\n\t\tTypeID:    idSetChunkSize,\n\t\tData:      []byte{0x00, 0x00, 0x00, 0x96},\n\t}\n\tbuf := bytes.NewBuffer(nil)\n\trw := NewReadWriter(buf, 1024)\n\tconn := &Conn{\n\t\tpool:                pool.NewPool(),\n\t\trw:                  rw,\n\t\tchunkSize:           128,\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n\n\taudio := ChunkStream{\n\t\tFormat:    0,\n\t\tCSID:      4,\n\t\tTimestamp: 40,\n\t\tLength:    133,\n\t\tStreamID:  1,\n\t\tTypeID:    0x8,\n\t}\n\taudio.Data = make([]byte, 133)\n\taudio.Data = audio.Data[:133]\n\taudio.Data[0] = 0xff\n\taudio.Data[128] = 0xff\n\terr := conn.Write(&audio)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\tat.Equal(len(buf.Bytes()), 146)\n\n\tbuf.Reset()\n\terr = conn.Write(&chunk)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\n\tbuf.Reset()\n\terr = conn.Write(&audio)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\tat.Equal(len(buf.Bytes()), 145)\n}\n\nfunc TestSetChunksize(t *testing.T) {\n\tat := assert.New(t)\n\tdata := []byte{\n\t\t0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x09, 0x01, 0x00, 0x00, 0x00,\n\t}\n\tdata1 := make([]byte, 128)\n\tdata2 := make([]byte, 51)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data2...)\n\trw := NewReadWriter(bytes.NewBuffer(data), 1024)\n\tconn := &Conn{\n\t\tpool:                pool.NewPool(),\n\t\trw:                  rw,\n\t\tchunkSize:           128,\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n\n\tvar c ChunkStream\n\terr := conn.Read(&c)\n\tat.Equal(err, nil)\n\tat.Equal(int(c.TypeID), 9)\n\tat.Equal(int(c.CSID), 6)\n\tat.Equal(int(c.StreamID), 1)\n\tat.Equal(len(c.Data), 307)\n\n\t//设置chunksize\n\tchunkBuf := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x96}\n\tconn.rw = NewReadWriter(bytes.NewBuffer(chunkBuf), 1024)\n\terr = conn.Read(&c)\n\tat.Equal(err, nil)\n\n\tdata = data[:12]\n\tdata[7] = 0x8\n\tdata1 = make([]byte, 150)\n\tdata2 = make([]byte, 7)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data1...)\n\tdata = append(data, 0xc6)\n\tdata = append(data, data2...)\n\n\tconn.rw = NewReadWriter(bytes.NewBuffer(data), 1024)\n\terr = conn.Read(&c)\n\tat.Equal(err, nil)\n\tat.Equal(len(c.Data), 307)\n\n\terr = conn.Read(&c)\n\tat.Equal(err, io.EOF)\n}\n\nfunc TestConnWrite(t *testing.T) {\n\tat := assert.New(t)\n\twr := bytes.NewBuffer(nil)\n\treadWriter := NewReadWriter(wr, 128)\n\tconn := &Conn{\n\t\tpool:                pool.NewPool(),\n\t\trw:                  readWriter,\n\t\tchunkSize:           128,\n\t\tremoteChunkSize:     128,\n\t\twindowAckSize:       2500000,\n\t\tremoteWindowAckSize: 2500000,\n\t\tchunks:              make(map[uint32]ChunkStream),\n\t}\n\n\tc1 := ChunkStream{\n\t\tLength:    3,\n\t\tTypeID:    8,\n\t\tCSID:      3,\n\t\tTimestamp: 40,\n\t\tData:      []byte{0x01, 0x02, 0x03},\n\t}\n\terr := conn.Write(&c1)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\tat.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x28, 0x0, 0x0, 0x3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3})\n\n\t//for type 1\n\twr.Reset()\n\tc1 = ChunkStream{\n\t\tLength:    4,\n\t\tTypeID:    8,\n\t\tCSID:      3,\n\t\tTimestamp: 80,\n\t\tData:      []byte{0x01, 0x02, 0x03, 0x4},\n\t}\n\terr = conn.Write(&c1)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\tat.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0x50, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4})\n\n\t//for type 2\n\twr.Reset()\n\tc1.Timestamp = 160\n\terr = conn.Write(&c1)\n\tat.Equal(err, nil)\n\tconn.Flush()\n\tat.Equal(wr.Bytes(), []byte{0x4, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4})\n}\n"
  },
  {
    "path": "protocol/rtmp/core/handshake.go",
    "content": "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/gwuhaolin/livego/utils/pio\"\n)\n\nvar (\n\ttimeout = 5 * time.Second\n)\n\nvar (\n\thsClientFullKey = []byte{\n\t\t'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',\n\t\t'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',\n\t\t'0', '0', '1',\n\t\t0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,\n\t\t0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,\n\t\t0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,\n\t}\n\thsServerFullKey = []byte{\n\t\t'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',\n\t\t'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',\n\t\t'S', 'e', 'r', 'v', 'e', 'r', ' ',\n\t\t'0', '0', '1',\n\t\t0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,\n\t\t0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,\n\t\t0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,\n\t}\n\thsClientPartialKey = hsClientFullKey[:30]\n\thsServerPartialKey = hsServerFullKey[:36]\n)\n\nfunc hsMakeDigest(key []byte, src []byte, gap int) (dst []byte) {\n\th := hmac.New(sha256.New, key)\n\tif gap <= 0 {\n\t\th.Write(src)\n\t} else {\n\t\th.Write(src[:gap])\n\t\th.Write(src[gap+32:])\n\t}\n\treturn h.Sum(nil)\n}\n\nfunc hsCalcDigestPos(p []byte, base int) (pos int) {\n\tfor i := 0; i < 4; i++ {\n\t\tpos += int(p[base+i])\n\t}\n\tpos = (pos % 728) + base + 4\n\treturn\n}\n\nfunc hsFindDigest(p []byte, key []byte, base int) int {\n\tgap := hsCalcDigestPos(p, base)\n\tdigest := hsMakeDigest(key, p, gap)\n\tif bytes.Compare(p[gap:gap+32], digest) != 0 {\n\t\treturn -1\n\t}\n\treturn gap\n}\n\nfunc hsParse1(p []byte, peerkey []byte, key []byte) (ok bool, digest []byte) {\n\tvar pos int\n\tif pos = hsFindDigest(p, peerkey, 772); pos == -1 {\n\t\tif pos = hsFindDigest(p, peerkey, 8); pos == -1 {\n\t\t\treturn\n\t\t}\n\t}\n\tok = true\n\tdigest = hsMakeDigest(key, p[pos:pos+32], -1)\n\treturn\n}\n\nfunc hsCreate01(p []byte, time uint32, ver uint32, key []byte) {\n\tp[0] = 3\n\tp1 := p[1:]\n\trand.Read(p1[8:])\n\tpio.PutU32BE(p1[0:4], time)\n\tpio.PutU32BE(p1[4:8], ver)\n\tgap := hsCalcDigestPos(p1, 8)\n\tdigest := hsMakeDigest(key, p1, gap)\n\tcopy(p1[gap:], digest)\n}\n\nfunc hsCreate2(p []byte, key []byte) {\n\trand.Read(p)\n\tgap := len(p) - 32\n\tdigest := hsMakeDigest(key, p, gap)\n\tcopy(p[gap:], digest)\n}\n\nfunc (conn *Conn) HandshakeClient() (err error) {\n\tvar random [(1 + 1536*2) * 2]byte\n\n\tC0C1C2 := random[:1536*2+1]\n\tC0 := C0C1C2[:1]\n\tC0C1 := C0C1C2[:1536+1]\n\tC2 := C0C1C2[1536+1:]\n\n\tS0S1S2 := random[1536*2+1:]\n\n\tC0[0] = 3\n\t// > C0C1\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = conn.rw.Write(C0C1); err != nil {\n\t\treturn\n\t}\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif err = conn.rw.Flush(); err != nil {\n\t\treturn\n\t}\n\n\t// < S0S1S2\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = io.ReadFull(conn.rw, S0S1S2); err != nil {\n\t\treturn\n\t}\n\n\tS1 := S0S1S2[1 : 1536+1]\n\tif ver := pio.U32BE(S1[4:8]); ver != 0 {\n\t\tC2 = S1\n\t} else {\n\t\tC2 = S1\n\t}\n\n\t// > C2\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = conn.rw.Write(C2); err != nil {\n\t\treturn\n\t}\n\tconn.Conn.SetDeadline(time.Time{})\n\treturn\n}\n\nfunc (conn *Conn) HandshakeServer() (err error) {\n\tvar random [(1 + 1536*2) * 2]byte\n\n\tC0C1C2 := random[:1536*2+1]\n\tC0 := C0C1C2[:1]\n\tC1 := C0C1C2[1 : 1536+1]\n\tC0C1 := C0C1C2[:1536+1]\n\tC2 := C0C1C2[1536+1:]\n\n\tS0S1S2 := random[1536*2+1:]\n\tS0 := S0S1S2[:1]\n\tS1 := S0S1S2[1 : 1536+1]\n\tS0S1 := S0S1S2[:1536+1]\n\tS2 := S0S1S2[1536+1:]\n\n\t// < C0C1\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = io.ReadFull(conn.rw, C0C1); err != nil {\n\t\treturn\n\t}\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif C0[0] != 3 {\n\t\terr = fmt.Errorf(\"rtmp: handshake version=%d invalid\", C0[0])\n\t\treturn\n\t}\n\n\tS0[0] = 3\n\n\tclitime := pio.U32BE(C1[0:4])\n\tsrvtime := clitime\n\tsrvver := uint32(0x0d0e0a0d)\n\tcliver := pio.U32BE(C1[4:8])\n\n\tif cliver != 0 {\n\t\tvar ok bool\n\t\tvar digest []byte\n\t\tif ok, digest = hsParse1(C1, hsClientPartialKey, hsServerFullKey); !ok {\n\t\t\terr = fmt.Errorf(\"rtmp: handshake server: C1 invalid\")\n\t\t\treturn\n\t\t}\n\t\thsCreate01(S0S1, srvtime, srvver, hsServerPartialKey)\n\t\thsCreate2(S2, digest)\n\t} else {\n\t\tcopy(S1, C2)\n\t\tcopy(S2, C1)\n\t}\n\n\t// > S0S1S2\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = conn.rw.Write(S0S1S2); err != nil {\n\t\treturn\n\t}\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif err = conn.rw.Flush(); err != nil {\n\t\treturn\n\t}\n\n\t// < C2\n\tconn.Conn.SetDeadline(time.Now().Add(timeout))\n\tif _, err = io.ReadFull(conn.rw, C2); err != nil {\n\t\treturn\n\t}\n\tconn.Conn.SetDeadline(time.Time{})\n\treturn\n}\n"
  },
  {
    "path": "protocol/rtmp/core/read_writer.go",
    "content": "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\n}\n\nfunc NewReadWriter(rw io.ReadWriter, bufSize int) *ReadWriter {\n\treturn &ReadWriter{\n\t\tReadWriter: bufio.NewReadWriter(bufio.NewReaderSize(rw, bufSize), bufio.NewWriterSize(rw, bufSize)),\n\t}\n}\n\nfunc (rw *ReadWriter) Read(p []byte) (int, error) {\n\tif rw.readError != nil {\n\t\treturn 0, rw.readError\n\t}\n\tn, err := io.ReadAtLeast(rw.ReadWriter, p, len(p))\n\trw.readError = err\n\treturn n, err\n}\n\nfunc (rw *ReadWriter) ReadError() error {\n\treturn rw.readError\n}\n\nfunc (rw *ReadWriter) ReadUintBE(n int) (uint32, error) {\n\tif rw.readError != nil {\n\t\treturn 0, rw.readError\n\t}\n\tret := uint32(0)\n\tfor i := 0; i < n; i++ {\n\t\tb, err := rw.ReadByte()\n\t\tif err != nil {\n\t\t\trw.readError = err\n\t\t\treturn 0, err\n\t\t}\n\t\tret = ret<<8 + uint32(b)\n\t}\n\treturn ret, nil\n}\n\nfunc (rw *ReadWriter) ReadUintLE(n int) (uint32, error) {\n\tif rw.readError != nil {\n\t\treturn 0, rw.readError\n\t}\n\tret := uint32(0)\n\tfor i := 0; i < n; i++ {\n\t\tb, err := rw.ReadByte()\n\t\tif err != nil {\n\t\t\trw.readError = err\n\t\t\treturn 0, err\n\t\t}\n\t\tret += uint32(b) << uint32(i*8)\n\t}\n\treturn ret, nil\n}\n\nfunc (rw *ReadWriter) Flush() error {\n\tif rw.writeError != nil {\n\t\treturn rw.writeError\n\t}\n\n\tif rw.ReadWriter.Writer.Buffered() == 0 {\n\t\treturn nil\n\t}\n\treturn rw.ReadWriter.Flush()\n}\n\nfunc (rw *ReadWriter) Write(p []byte) (int, error) {\n\tif rw.writeError != nil {\n\t\treturn 0, rw.writeError\n\t}\n\treturn rw.ReadWriter.Write(p)\n}\n\nfunc (rw *ReadWriter) WriteError() error {\n\treturn rw.writeError\n}\n\nfunc (rw *ReadWriter) WriteUintBE(v uint32, n int) error {\n\tif rw.writeError != nil {\n\t\treturn rw.writeError\n\t}\n\tfor i := 0; i < n; i++ {\n\t\tb := byte(v>>uint32((n-i-1)<<3)) & 0xff\n\t\tif err := rw.WriteByte(b); err != nil {\n\t\t\trw.writeError = err\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (rw *ReadWriter) WriteUintLE(v uint32, n int) error {\n\tif rw.writeError != nil {\n\t\treturn rw.writeError\n\t}\n\tfor i := 0; i < n; i++ {\n\t\tb := byte(v) & 0xff\n\t\tif err := rw.WriteByte(b); err != nil {\n\t\t\trw.writeError = err\n\t\t\treturn err\n\t\t}\n\t\tv = v >> 8\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "protocol/rtmp/core/read_writer_test.go",
    "content": "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) {\n\tat := assert.New(t)\n\tbuf := bytes.NewBufferString(\"abc\")\n\tr := NewReadWriter(buf, 1024)\n\tb := make([]byte, 3)\n\tn, err := r.Read(b)\n\tat.Equal(err, nil)\n\tat.Equal(r.ReadError(), nil)\n\tat.Equal(n, 3)\n\tn, err = r.Read(b)\n\tat.Equal(err, io.EOF)\n\tat.Equal(r.ReadError(), io.EOF)\n\tbuf.WriteString(\"123\")\n\tn, err = r.Read(b)\n\tat.Equal(err, io.EOF)\n\tat.Equal(r.ReadError(), io.EOF)\n\tat.Equal(n, 0)\n}\n\nfunc TestReaderUintBE(t *testing.T) {\n\tat := assert.New(t)\n\ttype Test struct {\n\t\ti     int\n\t\tvalue uint32\n\t\tbytes []byte\n\t}\n\ttests := []Test{\n\t\t{1, 0x01, []byte{0x01}},\n\t\t{2, 0x0102, []byte{0x01, 0x02}},\n\t\t{3, 0x010203, []byte{0x01, 0x02, 0x03}},\n\t\t{4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}},\n\t}\n\tfor _, test := range tests {\n\t\tbuf := bytes.NewBuffer(test.bytes)\n\t\tr := NewReadWriter(buf, 1024)\n\t\tn, err := r.ReadUintBE(test.i)\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\tat.Equal(n, test.value, \"test %d\", test.i)\n\t}\n}\n\nfunc TestReaderUintLE(t *testing.T) {\n\tat := assert.New(t)\n\ttype Test struct {\n\t\ti     int\n\t\tvalue uint32\n\t\tbytes []byte\n\t}\n\ttests := []Test{\n\t\t{1, 0x01, []byte{0x01}},\n\t\t{2, 0x0102, []byte{0x02, 0x01}},\n\t\t{3, 0x010203, []byte{0x03, 0x02, 0x01}},\n\t\t{4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}},\n\t}\n\tfor _, test := range tests {\n\t\tbuf := bytes.NewBuffer(test.bytes)\n\t\tr := NewReadWriter(buf, 1024)\n\t\tn, err := r.ReadUintLE(test.i)\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\tat.Equal(n, test.value, \"test %d\", test.i)\n\t}\n}\n\nfunc TestWriter(t *testing.T) {\n\tat := assert.New(t)\n\tbuf := bytes.NewBuffer(nil)\n\tw := NewReadWriter(buf, 1024)\n\tb := []byte{1, 2, 3}\n\tn, err := w.Write(b)\n\tat.Equal(err, nil)\n\tat.Equal(w.WriteError(), nil)\n\tat.Equal(n, 3)\n\tw.writeError = io.EOF\n\tn, err = w.Write(b)\n\tat.Equal(err, io.EOF)\n\tat.Equal(w.WriteError(), io.EOF)\n\tat.Equal(n, 0)\n}\n\nfunc TestWriteUintBE(t *testing.T) {\n\tat := assert.New(t)\n\ttype Test struct {\n\t\ti     int\n\t\tvalue uint32\n\t\tbytes []byte\n\t}\n\ttests := []Test{\n\t\t{1, 0x01, []byte{0x01}},\n\t\t{2, 0x0102, []byte{0x01, 0x02}},\n\t\t{3, 0x010203, []byte{0x01, 0x02, 0x03}},\n\t\t{4, 0x01020304, []byte{0x01, 0x02, 0x03, 0x04}},\n\t}\n\tfor _, test := range tests {\n\t\tbuf := bytes.NewBuffer(nil)\n\t\tr := NewReadWriter(buf, 1024)\n\t\terr := r.WriteUintBE(test.value, test.i)\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\terr = r.Flush()\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\tat.Equal(buf.Bytes(), test.bytes, \"test %d\", test.i)\n\t}\n}\n\nfunc TestWriteUintLE(t *testing.T) {\n\tat := assert.New(t)\n\ttype Test struct {\n\t\ti     int\n\t\tvalue uint32\n\t\tbytes []byte\n\t}\n\ttests := []Test{\n\t\t{1, 0x01, []byte{0x01}},\n\t\t{2, 0x0102, []byte{0x02, 0x01}},\n\t\t{3, 0x010203, []byte{0x03, 0x02, 0x01}},\n\t\t{4, 0x01020304, []byte{0x04, 0x03, 0x02, 0x01}},\n\t}\n\tfor _, test := range tests {\n\t\tbuf := bytes.NewBuffer(nil)\n\t\tr := NewReadWriter(buf, 1024)\n\t\terr := r.WriteUintLE(test.value, test.i)\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\terr = r.Flush()\n\t\tat.Equal(err, nil, \"test %d\", test.i)\n\t\tat.Equal(buf.Bytes(), test.bytes, \"test %d\", test.i)\n\t}\n}\n"
  },
  {
    "path": "protocol/rtmp/rtmp.go",
    "content": "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\n\t\"github.com/gwuhaolin/livego/av\"\n\t\"github.com/gwuhaolin/livego/configure\"\n\t\"github.com/gwuhaolin/livego/container/flv\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp/core\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tmaxQueueNum           = 1024\n\tSAVE_STATICS_INTERVAL = 5000\n)\n\nvar (\n\treadTimeout  = configure.Config.GetInt(\"read_timeout\")\n\twriteTimeout = configure.Config.GetInt(\"write_timeout\")\n)\n\ntype Client struct {\n\thandler av.Handler\n\tgetter  av.GetWriter\n}\n\nfunc NewRtmpClient(h av.Handler, getter av.GetWriter) *Client {\n\treturn &Client{\n\t\thandler: h,\n\t\tgetter:  getter,\n\t}\n}\n\nfunc (c *Client) Dial(url string, method string) error {\n\tconnClient := core.NewConnClient()\n\tif err := connClient.Start(url, method); err != nil {\n\t\treturn err\n\t}\n\tif method == av.PUBLISH {\n\t\twriter := NewVirWriter(connClient)\n\t\tlog.Debugf(\"client Dial call NewVirWriter url=%s, method=%s\", url, method)\n\t\tc.handler.HandleWriter(writer)\n\t} else if method == av.PLAY {\n\t\treader := NewVirReader(connClient)\n\t\tlog.Debugf(\"client Dial call NewVirReader url=%s, method=%s\", url, method)\n\t\tc.handler.HandleReader(reader)\n\t\tif c.getter != nil {\n\t\t\twriter := c.getter.GetWriter(reader.Info())\n\t\t\tc.handler.HandleWriter(writer)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Client) GetHandle() av.Handler {\n\treturn c.handler\n}\n\ntype Server struct {\n\thandler av.Handler\n\tgetter  av.GetWriter\n}\n\nfunc NewRtmpServer(h av.Handler, getter av.GetWriter) *Server {\n\treturn &Server{\n\t\thandler: h,\n\t\tgetter:  getter,\n\t}\n}\n\nfunc (s *Server) Serve(listener net.Listener) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Error(\"rtmp serve panic: \", r)\n\t\t}\n\t}()\n\n\tfor {\n\t\tvar netconn net.Conn\n\t\tnetconn, err = listener.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tconn := core.NewConn(netconn, 4*1024)\n\t\tlog.Debug(\"new client, connect remote: \", conn.RemoteAddr().String(),\n\t\t\t\"local:\", conn.LocalAddr().String())\n\t\tgo s.handleConn(conn)\n\t}\n}\n\nfunc (s *Server) handleConn(conn *core.Conn) error {\n\tif err := conn.HandshakeServer(); err != nil {\n\t\tconn.Close()\n\t\tlog.Error(\"handleConn HandshakeServer err: \", err)\n\t\treturn err\n\t}\n\tconnServer := core.NewConnServer(conn)\n\n\tif err := connServer.ReadMsg(); err != nil {\n\t\tconn.Close()\n\t\tlog.Error(\"handleConn read msg err: \", err)\n\t\treturn err\n\t}\n\n\tappname, name, _ := connServer.GetInfo()\n\n\tif ret := configure.CheckAppName(appname); !ret {\n\t\terr := fmt.Errorf(\"application name=%s is not configured\", appname)\n\t\tconn.Close()\n\t\tlog.Error(\"CheckAppName err: \", err)\n\t\treturn err\n\t}\n\n\tlog.Debugf(\"handleConn: IsPublisher=%v\", connServer.IsPublisher())\n\tif connServer.IsPublisher() {\n\t\tif configure.Config.GetBool(\"rtmp_noauth\") {\n\t\t\tkey, err := configure.RoomKeys.GetKey(name)\n\t\t\tif err != nil {\n\t\t\t\terr := fmt.Errorf(\"Cannot create key err=%s\", err.Error())\n\t\t\t\tconn.Close()\n\t\t\t\tlog.Error(\"GetKey err: \", err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tname = key\n\t\t}\n\t\tchannel, err := configure.RoomKeys.GetChannel(name)\n\t\tif err != nil {\n\t\t\terr := fmt.Errorf(\"invalid key err=%s\", err.Error())\n\t\t\tconn.Close()\n\t\t\tlog.Error(\"CheckKey err: \", err)\n\t\t\treturn err\n\t\t}\n\t\tconnServer.PublishInfo.Name = channel\n\t\tif pushlist, ret := configure.GetStaticPushUrlList(appname); ret && (pushlist != nil) {\n\t\t\tlog.Debugf(\"GetStaticPushUrlList: %v\", pushlist)\n\t\t}\n\t\treader := NewVirReader(connServer)\n\t\ts.handler.HandleReader(reader)\n\t\tlog.Debugf(\"new publisher: %+v\", reader.Info())\n\n\t\tif s.getter != nil {\n\t\t\twriteType := reflect.TypeOf(s.getter)\n\t\t\tlog.Debugf(\"handleConn:writeType=%v\", writeType)\n\t\t\twriter := s.getter.GetWriter(reader.Info())\n\t\t\ts.handler.HandleWriter(writer)\n\t\t}\n\t\tif configure.Config.GetBool(\"flv_archive\") {\n\t\t\tflvWriter := new(flv.FlvDvr)\n\t\t\ts.handler.HandleWriter(flvWriter.GetWriter(reader.Info()))\n\t\t}\n\t} else {\n\t\twriter := NewVirWriter(connServer)\n\t\tlog.Debugf(\"new player: %+v\", writer.Info())\n\t\ts.handler.HandleWriter(writer)\n\t}\n\n\treturn nil\n}\n\ntype GetInFo interface {\n\tGetInfo() (string, string, string)\n}\n\ntype StreamReadWriteCloser interface {\n\tGetInFo\n\tClose(error)\n\tWrite(core.ChunkStream) error\n\tRead(c *core.ChunkStream) error\n}\n\ntype StaticsBW struct {\n\tStreamId               uint32\n\tVideoDatainBytes       uint64\n\tLastVideoDatainBytes   uint64\n\tVideoSpeedInBytesperMS uint64\n\n\tAudioDatainBytes       uint64\n\tLastAudioDatainBytes   uint64\n\tAudioSpeedInBytesperMS uint64\n\n\tLastTimestamp int64\n}\n\ntype VirWriter struct {\n\tUid    string\n\tclosed bool\n\tav.RWBaser\n\tconn        StreamReadWriteCloser\n\tpacketQueue chan *av.Packet\n\tWriteBWInfo StaticsBW\n}\n\nfunc NewVirWriter(conn StreamReadWriteCloser) *VirWriter {\n\tret := &VirWriter{\n\t\tUid:         uid.NewId(),\n\t\tconn:        conn,\n\t\tRWBaser:     av.NewRWBaser(time.Second * time.Duration(writeTimeout)),\n\t\tpacketQueue: make(chan *av.Packet, maxQueueNum),\n\t\tWriteBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},\n\t}\n\n\tgo ret.Check()\n\tgo func() {\n\t\terr := ret.SendPacket()\n\t\tif err != nil {\n\t\t\tlog.Warning(err)\n\t\t}\n\t}()\n\treturn ret\n}\n\nfunc (v *VirWriter) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) {\n\tnowInMS := int64(time.Now().UnixNano() / 1e6)\n\n\tv.WriteBWInfo.StreamId = streamid\n\tif isVideoFlag {\n\t\tv.WriteBWInfo.VideoDatainBytes = v.WriteBWInfo.VideoDatainBytes + length\n\t} else {\n\t\tv.WriteBWInfo.AudioDatainBytes = v.WriteBWInfo.AudioDatainBytes + length\n\t}\n\n\tif v.WriteBWInfo.LastTimestamp == 0 {\n\t\tv.WriteBWInfo.LastTimestamp = nowInMS\n\t} else if (nowInMS - v.WriteBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL {\n\t\tdiffTimestamp := (nowInMS - v.WriteBWInfo.LastTimestamp) / 1000\n\n\t\tv.WriteBWInfo.VideoSpeedInBytesperMS = (v.WriteBWInfo.VideoDatainBytes - v.WriteBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000\n\t\tv.WriteBWInfo.AudioSpeedInBytesperMS = (v.WriteBWInfo.AudioDatainBytes - v.WriteBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000\n\n\t\tv.WriteBWInfo.LastVideoDatainBytes = v.WriteBWInfo.VideoDatainBytes\n\t\tv.WriteBWInfo.LastAudioDatainBytes = v.WriteBWInfo.AudioDatainBytes\n\t\tv.WriteBWInfo.LastTimestamp = nowInMS\n\t}\n}\n\nfunc (v *VirWriter) Check() {\n\tvar c core.ChunkStream\n\tfor {\n\t\tif err := v.conn.Read(&c); err != nil {\n\t\t\tv.Close(err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (v *VirWriter) DropPacket(pktQue chan *av.Packet, info av.Info) {\n\tlog.Warningf(\"[%v] packet queue max!!!\", info)\n\tfor i := 0; i < maxQueueNum-84; i++ {\n\t\ttmpPkt, ok := <-pktQue\n\t\t// try to don't drop audio\n\t\tif ok && tmpPkt.IsAudio {\n\t\t\tif len(pktQue) > maxQueueNum-2 {\n\t\t\t\tlog.Debug(\"drop audio pkt\")\n\t\t\t\t<-pktQue\n\t\t\t} else {\n\t\t\t\tpktQue <- tmpPkt\n\t\t\t}\n\n\t\t}\n\n\t\tif ok && tmpPkt.IsVideo {\n\t\t\tvideoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader)\n\t\t\t// dont't drop sps config and dont't drop key frame\n\t\t\tif ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) {\n\t\t\t\tpktQue <- tmpPkt\n\t\t\t}\n\t\t\tif len(pktQue) > maxQueueNum-10 {\n\t\t\t\tlog.Debug(\"drop video pkt\")\n\t\t\t\t<-pktQue\n\t\t\t}\n\t\t}\n\n\t}\n\tlog.Debug(\"packet queue len: \", len(pktQue))\n}\n\n//\nfunc (v *VirWriter) Write(p *av.Packet) (err error) {\n\terr = nil\n\n\tif v.closed {\n\t\terr = fmt.Errorf(\"VirWriter closed\")\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif e := recover(); e != nil {\n\t\t\terr = fmt.Errorf(\"VirWriter has already been closed:%v\", e)\n\t\t}\n\t}()\n\tif len(v.packetQueue) >= maxQueueNum-24 {\n\t\tv.DropPacket(v.packetQueue, v.Info())\n\t} else {\n\t\tv.packetQueue <- p\n\t}\n\n\treturn\n}\n\nfunc (v *VirWriter) SendPacket() error {\n\tFlush := reflect.ValueOf(v.conn).MethodByName(\"Flush\")\n\tvar cs core.ChunkStream\n\tfor {\n\t\tp, ok := <-v.packetQueue\n\t\tif ok {\n\t\t\tcs.Data = p.Data\n\t\t\tcs.Length = uint32(len(p.Data))\n\t\t\tcs.StreamID = p.StreamID\n\t\t\tcs.Timestamp = p.TimeStamp\n\t\t\tcs.Timestamp += v.BaseTimeStamp()\n\n\t\t\tif p.IsVideo {\n\t\t\t\tcs.TypeID = av.TAG_VIDEO\n\t\t\t} else {\n\t\t\t\tif p.IsMetadata {\n\t\t\t\t\tcs.TypeID = av.TAG_SCRIPTDATAAMF0\n\t\t\t\t} else {\n\t\t\t\t\tcs.TypeID = av.TAG_AUDIO\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tv.SaveStatics(p.StreamID, uint64(cs.Length), p.IsVideo)\n\t\t\tv.SetPreTime()\n\t\t\tv.RecTimeStamp(cs.Timestamp, cs.TypeID)\n\t\t\terr := v.conn.Write(cs)\n\t\t\tif err != nil {\n\t\t\t\tv.closed = true\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tFlush.Call(nil)\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"closed\")\n\t\t}\n\n\t}\n}\n\nfunc (v *VirWriter) Info() (ret av.Info) {\n\tret.UID = v.Uid\n\t_, _, URL := v.conn.GetInfo()\n\tret.URL = URL\n\t_url, err := url.Parse(URL)\n\tif err != nil {\n\t\tlog.Warning(err)\n\t}\n\tret.Key = strings.TrimLeft(_url.Path, \"/\")\n\tret.Inter = true\n\treturn\n}\n\nfunc (v *VirWriter) Close(err error) {\n\tlog.Warning(\"player \", v.Info(), \"closed: \"+err.Error())\n\tif !v.closed {\n\t\tclose(v.packetQueue)\n\t}\n\tv.closed = true\n\tv.conn.Close(err)\n}\n\ntype VirReader struct {\n\tUid string\n\tav.RWBaser\n\tdemuxer    *flv.Demuxer\n\tconn       StreamReadWriteCloser\n\tReadBWInfo StaticsBW\n}\n\nfunc NewVirReader(conn StreamReadWriteCloser) *VirReader {\n\treturn &VirReader{\n\t\tUid:        uid.NewId(),\n\t\tconn:       conn,\n\t\tRWBaser:    av.NewRWBaser(time.Second * time.Duration(writeTimeout)),\n\t\tdemuxer:    flv.NewDemuxer(),\n\t\tReadBWInfo: StaticsBW{0, 0, 0, 0, 0, 0, 0, 0},\n\t}\n}\n\nfunc (v *VirReader) SaveStatics(streamid uint32, length uint64, isVideoFlag bool) {\n\tnowInMS := int64(time.Now().UnixNano() / 1e6)\n\n\tv.ReadBWInfo.StreamId = streamid\n\tif isVideoFlag {\n\t\tv.ReadBWInfo.VideoDatainBytes = v.ReadBWInfo.VideoDatainBytes + length\n\t} else {\n\t\tv.ReadBWInfo.AudioDatainBytes = v.ReadBWInfo.AudioDatainBytes + length\n\t}\n\n\tif v.ReadBWInfo.LastTimestamp == 0 {\n\t\tv.ReadBWInfo.LastTimestamp = nowInMS\n\t} else if (nowInMS - v.ReadBWInfo.LastTimestamp) >= SAVE_STATICS_INTERVAL {\n\t\tdiffTimestamp := (nowInMS - v.ReadBWInfo.LastTimestamp) / 1000\n\n\t\t//log.Printf(\"now=%d, last=%d, diff=%d\", nowInMS, v.ReadBWInfo.LastTimestamp, diffTimestamp)\n\t\tv.ReadBWInfo.VideoSpeedInBytesperMS = (v.ReadBWInfo.VideoDatainBytes - v.ReadBWInfo.LastVideoDatainBytes) * 8 / uint64(diffTimestamp) / 1000\n\t\tv.ReadBWInfo.AudioSpeedInBytesperMS = (v.ReadBWInfo.AudioDatainBytes - v.ReadBWInfo.LastAudioDatainBytes) * 8 / uint64(diffTimestamp) / 1000\n\n\t\tv.ReadBWInfo.LastVideoDatainBytes = v.ReadBWInfo.VideoDatainBytes\n\t\tv.ReadBWInfo.LastAudioDatainBytes = v.ReadBWInfo.AudioDatainBytes\n\t\tv.ReadBWInfo.LastTimestamp = nowInMS\n\t}\n}\n\nfunc (v *VirReader) Read(p *av.Packet) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Warning(\"rtmp read packet panic: \", r)\n\t\t}\n\t}()\n\n\tv.SetPreTime()\n\tvar cs core.ChunkStream\n\tfor {\n\t\terr = v.conn.Read(&cs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif cs.TypeID == av.TAG_AUDIO ||\n\t\t\tcs.TypeID == av.TAG_VIDEO ||\n\t\t\tcs.TypeID == av.TAG_SCRIPTDATAAMF0 ||\n\t\t\tcs.TypeID == av.TAG_SCRIPTDATAAMF3 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tp.IsAudio = cs.TypeID == av.TAG_AUDIO\n\tp.IsVideo = cs.TypeID == av.TAG_VIDEO\n\tp.IsMetadata = cs.TypeID == av.TAG_SCRIPTDATAAMF0 || cs.TypeID == av.TAG_SCRIPTDATAAMF3\n\tp.StreamID = cs.StreamID\n\tp.Data = cs.Data\n\tp.TimeStamp = cs.Timestamp\n\n\tv.SaveStatics(p.StreamID, uint64(len(p.Data)), p.IsVideo)\n\tv.demuxer.DemuxH(p)\n\treturn err\n}\n\nfunc (v *VirReader) Info() (ret av.Info) {\n\tret.UID = v.Uid\n\t_, _, URL := v.conn.GetInfo()\n\tret.URL = URL\n\t_url, err := url.Parse(URL)\n\tif err != nil {\n\t\tlog.Warning(err)\n\t}\n\tret.Key = strings.TrimLeft(_url.Path, \"/\")\n\treturn\n}\n\nfunc (v *VirReader) Close(err error) {\n\tlog.Debug(\"publisher \", v.Info(), \"closed: \"+err.Error())\n\tv.conn.Close(err)\n}\n"
  },
  {
    "path": "protocol/rtmp/rtmprelay/rtmprelay.go",
    "content": "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/protocol/amf\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp/core\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tSTOP_CTRL = \"RTMPRELAY_STOP\"\n)\n\ntype RtmpRelay struct {\n\tPlayUrl              string\n\tPublishUrl           string\n\tcs_chan              chan core.ChunkStream\n\tsndctrl_chan         chan string\n\tconnectPlayClient    *core.ConnClient\n\tconnectPublishClient *core.ConnClient\n\tstartflag            bool\n}\n\nfunc NewRtmpRelay(playurl *string, publishurl *string) *RtmpRelay {\n\treturn &RtmpRelay{\n\t\tPlayUrl:              *playurl,\n\t\tPublishUrl:           *publishurl,\n\t\tcs_chan:              make(chan core.ChunkStream, 500),\n\t\tsndctrl_chan:         make(chan string),\n\t\tconnectPlayClient:    nil,\n\t\tconnectPublishClient: nil,\n\t\tstartflag:            false,\n\t}\n}\n\nfunc (self *RtmpRelay) rcvPlayChunkStream() {\n\tlog.Debug(\"rcvPlayRtmpMediaPacket connectClient.Read...\")\n\tfor {\n\t\tvar rc core.ChunkStream\n\n\t\tif self.startflag == false {\n\t\t\tself.connectPlayClient.Close(nil)\n\t\t\tlog.Debugf(\"rcvPlayChunkStream close: playurl=%s, publishurl=%s\", self.PlayUrl, self.PublishUrl)\n\t\t\tbreak\n\t\t}\n\t\terr := self.connectPlayClient.Read(&rc)\n\n\t\tif err != nil && err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\t//log.Debugf(\"connectPlayClient.Read return rc.TypeID=%v length=%d, err=%v\", rc.TypeID, len(rc.Data), err)\n\t\tswitch rc.TypeID {\n\t\tcase 20, 17:\n\t\t\tr := bytes.NewReader(rc.Data)\n\t\t\tvs, err := self.connectPlayClient.DecodeBatch(r, amf.AMF0)\n\n\t\t\tlog.Debugf(\"rcvPlayRtmpMediaPacket: vs=%v, err=%v\", vs, err)\n\t\tcase 18:\n\t\t\tlog.Debug(\"rcvPlayRtmpMediaPacket: metadata....\")\n\t\t\tself.cs_chan <- rc\n\t\tcase 8, 9:\n\t\t\tself.cs_chan <- rc\n\t\t}\n\t}\n}\n\nfunc (self *RtmpRelay) sendPublishChunkStream() {\n\tfor {\n\t\tselect {\n\t\tcase rc := <-self.cs_chan:\n\t\t\t//log.Debugf(\"sendPublishChunkStream: rc.TypeID=%v length=%d\", rc.TypeID, len(rc.Data))\n\t\t\tself.connectPublishClient.Write(rc)\n\t\tcase ctrlcmd := <-self.sndctrl_chan:\n\t\t\tif ctrlcmd == STOP_CTRL {\n\t\t\t\tself.connectPublishClient.Close(nil)\n\t\t\t\tlog.Debugf(\"sendPublishChunkStream close: playurl=%s, publishurl=%s\", self.PlayUrl, self.PublishUrl)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (self *RtmpRelay) Start() error {\n\tif self.startflag {\n\t\treturn fmt.Errorf(\"The rtmprelay already started, playurl=%s, publishurl=%s\\n\", self.PlayUrl, self.PublishUrl)\n\t}\n\n\tself.connectPlayClient = core.NewConnClient()\n\tself.connectPublishClient = core.NewConnClient()\n\n\tlog.Debugf(\"play server addr:%v starting....\", self.PlayUrl)\n\terr := self.connectPlayClient.Start(self.PlayUrl, av.PLAY)\n\tif err != nil {\n\t\tlog.Debugf(\"connectPlayClient.Start url=%v error\", self.PlayUrl)\n\t\treturn err\n\t}\n\n\tlog.Debugf(\"publish server addr:%v starting....\", self.PublishUrl)\n\terr = self.connectPublishClient.Start(self.PublishUrl, av.PUBLISH)\n\tif err != nil {\n\t\tlog.Debugf(\"connectPublishClient.Start url=%v error\", self.PublishUrl)\n\t\tself.connectPlayClient.Close(nil)\n\t\treturn err\n\t}\n\n\tself.startflag = true\n\tgo self.rcvPlayChunkStream()\n\tgo self.sendPublishChunkStream()\n\n\treturn nil\n}\n\nfunc (self *RtmpRelay) Stop() {\n\tif !self.startflag {\n\t\tlog.Debugf(\"The rtmprelay already stoped, playurl=%s, publishurl=%s\", self.PlayUrl, self.PublishUrl)\n\t\treturn\n\t}\n\n\tself.startflag = false\n\tself.sndctrl_chan <- STOP_CTRL\n}\n"
  },
  {
    "path": "protocol/rtmp/rtmprelay/staticrelay.go",
    "content": "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\"github.com/gwuhaolin/livego/protocol/rtmp/core\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype StaticPush struct {\n\tRtmpUrl       string\n\tpacket_chan   chan *av.Packet\n\tsndctrl_chan  chan string\n\tconnectClient *core.ConnClient\n\tstartflag     bool\n}\n\nvar G_StaticPushMap = make(map[string](*StaticPush))\nvar g_MapLock = new(sync.RWMutex)\nvar G_PushUrlList []string = nil\n\nvar (\n\tSTATIC_RELAY_STOP_CTRL = \"STATIC_RTMPRELAY_STOP\"\n)\n\nfunc GetStaticPushList(appname string) ([]string, error) {\n\tif G_PushUrlList == nil {\n\t\t// Do not unmarshel the config every time, lots of reflect works -gs\n\t\tpushurlList, ok := configure.GetStaticPushUrlList(appname)\n\t\tif !ok {\n\t\t\tG_PushUrlList = []string{}\n\t\t} else {\n\t\t\tG_PushUrlList = pushurlList\n\t\t}\n\t}\n\n\tif len(G_PushUrlList) == 0 {\n\t\treturn nil, fmt.Errorf(\"no static push url\")\n\t}\n\n\treturn G_PushUrlList, nil\n}\n\nfunc GetAndCreateStaticPushObject(rtmpurl string) *StaticPush {\n\tg_MapLock.RLock()\n\tstaticpush, ok := G_StaticPushMap[rtmpurl]\n\tlog.Debugf(\"GetAndCreateStaticPushObject: %s, return %v\", rtmpurl, ok)\n\tif !ok {\n\t\tg_MapLock.RUnlock()\n\t\tnewStaticpush := NewStaticPush(rtmpurl)\n\n\t\tg_MapLock.Lock()\n\t\tG_StaticPushMap[rtmpurl] = newStaticpush\n\t\tg_MapLock.Unlock()\n\n\t\treturn newStaticpush\n\t}\n\tg_MapLock.RUnlock()\n\n\treturn staticpush\n}\n\nfunc GetStaticPushObject(rtmpurl string) (*StaticPush, error) {\n\tg_MapLock.RLock()\n\tif staticpush, ok := G_StaticPushMap[rtmpurl]; ok {\n\t\tg_MapLock.RUnlock()\n\t\treturn staticpush, nil\n\t}\n\tg_MapLock.RUnlock()\n\n\treturn nil, fmt.Errorf(\"G_StaticPushMap[%s] not exist....\", rtmpurl)\n}\n\nfunc ReleaseStaticPushObject(rtmpurl string) {\n\tg_MapLock.RLock()\n\tif _, ok := G_StaticPushMap[rtmpurl]; ok {\n\t\tg_MapLock.RUnlock()\n\n\t\tlog.Debugf(\"ReleaseStaticPushObject %s ok\", rtmpurl)\n\t\tg_MapLock.Lock()\n\t\tdelete(G_StaticPushMap, rtmpurl)\n\t\tg_MapLock.Unlock()\n\t} else {\n\t\tg_MapLock.RUnlock()\n\t\tlog.Debugf(\"ReleaseStaticPushObject: not find %s\", rtmpurl)\n\t}\n}\n\nfunc NewStaticPush(rtmpurl string) *StaticPush {\n\treturn &StaticPush{\n\t\tRtmpUrl:       rtmpurl,\n\t\tpacket_chan:   make(chan *av.Packet, 500),\n\t\tsndctrl_chan:  make(chan string),\n\t\tconnectClient: nil,\n\t\tstartflag:     false,\n\t}\n}\n\nfunc (self *StaticPush) Start() error {\n\tif self.startflag {\n\t\treturn fmt.Errorf(\"StaticPush already start %s\", self.RtmpUrl)\n\t}\n\n\tself.connectClient = core.NewConnClient()\n\n\tlog.Debugf(\"static publish server addr:%v starting....\", self.RtmpUrl)\n\terr := self.connectClient.Start(self.RtmpUrl, \"publish\")\n\tif err != nil {\n\t\tlog.Debugf(\"connectClient.Start url=%v error\", self.RtmpUrl)\n\t\treturn err\n\t}\n\tlog.Debugf(\"static publish server addr:%v started, streamid=%d\", self.RtmpUrl, self.connectClient.GetStreamId())\n\tgo self.HandleAvPacket()\n\n\tself.startflag = true\n\treturn nil\n}\n\nfunc (self *StaticPush) Stop() {\n\tif !self.startflag {\n\t\treturn\n\t}\n\n\tlog.Debugf(\"StaticPush Stop: %s\", self.RtmpUrl)\n\tself.sndctrl_chan <- STATIC_RELAY_STOP_CTRL\n\tself.startflag = false\n}\n\nfunc (self *StaticPush) WriteAvPacket(packet *av.Packet) {\n\tif !self.startflag {\n\t\treturn\n\t}\n\n\tself.packet_chan <- packet\n}\n\nfunc (self *StaticPush) sendPacket(p *av.Packet) {\n\tif !self.startflag {\n\t\treturn\n\t}\n\tvar cs core.ChunkStream\n\n\tcs.Data = p.Data\n\tcs.Length = uint32(len(p.Data))\n\tcs.StreamID = self.connectClient.GetStreamId()\n\tcs.Timestamp = p.TimeStamp\n\t//cs.Timestamp += v.BaseTimeStamp()\n\n\t//log.Printf(\"Static sendPacket: rtmpurl=%s, length=%d, streamid=%d\",\n\t//\tself.RtmpUrl, len(p.Data), cs.StreamID)\n\tif p.IsVideo {\n\t\tcs.TypeID = av.TAG_VIDEO\n\t} else {\n\t\tif p.IsMetadata {\n\t\t\tcs.TypeID = av.TAG_SCRIPTDATAAMF0\n\t\t} else {\n\t\t\tcs.TypeID = av.TAG_AUDIO\n\t\t}\n\t}\n\n\tself.connectClient.Write(cs)\n}\n\nfunc (self *StaticPush) HandleAvPacket() {\n\tif !self.IsStart() {\n\t\tlog.Debugf(\"static push %s not started\", self.RtmpUrl)\n\t\treturn\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase packet := <-self.packet_chan:\n\t\t\tself.sendPacket(packet)\n\t\tcase ctrlcmd := <-self.sndctrl_chan:\n\t\t\tif ctrlcmd == STATIC_RELAY_STOP_CTRL {\n\t\t\t\tself.connectClient.Close(nil)\n\t\t\t\tlog.Debugf(\"Static HandleAvPacket close: publishurl=%s\", self.RtmpUrl)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (self *StaticPush) IsStart() bool {\n\treturn self.startflag\n}\n"
  },
  {
    "path": "protocol/rtmp/stream.go",
    "content": "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/livego/protocol/rtmp/cache\"\n\t\"github.com/gwuhaolin/livego/protocol/rtmp/rtmprelay\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tEmptyID = \"\"\n)\n\ntype RtmpStream struct {\n\tstreams *sync.Map //key\n}\n\nfunc NewRtmpStream() *RtmpStream {\n\tret := &RtmpStream{\n\t\tstreams: &sync.Map{},\n\t}\n\tgo ret.CheckAlive()\n\treturn ret\n}\n\nfunc (rs *RtmpStream) HandleReader(r av.ReadCloser) {\n\tinfo := r.Info()\n\tlog.Debugf(\"HandleReader: info[%v]\", info)\n\n\tvar stream *Stream\n\ti, ok := rs.streams.Load(info.Key)\n\tif stream, ok = i.(*Stream); ok {\n\t\tstream.TransStop()\n\t\tid := stream.ID()\n\t\tif id != EmptyID && id != info.UID {\n\t\t\tns := NewStream()\n\t\t\tstream.Copy(ns)\n\t\t\tstream = ns\n\t\t\trs.streams.Store(info.Key, ns)\n\t\t}\n\t} else {\n\t\tstream = NewStream()\n\t\trs.streams.Store(info.Key, stream)\n\t\tstream.info = info\n\t}\n\n\tstream.AddReader(r)\n}\n\nfunc (rs *RtmpStream) HandleWriter(w av.WriteCloser) {\n\tinfo := w.Info()\n\tlog.Debugf(\"HandleWriter: info[%v]\", info)\n\n\tvar s *Stream\n\titem, ok := rs.streams.Load(info.Key)\n\tif !ok {\n\t\tlog.Debugf(\"HandleWriter: not found create new info[%v]\", info)\n\t\ts = NewStream()\n\t\trs.streams.Store(info.Key, s)\n\t\ts.info = info\n\t} else {\n\t\ts = item.(*Stream)\n\t\ts.AddWriter(w)\n\t}\n}\n\nfunc (rs *RtmpStream) GetStreams() *sync.Map {\n\treturn rs.streams\n}\n\nfunc (rs *RtmpStream) CheckAlive() {\n\tfor {\n\t\t<-time.After(5 * time.Second)\n\t\trs.streams.Range(func(key, val interface{}) bool {\n\t\t\tv := val.(*Stream)\n\t\t\tif v.CheckAlive() == 0 {\n\t\t\t\trs.streams.Delete(key)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\ntype Stream struct {\n\tisStart bool\n\tcache   *cache.Cache\n\tr       av.ReadCloser\n\tws      *sync.Map\n\tinfo    av.Info\n}\n\ntype PackWriterCloser struct {\n\tinit bool\n\tw    av.WriteCloser\n}\n\nfunc (p *PackWriterCloser) GetWriter() av.WriteCloser {\n\treturn p.w\n}\n\nfunc NewStream() *Stream {\n\treturn &Stream{\n\t\tcache: cache.NewCache(),\n\t\tws:    &sync.Map{},\n\t}\n}\n\nfunc (s *Stream) ID() string {\n\tif s.r != nil {\n\t\treturn s.r.Info().UID\n\t}\n\treturn EmptyID\n}\n\nfunc (s *Stream) GetReader() av.ReadCloser {\n\treturn s.r\n}\n\nfunc (s *Stream) GetWs() *sync.Map {\n\treturn s.ws\n}\n\nfunc (s *Stream) Copy(dst *Stream) {\n\tdst.info = s.info\n\ts.ws.Range(func(key, val interface{}) bool {\n\t\tv := val.(*PackWriterCloser)\n\t\ts.ws.Delete(key)\n\t\tv.w.CalcBaseTimestamp()\n\t\tdst.AddWriter(v.w)\n\t\treturn true\n\t})\n}\n\nfunc (s *Stream) AddReader(r av.ReadCloser) {\n\ts.r = r\n\tgo s.TransStart()\n}\n\nfunc (s *Stream) AddWriter(w av.WriteCloser) {\n\tinfo := w.Info()\n\tpw := &PackWriterCloser{w: w}\n\ts.ws.Store(info.UID, pw)\n}\n\n/*检测本application下是否配置static_push,\n如果配置, 启动push远端的连接*/\nfunc (s *Stream) StartStaticPush() {\n\tkey := s.info.Key\n\n\tdscr := strings.Split(key, \"/\")\n\tif len(dscr) < 1 {\n\t\treturn\n\t}\n\n\tindex := strings.Index(key, \"/\")\n\tif index < 0 {\n\t\treturn\n\t}\n\n\tstreamname := key[index+1:]\n\tappname := dscr[0]\n\n\tlog.Debugf(\"StartStaticPush: current streamname=%s， appname=%s\", streamname, appname)\n\tpushurllist, err := rtmprelay.GetStaticPushList(appname)\n\tif err != nil || len(pushurllist) < 1 {\n\t\tlog.Debugf(\"StartStaticPush: GetStaticPushList error=%v\", err)\n\t\treturn\n\t}\n\n\tfor _, pushurl := range pushurllist {\n\t\tpushurl := pushurl + \"/\" + streamname\n\t\tlog.Debugf(\"StartStaticPush: static pushurl=%s\", pushurl)\n\n\t\tstaticpushObj := rtmprelay.GetAndCreateStaticPushObject(pushurl)\n\t\tif staticpushObj != nil {\n\t\t\tif err := staticpushObj.Start(); err != nil {\n\t\t\t\tlog.Debugf(\"StartStaticPush: staticpushObj.Start %s error=%v\", pushurl, err)\n\t\t\t} else {\n\t\t\t\tlog.Debugf(\"StartStaticPush: staticpushObj.Start %s ok\", pushurl)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debugf(\"StartStaticPush GetStaticPushObject %s error\", pushurl)\n\t\t}\n\t}\n}\n\nfunc (s *Stream) StopStaticPush() {\n\tkey := s.info.Key\n\n\tlog.Debugf(\"StopStaticPush......%s\", key)\n\tdscr := strings.Split(key, \"/\")\n\tif len(dscr) < 1 {\n\t\treturn\n\t}\n\n\tindex := strings.Index(key, \"/\")\n\tif index < 0 {\n\t\treturn\n\t}\n\n\tstreamname := key[index+1:]\n\tappname := dscr[0]\n\n\tlog.Debugf(\"StopStaticPush: current streamname=%s， appname=%s\", streamname, appname)\n\tpushurllist, err := rtmprelay.GetStaticPushList(appname)\n\tif err != nil || len(pushurllist) < 1 {\n\t\tlog.Debugf(\"StopStaticPush: GetStaticPushList error=%v\", err)\n\t\treturn\n\t}\n\n\tfor _, pushurl := range pushurllist {\n\t\tpushurl := pushurl + \"/\" + streamname\n\t\tlog.Debugf(\"StopStaticPush: static pushurl=%s\", pushurl)\n\n\t\tstaticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)\n\t\tif (staticpushObj != nil) && (err == nil) {\n\t\t\tstaticpushObj.Stop()\n\t\t\trtmprelay.ReleaseStaticPushObject(pushurl)\n\t\t\tlog.Debugf(\"StopStaticPush: staticpushObj.Stop %s \", pushurl)\n\t\t} else {\n\t\t\tlog.Debugf(\"StopStaticPush GetStaticPushObject %s error\", pushurl)\n\t\t}\n\t}\n}\n\nfunc (s *Stream) IsSendStaticPush() bool {\n\tkey := s.info.Key\n\n\tdscr := strings.Split(key, \"/\")\n\tif len(dscr) < 1 {\n\t\treturn false\n\t}\n\n\tappname := dscr[0]\n\n\t//log.Debugf(\"SendStaticPush: current streamname=%s， appname=%s\", streamname, appname)\n\tpushurllist, err := rtmprelay.GetStaticPushList(appname)\n\tif err != nil || len(pushurllist) < 1 {\n\t\t//log.Debugf(\"SendStaticPush: GetStaticPushList error=%v\", err)\n\t\treturn false\n\t}\n\n\tindex := strings.Index(key, \"/\")\n\tif index < 0 {\n\t\treturn false\n\t}\n\n\tstreamname := key[index+1:]\n\n\tfor _, pushurl := range pushurllist {\n\t\tpushurl := pushurl + \"/\" + streamname\n\t\t//log.Debugf(\"SendStaticPush: static pushurl=%s\", pushurl)\n\n\t\tstaticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)\n\t\tif (staticpushObj != nil) && (err == nil) {\n\t\t\treturn true\n\t\t\t//staticpushObj.WriteAvPacket(&packet)\n\t\t\t//log.Debugf(\"SendStaticPush: WriteAvPacket %s \", pushurl)\n\t\t} else {\n\t\t\tlog.Debugf(\"SendStaticPush GetStaticPushObject %s error\", pushurl)\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *Stream) SendStaticPush(packet av.Packet) {\n\tkey := s.info.Key\n\n\tdscr := strings.Split(key, \"/\")\n\tif len(dscr) < 1 {\n\t\treturn\n\t}\n\n\tindex := strings.Index(key, \"/\")\n\tif index < 0 {\n\t\treturn\n\t}\n\n\tstreamname := key[index+1:]\n\tappname := dscr[0]\n\n\t//log.Debugf(\"SendStaticPush: current streamname=%s， appname=%s\", streamname, appname)\n\tpushurllist, err := rtmprelay.GetStaticPushList(appname)\n\tif err != nil || len(pushurllist) < 1 {\n\t\t//log.Debugf(\"SendStaticPush: GetStaticPushList error=%v\", err)\n\t\treturn\n\t}\n\n\tfor _, pushurl := range pushurllist {\n\t\tpushurl := pushurl + \"/\" + streamname\n\t\t//log.Debugf(\"SendStaticPush: static pushurl=%s\", pushurl)\n\n\t\tstaticpushObj, err := rtmprelay.GetStaticPushObject(pushurl)\n\t\tif (staticpushObj != nil) && (err == nil) {\n\t\t\tstaticpushObj.WriteAvPacket(&packet)\n\t\t\t//log.Debugf(\"SendStaticPush: WriteAvPacket %s \", pushurl)\n\t\t} else {\n\t\t\tlog.Debugf(\"SendStaticPush GetStaticPushObject %s error\", pushurl)\n\t\t}\n\t}\n}\n\nfunc (s *Stream) TransStart() {\n\ts.isStart = true\n\tvar p av.Packet\n\n\tlog.Debugf(\"TransStart: %v\", s.info)\n\n\ts.StartStaticPush()\n\n\tfor {\n\t\tif !s.isStart {\n\t\t\ts.closeInter()\n\t\t\treturn\n\t\t}\n\t\terr := s.r.Read(&p)\n\t\tif err != nil {\n\t\t\ts.closeInter()\n\t\t\ts.isStart = false\n\t\t\treturn\n\t\t}\n\n\t\tif s.IsSendStaticPush() {\n\t\t\ts.SendStaticPush(p)\n\t\t}\n\n\t\ts.cache.Write(p)\n\n\t\ts.ws.Range(func(key, val interface{}) bool {\n\t\t\tv := val.(*PackWriterCloser)\n\t\t\tif !v.init {\n\t\t\t\t//log.Debugf(\"cache.send: %v\", v.w.Info())\n\t\t\t\tif err = s.cache.Send(v.w); err != nil {\n\t\t\t\t\tlog.Debugf(\"[%s] send cache packet error: %v, remove\", v.w.Info(), err)\n\t\t\t\t\ts.ws.Delete(key)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tv.init = true\n\t\t\t} else {\n\t\t\t\tnewPacket := p\n\t\t\t\t//writeType := reflect.TypeOf(v.w)\n\t\t\t\t//log.Debugf(\"w.Write: type=%v, %v\", writeType, v.w.Info())\n\t\t\t\tif err = v.w.Write(&newPacket); err != nil {\n\t\t\t\t\tlog.Debugf(\"[%s] write packet error: %v, remove\", v.w.Info(), err)\n\t\t\t\t\ts.ws.Delete(key)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\nfunc (s *Stream) TransStop() {\n\tlog.Debugf(\"TransStop: %s\", s.info.Key)\n\n\tif s.isStart && s.r != nil {\n\t\ts.r.Close(fmt.Errorf(\"stop old\"))\n\t}\n\n\ts.isStart = false\n}\n\nfunc (s *Stream) CheckAlive() (n int) {\n\tif s.r != nil && s.isStart {\n\t\tif s.r.Alive() {\n\t\t\tn++\n\t\t} else {\n\t\t\ts.r.Close(fmt.Errorf(\"read timeout\"))\n\t\t}\n\t}\n\n\ts.ws.Range(func(key, val interface{}) bool {\n\t\tv := val.(*PackWriterCloser)\n\t\tif v.w != nil {\n\t\t\t//Alive from RWBaser, check last frame now - timestamp, if > timeout then Remove it\n\t\t\tif !v.w.Alive() {\n\t\t\t\tlog.Infof(\"write timeout remove\")\n\t\t\t\ts.ws.Delete(key)\n\t\t\t\tv.w.Close(fmt.Errorf(\"write timeout\"))\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tn++\n\t\t}\n\t\treturn true\n\t})\n\n\treturn\n}\n\nfunc (s *Stream) closeInter() {\n\tif s.r != nil {\n\t\ts.StopStaticPush()\n\t\tlog.Debugf(\"[%v] publisher closed\", s.r.Info())\n\t}\n\n\ts.ws.Range(func(key, val interface{}) bool {\n\t\tv := val.(*PackWriterCloser)\n\t\tif v.w != nil {\n\t\t\tv.w.Close(fmt.Errorf(\"closed\"))\n\t\t\tif v.w.Info().IsInterval() {\n\t\t\t\ts.ws.Delete(key)\n\t\t\t\tlog.Debugf(\"[%v] player closed and remove\\n\", v.w.Info())\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n}\n"
  },
  {
    "path": "test.go",
    "content": "package main\n"
  },
  {
    "path": "utils/pio/pio.go",
    "content": "package pio\n\nvar RecommendBufioSize = 1024 * 64\n"
  },
  {
    "path": "utils/pio/reader.go",
    "content": "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\ti |= uint16(b[1])\n\treturn\n}\n\nfunc I16BE(b []byte) (i int16) {\n\ti = int16(b[0])\n\ti <<= 8\n\ti |= int16(b[1])\n\treturn\n}\n\nfunc I24BE(b []byte) (i int32) {\n\ti = int32(int8(b[0]))\n\ti <<= 8\n\ti |= int32(b[1])\n\ti <<= 8\n\ti |= int32(b[2])\n\treturn\n}\n\nfunc U24BE(b []byte) (i uint32) {\n\ti = uint32(b[0])\n\ti <<= 8\n\ti |= uint32(b[1])\n\ti <<= 8\n\ti |= uint32(b[2])\n\treturn\n}\n\nfunc I32BE(b []byte) (i int32) {\n\ti = int32(int8(b[0]))\n\ti <<= 8\n\ti |= int32(b[1])\n\ti <<= 8\n\ti |= int32(b[2])\n\ti <<= 8\n\ti |= int32(b[3])\n\treturn\n}\n\nfunc U32LE(b []byte) (i uint32) {\n\ti = uint32(b[3])\n\ti <<= 8\n\ti |= uint32(b[2])\n\ti <<= 8\n\ti |= uint32(b[1])\n\ti <<= 8\n\ti |= uint32(b[0])\n\treturn\n}\n\nfunc U32BE(b []byte) (i uint32) {\n\ti = uint32(b[0])\n\ti <<= 8\n\ti |= uint32(b[1])\n\ti <<= 8\n\ti |= uint32(b[2])\n\ti <<= 8\n\ti |= uint32(b[3])\n\treturn\n}\n\nfunc U40BE(b []byte) (i uint64) {\n\ti = uint64(b[0])\n\ti <<= 8\n\ti |= uint64(b[1])\n\ti <<= 8\n\ti |= uint64(b[2])\n\ti <<= 8\n\ti |= uint64(b[3])\n\ti <<= 8\n\ti |= uint64(b[4])\n\treturn\n}\n\nfunc U64BE(b []byte) (i uint64) {\n\ti = uint64(b[0])\n\ti <<= 8\n\ti |= uint64(b[1])\n\ti <<= 8\n\ti |= uint64(b[2])\n\ti <<= 8\n\ti |= uint64(b[3])\n\ti <<= 8\n\ti |= uint64(b[4])\n\ti <<= 8\n\ti |= uint64(b[5])\n\ti <<= 8\n\ti |= uint64(b[6])\n\ti <<= 8\n\ti |= uint64(b[7])\n\treturn\n}\n\nfunc I64BE(b []byte) (i int64) {\n\ti = int64(int8(b[0]))\n\ti <<= 8\n\ti |= int64(b[1])\n\ti <<= 8\n\ti |= int64(b[2])\n\ti <<= 8\n\ti |= int64(b[3])\n\ti <<= 8\n\ti |= int64(b[4])\n\ti <<= 8\n\ti |= int64(b[5])\n\ti <<= 8\n\ti |= int64(b[6])\n\ti <<= 8\n\ti |= int64(b[7])\n\treturn\n}\n"
  },
  {
    "path": "utils/pio/writer.go",
    "content": "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] = byte(v)\n}\n\nfunc PutU16BE(b []byte, v uint16) {\n\tb[0] = byte(v >> 8)\n\tb[1] = byte(v)\n}\n\nfunc PutI24BE(b []byte, v int32) {\n\tb[0] = byte(v >> 16)\n\tb[1] = byte(v >> 8)\n\tb[2] = byte(v)\n}\n\nfunc PutU24BE(b []byte, v uint32) {\n\tb[0] = byte(v >> 16)\n\tb[1] = byte(v >> 8)\n\tb[2] = byte(v)\n}\n\nfunc PutI32BE(b []byte, v int32) {\n\tb[0] = byte(v >> 24)\n\tb[1] = byte(v >> 16)\n\tb[2] = byte(v >> 8)\n\tb[3] = byte(v)\n}\n\nfunc PutU32BE(b []byte, v uint32) {\n\tb[0] = byte(v >> 24)\n\tb[1] = byte(v >> 16)\n\tb[2] = byte(v >> 8)\n\tb[3] = byte(v)\n}\n\nfunc PutU32LE(b []byte, v uint32) {\n\tb[3] = byte(v >> 24)\n\tb[2] = byte(v >> 16)\n\tb[1] = byte(v >> 8)\n\tb[0] = byte(v)\n}\n\nfunc PutU40BE(b []byte, v uint64) {\n\tb[0] = byte(v >> 32)\n\tb[1] = byte(v >> 24)\n\tb[2] = byte(v >> 16)\n\tb[3] = byte(v >> 8)\n\tb[4] = byte(v)\n}\n\nfunc PutU48BE(b []byte, v uint64) {\n\tb[0] = byte(v >> 40)\n\tb[1] = byte(v >> 32)\n\tb[2] = byte(v >> 24)\n\tb[3] = byte(v >> 16)\n\tb[4] = byte(v >> 8)\n\tb[5] = byte(v)\n}\n\nfunc PutU64BE(b []byte, v uint64) {\n\tb[0] = byte(v >> 56)\n\tb[1] = byte(v >> 48)\n\tb[2] = byte(v >> 40)\n\tb[3] = byte(v >> 32)\n\tb[4] = byte(v >> 24)\n\tb[5] = byte(v >> 16)\n\tb[6] = byte(v >> 8)\n\tb[7] = byte(v)\n}\n\nfunc PutI64BE(b []byte, v int64) {\n\tb[0] = byte(v >> 56)\n\tb[1] = byte(v >> 48)\n\tb[2] = byte(v >> 40)\n\tb[3] = byte(v >> 32)\n\tb[4] = byte(v >> 24)\n\tb[5] = byte(v >> 16)\n\tb[6] = byte(v >> 8)\n\tb[7] = byte(v)\n}\n"
  },
  {
    "path": "utils/pool/pool.go",
    "content": "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) []byte {\n\tif maxpoolsize-pool.pos < size {\n\t\tpool.pos = 0\n\t\tpool.buf = make([]byte, maxpoolsize)\n\t}\n\tb := pool.buf[pool.pos : pool.pos+size]\n\tpool.pos += size\n\treturn b\n}\n\nfunc NewPool() *Pool {\n\treturn &Pool{\n\t\tbuf: make([]byte, maxpoolsize),\n\t}\n}\n"
  },
  {
    "path": "utils/queue/queue.go",
    "content": "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 Queue struct {\n\tmaxSize int\n\n\tlist  []*av.Packet\n\tmutex sync.Mutex\n}\n\n// NewQueue returns a new Queue. If maxSize is greater than zero the queue will\n// not grow more than the defined size.\nfunc NewQueue(maxSize int) *Queue {\n\treturn &Queue{\n\t\tmaxSize: maxSize,\n\t}\n}\n\n// Push adds a message to the queue.\nfunc (q *Queue) Push(msg *av.Packet) {\n\tq.mutex.Lock()\n\tdefer q.mutex.Unlock()\n\n\tif len(q.list) == q.maxSize {\n\t\tq.pop()\n\t}\n\n\tq.list = append(q.list, msg)\n}\n\n// Pop removes and returns a message from the queue in first to last order.\nfunc (q *Queue) Pop() *av.Packet {\n\tq.mutex.Lock()\n\tdefer q.mutex.Unlock()\n\n\tif len(q.list) == 0 {\n\t\treturn nil\n\t}\n\n\treturn q.pop()\n}\n\nfunc (q *Queue) pop() *av.Packet {\n\tx := len(q.list) - 1\n\tmsg := q.list[x]\n\tq.list = q.list[:x]\n\treturn msg\n}\n\n// Len returns the length of the queue.\nfunc (q *Queue) Len() int {\n\tq.mutex.Lock()\n\tdefer q.mutex.Unlock()\n\n\treturn len(q.list)\n}\n\n// All returns and removes all messages from the queue.\nfunc (q *Queue) All() []*av.Packet {\n\tq.mutex.Lock()\n\tdefer q.mutex.Unlock()\n\n\tcache := q.list\n\tq.list = nil\n\treturn cache\n}\n"
  },
  {
    "path": "utils/uid/rand.go",
    "content": "package uid\n\nimport \"math/rand\"\n\nvar letterRunes = []rune(\"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\nfunc RandStringRunes(n int) string {\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letterRunes[rand.Intn(len(letterRunes))]\n\t}\n\treturn string(b)\n}\n"
  },
  {
    "path": "utils/uid/uuid.go",
    "content": "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 := base64.URLEncoding.EncodeToString(id.Bytes()[:12])\n\treturn b64\n}\n"
  }
]