mirror of
https://github.com/Monibuca/plugin-gb28181.git
synced 2025-12-24 13:27:57 +08:00
Compare commits
41 Commits
v1.0.0-bet
...
v1.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fac74846a | ||
|
|
fed0b22513 | ||
|
|
2326500086 | ||
|
|
c31d10c349 | ||
|
|
0d1a15f511 | ||
|
|
0f0b36dc3d | ||
|
|
f224a96033 | ||
|
|
3b70a3ee69 | ||
|
|
b1b0bf06f2 | ||
|
|
7c48ad044c | ||
|
|
bcd59cfc0f | ||
|
|
89f133e50e | ||
|
|
b885173222 | ||
|
|
e7e85466bf | ||
|
|
98cc8824f0 | ||
|
|
dbdf66cdef | ||
|
|
66c1182a4d | ||
|
|
07498fbe58 | ||
|
|
ed3cea25ef | ||
|
|
49b465be1b | ||
|
|
8faeab6728 | ||
|
|
5ccebf2479 | ||
|
|
67c37b56a8 | ||
|
|
78b163384f | ||
|
|
a1534f72f8 | ||
|
|
beed7cba2a | ||
|
|
5799281628 | ||
|
|
3ae1805543 | ||
|
|
c5d328da16 | ||
|
|
9ceeb2d511 | ||
|
|
f3ffbb7f3d | ||
|
|
af8829baa2 | ||
|
|
5b8f63a13b | ||
|
|
822f75d36b | ||
|
|
05b8d75155 | ||
|
|
9669085328 | ||
|
|
22a56b02fb | ||
|
|
0f58d9dde6 | ||
|
|
7f9fb67230 | ||
|
|
d25bb3854a | ||
|
|
261bc00de0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.vscode
|
||||
.vscode
|
||||
.idea
|
||||
339
device.go
Normal file
339
device.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/transaction"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
)
|
||||
|
||||
type ChannelEx struct {
|
||||
device *Device
|
||||
inviteRes *sip.Message
|
||||
recordInviteRes *sip.Message
|
||||
RecordSP string //正在播放录像的StreamPath
|
||||
LiveSP string //实时StreamPath
|
||||
Connected bool
|
||||
Records []*Record
|
||||
}
|
||||
|
||||
// Channel 通道
|
||||
type Channel struct {
|
||||
DeviceID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
Secrecy int
|
||||
Status string
|
||||
ChannelEx //自定义属性
|
||||
}
|
||||
|
||||
// func (c *Channel) MarshalJSON() ([]byte, error) {
|
||||
// var data = map[string]interface{}{
|
||||
// "DeviceID": c.DeviceID,
|
||||
// "Name": c.Name,
|
||||
// "Manufacturer": c.Manufacturer,
|
||||
// "Address": c.Address,
|
||||
// "Status": c.Status,
|
||||
// "RecordSP": c.RecordSP,
|
||||
// "LiveSP": c.LiveSP,
|
||||
// "Records": c.Records,
|
||||
// "Connected": c.Connected,
|
||||
// }
|
||||
// return json.Marshal(data)
|
||||
// }
|
||||
|
||||
// Record 录像
|
||||
type Record struct {
|
||||
//channel *Channel
|
||||
DeviceID string
|
||||
Name string
|
||||
FilePath string
|
||||
Address string
|
||||
StartTime string
|
||||
EndTime string
|
||||
Secrecy int
|
||||
Type string
|
||||
}
|
||||
|
||||
func (r *Record) GetPublishStreamPath() string {
|
||||
return fmt.Sprintf("%s/%s", r.DeviceID, r.StartTime)
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
*transaction.Core `json:"-"`
|
||||
ID string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
Status string
|
||||
Channels []*Channel
|
||||
sn int
|
||||
from *sip.Contact
|
||||
to *sip.Contact
|
||||
Addr string
|
||||
SipIP string //暴露的IP
|
||||
}
|
||||
|
||||
func (d *Device) UpdateChannels(list []*Channel) {
|
||||
for _, c := range list {
|
||||
c.device = d
|
||||
have := false
|
||||
for i, o := range d.Channels {
|
||||
if o.DeviceID == c.DeviceID {
|
||||
c.ChannelEx = o.ChannelEx
|
||||
d.Channels[i] = c
|
||||
have = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
d.Channels = append(d.Channels, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (d *Device) UpdateRecord(channelId string, list []*Record) {
|
||||
for _, c := range d.Channels {
|
||||
if c.DeviceID == channelId {
|
||||
c.Records = list
|
||||
//for _, o := range list {
|
||||
// o.channel = c
|
||||
//}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c *Channel) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
|
||||
requestMsg = c.device.CreateMessage(Method)
|
||||
requestMsg.StartLine.Uri = sip.NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
|
||||
requestMsg.To = &sip.Contact{
|
||||
Uri: requestMsg.StartLine.Uri,
|
||||
}
|
||||
requestMsg.From = &sip.Contact{
|
||||
Uri: sip.NewURI(config.Serial + "@" + config.Realm),
|
||||
Params: map[string]string{"tag": utils.RandNumString(9)},
|
||||
}
|
||||
return
|
||||
}
|
||||
func (c *Channel) GetPublishStreamPath(start string) string {
|
||||
if start == "0" {
|
||||
return fmt.Sprintf("%s/%s", c.device.ID, c.DeviceID)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", c.DeviceID, start)
|
||||
}
|
||||
func (d *Device) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
|
||||
d.sn++
|
||||
requestMsg = &sip.Message{
|
||||
Mode: sip.SIP_MESSAGE_REQUEST,
|
||||
MaxForwards: 70,
|
||||
UserAgent: "Monibuca",
|
||||
StartLine: &sip.StartLine{
|
||||
Method: Method,
|
||||
Uri: d.to.Uri,
|
||||
}, Via: &sip.Via{
|
||||
Transport: "UDP",
|
||||
Host: d.Core.SipIP,
|
||||
Port: fmt.Sprintf("%d", d.SipPort),
|
||||
Params: map[string]string{
|
||||
"branch": fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8)),
|
||||
"rport": "-1", //only key,no-value
|
||||
},
|
||||
}, From: d.from,
|
||||
To: d.to, CSeq: &sip.CSeq{
|
||||
ID: uint32(d.sn),
|
||||
Method: Method,
|
||||
}, CallID: utils.RandNumString(10),
|
||||
Addr: d.Addr,
|
||||
}
|
||||
requestMsg.From.Params["tag"] = utils.RandNumString(9)
|
||||
return
|
||||
}
|
||||
func (d *Device) Query() int {
|
||||
requestMsg := d.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>Catalog</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
</Query>`, d.sn, requestMsg.To.Uri.UserInfo())
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
response := d.SendMessage(requestMsg)
|
||||
if response.Data != nil && response.Data.Via.Params["received"] != "" {
|
||||
d.SipIP = response.Data.Via.Params["received"]
|
||||
}
|
||||
return response.Code
|
||||
}
|
||||
func (d *Device) QueryRecord(channelIndex int, startTime, endTime string) int {
|
||||
channel := d.Channels[channelIndex]
|
||||
requestMsg := channel.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>RecordInfo</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<StartTime>%s</StartTime>
|
||||
<EndTime>%s</EndTime>
|
||||
<Secrecy>0</Secrecy>
|
||||
<Type>time</Type>
|
||||
</Query>`, d.sn, requestMsg.To.Uri.UserInfo(), startTime, endTime)
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
return d.SendMessage(requestMsg).Code
|
||||
}
|
||||
func (d *Device) Control(channelIndex int, PTZCmd string) int {
|
||||
channel := d.Channels[channelIndex]
|
||||
requestMsg := channel.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Control>
|
||||
<CmdType>DeviceControl</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<PTZCmd>%s</PTZCmd>
|
||||
</Control>`, d.sn, requestMsg.To.Uri.UserInfo(), PTZCmd)
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
return d.SendMessage(requestMsg).Code
|
||||
}
|
||||
|
||||
/*
|
||||
f字段: f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
|
||||
各项具体含义:
|
||||
v:后续参数为视频的参数;各参数间以 “/”分割;
|
||||
编码格式:十进制整数字符串表示
|
||||
1 –MPEG-4 2 –H.264 3 – SVAC 4 –3GP
|
||||
分辨率:十进制整数字符串表示
|
||||
1 – QCIF 2 – CIF 3 – 4CIF 4 – D1 5 –720P 6 –1080P/I
|
||||
帧率:十进制整数字符串表示 0~99
|
||||
码率类型:十进制整数字符串表示
|
||||
1 – 固定码率(CBR) 2 – 可变码率(VBR)
|
||||
码率大小:十进制整数字符串表示 0~100000(如 1表示1kbps)
|
||||
a:后续参数为音频的参数;各参数间以 “/”分割;
|
||||
编码格式:十进制整数字符串表示
|
||||
1 – G.711 2 – G.723.1 3 – G.729 4 – G.722.1
|
||||
码率大小:十进制整数字符串
|
||||
音频编码码率: 1 — 5.3 kbps (注:G.723.1中使用)
|
||||
2 — 6.3 kbps (注:G.723.1中使用)
|
||||
3 — 8 kbps (注:G.729中使用)
|
||||
4 — 16 kbps (注:G.722.1中使用)
|
||||
5 — 24 kbps (注:G.722.1中使用)
|
||||
6 — 32 kbps (注:G.722.1中使用)
|
||||
7 — 48 kbps (注:G.722.1中使用)
|
||||
8 — 64 kbps(注:G.711中使用)
|
||||
采样率:十进制整数字符串表示
|
||||
1 — 8 kHz(注:G.711/ G.723.1/ G.729中使用)
|
||||
2—14 kHz(注:G.722.1中使用)
|
||||
3—16 kHz(注:G.722.1中使用)
|
||||
4—32 kHz(注:G.722.1中使用)
|
||||
注1:字符串说明
|
||||
本节中使用的“十进制整数字符串”的含义为“0”~“4294967296” 之间的十进制数字字符串。
|
||||
注2:参数分割标识
|
||||
各参数间以“/”分割,参数间的分割符“/”不能省略;
|
||||
若两个分割符 “/”间的某参数为空时(即两个分割符 “/”直接将相连时)表示无该参数值;
|
||||
注3:f字段说明
|
||||
使用f字段时,应保证视频和音频参数的结构完整性,即在任何时候,f字段的结构都应是完整的结构:
|
||||
f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
|
||||
若只有视频时,音频中的各参数项可以不填写,但应保持 “a///”的结构:
|
||||
f = v/编码格式/分辨率/帧率/码率类型/码率大小a///
|
||||
若只有音频时也类似处理,视频中的各参数项可以不填写,但应保持 “v/”的结构:
|
||||
f = v/a/编码格式/码率大小/采样率
|
||||
f字段中视、音频参数段之间不需空格分割。
|
||||
可使用f字段中的分辨率参数标识同一设备不同分辨率的码流。
|
||||
*/
|
||||
func (d *Device) Invite(channelIndex int, start, end string, f string) int {
|
||||
channel := d.Channels[channelIndex]
|
||||
port, publisher := d.publish(channel.GetPublishStreamPath(start))
|
||||
if port == 0 {
|
||||
channel.Connected = true
|
||||
return 304
|
||||
}
|
||||
ssrc := "0200000001"
|
||||
// size := 1
|
||||
// fps := 15
|
||||
// bitrate := 200
|
||||
// fmt.Sprintf("f=v/2/%d/%d/1/%da///", size, fps, bitrate)
|
||||
s := "Play"
|
||||
if start != "0" {
|
||||
s = "Playback"
|
||||
publisher.AutoUnPublish = true
|
||||
channel.RecordSP = publisher.StreamPath
|
||||
} else {
|
||||
channel.LiveSP = publisher.StreamPath
|
||||
}
|
||||
sdpInfo := []string{
|
||||
"v=0",
|
||||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", d.Serial, d.SipIP),
|
||||
"s=" + s,
|
||||
"u=" + channel.DeviceID + ":0",
|
||||
"c=IN IP4 " + d.SipIP,
|
||||
fmt.Sprintf("t=%s %s", start, end),
|
||||
fmt.Sprintf("m=video %d RTP/AVP 96 97 98", port),
|
||||
"a=recvonly",
|
||||
"a=rtpmap:96 PS/90000",
|
||||
"a=rtpmap:97 MPEG4/90000",
|
||||
"a=rtpmap:98 H264/90000",
|
||||
"y=" + ssrc,
|
||||
"f=" + f,
|
||||
}
|
||||
|
||||
invite := channel.CreateMessage(sip.INVITE)
|
||||
invite.ContentType = "application/sdp"
|
||||
invite.Contact = &sip.Contact{
|
||||
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", d.Serial, d.SipIP, d.SipPort)),
|
||||
}
|
||||
invite.Body = strings.Join(sdpInfo, "\r\n") + "\r\n"
|
||||
invite.ContentLength = len(invite.Body)
|
||||
invite.Subject = fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, ssrc, config.Serial)
|
||||
response := d.SendMessage(invite)
|
||||
fmt.Printf("invite response statuscode: %d\n", response.Code)
|
||||
if response.Code == 200 {
|
||||
if start == "0" {
|
||||
channel.inviteRes = response.Data
|
||||
channel.Connected = true
|
||||
} else {
|
||||
channel.recordInviteRes = response.Data
|
||||
}
|
||||
ack := d.CreateMessage(sip.ACK)
|
||||
ack.StartLine = &sip.StartLine{
|
||||
Uri: sip.NewURI(channel.DeviceID + "@" + d.to.Uri.Domain()),
|
||||
Method: sip.ACK,
|
||||
}
|
||||
ack.From = response.Data.From
|
||||
ack.To = response.Data.To
|
||||
ack.CallID = response.Data.CallID
|
||||
ack.CSeq.ID = invite.CSeq.ID
|
||||
go d.Send(ack)
|
||||
}
|
||||
return response.Code
|
||||
}
|
||||
func (d *Device) Bye(channelIndex int) int {
|
||||
channel := d.Channels[channelIndex]
|
||||
defer func() {
|
||||
channel.inviteRes = nil
|
||||
channel.Connected = false
|
||||
}()
|
||||
return channel.Bye(channel.inviteRes).Code
|
||||
}
|
||||
func (c *Channel) Bye(res *sip.Message) *transaction.Response {
|
||||
if res == nil {
|
||||
return nil
|
||||
}
|
||||
bye := c.device.CreateMessage(sip.BYE)
|
||||
bye.StartLine = &sip.StartLine{
|
||||
Uri: sip.NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain()),
|
||||
Method: sip.BYE,
|
||||
}
|
||||
bye.From = res.From
|
||||
bye.To = res.To
|
||||
bye.CallID = res.CallID
|
||||
return c.device.SendMessage(bye)
|
||||
}
|
||||
11
go.mod
11
go.mod
@@ -3,8 +3,17 @@ module github.com/Monibuca/plugin-gb28181
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine/v2 v2.2.2
|
||||
github.com/Monibuca/engine/v2 v2.2.5
|
||||
github.com/Monibuca/plugin-gateway v1.2.6 // indirect
|
||||
github.com/Monibuca/plugin-hdl v1.2.5 // indirect
|
||||
github.com/Monibuca/plugin-hls v1.2.1 // indirect
|
||||
github.com/Monibuca/plugin-jessica v1.3.0 // indirect
|
||||
github.com/Monibuca/plugin-logrotate v1.2.3 // indirect
|
||||
github.com/Monibuca/plugin-record v1.0.4 // indirect
|
||||
github.com/Monibuca/plugin-rtmp v1.2.5 // indirect
|
||||
github.com/Monibuca/plugin-rtp v1.0.0
|
||||
github.com/Monibuca/plugin-rtsp v1.3.1 // indirect
|
||||
github.com/Monibuca/plugin-webrtc v1.2.2 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d
|
||||
)
|
||||
|
||||
133
go.sum
133
go.sum
@@ -1,53 +1,180 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Monibuca/engine/v2 v2.0.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.1.9/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.2.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.2.2 h1:ho5M3aFW9Mlj9Lb56Qvk0m+9L8yWc7RhwPh8dRWAeBk=
|
||||
github.com/Monibuca/engine/v2 v2.2.2/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/plugin-gb28181 v1.0.0-alpha3 h1:1oOSVIhkHxWZ5ALCVJG7P5MKxywNVm2zEwaHj+UqU1g=
|
||||
github.com/Monibuca/plugin-gb28181 v1.0.0-alpha3/go.mod h1:fyzQG2o13Df9VdCd4QrjbY0AFtcoKeIfnTMErVhWpLA=
|
||||
github.com/Monibuca/engine/v2 v2.2.5 h1:/w0BrvdTy4cqLD2uaIRaqBwdnu+/VDk+r3sjFbpbc1E=
|
||||
github.com/Monibuca/engine/v2 v2.2.5/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/plugin-gateway v1.2.6 h1:ZHwHMmjBs3Itxxd/Z/OR7c5QdED1sqSmBJ/6U+Y/Hc0=
|
||||
github.com/Monibuca/plugin-gateway v1.2.6/go.mod h1:l+BaE5QZY0Pr92iiIXS67gt2VxcBuOxYyqIGOEGbuFk=
|
||||
github.com/Monibuca/plugin-hdl v1.2.5 h1:zIo4ptkIzn/L1KhutsZ2Bk17OoyRbundXG+8c1ki6cQ=
|
||||
github.com/Monibuca/plugin-hdl v1.2.5/go.mod h1:+Qm9B5tQfxQdZ1hJQZbW4BZLOQWbPwN9qkB0vPGFo1U=
|
||||
github.com/Monibuca/plugin-hls v1.2.1 h1:vD8LFB1t87fKuEycuQYa1nayCcEkRpIo0xosDJX3yGM=
|
||||
github.com/Monibuca/plugin-hls v1.2.1/go.mod h1:8mIEvD09qDxp2Lso7fOQnfNFcxPcMxa6P9dGBbUOZD0=
|
||||
github.com/Monibuca/plugin-jessica v1.3.0 h1:VPLPl76ykEHvrtQMy4+UZQBJd2gjYfWQbrg47qnOIT0=
|
||||
github.com/Monibuca/plugin-jessica v1.3.0/go.mod h1:9TOsOzeVNbTCbgZ6xMANNMA0h5O/GxdlCLhavo6yVHM=
|
||||
github.com/Monibuca/plugin-logrotate v1.2.3 h1:Q3V4VrN8fVUQnN+RIP+wn/+ZrZ/cu8xM5rTeIg74QVM=
|
||||
github.com/Monibuca/plugin-logrotate v1.2.3/go.mod h1:bo2zR3H2CuyN2+dbsnZ6IvM+NT87r9H2RptVyyxkvTY=
|
||||
github.com/Monibuca/plugin-record v1.0.4 h1:dPXJKlkljyhNUrpVP3586QO1L2DFvvl8m+ZXmQlEul8=
|
||||
github.com/Monibuca/plugin-record v1.0.4/go.mod h1:POh+M09c+U1YAt6ratoKXahjt8GopEL0k6M8DUXbmAc=
|
||||
github.com/Monibuca/plugin-rtmp v1.2.5 h1:y9tQ7ayg8y3PE8jpBmb3/EHNgBL5cppptKvulFdvkzE=
|
||||
github.com/Monibuca/plugin-rtmp v1.2.5/go.mod h1:RSbufAndiyYfeLz4OiZwcGS/UTHCyTDtqvHzvzRQtWE=
|
||||
github.com/Monibuca/plugin-rtp v1.0.0 h1:yksNsIIGxoKX8UZirkAUK+mGZ/XoEeS2vqbIqtqXyCg=
|
||||
github.com/Monibuca/plugin-rtp v1.0.0/go.mod h1:0xkNm23a/BjVnEMz1zXyOqfEjoVmGe3PJqPNF1KyFGc=
|
||||
github.com/Monibuca/plugin-rtsp v1.3.1 h1:b7xtRIBVcoa6WDseErVDzt+8dZGaUwhDLHxGuutROag=
|
||||
github.com/Monibuca/plugin-rtsp v1.3.1/go.mod h1:N5nEu2SZTf4NMJn7DLlw39wQ4kXZAuMrfbbNtYES894=
|
||||
github.com/Monibuca/plugin-ts v1.2.1 h1:Y2QgEal9/ot43QEvtzRkmA8yk+N2uzieAzKuujZJFMs=
|
||||
github.com/Monibuca/plugin-ts v1.2.1/go.mod h1:MZsdv34Od1Kh1WOkuqIAO/t2irjzW39RMO9WbpFks4g=
|
||||
github.com/Monibuca/plugin-webrtc v1.2.2 h1:XTc3kUyzpoxXC0L9qVUhrhGFBy9YagdRzJb6lDU9HTc=
|
||||
github.com/Monibuca/plugin-webrtc v1.2.2/go.mod h1:ZPcGimoI8Yl/IzQJhSp4jgVOpmMeqPDcN7H8xv1Xj9g=
|
||||
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/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
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/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
|
||||
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/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/mask-pp/rtp-ps v1.0.0 h1:JFxuJL9N+gD1ldgJlAy3b7rYfY8wAVHi9ODNmdP4+EE=
|
||||
github.com/mask-pp/rtp-ps v1.0.0/go.mod h1:jCxsZ2G7z/jX+aqFypEWMePnhNrfnUiXUEKm6Xp0vgU=
|
||||
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.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/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pion/datachannel v1.4.17 h1:8CChK5VrJoGrwKCysoTscoWvshCAFpUkgY11Tqgz5hE=
|
||||
github.com/pion/datachannel v1.4.17/go.mod h1:+vPQfypU9vSsyPXogYj1hBThWQ6MNXEQoQAzxoPvjYM=
|
||||
github.com/pion/dtls/v2 v2.0.0 h1:Fk+MBhLZ/U1bImzAhmzwbO/pP2rKhtTw8iA934H3ybE=
|
||||
github.com/pion/dtls/v2 v2.0.0/go.mod h1:VkY5VL2wtsQQOG60xQ4lkV5pdn0wwBBTzCfRJqXhp3A=
|
||||
github.com/pion/ice v0.7.15 h1:s1In+gnuyVq7WKWGVQL+1p+OcrMsbfL+VfSe2isH8Ag=
|
||||
github.com/pion/ice v0.7.15/go.mod h1:Z6zybEQgky5mZkKcLfmvc266JukK2srz3VZBBD1iXBw=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
|
||||
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
|
||||
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
|
||||
github.com/pion/randutil v0.0.0 h1:aLWLVhTG2jzoD25F0OlW6nXvXrjoGwiXq2Sz7j7NzL0=
|
||||
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak=
|
||||
github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
|
||||
github.com/pion/rtp v1.5.4 h1:PuNg6xqV3brIUihatcKZj1YDUs+M45L0ZbrZWYtkDxY=
|
||||
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/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
|
||||
github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
|
||||
github.com/pion/sdp/v2 v2.3.7 h1:WUZHI3pfiYCaE8UGUYcabk863LCK+Bq3AklV5O0oInQ=
|
||||
github.com/pion/sdp/v2 v2.3.7/go.mod h1:+ZZf35r1+zbaWYiZLfPutWfx58DAWcGb2QsS3D/s9M8=
|
||||
github.com/pion/srtp v1.3.3 h1:8bjs9YaSNvSrbH0OfKxzPX+PTrCyAC2LoT9Qesugi+U=
|
||||
github.com/pion/srtp v1.3.3/go.mod h1:jNe0jmIOqksuurR9S/7yoKDalfPeluUFrNPCBqI4FOI=
|
||||
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
|
||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
|
||||
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
|
||||
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
|
||||
github.com/pion/turn/v2 v2.0.3/go.mod h1:kl1hmT3NxcLynpXVnwJgObL8C9NaCyPTeqI2DcCpSZs=
|
||||
github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
|
||||
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
|
||||
github.com/pion/webrtc/v2 v2.2.14 h1:bRjnXTqMDJ3VERPF45z439Sv6QfDfjdYvdQk1QcIx8M=
|
||||
github.com/pion/webrtc/v2 v2.2.14/go.mod h1:G+8lShCMbHhjpMF1ZJBkyuvrxXrvW4bxs3nOt+mJ2UI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0 h1:8oseBjJn5IKHQKdRZwSNskkua3NLrRtlvXXtoVgBzMk=
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0/go.mod h1:smzfWHlYpBATVNu1GapKLYiCtEo5JxridIgvvudZ+Wc=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
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.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.20.8+incompatible h1:8c7Atn0FAUZJo+f4wYbN0iVpdWniCQk7IYwGtgdh1mY=
|
||||
github.com/shirou/gopsutil v2.20.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/zhangpeihao/goamf v0.0.0-20140409082417-3ff2c19514a8 h1:r1JUI0wuHlgRb8jNd3zPBBkjUdrjpVKr8SdJWc8ntg8=
|
||||
github.com/zhangpeihao/goamf v0.0.0-20140409082417-3ff2c19514a8/go.mod h1:RZd/IqzNpFANwOB9rVmsnAYpo/6KesK4PqrN1a5cRgg=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
156
main.go
156
main.go
@@ -1,6 +1,8 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
@@ -10,6 +12,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"golang.org/x/net/html/charset"
|
||||
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
"github.com/Monibuca/engine/v2/util"
|
||||
"github.com/Monibuca/plugin-gb28181/transaction"
|
||||
@@ -62,14 +67,34 @@ func run() {
|
||||
MediaPortMax: config.MediaPortMax,
|
||||
MediaIdleTimeout: 30,
|
||||
}
|
||||
s := transaction.NewCore(config)
|
||||
s.OnInvite = onPublish
|
||||
|
||||
http.HandleFunc("/gb28181/query/records", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
id := r.URL.Query().Get("id")
|
||||
channel, err := strconv.Atoi(r.URL.Query().Get("channel"))
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
startTime := r.URL.Query().Get("startTime")
|
||||
endTime := r.URL.Query().Get("endTime")
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*Device).QueryRecord(channel, startTime, endTime))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/gb28181/list", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
for {
|
||||
var list []*transaction.Device
|
||||
s.Devices.Range(func(key, value interface{}) bool {
|
||||
list = append(list, value.(*transaction.Device))
|
||||
var list []*Device
|
||||
Devices.Range(func(key, value interface{}) bool {
|
||||
device := value.(*Device)
|
||||
if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
|
||||
Devices.Delete(key)
|
||||
} else {
|
||||
list = append(list, device)
|
||||
}
|
||||
return true
|
||||
})
|
||||
sse.WriteJSON(list)
|
||||
@@ -88,21 +113,31 @@ func run() {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
ptzcmd := r.URL.Query().Get("ptzcmd")
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Control(channel, ptzcmd))
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*Device).Control(channel, ptzcmd))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/gb28181/invite", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
id := r.URL.Query().Get("id")
|
||||
channel, err := strconv.Atoi(r.URL.Query().Get("channel"))
|
||||
query := r.URL.Query()
|
||||
id := query.Get("id")
|
||||
channel, err := strconv.Atoi(query.Get("channel"))
|
||||
startTime := query.Get("startTime")
|
||||
endTime := query.Get("endTime")
|
||||
f := query.Get("f")
|
||||
if startTime == "" {
|
||||
startTime = "0"
|
||||
}
|
||||
if endTime == "" {
|
||||
endTime = "0"
|
||||
}
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Invite(channel))
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*Device).Invite(channel, startTime, endTime, f))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
@@ -114,25 +149,83 @@ func run() {
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Bye(channel))
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*Device).Bye(channel))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
s := transaction.NewCore(config)
|
||||
s.OnRegister = func(msg *sip.Message) {
|
||||
Devices.Store(msg.From.Uri.UserInfo(), &Device{
|
||||
ID: msg.From.Uri.UserInfo(),
|
||||
RegisterTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Status: string(sip.REGISTER),
|
||||
Core: s,
|
||||
from: &sip.Contact{Uri: msg.StartLine.Uri, Params: make(map[string]string)},
|
||||
to: msg.To,
|
||||
Addr: msg.Via.GetSendBy(),
|
||||
SipIP: config.MediaIP,
|
||||
})
|
||||
}
|
||||
s.OnMessage = func(msg *sip.Message) bool {
|
||||
if v, ok := Devices.Load(msg.From.Uri.UserInfo()); ok {
|
||||
d := v.(*Device)
|
||||
if d.Status == string(sip.REGISTER) {
|
||||
d.Status = "ONLINE"
|
||||
}
|
||||
d.UpdateTime = time.Now()
|
||||
temp := &struct {
|
||||
XMLName xml.Name
|
||||
CmdType string
|
||||
DeviceID string
|
||||
DeviceList []*Channel `xml:"DeviceList>Item"`
|
||||
RecordList []*Record `xml:"RecordList>Item"`
|
||||
}{}
|
||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(msg.Body)))
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
decoder.Decode(temp)
|
||||
switch temp.XMLName.Local {
|
||||
case "Notify":
|
||||
go d.Query()
|
||||
case "Response":
|
||||
switch temp.CmdType {
|
||||
case "Catalog":
|
||||
d.UpdateChannels(temp.DeviceList)
|
||||
case "RecordInfo":
|
||||
d.UpdateRecord(temp.DeviceID, temp.RecordList)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
//OnStreamClosedHooks.AddHook(func(stream *Stream) {
|
||||
// Devices.Range(func(key, value interface{}) bool {
|
||||
// device:=value.(*Device)
|
||||
// for _,channel := range device.Channels {
|
||||
// if stream.StreamPath == channel.RecordSP {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//})
|
||||
s.Start()
|
||||
}
|
||||
func onPublish(channel *transaction.Channel) (port int) {
|
||||
rtpPublisher := new(rtp.RTP_PS)
|
||||
if !rtpPublisher.Publish("gb28181/" + channel.DeviceID) {
|
||||
|
||||
func (d *Device) publish(name string) (port int, publisher *rtp.RTP_PS) {
|
||||
publisher = new(rtp.RTP_PS)
|
||||
if !publisher.Publish(name) {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if port == 0 {
|
||||
rtpPublisher.Close()
|
||||
publisher.Close()
|
||||
}
|
||||
}()
|
||||
rtpPublisher.Type = "GB28181"
|
||||
publisher.Type = "GB28181"
|
||||
publisher.AutoUnPublish = true
|
||||
var conn *net.UDPConn
|
||||
var err error
|
||||
rang := int(config.MediaPortMax - config.MediaPortMin)
|
||||
@@ -166,16 +259,35 @@ func onPublish(channel *transaction.Channel) (port int) {
|
||||
bufUDP := make([]byte, 1048576)
|
||||
Printf("udp server start listen video port[%d]", port)
|
||||
defer Printf("udp server stop listen video port[%d]", port)
|
||||
defer conn.Close()
|
||||
for rtpPublisher.Err() == nil {
|
||||
for publisher.Err() == nil {
|
||||
if err = conn.SetReadDeadline(time.Now().Add(time.Second * 30)); err != nil {
|
||||
return
|
||||
}
|
||||
if n, _, err := conn.ReadFromUDP(bufUDP); err == nil {
|
||||
rtpPublisher.PushPS(bufUDP[:n])
|
||||
publisher.PushPS(bufUDP[:n])
|
||||
} else {
|
||||
Println("udp server read video pack error", err)
|
||||
rtpPublisher.Close()
|
||||
publisher.Close()
|
||||
if !publisher.AutoUnPublish {
|
||||
for _, channel := range d.Channels {
|
||||
if channel.LiveSP == name {
|
||||
channel.LiveSP = ""
|
||||
channel.Connected = false
|
||||
channel.Bye(channel.inviteRes)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
if publisher.AutoUnPublish {
|
||||
for _, channel := range d.Channels {
|
||||
if channel.RecordSP == name {
|
||||
channel.RecordSP = ""
|
||||
channel.Bye(channel.recordInviteRes)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
27
sip/head.go
27
sip/head.go
@@ -12,7 +12,7 @@ import (
|
||||
//windows : \n
|
||||
//Mac OS : \r
|
||||
const (
|
||||
VERSION = "SIP/2.0" // sip version
|
||||
VERSION = "SIP/2.0" // sip version
|
||||
CRLF = "\r\n" // 0x0D0A
|
||||
CRLFCRLF = "\r\n\r\n" // 0x0D0A0D0A
|
||||
|
||||
@@ -440,28 +440,29 @@ type URI struct {
|
||||
params map[string]string // include branch/maddr/received/ttl/rport
|
||||
headers map[string]string // include branch/maddr/received/ttl/rport
|
||||
}
|
||||
|
||||
func (u *URI) Host() string {
|
||||
return u.host
|
||||
}
|
||||
func (u *URI) UserInfo() string {
|
||||
return strings.Split(u.host,"@")[0]
|
||||
return strings.Split(u.host, "@")[0]
|
||||
}
|
||||
func (u *URI) Domain() string {
|
||||
return strings.Split(u.host,"@")[1]
|
||||
return strings.Split(u.host, "@")[1]
|
||||
}
|
||||
func (u *URI) IP() string {
|
||||
t:=strings.Split(u.host,"@")
|
||||
t := strings.Split(u.host, "@")
|
||||
if len(t) == 1 {
|
||||
return strings.Split(t[0],":")[0]
|
||||
return strings.Split(t[0], ":")[0]
|
||||
}
|
||||
return strings.Split(t[1],":")[0]
|
||||
return strings.Split(t[1], ":")[0]
|
||||
}
|
||||
func (u *URI) Port() string {
|
||||
t:=strings.Split(u.host,"@")
|
||||
t := strings.Split(u.host, "@")
|
||||
if len(t) == 1 {
|
||||
return strings.Split(t[0],":")[1]
|
||||
return strings.Split(t[0], ":")[1]
|
||||
}
|
||||
return strings.Split(t[1],":")[1]
|
||||
return strings.Split(t[1], ":")[1]
|
||||
}
|
||||
func (u *URI) String() string {
|
||||
if u.scheme == "" {
|
||||
@@ -546,8 +547,12 @@ func parseURI(str string) (ret URI, err error) {
|
||||
arr1 := strings.Split(paramStr, ";")
|
||||
for _, one := range arr1 {
|
||||
tmp := strings.Split(one, "=")
|
||||
k, v := tmp[0], tmp[1]
|
||||
ret.params[k] = v
|
||||
if len(tmp) == 2 {
|
||||
k, v := tmp[0], tmp[1]
|
||||
ret.params[k] = v
|
||||
} else {
|
||||
ret.params[tmp[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestContact(t *testing.T) {
|
||||
str1 := "\"Mr.Watson\" <sip:watson@worcester.bell-telephone.com>;q=0.7; expires=3600,\"Mr.Watson\" <mailto:watson@bell-telephone.com>";
|
||||
str1 := "\"Mr.Watson\" <sip:watson@worcester.bell-telephone.com>;q=0.7; expires=3600,\"Mr.Watson\" <mailto:watson@bell-telephone.com>"
|
||||
//str1 := `"Mr.Watson" <sip:watson@worcester.bell-telephone.com>;q=0.7;`
|
||||
c := &Contact{}
|
||||
err := c.Parse(str1)
|
||||
|
||||
@@ -219,8 +219,10 @@ func Decode(data []byte) (msg *Message, err error) {
|
||||
}
|
||||
|
||||
headStr := strings.TrimSpace(msgArr[0])
|
||||
if len(msgArr) > 1 {
|
||||
msg.Body = strings.TrimSpace(msgArr[1])
|
||||
if msgArrLen := len(msgArr); msgArrLen > 1 {
|
||||
for i := 1; i < msgArrLen; i++ {
|
||||
msg.Body += strings.TrimSpace(msgArr[i])
|
||||
}
|
||||
}
|
||||
|
||||
headStr = strings.Trim(headStr, CRLF)
|
||||
@@ -426,6 +428,12 @@ func Encode(msg *Message) ([]byte, error) {
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.Subject != "" {
|
||||
sb.WriteString("Subject: ")
|
||||
sb.WriteString(msg.Subject)
|
||||
sb.WriteString(CRLF)
|
||||
}
|
||||
|
||||
if msg.IsRequest() {
|
||||
//request only
|
||||
|
||||
|
||||
@@ -88,9 +88,8 @@ var errorMap = map[int]string{
|
||||
}
|
||||
|
||||
func DumpError(code int) string {
|
||||
if code == 0{
|
||||
if code == 0 {
|
||||
return "invalid status reason for request"
|
||||
}
|
||||
return fmt.Sprintf("%d %s", code, errorMap[code])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -14,7 +11,6 @@ import (
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/transport"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
//Core: transactions manager
|
||||
@@ -26,9 +22,9 @@ type Core struct {
|
||||
mutex sync.RWMutex //transactions的锁
|
||||
removeTa chan string //要删除transaction的时候,通过chan传递tid
|
||||
tp transport.ITransport //transport
|
||||
config *Config //sip server配置信息
|
||||
Devices sync.Map
|
||||
OnInvite func(*Channel) int
|
||||
*Config //sip server配置信息
|
||||
OnRegister func(*sip.Message)
|
||||
OnMessage func(*sip.Message) bool
|
||||
}
|
||||
|
||||
//初始化一个 Core,需要能响应请求,也要能发起请求
|
||||
@@ -42,7 +38,7 @@ func NewCore(config *Config) *Core {
|
||||
handlers: make(map[State]map[Event]Handler),
|
||||
transactions: make(map[string]*Transaction),
|
||||
removeTa: make(chan string, 10),
|
||||
config: config,
|
||||
Config: config,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
if config.SipNetwork == "TCP" {
|
||||
@@ -221,9 +217,7 @@ func (c *Core) Handler() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
ch := c.tp.ReadPacketChan()
|
||||
timer := time.Tick(time.Second * 5)
|
||||
//阻塞读取消息
|
||||
for {
|
||||
//fmt.Println("PacketHandler ========== SIP Client")
|
||||
@@ -236,8 +230,6 @@ func (c *Core) Handler() {
|
||||
fmt.Println("handler sip response message failed:", err.Error())
|
||||
continue
|
||||
}
|
||||
case <-timer:
|
||||
c.RemoveDead()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,8 +243,9 @@ func (c *Core) Handler() {
|
||||
//发送之后,就开启timer,超时重传,还要记录和修改每次超时时间。不超时的话,记得删掉timer
|
||||
//发送 register 消息
|
||||
func (c *Core) SendMessage(msg *sip.Message) *Response {
|
||||
methond := msg.GetMethod()
|
||||
fmt.Println("send message:", methond)
|
||||
method := msg.GetMethod()
|
||||
// data, _ := sip.Encode(msg)
|
||||
fmt.Println("send message:", method)
|
||||
|
||||
e := c.NewOutGoingMessageEvent(msg)
|
||||
|
||||
@@ -267,7 +260,7 @@ func (c *Core) SendMessage(msg *sip.Message) *Response {
|
||||
//如果是sip 消息事件,则将消息缓存,填充typo和state
|
||||
if msg.IsRequest() {
|
||||
//as uac
|
||||
if msg.GetMethod() == sip.INVITE || msg.GetMethod() == sip.ACK {
|
||||
if method == sip.INVITE || method == sip.ACK {
|
||||
ta.typo = FSM_ICT
|
||||
ta.state = ICT_PRE_CALLING
|
||||
} else {
|
||||
@@ -304,7 +297,7 @@ func (c *Core) SendMessage(msg *sip.Message) *Response {
|
||||
//响应消息则需要匹配到请求,让请求的transaction来处理。
|
||||
//TODO:参考srs和osip的流程,以及文档,做最终处理。需要将逻辑分成两层:TU 层和 transaction 层
|
||||
func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
//fmt.Println("packet content:", string(p.Data))
|
||||
// fmt.Println("packet content:", string(p.Data))
|
||||
var msg *sip.Message
|
||||
msg, err = sip.Decode(p.Data)
|
||||
if err != nil {
|
||||
@@ -345,34 +338,8 @@ func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
c.Send(msg.BuildResponse(200))
|
||||
return
|
||||
case sip.MESSAGE:
|
||||
if v, ok := c.Devices.Load(msg.From.Uri.UserInfo()); ok {
|
||||
d := v.(*Device)
|
||||
if d.Status == string(sip.REGISTER) {
|
||||
d.Status = "ONLINE"
|
||||
}
|
||||
d.UpdateTime = time.Now()
|
||||
temp := &struct {
|
||||
XMLName xml.Name
|
||||
CmdType string
|
||||
DeviceList []Channel `xml:"DeviceList>Item"`
|
||||
}{}
|
||||
decoder := xml.NewDecoder(bytes.NewReader([]byte(msg.Body)))
|
||||
decoder.CharsetReader = func(c string, i io.Reader) (io.Reader, error) {
|
||||
return charset.NewReaderLabel(c, i)
|
||||
}
|
||||
decoder.Decode(temp)
|
||||
switch temp.XMLName.Local {
|
||||
case "Notify":
|
||||
go d.Query()
|
||||
case "Response":
|
||||
switch temp.CmdType {
|
||||
case "Catalog":
|
||||
d.UpdateChannels(temp.DeviceList)
|
||||
}
|
||||
}
|
||||
if ta == nil {
|
||||
c.Send(msg.BuildResponse(200))
|
||||
}
|
||||
if c.OnMessage(msg) && ta == nil {
|
||||
c.Send(msg.BuildResponse(200))
|
||||
}
|
||||
if ta != nil {
|
||||
ta.event <- c.NewOutGoingMessageEvent(msg.BuildResponse(200))
|
||||
@@ -384,7 +351,7 @@ func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
ta.state = NIST_PROCEEDING
|
||||
c.AddTransaction(ta)
|
||||
}
|
||||
c.AddDevice(msg)
|
||||
c.OnRegister(msg)
|
||||
ta.event <- c.NewOutGoingMessageEvent(msg.BuildResponse(200))
|
||||
//case sip.INVITE:
|
||||
// ta.typo = FSM_IST
|
||||
@@ -451,17 +418,3 @@ func (c *Core) Send(msg *sip.Message) error {
|
||||
c.tp.WritePacket(pkt)
|
||||
return nil
|
||||
}
|
||||
func (c *Core) AddDevice(msg *sip.Message) *Device {
|
||||
v := &Device{
|
||||
ID: msg.From.Uri.UserInfo(),
|
||||
RegisterTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Status: string(sip.REGISTER),
|
||||
core: c,
|
||||
from: &sip.Contact{Uri: msg.StartLine.Uri, Params: make(map[string]string)},
|
||||
to: msg.To,
|
||||
Addr: msg.Via.GetSendBy(),
|
||||
}
|
||||
c.Devices.Store(msg.From.Uri.UserInfo(), v)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
DeviceID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
Secrecy int
|
||||
Status string
|
||||
device *Device
|
||||
inviteRes *sip.Message
|
||||
Connected bool
|
||||
}
|
||||
type Device struct {
|
||||
ID string
|
||||
RegisterTime time.Time
|
||||
UpdateTime time.Time
|
||||
Status string
|
||||
Channels []Channel
|
||||
core *Core
|
||||
sn int
|
||||
from *sip.Contact
|
||||
to *sip.Contact
|
||||
Addr string
|
||||
}
|
||||
|
||||
func (c *Core) RemoveDead() {
|
||||
c.Devices.Range(func(k, v interface{}) bool {
|
||||
device := v.(*Device)
|
||||
if device.UpdateTime.Sub(device.RegisterTime) > time.Duration(c.config.RegisterValidity)*time.Second {
|
||||
c.Devices.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
func (d *Device) UpdateChannels(list []Channel) {
|
||||
for _, c := range list {
|
||||
c.device = d
|
||||
have := false
|
||||
for i, o := range d.Channels {
|
||||
if o.DeviceID == c.DeviceID {
|
||||
c.inviteRes = o.inviteRes
|
||||
c.Connected = o.inviteRes != nil
|
||||
d.Channels[i] = c
|
||||
have = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !have {
|
||||
d.Channels = append(d.Channels, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c *Channel) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
|
||||
requestMsg = c.device.CreateMessage(Method)
|
||||
requestMsg.StartLine.Uri = sip.NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
|
||||
requestMsg.To = &sip.Contact{
|
||||
Uri: requestMsg.StartLine.Uri,
|
||||
}
|
||||
requestMsg.From = &sip.Contact{
|
||||
Uri: sip.NewURI(c.device.core.config.Serial + "@" + c.device.core.config.Realm),
|
||||
Params: map[string]string{"tag": utils.RandNumString(9)},
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d *Device) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
|
||||
d.sn++
|
||||
requestMsg = &sip.Message{
|
||||
Mode: sip.SIP_MESSAGE_REQUEST,
|
||||
MaxForwards: 70,
|
||||
UserAgent: "Monibuca",
|
||||
StartLine: &sip.StartLine{
|
||||
Method: Method,
|
||||
Uri: d.to.Uri,
|
||||
}, Via: &sip.Via{
|
||||
Transport: "UDP",
|
||||
Host: d.core.config.SipIP,
|
||||
Port: fmt.Sprintf("%d", d.core.config.SipPort),
|
||||
Params: map[string]string{
|
||||
"branch": fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8)),
|
||||
"rport": "-1", //only key,no-value
|
||||
},
|
||||
}, From: d.from,
|
||||
To: d.to, CSeq: &sip.CSeq{
|
||||
ID: 1,
|
||||
Method: Method,
|
||||
}, CallID: utils.RandNumString(10),
|
||||
Addr: d.Addr,
|
||||
}
|
||||
requestMsg.From.Params["tag"] = utils.RandNumString(9)
|
||||
return
|
||||
}
|
||||
func (d *Device) Query() int {
|
||||
requestMsg := d.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Query>
|
||||
<CmdType>Catalog</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
</Query>`, d.sn, requestMsg.To.Uri.UserInfo())
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
return d.core.SendMessage(requestMsg).Code
|
||||
}
|
||||
func (d *Device) Control(channelIndex int, PTZCmd string) int {
|
||||
channel := &d.Channels[channelIndex]
|
||||
requestMsg := channel.CreateMessage(sip.MESSAGE)
|
||||
requestMsg.ContentType = "Application/MANSCDP+xml"
|
||||
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
|
||||
<Control>
|
||||
<CmdType>DeviceControl</CmdType>
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<PTZCmd>%s</PTZCmd>
|
||||
</Control>`, d.sn, requestMsg.To.Uri.UserInfo(), PTZCmd)
|
||||
requestMsg.ContentLength = len(requestMsg.Body)
|
||||
return d.core.SendMessage(requestMsg).Code
|
||||
}
|
||||
func (d *Device) Invite(channelIndex int) int {
|
||||
channel := &d.Channels[channelIndex]
|
||||
port := d.core.OnInvite(channel)
|
||||
if port == 0 {
|
||||
channel.Connected = true
|
||||
return 304
|
||||
}
|
||||
sdp := fmt.Sprintf(`v=0
|
||||
o=%s 0 0 IN IP4 %s
|
||||
s=Play
|
||||
c=IN IP4 %s
|
||||
t=0 0
|
||||
m=video %d RTP/AVP 96 98 97
|
||||
a=recvonly
|
||||
a=rtpmap:96 PS/90000
|
||||
a=rtpmap:97 MPEG4/90000
|
||||
a=rtpmap:98 H264/90000
|
||||
y=0200000001
|
||||
`, d.core.config.Serial, d.core.config.MediaIP, d.core.config.MediaIP, port)
|
||||
sdp = strings.ReplaceAll(sdp, "\n", "\r\n")
|
||||
invite := channel.CreateMessage(sip.INVITE)
|
||||
invite.ContentType = "application/sdp"
|
||||
invite.Contact = &sip.Contact{
|
||||
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", d.core.config.Serial, d.core.config.SipIP, d.core.config.SipPort)),
|
||||
}
|
||||
invite.Body = sdp
|
||||
invite.ContentLength = len(sdp)
|
||||
invite.Subject = fmt.Sprintf("%s:0200000001,34020000002020000001:0", channel.DeviceID)
|
||||
response := d.core.SendMessage(invite)
|
||||
fmt.Printf("invite response statuscode: %d\n", response.Code)
|
||||
if response.Code == 200 {
|
||||
channel.inviteRes = response.Data
|
||||
channel.Connected = true
|
||||
channel.Ack()
|
||||
}
|
||||
return response.Code
|
||||
}
|
||||
func (d *Device) Bye(channelIndex int) int {
|
||||
channel := &d.Channels[channelIndex]
|
||||
defer func() {
|
||||
channel.inviteRes = nil
|
||||
channel.Connected = false
|
||||
}()
|
||||
return channel.Bye().Code
|
||||
}
|
||||
func (c *Channel) Ack() {
|
||||
ack := c.CreateMessage(sip.ACK)
|
||||
ack.From = c.inviteRes.From
|
||||
ack.To = c.inviteRes.To
|
||||
ack.CallID = c.inviteRes.CallID
|
||||
go c.device.core.Send(ack)
|
||||
}
|
||||
func (c *Channel) Bye() *Response {
|
||||
bye := c.CreateMessage(sip.BYE)
|
||||
bye.From = c.inviteRes.From
|
||||
bye.To = c.inviteRes.To
|
||||
bye.CallID = c.inviteRes.CallID
|
||||
return c.device.core.SendMessage(bye)
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
//transaction 的错误定义
|
||||
var (
|
||||
ErrorSyntax = errors.New("message syntax error")
|
||||
ErrorCheck = errors.New("message check failed")
|
||||
ErrorParse = errors.New("message parse failed")
|
||||
ErrorUnknown = errors.New("message unknown")
|
||||
ErrorSyntax = errors.New("message syntax error")
|
||||
ErrorCheck = errors.New("message check failed")
|
||||
ErrorParse = errors.New("message parse failed")
|
||||
ErrorUnknown = errors.New("message unknown")
|
||||
)
|
||||
|
||||
@@ -135,8 +135,25 @@ func ict_rcv_3456xx(t *Transaction, e *EventObj) error {
|
||||
}
|
||||
|
||||
func ict_create_ack(t *Transaction, resp *sip.Message) *sip.Message {
|
||||
|
||||
return nil
|
||||
return &sip.Message{
|
||||
Mode: t.origRequest.Mode,
|
||||
Addr: t.origRequest.Addr,
|
||||
StartLine: &sip.StartLine{
|
||||
Method: sip.ACK,
|
||||
Uri: t.origRequest.StartLine.Uri,
|
||||
},
|
||||
MaxForwards: t.origRequest.MaxForwards,
|
||||
CallID: t.callID,
|
||||
Contact: t.origRequest.Contact,
|
||||
UserAgent: t.origRequest.UserAgent,
|
||||
Via: t.via,
|
||||
From: t.from,
|
||||
To: t.to,
|
||||
CSeq: &sip.CSeq{
|
||||
ID: 1,
|
||||
Method: sip.ACK,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ict_retransmit_ack(t *Transaction, e *EventObj) error {
|
||||
|
||||
@@ -128,7 +128,7 @@ func nict_rcv_1xx(t *Transaction, e *EventObj) error {
|
||||
func nict_rcv_23456xx(t *Transaction, e *EventObj) error {
|
||||
t.lastResponse = e.msg
|
||||
t.state = NICT_COMPLETED
|
||||
|
||||
|
||||
if e.msg.IsReliable() {
|
||||
//不设置timerK
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ func (c *TCPClient) Start() error {
|
||||
if err != nil {
|
||||
fmt.Println("dial tcp server failed :", err.Error())
|
||||
return err
|
||||
}else{
|
||||
} else {
|
||||
fmt.Println("start tcp client")
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ func NewClient(config *transaction.Config, static *ClientStatic) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO:对于一个TU,开启之后
|
||||
//运行一个sip client
|
||||
func RunClient() {
|
||||
@@ -82,7 +81,7 @@ func RunClient() {
|
||||
//TODO:先发起注册
|
||||
//TODO:build sip message
|
||||
msg := BuildMessageRequest("", "", "", "", "", "",
|
||||
0, 0, 0,"")
|
||||
0, 0, 0, "")
|
||||
resp := c.SendMessage(msg)
|
||||
if resp.Code != 0 {
|
||||
fmt.Println("request failed")
|
||||
|
||||
@@ -20,7 +20,7 @@ expires: 过期时间
|
||||
cseq:消息序列号,当前对话递增
|
||||
*/
|
||||
//构建消息:以客户端(可能是IPC,也可能是SIP Server)的角度
|
||||
func BuildMessageRequest(method sip.Method, transport, sipSerial, sipRealm, username , srcIP string, srcPort uint16, expires, cseq int,body string) *sip.Message {
|
||||
func BuildMessageRequest(method sip.Method, transport, sipSerial, sipRealm, username, srcIP string, srcPort uint16, expires, cseq int, body string) *sip.Message {
|
||||
server := fmt.Sprintf("%s@%s", sipSerial, sipRealm)
|
||||
client := fmt.Sprintf("%s@%s", username, sipRealm)
|
||||
|
||||
@@ -62,7 +62,7 @@ func BuildMessageRequest(method sip.Method, transport, sipSerial, sipRealm, user
|
||||
msg.Contact = &sip.Contact{
|
||||
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", username, srcIP, srcPort)),
|
||||
}
|
||||
if len(body)>0{
|
||||
if len(body) > 0 {
|
||||
msg.ContentLength = len(body)
|
||||
msg.Body = body
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type Server struct {
|
||||
//提供config参数
|
||||
func NewServer(config *transaction.Config) *Server {
|
||||
return &Server{
|
||||
Core: transaction.NewCore(config),
|
||||
Core: transaction.NewCore(config),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
564
ui/dist/plugin-gb28181.common.js
vendored
564
ui/dist/plugin-gb28181.common.js
vendored
File diff suppressed because one or more lines are too long
2
ui/dist/plugin-gb28181.common.js.map
vendored
2
ui/dist/plugin-gb28181.common.js.map
vendored
File diff suppressed because one or more lines are too long
2
ui/dist/plugin-gb28181.css
vendored
2
ui/dist/plugin-gb28181.css
vendored
@@ -1 +1 @@
|
||||
.arrow1[data-v-76558468]{grid-column:2;grid-row:1}.arrow2[data-v-76558468]{transform:rotate(90deg);grid-column:3;grid-row:2}.arrow3[data-v-76558468]{transform:rotate(180deg);grid-column:2;grid-row:3}.arrow4[data-v-76558468]{transform:rotate(270deg);grid-column:1;grid-row:2}.arrow5[data-v-76558468]{transform:rotate(-45deg);grid-column:1;grid-row:1}.arrow6[data-v-76558468]{transform:rotate(45deg);grid-column:3;grid-row:1}.arrow7[data-v-76558468]{transform:rotate(-135deg);grid-column:1;grid-row:3}.arrow8[data-v-76558468]{transform:rotate(135deg);grid-column:3;grid-row:3}.arrow9[data-v-76558468]{grid-column:2;grid-row:2}.container[data-v-76558468]{position:relative;height:350px}.control[data-v-76558468]{position:absolute;top:20px;right:0;display:grid;grid-template-columns:repeat(3,33.33%);grid-template-rows:repeat(3,33.33%);width:192px;height:192px}.control2[data-v-76558468]{top:210px}.control3[data-v-76558468]{top:260px}.control4[data-v-76558468]{top:310px}.control5[data-v-76558468]{top:360px}.control>[data-v-76558468]{cursor:pointer;fill:grey;width:50px;height:50px}.control5>[data-v-76558468]{margin-right:10px}.control2>[data-v-76558468],.control3>[data-v-76558468],.control4>[data-v-76558468]{width:40px;height:40px}.control>[data-v-76558468]:hover,.cycling[data-v-76558468]{fill:#0ff}.player-wrap[data-v-683c37a8]{width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px #40d3fc,inset 0 0 5px #40d3fc,0 0 0 1px #40d3fc}.player-wrap video[data-v-683c37a8]{width:100%;height:100%}.flex-box[data-v-31458ad7]{display:flex;flex-flow:row wrap;align-content:flex-start}.flex-item[data-v-31458ad7]{flex:0 0 33.3333%;height:275px;box-sizing:border-box;padding:10px}
|
||||
.arrow1[data-v-43b2c727]{grid-column:2;grid-row:1}.arrow2[data-v-43b2c727]{transform:rotate(90deg);grid-column:3;grid-row:2}.arrow3[data-v-43b2c727]{transform:rotate(180deg);grid-column:2;grid-row:3}.arrow4[data-v-43b2c727]{transform:rotate(270deg);grid-column:1;grid-row:2}.arrow5[data-v-43b2c727]{transform:rotate(-45deg);grid-column:1;grid-row:1}.arrow6[data-v-43b2c727]{transform:rotate(45deg);grid-column:3;grid-row:1}.arrow7[data-v-43b2c727]{transform:rotate(-135deg);grid-column:1;grid-row:3}.arrow8[data-v-43b2c727]{transform:rotate(135deg);grid-column:3;grid-row:3}.arrow9[data-v-43b2c727]{grid-column:2;grid-row:2}.container[data-v-43b2c727]{position:relative;height:350px}.control[data-v-43b2c727]{position:absolute;top:20px;right:0;display:grid;grid-template-columns:repeat(3,33.33%);grid-template-rows:repeat(3,33.33%);width:192px;height:192px}.control2[data-v-43b2c727]{top:210px}.control3[data-v-43b2c727]{top:260px}.control4[data-v-43b2c727]{top:310px}.control5[data-v-43b2c727]{top:360px}.control>[data-v-43b2c727]{cursor:pointer;fill:grey;width:50px;height:50px}.control5>[data-v-43b2c727]{margin-right:10px}.control2>[data-v-43b2c727],.control3>[data-v-43b2c727],.control4>[data-v-43b2c727]{width:40px;height:40px}.control>[data-v-43b2c727]:hover,.cycling[data-v-43b2c727]{fill:#0ff}.player-wrap[data-v-3d23233a]{width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px #40d3fc,inset 0 0 5px #40d3fc,0 0 0 1px #40d3fc}.player-wrap video[data-v-3d23233a]{width:100%;height:100%}.container[data-v-1496ea1d]{position:relative;height:500px;background-image:radial-gradient(rgba(197,45,208,.48),rgba(74,23,152,.48),rgba(3,0,19,.48));color:#fff;background-color:#000;overflow:auto}.search[data-v-1496ea1d]{padding:10px 0}.flex-box[data-v-6660cc55]{display:flex;flex-flow:row wrap;align-content:flex-start}.flex-item[data-v-6660cc55]{flex:0 0 33.3333%;height:275px;box-sizing:border-box;padding:10px}
|
||||
564
ui/dist/plugin-gb28181.umd.js
vendored
564
ui/dist/plugin-gb28181.umd.js
vendored
File diff suppressed because one or more lines are too long
2
ui/dist/plugin-gb28181.umd.js.map
vendored
2
ui/dist/plugin-gb28181.umd.js.map
vendored
File diff suppressed because one or more lines are too long
6
ui/dist/plugin-gb28181.umd.min.js
vendored
6
ui/dist/plugin-gb28181.umd.min.js
vendored
File diff suppressed because one or more lines are too long
2
ui/dist/plugin-gb28181.umd.min.js.map
vendored
2
ui/dist/plugin-gb28181.umd.min.js.map
vendored
File diff suppressed because one or more lines are too long
3298
ui/package-lock.json
generated
3298
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,5 +11,8 @@
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^4.5.4",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^2.6.12"
|
||||
}
|
||||
}
|
||||
|
||||
130
ui/src/App.vue
130
ui/src/App.vue
@@ -16,22 +16,28 @@
|
||||
<td>
|
||||
<mu-button
|
||||
flat
|
||||
v-if="item.Connected"
|
||||
@click="ptz(prop.row.ID, $index, item)"
|
||||
>云台
|
||||
</mu-button>
|
||||
<mu-button
|
||||
<!-- <mu-button
|
||||
flat
|
||||
v-if="item.Connected"
|
||||
@click="bye(prop.row.ID, $index)"
|
||||
>断开</mu-button
|
||||
>
|
||||
@click="bye(prop.row.ID, $index, item)"
|
||||
>断开
|
||||
</mu-button>
|
||||
<mu-button
|
||||
v-else
|
||||
flat
|
||||
@click="invite(prop.row.ID, $index, item)"
|
||||
>连接
|
||||
</mu-button>
|
||||
</mu-button> -->
|
||||
<mu-button
|
||||
flat
|
||||
@click="
|
||||
getRecords(prop.row.ID, $index, item)
|
||||
"
|
||||
>录像</mu-button
|
||||
>
|
||||
</td>
|
||||
</template>
|
||||
</mu-data-table>
|
||||
@@ -52,33 +58,58 @@
|
||||
</div>
|
||||
<div class="tabpanel" v-if="$parent.titleTabActive === 1">
|
||||
<div class="flex-box">
|
||||
<template v-for="(channel,index) in channelShowList">
|
||||
<div class="flex-item" :key="index" v-if="channel.DeviceID">
|
||||
<webrtc-player2 :stream-path="'gb28181/'+channel.DeviceID"></webrtc-player2>
|
||||
<template v-for="(channel, index) in channelShowList">
|
||||
<div class="flex-item" :key="index">
|
||||
<webrtc-player2
|
||||
@hook:mounted="
|
||||
invite(
|
||||
channel.device.ID,
|
||||
channel.device.Channels.indexOf(channel),
|
||||
channel,
|
||||
'v/2/1/15/1/200a///'
|
||||
)
|
||||
"
|
||||
:stream-path="
|
||||
channel.device.ID + '/' + channel.DeviceID
|
||||
"
|
||||
></webrtc-player2>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="channelList.length > 0">
|
||||
<Page :total="channelList.length" :page-size="pageInfo.onePageSize" @on-change="handlePageChange"></Page>
|
||||
<Page
|
||||
:total="channelList.length"
|
||||
:page-size="pageInfo.onePageSize"
|
||||
@on-change="handlePageChange"
|
||||
></Page>
|
||||
</template>
|
||||
</div>
|
||||
<webrtc-player
|
||||
ref="player"
|
||||
@ptz="sendPtz"
|
||||
v-model="previewStreamPath"
|
||||
:PublicIP="PublicIP"
|
||||
></webrtc-player>
|
||||
<records
|
||||
ref="records"
|
||||
v-model="recordModal"
|
||||
:search="recordSearch"
|
||||
:channel="currentChannel"
|
||||
@close="initRecordSearch"
|
||||
></records>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import WebrtcPlayer from "./components/Player";
|
||||
import WebrtcPlayer2 from "./components/Player2";
|
||||
import Records from "./components/Records";
|
||||
import { getPTZCmd, PTZ_TYPE } from "./utils/ptz-cmd";
|
||||
import { getOneTimeRange } from "./utils";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WebrtcPlayer,
|
||||
WebrtcPlayer2,
|
||||
Records,
|
||||
},
|
||||
props: {
|
||||
ListenAddr: String,
|
||||
@@ -97,7 +128,14 @@ export default {
|
||||
pageInfo: {
|
||||
onePageSize: 9,
|
||||
totalPage: 0,
|
||||
currentPage: 0
|
||||
currentPage: 0,
|
||||
},
|
||||
currentChannel: null,
|
||||
recordModal: false,
|
||||
recordSearch: {
|
||||
id: null,
|
||||
channel: null,
|
||||
deviceId: null,
|
||||
},
|
||||
context: {
|
||||
id: null,
|
||||
@@ -134,7 +172,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchlist() {
|
||||
const listES = new EventSource(this.apiHost + "/gb28181/list");
|
||||
const listES = new EventSource("/gb28181/list");
|
||||
listES.onmessage = (evt) => {
|
||||
if (!evt.data) return;
|
||||
this.Devices = JSON.parse(evt.data) || [];
|
||||
@@ -143,7 +181,18 @@ export default {
|
||||
this.Devices.forEach((device) => {
|
||||
const channels = device.Channels || [];
|
||||
if (channels.length > 0) {
|
||||
channelList = channelList.concat(channels);
|
||||
channelList = channelList.concat(
|
||||
channels.map((x) => ((x.device = device), x))
|
||||
);
|
||||
}
|
||||
if (this.recordSearch.id && this.recordSearch.deviceId) {
|
||||
const channel = channels.find((i) => {
|
||||
return (
|
||||
i.DeviceID === this.recordSearch.deviceId &&
|
||||
this.recordSearch.id === device.ID
|
||||
);
|
||||
});
|
||||
if (channel) this.currentChannel = channel;
|
||||
}
|
||||
});
|
||||
if (channelList.length > 0) {
|
||||
@@ -153,15 +202,23 @@ export default {
|
||||
};
|
||||
this.$once("hook:destroyed", () => listES.close());
|
||||
},
|
||||
ptz(id, channel, item) {
|
||||
async ptz(id, channel, item) {
|
||||
await this.invite(id, channel, item);
|
||||
this.context = {
|
||||
id,
|
||||
channel,
|
||||
item,
|
||||
};
|
||||
this.previewStreamPath = true;
|
||||
const unwatch = this.$watch(
|
||||
"previewStreamPath",
|
||||
(newValue, oldValue) => {
|
||||
this.bye(id, channel, item);
|
||||
unwatch();
|
||||
}
|
||||
);
|
||||
this.$nextTick(() =>
|
||||
this.$refs.player.play("gb28181/" + item.DeviceID)
|
||||
this.$refs.player.play(id + "/" + item.DeviceID)
|
||||
);
|
||||
},
|
||||
sendPtz(options) {
|
||||
@@ -190,21 +247,24 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
handlePageChange(page){
|
||||
sendQueryRecords(options) {},
|
||||
|
||||
handlePageChange(page) {
|
||||
let showList = [];
|
||||
const onePageSize = this.pageInfo.onePageSize;
|
||||
const firstIndex = page * onePageSize - onePageSize;
|
||||
const lastIndex = page * onePageSize - 1 ;
|
||||
const lastIndex = page * onePageSize - 1;
|
||||
showList = this.channelList.filter((item, index) => {
|
||||
return index >= firstIndex && index <= lastIndex;
|
||||
});
|
||||
|
||||
this.channelShowList = showList;
|
||||
if(showList.length > 0){
|
||||
|
||||
if (showList.length > 0) {
|
||||
this.pageInfo.currentPage = page;
|
||||
}
|
||||
},
|
||||
updatePageInfo(totalSize){
|
||||
updatePageInfo(totalSize) {
|
||||
const onePageSize = this.pageInfo.onePageSize;
|
||||
let totalPage = totalSize / onePageSize;
|
||||
|
||||
@@ -212,20 +272,36 @@ export default {
|
||||
totalPage = totalPage + 1;
|
||||
}
|
||||
this.pageInfo.totalPage = totalPage;
|
||||
if(this.pageInfo.currentPage === 0){
|
||||
this.handlePageChange(1)
|
||||
if (this.pageInfo.currentPage === 0) {
|
||||
this.handlePageChange(1);
|
||||
}
|
||||
},
|
||||
invite(id, channel, item) {
|
||||
this.ajax.get("/gb28181/invite", { id, channel }).then((x) => {
|
||||
item.Connected = true;
|
||||
});
|
||||
invite(id, channel, item, f = "") {
|
||||
return this.ajax
|
||||
.get("/gb28181/invite", { id, channel, f })
|
||||
.then((x) => {
|
||||
item.Connected = true;
|
||||
});
|
||||
},
|
||||
bye(id, channel, item) {
|
||||
this.ajax.get("/gb28181/bye", { id, channel }).then((x) => {
|
||||
return this.ajax.get("/gb28181/bye", { id, channel }).then((x) => {
|
||||
item.Connected = false;
|
||||
});
|
||||
},
|
||||
|
||||
getRecords(id, channel, item) {
|
||||
this.recordSearch.id = id;
|
||||
this.recordSearch.channel = channel;
|
||||
this.recordSearch.deviceId = item.DeviceID;
|
||||
this.recordModal = true;
|
||||
},
|
||||
|
||||
initRecordSearch() {
|
||||
this.recordModal = false;
|
||||
this.recordSearch.id = null;
|
||||
this.recordSearch.channel = null;
|
||||
this.recordSearch.deviceId = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -65,9 +65,6 @@ export default {
|
||||
ptzPositionIndex:1,
|
||||
ptzType:PTZ_TYPE,
|
||||
};
|
||||
},
|
||||
props:{
|
||||
PublicIP:String
|
||||
},
|
||||
methods: {
|
||||
async play(streamPath) {
|
||||
|
||||
@@ -71,11 +71,12 @@
|
||||
const result = await this.ajax({
|
||||
type: "POST",
|
||||
processData: false,
|
||||
data: localDescriptionData,
|
||||
data: JSON.stringify(localDescriptionData),
|
||||
url: "/webrtc/play?streamPath=" + streamPath,
|
||||
dataType: "json"
|
||||
});
|
||||
if (result.error) {
|
||||
if (result.errmsg) {
|
||||
console.error(result.errmsg);
|
||||
return;
|
||||
}
|
||||
//
|
||||
|
||||
172
ui/src/components/Records.vue
Normal file
172
ui/src/components/Records.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-bind="$attrs"
|
||||
draggable
|
||||
width="900"
|
||||
v-on="$listeners"
|
||||
title="录像列表"
|
||||
@on-ok="$emit('close')"
|
||||
>
|
||||
<webrtc-player2
|
||||
v-if="channel && channel.RecordSP && player"
|
||||
:streamPath="channel.RecordSP"
|
||||
></webrtc-player2>
|
||||
<div class="container" v-else-if="!player">
|
||||
<div class="search">
|
||||
<DatePicker
|
||||
type="date"
|
||||
:options="timeOptions"
|
||||
:value="search.time"
|
||||
placeholder="请选择时间"
|
||||
style="width: 200px"
|
||||
:clearable="false"
|
||||
@on-change="handleTimeChange"
|
||||
></DatePicker>
|
||||
</div>
|
||||
<div>
|
||||
<mu-data-table :columns="columns" :data="recordList">
|
||||
<template #default="scope">
|
||||
<td>{{ scope.row.DeviceID }}</td>
|
||||
<td>{{ scope.row.Name }}</td>
|
||||
<td>{{ scope.row.startTime }}</td>
|
||||
<td>{{ scope.row.endTime }}</td>
|
||||
<td>{{ scope.row.length }}</td>
|
||||
<td>
|
||||
<m-button @click="play(scope.row)">播放</m-button>
|
||||
</td>
|
||||
</template>
|
||||
</mu-data-table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>正在连接,请稍后</div>
|
||||
<div slot="footer" v-if="player">
|
||||
<mu-button @click="back">返回</mu-button>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getOneTimeRange, formatTimeTips, parseTime, isDef } from "../utils";
|
||||
import WebrtcPlayer2 from "./Player2";
|
||||
const _now = new Date();
|
||||
|
||||
export default {
|
||||
name: "Records",
|
||||
components: {
|
||||
WebrtcPlayer2,
|
||||
},
|
||||
props: ["search", "channel"],
|
||||
data() {
|
||||
return {
|
||||
player: false,
|
||||
timeOptions: {
|
||||
disabledDate(date) {
|
||||
return date && date.valueOf() > Date.now();
|
||||
},
|
||||
},
|
||||
columns: Object.freeze(
|
||||
["设备ID", "名称", "开始时间", "结束时间", "时长", "操作"].map(
|
||||
(title) => ({
|
||||
title,
|
||||
})
|
||||
)
|
||||
),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
records() {
|
||||
return (this.channel && this.channel.Records) || [];
|
||||
},
|
||||
startTime() {
|
||||
if (!this.search.time) {
|
||||
return "";
|
||||
}
|
||||
const start = getOneTimeRange(this.search.time).start;
|
||||
const isoString = new Date(start).toISOString();
|
||||
return isoString.replace(".000Z", "");
|
||||
},
|
||||
endTime() {
|
||||
if (!this.search.time) {
|
||||
return "";
|
||||
}
|
||||
const end = getOneTimeRange(this.search.time).end;
|
||||
const isoString = new Date(end).toISOString();
|
||||
return isoString.replace(".000Z", "");
|
||||
},
|
||||
recordList() {
|
||||
const list = this.records.map((record) => {
|
||||
const startTime = new Date(record.StartTime).getTime();
|
||||
const endTime = new Date(record.EndTime).getTime();
|
||||
const timestamp = endTime - startTime;
|
||||
const timeLength = formatTimeTips(timestamp / 1000);
|
||||
|
||||
const _startTime = parseTime(startTime);
|
||||
const _endTime = parseTime(endTime);
|
||||
record._startTime = (startTime / 1000) >> 0;
|
||||
record._endTime = (endTime / 1000) >> 0;
|
||||
record.length = timeLength;
|
||||
record.startTime = _startTime;
|
||||
record.endTime = _endTime;
|
||||
return record;
|
||||
});
|
||||
return list;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this._fetchList();
|
||||
},
|
||||
methods: {
|
||||
_fetchList() {
|
||||
if (
|
||||
isDef(this.search.id) &&
|
||||
isDef(this.search.channel) &&
|
||||
this.startTime &&
|
||||
this.endTime
|
||||
) {
|
||||
const query = {
|
||||
id: this.search.id,
|
||||
channel: this.search.channel,
|
||||
startTime: this.startTime,
|
||||
endTime: this.endTime,
|
||||
};
|
||||
|
||||
this.ajax.get("/gb28181/query/records", query).then((x) => {});
|
||||
}
|
||||
},
|
||||
handleTimeChange(date) {
|
||||
this.search.time = new Date(date);
|
||||
this._fetchList();
|
||||
},
|
||||
play(record) {
|
||||
const query = {
|
||||
id: this.search.id,
|
||||
channel: this.search.channel,
|
||||
startTime: record._startTime,
|
||||
endTime: record._endTime,
|
||||
};
|
||||
|
||||
this.ajax.get("/gb28181/invite", query).then((x) => {});
|
||||
this.player = true;
|
||||
},
|
||||
back() {
|
||||
fetch("/api/stop?stream=" + this.streamPath);
|
||||
this.player = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
height: 500px;
|
||||
background-image: radial-gradient(#c52dd07a, #4a17987a, #0300137a);
|
||||
color: #ffffff;
|
||||
background-color: black;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
125
ui/src/utils/index.js
Normal file
125
ui/src/utils/index.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Date:2020/12/24
|
||||
* Desc:
|
||||
*/
|
||||
|
||||
export function getOneTimeRange(time, options) {
|
||||
let date;
|
||||
|
||||
// 都为空的时候
|
||||
if (!time && !options) {
|
||||
date = new Date();
|
||||
} else if (Object.prototype.toString.call(time) !== '[object Date]' && time !== null && typeof time === 'object') {
|
||||
// time 为 options 参数。
|
||||
options = time;
|
||||
date = new Date();
|
||||
} else if (Object.prototype.toString.call(time) === '[object Date]') {
|
||||
// time 是时间格式
|
||||
date = time;
|
||||
} else {
|
||||
// time 是 int 格式。
|
||||
if (('' + time).length === 10) time = parseInt(time) * 1000;
|
||||
time = +time; // 转成int 型
|
||||
date = new Date(time);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
let result = {
|
||||
start: 0,
|
||||
end: 0
|
||||
};
|
||||
|
||||
let _startTime = new Date(date).setHours(options.startHour || 0, options.startMin || 0, 0, 0);
|
||||
let _endTime = new Date(date).setHours(options.endHour || 23, options.endMin || 59, 59, 0);
|
||||
result.start = new Date(_startTime).getTime();
|
||||
result.end = new Date(_endTime).getTime();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
export function formatTimestamp(t) {
|
||||
var d = 0,
|
||||
h = 0,
|
||||
m = 0,
|
||||
s = 0;
|
||||
if (t > 0) {
|
||||
d = Math.floor(t / 1000 / 3600 / 24)
|
||||
h = Math.floor(t / 1000 / 60 / 60 % 24)
|
||||
m = Math.floor(t / 1000 / 60 % 60)
|
||||
s = Math.floor(t / 1000 % 60)
|
||||
}
|
||||
|
||||
return `${d}天${h}时${m}分${s}秒`
|
||||
}
|
||||
|
||||
// 单位秒
|
||||
export function formatTimeTips(timestamp) {
|
||||
let result;
|
||||
|
||||
//
|
||||
if (timestamp > -1) {
|
||||
let hour = Math.floor(timestamp / 3600);
|
||||
let min = Math.floor(timestamp / 60) % 60;
|
||||
let sec = timestamp % 60;
|
||||
|
||||
sec = Math.round(sec);
|
||||
|
||||
if (hour < 10) {
|
||||
result = '0' + hour + ":";
|
||||
} else {
|
||||
result = hour + ":";
|
||||
}
|
||||
|
||||
if (min < 10) {
|
||||
result += "0";
|
||||
}
|
||||
result += min + ":";
|
||||
if (sec < 10) {
|
||||
result += "0";
|
||||
}
|
||||
result += sec.toFixed(0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null
|
||||
}
|
||||
var format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
var date;
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if (('' + time).length === 10) time = parseInt(time) * 1000;
|
||||
time = +time; // 转成int 型
|
||||
date = new Date(time)
|
||||
}
|
||||
var formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
var time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
var value = formatObj[key]
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value
|
||||
}
|
||||
return value || 0
|
||||
});
|
||||
return time_str
|
||||
}
|
||||
|
||||
|
||||
export function isDef(v) {
|
||||
return v !== undefined && v !== null;
|
||||
}
|
||||
Reference in New Issue
Block a user