mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
270 lines
5.9 KiB
Go
270 lines
5.9 KiB
Go
package rtmp
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
|
|
task "github.com/langhuihui/gotask"
|
|
pkg "m7s.live/v5/pkg"
|
|
"m7s.live/v5/pkg/config"
|
|
|
|
"m7s.live/v5"
|
|
)
|
|
|
|
// Fixed progress steps for RTMP pull workflow
|
|
var rtmpPullSteps = []pkg.StepDef{
|
|
{Name: pkg.StepPublish, Description: "Publishing stream"},
|
|
{Name: pkg.StepURLParsing, Description: "Parsing RTMP URL"},
|
|
{Name: pkg.StepConnection, Description: "Connecting to RTMP server"},
|
|
{Name: pkg.StepHandshake, Description: "Performing RTMP handshake"},
|
|
{Name: pkg.StepStreaming, Description: "Receiving media stream"},
|
|
}
|
|
|
|
type Client struct {
|
|
NetStream
|
|
chunkSize int
|
|
u *url.URL
|
|
}
|
|
|
|
func (c *Client) GetPullJob() *m7s.PullJob {
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) GetPushJob() *m7s.PushJob {
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) commonStart(addr string) (err error) {
|
|
c.u, err = url.Parse(addr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ps := strings.Split(c.u.Path, "/")
|
|
if len(ps) < 2 {
|
|
return errors.New("illegal rtmp url")
|
|
}
|
|
|
|
isRtmps := c.u.Scheme == "rtmps"
|
|
if strings.Count(c.u.Host, ":") == 0 {
|
|
if isRtmps {
|
|
c.u.Host += ":443"
|
|
} else {
|
|
c.u.Host += ":1935"
|
|
}
|
|
}
|
|
var conn net.Conn
|
|
if isRtmps {
|
|
var tlsconn *tls.Conn
|
|
tlsconn, err = tls.Dial("tcp", c.u.Host, &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
})
|
|
conn = tlsconn
|
|
} else {
|
|
conn, err = net.Dial("tcp", c.u.Host)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Init(conn)
|
|
c.SetDescription("local", conn.LocalAddr().String())
|
|
c.Info("connect")
|
|
c.WriteChunkSize = c.chunkSize
|
|
c.AppName = strings.Join(ps[1:len(ps)-1], "/")
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *Client) commonRun(handler func(commander Commander) error) (err error) {
|
|
if err = c.ClientHandshake(); err != nil {
|
|
return
|
|
}
|
|
err = c.SendMessage(RTMP_MSG_CHUNK_SIZE, Uint32Message(c.chunkSize))
|
|
if err != nil {
|
|
return
|
|
}
|
|
path := c.u.Path
|
|
if len(c.u.Query()) != 0 {
|
|
path += "?" + c.u.RawQuery
|
|
}
|
|
err = c.SendMessage(RTMP_MSG_AMF0_COMMAND, &CallMessage{
|
|
CommandMessage{"connect", 1},
|
|
map[string]any{
|
|
"app": c.AppName,
|
|
"flashVer": "monibuca/" + m7s.Version,
|
|
"swfUrl": c.u.String(),
|
|
"tcUrl": strings.TrimSuffix(c.u.String(), path) + "/" + c.AppName,
|
|
},
|
|
nil,
|
|
})
|
|
var commander Commander
|
|
for err == nil {
|
|
if commander, err = c.RecvMessage(); err != nil {
|
|
return err
|
|
}
|
|
cmd := commander.GetCommand()
|
|
c.Debug(cmd.CommandName)
|
|
switch cmd.CommandName {
|
|
case Response_Result, Response_OnStatus:
|
|
switch response := commander.(type) {
|
|
case *ResponseMessage:
|
|
c.SetDescriptions(response.Properties)
|
|
if response.Infomation["code"] == NetConnection_Connect_Success {
|
|
err = c.SendMessage(RTMP_MSG_AMF0_COMMAND, &CommandMessage{"createStream", 2})
|
|
if err == nil {
|
|
c.Info("connected")
|
|
c.OnConnected()
|
|
}
|
|
}
|
|
case *ResponseCreateStreamMessage:
|
|
c.StreamID = response.StreamId
|
|
if handler != nil {
|
|
if err = handler(commander); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type Puller struct {
|
|
Client
|
|
pullCtx m7s.PullJob
|
|
}
|
|
|
|
func (p *Puller) GetPullJob() *m7s.PullJob {
|
|
return &p.pullCtx
|
|
}
|
|
|
|
func (p *Puller) Start() (err error) {
|
|
// Initialize progress tracking for pull operations
|
|
p.pullCtx.SetProgressStepsDefs(rtmpPullSteps)
|
|
|
|
addr := p.pullCtx.Connection.RemoteURL
|
|
err = p.pullCtx.Publish()
|
|
if err != nil {
|
|
p.pullCtx.Fail(err.Error())
|
|
return
|
|
}
|
|
|
|
p.pullCtx.GoToStepConst(pkg.StepURLParsing)
|
|
|
|
err = p.commonStart(addr)
|
|
if err != nil {
|
|
p.pullCtx.Fail(err.Error())
|
|
return
|
|
}
|
|
|
|
p.pullCtx.GoToStepConst(pkg.StepConnection)
|
|
p.pullCtx.GoToStepConst(pkg.StepHandshake)
|
|
p.pullCtx.GoToStepConst(pkg.StepStreaming)
|
|
|
|
return
|
|
}
|
|
|
|
func (p *Puller) Run() (err error) {
|
|
return p.commonRun(func(commander Commander) error {
|
|
switch response := commander.(type) {
|
|
case *ResponseCreateStreamMessage:
|
|
p.StreamID = response.StreamId
|
|
m := &PlayMessage{}
|
|
m.StreamId = response.StreamId
|
|
m.TransactionId = 4
|
|
m.CommandMessage.CommandName = "play"
|
|
URL, _ := url.Parse(p.pullCtx.Connection.RemoteURL)
|
|
ps := strings.Split(URL.Path, "/")
|
|
args := URL.Query()
|
|
m.StreamName = ps[len(ps)-1]
|
|
if len(args) > 0 {
|
|
m.StreamName += "?" + args.Encode()
|
|
}
|
|
if p.pullCtx.Publisher != nil {
|
|
p.Writers[response.StreamId] = &struct {
|
|
m7s.PublishWriter[*AudioFrame, *VideoFrame]
|
|
*m7s.Publisher
|
|
}{Publisher: p.pullCtx.Publisher}
|
|
}
|
|
return p.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type Pusher struct {
|
|
Client
|
|
pushCtx m7s.PushJob
|
|
}
|
|
|
|
func (p *Pusher) GetPushJob() *m7s.PushJob {
|
|
return &p.pushCtx
|
|
}
|
|
|
|
func (p *Pusher) Start() (err error) {
|
|
return p.commonStart(p.pushCtx.Connection.RemoteURL)
|
|
}
|
|
|
|
func (p *Pusher) Run() (err error) {
|
|
return p.commonRun(func(commander Commander) error {
|
|
switch response := commander.(type) {
|
|
case *ResponseCreateStreamMessage:
|
|
p.StreamID = response.StreamId
|
|
err = p.pushCtx.Subscribe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
URL, _ := url.Parse(p.pushCtx.Connection.RemoteURL)
|
|
_, streamPath, _ := strings.Cut(URL.Path, "/")
|
|
_, streamPath, _ = strings.Cut(streamPath, "/")
|
|
args := URL.Query()
|
|
if len(args) > 0 {
|
|
streamPath += "?" + args.Encode()
|
|
}
|
|
return p.SendMessage(RTMP_MSG_AMF0_COMMAND, &PublishMessage{
|
|
CURDStreamMessage{
|
|
CommandMessage{
|
|
"publish",
|
|
1,
|
|
},
|
|
response.StreamId,
|
|
},
|
|
streamPath,
|
|
"live",
|
|
})
|
|
case *ResponsePublishMessage:
|
|
if response.Infomation["code"] == NetStream_Publish_Start {
|
|
p.Subscribe(p.pushCtx.Subscriber)
|
|
} else {
|
|
return errors.New(response.Infomation["code"].(string))
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func NewPuller(_ config.Pull) m7s.IPuller {
|
|
ret := &Puller{
|
|
Client: Client{
|
|
chunkSize: 4096,
|
|
},
|
|
}
|
|
ret.NetConnection = &NetConnection{}
|
|
ret.SetDescription(task.OwnerTypeKey, "RTMPPuller")
|
|
return ret
|
|
}
|
|
|
|
func NewPusher() m7s.IPusher {
|
|
ret := &Pusher{
|
|
Client: Client{
|
|
chunkSize: 4096,
|
|
},
|
|
}
|
|
ret.NetConnection = &NetConnection{}
|
|
ret.SetDescription(task.OwnerTypeKey, "RTMPPusher")
|
|
return ret
|
|
}
|