mirror of
https://github.com/Monibuca/plugin-gb28181.git
synced 2025-12-24 13:27:57 +08:00
Compare commits
121 Commits
v1.0.0-alp
...
dwdcth-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1ef3a560 | ||
|
|
8c248fc0c0 | ||
|
|
51d8a8fa91 | ||
|
|
1105d732ae | ||
|
|
72be771799 | ||
|
|
5cae3bd5c4 | ||
|
|
686ce753a3 | ||
|
|
0f3fda9159 | ||
|
|
0fcd23cee0 | ||
|
|
28f5774073 | ||
|
|
e813086907 | ||
|
|
8a8d97c676 | ||
|
|
356142a2db | ||
|
|
0c9712e1e5 | ||
|
|
f22a6303c6 | ||
|
|
53bb479588 | ||
|
|
aea30f751a | ||
|
|
4b33a4c8a3 | ||
|
|
0a0f58b1db | ||
|
|
4528a67e92 | ||
|
|
87ac220de1 | ||
|
|
640c310a77 | ||
|
|
53bb4244db | ||
|
|
6b193a4208 | ||
|
|
bf54332376 | ||
|
|
a527903825 | ||
|
|
bb0209fed9 | ||
|
|
23e01d4ccc | ||
|
|
c52549b0c2 | ||
|
|
3a00e5dccc | ||
|
|
65d758ed0b | ||
|
|
5cf4b49033 | ||
|
|
04c93225a1 | ||
|
|
709b394af6 | ||
|
|
57e7977eae | ||
|
|
ed0826a35d | ||
|
|
4495bd2d4d | ||
|
|
7d235cdd04 | ||
|
|
6188118ef6 | ||
|
|
0481b71d52 | ||
|
|
6ab0f9d1c8 | ||
|
|
fd3564afe0 | ||
|
|
0d0208f0f7 | ||
|
|
cc47907517 | ||
|
|
8729b32cb2 | ||
|
|
43fa8b3c37 | ||
|
|
c33b93431a | ||
|
|
1c8618076d | ||
|
|
12d069d949 | ||
|
|
779a868821 | ||
|
|
5841faa3cb | ||
|
|
e5f9aa54c1 | ||
|
|
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 | ||
|
|
7cfd4fccbd | ||
|
|
211c8bd32c | ||
|
|
f3b0595863 | ||
|
|
de07b41647 | ||
|
|
38220d62e3 | ||
|
|
818fd6bd33 | ||
|
|
8154d852f4 | ||
|
|
c4a54d7eae | ||
|
|
3ffb58606a | ||
|
|
c284e4e28e | ||
|
|
7269ec50de | ||
|
|
e33079e36b | ||
|
|
d1de189dcf | ||
|
|
e45b266de9 | ||
|
|
8663a9ecef | ||
|
|
f36ddce527 | ||
|
|
70f2c64a5c | ||
|
|
2d61fc6308 | ||
|
|
dd23d81a40 | ||
|
|
a758a770f1 | ||
|
|
1b9711ea2f | ||
|
|
f6eec6d6b7 | ||
|
|
801ccb98ca | ||
|
|
c8323822e8 | ||
|
|
7dfb19d9c3 | ||
|
|
934926e596 | ||
|
|
f06b9146be | ||
|
|
b4b04ec0f9 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.vscode
|
||||
.vscode
|
||||
.idea
|
||||
287
channel.go
Normal file
287
channel.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
type ChannelEx struct {
|
||||
device *Device
|
||||
inviteRes *sip.Message
|
||||
recordInviteRes *sip.Message
|
||||
RecordSP string //正在播放录像的StreamPath
|
||||
LiveSP string //实时StreamPath
|
||||
Records []*Record
|
||||
RecordStartTime string
|
||||
RecordEndTime string
|
||||
recordStartTime time.Time
|
||||
recordEndTime time.Time
|
||||
}
|
||||
|
||||
// Channel 通道
|
||||
type Channel struct {
|
||||
DeviceID string
|
||||
ParentID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Model string
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
Secrecy int
|
||||
Status string
|
||||
Children []*Channel
|
||||
ChannelEx //自定义属性
|
||||
}
|
||||
|
||||
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 (channel *Channel) QueryRecord(startTime, endTime string) int {
|
||||
d := channel.device
|
||||
channel.RecordStartTime = startTime
|
||||
channel.RecordEndTime = endTime
|
||||
channel.recordStartTime, _ = time.Parse(TIME_LAYOUT, startTime)
|
||||
channel.recordEndTime, _ = time.Parse(TIME_LAYOUT, endTime)
|
||||
channel.Records = nil
|
||||
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 (channel *Channel) Control(PTZCmd string) int {
|
||||
d := channel.device
|
||||
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 (channel *Channel) Invite(start, end string) int {
|
||||
sint, err1 := strconv.ParseInt(start,10,0)
|
||||
eint, err2 := strconv.ParseInt(end,10,0)
|
||||
d := channel.device
|
||||
streamPath := fmt.Sprintf("%s/%s", d.ID, channel.DeviceID)
|
||||
s := "Play"
|
||||
ssrc := make([]byte, 10)
|
||||
if start != "" {
|
||||
if err1 != nil || err2 != nil {
|
||||
return 400
|
||||
}
|
||||
s = "Playback"
|
||||
ssrc[0] = '1'
|
||||
streamPath = fmt.Sprintf("%s/%s/%s-%s", d.ID, channel.DeviceID, start, end)
|
||||
} else {
|
||||
ssrc[0] = '0'
|
||||
}
|
||||
var publisher Publisher
|
||||
// size := 1
|
||||
// fps := 15
|
||||
// bitrate := 200
|
||||
// fmt.Sprintf("f=v/2/%d/%d/1/%da///", size, fps, bitrate)
|
||||
copy(ssrc[1:6], []byte(config.Serial[3:8]))
|
||||
randNum := rand.Intn(10000)
|
||||
copy(ssrc[6:], []byte(strconv.Itoa(randNum)))
|
||||
_ssrc := string(ssrc)
|
||||
_SSRC, _ := strconv.Atoi(_ssrc)
|
||||
SSRC := uint32(_SSRC)
|
||||
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=%d %d", sint, eint),
|
||||
fmt.Sprintf("m=video %d RTP/AVP 96 97 98", config.MediaPort),
|
||||
"a=recvonly",
|
||||
"a=rtpmap:96 PS/90000",
|
||||
"a=rtpmap:97 MPEG4/90000",
|
||||
"a=rtpmap:98 H264/90000",
|
||||
"y=" + _ssrc,
|
||||
}
|
||||
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 {
|
||||
ds := strings.Split(response.Data.Body, "\r\n")
|
||||
for _, l := range ds {
|
||||
if ls := strings.Split(l, "="); len(ls) > 1 {
|
||||
if ls[0] == "y" {
|
||||
_ssrc = ls[1]
|
||||
_SSRC, _ = strconv.Atoi(_ssrc)
|
||||
SSRC = uint32(_SSRC)
|
||||
}
|
||||
}
|
||||
}
|
||||
publisher.Stream = &engine.Stream{
|
||||
StreamPath: streamPath,
|
||||
Type: "GB18181",
|
||||
AutoUnPublish: config.AutoUnPublish,
|
||||
}
|
||||
if start == "" {
|
||||
publisher.Close = func() {
|
||||
publishers.Remove(SSRC)
|
||||
channel.LiveSP = ""
|
||||
if channel.inviteRes != nil {
|
||||
channel.ByeBye(channel.inviteRes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
publisher.Close = func() {
|
||||
publishers.Remove(SSRC)
|
||||
channel.RecordSP = ""
|
||||
if channel.recordInviteRes != nil {
|
||||
channel.ByeBye(channel.recordInviteRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !publisher.Publish() {
|
||||
return 403
|
||||
}
|
||||
publishers.Add(SSRC, &publisher)
|
||||
if start == "" {
|
||||
channel.inviteRes = response.Data
|
||||
channel.LiveSP = _ssrc
|
||||
} else {
|
||||
channel.RecordSP = _ssrc
|
||||
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 (channel *Channel) Bye() int {
|
||||
if channel.inviteRes != nil {
|
||||
defer func() {
|
||||
channel.inviteRes = nil
|
||||
SSRC, _ := strconv.Atoi(channel.LiveSP)
|
||||
if p := publishers.Get(uint32(SSRC)); p != nil {
|
||||
p.Close()
|
||||
}
|
||||
}()
|
||||
return channel.ByeBye(channel.inviteRes).Code
|
||||
}
|
||||
if channel.recordInviteRes != nil {
|
||||
defer func() {
|
||||
channel.recordInviteRes = nil
|
||||
SSRC, _ := strconv.Atoi(channel.RecordSP)
|
||||
if p := publishers.Get(uint32(SSRC)); p != nil {
|
||||
p.Close()
|
||||
}
|
||||
}()
|
||||
return channel.ByeBye(channel.recordInviteRes).Code
|
||||
}
|
||||
return 404
|
||||
}
|
||||
func (c *Channel) ByeBye(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)
|
||||
}
|
||||
140
device.go
Normal file
140
device.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
// . "github.com/Monibuca/utils/v3"
|
||||
// . "github.com/logrusorgru/aurora"
|
||||
)
|
||||
|
||||
const TIME_LAYOUT = "2006-01-02T15:04:05"
|
||||
|
||||
// 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
|
||||
channelMap map[string]*Channel
|
||||
channelMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (d *Device) addChannel(channel *Channel) {
|
||||
for _, c := range d.Channels {
|
||||
if c.DeviceID == channel.DeviceID {
|
||||
return
|
||||
}
|
||||
}
|
||||
d.Channels = append(d.Channels, channel)
|
||||
}
|
||||
func (d *Device) UpdateChannels(list []*Channel) {
|
||||
d.channelMutex.Lock()
|
||||
defer d.channelMutex.Unlock()
|
||||
for _, c := range list {
|
||||
c.device = d
|
||||
if c.ParentID != "" {
|
||||
path := strings.Split(c.ParentID, "/")
|
||||
parentId := path[len(path)-1]
|
||||
if parent, ok := d.channelMap[parentId]; ok {
|
||||
parent.Children = append(parent.Children, c)
|
||||
} else {
|
||||
d.addChannel(c)
|
||||
}
|
||||
} else {
|
||||
d.addChannel(c)
|
||||
}
|
||||
if old, ok := d.channelMap[c.DeviceID]; ok {
|
||||
c.ChannelEx = old.ChannelEx
|
||||
if len(old.Children) == 0 {
|
||||
n := time.Now()
|
||||
n = time.Date(n.Year(), n.Month(), n.Day(), 0, 0, 0, 0, time.Local)
|
||||
if len(c.Records) == 0 || (n.Format(TIME_LAYOUT) == c.RecordStartTime && n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT) == c.RecordEndTime) {
|
||||
go c.QueryRecord(n.Format(TIME_LAYOUT), n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT))
|
||||
}
|
||||
if config.AutoInvite && c.LiveSP == "" {
|
||||
go c.Invite("", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
d.channelMap[c.DeviceID] = c
|
||||
}
|
||||
}
|
||||
func (d *Device) UpdateRecord(channelId string, list []*Record) {
|
||||
d.channelMutex.RLock()
|
||||
if c, ok := d.channelMap[channelId]; ok {
|
||||
c.Records = append(c.Records, list...)
|
||||
}
|
||||
d.channelMutex.RUnlock()
|
||||
}
|
||||
|
||||
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: &sip.Contact{Uri: d.from.Uri, Params: map[string]string{"tag": utils.RandNumString(9)}},
|
||||
To: d.to, CSeq: &sip.CSeq{
|
||||
ID: uint32(d.sn),
|
||||
Method: Method,
|
||||
}, CallID: utils.RandNumString(10),
|
||||
Addr: d.Addr,
|
||||
}
|
||||
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
|
||||
}
|
||||
10
go.mod
10
go.mod
@@ -1,10 +1,12 @@
|
||||
module github.com/Monibuca/plugin-gb28181
|
||||
module github.com/Monibuca/plugin-gb28181/v3
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine/v2 v2.2.2
|
||||
github.com/Monibuca/plugin-rtp v1.0.0
|
||||
github.com/Monibuca/engine/v3 v3.0.0-beta8
|
||||
github.com/Monibuca/utils/v3 v3.0.0-beta1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d
|
||||
github.com/mask-pp/rtp-ps v1.0.0
|
||||
github.com/pion/rtp v1.6.5
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||
)
|
||||
|
||||
60
go.sum
60
go.sum
@@ -1,53 +1,39 @@
|
||||
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.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/plugin-rtp v1.0.0 h1:yksNsIIGxoKX8UZirkAUK+mGZ/XoEeS2vqbIqtqXyCg=
|
||||
github.com/Monibuca/plugin-rtp v1.0.0/go.mod h1:0xkNm23a/BjVnEMz1zXyOqfEjoVmGe3PJqPNF1KyFGc=
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/Monibuca/engine/v3 v3.0.0-beta8 h1:bJ3VHKAd8eJO7qOrSQp8Byve2xfCN7/fBDhz4Nz+AM8=
|
||||
github.com/Monibuca/engine/v3 v3.0.0-beta8/go.mod h1:ckcxVangFrP8uC7UlhuLJabN4A4NMLYCKEmYHud1Tbk=
|
||||
github.com/Monibuca/utils/v3 v3.0.0-beta1 h1:M+miUm9+ojr6AahACOvVaFs+jc5jHmcUi38Dpe1QGgQ=
|
||||
github.com/Monibuca/utils/v3 v3.0.0-beta1/go.mod h1:mQYP/OMox1tkWP6Qut7pBfARr1TXSRkK662dexQl6kI=
|
||||
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/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/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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/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/pion/rtp v1.5.4 h1:PuNg6xqV3brIUihatcKZj1YDUs+M45L0ZbrZWYtkDxY=
|
||||
github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
|
||||
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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/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=
|
||||
github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY=
|
||||
github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
306
main.go
306
main.go
@@ -1,36 +1,78 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
"github.com/Monibuca/engine/v2/util"
|
||||
"github.com/Monibuca/plugin-gb28181/transaction"
|
||||
rtp "github.com/Monibuca/plugin-rtp"
|
||||
. "github.com/logrusorgru/aurora"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
. "github.com/logrusorgru/aurora"
|
||||
"github.com/pion/rtp"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
var Devices sync.Map
|
||||
|
||||
func FindChannel(deviceId string, channelId string) (c *Channel) {
|
||||
if v, ok := Devices.Load(deviceId); ok {
|
||||
d := v.(*Device)
|
||||
d.channelMutex.RLock()
|
||||
c = d.channelMap[channelId]
|
||||
d.channelMutex.RUnlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Publishers struct {
|
||||
data map[uint32]*Publisher
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var publishers Publishers
|
||||
|
||||
func (p *Publishers) Add(key uint32, pp *Publisher) {
|
||||
p.Lock()
|
||||
p.data[key] = pp
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Remove(key uint32) {
|
||||
p.Lock()
|
||||
delete(p.data, key)
|
||||
p.Unlock()
|
||||
}
|
||||
func (p *Publishers) Get(key uint32) *Publisher {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
return p.data[key]
|
||||
}
|
||||
|
||||
var config = struct {
|
||||
Serial string
|
||||
Realm string
|
||||
ListenAddr string
|
||||
Expires int
|
||||
AutoInvite bool
|
||||
}{"34020000002000000001", "3402000000", "127.0.0.1:5060", 3600, true}
|
||||
Serial string
|
||||
Realm string
|
||||
ListenAddr string
|
||||
Expires int
|
||||
MediaPort uint16
|
||||
AutoInvite bool
|
||||
AutoUnPublish bool
|
||||
Debug bool
|
||||
CatalogInterval int
|
||||
}{"34020000002000000001", "3402000000", "127.0.0.1:5060", 3600, 58200, false, true, false, 30}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
engine.InstallPlugin(&engine.PluginConfig{
|
||||
Name: "GB28181",
|
||||
Config: &config,
|
||||
Type: PLUGIN_PUBLISHER,
|
||||
Run: run,
|
||||
})
|
||||
publishers.data = make(map[uint32]*Publisher)
|
||||
}
|
||||
|
||||
func run() {
|
||||
@@ -51,21 +93,37 @@ func run() {
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
MediaPortMin: 58200,
|
||||
MediaPortMax: 58300,
|
||||
MediaIdleTimeout: 30,
|
||||
Debug: config.Debug,
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
MediaIdleTimeout: 30,
|
||||
CatalogInterval: config.CatalogInterval,
|
||||
}
|
||||
s := transaction.NewCore(config)
|
||||
s.OnInvite = onPublish
|
||||
http.HandleFunc("/gb28181/list", func(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
|
||||
http.HandleFunc("/api/gb28181/query/records", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := r.URL.Query().Get("startTime")
|
||||
endTime := r.URL.Query().Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.QueryRecord(startTime, endTime))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/list", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
sse := 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)
|
||||
@@ -76,90 +134,164 @@ func run() {
|
||||
}
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/gb28181/control", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
http.HandleFunc("/api/gb28181/control", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel ,err:= strconv.Atoi(r.URL.Query().Get("channel"))
|
||||
if err!=nil{
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
channel := r.URL.Query().Get("channel")
|
||||
ptzcmd := r.URL.Query().Get("ptzcmd")
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Control(channel,ptzcmd))
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Control(ptzcmd))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/gb28181/invite", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
http.HandleFunc("/api/gb28181/invite", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
query := r.URL.Query()
|
||||
id := query.Get("id")
|
||||
channel := r.URL.Query().Get("channel")
|
||||
startTime := query.Get("startTime")
|
||||
endTime := query.Get("endTime")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
if startTime == "" && c.LiveSP != "" {
|
||||
w.WriteHeader(304) //直播流已存在
|
||||
} else {
|
||||
w.WriteHeader(c.Invite(startTime, endTime))
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/gb28181/bye", func(w http.ResponseWriter, r *http.Request) {
|
||||
CORS(w, r)
|
||||
id := r.URL.Query().Get("id")
|
||||
channel ,err:= strconv.Atoi(r.URL.Query().Get("channel"))
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Invite(channel))
|
||||
channel := r.URL.Query().Get("channel")
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Bye())
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/gb28181/bye", 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)
|
||||
s := transaction.NewCore(config)
|
||||
s.OnRegister = func(msg *sip.Message) {
|
||||
id := msg.From.Uri.UserInfo()
|
||||
d := &Device{
|
||||
ID: id,
|
||||
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,
|
||||
channelMap: make(map[string]*Channel),
|
||||
}
|
||||
if v, ok := s.Devices.Load(id); ok {
|
||||
w.WriteHeader(v.(*transaction.Device).Bye(channel))
|
||||
if old, ok := Devices.Load(id); !ok {
|
||||
go d.Query()
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
oldD := old.(*Device)
|
||||
d.RegisterTime = oldD.RegisterTime
|
||||
d.channelMap = oldD.channelMap
|
||||
d.Status = oldD.Status
|
||||
}
|
||||
})
|
||||
Devices.Store(id, d)
|
||||
}
|
||||
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":
|
||||
if d.Channels == nil {
|
||||
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 {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//})
|
||||
go listenMedia()
|
||||
go queryCatalog(config)
|
||||
s.Start()
|
||||
}
|
||||
|
||||
func onPublish(channel *transaction.Channel) (port int) {
|
||||
rtpPublisher := new(rtp.RTP_PS)
|
||||
if !rtpPublisher.Publish("gb28181/" + channel.DeviceID) {
|
||||
return
|
||||
}
|
||||
rtpPublisher.Type = "GB28181"
|
||||
addr, err := net.ResolveUDPAddr("udp", ":0")
|
||||
func listenMedia() {
|
||||
networkBuffer := 1048576
|
||||
var rtpPacket rtp.Packet
|
||||
addr, err := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(int(config.MediaPort)))
|
||||
if err != nil {
|
||||
return
|
||||
log.Fatalf("udp server ResolveUDPAddr MediaPort:%d error, %v", config.MediaPort, err)
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
log.Fatalf("udp server ListenUDP MediaPort:%d error, %v", config.MediaPort, err)
|
||||
}
|
||||
networkBuffer := 1048576
|
||||
if err := conn.SetReadBuffer(networkBuffer); err != nil {
|
||||
if err = conn.SetReadBuffer(networkBuffer); err != nil {
|
||||
Printf("udp server video conn set read buffer error, %v", err)
|
||||
}
|
||||
if err := conn.SetWriteBuffer(networkBuffer); err != nil {
|
||||
if err = conn.SetWriteBuffer(networkBuffer); err != nil {
|
||||
Printf("udp server video conn set write buffer error, %v", err)
|
||||
}
|
||||
la := conn.LocalAddr().String()
|
||||
strPort := la[strings.LastIndex(la, ":")+1:]
|
||||
if port, err = strconv.Atoi(strPort); err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
bufUDP := make([]byte, 1048576)
|
||||
Printf("udp server start listen video port[%d]", port)
|
||||
defer Printf("udp server stop listen video port[%d]", port)
|
||||
for rtpPublisher.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])
|
||||
} else {
|
||||
Println("udp server read video pack error", err)
|
||||
rtpPublisher.Close()
|
||||
}
|
||||
bufUDP := make([]byte, 1048576)
|
||||
Printf("udp server start listen video port[%d]", config.MediaPort)
|
||||
defer Printf("udp server stop listen video port[%d]", config.MediaPort)
|
||||
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
|
||||
ps := bufUDP[:n]
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
Println(err)
|
||||
}
|
||||
}()
|
||||
return
|
||||
if publisher := publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
publisher.PushPS(rtpPacket.Payload, rtpPacket.Timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func queryCatalog(config *transaction.Config) {
|
||||
t := time.NewTicker(time.Duration(config.CatalogInterval) * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
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 {
|
||||
go device.Query()
|
||||
}
|
||||
return true
|
||||
})
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
publisher.go
Normal file
78
publisher.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/engine/v3"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
. "github.com/Monibuca/utils/v3"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
*engine.Stream
|
||||
psPacket []byte
|
||||
parser utils.DecPSPackage
|
||||
pushVideo func(engine.VideoPack)
|
||||
pushAudio func(engine.AudioPack)
|
||||
}
|
||||
|
||||
func (p *Publisher) Publish() (result bool) {
|
||||
if result = p.Stream.Publish(); result {
|
||||
p.pushVideo = func(pack engine.VideoPack) {
|
||||
var vt *engine.VideoTrack
|
||||
switch p.parser.VideoStreamType {
|
||||
case utils.StreamTypeH264:
|
||||
vt = p.Stream.NewVideoTrack(7)
|
||||
case utils.StreamTypeH265:
|
||||
vt = p.Stream.NewVideoTrack(12)
|
||||
default:
|
||||
return
|
||||
}
|
||||
vt.PushAnnexB(pack)
|
||||
p.pushVideo = vt.PushAnnexB
|
||||
}
|
||||
p.pushAudio = func(pack engine.AudioPack) {
|
||||
switch p.parser.AudioStreamType {
|
||||
case utils.G711A:
|
||||
at := p.Stream.NewAudioTrack(7)
|
||||
at.SoundRate = 8000
|
||||
at.SoundSize = 16
|
||||
at.Channels = 1
|
||||
at.ExtraData = []byte{(at.CodecID << 4) | (1 << 1)}
|
||||
at.PushRaw(pack)
|
||||
p.pushAudio = at.PushRaw
|
||||
// case utils.G711U:
|
||||
// at := p.Stream.NewAudioTrack(8)
|
||||
// at.SoundRate = 8000
|
||||
// at.SoundSize = 16
|
||||
// asc := at.CodecID << 4
|
||||
// asc = asc + 1<<1
|
||||
// at.ExtraData = []byte{asc}
|
||||
// at.PushRaw(pack)
|
||||
// p.pushAudio = at.PushRaw
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (p *Publisher) PushPS(ps []byte, ts uint32) {
|
||||
if len(ps) >= 4 && BigEndian.Uint32(ps) == utils.StartCodePS {
|
||||
if p.psPacket != nil {
|
||||
if err := p.parser.Read(p.psPacket); err == nil {
|
||||
if p.parser.DTS != 0 {
|
||||
ts = p.parser.DTS
|
||||
p.pushVideo(engine.VideoPack{Timestamp: ts / 90, CompositionTime: (p.parser.PTS/90 - p.parser.DTS/90), Payload: p.parser.VideoPayload})
|
||||
} else {
|
||||
p.pushVideo(engine.VideoPack{Timestamp: ts / 90, Payload: p.parser.VideoPayload})
|
||||
}
|
||||
if p.parser.AudioPayload != nil {
|
||||
p.pushAudio(engine.AudioPack{Timestamp: ts / 8, Raw: p.parser.AudioPayload})
|
||||
}
|
||||
} else {
|
||||
Print(err)
|
||||
}
|
||||
p.psPacket = nil
|
||||
}
|
||||
p.psPacket = append(p.psPacket, ps...)
|
||||
} else if p.psPacket != nil {
|
||||
p.psPacket = append(p.psPacket, ps...)
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
//Content-Type: Application/MANSCDP+xml
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
|
||||
@@ -57,4 +57,6 @@ type Config struct {
|
||||
MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
|
||||
AudioEnable bool //是否开启音频
|
||||
WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
Debug bool //是否打印调试信息
|
||||
CatalogInterval int //目录查询间隔
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/transport"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
"golang.org/x/net/html/charset"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transport"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
//Core: transactions manager
|
||||
@@ -23,12 +19,12 @@ type Core struct {
|
||||
ctx context.Context //上下文
|
||||
handlers map[State]map[Event]Handler //每个状态都可以处理有限个事件。不必加锁。
|
||||
transactions map[string]*Transaction //管理所有 transactions,key:tid,value:transaction
|
||||
mutex sync.Mutex //transactions的锁
|
||||
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,13 +243,16 @@ 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)
|
||||
|
||||
//匹配事物
|
||||
c.mutex.RLock()
|
||||
ta, ok := c.transactions[e.tid]
|
||||
c.mutex.RUnlock()
|
||||
if !ok {
|
||||
//新的请求
|
||||
ta = c.initTransaction(c.ctx, e)
|
||||
@@ -265,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 {
|
||||
@@ -282,9 +277,18 @@ func (c *Core) SendMessage(msg *sip.Message) *Response {
|
||||
|
||||
//把event推到transaction
|
||||
ta.event <- e
|
||||
|
||||
//等待事件结束,并返回
|
||||
return <-ta.response
|
||||
<-ta.done
|
||||
if ta.lastResponse != nil {
|
||||
return &Response{
|
||||
Code: ta.lastResponse.GetStatusCode(),
|
||||
Data: ta.lastResponse,
|
||||
Message: ta.lastResponse.GetReason(),
|
||||
}
|
||||
} else {
|
||||
return &Response{
|
||||
Code: 504,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//接收到的消息处理
|
||||
@@ -293,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 {
|
||||
@@ -321,8 +325,9 @@ func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
}
|
||||
//TODO:CANCEL、BYE 和 ACK 需要特殊处理,使用事物或者直接由TU层处理
|
||||
//查找transaction
|
||||
c.mutex.RLock()
|
||||
ta, ok := c.transactions[e.tid]
|
||||
|
||||
c.mutex.RUnlock()
|
||||
method := msg.GetMethod()
|
||||
if msg.IsRequest() {
|
||||
switch method {
|
||||
@@ -333,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))
|
||||
@@ -372,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
|
||||
@@ -386,13 +365,7 @@ func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
|
||||
}
|
||||
} else if ok {
|
||||
ta.event <- e
|
||||
if msg.GetStatusCode() >= 200 {
|
||||
ta.response <- &Response{
|
||||
Code: msg.GetStatusCode(),
|
||||
Data: msg,
|
||||
Message: msg.GetReason(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//TODO:TU层处理:根据需要,创建,或者匹配 Dialog
|
||||
//通过tag匹配到call和dialog
|
||||
@@ -423,8 +396,8 @@ func (c *Core) Send(msg *sip.Message) error {
|
||||
|
||||
addr = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
|
||||
fmt.Println("dest addr:", addr)
|
||||
|
||||
// fmt.Println("dest addr:", addr)
|
||||
var err1, err2 error
|
||||
pkt := &transport.Packet{}
|
||||
pkt.Data, err1 = sip.Encode(msg)
|
||||
@@ -445,18 +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,
|
||||
host: msg.Via.Host,
|
||||
port: msg.Via.Port,
|
||||
}
|
||||
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
|
||||
host string
|
||||
port string
|
||||
}
|
||||
|
||||
func (c *Core) RemoveDead() {
|
||||
c.Devices.Range(func(k, v interface{}) bool {
|
||||
device := v.(*Device)
|
||||
if device.UpdateTime.Sub(device.RegisterTime) > time.Second*36 {
|
||||
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.host + ":" + d.port,
|
||||
}
|
||||
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 {
|
||||
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")
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"fmt"
|
||||
// "fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -58,9 +59,9 @@ func ict_snd_invite(t *Transaction, e *EventObj) error {
|
||||
//发送出去之后,开启 timer
|
||||
if msg.IsReliable() {
|
||||
//stop timer E in reliable transport
|
||||
fmt.Println("Reliabel")
|
||||
//fmt.Println("Reliabel")
|
||||
} else {
|
||||
fmt.Println("Not Reliable")
|
||||
//fmt.Println("Not Reliable")
|
||||
//发送定时器,每次加倍,没有上限?
|
||||
t.timerA = NewSipTimer(T1, 0, func() {
|
||||
t.event <- &EventObj{
|
||||
@@ -105,14 +106,12 @@ func ict_rcv_1xx(t *Transaction, e *EventObj) error {
|
||||
}
|
||||
func ict_rcv_2xx(t *Transaction, e *EventObj) error {
|
||||
t.lastResponse = e.msg
|
||||
|
||||
t.Terminate()
|
||||
|
||||
return nil
|
||||
}
|
||||
func ict_rcv_3456xx(t *Transaction, e *EventObj) error {
|
||||
t.lastResponse = e.msg
|
||||
|
||||
if t.state != ICT_COMPLETED {
|
||||
/* not a retransmission */
|
||||
/* automatic handling of ack! */
|
||||
@@ -136,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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
// "fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ import (
|
||||
*/
|
||||
func nict_snd_request(t *Transaction, e *EventObj) error {
|
||||
msg := e.msg
|
||||
fmt.Println("nict request:", msg.GetMethod())
|
||||
//fmt.Println("nict request:", msg.GetMethod())
|
||||
|
||||
t.origRequest = msg
|
||||
t.state = NICT_TRYING
|
||||
@@ -67,9 +67,9 @@ func nict_snd_request(t *Transaction, e *EventObj) error {
|
||||
//发送出去之后,开启 timer
|
||||
if msg.IsReliable() {
|
||||
//stop timer E in reliable transport
|
||||
fmt.Println("Reliabel")
|
||||
//fmt.Println("Reliabel")
|
||||
} else {
|
||||
fmt.Println("Not Reliable")
|
||||
//fmt.Println("Not Reliable")
|
||||
//发送定时器
|
||||
t.timerE = NewSipTimer(T1, T2, func() {
|
||||
t.event <- &EventObj{
|
||||
|
||||
@@ -2,11 +2,12 @@ package transaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/transport"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transport"
|
||||
)
|
||||
|
||||
//状态机之状态
|
||||
@@ -337,31 +338,30 @@ func (ta *Transaction) Run() {
|
||||
select {
|
||||
case e := <-ta.event:
|
||||
//根据event调用对应的handler
|
||||
fmt.Println("fsm run event:", e.evt.String())
|
||||
//fmt.Println("fsm run event:", e.evt.String())
|
||||
core := ta.core
|
||||
state := ta.state
|
||||
evtHandlers, ok1 := core.handlers[state]
|
||||
if !ok1 {
|
||||
fmt.Println("invalid state:", ta.state.String())
|
||||
//fmt.Println("invalid state:", ta.state.String())
|
||||
break
|
||||
}
|
||||
f, ok2 := evtHandlers[e.evt]
|
||||
if !ok2 {
|
||||
fmt.Println("invalid handler for this event:", e.evt.String())
|
||||
//fmt.Println("invalid handler for this event:", e.evt.String())
|
||||
break
|
||||
}
|
||||
fmt.Printf("state:%s, event:%s\n", state.String(), e.evt.String())
|
||||
//fmt.Printf("state:%s, event:%s\n", state.String(), e.evt.String())
|
||||
err := f(ta, e)
|
||||
if err != nil {
|
||||
fmt.Printf("transaction run failed, state:%s, event:%s\n", state.String(), e.evt.String())
|
||||
//fmt.Printf("transaction run failed, state:%s, event:%s\n", state.String(), e.evt.String())
|
||||
}
|
||||
|
||||
case <-ta.done:
|
||||
fmt.Println("fsm exit")
|
||||
//fmt.Println("fsm exit")
|
||||
return
|
||||
|
||||
case <-ta.ctx.Done():
|
||||
fmt.Println("fsm killed")
|
||||
//fmt.Println("fsm killed")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -399,8 +399,8 @@ func (ta *Transaction) SipSend(msg *sip.Message) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := msg.Addr
|
||||
if addr==""{
|
||||
addr := msg.Addr
|
||||
if addr == "" {
|
||||
viaParams := msg.Via.Params
|
||||
//host
|
||||
var host, port string
|
||||
@@ -424,7 +424,7 @@ func (ta *Transaction) SipSend(msg *sip.Message) error {
|
||||
addr = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
|
||||
fmt.Println("dest addr:", addr)
|
||||
//fmt.Println("dest addr:", addr)
|
||||
|
||||
var err1, err2 error
|
||||
pkt := &transport.Packet{}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package tu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/transaction"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
)
|
||||
|
||||
//sip server和client的配置,可以得到sip URI:sip
|
||||
@@ -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")
|
||||
|
||||
@@ -2,8 +2,8 @@ package tu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Monibuca/plugin-gb28181/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/utils"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/sip"
|
||||
"github.com/Monibuca/plugin-gb28181/v3/utils"
|
||||
)
|
||||
|
||||
//根据参数构建各种消息
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package tu
|
||||
|
||||
import (
|
||||
"github.com/Monibuca/plugin-gb28181/transaction"
|
||||
"sync"
|
||||
|
||||
"github.com/Monibuca/plugin-gb28181/v3/transaction"
|
||||
)
|
||||
|
||||
//TODO:参考http服务,使用者仅需要根据需要实现某些handler,替换某些header fileds or body信息。其他的处理都由库来实现。
|
||||
@@ -15,7 +16,7 @@ type Server struct {
|
||||
//提供config参数
|
||||
func NewServer(config *transaction.Config) *Server {
|
||||
return &Server{
|
||||
Core: transaction.NewCore(config),
|
||||
Core: transaction.NewCore(config),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
ui/dist/demo.html
vendored
19
ui/dist/demo.html
vendored
@@ -1,19 +0,0 @@
|
||||
<meta charset="utf-8">
|
||||
<title>plugin-gb28181 demo</title>
|
||||
<script src="https://unpkg.com/vue"></script>
|
||||
<script src="./plugin-gb28181.umd.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="./plugin-gb28181.css">
|
||||
|
||||
|
||||
<div id="app">
|
||||
<demo></demo>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
components: {
|
||||
demo: plugin-gb28181
|
||||
}
|
||||
}).$mount('#app')
|
||||
</script>
|
||||
2947
ui/dist/plugin-gb28181.common.js
vendored
2947
ui/dist/plugin-gb28181.common.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/plugin-gb28181.common.js.map
vendored
1
ui/dist/plugin-gb28181.common.js.map
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/plugin-gb28181.css
vendored
1
ui/dist/plugin-gb28181.css
vendored
@@ -1 +0,0 @@
|
||||
.arrow1[data-v-3ceca3db]{grid-column:2;grid-row:1}.arrow2[data-v-3ceca3db]{transform:rotate(90deg);grid-column:3;grid-row:2}.arrow3[data-v-3ceca3db]{transform:rotate(180deg);grid-column:2;grid-row:3}.arrow4[data-v-3ceca3db]{transform:rotate(270deg);grid-column:1;grid-row:2}.arrow5[data-v-3ceca3db]{transform:rotate(-45deg);grid-column:1;grid-row:1}.arrow6[data-v-3ceca3db]{transform:rotate(45deg);grid-column:3;grid-row:1}.arrow7[data-v-3ceca3db]{transform:rotate(-135deg);grid-column:1;grid-row:3}.arrow8[data-v-3ceca3db]{transform:rotate(135deg);grid-column:3;grid-row:3}.arrow9[data-v-3ceca3db]{grid-column:2;grid-row:2}.container[data-v-3ceca3db]{position:relative;height:350px}.control[data-v-3ceca3db]{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-3ceca3db]{top:210px}.control3[data-v-3ceca3db]{top:260px}.control4[data-v-3ceca3db]{top:310px}.control5[data-v-3ceca3db]{top:360px}.control>[data-v-3ceca3db]{cursor:pointer;fill:grey;width:50px;height:50px}.control5>[data-v-3ceca3db]{margin-right:10px}.control2>[data-v-3ceca3db],.control3>[data-v-3ceca3db],.control4>[data-v-3ceca3db]{width:40px;height:40px}.control>[data-v-3ceca3db]:hover,.cycling[data-v-3ceca3db]{fill:#0ff}
|
||||
2957
ui/dist/plugin-gb28181.umd.js
vendored
2957
ui/dist/plugin-gb28181.umd.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/plugin-gb28181.umd.js.map
vendored
1
ui/dist/plugin-gb28181.umd.js.map
vendored
File diff suppressed because one or more lines are too long
11
ui/dist/plugin-gb28181.umd.min.js
vendored
11
ui/dist/plugin-gb28181.umd.min.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/plugin-gb28181.umd.min.js.map
vendored
1
ui/dist/plugin-gb28181.umd.min.js.map
vendored
File diff suppressed because one or more lines are too long
9373
ui/package-lock.json
generated
9373
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "dashboard of gb28181 plugin for monibuca",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "vue-cli-service build --target lib --name plugin-gb28181"
|
||||
},
|
||||
"author": "dexter",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^4.5.4",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
}
|
||||
}
|
||||
131
ui/src/App.vue
131
ui/src/App.vue
@@ -1,131 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<mu-data-table :data="Devices" :columns="columns">
|
||||
<template #expand="prop">
|
||||
<mu-data-table :data="prop.row.Channels" :columns="columns2">
|
||||
<template #default="{ row: item, $index }">
|
||||
<td>{{ item.DeviceID }}</td>
|
||||
<td>{{ item.Name }}</td>
|
||||
<td>{{ item.Manufacturer }}</td>
|
||||
<td>{{ item.Address }}</td>
|
||||
<td>{{ item.Status }}</td>
|
||||
<td>
|
||||
<mu-button flat v-if="item.Connected" @click="ptz(prop.row.ID, $index,item)">云台</mu-button>
|
||||
<mu-button flat v-if="item.Connected" @click="bye(prop.row.ID, $index)">断开</mu-button>
|
||||
<mu-button v-else flat @click="invite(prop.row.ID, $index,item)"
|
||||
>连接
|
||||
</mu-button
|
||||
>
|
||||
</td>
|
||||
</template>
|
||||
</mu-data-table>
|
||||
</template>
|
||||
<template #default="{ row: item }">
|
||||
<td>{{ item.ID }}</td>
|
||||
<td>{{ item.Channels ? item.Channels.length : 0 }}</td>
|
||||
<td>
|
||||
<StartTime :value="item.RegisterTime"></StartTime>
|
||||
</td>
|
||||
<td>
|
||||
<StartTime :value="item.UpdateTime"></StartTime>
|
||||
</td>
|
||||
<td>{{ item.Status }}</td>
|
||||
</template>
|
||||
</mu-data-table>
|
||||
<webrtc-player ref="player" @ptz="sendPtz" v-model="previewStreamPath" :PublicIP="PublicIP"></webrtc-player>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import WebrtcPlayer from "./components/Player"
|
||||
import {getPTZCmd,PTZ_TYPE} from "./utils/ptz-cmd";
|
||||
|
||||
export default {
|
||||
components:{
|
||||
WebrtcPlayer
|
||||
},
|
||||
props:{
|
||||
ListenAddr:String
|
||||
},
|
||||
computed:{
|
||||
PublicIP(){
|
||||
return this.ListenAddr.split(":")[0]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Devices: [], previewStreamPath:false,
|
||||
context:{
|
||||
id:null,
|
||||
channel:0,
|
||||
item:null
|
||||
},
|
||||
columns: Object.freeze(
|
||||
["设备号", "通道数", "注册时间", "更新时间", "状态"].map(
|
||||
(title) => ({
|
||||
title,
|
||||
})
|
||||
)
|
||||
),
|
||||
columns2: Object.freeze([
|
||||
"通道编号",
|
||||
"名称",
|
||||
"厂商",
|
||||
"地址",
|
||||
"状态",
|
||||
"操作",
|
||||
]).map((title) => ({title})),
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchlist();
|
||||
},
|
||||
methods: {
|
||||
fetchlist() {
|
||||
const listES = new EventSource(this.apiHost + "/gb28181/list");
|
||||
listES.onmessage = (evt) => {
|
||||
if (!evt.data) return;
|
||||
this.Devices = JSON.parse(evt.data) || [];
|
||||
this.Devices.sort((a, b) => (a.ID > b.ID ? 1 : -1));
|
||||
};
|
||||
this.$once("hook:destroyed", () => listES.close());
|
||||
},
|
||||
ptz(id, channel,item) {
|
||||
this.context = {
|
||||
id,channel,item
|
||||
};
|
||||
this.previewStreamPath = true
|
||||
this.$nextTick(() =>this.$refs.player.play("gb28181/"+item.DeviceID));
|
||||
},
|
||||
sendPtz(options){
|
||||
const ptzCmd = getPTZCmd(options);
|
||||
const ptzCmdStop = getPTZCmd({type:PTZ_TYPE.stop});
|
||||
this.ajax.get("/gb28181/control", {
|
||||
id:this.context.id,
|
||||
channel:this.context.channel,
|
||||
ptzcmd: ptzCmd,
|
||||
}).then(x=>{
|
||||
if(options.type === PTZ_TYPE.stop || options.cycle === true){
|
||||
return;
|
||||
}
|
||||
setTimeout(()=>{
|
||||
this.ajax.get("/gb28181/control", {
|
||||
id:this.context.id,
|
||||
channel:this.context.channel,
|
||||
ptzcmd: ptzCmdStop,
|
||||
});
|
||||
},500)
|
||||
});
|
||||
},
|
||||
invite(id, channel,item) {
|
||||
this.ajax.get("/gb28181/invite", {id, channel}).then(x=>{
|
||||
item.Connected = true
|
||||
});
|
||||
},
|
||||
bye(id, channel,item) {
|
||||
this.ajax.get("/gb28181/bye", {id, channel}).then(x=>{
|
||||
item.Connected = false
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,245 +0,0 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-bind="$attrs"
|
||||
draggable
|
||||
width="750"
|
||||
v-on="$listeners"
|
||||
:title="streamPath"
|
||||
@on-ok="onClosePreview"
|
||||
@on-cancel="onClosePreview"
|
||||
>
|
||||
<div class="container">
|
||||
<video ref="webrtc" :srcObject.prop="stream" width="488" height="275" autoplay muted controls></video>
|
||||
<div class="control">
|
||||
<svg v-for="n in 8" @click="ptzCmdDirection(n)" :class="'arrow'+n" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M682.666667 955.733333H341.333333a17.066667 17.066667 0 0 1-17.066666-17.066666V529.066667H85.333333a17.066667 17.066667 0 0 1-12.066133-29.1328l426.666667-426.666667a17.0496 17.0496 0 0 1 24.132266 0l426.666667 426.666667A17.066667 17.066667 0 0 1 938.666667 529.066667H699.733333v409.6a17.066667 17.066667 0 0 1-17.066666 17.066666z m-324.266667-34.133333h307.2V512a17.066667 17.066667 0 0 1 17.066667-17.066667h214.801066L512 109.4656 126.532267 494.933333H341.333333a17.066667 17.066667 0 0 1 17.066667 17.066667v409.6z" p-id="6849"></path></svg>
|
||||
<svg @click="ptzCmdCycle" class="arrow9" :class="{'cycling':isCycling}" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 960c-210.96 0-395.36-149.68-438.47-355.91-2.98-14.24 6.16-28.21 20.4-31.19 14.22-2.93 28.21 6.15 31.18 20.41C163.15 775.25 325.86 907.29 512 907.29s348.85-132.05 386.89-313.98c2.99-14.26 16.97-23.35 31.19-20.41 14.24 2.99 23.38 16.95 20.41 31.19C907.36 810.32 722.95 960 512 960zM927.48 466.94c-12.61 0-23.75-9.07-25.95-21.91C869.06 254.78 705.24 116.71 512 116.71c-193.23 0-357.05 138.07-389.52 328.32-2.45 14.35-16.08 24.01-30.41 21.54-14.35-2.46-23.99-16.07-21.55-30.42C107.33 220.51 293 64 512 64c219.01 0 404.68 156.51 441.48 372.15 2.44 14.35-7.21 27.97-21.54 30.42-1.5 0.25-3 0.37-4.46 0.37z" /><path d="M96.52 466.94c-9.11 0-17.97-4.72-22.85-13.18-7.28-12.61-2.96-28.72 9.64-36l131.76-76.07c12.6-7.26 28.73-2.96 36 9.65 7.28 12.61 2.96 28.72-9.64 36l-131.76 76.07a26.18 26.18 0 0 1-13.15 3.53zM792.95 701.14c-9.11 0-17.96-4.72-22.85-13.18-7.28-12.6-2.96-28.72 9.64-36l131.76-76.09c12.58-7.28 28.72-2.95 36 9.65 7.27 12.6 2.96 28.72-9.65 36l-131.75 76.1a26.271 26.271 0 0 1-13.15 3.52z" /></svg>
|
||||
</div>
|
||||
<div class="control control2">
|
||||
<svg @click="ptzCmd(ptzType.zoomFar)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M994.990643 859.352971L713.884166 578.246494A381.208198 381.208198 0 0 0 767.307984 383.653992C767.307984 171.765089 595.542895 0 383.653992 0S0 171.765089 0 383.653992s171.765089 383.653992 383.653992 383.653992c71.119859 0 137.507985-19.694238 194.592502-53.423818l281.106477 281.090491a95.913498 95.913498 0 1 0 135.637672-135.621686zM383.653992 671.394486c-158.912681 0-287.740494-128.827813-287.740494-287.740494S224.741311 95.913498 383.653992 95.913498s287.740494 128.827813 287.740494 287.740494-128.827813 287.740494-287.740494 287.740494z m159.85583-335.697243h-111.899081v-111.899081a47.956749 47.956749 0 1 0-95.913498 0v111.899081h-111.899081a47.956749 47.956749 0 1 0 0 95.913498h111.899081v111.899081a47.956749 47.956749 0 1 0 95.913498 0v-111.899081h111.899081a47.956749 47.956749 0 1 0 0-95.913498z" /></svg>
|
||||
<svg @click="ptzCmd(ptzType.zoomNear)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M994.990643 859.352971L713.884166 578.246494A381.208198 381.208198 0 0 0 767.307984 383.653992C767.307984 171.765089 595.542895 0 383.653992 0S0 171.765089 0 383.653992s171.765089 383.653992 383.653992 383.653992c71.119859 0 137.507985-19.694238 194.592502-53.423818l281.106477 281.090491a95.913498 95.913498 0 1 0 135.637672-135.621686zM383.653992 671.394486c-158.912681 0-287.740494-128.827813-287.740494-287.740494S224.741311 95.913498 383.653992 95.913498s287.740494 128.827813 287.740494 287.740494-128.827813 287.740494-287.740494 287.740494z m159.85583-335.697243H223.798162a47.956749 47.956749 0 1 0 0 95.913498h319.71166a47.956749 47.956749 0 1 0 0-95.913498z" /></svg>
|
||||
</div>
|
||||
<div class="control control3">
|
||||
<svg @click="ptzCmd(ptzType.apertureFar)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M956.39 400.827C922.164 266.675 828.186 155.703 701.502 99.874l94.522 443.782L956.39 400.827zM206.208 189.167C106.183 286.191 56.845 424.181 72.696 562.659l351.347-309.096-217.835-64.396zM643.118 78.847a446.363 446.363 0 0 0-138.947-16.775 448.047 448.047 0 0 0-250.583 86.934l437.868 146.949-48.338-217.108zM83.786 623.979c34.443 133.772 128.248 244.407 254.583 300.291l-95.915-426.55L83.786 623.979zM969.893 496.089a372.746 372.746 0 0 0-2.37-34.138l-329.972 303.78 196.157 69.256c91.522-88.456 141.056-211.704 136.185-338.898zM396.862 945.166a447.857 447.857 0 0 0 139.077 16.766 447.784 447.784 0 0 0 250.322-86.718L349.286 733.05l47.576 212.116z" /><path fill="#333333" d="M397.253 471.171h245.668c22.593 0 40.923 18.32 40.923 40.913 0 22.592-18.33 40.922-40.923 40.922H397.253c-22.592 0-40.922-18.33-40.922-40.922 0-22.593 18.33-40.913 40.922-40.913z" /><path fill="#333333" d="M479.17 634.879V389.21c0-22.593 18.32-40.923 40.913-40.923s40.923 18.33 40.923 40.923v245.668c0 22.592-18.33 40.922-40.923 40.922s-40.913-18.329-40.913-40.921z" /></svg>
|
||||
<svg @click="ptzCmd(ptzType.apertureNear)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M956.39 400.827C922.164 266.675 828.186 155.703 701.502 99.874l94.522 443.782L956.39 400.827z m-750.182-211.66C106.183 286.191 56.845 424.181 72.696 562.659l351.347-309.096-217.835-64.396z m436.91-110.32a446.363 446.363 0 0 0-138.947-16.775 448.047 448.047 0 0 0-250.583 86.934l437.868 146.949-48.338-217.108zM83.786 623.979c34.443 133.772 128.248 244.407 254.583 300.291l-95.915-426.55L83.786 623.979z m886.107-127.89a372.746 372.746 0 0 0-2.37-34.138l-329.972 303.78 196.157 69.256c91.522-88.456 141.056-211.704 136.185-338.898zM396.862 945.166a447.857 447.857 0 0 0 139.077 16.766 447.784 447.784 0 0 0 250.322-86.718L349.286 733.05l47.576 212.116z m0.391-474.039h245.668c22.593 0 40.923 18.32 40.923 40.912 0 22.593-18.33 40.923-40.923 40.923H397.253c-22.592 0-40.922-18.33-40.922-40.923 0-22.592 18.33-40.912 40.922-40.912z" /></svg>
|
||||
</div>
|
||||
<div class="control control4">
|
||||
<svg @click="ptzCmd(ptzType.focusFar)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M849.07153297 646.81872559c9.30432153 0 17.26391602 3.30249 23.82934617 9.88769507 6.60992408 6.59509253 9.88769508 14.52502465 9.88769508 23.79473901v101.14617896c0 27.90801978-9.87780761 51.70275879-29.61364722 71.47814965-19.75067115 19.77539086-43.56518578 29.66308594-71.48803711 29.66308594h-101.1165166c-9.32409644 0-17.25402856-3.29754663-23.83428954-9.9865725-6.59509253-6.49127173-9.90252662-14.52502465-9.90252662-23.7947383 0-9.26971435 3.30743408-17.20458984 9.90252662-23.79473901 6.58026099-6.59014916 14.51019311-9.88769508 23.83428954-9.88769507h101.1165166c9.29937744 0 17.26391602-3.29754663 23.82440137-9.88769579 6.61486816-6.59014916 9.88769508-14.52008057 9.88769579-23.89361573v-101.04235815c0-9.36859107 3.28765845-17.30346656 9.89758254-23.78979493 6.57531762-6.69396997 14.52502465-9.99151587 23.83923363-9.99151587l-0.06427025 0.09887671zM242.38726782 141.3103025h101.10168506c9.30432153 0 17.2688601 3.29754663 23.819458 9.88769578 6.62475562 6.59509253 9.89758325 14.52502465 9.89758254 23.7947383 0 9.37353516-3.27282691 17.30346656-9.89758254 23.79473901-6.5505979 6.69396997-14.51513648 9.9865725-23.81451463 9.9865725h-101.10168433c-9.31915307 0-17.2688601 3.19372583-23.82934547 9.88769508-6.62475562 6.49127173-9.91241479 14.52502465-9.91241479 23.794739v101.04235816c0 9.36859107-3.28271508 17.30346656-9.89758324 23.89361573-6.57531762 6.59014916-14.51513648 9.88769508-23.81451392 9.88769507-9.31420898 0-17.25402856-3.29754663-23.82934547-9.88769507C144.49908423 360.80230689 141.21142578 352.86743141 141.21142578 343.49884033V242.45648217c0-27.91296386 9.86792016-51.70275879 29.62353539-71.47814965 19.75067115-19.77539086 43.57507324-29.66308594 71.48803711-29.66308594h0.06426954zM174.9877932 646.81872559c9.30432153 0 17.24414039 3.30249 23.81451393 9.88769507 6.62475562 6.59509253 9.90252662 14.52502465 9.90252662 23.79473901v101.14617896c0 9.26971435 3.27282691 17.19964576 9.89758324 23.78979492 6.57531762 6.59014916 14.51513648 9.88769508 23.81451393 9.88769579h101.12640404c9.29937744 0 17.25402856 3.29754663 23.82934547 9.88769507 6.60992408 6.59014916 9.88769508 14.52502465 9.88769579 23.89361572 0 9.26971435-3.27777099 17.20458984-9.88769579 23.79473901-6.57531762 6.59014916-14.52996803 9.88769508-23.82934547 9.88769508H242.41693092c-27.91296386 0-51.71264625-9.88769508-71.47814895-29.66308594-19.75561523-19.67651344-29.62353539-43.57012915-29.62353539-71.47814965v-101.04235816c0-9.26971435 3.27282691-17.30346656 9.88769507-23.89361573 6.58026099-6.59509253 14.52502465-9.88769508 23.81451464-9.88769507h-0.02966309zM680.57037329 141.3103025h101.1165166c27.92285133 0 51.73736596 9.88769508 71.48803711 29.56420922 19.73583961 19.77539086 29.61364722 43.57012915 29.61364722 71.47814965v101.14617896c0 9.26971435-3.27777099 17.30346656-9.88769508 23.78979493-6.56542945 6.69396997-14.52502465 9.88769508-23.82934617 9.88769506-9.29937744 0-17.26391602-3.19372583-23.82440139-9.88769506-6.61486816-6.48632836-9.88769508-14.52008057-9.88769579-23.78979493V242.35266137c0-9.26971435-3.28765845-17.19964576-9.90252661-23.78979492-6.57037354-6.59509253-14.52008057-9.88769508-23.83428955-9.88769579h-101.10168433c-9.31420898 0-17.2688601-3.29754663-23.82934618-9.88769507-6.60992408-6.59509253-9.89758325-14.52502465-9.89758254-23.79473902 0-9.37353516 3.28765845-17.30346656 9.89758254-23.89361571 6.56048608-6.59014916 14.51513648-9.88769508 23.82934618-9.88769508l0.04943799 0.09887672z" /></svg>
|
||||
<svg @click="ptzCmd(ptzType.focusNear)" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 170.666667A341.333333 341.333333 0 1 1 170.666667 512 341.333333 341.333333 0 0 1 512 170.666667m0-42.666667a384 384 0 1 0 384 384A384 384 0 0 0 512 128z" /><path fill="#333333" d="M298.666667 533.333333H170.666667a21.333333 21.333333 0 0 1 0-42.666666h128a21.333333 21.333333 0 0 1 0 42.666666zM853.333333 533.333333h-128a21.333333 21.333333 0 0 1 0-42.666666h128a21.333333 21.333333 0 0 1 0 42.666666zM512 320a21.333333 21.333333 0 0 1-21.333333-21.333333V170.666667a21.333333 21.333333 0 0 1 42.666666 0v128a21.333333 21.333333 0 0 1-21.333333 21.333333zM512 874.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-128a21.333333 21.333333 0 0 1 42.666666 0v128a21.333333 21.333333 0 0 1-21.333333 21.333334z" /></svg>
|
||||
</div>
|
||||
<div class="control5">
|
||||
<i-select v-model="ptzPositionIndex" style="width: 100px">
|
||||
<Option v-for="n in 10" :value="n" :key="n">预置点{{n}}</Option>
|
||||
</i-select>
|
||||
<i-button type="primary" @click="ptzCmd(ptzType.setPos)">设置</i-button>
|
||||
<i-button type="success" @click="ptzCmd(ptzType.calPos)">调用</i-button>
|
||||
<i-button type="error" @click="ptzCmd(ptzType.delPos)">删除</i-button>
|
||||
速度:
|
||||
<InputNumber :max="10" :min="1" v-model="ptzSpeed"></InputNumber>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<mu-badge v-if="remoteSDP">
|
||||
<a slot="content" :href="remoteSDPURL" download="remoteSDP.txt">remoteSDP</a>
|
||||
</mu-badge>
|
||||
<mu-badge v-if="localSDP">
|
||||
<a slot="content" :href="localSDPURL" download="localSDP.txt">localSDP</a>
|
||||
</mu-badge>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
import {PTZ_TYPE} from "../utils/ptz-cmd";
|
||||
const PTZ_DIRECTION_ARRAY = [PTZ_TYPE.up,PTZ_TYPE.right,PTZ_TYPE.down,PTZ_TYPE.left,PTZ_TYPE.leftUp,PTZ_TYPE.rightUp,PTZ_TYPE.leftDown,PTZ_TYPE.rightDown];
|
||||
let pc = null;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
iceConnectionState: pc && pc.iceConnectionState,
|
||||
stream: null,
|
||||
localSDP: "",
|
||||
remoteSDP: "",
|
||||
remoteSDPURL: "",
|
||||
localSDPURL: "",
|
||||
streamPath: "",
|
||||
ptzSpeed:5,
|
||||
ptzPositionIndex:1,
|
||||
ptzType:PTZ_TYPE,
|
||||
isCycling:false
|
||||
};
|
||||
},
|
||||
props:{
|
||||
PublicIP:String
|
||||
},
|
||||
methods: {
|
||||
async play(streamPath) {
|
||||
pc = new RTCPeerConnection();
|
||||
pc.addTransceiver('video',{
|
||||
direction:'recvonly'
|
||||
})
|
||||
this.streamPath = streamPath;
|
||||
pc.onsignalingstatechange = e => {
|
||||
//console.log(e);
|
||||
};
|
||||
pc.oniceconnectionstatechange = e => {
|
||||
this.$toast.info(pc.iceConnectionState);
|
||||
this.iceConnectionState = pc.iceConnectionState;
|
||||
};
|
||||
pc.onicecandidate = event => {
|
||||
console.log(event)
|
||||
};
|
||||
pc.ontrack = event => {
|
||||
// console.log(event);
|
||||
if (event.track.kind == "video")
|
||||
this.stream = event.streams[0];
|
||||
};
|
||||
await pc.setLocalDescription(await pc.createOffer());
|
||||
this.localSDP = pc.localDescription.sdp;
|
||||
this.localSDPURL = URL.createObjectURL(
|
||||
new Blob([this.localSDP], { type: "text/plain" })
|
||||
);
|
||||
const result = await this.ajax({
|
||||
type: "POST",
|
||||
processData: false,
|
||||
data: JSON.stringify(pc.localDescription.toJSON()),
|
||||
url: "/webrtc/play?streamPath=" + this.streamPath,
|
||||
dataType: "json"
|
||||
});
|
||||
if (result.errmsg) {
|
||||
this.$toast.error(result.errmsg);
|
||||
return;
|
||||
} else {
|
||||
this.remoteSDP = result.sdp;
|
||||
this.remoteSDPURL = URL.createObjectURL(new Blob([this.remoteSDP], { type: "text/plain" }));
|
||||
}
|
||||
await pc.setRemoteDescription(new RTCSessionDescription(result));
|
||||
},
|
||||
ptzCmdDirection(direction){
|
||||
const type = PTZ_DIRECTION_ARRAY[direction-1];
|
||||
if(this.isCycling){
|
||||
this.ptzCmdCycle();
|
||||
}
|
||||
this.ptzCmd(type);
|
||||
},
|
||||
ptzCmdCycle(){
|
||||
if(this.isCycling){
|
||||
this.isCycling = false;
|
||||
this.ptzCmd(PTZ_TYPE.stop);
|
||||
}
|
||||
else {
|
||||
this.isCycling = true;
|
||||
this.ptzCmd(PTZ_TYPE.right);
|
||||
}
|
||||
},
|
||||
ptzCmd(type){
|
||||
this.$emit('ptz',{type:type,speed:this.ptzSpeed,index:this.ptzPositionIndex,cycle:this.isCycling})
|
||||
},
|
||||
onClosePreview() {
|
||||
pc.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.arrow1{
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
.arrow2{
|
||||
transform: rotate(90deg);
|
||||
grid-column: 3;
|
||||
grid-row: 2;
|
||||
}
|
||||
.arrow3{
|
||||
transform: rotate(180deg);
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
}
|
||||
.arrow4{
|
||||
transform: rotate(270deg);
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.arrow5{
|
||||
transform: rotate(-45deg);
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.arrow6{
|
||||
transform: rotate(45deg);
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.arrow7{
|
||||
transform: rotate(-135deg);
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.arrow8{
|
||||
transform: rotate(135deg);
|
||||
grid-column: 3;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.arrow9{
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
height: 350px;
|
||||
}
|
||||
.control {
|
||||
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{
|
||||
top: 210px;
|
||||
}
|
||||
|
||||
.control3{
|
||||
top: 260px;
|
||||
}
|
||||
|
||||
.control4{
|
||||
top: 310px;
|
||||
}
|
||||
|
||||
.control5{
|
||||
top: 360px;
|
||||
}
|
||||
|
||||
.control >* {
|
||||
cursor: pointer;
|
||||
fill: gray;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.control5 >*{
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.control2 >*,.control3 >*,.control4 >*{
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.control >*:hover,.cycling{
|
||||
fill: cyan
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* Date:2020/11/9
|
||||
* Desc:
|
||||
*/
|
||||
/**
|
||||
* Date:2020/11/2
|
||||
* Desc: ptz cmd 封装
|
||||
* cmd[0] //首字节以05H开头
|
||||
* cmd[1] //组合码,高4位为版本信息v1.0,版本信息0H,低四位为校验码
|
||||
* // 校验码 = (cmd[0]的高4位+cmd[0]的低4位+cmd[1]的高4位)%16
|
||||
* cmd[2] //地址的低8位???什么地址,地址范围000h ~ FFFh(0~4095),其中000h为广播地址
|
||||
* cmd[3] //指令码
|
||||
* cmd[4] //数据1,水平控制速度、聚焦速度
|
||||
* cmd[5] //数据2,垂直控制速度、光圈速度
|
||||
* cmd[6] // 高4位为数据3=变倍控制速度,低4位为地址高4位
|
||||
*/
|
||||
|
||||
const PTZ_TYPE = {
|
||||
stop: 'stop',
|
||||
right: 'right',
|
||||
left: 'left',
|
||||
up: 'up',
|
||||
down: 'down',
|
||||
leftUp: 'leftUp',
|
||||
leftDown: 'leftDown',
|
||||
rightUp: 'rightUp',
|
||||
rightDown: 'rightDown',
|
||||
zoomFar: 'zoomFar',
|
||||
zoomNear: 'zoomNear',
|
||||
apertureFar: 'apertureFar',
|
||||
apertureNear: 'apertureNear',
|
||||
focusFar: 'focusFar',
|
||||
focusNear: 'focusNear',
|
||||
setPos: 'setPos',
|
||||
calPos: 'calPos',
|
||||
delPos: 'delPos'
|
||||
};
|
||||
|
||||
const PTZ_CMD_TYPE = {
|
||||
stop: 0x00,
|
||||
|
||||
right: 0x01,
|
||||
left: 0x02,
|
||||
up: 0x08,
|
||||
down: 0x04,
|
||||
|
||||
leftUp: 0x0A,
|
||||
leftDown: 0x06,
|
||||
rightUp: 0x09,
|
||||
rightDown: 0x05,
|
||||
|
||||
zoomFar: 0x10, // 镜头 放大
|
||||
zoomNear: 0x20, // 镜头 缩小
|
||||
|
||||
apertureFar: 0x48, // 光圈 缩小
|
||||
apertureNear: 0x44, // 光圈 放大
|
||||
|
||||
focusFar: 0x42, // 聚焦 近
|
||||
focusNear: 0x41, // 聚焦 远
|
||||
|
||||
setPos: 0x81,
|
||||
calPos: 0x82,
|
||||
delPos: 0x83
|
||||
};
|
||||
|
||||
const SPEED_ARRAY = [0x19, 0x32, 0x4b, 0x64, 0x7d, 0x96, 0xAF, 0xC8, 0xE1, 0xFA];
|
||||
const POSITION_ARRAY = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10];
|
||||
|
||||
// 获取 direction 方向型
|
||||
/**
|
||||
*
|
||||
* @param options
|
||||
* type:
|
||||
* speed:default 5
|
||||
* index:
|
||||
* @returns {string}
|
||||
*/
|
||||
function getPTZCmd(options) {
|
||||
const {type, speed, index} = options;
|
||||
const ptzSpeed = getPTZSpeed(speed);
|
||||
let indexValue3, indexValue4, indexValue5, indexValue6;
|
||||
indexValue3 = PTZ_CMD_TYPE[type];
|
||||
switch (type) {
|
||||
case PTZ_TYPE.up:
|
||||
case PTZ_TYPE.down:
|
||||
case PTZ_TYPE.apertureFar:
|
||||
case PTZ_TYPE.apertureNear:
|
||||
indexValue5 = ptzSpeed;
|
||||
break;
|
||||
case PTZ_TYPE.right:
|
||||
case PTZ_TYPE.left:
|
||||
case PTZ_TYPE.focusFar:
|
||||
case PTZ_TYPE.focusNear:
|
||||
indexValue4 = ptzSpeed;
|
||||
break;
|
||||
case PTZ_TYPE.leftUp:
|
||||
case PTZ_TYPE.leftDown:
|
||||
case PTZ_TYPE.rightUp:
|
||||
case PTZ_TYPE.rightDown:
|
||||
indexValue4 = ptzSpeed;
|
||||
indexValue5 = ptzSpeed;
|
||||
break;
|
||||
case PTZ_TYPE.zoomFar:
|
||||
case PTZ_TYPE.zoomNear:
|
||||
indexValue6 = 0x10;
|
||||
break;
|
||||
case PTZ_TYPE.calPos:
|
||||
case PTZ_TYPE.delPos:
|
||||
case PTZ_TYPE.setPos:
|
||||
indexValue5 = getPTZPositionIndex(index);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ptzCmdToString(indexValue3, indexValue4, indexValue5, indexValue6);
|
||||
}
|
||||
|
||||
function getPTZSpeed(speed) {
|
||||
speed = speed || 5;
|
||||
const speedIndex = speed - 1;
|
||||
const ptzSpeed = SPEED_ARRAY[speedIndex] || SPEED_ARRAY[4];
|
||||
return ptzSpeed;
|
||||
}
|
||||
|
||||
function getPTZPositionIndex(index) {
|
||||
return POSITION_ARRAY[index - 1];
|
||||
}
|
||||
|
||||
function ptzCmdToString(indexValue3, indexValue4, indexValue5, indexValue6) {
|
||||
//
|
||||
let cmd = Buffer.alloc(8);
|
||||
// 首字节以05H开头
|
||||
cmd[0] = 0xA5;
|
||||
// 组合码,高4位为版本信息v1.0,版本信息0H,低四位为校验码
|
||||
cmd[1] = 0x0F;
|
||||
// 校验码 = (cmd[0]的高4位+cmd[0]的低4位+cmd[1]的高4位)%16
|
||||
cmd[2] = 0x01;
|
||||
//
|
||||
if (indexValue3) {
|
||||
cmd[3] = indexValue3;
|
||||
}
|
||||
if (indexValue4) {
|
||||
cmd[4] = indexValue4;
|
||||
}
|
||||
if (indexValue5) {
|
||||
cmd[5] = indexValue5;
|
||||
}
|
||||
if (indexValue6) {
|
||||
cmd[6] = indexValue6;
|
||||
}
|
||||
|
||||
cmd[7] = (cmd[0] + cmd[1] + cmd[2] + cmd[3] + cmd[4] + cmd[5] + cmd[6]) % 256;
|
||||
|
||||
return bytes2HexString(cmd);
|
||||
}
|
||||
|
||||
function bytes2HexString(byte) {
|
||||
let hexs = "";
|
||||
for (let i = 0; i < byte.length; i++) {
|
||||
let hex = (byte[i]).toString(16);
|
||||
if (hex.length === 1) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
hexs += hex.toUpperCase();
|
||||
}
|
||||
return hexs;
|
||||
}
|
||||
|
||||
export {
|
||||
getPTZCmd,
|
||||
PTZ_TYPE
|
||||
}
|
||||
333
utils/ps.go
Normal file
333
utils/ps.go
Normal file
@@ -0,0 +1,333 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mask-pp/rtp-ps/buffer"
|
||||
)
|
||||
|
||||
//
|
||||
const (
|
||||
UDPTransfer int = 0
|
||||
TCPTransferActive int = 1
|
||||
TCPTransferPassive int = 2
|
||||
LocalCache int = 3
|
||||
|
||||
StreamTypeH264 = 0x1b
|
||||
StreamTypeH265 = 0x24
|
||||
G711A = 0x90 //PCMA
|
||||
G7221AUDIOTYPE = 0x92
|
||||
G7231AUDIOTYPE = 0x93
|
||||
G729AUDIOTYPE = 0x99
|
||||
|
||||
StreamIDVideo = 0xe0
|
||||
StreamIDAudio = 0xc0
|
||||
|
||||
StartCodePS = 0x000001ba
|
||||
StartCodeSYS = 0x000001bb
|
||||
StartCodeMAP = 0x000001bc
|
||||
StartCodeVideo = 0x000001e0
|
||||
StartCodeAudio = 0x000001c0
|
||||
HaiKangCode = 0x000001bd
|
||||
MEPGProgramEndCode = 0x000001b9
|
||||
|
||||
RTPHeaderLength int = 12
|
||||
PSHeaderLength int = 14
|
||||
SystemHeaderLength int = 18
|
||||
MAPHeaderLength int = 24
|
||||
PESHeaderLength int = 19
|
||||
RtpLoadLength int = 1460
|
||||
PESLoadLength int = 0xFFFF
|
||||
MAXFrameLen int = 1024 * 1024 * 2
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFoundStartCode = errors.New("not found the need start code flag")
|
||||
ErrMarkerBit = errors.New("marker bit value error")
|
||||
ErrFormatPack = errors.New("not package standard")
|
||||
ErrParsePakcet = errors.New("parse ps packet error")
|
||||
)
|
||||
|
||||
/*
|
||||
This implement from VLC source code
|
||||
notes: https://github.com/videolan/vlc/blob/master/modules/mux/mpeg/bits.h
|
||||
*/
|
||||
|
||||
//bitsBuffer bits buffer
|
||||
type bitsBuffer struct {
|
||||
iSize int
|
||||
iData int
|
||||
iMask uint8
|
||||
pData []byte
|
||||
}
|
||||
|
||||
func bitsInit(isize int, buffer []byte) *bitsBuffer {
|
||||
|
||||
bits := &bitsBuffer{
|
||||
iSize: isize,
|
||||
iData: 0,
|
||||
iMask: 0x80,
|
||||
pData: buffer,
|
||||
}
|
||||
if bits.pData == nil {
|
||||
bits.pData = make([]byte, isize)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func bitsAlign(bits *bitsBuffer) {
|
||||
|
||||
if bits.iMask != 0x80 && bits.iData < bits.iSize {
|
||||
bits.iMask = 0x80
|
||||
bits.iData++
|
||||
bits.pData[bits.iData] = 0x00
|
||||
}
|
||||
}
|
||||
func bitsWrite(bits *bitsBuffer, count int, src uint64) *bitsBuffer {
|
||||
|
||||
for count > 0 {
|
||||
count--
|
||||
if ((src >> uint(count)) & 0x01) != 0 {
|
||||
bits.pData[bits.iData] |= bits.iMask
|
||||
} else {
|
||||
bits.pData[bits.iData] &= ^bits.iMask
|
||||
}
|
||||
bits.iMask >>= 1
|
||||
if bits.iMask == 0 {
|
||||
bits.iData++
|
||||
bits.iMask = 0x80
|
||||
}
|
||||
}
|
||||
|
||||
return bits
|
||||
}
|
||||
|
||||
/*
|
||||
https://github.com/videolan/vlc/blob/master/modules/demux/mpeg
|
||||
*/
|
||||
type DecPSPackage struct {
|
||||
systemClockReferenceBase uint64
|
||||
systemClockReferenceExtension uint64
|
||||
programMuxRate uint32
|
||||
|
||||
VideoStreamType uint32
|
||||
AudioStreamType uint32
|
||||
buffer.RawBuffer
|
||||
VideoPayload []byte
|
||||
AudioPayload []byte
|
||||
PTS uint32
|
||||
DTS uint32
|
||||
}
|
||||
|
||||
// data包含 接受到完整一帧数据后,所有的payload, 解析出去后是一阵完整的raw数据
|
||||
func (dec *DecPSPackage) Read(data []byte) error {
|
||||
return dec.decPackHeader(append(data, 0x00, 0x00, 0x01, 0xb9))
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) clean() {
|
||||
dec.systemClockReferenceBase = 0
|
||||
dec.systemClockReferenceExtension = 0
|
||||
dec.programMuxRate = 0
|
||||
dec.VideoPayload = nil
|
||||
dec.AudioPayload = nil
|
||||
dec.PTS = 0
|
||||
dec.DTS = 0
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) decPackHeader(data []byte) error {
|
||||
dec.clean()
|
||||
|
||||
// 加载数据
|
||||
dec.LoadBuffer(data)
|
||||
|
||||
if startcode, err := dec.Uint32(); err != nil {
|
||||
return err
|
||||
} else if startcode != StartCodePS {
|
||||
return ErrNotFoundStartCode
|
||||
}
|
||||
|
||||
if err := dec.Skip(9); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
psl, err := dec.Uint8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
psl &= 0x07
|
||||
if err = dec.Skip(int(psl)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
nextStartCode, err := dec.Uint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch nextStartCode {
|
||||
case StartCodeSYS:
|
||||
if err := dec.decSystemHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
case StartCodeMAP:
|
||||
if err := dec.decProgramStreamMap(); err != nil {
|
||||
return err
|
||||
}
|
||||
case StartCodeVideo, StartCodeAudio:
|
||||
if err := dec.decPESPacket(nextStartCode); err != nil {
|
||||
return err
|
||||
}
|
||||
case HaiKangCode, MEPGProgramEndCode:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) decSystemHeader() error {
|
||||
syslens, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// drop rate video audio bound and lock flag
|
||||
syslens -= 6
|
||||
if err = dec.Skip(6); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ONE WAY: do not to parse the stream and skip the buffer
|
||||
//br.Skip(syslen * 8)
|
||||
|
||||
// TWO WAY: parse every stream info
|
||||
for syslens > 0 {
|
||||
if nextbits, err := dec.Uint8(); err != nil {
|
||||
return err
|
||||
} else if (nextbits&0x80)>>7 != 1 {
|
||||
break
|
||||
}
|
||||
if err = dec.Skip(2); err != nil {
|
||||
return err
|
||||
}
|
||||
syslens -= 3
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) decProgramStreamMap() error {
|
||||
psm, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//drop psm version infor
|
||||
if err = dec.Skip(2); err != nil {
|
||||
return err
|
||||
}
|
||||
psm -= 2
|
||||
|
||||
programStreamInfoLen, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = dec.Skip(int(programStreamInfoLen)); err != nil {
|
||||
return err
|
||||
}
|
||||
psm -= programStreamInfoLen + 2
|
||||
|
||||
programStreamMapLen, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
psm -= 2 + programStreamMapLen
|
||||
|
||||
for programStreamMapLen > 0 {
|
||||
streamType, err := dec.Uint8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elementaryStreamID, err := dec.Uint8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if elementaryStreamID >= 0xe0 && elementaryStreamID <= 0xef {
|
||||
dec.VideoStreamType = uint32(streamType)
|
||||
} else if elementaryStreamID >= 0xc0 && elementaryStreamID <= 0xdf {
|
||||
dec.AudioStreamType = uint32(streamType)
|
||||
}
|
||||
|
||||
elementaryStreamInfoLength, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = dec.Skip(int(elementaryStreamInfoLength)); err != nil {
|
||||
return err
|
||||
}
|
||||
programStreamMapLen -= 4 + elementaryStreamInfoLength
|
||||
}
|
||||
|
||||
// crc 32
|
||||
if psm != 4 {
|
||||
return ErrFormatPack
|
||||
}
|
||||
if err = dec.Skip(4); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) decPESPacket(t uint32) error {
|
||||
payloadlen, err := dec.Uint16()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = dec.Skip(1); err != nil {
|
||||
return err
|
||||
}
|
||||
flag, err := dec.Uint8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptsFlag := flag >> 7
|
||||
dtsFlag := (flag & 0b0100_000) >> 6
|
||||
var pts ,dts uint32
|
||||
payloadlen -= 2
|
||||
pesHeaderDataLen, err := dec.Uint8()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payloadlen -= uint16(pesHeaderDataLen) + 1
|
||||
|
||||
if extraData, err := dec.Bytes(int(pesHeaderDataLen)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if ptsFlag == 1 {
|
||||
pts = uint32(extraData[0]&0b0000_1110) << 29
|
||||
pts += uint32(extraData[1]) << 22
|
||||
pts += uint32(extraData[2]&0b1111_1110) << 14
|
||||
pts += uint32(extraData[3]) << 7
|
||||
pts += uint32(extraData[4]) >> 1
|
||||
if dtsFlag == 1 {
|
||||
dts = uint32(extraData[5]&0b0000_1110) << 29
|
||||
dts += uint32(extraData[6]) << 22
|
||||
dts += uint32(extraData[7]&0b1111_1110) << 14
|
||||
dts += uint32(extraData[8]) << 7
|
||||
dts += uint32(extraData[9]) >> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if payload, err := dec.Bytes(int(payloadlen)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if StartCodeVideo == t {
|
||||
dec.PTS = pts
|
||||
dec.DTS = dts
|
||||
dec.VideoPayload = append(dec.VideoPayload, payload...)
|
||||
} else {
|
||||
dec.AudioPayload = append(dec.AudioPayload, payload...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user