mirror of
https://github.com/Monibuca/plugin-rtsp.git
synced 2025-09-26 19:51:14 +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
|
||||
|
||||
## 配置
|
||||
```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() {
|
||||
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)
|
||||
|
||||
}
|
||||
|
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
58
main.go
58
main.go
@@ -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
|
||||
|
Reference in New Issue
Block a user