mirror of https://github.com/gwuhaolin/livego.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
8.7 KiB
358 lines
8.7 KiB
package core |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"io" |
|
|
|
"log" |
|
|
|
"github.com/gwuhaolin/livego/av" |
|
"github.com/gwuhaolin/livego/protocol/amf" |
|
) |
|
|
|
var ( |
|
publishLive = "live" |
|
publishRecord = "record" |
|
publishAppend = "append" |
|
) |
|
|
|
var ( |
|
ErrReq = errors.New("req error") |
|
) |
|
|
|
var ( |
|
cmdConnect = "connect" |
|
cmdFcpublish = "FCPublish" |
|
cmdReleaseStream = "releaseStream" |
|
cmdCreateStream = "createStream" |
|
cmdPublish = "publish" |
|
cmdFCUnpublish = "FCUnpublish" |
|
cmdDeleteStream = "deleteStream" |
|
cmdPlay = "play" |
|
) |
|
|
|
type ConnectInfo struct { |
|
App string `amf:"app" json:"app"` |
|
Flashver string `amf:"flashVer" json:"flashVer"` |
|
SwfUrl string `amf:"swfUrl" json:"swfUrl"` |
|
TcUrl string `amf:"tcUrl" json:"tcUrl"` |
|
Fpad bool `amf:"fpad" json:"fpad"` |
|
AudioCodecs int `amf:"audioCodecs" json:"audioCodecs"` |
|
VideoCodecs int `amf:"videoCodecs" json:"videoCodecs"` |
|
VideoFunction int `amf:"videoFunction" json:"videoFunction"` |
|
PageUrl string `amf:"pageUrl" json:"pageUrl"` |
|
ObjectEncoding int `amf:"objectEncoding" json:"objectEncoding"` |
|
} |
|
|
|
type ConnectResp struct { |
|
FMSVer string `amf:"fmsVer"` |
|
Capabilities int `amf:"capabilities"` |
|
} |
|
|
|
type ConnectEvent struct { |
|
Level string `amf:"level"` |
|
Code string `amf:"code"` |
|
Description string `amf:"description"` |
|
ObjectEncoding int `amf:"objectEncoding"` |
|
} |
|
|
|
type PublishInfo struct { |
|
Name string |
|
Type string |
|
} |
|
|
|
type ConnServer struct { |
|
done bool |
|
streamID int |
|
isPublisher bool |
|
conn *Conn |
|
transactionID int |
|
ConnInfo ConnectInfo |
|
PublishInfo PublishInfo |
|
decoder *amf.Decoder |
|
encoder *amf.Encoder |
|
bytesw *bytes.Buffer |
|
} |
|
|
|
func NewConnServer(conn *Conn) *ConnServer { |
|
return &ConnServer{ |
|
conn: conn, |
|
streamID: 1, |
|
bytesw: bytes.NewBuffer(nil), |
|
decoder: &amf.Decoder{}, |
|
encoder: &amf.Encoder{}, |
|
} |
|
} |
|
|
|
func (connServer *ConnServer) writeMsg(csid, streamID uint32, args ...interface{}) error { |
|
connServer.bytesw.Reset() |
|
for _, v := range args { |
|
if _, err := connServer.encoder.Encode(connServer.bytesw, v, amf.AMF0); err != nil { |
|
return err |
|
} |
|
} |
|
msg := connServer.bytesw.Bytes() |
|
c := ChunkStream{ |
|
Format: 0, |
|
CSID: csid, |
|
Timestamp: 0, |
|
TypeID: 20, |
|
StreamID: streamID, |
|
Length: uint32(len(msg)), |
|
Data: msg, |
|
} |
|
connServer.conn.Write(&c) |
|
return connServer.conn.Flush() |
|
} |
|
|
|
func (connServer *ConnServer) connect(vs []interface{}) error { |
|
for _, v := range vs { |
|
switch v.(type) { |
|
case string: |
|
case float64: |
|
id := int(v.(float64)) |
|
if id != 1 { |
|
return ErrReq |
|
} |
|
connServer.transactionID = id |
|
case amf.Object: |
|
obimap := v.(amf.Object) |
|
if app, ok := obimap["app"]; ok { |
|
connServer.ConnInfo.App = app.(string) |
|
} |
|
if flashVer, ok := obimap["flashVer"]; ok { |
|
connServer.ConnInfo.Flashver = flashVer.(string) |
|
} |
|
if tcurl, ok := obimap["tcUrl"]; ok { |
|
connServer.ConnInfo.TcUrl = tcurl.(string) |
|
} |
|
if encoding, ok := obimap["objectEncoding"]; ok { |
|
connServer.ConnInfo.ObjectEncoding = int(encoding.(float64)) |
|
} |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) releaseStream(vs []interface{}) error { |
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) fcPublish(vs []interface{}) error { |
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) connectResp(cur *ChunkStream) error { |
|
c := connServer.conn.NewWindowAckSize(2500000) |
|
connServer.conn.Write(&c) |
|
c = connServer.conn.NewSetPeerBandwidth(2500000) |
|
connServer.conn.Write(&c) |
|
c = connServer.conn.NewSetChunkSize(uint32(1024)) |
|
connServer.conn.Write(&c) |
|
|
|
resp := make(amf.Object) |
|
resp["fmsVer"] = "FMS/3,0,1,123" |
|
resp["capabilities"] = 31 |
|
|
|
event := make(amf.Object) |
|
event["level"] = "status" |
|
event["code"] = "NetConnection.Connect.Success" |
|
event["description"] = "Connection succeeded." |
|
event["objectEncoding"] = connServer.ConnInfo.ObjectEncoding |
|
return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, resp, event) |
|
} |
|
|
|
func (connServer *ConnServer) createStream(vs []interface{}) error { |
|
for _, v := range vs { |
|
switch v.(type) { |
|
case string: |
|
case float64: |
|
connServer.transactionID = int(v.(float64)) |
|
case amf.Object: |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) createStreamResp(cur *ChunkStream) error { |
|
return connServer.writeMsg(cur.CSID, cur.StreamID, "_result", connServer.transactionID, nil, connServer.streamID) |
|
} |
|
|
|
func (connServer *ConnServer) publishOrPlay(vs []interface{}) error { |
|
for k, v := range vs { |
|
switch v.(type) { |
|
case string: |
|
if k == 2 { |
|
connServer.PublishInfo.Name = v.(string) |
|
} else if k == 3 { |
|
connServer.PublishInfo.Type = v.(string) |
|
} |
|
case float64: |
|
id := int(v.(float64)) |
|
connServer.transactionID = id |
|
case amf.Object: |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) publishResp(cur *ChunkStream) error { |
|
event := make(amf.Object) |
|
event["level"] = "status" |
|
event["code"] = "NetStream.Publish.Start" |
|
event["description"] = "Start publising." |
|
return connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event) |
|
} |
|
|
|
func (connServer *ConnServer) playResp(cur *ChunkStream) error { |
|
connServer.conn.SetRecorded() |
|
connServer.conn.SetBegin() |
|
|
|
event := make(amf.Object) |
|
event["level"] = "status" |
|
event["code"] = "NetStream.Play.Reset" |
|
event["description"] = "Playing and resetting stream." |
|
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { |
|
return err |
|
} |
|
|
|
event["level"] = "status" |
|
event["code"] = "NetStream.Play.Start" |
|
event["description"] = "Started playing stream." |
|
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { |
|
return err |
|
} |
|
|
|
event["level"] = "status" |
|
event["code"] = "NetStream.Data.Start" |
|
event["description"] = "Started playing stream." |
|
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { |
|
return err |
|
} |
|
|
|
event["level"] = "status" |
|
event["code"] = "NetStream.Play.PublishNotify" |
|
event["description"] = "Started playing notify." |
|
if err := connServer.writeMsg(cur.CSID, cur.StreamID, "onStatus", 0, nil, event); err != nil { |
|
return err |
|
} |
|
return connServer.conn.Flush() |
|
} |
|
|
|
func (connServer *ConnServer) handleCmdMsg(c *ChunkStream) error { |
|
amfType := amf.AMF0 |
|
if c.TypeID == 17 { |
|
c.Data = c.Data[1:] |
|
} |
|
r := bytes.NewReader(c.Data) |
|
vs, err := connServer.decoder.DecodeBatch(r, amf.Version(amfType)) |
|
if err != nil && err != io.EOF { |
|
return err |
|
} |
|
// log.Printf("rtmp req: %#v", vs) |
|
switch vs[0].(type) { |
|
case string: |
|
switch vs[0].(string) { |
|
case cmdConnect: |
|
if err = connServer.connect(vs[1:]); err != nil { |
|
return err |
|
} |
|
if err = connServer.connectResp(c); err != nil { |
|
return err |
|
} |
|
case cmdCreateStream: |
|
if err = connServer.createStream(vs[1:]); err != nil { |
|
return err |
|
} |
|
if err = connServer.createStreamResp(c); err != nil { |
|
return err |
|
} |
|
case cmdPublish: |
|
if err = connServer.publishOrPlay(vs[1:]); err != nil { |
|
return err |
|
} |
|
if err = connServer.publishResp(c); err != nil { |
|
return err |
|
} |
|
connServer.done = true |
|
connServer.isPublisher = true |
|
log.Println("handle publish req done") |
|
case cmdPlay: |
|
if err = connServer.publishOrPlay(vs[1:]); err != nil { |
|
return err |
|
} |
|
if err = connServer.playResp(c); err != nil { |
|
return err |
|
} |
|
connServer.done = true |
|
connServer.isPublisher = false |
|
log.Println("handle play req done") |
|
case cmdFcpublish: |
|
connServer.fcPublish(vs) |
|
case cmdReleaseStream: |
|
connServer.releaseStream(vs) |
|
case cmdFCUnpublish: |
|
case cmdDeleteStream: |
|
default: |
|
log.Println("no support command=", vs[0].(string)) |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) ReadMsg() error { |
|
var c ChunkStream |
|
for { |
|
if err := connServer.conn.Read(&c); err != nil { |
|
return err |
|
} |
|
switch c.TypeID { |
|
case 20, 17: |
|
if err := connServer.handleCmdMsg(&c); err != nil { |
|
return err |
|
} |
|
} |
|
if connServer.done { |
|
break |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func (connServer *ConnServer) IsPublisher() bool { |
|
return connServer.isPublisher |
|
} |
|
|
|
func (connServer *ConnServer) Write(c ChunkStream) error { |
|
if c.TypeID == av.TAG_SCRIPTDATAAMF0 || |
|
c.TypeID == av.TAG_SCRIPTDATAAMF3 { |
|
var err error |
|
if c.Data, err = amf.MetaDataReform(c.Data, amf.DEL); err != nil { |
|
return err |
|
} |
|
c.Length = uint32(len(c.Data)) |
|
} |
|
return connServer.conn.Write(&c) |
|
} |
|
|
|
func (connServer *ConnServer) Flush() error { |
|
return connServer.conn.Flush() |
|
} |
|
|
|
func (connServer *ConnServer) Read(c *ChunkStream) (err error) { |
|
return connServer.conn.Read(c) |
|
} |
|
|
|
func (connServer *ConnServer) GetInfo() (app string, name string, url string) { |
|
app = connServer.ConnInfo.App |
|
name = connServer.PublishInfo.Name |
|
url = connServer.ConnInfo.TcUrl + "/" + connServer.PublishInfo.Name |
|
return |
|
} |
|
|
|
func (connServer *ConnServer) Close(err error) { |
|
connServer.conn.Close() |
|
}
|
|
|