Compare commits

...

22 Commits

Author SHA1 Message Date
langhuihui
7f40078b50 添加对engine版本的依赖 2020-08-29 08:00:25 +08:00
dexter
bb563d64c7 Merge pull request #5 from ourfor-pp/master
修复保活请求的bug
2020-08-29 07:48:16 +08:00
mqh
f7cb146b89 修复client.Session未保存,导致保活请求未携带session的bug 2020-08-28 17:05:45 +08:00
dexter
9bb49cb9f7 Merge pull request #4 from ourfor-pp/master
修改保活请求
2020-08-28 13:53:48 +08:00
mqh
087d1aab4d 增加basic登录(大华录像机测试验证) 2020-08-28 09:56:01 +08:00
mqh
f949464328 Merge branch 'master' of https://github.com/ourfor-pp/plugin-rtsp 2020-08-28 09:33:53 +08:00
mqh
d89f1e2405 将保活请求由OPTIONS改为GET_PARAMETER(来自VLC保活参考) 2020-08-28 09:26:57 +08:00
langhuihui
1d3fbfc20b 增加对纯音频的播放的支持 2020-08-27 08:50:45 +08:00
dexter
fd64a69a12 Merge pull request #2 from zbjlala/master
master
2020-08-26 22:17:37 +08:00
zbj
0e4406ad14 [fix]宇视摄像头DESCRIBE 有两个RTSP流 一个video matedata 需要两个SETUP才可播放 2020-08-25 11:55:49 +08:00
zbj
22f33886a9 [fix]宇视摄像头不支持该OPTIONS操作 551 2020-08-25 11:44:54 +08:00
langhuihui
8b1892209d 增加5分钟重连机制 2020-07-12 10:40:02 +08:00
langhuihui
2e9cf9a4ca 重连机制修复 2020-07-11 21:54:59 +08:00
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
15 changed files with 545 additions and 663 deletions

210
client.go
View File

@@ -4,10 +4,10 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/base64"
"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 +15,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 +31,17 @@ 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 { rtsp.UDPServer = &UDPServer{Session: rtsp}
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)
@@ -75,6 +80,18 @@ func DigestAuth(authLine string, method string, URL string) (string, error) {
Authorization := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", username, realm, nonce, l.String(), response) Authorization := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", username, realm, nonce, l.String(), response)
return Authorization, nil return Authorization, nil
} }
// auth Basic验证
func BasicAuth(authLine string, method string, URL string) (string, error) {
l, err := url.Parse(URL)
if err != nil {
return "", fmt.Errorf("Url parse error:%v,%v", URL, err)
}
username := l.User.Username()
password, _ := l.User.Password()
userAndpass := []byte(username + ":" + password)
Authorization := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(userAndpass))
return Authorization, nil
}
func (client *RTSP) checkAuth(method string, resp *Response) (string, error) { func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
if resp.StatusCode == 401 { if resp.StatusCode == 401 {
// need auth. // need auth.
@@ -88,8 +105,7 @@ func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
client.authLine = authLine client.authLine = authLine
return DigestAuth(authLine, method, client.URL) return DigestAuth(authLine, method, client.URL)
} else if strings.IndexAny(authLine, "Basic") == 0 { } else if strings.IndexAny(authLine, "Basic") == 0 {
// not support yet return BasicAuth(authLine, method, client.URL)
// TODO..
} }
} }
return "", fmt.Errorf("auth error") return "", fmt.Errorf("auth error")
@@ -99,9 +115,7 @@ func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
client.authLine = authLine client.authLine = authLine
return DigestAuth(authLine, method, client.URL) return DigestAuth(authLine, method, client.URL)
} else if strings.IndexAny(authLine, "Basic") == 0 { } else if strings.IndexAny(authLine, "Basic") == 0 {
// not support yet return BasicAuth(authLine, method, client.URL)
// TODO..
return "", fmt.Errorf("not support Basic auth yet")
} }
} }
} }
@@ -141,7 +155,7 @@ func (client *RTSP) requestStream() (err error) {
client.connRW = bufio.NewReadWriter(bufio.NewReaderSize(&timeoutConn, networkBuffer), bufio.NewWriterSize(&timeoutConn, networkBuffer)) client.connRW = bufio.NewReadWriter(bufio.NewReaderSize(&timeoutConn, networkBuffer), bufio.NewWriterSize(&timeoutConn, networkBuffer))
headers := make(map[string]string) headers := make(map[string]string)
headers["Require"] = "implicit-play" //headers["Require"] = "implicit-play"
// An OPTIONS request returns the request types the server will accept. // An OPTIONS request returns the request types the server will accept.
resp, err := client.Request("OPTIONS", headers) resp, err := client.Request("OPTIONS", headers)
if err != nil { if err != nil {
@@ -184,115 +198,116 @@ 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)
client.VSdp, client.HasVideo = client.SDPMap["video"]
client.ASdp, client.HasAudio = client.SDPMap["audio"]
session := "" session := ""
for _, media := range _sdp.Media { otherChannel := 4
switch media.Type { for t, sdpInfo := range client.SDPMap {
headers = make(map[string]string)
if session != "" {
headers["Session"] = session
}
var _url = sdpInfo.Control
if !strings.HasPrefix(strings.ToLower(sdpInfo.Control), "rtsp://") {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(sdpInfo.Control, "/")
}
switch t {
case "video": case "video":
client.VControl = media.Attributes.Get("control") if len(sdpInfo.SpropParameterSets) > 1 {
client.VCodec = media.Format[0].Name client.WriteSPS(sdpInfo.SpropParameterSets[0])
client.SPS = client.SDPMap["video"].SpropParameterSets[0] client.WritePPS(sdpInfo.SpropParameterSets[1])
client.PPS = client.SDPMap["video"].SpropParameterSets[1]
var _url = ""
if strings.Index(strings.ToLower(client.VControl), "rtsp://") == 0 {
_url = client.VControl
} else {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.VControl, "/")
} }
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP { if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel) headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
} else { } else {
if client.UDPServer == nil {
client.UDPServer = &UDPServer{Session: client}
}
//RTP/AVP;unicast;client_port=64864-64865 //RTP/AVP;unicast;client_port=64864-64865
err = client.UDPServer.SetupVideo() if err = client.UDPServer.SetupVideo(); err != nil {
if err != nil {
Printf("Setup video err.%v", err) Printf("Setup video err.%v", err)
return err return err
} }
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.VPort, client.UDPServer.VControlPort) 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 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": case "audio":
client.AControl = media.Attributes.Get("control") if len(sdpInfo.Config) < 2 {
client.ACodec = media.Format[0].Name Printf("Setup audio err codec not support: %s", client.ASdp.Codec)
client.AudioSpecificConfig = client.SDPMap["audio"].Config
var _url = ""
if strings.Index(strings.ToLower(client.AControl), "rtsp://") == 0 {
_url = client.AControl
} else { } else {
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.AControl, "/") client.WriteASC(sdpInfo.Config)
} }
headers = make(map[string]string)
if client.TransType == TRANS_TYPE_TCP { if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel) headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel)
} else { } else {
if client.UDPServer == nil { if err = client.UDPServer.SetupAudio(); err != nil {
client.UDPServer = &UDPServer{Session: client}
}
err = client.UDPServer.SetupAudio()
if err != nil {
Printf("Setup audio err.%v", err) Printf("Setup audio err.%v", err)
return err return err
} }
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.APort, client.UDPServer.AControlPort) 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 client.Conn.timeout = 0 // UDP ignore timeout
} }
if session != "" { default:
headers["Session"] = session if client.TransType == TRANS_TYPE_TCP {
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", otherChannel, otherChannel+1)
otherChannel += 2
} else {
//TODO: UDP support
} }
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)
} }
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
client.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 {
//if client.OptionIntervalMillis > 0 { Printf("reconnecting:%s", client.URL)
// if time.Since(startTime) > time.Duration(client.OptionIntervalMillis)*time.Millisecond { client.RTSPClientInfo = RTSPClientInfo{}
// startTime = time.Now() if err := client.requestStream(); err != nil {
// headers := make(map[string]string) t := time.NewTicker(time.Second * 5)
// headers["Require"] = "implicit-play" for {
// // An OPTIONS request returns the request types the server will accept. Printf("reconnecting:%s in 5 seconds", client.URL)
// if err := client.RequestNoResp("OPTIONS", headers); err != nil { select {
// // ignore... case <-client.Done():
// } client.Stop()
// } return
//} case <-t.C:
if err = client.requestStream(); err == nil {
go client.startStream()
return
}
}
}
} else {
go client.startStream()
}
} else {
client.Stop()
}
}()
for client.Err() == nil {
if time.Since(startTime) > time.Minute {
startTime = time.Now()
headers := make(map[string]string)
headers["Require"] = "implicit-play"
// An OPTIONS request returns the request types the server will accept.
if err := client.RequestNoResp("GET_PARAMETER", headers); err != nil {
// ignore...
}
}
b, err := client.connRW.ReadByte() b, err := client.connRW.ReadByte()
if err != nil { if err != nil {
Printf("client.connRW.ReadByte err:%v", err) Printf("client.connRW.ReadByte err:%v", err)
@@ -315,38 +330,33 @@ 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, }
if client.ASdp.Codec != "aac" {
continue
} }
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 +375,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)

11
go.mod
View File

@@ -3,9 +3,12 @@ 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.9
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/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/reactivex/rxgo v1.0.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect
github.com/pion/rtp v1.6.0 // indirect
github.com/shirou/gopsutil v2.20.7+incompatible // indirect
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16 // indirect
) )

67
go.sum
View File

@@ -1,81 +1,58 @@
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/engine/v2 v2.1.9 h1:IulMIeP24qv8xWaI+tcg233Y7w3mCaLXxt4iQaVpT7s=
github.com/Monibuca/engine/v2 v2.0.0 h1:8FjaScrtN8QdbcxO9zZYABMC0Re3I1O1T4p94zAXYb0= github.com/Monibuca/engine/v2 v2.1.9/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
github.com/Monibuca/engine/v2 v2.0.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc= github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb h1:CnmoQ8XsWxs/6mulbQfTGUa8cPr6c/3bkkTsNozRBwE=
github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb/go.mod h1:8HxBilkF835Lepe/DLUCjaw1mRiu3MxTDsG7g9UcfZA=
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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/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-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/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/randutil v0.0.0 h1:aLWLVhTG2jzoD25F0OlW6nXvXrjoGwiXq2Sz7j7NzL0=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pixelbender/go-sdp v1.0.0 h1:hLP2ALBN4sLpgp2r3EDcFUSN3AyOkg1jonuWEJniotY= github.com/pion/rtp v1.5.4 h1:PuNg6xqV3brIUihatcKZj1YDUs+M45L0ZbrZWYtkDxY=
github.com/pixelbender/go-sdp v1.0.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs= github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
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/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w=
github.com/shirou/gopsutil v2.20.7+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=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
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= golang.org/x/sys v0.0.0-20200828161417-c663848e9a16 h1:54u1berWyLujz9htI1BHtZpcCEYaYNUSDFLXMNDd7To=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=

189
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,34 @@ 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 ASdp *SDPInfo
VControl string VSdp *SDPInfo
ACodec string aacsent bool
VCodec string Timeout int
avcsent bool
aacsent bool
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 +182,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

@@ -31,17 +31,13 @@ func ParseSDP(sdpRaw string) map[string]*SDPInfo {
switch typeval[0] { switch typeval[0] {
case "m": case "m":
if len(fields) > 0 { if len(fields) > 0 {
switch fields[0] { info = &SDPInfo{AVType: fields[0]}
case "audio", "video": sdpMap[info.AVType] = info
sdpMap[fields[0]] = &SDPInfo{AVType: fields[0]} mfields := strings.Split(fields[1], " ")
info = sdpMap[fields[0]] if len(mfields) >= 3 {
mfields := strings.Split(fields[1], " ") info.PayloadType, _ = strconv.Atoi(mfields[2])
if len(mfields) >= 3 {
info.PayloadType, _ = strconv.Atoi(mfields[2])
}
} }
} }
case "a": case "a":
if info != nil { if info != nil {
for _, field := range fields { for _, field := range fields {
@@ -60,6 +56,10 @@ func ParseSDP(sdpRaw string) map[string]*SDPInfo {
if len(keyval) >= 2 { if len(keyval) >= 2 {
key := keyval[0] key := keyval[0]
switch key { switch key {
case "PCMA":
info.Codec = "pcma"
case "PCMU":
info.Codec = "pcmu"
case "MPEG4-GENERIC": case "MPEG4-GENERIC":
info.Codec = "aac" info.Codec = "aac"
case "H264": case "H264":

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,23 @@ 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 { var ok bool
session.AControl = sdp.Control if session.ASdp, ok = session.SDPMap["audio"]; ok {
session.ACodec = sdp.Codec session.WriteASC(session.ASdp.Config)
session.AudioSpecificConfig = sdp.Config Printf("audio codec[%s]\n", session.ASdp.Codec)
Printf("audio codec[%s]\n", session.ACodec) }
} if session.VSdp, ok = session.SDPMap["video"]; ok {
if sdp, ok = session.SDPMap["video"];ok { if len(session.VSdp.SpropParameterSets) > 1 {
session.VControl = sdp.Control session.WriteSPS(session.VSdp.SpropParameterSets[0])
session.VCodec = sdp.Codec session.WritePPS(session.VSdp.SpropParameterSets[1])
session.SPS = sdp.SpropParameterSets[0] }
session.PPS = sdp.SpropParameterSets[1] Printf("video codec[%s]\n", session.VSdp.Codec)
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)
@@ -416,8 +377,8 @@ func (session *RTSP) handleRequest(req *Request) {
// return // return
//} //}
vPath := "" vPath := ""
if strings.Index(strings.ToLower(session.VControl), "rtsp://") == 0 { if strings.Index(strings.ToLower(session.VSdp.Control), "rtsp://") == 0 {
vControlUrl, err := url.Parse(session.VControl) vControlUrl, err := url.Parse(session.VSdp.Control)
if err != nil { if err != nil {
res.StatusCode = 500 res.StatusCode = 500
res.Status = "Invalid VControl" res.Status = "Invalid VControl"
@@ -428,12 +389,12 @@ func (session *RTSP) handleRequest(req *Request) {
} }
vPath = vControlUrl.String() vPath = vControlUrl.String()
} else { } else {
vPath = session.VControl vPath = session.VSdp.Control
} }
aPath := "" aPath := ""
if strings.Index(strings.ToLower(session.AControl), "rtsp://") == 0 { if strings.Index(strings.ToLower(session.ASdp.Control), "rtsp://") == 0 {
aControlUrl, err := url.Parse(session.AControl) aControlUrl, err := url.Parse(session.ASdp.Control)
if err != nil { if err != nil {
res.StatusCode = 500 res.StatusCode = 500
res.Status = "Invalid AControl" res.Status = "Invalid AControl"
@@ -444,7 +405,7 @@ func (session *RTSP) handleRequest(req *Request) {
} }
aPath = aControlUrl.String() aPath = aControlUrl.String()
} else { } else {
aPath = session.AControl aPath = session.ASdp.Control
} }
mtcp := regexp.MustCompile("interleaved=(\\d+)(-(\\d+))?") mtcp := regexp.MustCompile("interleaved=(\\d+)(-(\\d+))?")
@@ -575,7 +536,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 +547,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 +560,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 +573,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 +586,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>