func (rtmp *Rtmp) recvChunk(findTrId int, waitPause bool) (done, incomplete, trFound, pauseFound bool, trData interface{}, err error) {
	ts, msg_t, res, rdbytes, err := decodeOne(rtmp.conn, rtmp.chunkSizeRecv, rtmp.chunkInfo)
	if err != nil {
		switch err.(type) {
		case *net.OpError:
			return
		case *DecodeError:
			fmt.Printf("Please retry: RTMP: %v\n", err.Error())
			incomplete = true
			err = nil
			return
		}
		return
	}
	ts = ts + rtmp.startTime
	rtmp.totalReadBytes += rdbytes
	rtmp.readCount += rdbytes
	if rtmp.readCount >= (rtmp.windowSize / 2) {
		rtmp.readCount = 0
		if err = rtmp.acknowledgement(); err != nil {
			return
		}
	}
	if true {
		if rtmp.duration > 0 {
			switch msg_t {
			case TID_AUDIO, TID_VIDEO, TID_AGGREGATE:
				if ts >= rtmp.nextLogTs {
					fmt.Printf("#%8d/%d(%4.1f%%) : %s\n", ts, rtmp.duration, float64(ts)/float64(rtmp.duration)*100, rtmp.flvName)
					rtmp.nextLogTs = ts + 10000
				}
			}
		} else {
			switch msg_t {
			case TID_AUDIO, TID_VIDEO, TID_AGGREGATE:
				if ts >= rtmp.nextLogTs {
					fmt.Printf("#%8d : %s\n", ts, rtmp.flvName)
					rtmp.nextLogTs = ts + 10000
				}
			}
		}
	}
	switch msg_t {
	case TID_AUDIO:
		if ts > rtmp.timestamp {
			rtmp.timestamp = ts
		}
		if err = rtmp.writeAudio(res.(*bytes.Buffer), ts); err != nil {
			return
		}
	case TID_VIDEO:
		if ts > rtmp.timestamp {
			rtmp.timestamp = ts
		}
		if err = rtmp.writeVideo(res.(*bytes.Buffer), ts); err != nil {
			return
		}
	case TID_AGGREGATE:
		if ts > rtmp.timestamp {
			rtmp.timestamp = ts
		}
		var fstTs int
		for i, v := range res.([]message) {
			var tsAggr int
			if rtmp.fixAggrTimestamp {
				var delta int
				if i == 0 {
					fstTs = v.timestamp
				}
				delta = v.timestamp - fstTs
				tsAggr = ts + delta
			} else {
				if i == 0 {
					if ts != v.timestamp {
						err = fmt.Errorf("aggregate timestamp incorrect: ts:(%v) vs aggr[0].ts(%v)", ts, v.timestamp)
						return
					}
				}
				tsAggr = v.timestamp
			}
			if   rtmp.duration > 0 {
				switch v.msg_t {
				case TID_AUDIO, TID_VIDEO:
				}
			}
			switch v.msg_t {
			case TID_AUDIO:
				if err = rtmp.writeAudio(v.data, tsAggr); err != nil {
					return
				}
			case TID_VIDEO:
				if err = rtmp.writeVideo(v.data, tsAggr); err != nil {
					return
				}
			}
		}
	case TID_AMF0DATA, TID_AMF3DATA:
		objs.PrintAsJson(res)
		list, ok := res.([]interface{})
		if !ok {
			err = fmt.Errorf("result AMF Data is not array")
			return
		}
		if len(list) >= 2 {
			name, ok := list[0].(string)
			if !ok {
				err = fmt.Errorf("result AMF Data[0] is not string")
				return
			}
			switch name {
			case "onPlayStatus":
				done, incomplete, pauseFound, err = rtmp.CheckStatus("onPlayStatus", ts, list[1], waitPause)
			case "onMetaData":
				dur, ok := objs.FindFloat64(list[1], "duration")
				if ok {
					rtmp.duration = int(dur * 1000)
				} else {
					if rtmp.isRecorded {
						fmt.Println("[WARN] onMetaData: duration not found")
					}
				}
				if meta, ok := list[1].(map[string]interface{}); ok {
					rtmp.writeMetaData(meta, ts)
				}
				_, ok = objs.Find(list[1], "videoframerate")
				if ok {
					rtmp.VideoExists = true
				}
			}
		}
	case TID_AMF0COMMAND, TID_AMF3COMMAND:
		objs.PrintAsJson(res)
		list, ok := res.([]interface{})
		if !ok {
			err = fmt.Errorf("result AMF Command is not array")
			return
		}
		if len(list) >= 3 {
			name, ok := list[0].(string)
			if !ok {
				err = fmt.Errorf("result AMF Command name is not string")
				return
			}
			trIdFloat, ok := list[1].(float64)
			if !ok {
				err = fmt.Errorf("result AMF Command transaction id is not number")
				return
			}
			trId := int(trIdFloat)
			if (trId > 0) && (trId == findTrId) {
				trFound = true
				if len(list) >= 4 {
					trData = list[3]
				}
			}
			switch name {
			case "_error", "close":
				err = fmt.Errorf("AMF command not success: transaction id(%d) -> %s", trId, name)
				return
			case "onStatus":
				done, incomplete, pauseFound, err = rtmp.CheckStatus("onStatus", ts, list[3], waitPause)
			}
		}
	case TID_SETCHUNKSIZE:
		rtmp.chunkSizeRecv = res.(int)
	case TID_WINDOW_ACK_SIZE:
		rtmp.windowSize = res.(int)
	case TID_USERCONTROL:
		switch res.([]int)[0] {
		case UC_PINGREQUEST:
			if err = rtmp.pingResponse(res.([]int)[1]); err != nil {
				return
			}
		case UC_STREAMBEGIN:
			rtmp.streamId = res.([]int)[1]
		case UC_STREAMISRECORDED:
			fmt.Printf("stream is recorded\n")
			rtmp.isRecorded = true
		case UC_BUFFEREMPTY:
			if rtmp.isRecorded {
				fmt.Printf("required Seek: %d\n", rtmp.timestamp)
				rtmp.PauseRaw()
				incomplete = true
				return
				if rtmp.noSeek {
					incomplete = true
					return
				}
				ts := rtmp.timestamp - 10000
				if ts < 0 {
					ts = 0
				}
				done, incomplete, err = rtmp.PauseUnpause(ts)
				if done || incomplete || err != nil {
					return
				}
			}
		}
	default:
	}
	return
}
trId: transaction id to find
package rtmps
import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net"
	"regexp"
	"time"
	"github.com/himananiito/livedl/amf"
	"github.com/himananiito/livedl/files"
	"github.com/himananiito/livedl/flvs"
	"github.com/himananiito/livedl/objs"
)
type DecodeError struct {
	Fun string
	Msg string
}
func (e *DecodeError) Error() string {
	return fmt.Sprintf("%s: %s", e.Fun, e.Msg)
}
type chunkInfo struct {
	timestampField  int
	timestampDelta  int
	timestampActual int
	msgLength       int
	msgTypeId       int
	msgStreamId     int
}
type Rtmp struct {
	proto      string  
	address    string  
	app        string  
	tcUrl      string  
	swfUrl     string  
	pageUrl    string  
	connectOpt []interface{}
	conn          *net.TCPConn       
	chunkSizeSend int                
	chunkSizeRecv int                
	transactionId int                
	windowSize    int                
	chunkInfo     map[int]chunkInfo  
	readCount      int  
	totalReadBytes int  
	isRecorded     bool
	timestamp int  
	duration  int
	flvName string
	flv     *flvs.Flv
	fixAggrTimestamp bool
	streamId         int
	nextLogTs        int
	VideoExists bool
	noSeek      bool
	flush       bool
	startTime int
}
func NewRtmp(tc, swf, page string, opt ...interface{}) (rtmp *Rtmp, err error) {
	re := regexp.MustCompile(`\A(\w+): 
	mstr := re.FindStringSubmatch(tc)
	if mstr == nil {
		err = fmt.Errorf("tcUrl incorrect: %v", tc)
		return
	}
	rtmp = &Rtmp{
		proto:      mstr[1],
		address:    mstr[2],
		app:        mstr[3],
		tcUrl:      tc,
		swfUrl:     swf,
		pageUrl:    page,
		connectOpt: opt,
	}
	return
}
func (rtmp *Rtmp) Connect() (err error) {
	if rtmp.conn != nil {
		rtmp.conn.Close()
		rtmp.conn = nil
		time.Sleep(3)
	}
	rtmp.windowSize = 2500000
	rtmp.chunkInfo = make(map[int]chunkInfo)
	rtmp.chunkSizeSend = 128
	rtmp.chunkSizeRecv = 128
	rtmp.transactionId = 1
	rtmp.readCount = 0
	rtmp.totalReadBytes = 0
	err = rtmp.connect(
		rtmp.app,
		rtmp.tcUrl,
		rtmp.swfUrl,
		rtmp.pageUrl,
		rtmp.connectOpt...,
	)
	return
}
func (rtmp *Rtmp) SetFlush(b bool) {
	rtmp.flush = b
}
func (rtmp *Rtmp) SetNoSeek(b bool) {
	rtmp.noSeek = b
}
func (rtmp *Rtmp) SetConnectOpt(opt ...interface{}) {
	rtmp.connectOpt = opt
}
func (rtmp *Rtmp) connect(app, tc, swf, page string, opt ...interface{}) (err error) {
	raddr, err := net.ResolveTCPAddr("tcp", rtmp.address)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
	switch rtmp.proto {
	case "rtmp":
		conn, e := net.DialTCP("tcp", nil, raddr)
		if e != nil {
			err = e
			return
		}
		rtmp.conn = conn
	default:
		err = fmt.Errorf("Unknown protocol: %v", rtmp.proto)
		return
	}
	err = handshake(rtmp.conn)
	if err != nil {
		rtmp.conn.Close()
		return
	}
	var data []interface{}
	data = append(data, map[string]interface{}{
		"app":            app,
		"flashVer":       "WIN 29,0,0,113",
		"swfUrl":         swf,
		"tcUrl":          tc,
		"fpad":           false,
		"capabilities":   239,
		"audioCodecs":    0xFFF,  
		"videoCodecs":    0xFF,   
		"videoFunction":  1,
		"pageUrl":        page,
		"objectEncoding": 3,
	})
	for _, o := range opt {
		data = append(data, o)
	}
	_, err = rtmp.Command("connect", data)
	return
}
const (
	NORMAL = iota
	COMMAND
	PAUSE
	TEST
)
func (rtmp *Rtmp) wait(findTrId int, pause bool, testTimeout int) (done, incomplete bool, trData interface{}, err error) {
	var mode int
	var endUnix int64
	var endTime time.Time
	if findTrId >= 0 {
		mode = COMMAND
	} else if pause {
		mode = PAUSE
	} else if testTimeout > 0 {
		mode = TEST
		endUnix = time.Now().Unix() + int64(testTimeout)
		endTime = time.Unix(endUnix, 0)
	}
	if mode != COMMAND {
		findTrId = -1
	}
	for {
		if mode == TEST {
			rtmp.conn.SetReadDeadline(endTime)
		} else {
			rtmp.conn.SetReadDeadline(time.Now().Add(300 * time.Second))
		}
		__done, __incomplete, trFound, pause, __trData, e := rtmp.recvChunk(findTrId, pause)
		if e != nil {
			err = e
			return
		}
		if __done || __incomplete {
			done = __done
			incomplete = __incomplete
			return
		}
		switch mode {
		case COMMAND:
			if trFound {
				trData = __trData
				return
			}
		case PAUSE:
			if pause {
				return
			}
		case TEST:
			if time.Now().Unix() >= endUnix {
				return
			}
		}
	}
}
func (rtmp *Rtmp) WaitPause() (done, incomplete bool, err error) {
	done, incomplete, _, err = rtmp.wait(-1, true, -1)
	return
}
func (rtmp *Rtmp) WaitTest(testTimeout int) (done, incomplete bool, err error) {
	done, incomplete, _, err = rtmp.wait(-1, false, testTimeout)
	return
}
func (rtmp *Rtmp) Wait() (done, incomplete bool, err error) {
	done, incomplete, _, err = rtmp.wait(-1, false, -1)
	return
}
func (rtmp *Rtmp) waitCommand(findTrId int) (done, incomplete bool, trData interface{}, err error) {
	done, incomplete, trData, err = rtmp.wait(findTrId, false, -1)
	return
}
func (rtmp *Rtmp) SetFlvName(name string) {
	rtmp.flvName = name
}
func (rtmp *Rtmp) openFlv(incr bool) (err error) {
	if rtmp.flvName == "" {
		err = fmt.Errorf("FLV file name not set: call SetFlvName(string)")
		return
	}
	var fileName string
	if incr {
		if fileName, err = files.GetFileNameNext(rtmp.flvName); err != nil {
			return
		}
	} else {
		fileName = rtmp.flvName
	}
	flv, err := flvs.Open(fileName)
	if err != nil {
		return
	}
	rtmp.flv = flv
	return
}
func (rtmp *Rtmp) GetTimestamp() int {
	return rtmp.timestamp
}
func (rtmp *Rtmp) SetTimestamp(t int) {
	rtmp.timestamp = t
}
func (rtmp *Rtmp) writeMetaData(body map[string]interface{}, ts int) (err error) {
	if rtmp.flv == nil {
		if err = rtmp.openFlv(false); err != nil {
			return
		}
	}
	data := []interface{}{}
	data = append(data, "onMetaData")
	data = append(data, body)
	dat, err := amf.EncodeAmf0(data, true)
	rdr := bytes.NewBuffer(dat)
	err = rtmp.flv.WriteMetaData(rdr, ts)
	return
}
func (rtmp *Rtmp) writeAudio(rdr *bytes.Buffer, ts int) (err error) {
	if rtmp.flv == nil {
		if err = rtmp.openFlv(false); err != nil {
			return
		}
	}
	err = rtmp.flv.WriteAudio(rdr, ts)
	return
}
func (rtmp *Rtmp) writeVideo(rdr *bytes.Buffer, ts int) (err error) {
	if rtmp.flv == nil {
		if err = rtmp.openFlv(false); err != nil {
			return
		}
	}  
	err = rtmp.flv.WriteVideo(rdr, ts)
	return
}
func (rtmp *Rtmp) SetFixAggrTimestamp(sw bool) {
	rtmp.fixAggrTimestamp = sw
}
func (rtmp *Rtmp) CheckStatus(label string, ts int, data interface{}, waitPause bool) (done, incomplete, pauseFound bool, err error) {
	code, ok := objs.FindString(data, "code")
	if !ok {
		err = fmt.Errorf("%s: code Not found", label)
		return
	}
	switch code {
	case "NetStream.Pause.Notify":
		if waitPause {
			pauseFound = true
		}
	case "NetStream.Unpause.Notify":
	case "NetStream.Play.Stop":
	case "NetStream.Play.Complete":
		fmt.Printf("NetStream.Play.Complete: last timestamp: %d(flv)\n", rtmp.flv.GetLastTimestamp())
		if (ts + 1000) > rtmp.duration {
			done = true
		} else {
			incomplete = true
		}
	case "NetStream.Play.Start":
	case "NetStream.Play.Reset":
	case "NetStream.Seek.Notify":
	case "NetStream.Play.Failed":
		done = true
	default:
		fmt.Printf("[FIXME] Unknown Code: %s\n", code)
	}
	return
}