mirror of
https://github.com/Monibuca/plugin-rtsp.git
synced 2025-10-05 15:47:00 +08:00
增加UI界面
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.vscode
|
||||||
|
node_modules
|
14
README.md
14
README.md
@@ -7,6 +7,18 @@
|
|||||||
RTSP
|
RTSP
|
||||||
|
|
||||||
## 配置
|
## 配置
|
||||||
|
```toml
|
||||||
|
[RTSP]
|
||||||
|
BufferLength = 2048
|
||||||
|
AutoPull = false
|
||||||
|
RemoteAddr = "rtsp://localhost/${streamPath}"
|
||||||
|
```
|
||||||
|
- BufferLength是指解析拉取的rtp包的缓冲大小
|
||||||
|
- AutoPull是指当有用户订阅一个新房间的时候自动向远程拉流转发
|
||||||
|
- RemoteAddr 指远程拉流地址,其中${streamPath}是占位符,实际使用流路径替换。
|
||||||
|
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法(拉流转发)
|
||||||
|
```go
|
||||||
|
new(RTSP).Publish("live/user1","rtsp://xxx.xxx.xxx.xxx/live/user1")
|
||||||
|
```
|
@@ -25,7 +25,6 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
spropReg, _ = regexp.Compile("sprop-parameter-sets=(.+);")
|
spropReg, _ = regexp.Compile("sprop-parameter-sets=(.+);")
|
||||||
configReg, _ = regexp.Compile("config=(.+)(;|$)")
|
configReg, _ = regexp.Compile("config=(.+)(;|$)")
|
||||||
log.Println(spropReg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RtspClient struct {
|
type RtspClient struct {
|
||||||
@@ -47,6 +46,7 @@ type RtspClient struct {
|
|||||||
videoh int
|
videoh int
|
||||||
SPS []byte
|
SPS []byte
|
||||||
PPS []byte
|
PPS []byte
|
||||||
|
Header string
|
||||||
AudioSpecificConfig []byte
|
AudioSpecificConfig []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +107,7 @@ func (this *RtspClient) Client(rtsp_url string) (bool, string) {
|
|||||||
} else if !strings.Contains(message, "200") {
|
} else if !strings.Contains(message, "200") {
|
||||||
return false, "error DESCRIBE not status code 200 OK " + message
|
return false, "error DESCRIBE not status code 200 OK " + message
|
||||||
} else {
|
} else {
|
||||||
|
this.Header = message
|
||||||
this.track = this.ParseMedia(message)
|
this.track = this.ParseMedia(message)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
17
dashboard/components/StartTime.vue
Normal file
17
dashboard/components/StartTime.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<Poptip trigger="hover" :content="'⌚️'+ new Date(value).toLocaleString()">
|
||||||
|
<Time :time="new Date(value)"></Time>
|
||||||
|
</Poptip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "StartTime",
|
||||||
|
props: {
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
150
dashboard/index.vue
Normal file
150
dashboard/index.vue
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Button @click="addPull" type="success">拉流转发</Button>
|
||||||
|
<Spin fix v-if="Rooms==null">
|
||||||
|
<Icon type="ios-loading" size="18" class="demo-spin-icon-load"></Icon>
|
||||||
|
<div>Loading</div>
|
||||||
|
</Spin>
|
||||||
|
<div v-else-if="Rooms.length==0" class="empty">
|
||||||
|
<Icon type="md-wine" size="50" />没有任何房间
|
||||||
|
</div>
|
||||||
|
<div class="layout" v-else>
|
||||||
|
<Card v-for="item in Rooms" :key="item.RoomInfo.StreamPath" class="room">
|
||||||
|
<p slot="title">{{item.RoomInfo.StreamPath}}</p>
|
||||||
|
<StartTime slot="extra" :value="item.RoomInfo.StartTime"></StartTime>
|
||||||
|
<div class="hls-info">
|
||||||
|
<Progress :stroke-width="20" :percent="Math.ceil(item.BufferRate)" text-inside />
|
||||||
|
<div>📜{{item.SyncCount}}</div>
|
||||||
|
</div>
|
||||||
|
<Button @click="showHeader(item)">
|
||||||
|
<Icon type="ios-code-working" />
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let listES = null;
|
||||||
|
import StartTime from "./components/StartTime";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
StartTime
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentStream: null,
|
||||||
|
Rooms: null,
|
||||||
|
remoteAddr: "",
|
||||||
|
streamPath: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchlist() {
|
||||||
|
listES = new EventSource("/rtsp/list");
|
||||||
|
listES.onmessage = evt => {
|
||||||
|
if (!evt.data) return;
|
||||||
|
this.Rooms = JSON.parse(evt.data) || [];
|
||||||
|
this.Rooms.sort((a, b) =>
|
||||||
|
a.RoomInfo.StreamPath > b.RoomInfo.StreamPath ? 1 : -1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
showHeader(item) {
|
||||||
|
this.$Modal.info({
|
||||||
|
title: "RTSP Header",
|
||||||
|
width: "1000px",
|
||||||
|
scrollable: true,
|
||||||
|
content: item.Header
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addPull() {
|
||||||
|
this.$Modal.confirm({
|
||||||
|
title: "拉流转发",
|
||||||
|
onOk() {
|
||||||
|
window.ajax
|
||||||
|
.getJSON("/rtsp/pull", {
|
||||||
|
target: this.remoteAddr,
|
||||||
|
streamPath: this.streamPath
|
||||||
|
})
|
||||||
|
.then(x => {
|
||||||
|
if (x.code == 0) {
|
||||||
|
this.$Message.success({
|
||||||
|
title: "提示",
|
||||||
|
content: "已启动拉流"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$Message.error({
|
||||||
|
title: "提示",
|
||||||
|
content: x.msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: h => {
|
||||||
|
return h("div", {}, [
|
||||||
|
h("Input", {
|
||||||
|
props: {
|
||||||
|
value: this.remoteAddr,
|
||||||
|
autofocus: true,
|
||||||
|
placeholder: "Please enter URL of rtsp..."
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
input: val => {
|
||||||
|
this.remoteAddr = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h("Input", {
|
||||||
|
props: {
|
||||||
|
value: this.streamPath,
|
||||||
|
placeholder:
|
||||||
|
"Please enter streamPath to publish."
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
input: val => {
|
||||||
|
this.streamPath = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchlist();
|
||||||
|
},
|
||||||
|
deactivated() {
|
||||||
|
listES.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url("/iview.css");
|
||||||
|
.empty {
|
||||||
|
color: #eb5e46;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 500px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
padding-bottom: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.ts-info {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hls-info {
|
||||||
|
width: 350px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
9560
dashboard/package-lock.json
generated
Normal file
9560
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
dashboard/package.json
Normal file
15
dashboard/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "dashboard",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "dashboard of rtsp plugin for monibuca",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vue-cli-service build --dest ui --target wc --name plugin-rtsp index.vue"
|
||||||
|
},
|
||||||
|
"author": "dexter",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-service": "^4.2.3",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
}
|
||||||
|
}
|
8
dashboard/ui/demo.html
Normal file
8
dashboard/ui/demo.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<meta charset="utf-8">
|
||||||
|
<title>plugin-rtsp demo</title>
|
||||||
|
<script src="https://unpkg.com/vue"></script>
|
||||||
|
<script src="./plugin-rtsp.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<plugin-rtsp></plugin-rtsp>
|
||||||
|
|
1017
dashboard/ui/plugin-rtsp.js
Normal file
1017
dashboard/ui/plugin-rtsp.js
Normal file
File diff suppressed because it is too large
Load Diff
1
dashboard/ui/plugin-rtsp.js.map
Normal file
1
dashboard/ui/plugin-rtsp.js.map
Normal file
File diff suppressed because one or more lines are too long
2
dashboard/ui/plugin-rtsp.min.js
vendored
Normal file
2
dashboard/ui/plugin-rtsp.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/ui/plugin-rtsp.min.js.map
Normal file
1
dashboard/ui/plugin-rtsp.min.js.map
Normal file
File diff suppressed because one or more lines are too long
42
main.go
42
main.go
@@ -2,14 +2,19 @@ package rtspplugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/Monibuca/engine"
|
. "github.com/Monibuca/engine"
|
||||||
. "github.com/Monibuca/engine/avformat"
|
. "github.com/Monibuca/engine/avformat"
|
||||||
"github.com/Monibuca/engine/util"
|
"github.com/Monibuca/engine/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var collection = sync.Map{}
|
||||||
var config = struct {
|
var config = struct {
|
||||||
BufferLength int
|
BufferLength int
|
||||||
AutoPull bool
|
AutoPull bool
|
||||||
@@ -19,9 +24,14 @@ var config = struct {
|
|||||||
func init() {
|
func init() {
|
||||||
InstallPlugin(&PluginConfig{
|
InstallPlugin(&PluginConfig{
|
||||||
Name: "RTSP",
|
Name: "RTSP",
|
||||||
|
Type: PLUGIN_PUBLISHER | PLUGIN_HOOK,
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
Config: &config,
|
Config: &config,
|
||||||
Run: func() {
|
UI: util.CurrentDir("dashboard", "ui", "plugin-rtsp.min.js"),
|
||||||
|
Run: runPlugin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func runPlugin() {
|
||||||
if config.AutoPull {
|
if config.AutoPull {
|
||||||
OnSubscribeHooks.AddHook(func(s *OutputStream) {
|
OnSubscribeHooks.AddHook(func(s *OutputStream) {
|
||||||
if s.Publisher == nil {
|
if s.Publisher == nil {
|
||||||
@@ -29,7 +39,31 @@ func init() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
http.HandleFunc("/rtsp/list", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sse := util.NewSSE(w, r.Context())
|
||||||
|
var err error
|
||||||
|
for tick := time.NewTicker(time.Second); err == nil; <-tick.C {
|
||||||
|
var info []*RTSPInfo
|
||||||
|
collection.Range(func(key, value interface{}) bool {
|
||||||
|
rtsp := value.(*RTSP)
|
||||||
|
pinfo := &rtsp.RTSPInfo
|
||||||
|
pinfo.BufferRate = len(rtsp.OutGoing) * 100 / config.BufferLength
|
||||||
|
info = append(info, pinfo)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
err = sse.WriteJSON(info)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
http.HandleFunc("/rtsp/pull", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
targetURL := r.URL.Query().Get("target")
|
||||||
|
streamPath := r.URL.Query().Get("streamPath")
|
||||||
|
var err error
|
||||||
|
if err == nil {
|
||||||
|
new(RTSP).Publish(streamPath, targetURL)
|
||||||
|
w.Write([]byte(`{"code":0}`))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"code":1,"msg":"%s"}`, err.Error())))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +74,8 @@ type RTSP struct {
|
|||||||
}
|
}
|
||||||
type RTSPInfo struct {
|
type RTSPInfo struct {
|
||||||
SyncCount int64
|
SyncCount int64
|
||||||
|
Header *string
|
||||||
|
BufferRate int
|
||||||
RoomInfo *RoomInfo
|
RoomInfo *RoomInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,10 +225,12 @@ func (rtsp *RTSP) Publish(streamPath string, rtspUrl string) (result bool) {
|
|||||||
if result = rtsp.InputStream.Publish(streamPath, rtsp); result {
|
if result = rtsp.InputStream.Publish(streamPath, rtsp); result {
|
||||||
rtsp.RTSPInfo.RoomInfo = &rtsp.Room.RoomInfo
|
rtsp.RTSPInfo.RoomInfo = &rtsp.Room.RoomInfo
|
||||||
rtsp.RtspClient = RtspClientNew(config.BufferLength)
|
rtsp.RtspClient = RtspClientNew(config.BufferLength)
|
||||||
|
rtsp.RTSPInfo.Header = &rtsp.RtspClient.Header
|
||||||
if status, message := rtsp.RtspClient.Client(rtspUrl); !status {
|
if status, message := rtsp.RtspClient.Client(rtspUrl); !status {
|
||||||
log.Println(message)
|
log.Println(message)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
collection.Store(streamPath, rtsp)
|
||||||
go rtsp.run()
|
go rtsp.run()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user