增加UI界面

This commit is contained in:
李宇翔
2020-03-10 15:39:57 +08:00
parent 1e6c07850a
commit eb41f1dbec
13 changed files with 10836 additions and 12 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
node_modules

View File

@@ -7,6 +7,18 @@
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")
```

View File

@@ -25,7 +25,6 @@ var (
func init() {
spropReg, _ = regexp.Compile("sprop-parameter-sets=(.+);")
configReg, _ = regexp.Compile("config=(.+)(;|$)")
log.Println(spropReg)
}
type RtspClient struct {
@@ -47,6 +46,7 @@ type RtspClient struct {
videoh int
SPS []byte
PPS []byte
Header string
AudioSpecificConfig []byte
}
@@ -107,6 +107,7 @@ func (this *RtspClient) Client(rtsp_url string) (bool, string) {
} else if !strings.Contains(message, "200") {
return false, "error DESCRIBE not status code 200 OK " + message
} else {
this.Header = message
this.track = this.ParseMedia(message)
}

View 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
View 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

File diff suppressed because it is too large Load Diff

15
dashboard/package.json Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
dashboard/ui/plugin-rtsp.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

58
main.go
View File

@@ -2,14 +2,19 @@ package rtspplugin
import (
"bytes"
"fmt"
"log"
"net/http"
"strings"
"sync"
"time"
. "github.com/Monibuca/engine"
. "github.com/Monibuca/engine/avformat"
"github.com/Monibuca/engine/util"
)
var collection = sync.Map{}
var config = struct {
BufferLength int
AutoPull bool
@@ -19,17 +24,46 @@ var config = struct {
func init() {
InstallPlugin(&PluginConfig{
Name: "RTSP",
Type: PLUGIN_PUBLISHER | PLUGIN_HOOK,
Version: "1.0.0",
Config: &config,
Run: func() {
if config.AutoPull {
OnSubscribeHooks.AddHook(func(s *OutputStream) {
if s.Publisher == nil {
new(RTSP).Publish(s.StreamPath, strings.Replace(config.RemoteAddr, "${streamPath}", s.StreamPath, -1))
}
})
UI: util.CurrentDir("dashboard", "ui", "plugin-rtsp.min.js"),
Run: runPlugin,
})
}
func runPlugin() {
if config.AutoPull {
OnSubscribeHooks.AddHook(func(s *OutputStream) {
if s.Publisher == nil {
new(RTSP).Publish(s.StreamPath, strings.Replace(config.RemoteAddr, "${streamPath}", s.StreamPath, -1))
}
},
})
}
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())))
}
})
}
@@ -39,8 +73,10 @@ type RTSP struct {
RTSPInfo
}
type RTSPInfo struct {
SyncCount int64
RoomInfo *RoomInfo
SyncCount int64
Header *string
BufferRate int
RoomInfo *RoomInfo
}
func (rtsp *RTSP) run() {
@@ -189,10 +225,12 @@ func (rtsp *RTSP) Publish(streamPath string, rtspUrl string) (result bool) {
if result = rtsp.InputStream.Publish(streamPath, rtsp); result {
rtsp.RTSPInfo.RoomInfo = &rtsp.Room.RoomInfo
rtsp.RtspClient = RtspClientNew(config.BufferLength)
rtsp.RTSPInfo.Header = &rtsp.RtspClient.Header
if status, message := rtsp.RtspClient.Client(rtspUrl); !status {
log.Println(message)
return false
}
collection.Store(streamPath, rtsp)
go rtsp.run()
}
return