Compare commits

..

9 Commits

Author SHA1 Message Date
langhuihui
67da93d8e2 增加重连时的判断 2020-07-09 22:04:00 +08:00
unknown
cb733b368f 增加重连功能 2020-07-09 20:06:35 +08:00
langhuihui
fadeccddab 更新依赖engine的版本 2020-06-09 07:12:20 +08:00
langhuihui
93df7632a6 修正升级带来的bug 2020-06-09 07:09:25 +08:00
langhuihui
53c4788df2 改用RTP插件 2020-05-31 10:01:29 +08:00
langhuihui
f5bdd6a298 内存复用 2020-05-24 22:58:38 +08:00
李宇翔
eaddc60775 过滤开头的NonIDR 2020-05-21 18:16:56 +08:00
langhuihui
655170cb24 修复中止拉流的操作 2020-05-20 09:55:39 +08:00
李宇翔
55bd2ce785 增加中止操作 2020-05-19 19:25:44 +08:00
14 changed files with 514 additions and 655 deletions

215
client.go
View File

@@ -5,9 +5,8 @@ import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
. "github.com/Monibuca/engine/v2"
"github.com/pixelbender/go-sdp/sdp"
"io" "io"
"net" "net"
"net/url" "net/url"
@@ -15,11 +14,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
. "github.com/Monibuca/engine/v2"
. "github.com/Monibuca/plugin-rtp"
) )
// PullStream 从外部拉流 // PullStream 从外部拉流
func (rtsp *RTSP) PullStream(streamPath string, rtspUrl string) (result bool) { func (rtsp *RTSP) PullStream(streamPath string, rtspUrl string) (err error) {
if result = rtsp.Publisher.Publish(streamPath); result { if result := rtsp.Publish(streamPath); result {
rtsp.Stream.Type = "RTSP" rtsp.Stream.Type = "RTSP"
rtsp.RTSPInfo.StreamInfo = &rtsp.Stream.StreamInfo rtsp.RTSPInfo.StreamInfo = &rtsp.Stream.StreamInfo
rtsp.TransType = TRANS_TYPE_TCP rtsp.TransType = TRANS_TYPE_TCP
@@ -28,15 +30,16 @@ func (rtsp *RTSP) PullStream(streamPath string, rtspUrl string) (result bool) {
rtsp.aRTPChannel = 2 rtsp.aRTPChannel = 2
rtsp.aRTPControlChannel = 3 rtsp.aRTPControlChannel = 3
rtsp.URL = rtspUrl rtsp.URL = rtspUrl
if err := rtsp.requestStream();err != nil { if err = rtsp.requestStream(); err != nil {
Println(err)
rtsp.Close() rtsp.Close()
return false return
} }
go rtsp.startStream() go rtsp.startStream()
collection.Store(streamPath, rtsp) collection.Store(streamPath, rtsp)
// go rtsp.run() return
} }
return return errors.New("publish badname")
} }
func DigestAuth(authLine string, method string, URL string) (string, error) { func DigestAuth(authLine string, method string, URL string) (string, error) {
l, err := url.Parse(URL) l, err := url.Parse(URL)
@@ -184,104 +187,110 @@ func (client *RTSP) requestStream() (err error) {
return err return err
} }
} }
_sdp, err := sdp.ParseString(resp.Body)
if err != nil {
return err
}
client.Sdp = _sdp
client.SDPRaw = resp.Body client.SDPRaw = resp.Body
client.SDPMap = ParseSDP(client.SDPRaw) client.SDPMap = ParseSDP(client.SDPRaw)
session := "" session := ""
for _, media := range _sdp.Media { if videoInfo, ok := client.SDPMap["video"]; ok {
switch media.Type { client.VControl = videoInfo.Control
case "video": client.VCodec = videoInfo.Codec
client.VControl = media.Attributes.Get("control") client.WriteSPS(videoInfo.SpropParameterSets[0])
client.VCodec = media.Format[0].Name client.WritePPS(videoInfo.SpropParameterSets[1])
client.SPS = client.SDPMap["video"].SpropParameterSets[0] var _url = ""
client.PPS = client.SDPMap["video"].SpropParameterSets[1] if strings.Index(strings.ToLower(client.VControl), "rtsp://") == 0 {
var _url = "" _url = client.VControl
if strings.Index(strings.ToLower(client.VControl), "rtsp://") == 0 { } else {
_url = client.VControl _url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.VControl, "/")
} else {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.VControl, "/")
}
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
} else {
if client.UDPServer == nil {
client.UDPServer = &UDPServer{Session: client}
}
//RTP/AVP;unicast;client_port=64864-64865
err = client.UDPServer.SetupVideo()
if err != nil {
Printf("Setup video err.%v", err)
return err
}
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.VPort, client.UDPServer.VControlPort)
client.Conn.timeout = 0 // UDP ignore timeout
}
if session != "" {
headers["Session"] = session
}
Printf("Parse DESCRIBE response, VIDEO VControl:%s, VCode:%s, url:%s,Session:%s,vRTPChannel:%d,vRTPControlChannel:%d", client.VControl, client.VCodec, _url, session, client.vRTPChannel, client.vRTPControlChannel)
resp, err = client.RequestWithPath("SETUP", _url, headers, true)
if err != nil {
return err
}
session, _ = resp.Header["Session"].(string)
case "audio":
client.AControl = media.Attributes.Get("control")
client.ACodec = media.Format[0].Name
client.AudioSpecificConfig = client.SDPMap["audio"].Config
var _url = ""
if strings.Index(strings.ToLower(client.AControl), "rtsp://") == 0 {
_url = client.AControl
} else {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.AControl, "/")
}
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel)
} else {
if client.UDPServer == nil {
client.UDPServer = &UDPServer{Session: client}
}
err = client.UDPServer.SetupAudio()
if err != nil {
Printf("Setup audio err.%v", err)
return err
}
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.APort, client.UDPServer.AControlPort)
client.Conn.timeout = 0 // UDP ignore timeout
}
if session != "" {
headers["Session"] = session
}
Printf("Parse DESCRIBE response, AUDIO AControl:%s, ACodec:%s, url:%s,Session:%s, aRTPChannel:%d,aRTPControlChannel:%d", client.AControl, client.ACodec, _url, session, client.aRTPChannel, client.aRTPControlChannel)
resp, err = client.RequestWithPath("SETUP", _url, headers, true)
if err != nil {
return err
}
session, _ = resp.Header["Session"].(string)
} }
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
} else {
if client.UDPServer == nil {
client.UDPServer = &UDPServer{Session: client}
}
//RTP/AVP;unicast;client_port=64864-64865
err = client.UDPServer.SetupVideo()
if err != nil {
Printf("Setup video err.%v", err)
return err
}
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.VPort, client.UDPServer.VControlPort)
client.Conn.timeout = 0 // UDP ignore timeout
}
if session != "" {
headers["Session"] = session
}
Printf("Parse DESCRIBE response, VIDEO VControl:%s, VCode:%s, url:%s,Session:%s,vRTPChannel:%d,vRTPControlChannel:%d", client.VControl, client.VCodec, _url, session, client.vRTPChannel, client.vRTPControlChannel)
if resp, err = client.RequestWithPath("SETUP", _url, headers, true); err != nil {
return err
}
session, _ = resp.Header["Session"].(string)
session = strings.Split(session, ";")[0]
}
if audioInfo, ok := client.SDPMap["audio"]; ok {
client.AControl = audioInfo.Control
client.ACodec = audioInfo.Codec
if len(audioInfo.Config) < 2 {
Printf("Setup audio err codec not support: %s", client.ACodec)
} else {
client.WriteASC(audioInfo.Config)
}
var _url = ""
if strings.Index(strings.ToLower(client.AControl), "rtsp://") == 0 {
_url = client.AControl
} else {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.AControl, "/")
}
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel)
} else {
if client.UDPServer == nil {
client.UDPServer = &UDPServer{Session: client}
}
err = client.UDPServer.SetupAudio()
if err != nil {
Printf("Setup audio err.%v", err)
return err
}
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.APort, client.UDPServer.AControlPort)
client.Conn.timeout = 0 // UDP ignore timeout
}
if session != "" {
headers["Session"] = session
}
Printf("Parse DESCRIBE response, AUDIO AControl:%s, ACodec:%s, url:%s,Session:%s, aRTPChannel:%d,aRTPControlChannel:%d", client.AControl, client.ACodec, _url, session, client.aRTPChannel, client.aRTPControlChannel)
if resp, err = client.RequestWithPath("SETUP", _url, headers, true); err != nil {
return err
}
session, _ = resp.Header["Session"].(string)
session = strings.Split(session, ";")[0]
} }
headers = make(map[string]string) headers = make(map[string]string)
if session != "" { if session != "" {
headers["Session"] = session headers["Session"] = session
} }
resp, err = client.Request("PLAY", headers) resp, err = client.Request("PLAY", headers)
if err != nil { return err
return err
}
return nil
} }
func (client *RTSP) startStream() { func (client *RTSP) startStream() {
//startTime := time.Now() //startTime := time.Now()
//loggerTime := time.Now().Add(-10 * time.Second) //loggerTime := time.Now().Add(-10 * time.Second)
defer client.Stop() defer func() {
for { if client.Err() == nil && config.Reconnect {
Printf("reconnecting:", client.URL)
if err := client.requestStream(); err != nil {
Println(err)
client.Close()
return
}
go client.startStream()
} else {
client.Stop()
}
}()
for client.Err() == nil {
//if client.OptionIntervalMillis > 0 { //if client.OptionIntervalMillis > 0 {
// if time.Since(startTime) > time.Duration(client.OptionIntervalMillis)*time.Millisecond { // if time.Since(startTime) > time.Duration(client.OptionIntervalMillis)*time.Millisecond {
// startTime = time.Now() // startTime = time.Now()
@@ -315,38 +324,30 @@ func (client *RTSP) startStream() {
Printf("io.ReadFull err:%v", err) Printf("io.ReadFull err:%v", err)
return return
} }
rtpBuf := content
var pack *RTPPack var pack *RTPPack
switch channel { switch channel {
case client.aRTPChannel: case client.aRTPChannel:
pack = &RTPPack{ pack = &RTPPack{
Type: RTP_TYPE_AUDIO, Type: RTP_TYPE_AUDIO,
Buffer: rtpBuf,
} }
case client.aRTPControlChannel: case client.aRTPControlChannel:
pack = &RTPPack{ pack = &RTPPack{
Type: RTP_TYPE_AUDIOCONTROL, Type: RTP_TYPE_AUDIOCONTROL,
Buffer: rtpBuf,
} }
case client.vRTPChannel: case client.vRTPChannel:
pack = &RTPPack{ pack = &RTPPack{
Type: RTP_TYPE_VIDEO, Type: RTP_TYPE_VIDEO,
Buffer: rtpBuf,
} }
case client.vRTPControlChannel: case client.vRTPControlChannel:
pack = &RTPPack{ pack = &RTPPack{
Type: RTP_TYPE_VIDEOCONTROL, Type: RTP_TYPE_VIDEOCONTROL,
Buffer: rtpBuf,
} }
default: default:
Printf("unknow rtp pack type, channel:%v", channel) Printf("unknow rtp pack type, channel:%v", channel)
continue continue
} }
if pack == nil { pack.Unmarshal(content)
Printf("session tcp got nil rtp pack")
continue
}
//if client.debugLogEnable { //if client.debugLogEnable {
// rtp := ParseRTP(pack.Buffer) // rtp := ParseRTP(pack.Buffer)
// if rtp != nil { // if rtp != nil {
@@ -365,13 +366,13 @@ func (client *RTSP) startStream() {
//} //}
client.InBytes += int(length + 4) client.InBytes += int(length + 4)
client.handleRTP(pack) client.PushPack(pack)
default: // rtsp default: // rtsp
builder := bytes.Buffer{} builder := bytes.Buffer{}
builder.WriteByte(b) builder.WriteByte(b)
contentLen := 0 contentLen := 0
for { for client.Err() == nil {
line, prefix, err := client.connRW.ReadLine() line, prefix, err := client.connRW.ReadLine()
if err != nil { if err != nil {
Printf("client.connRW.ReadLine err:%v", err) Printf("client.connRW.ReadLine err:%v", err)

7
go.mod
View File

@@ -3,9 +3,8 @@ module github.com/Monibuca/plugin-rtsp
go 1.13 go 1.13
require ( require (
github.com/Monibuca/engine/v2 v2.0.0 github.com/Monibuca/engine/v2 v2.1.2
github.com/jinzhu/gorm v1.9.12 // indirect github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb
github.com/pixelbender/go-sdp v1.0.0 github.com/pion/rtp v1.5.4 // indirect
github.com/reactivex/rxgo v1.0.0 // indirect
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
) )

53
go.sum
View File

@@ -1,57 +1,33 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/EasyDarwin/EasyDarwin v8.1.0+incompatible h1:Rr8dRbZtcJhiJvGx5Vs7IENM6RUUwGkZiIj5+WrNhm8= github.com/Monibuca/engine/v2 v2.1.0 h1:pHeDCEFDusKFsZLpconYj8U5LCaWApnjd+yQRHYgQsQ=
github.com/EasyDarwin/EasyDarwin v8.1.0+incompatible/go.mod h1:xnmC+Q2+wugEDpQGxivSFNYPOhmNlIQHBfl0hMeriSU= github.com/Monibuca/engine/v2 v2.1.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
github.com/Monibuca/engine v1.2.1 h1:TJmC6eZA1lR1MScWgempZLiEZD4T6aY/nn/rlQ9UdK8= github.com/Monibuca/engine/v2 v2.1.2 h1:7dUrHJAPEtvGFOO4GsKGjfMCmcbMrtLyYQ7WoK5EpG0=
github.com/Monibuca/engine v1.2.1/go.mod h1:WbDkXENLjcPjyjCR1Mix1GA+uAlwORkv/+8aMVrDX2g= github.com/Monibuca/engine/v2 v2.1.2/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
github.com/Monibuca/engine v1.2.2 h1:hNjsrZpOmui8lYhgCJ5ltJU8g/k0Rrdysx2tHNGGnbI= github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb h1:CnmoQ8XsWxs/6mulbQfTGUa8cPr6c/3bkkTsNozRBwE=
github.com/Monibuca/engine/v2 v2.0.0 h1:8FjaScrtN8QdbcxO9zZYABMC0Re3I1O1T4p94zAXYb0= github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb/go.mod h1:8HxBilkF835Lepe/DLUCjaw1mRiu3MxTDsG7g9UcfZA=
github.com/Monibuca/engine/v2 v2.0.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/falconray0704/gortmp v0.0.0-20170613085150-e3f9bb02c7c8 h1:Bkx+0neYCcHW7BUeVCbR2GOn47NesdImh8nHHOKccD4=
github.com/falconray0704/gortmp v0.0.0-20170613085150-e3f9bb02c7c8/go.mod h1:/JBZajtCDe9Z4j84v5QWo4PLn1K6jcBHh6qXN/bm/vw=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0= github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo= github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg= github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg= github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/pion/rtp v1.5.4 h1:PuNg6xqV3brIUihatcKZj1YDUs+M45L0ZbrZWYtkDxY=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
github.com/pixelbender/go-sdp v1.0.0 h1:hLP2ALBN4sLpgp2r3EDcFUSN3AyOkg1jonuWEJniotY=
github.com/pixelbender/go-sdp v1.0.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/reactivex/rxgo v1.0.0 h1:qpT8/kVwAJDSeGsqx4oUXxgk3UCtAq/EreBGWYRxEcA=
github.com/reactivex/rxgo v1.0.0/go.mod h1:/S1ygE20oE1BvZGIwd3fXx/m6s6pOX5G6zmXg9ninlQ=
github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY= github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
@@ -60,22 +36,9 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
github.com/zhangpeihao/goamf v0.0.0-20140409082417-3ff2c19514a8/go.mod h1:RZd/IqzNpFANwOB9rVmsnAYpo/6KesK4PqrN1a5cRgg=
github.com/zhangpeihao/log v0.0.0-20170117094621-62e921e41859/go.mod h1:OAvmouyIV28taMw4SC4+hSnouObQqQkTQNOhU3Zowl0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

191
main.go
View File

@@ -2,7 +2,6 @@ package rtsp
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"log" "log"
"net" "net"
@@ -12,20 +11,19 @@ import (
"time" "time"
. "github.com/Monibuca/engine/v2" . "github.com/Monibuca/engine/v2"
. "github.com/Monibuca/engine/v2/avformat"
"github.com/Monibuca/engine/v2/util" "github.com/Monibuca/engine/v2/util"
"github.com/pixelbender/go-sdp/sdp" . "github.com/Monibuca/plugin-rtp"
"github.com/teris-io/shortid" "github.com/teris-io/shortid"
) )
var collection = sync.Map{} var collection sync.Map
var config = struct { var config = struct {
ListenAddr string ListenAddr string
BufferLength int AutoPull bool
AutoPull bool RemoteAddr string
RemoteAddr string Timeout int
Timeout int Reconnect bool
}{":554", 2048, true, "rtsp://localhost/${streamPath}", 0} }{":554", false, "rtsp://localhost/${streamPath}", 0,false}
func init() { func init() {
InstallPlugin(&PluginConfig{ InstallPlugin(&PluginConfig{
@@ -54,7 +52,6 @@ func runPlugin() {
collection.Range(func(key, value interface{}) bool { collection.Range(func(key, value interface{}) bool {
rtsp := value.(*RTSP) rtsp := value.(*RTSP)
pinfo := &rtsp.RTSPInfo pinfo := &rtsp.RTSPInfo
//pinfo.BufferRate = len(rtsp.OutGoing) * 100 / config.BufferLength
info = append(info, pinfo) info = append(info, pinfo)
return true return true
}) })
@@ -64,9 +61,7 @@ func runPlugin() {
http.HandleFunc("/rtsp/pull", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/rtsp/pull", func(w http.ResponseWriter, r *http.Request) {
targetURL := r.URL.Query().Get("target") targetURL := r.URL.Query().Get("target")
streamPath := r.URL.Query().Get("streamPath") streamPath := r.URL.Query().Get("streamPath")
var err error if err := new(RTSP).PullStream(streamPath, targetURL); err == nil {
if err == nil {
new(RTSP).PullStream(streamPath, targetURL)
w.Write([]byte(`{"code":0}`)) w.Write([]byte(`{"code":0}`))
} else { } else {
w.Write([]byte(fmt.Sprintf(`{"code":1,"msg":"%s"}`, err.Error()))) w.Write([]byte(fmt.Sprintf(`{"code":1,"msg":"%s"}`, err.Error())))
@@ -123,7 +118,7 @@ func ListenRtsp(addr string) error {
} }
type RTSP struct { type RTSP struct {
Publisher RTP
RTSPInfo RTSPInfo
RTSPClientInfo RTSPClientInfo
ID string ID string
@@ -132,46 +127,36 @@ type RTSP struct {
connWLock sync.RWMutex connWLock sync.RWMutex
Type SessionType Type SessionType
TransType TransType TransType TransType
SDPRaw string
SDPMap map[string]*SDPInfo SDPMap map[string]*SDPInfo
nonce string nonce string
closeOld bool closeOld bool
AControl string AControl string
VControl string VControl string
ACodec string ACodec string
VCodec string VCodec string
avcsent bool aacsent bool
aacsent bool Timeout int
Timeout int
// stats info
fuBuffer []byte
//tcp channels //tcp channels
aRTPChannel int aRTPChannel int
aRTPControlChannel int aRTPControlChannel int
vRTPChannel int vRTPChannel int
vRTPControlChannel int vRTPControlChannel int
UDPServer *UDPServer UDPServer *UDPServer
UDPClient *UDPClient UDPClient *UDPClient
SPS []byte Auth func(string) string
PPS []byte
AudioSpecificConfig []byte
Auth func(string) string
} }
type RTSPClientInfo struct { type RTSPClientInfo struct {
Agent string Agent string
Session string Session string
Sdp *sdp.Session authLine string
authLine string Seq int
Seq int
} }
type RTSPInfo struct { type RTSPInfo struct {
URL string URL string
SyncCount int64 SDPRaw string
Header *string
BufferRate int
InBytes int InBytes int
OutBytes int OutBytes int
StreamInfo *StreamInfo StreamInfo *StreamInfo
} }
@@ -199,115 +184,3 @@ func (conn *RichConn) Write(b []byte) (n int, err error) {
} }
return conn.Conn.Write(b) return conn.Conn.Write(b)
} }
func (rtsp *RTSP) handleNALU(nalType byte, payload []byte, ts int64) {
rtsp.SyncCount++
vl := len(payload)
switch nalType {
// case NALU_SPS:
// r := bytes.NewBuffer([]byte{})
// r.Write(RTMP_AVC_HEAD)
// util.BigEndian.PutUint16(spsHead[1:], uint16(vl))
// r.Write(spsHead)
// r.Write(payload)
// case NALU_PPS:
// r := bytes.NewBuffer([]byte{})
// util.BigEndian.PutUint16(ppsHead[1:], uint16(vl))
// r.Write(ppsHead)
// r.Write(payload)
// rtsp.PushVideo(0, r.Bytes())
// avcsent = true
case NALU_IDR_Picture:
if !rtsp.avcsent {
r := bytes.NewBuffer([]byte{})
r.Write(RTMP_AVC_HEAD)
spsHead := []byte{0xE1, 0, 0}
util.BigEndian.PutUint16(spsHead[1:], uint16(len(rtsp.SPS)))
r.Write(spsHead)
r.Write(rtsp.SPS)
ppsHead := []byte{0x01, 0, 0}
util.BigEndian.PutUint16(ppsHead[1:], uint16(len(rtsp.PPS)))
r.Write(ppsHead)
r.Write(rtsp.PPS)
rtsp.PushVideo(0, r.Bytes())
rtsp.avcsent = true
}
r := bytes.NewBuffer([]byte{})
iframeHead := []byte{0x17, 0x01, 0, 0, 0}
util.BigEndian.PutUint24(iframeHead[2:], 0)
r.Write(iframeHead)
nalLength := []byte{0, 0, 0, 0}
util.BigEndian.PutUint32(nalLength, uint32(vl))
r.Write(nalLength)
r.Write(payload)
rtsp.PushVideo(uint32(ts), r.Bytes())
case NALU_Non_IDR_Picture:
r := bytes.NewBuffer([]byte{})
pframeHead := []byte{0x27, 0x01, 0, 0, 0}
util.BigEndian.PutUint24(pframeHead[2:], 0)
r.Write(pframeHead)
nalLength := []byte{0, 0, 0, 0}
util.BigEndian.PutUint32(nalLength, uint32(vl))
r.Write(nalLength)
r.Write(payload)
rtsp.PushVideo(uint32(ts), r.Bytes())
}
}
func (rtsp *RTSP) handleRTP(pack *RTPPack) {
data := pack.Buffer
switch pack.Type {
case RTP_TYPE_AUDIO:
if !rtsp.aacsent {
rtsp.PushAudio(0, append([]byte{0xAF, 0x00}, rtsp.AudioSpecificConfig...))
rtsp.aacsent = true
}
cc := data[0] & 0xF
rtphdr := 12 + cc*4
payload := data[rtphdr:]
auHeaderLen := (int16(payload[0]) << 8) + int16(payload[1])
auHeaderLen = auHeaderLen >> 3
auHeaderCount := int(auHeaderLen / 2)
var auLenArray []int
for iIndex := 0; iIndex < int(auHeaderCount); iIndex++ {
auHeaderInfo := (int16(payload[2+2*iIndex]) << 8) + int16(payload[2+2*iIndex+1])
auLen := auHeaderInfo >> 3
auLenArray = append(auLenArray, int(auLen))
}
startOffset := 2 + 2*auHeaderCount
for _, auLen := range auLenArray {
endOffset := startOffset + auLen
addHead := []byte{0xAF, 0x01}
rtsp.PushAudio(0, append(addHead, payload[startOffset:endOffset]...))
startOffset = startOffset + auLen
}
case RTP_TYPE_VIDEO:
cc := data[0] & 0xF
//rtp header
rtphdr := 12 + cc*4
//packet time
ts := (int64(data[4]) << 24) + (int64(data[5]) << 16) + (int64(data[6]) << 8) + (int64(data[7]))
//packet number
//packno := (int64(data[6]) << 8) + int64(data[7])
data = data[rtphdr:]
nalType := data[0] & 0x1F
if nalType >= 1 && nalType <= 23 {
rtsp.handleNALU(nalType, data, ts)
} else if nalType == 28 {
isStart := data[1]&0x80 != 0
isEnd := data[1]&0x40 != 0
nalType := data[1] & 0x1F
//nri := (data[1]&0x60)>>5
nal := data[0]&0xE0 | data[1]&0x1F
if isStart {
rtsp.fuBuffer = []byte{0}
}
rtsp.fuBuffer = append(rtsp.fuBuffer, data[2:]...)
if isEnd {
rtsp.fuBuffer[0] = nal
rtsp.handleNALU(nalType, rtsp.fuBuffer, ts)
}
}
}
}

View File

@@ -13,14 +13,10 @@ import (
"time" "time"
. "github.com/Monibuca/engine/v2" . "github.com/Monibuca/engine/v2"
. "github.com/Monibuca/plugin-rtp"
"github.com/teris-io/shortid" "github.com/teris-io/shortid"
) )
type RTPPack struct {
Type RTPType
Buffer []byte
}
type SessionType int type SessionType int
const ( const (
@@ -38,29 +34,6 @@ func (st SessionType) String() string {
return "unknow" return "unknow"
} }
type RTPType int
const (
RTP_TYPE_AUDIO RTPType = iota
RTP_TYPE_VIDEO
RTP_TYPE_AUDIOCONTROL
RTP_TYPE_VIDEOCONTROL
)
func (rt RTPType) String() string {
switch rt {
case RTP_TYPE_AUDIO:
return "audio"
case RTP_TYPE_VIDEO:
return "video"
case RTP_TYPE_AUDIOCONTROL:
return "audio control"
case RTP_TYPE_VIDEOCONTROL:
return "video control"
}
return "unknow"
}
type TransType int type TransType int
const ( const (
@@ -99,9 +72,11 @@ func (session *RTSP) Stop() {
session.UDPServer = nil session.UDPServer = nil
} }
if session.Running() { if session.Running() {
collection.Delete(session.StreamPath)
session.Cancel() session.Cancel()
} }
if session.Stream != nil {
collection.Delete(session.StreamPath)
}
} }
// AcceptPush 接受推流 // AcceptPush 接受推流
@@ -126,53 +101,41 @@ func (session *RTSP) AcceptPush() {
} }
channel := int(buf1) channel := int(buf1)
rtpLen := int(binary.BigEndian.Uint16(buf2)) rtpLen := int(binary.BigEndian.Uint16(buf2))
pack := new(RTPPack)
rtpBytes := make([]byte, rtpLen) rtpBytes := make([]byte, rtpLen)
if _, err := io.ReadFull(session.connRW, rtpBytes); err != nil { if _, err := io.ReadFull(session.connRW, rtpBytes); err != nil {
Println(err) Println(err)
return return
} }
var pack *RTPPack if err = pack.Unmarshal(rtpBytes); err != nil {
Println(err)
return
}
switch channel { switch channel {
case session.aRTPChannel: case session.aRTPChannel:
pack = &RTPPack{ pack.Type = RTP_TYPE_AUDIO
Type: RTP_TYPE_AUDIO,
Buffer: rtpBytes,
}
elapsed := time.Now().Sub(timer) elapsed := time.Now().Sub(timer)
if elapsed >= 30*time.Second { if elapsed >= 30*time.Second {
Println("Recv an audio RTP package") Println("Recv an audio RTP package")
timer = time.Now() timer = time.Now()
} }
case session.aRTPControlChannel: case session.aRTPControlChannel:
pack = &RTPPack{ pack.Type = RTP_TYPE_AUDIOCONTROL
Type: RTP_TYPE_AUDIOCONTROL,
Buffer: rtpBytes,
}
case session.vRTPChannel: case session.vRTPChannel:
pack = &RTPPack{ pack.Type = RTP_TYPE_VIDEO
Type: RTP_TYPE_VIDEO,
Buffer: rtpBytes,
}
elapsed := time.Now().Sub(timer) elapsed := time.Now().Sub(timer)
if elapsed >= 30*time.Second { if elapsed >= 30*time.Second {
Println("Recv an video RTP package") Println("Recv an video RTP package")
timer = time.Now() timer = time.Now()
} }
case session.vRTPControlChannel: case session.vRTPControlChannel:
pack = &RTPPack{ pack.Type = RTP_TYPE_VIDEOCONTROL
Type: RTP_TYPE_VIDEOCONTROL,
Buffer: rtpBytes,
}
default: default:
Printf("unknow rtp pack type, %v", pack.Type) Printf("unknow rtp pack type, %v", pack.Type)
continue continue
} }
if pack == nil {
Printf("session tcp got nil rtp pack")
continue
}
session.InBytes += rtpLen + 4 session.InBytes += rtpLen + 4
session.handleRTP(pack) session.PushPack(pack)
} else { // rtsp cmd } else { // rtsp cmd
reqBuf := bytes.NewBuffer(nil) reqBuf := bytes.NewBuffer(nil)
reqBuf.WriteByte(buf1) reqBuf.WriteByte(buf1)
@@ -350,25 +313,25 @@ func (session *RTSP) handleRequest(req *Request) {
res.Status = "Invalid URL" res.Status = "Invalid URL"
return return
} }
streamPath := strings.TrimPrefix(url.Path,"/") streamPath := strings.TrimPrefix(url.Path, "/")
session.SDPRaw = req.Body session.SDPRaw = req.Body
session.SDPMap = ParseSDP(req.Body) session.SDPMap = ParseSDP(req.Body)
sdp, ok := session.SDPMap["audio"] if session.Publish(streamPath) {
if ok { sdp, ok := session.SDPMap["audio"]
session.AControl = sdp.Control if ok {
session.ACodec = sdp.Codec session.AControl = sdp.Control
session.AudioSpecificConfig = sdp.Config session.ACodec = sdp.Codec
Printf("audio codec[%s]\n", session.ACodec) session.WriteASC(sdp.Config)
} Printf("audio codec[%s]\n", session.ACodec)
if sdp, ok = session.SDPMap["video"];ok { }
session.VControl = sdp.Control if sdp, ok = session.SDPMap["video"]; ok {
session.VCodec = sdp.Codec session.VControl = sdp.Control
session.SPS = sdp.SpropParameterSets[0] session.VCodec = sdp.Codec
session.PPS = sdp.SpropParameterSets[1] session.WriteSPS(sdp.SpropParameterSets[0])
Printf("video codec[%s]\n", session.VCodec) session.WritePPS(sdp.SpropParameterSets[1])
} Printf("video codec[%s]\n", session.VCodec)
if session.Publisher.Publish(streamPath) { }
session.Stream.Type = "RTSP" session.Stream.Type = "RTSP"
session.RTSPInfo.StreamInfo = &session.Stream.StreamInfo session.RTSPInfo.StreamInfo = &session.Stream.StreamInfo
collection.Store(streamPath, session) collection.Store(streamPath, session)
@@ -575,7 +538,7 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
return return
} }
err = session.UDPClient.SendRTP(pack) err = session.UDPClient.SendRTP(pack)
session.OutBytes += len(pack.Buffer) session.OutBytes += len(pack.Raw)
return return
} }
switch pack.Type { switch pack.Type {
@@ -586,12 +549,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
session.connWLock.Lock() session.connWLock.Lock()
session.connRW.Write(bufChannel) session.connRW.Write(bufChannel)
bufLen := make([]byte, 2) bufLen := make([]byte, 2)
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer))) binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
session.connRW.Write(bufLen) session.connRW.Write(bufLen)
session.connRW.Write(pack.Buffer) session.connRW.Write(pack.Raw)
session.connRW.Flush() session.connRW.Flush()
session.connWLock.Unlock() session.connWLock.Unlock()
session.OutBytes += len(pack.Buffer) + 4 session.OutBytes += len(pack.Raw) + 4
case RTP_TYPE_AUDIOCONTROL: case RTP_TYPE_AUDIOCONTROL:
bufChannel := make([]byte, 2) bufChannel := make([]byte, 2)
bufChannel[0] = 0x24 bufChannel[0] = 0x24
@@ -599,12 +562,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
session.connWLock.Lock() session.connWLock.Lock()
session.connRW.Write(bufChannel) session.connRW.Write(bufChannel)
bufLen := make([]byte, 2) bufLen := make([]byte, 2)
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer))) binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
session.connRW.Write(bufLen) session.connRW.Write(bufLen)
session.connRW.Write(pack.Buffer) session.connRW.Write(pack.Raw)
session.connRW.Flush() session.connRW.Flush()
session.connWLock.Unlock() session.connWLock.Unlock()
session.OutBytes += len(pack.Buffer) + 4 session.OutBytes += len(pack.Raw) + 4
case RTP_TYPE_VIDEO: case RTP_TYPE_VIDEO:
bufChannel := make([]byte, 2) bufChannel := make([]byte, 2)
bufChannel[0] = 0x24 bufChannel[0] = 0x24
@@ -612,12 +575,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
session.connWLock.Lock() session.connWLock.Lock()
session.connRW.Write(bufChannel) session.connRW.Write(bufChannel)
bufLen := make([]byte, 2) bufLen := make([]byte, 2)
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer))) binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
session.connRW.Write(bufLen) session.connRW.Write(bufLen)
session.connRW.Write(pack.Buffer) session.connRW.Write(pack.Raw)
session.connRW.Flush() session.connRW.Flush()
session.connWLock.Unlock() session.connWLock.Unlock()
session.OutBytes += len(pack.Buffer) + 4 session.OutBytes += len(pack.Raw) + 4
case RTP_TYPE_VIDEOCONTROL: case RTP_TYPE_VIDEOCONTROL:
bufChannel := make([]byte, 2) bufChannel := make([]byte, 2)
bufChannel[0] = 0x24 bufChannel[0] = 0x24
@@ -625,12 +588,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
session.connWLock.Lock() session.connWLock.Lock()
session.connRW.Write(bufChannel) session.connRW.Write(bufChannel)
bufLen := make([]byte, 2) bufLen := make([]byte, 2)
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer))) binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
session.connRW.Write(bufLen) session.connRW.Write(bufLen)
session.connRW.Write(pack.Buffer) session.connRW.Write(pack.Raw)
session.connRW.Flush() session.connRW.Flush()
session.connWLock.Unlock() session.connWLock.Unlock()
session.OutBytes += len(pack.Buffer) + 4 session.OutBytes += len(pack.Raw) + 4
default: default:
err = fmt.Errorf("session tcp send rtp got unkown pack type[%v]", pack.Type) err = fmt.Errorf("session tcp send rtp got unkown pack type[%v]", pack.Type)
} }

View File

@@ -2,9 +2,10 @@ package rtsp
import ( import (
"fmt" "fmt"
. "github.com/Monibuca/engine/v2"
. "github.com/Monibuca/plugin-rtp"
"net" "net"
"strings" "strings"
. "github.com/Monibuca/engine/v2"
) )
type UDPClient struct { type UDPClient struct {
@@ -151,7 +152,7 @@ func (c *UDPClient) SendRTP(pack *RTPPack) (err error) {
return return
} }
if _, err = conn.Write(pack.Buffer);err != nil { if _, err = conn.Write(pack.Raw); err != nil {
err = fmt.Errorf("udp client write bytes error, %v", err) err = fmt.Errorf("udp client write bytes error, %v", err)
return return
} }

View File

@@ -9,10 +9,11 @@ import (
"time" "time"
. "github.com/Monibuca/engine/v2" . "github.com/Monibuca/engine/v2"
. "github.com/Monibuca/plugin-rtp"
) )
type UDPServer struct { type UDPServer struct {
Session *RTSP Session *RTSP
UDPClient UDPClient
sync.Mutex sync.Mutex
} }
@@ -29,7 +30,7 @@ func (s *UDPServer) HandleRTP(pack *RTPPack) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
if s.Session != nil { if s.Session != nil {
s.Session.handleRTP(pack) s.Session.PushPack(pack)
} }
} }
@@ -90,13 +91,11 @@ func (s *UDPServer) SetupAudio() (err error) {
Printf("Package recv from AConn.len:%d\n", n) Printf("Package recv from AConn.len:%d\n", n)
timer = time.Now() timer = time.Now()
} }
rtpBytes := make([]byte, n)
s.AddInputBytes(n) s.AddInputBytes(n)
copy(rtpBytes, bufUDP)
pack := &RTPPack{ pack := &RTPPack{
Type: RTP_TYPE_AUDIO, Type: RTP_TYPE_AUDIO,
Buffer: rtpBytes,
} }
pack.Unmarshal(bufUDP[:n])
s.HandleRTP(pack) s.HandleRTP(pack)
} else { } else {
Println("udp server read audio pack error", err) Println("udp server read audio pack error", err)
@@ -131,13 +130,11 @@ func (s *UDPServer) SetupAudio() (err error) {
for !s.Stoped { for !s.Stoped {
if n, _, err := s.AControlConn.ReadFromUDP(bufUDP); err == nil { if n, _, err := s.AControlConn.ReadFromUDP(bufUDP); err == nil {
//Printf("Package recv from AControlConn.len:%d\n", n) //Printf("Package recv from AControlConn.len:%d\n", n)
rtpBytes := make([]byte, n)
s.AddInputBytes(n) s.AddInputBytes(n)
copy(rtpBytes, bufUDP)
pack := &RTPPack{ pack := &RTPPack{
Type: RTP_TYPE_AUDIOCONTROL, Type: RTP_TYPE_AUDIOCONTROL,
Buffer: rtpBytes,
} }
pack.Unmarshal(bufUDP[:n])
s.HandleRTP(pack) s.HandleRTP(pack)
} else { } else {
Println("udp server read audio control pack error", err) Println("udp server read audio control pack error", err)
@@ -182,13 +179,11 @@ func (s *UDPServer) SetupVideo() (err error) {
Printf("Package recv from VConn.len:%d\n", n) Printf("Package recv from VConn.len:%d\n", n)
timer = time.Now() timer = time.Now()
} }
rtpBytes := make([]byte, n)
s.AddInputBytes(n) s.AddInputBytes(n)
copy(rtpBytes, bufUDP)
pack := &RTPPack{ pack := &RTPPack{
Type: RTP_TYPE_VIDEO, Type: RTP_TYPE_VIDEO,
Buffer: rtpBytes,
} }
pack.Unmarshal(bufUDP[:n])
s.HandleRTP(pack) s.HandleRTP(pack)
} else { } else {
Println("udp server read video pack error", err) Println("udp server read video pack error", err)
@@ -224,13 +219,11 @@ func (s *UDPServer) SetupVideo() (err error) {
for !s.Stoped { for !s.Stoped {
if n, _, err := s.VControlConn.ReadFromUDP(bufUDP); err == nil { if n, _, err := s.VControlConn.ReadFromUDP(bufUDP); err == nil {
//Printf("Package recv from VControlConn.len:%d\n", n) //Printf("Package recv from VControlConn.len:%d\n", n)
rtpBytes := make([]byte, n)
s.AddInputBytes(n) s.AddInputBytes(n)
copy(rtpBytes, bufUDP)
pack := &RTPPack{ pack := &RTPPack{
Type: RTP_TYPE_VIDEOCONTROL, Type: RTP_TYPE_VIDEOCONTROL,
Buffer: rtpBytes,
} }
pack.Unmarshal(bufUDP[:n])
s.HandleRTP(pack) s.HandleRTP(pack)
} else { } else {
Println("udp server read video control pack error", err) Println("udp server read video control pack error", err)

View File

@@ -172,14 +172,14 @@ if (typeof window !== 'undefined') {
// Indicate to webpack that this file can be concatenated // Indicate to webpack that this file can be concatenated
/* harmony default export */ var setPublicPath = (null); /* harmony default export */ var setPublicPath = (null);
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"29918b3a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=3ee6bce0& // CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"29918b3a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=de44c72c&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-data-table',{attrs:{"data":_vm.Streams,"columns":_vm.columns},scopedSlots:_vm._u([{key:"default",fn:function(ref){ var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-data-table',{attrs:{"data":_vm.Streams,"columns":_vm.columns},scopedSlots:_vm._u([{key:"default",fn:function(ref){
var item = ref.row; var item = ref.row;
return [_c('td',[_vm._v(_vm._s(item.StreamInfo.StreamPath))]),_c('td',[_c('StartTime',{attrs:{"value":item.StreamInfo.StartTime}})],1),_c('td',[_c('Progress',{attrs:{"stroke-width":20,"percent":Math.ceil(item.BufferRate),"text-inside":""}})],1),_c('td',[_vm._v(_vm._s(item.SyncCount))]),_c('td',[_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.showHeader(item)}}},[_vm._v("头信息")])],1)]}}])}),_c('mu-dialog',{attrs:{"title":"拉流转发","width":"360","open":_vm.openPull},on:{"update:open":function($event){_vm.openPull=$event}}},[_c('mu-text-field',{attrs:{"label":"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:(_vm.remoteAddr),callback:function ($$v) {_vm.remoteAddr=$$v},expression:"remoteAddr"}}),_c('mu-text-field',{attrs:{"label":"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:(_vm.streamPath),callback:function ($$v) {_vm.streamPath=$$v},expression:"streamPath"}}),_c('mu-button',{attrs:{"slot":"actions","flat":"","color":"primary"},on:{"click":_vm.addPull},slot:"actions"},[_vm._v("确定")])],1)],1)} return [_c('td',[_vm._v(_vm._s(item.StreamInfo.StreamPath))]),_c('td',[_c('StartTime',{attrs:{"value":item.StreamInfo.StartTime}})],1),_c('td',[_vm._v(_vm._s(_vm.unitFormat(item.InBytes)))]),_c('td',[_vm._v(_vm._s(_vm.unitFormat(item.OutBytes)))]),_c('td',[_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.showHeader(item)}}},[_vm._v("头信息")]),_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.stop(item)}}},[_vm._v("中止")])],1)]}}])}),_c('mu-dialog',{attrs:{"title":"拉流转发","width":"360","open":_vm.openPull},on:{"update:open":function($event){_vm.openPull=$event}}},[_c('mu-text-field',{attrs:{"label":"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:(_vm.remoteAddr),callback:function ($$v) {_vm.remoteAddr=$$v},expression:"remoteAddr"}}),_c('mu-text-field',{attrs:{"label":"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:(_vm.streamPath),callback:function ($$v) {_vm.streamPath=$$v},expression:"streamPath"}}),_c('mu-button',{attrs:{"slot":"actions","flat":"","color":"primary"},on:{"click":_vm.addPull},slot:"actions"},[_vm._v("确定")])],1)],1)}
var staticRenderFns = [] var staticRenderFns = []
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=3ee6bce0& // CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=de44c72c&
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=script&lang=js& // CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=script&lang=js&
// //
@@ -207,78 +207,100 @@ var staticRenderFns = []
// //
// //
// //
//
//
//
//
//
//
//
//
//
let listES = null; let listES = null;
/* harmony default export */ var Appvue_type_script_lang_js_ = ({ /* harmony default export */ var Appvue_type_script_lang_js_ = ({
data() { data() {
return { return {
currentStream: null, currentStream: null,
Streams: null, Streams: null,
remoteAddr: "", remoteAddr: "",
streamPath: "", streamPath: "",
openPull: false, openPull: false,
columns: [ columns: [
"StreamPath", "StreamPath",
"开始时间", "开始时间",
"缓冲", "总接收",
"同步数", "总发送",
"操作" "操作"
].map(title => ({ title })) ].map(title => ({ title }))
}; };
}, },
methods: { methods: {
fetchlist() { fetchlist() {
listES = new EventSource(this.apiHost + "/rtsp/list"); listES = new EventSource(this.apiHost + "/rtsp/list");
listES.onmessage = evt => { listES.onmessage = evt => {
if (!evt.data) return; if (!evt.data) return;
this.Streams = JSON.parse(evt.data) || []; this.Streams = JSON.parse(evt.data) || [];
this.Streams.sort((a, b) => this.Streams.sort((a, b) =>
a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1 a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1
); );
}; };
},
showHeader(item) {
this.$Modal.info({
title: "RTSP Header",
width: "1000px",
scrollable: true,
content: item.Header
});
},
addPull() {
this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
}
}, },
mounted() { showHeader(item) {
this.fetchlist(); this.$Modal.info({
let _this = this; title: "RTSP SDPRaw",
this.$parent.titleOps = [ width: "1000px",
{ scrollable: true,
template: '<m-button @click="onClick">拉流转发</m-button>', content: item.SDPRaw
methods: { });
onClick() {
_this.openPull = true;
}
}
}
];
}, },
destroyed() { addPull() {
listES.close(); this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
},
stop(item) {
this.ajax
.get(this.apiHost + "/api/stop", {
stream: item.StreamInfo.StreamPath
})
.then(x => {
if (x == "success") {
this.$toast.success("已停止拉流");
} else {
this.$toast.error(x.msg);
}
});
} }
},
mounted() {
this.fetchlist();
let _this = this;
this.$parent.titleOps = [
{
template: '<m-button @click="onClick">拉流转发</m-button>',
methods: {
onClick() {
_this.openPull = true;
}
}
}
];
},
destroyed() {
listES.close();
}
}); });
// CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js& // CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js&

File diff suppressed because one or more lines are too long

View File

@@ -181,14 +181,14 @@ if (typeof window !== 'undefined') {
// Indicate to webpack that this file can be concatenated // Indicate to webpack that this file can be concatenated
/* harmony default export */ var setPublicPath = (null); /* harmony default export */ var setPublicPath = (null);
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"29918b3a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=3ee6bce0& // CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"29918b3a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=de44c72c&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-data-table',{attrs:{"data":_vm.Streams,"columns":_vm.columns},scopedSlots:_vm._u([{key:"default",fn:function(ref){ var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-data-table',{attrs:{"data":_vm.Streams,"columns":_vm.columns},scopedSlots:_vm._u([{key:"default",fn:function(ref){
var item = ref.row; var item = ref.row;
return [_c('td',[_vm._v(_vm._s(item.StreamInfo.StreamPath))]),_c('td',[_c('StartTime',{attrs:{"value":item.StreamInfo.StartTime}})],1),_c('td',[_c('Progress',{attrs:{"stroke-width":20,"percent":Math.ceil(item.BufferRate),"text-inside":""}})],1),_c('td',[_vm._v(_vm._s(item.SyncCount))]),_c('td',[_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.showHeader(item)}}},[_vm._v("头信息")])],1)]}}])}),_c('mu-dialog',{attrs:{"title":"拉流转发","width":"360","open":_vm.openPull},on:{"update:open":function($event){_vm.openPull=$event}}},[_c('mu-text-field',{attrs:{"label":"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:(_vm.remoteAddr),callback:function ($$v) {_vm.remoteAddr=$$v},expression:"remoteAddr"}}),_c('mu-text-field',{attrs:{"label":"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:(_vm.streamPath),callback:function ($$v) {_vm.streamPath=$$v},expression:"streamPath"}}),_c('mu-button',{attrs:{"slot":"actions","flat":"","color":"primary"},on:{"click":_vm.addPull},slot:"actions"},[_vm._v("确定")])],1)],1)} return [_c('td',[_vm._v(_vm._s(item.StreamInfo.StreamPath))]),_c('td',[_c('StartTime',{attrs:{"value":item.StreamInfo.StartTime}})],1),_c('td',[_vm._v(_vm._s(_vm.unitFormat(item.InBytes)))]),_c('td',[_vm._v(_vm._s(_vm.unitFormat(item.OutBytes)))]),_c('td',[_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.showHeader(item)}}},[_vm._v("头信息")]),_c('mu-button',{attrs:{"flat":""},on:{"click":function($event){return _vm.stop(item)}}},[_vm._v("中止")])],1)]}}])}),_c('mu-dialog',{attrs:{"title":"拉流转发","width":"360","open":_vm.openPull},on:{"update:open":function($event){_vm.openPull=$event}}},[_c('mu-text-field',{attrs:{"label":"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:(_vm.remoteAddr),callback:function ($$v) {_vm.remoteAddr=$$v},expression:"remoteAddr"}}),_c('mu-text-field',{attrs:{"label":"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:(_vm.streamPath),callback:function ($$v) {_vm.streamPath=$$v},expression:"streamPath"}}),_c('mu-button',{attrs:{"slot":"actions","flat":"","color":"primary"},on:{"click":_vm.addPull},slot:"actions"},[_vm._v("确定")])],1)],1)}
var staticRenderFns = [] var staticRenderFns = []
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=3ee6bce0& // CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=de44c72c&
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=script&lang=js& // CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=script&lang=js&
// //
@@ -216,78 +216,100 @@ var staticRenderFns = []
// //
// //
// //
//
//
//
//
//
//
//
//
//
let listES = null; let listES = null;
/* harmony default export */ var Appvue_type_script_lang_js_ = ({ /* harmony default export */ var Appvue_type_script_lang_js_ = ({
data() { data() {
return { return {
currentStream: null, currentStream: null,
Streams: null, Streams: null,
remoteAddr: "", remoteAddr: "",
streamPath: "", streamPath: "",
openPull: false, openPull: false,
columns: [ columns: [
"StreamPath", "StreamPath",
"开始时间", "开始时间",
"缓冲", "总接收",
"同步数", "总发送",
"操作" "操作"
].map(title => ({ title })) ].map(title => ({ title }))
}; };
}, },
methods: { methods: {
fetchlist() { fetchlist() {
listES = new EventSource(this.apiHost + "/rtsp/list"); listES = new EventSource(this.apiHost + "/rtsp/list");
listES.onmessage = evt => { listES.onmessage = evt => {
if (!evt.data) return; if (!evt.data) return;
this.Streams = JSON.parse(evt.data) || []; this.Streams = JSON.parse(evt.data) || [];
this.Streams.sort((a, b) => this.Streams.sort((a, b) =>
a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1 a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1
); );
}; };
},
showHeader(item) {
this.$Modal.info({
title: "RTSP Header",
width: "1000px",
scrollable: true,
content: item.Header
});
},
addPull() {
this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
}
}, },
mounted() { showHeader(item) {
this.fetchlist(); this.$Modal.info({
let _this = this; title: "RTSP SDPRaw",
this.$parent.titleOps = [ width: "1000px",
{ scrollable: true,
template: '<m-button @click="onClick">拉流转发</m-button>', content: item.SDPRaw
methods: { });
onClick() {
_this.openPull = true;
}
}
}
];
}, },
destroyed() { addPull() {
listES.close(); this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
},
stop(item) {
this.ajax
.get(this.apiHost + "/api/stop", {
stream: item.StreamInfo.StreamPath
})
.then(x => {
if (x == "success") {
this.$toast.success("已停止拉流");
} else {
this.$toast.error(x.msg);
}
});
} }
},
mounted() {
this.fetchlist();
let _this = this;
this.$parent.titleOps = [
{
template: '<m-button @click="onClick">拉流转发</m-button>',
methods: {
onClick() {
_this.openPull = true;
}
}
}
];
},
destroyed() {
listES.close();
}
}); });
// CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js& // CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js&

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
(function(t,e){"object"===typeof exports&&"object"===typeof module?module.exports=e():"function"===typeof define&&define.amd?define([],e):"object"===typeof exports?exports["plugin-rtsp"]=e():t["plugin-rtsp"]=e()})("undefined"!==typeof self?self:this,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s="fb15")}({"034f":function(t,e,r){"use strict";var n=r("85ec"),o=r.n(n);o.a},"85ec":function(t,e,r){},f6fd:function(t,e){(function(t){var e="currentScript",r=t.getElementsByTagName("script");e in t||Object.defineProperty(t,e,{get:function(){try{throw new Error}catch(n){var t,e=(/.*at [^\(]*\((.*):.+:.+\)$/gi.exec(n.stack)||[!1])[1];for(t in r)if(r[t].src==e||"interactive"==r[t].readyState)return r[t];return null}}})})(document)},fb15:function(t,e,r){"use strict";var n;(r.r(e),"undefined"!==typeof window)&&(r("f6fd"),(n=window.document.currentScript)&&(n=n.src.match(/(.+\/)[^/]+\.js(\?.*)?$/))&&(r.p=n[1]));var o=function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("div",[r("mu-data-table",{attrs:{data:t.Streams,columns:t.columns},scopedSlots:t._u([{key:"default",fn:function(e){var n=e.row;return[r("td",[t._v(t._s(n.StreamInfo.StreamPath))]),r("td",[r("StartTime",{attrs:{value:n.StreamInfo.StartTime}})],1),r("td",[r("Progress",{attrs:{"stroke-width":20,percent:Math.ceil(n.BufferRate),"text-inside":""}})],1),r("td",[t._v(t._s(n.SyncCount))]),r("td",[r("mu-button",{attrs:{flat:""},on:{click:function(e){return t.showHeader(n)}}},[t._v("头信息")])],1)]}}])}),r("mu-dialog",{attrs:{title:"拉流转发",width:"360",open:t.openPull},on:{"update:open":function(e){t.openPull=e}}},[r("mu-text-field",{attrs:{label:"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:t.remoteAddr,callback:function(e){t.remoteAddr=e},expression:"remoteAddr"}}),r("mu-text-field",{attrs:{label:"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:t.streamPath,callback:function(e){t.streamPath=e},expression:"streamPath"}}),r("mu-button",{attrs:{slot:"actions",flat:"",color:"primary"},on:{click:t.addPull},slot:"actions"},[t._v("确定")])],1)],1)},a=[];let s=null;var i={data(){return{currentStream:null,Streams:null,remoteAddr:"",streamPath:"",openPull:!1,columns:["StreamPath","开始时间","缓冲","同步数","操作"].map(t=>({title:t}))}},methods:{fetchlist(){s=new EventSource(this.apiHost+"/rtsp/list"),s.onmessage=t=>{t.data&&(this.Streams=JSON.parse(t.data)||[],this.Streams.sort((t,e)=>t.StreamInfo.StreamPath>e.StreamInfo.StreamPath?1:-1))}},showHeader(t){this.$Modal.info({title:"RTSP Header",width:"1000px",scrollable:!0,content:t.Header})},addPull(){this.openPull=!1,this.ajax.getJSON(this.apiHost+"/rtsp/pull",{target:this.remoteAddr,streamPath:this.streamPath}).then(t=>{0==t.code?this.$toast.success("已启动拉流"):this.$toast.error(t.msg)})}},mounted(){this.fetchlist();let t=this;this.$parent.titleOps=[{template:'<m-button @click="onClick">拉流转发</m-button>',methods:{onClick(){t.openPull=!0}}}]},destroyed(){s.close()}},l=i;r("034f");function u(t,e,r,n,o,a,s,i){var l,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=r,u._compiled=!0),n&&(u.functional=!0),a&&(u._scopeId="data-v-"+a),s?(l=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(s)},u._ssrRegister=l):o&&(l=i?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(t,e){return l.call(e),c(t,e)}}else{var d=u.beforeCreate;u.beforeCreate=d?[].concat(d,l):[l]}return{exports:t,options:u}}var c=u(l,o,a,!1,null,null,null),d=c.exports;e["default"]=d}})["default"]})); (function(t,e){"object"===typeof exports&&"object"===typeof module?module.exports=e():"function"===typeof define&&define.amd?define([],e):"object"===typeof exports?exports["plugin-rtsp"]=e():t["plugin-rtsp"]=e()})("undefined"!==typeof self?self:this,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s="fb15")}({"034f":function(t,e,r){"use strict";var n=r("85ec"),o=r.n(n);o.a},"85ec":function(t,e,r){},f6fd:function(t,e){(function(t){var e="currentScript",r=t.getElementsByTagName("script");e in t||Object.defineProperty(t,e,{get:function(){try{throw new Error}catch(n){var t,e=(/.*at [^\(]*\((.*):.+:.+\)$/gi.exec(n.stack)||[!1])[1];for(t in r)if(r[t].src==e||"interactive"==r[t].readyState)return r[t];return null}}})})(document)},fb15:function(t,e,r){"use strict";var n;(r.r(e),"undefined"!==typeof window)&&(r("f6fd"),(n=window.document.currentScript)&&(n=n.src.match(/(.+\/)[^/]+\.js(\?.*)?$/))&&(r.p=n[1]));var o=function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("div",[r("mu-data-table",{attrs:{data:t.Streams,columns:t.columns},scopedSlots:t._u([{key:"default",fn:function(e){var n=e.row;return[r("td",[t._v(t._s(n.StreamInfo.StreamPath))]),r("td",[r("StartTime",{attrs:{value:n.StreamInfo.StartTime}})],1),r("td",[t._v(t._s(t.unitFormat(n.InBytes)))]),r("td",[t._v(t._s(t.unitFormat(n.OutBytes)))]),r("td",[r("mu-button",{attrs:{flat:""},on:{click:function(e){return t.showHeader(n)}}},[t._v("头信息")]),r("mu-button",{attrs:{flat:""},on:{click:function(e){return t.stop(n)}}},[t._v("中止")])],1)]}}])}),r("mu-dialog",{attrs:{title:"拉流转发",width:"360",open:t.openPull},on:{"update:open":function(e){t.openPull=e}}},[r("mu-text-field",{attrs:{label:"rtsp url","label-float":"","help-text":"Please enter URL of rtsp..."},model:{value:t.remoteAddr,callback:function(e){t.remoteAddr=e},expression:"remoteAddr"}}),r("mu-text-field",{attrs:{label:"streamPath","label-float":"","help-text":"Please enter streamPath to publish."},model:{value:t.streamPath,callback:function(e){t.streamPath=e},expression:"streamPath"}}),r("mu-button",{attrs:{slot:"actions",flat:"",color:"primary"},on:{click:t.addPull},slot:"actions"},[t._v("确定")])],1)],1)},a=[];let s=null;var i={data(){return{currentStream:null,Streams:null,remoteAddr:"",streamPath:"",openPull:!1,columns:["StreamPath","开始时间","总接收","总发送","操作"].map(t=>({title:t}))}},methods:{fetchlist(){s=new EventSource(this.apiHost+"/rtsp/list"),s.onmessage=t=>{t.data&&(this.Streams=JSON.parse(t.data)||[],this.Streams.sort((t,e)=>t.StreamInfo.StreamPath>e.StreamInfo.StreamPath?1:-1))}},showHeader(t){this.$Modal.info({title:"RTSP SDPRaw",width:"1000px",scrollable:!0,content:t.SDPRaw})},addPull(){this.openPull=!1,this.ajax.getJSON(this.apiHost+"/rtsp/pull",{target:this.remoteAddr,streamPath:this.streamPath}).then(t=>{0==t.code?this.$toast.success("已启动拉流"):this.$toast.error(t.msg)})},stop(t){this.ajax.get(this.apiHost+"/api/stop",{stream:t.StreamInfo.StreamPath}).then(t=>{"success"==t?this.$toast.success("已停止拉流"):this.$toast.error(t.msg)})}},mounted(){this.fetchlist();let t=this;this.$parent.titleOps=[{template:'<m-button @click="onClick">拉流转发</m-button>',methods:{onClick(){t.openPull=!0}}}]},destroyed(){s.close()}},l=i;r("034f");function u(t,e,r,n,o,a,s,i){var l,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=r,u._compiled=!0),n&&(u.functional=!0),a&&(u._scopeId="data-v-"+a),s?(l=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(s)},u._ssrRegister=l):o&&(l=i?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(u.functional){u._injectStyles=l;var c=u.render;u.render=function(t,e){return l.call(e),c(t,e)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return{exports:t,options:u}}var c=u(l,o,a,!1,null,null,null),f=c.exports;e["default"]=f}})["default"]}));
//# sourceMappingURL=plugin-rtsp.umd.min.js.map //# sourceMappingURL=plugin-rtsp.umd.min.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,125 +1,147 @@
<template> <template>
<div> <div>
<mu-data-table :data="Streams" :columns="columns"> <mu-data-table :data="Streams" :columns="columns">
<template #default="{row:item}"> <template #default="{row:item}">
<td>{{item.StreamInfo.StreamPath}}</td> <td>{{item.StreamInfo.StreamPath}}</td>
<td> <td>
<StartTime :value="item.StreamInfo.StartTime"></StartTime> <StartTime :value="item.StreamInfo.StartTime"></StartTime>
</td> </td>
<td><Progress :stroke-width="20" :percent="Math.ceil(item.BufferRate)" text-inside /></td> <td>{{unitFormat(item.InBytes)}}</td>
<td>{{item.SyncCount}}</td> <td>{{unitFormat(item.OutBytes)}}</td>
<td> <td>
<mu-button flat @click="showHeader(item)">头信息</mu-button> <mu-button flat @click="showHeader(item)">头信息</mu-button>
</td> <mu-button flat @click="stop(item)">中止</mu-button>
</template> </td>
</mu-data-table> </template>
<mu-dialog title="拉流转发" width="360" :open.sync="openPull"> </mu-data-table>
<mu-text-field v-model="remoteAddr" label="rtsp url" label-float help-text="Please enter URL of rtsp..."> <mu-dialog title="拉流转发" width="360" :open.sync="openPull">
</mu-text-field> <mu-text-field
<mu-text-field v-model="streamPath" label="streamPath" label-float v-model="remoteAddr"
help-text="Please enter streamPath to publish."></mu-text-field> label="rtsp url"
<mu-button slot="actions" flat color="primary" @click="addPull">确定</mu-button> label-float
</mu-dialog> help-text="Please enter URL of rtsp..."
</div> ></mu-text-field>
<mu-text-field
v-model="streamPath"
label="streamPath"
label-float
help-text="Please enter streamPath to publish."
></mu-text-field>
<mu-button slot="actions" flat color="primary" @click="addPull">确定</mu-button>
</mu-dialog>
</div>
</template> </template>
<script> <script>
let listES = null; let listES = null;
export default { export default {
data() { data() {
return { return {
currentStream: null, currentStream: null,
Streams: null, Streams: null,
remoteAddr: "", remoteAddr: "",
streamPath: "", streamPath: "",
openPull: false, openPull: false,
columns: [ columns: [
"StreamPath", "StreamPath",
"开始时间", "开始时间",
"缓冲", "总接收",
"同步数", "总发送",
"操作" "操作"
].map(title => ({ title })) ].map(title => ({ title }))
}; };
}, },
methods: { methods: {
fetchlist() { fetchlist() {
listES = new EventSource(this.apiHost + "/rtsp/list"); listES = new EventSource(this.apiHost + "/rtsp/list");
listES.onmessage = evt => { listES.onmessage = evt => {
if (!evt.data) return; if (!evt.data) return;
this.Streams = JSON.parse(evt.data) || []; this.Streams = JSON.parse(evt.data) || [];
this.Streams.sort((a, b) => this.Streams.sort((a, b) =>
a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1 a.StreamInfo.StreamPath > b.StreamInfo.StreamPath ? 1 : -1
); );
}; };
},
showHeader(item) {
this.$Modal.info({
title: "RTSP Header",
width: "1000px",
scrollable: true,
content: item.Header
});
},
addPull() {
this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
}
}, },
mounted() { showHeader(item) {
this.fetchlist(); this.$Modal.info({
let _this = this; title: "RTSP SDPRaw",
this.$parent.titleOps = [ width: "1000px",
{ scrollable: true,
template: '<m-button @click="onClick">拉流转发</m-button>', content: item.SDPRaw
methods: { });
onClick() {
_this.openPull = true;
}
}
}
];
}, },
destroyed() { addPull() {
listES.close(); this.openPull = false;
this.ajax
.getJSON(this.apiHost + "/rtsp/pull", {
target: this.remoteAddr,
streamPath: this.streamPath
})
.then(x => {
if (x.code == 0) {
this.$toast.success("已启动拉流");
} else {
this.$toast.error(x.msg);
}
});
},
stop(item) {
this.ajax
.get(this.apiHost + "/api/stop", {
stream: item.StreamInfo.StreamPath
})
.then(x => {
if (x == "success") {
this.$toast.success("已停止拉流");
} else {
this.$toast.error(x.msg);
}
});
} }
},
mounted() {
this.fetchlist();
let _this = this;
this.$parent.titleOps = [
{
template: '<m-button @click="onClick">拉流转发</m-button>',
methods: {
onClick() {
_this.openPull = true;
}
}
}
];
},
destroyed() {
listES.close();
}
}; };
</script> </script>
<style> <style>
.empty { .empty {
color: #eb5e46; color: #eb5e46;
width: 100%; width: 100%;
min-height: 500px; min-height: 500px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.layout { .layout {
padding-bottom: 30px; padding-bottom: 30px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.ts-info { .ts-info {
width: 300px; width: 300px;
} }
.hls-info { .hls-info {
width: 350px; width: 350px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
</style> </style>