mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-29 05:32:30 +08:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
87776b9540 |
1
dashboard/dist/css/app.93f68a59.css
vendored
Normal file
1
dashboard/dist/css/app.93f68a59.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}.log-container{overflow-y:auto;max-height:360px}@-webkit-keyframes recording-data-v-1ed98600{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-1ed98600{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-1ed98600]{-webkit-animation:recording-data-v-1ed98600 1s infinite;animation:recording-data-v-1ed98600 1s infinite}.layout[data-v-1ed98600]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-1ed98600]{width:250px;margin:10px;text-align:left}.empty[data-v-1ed98600]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-1ed98600],.status[data-v-1ed98600]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-1ed98600]{position:fixed;left:5px;bottom:10px}.status>div[data-v-1ed98600]{margin:0 5px}
|
1
dashboard/dist/css/app.b2f5eee5.css
vendored
1
dashboard/dist/css/app.b2f5eee5.css
vendored
@@ -1 +0,0 @@
|
|||||||
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}@-webkit-keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-65ce2c22]{-webkit-animation:recording-data-v-65ce2c22 1s infinite;animation:recording-data-v-65ce2c22 1s infinite}.layout[data-v-65ce2c22]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-65ce2c22]{width:250px;margin:10px;text-align:left}.empty[data-v-65ce2c22]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-65ce2c22],.status[data-v-65ce2c22]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-65ce2c22]{position:fixed;left:5px;bottom:10px}.status>div[data-v-65ce2c22]{margin:0 5px}
|
|
2
dashboard/dist/index.html
vendored
2
dashboard/dist/index.html
vendored
@@ -1 +1 @@
|
|||||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.b2f5eee5.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.2d98a4c2.js rel=preload as=script><link href=/js/chunk-vendors.ae8ac63d.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.b2f5eee5.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ae8ac63d.js></script><script src=/js/app.2d98a4c2.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.93f68a59.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.00b4a97a.js rel=preload as=script><link href=/js/chunk-vendors.ae8ac63d.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.93f68a59.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ae8ac63d.js></script><script src=/js/app.00b4a97a.js></script></body></html>
|
2
dashboard/dist/js/app.00b4a97a.js
vendored
Normal file
2
dashboard/dist/js/app.00b4a97a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.00b4a97a.js.map
vendored
Normal file
1
dashboard/dist/js/app.00b4a97a.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dashboard/dist/js/app.2d98a4c2.js
vendored
2
dashboard/dist/js/app.2d98a4c2.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.2d98a4c2.js.map
vendored
1
dashboard/dist/js/app.2d98a4c2.js.map
vendored
File diff suppressed because one or more lines are too long
31
dashboard/src/components/Config.vue
Normal file
31
dashboard/src/components/Config.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<Modal v-bind="$attrs" draggable v-on="$listeners" @on-visible-change="onVisible" title="配置">
|
||||||
|
<div>
|
||||||
|
<pre>{{config}}</pre>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
config: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onVisible(visible) {
|
||||||
|
if (visible) {
|
||||||
|
window.ajax.get(
|
||||||
|
"//" + location.host + "/api/config",
|
||||||
|
{},
|
||||||
|
x => (this.config = x)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
44
dashboard/src/components/Logs.vue
Normal file
44
dashboard/src/components/Logs.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<Modal v-bind="$attrs" draggable v-on="$listeners" @on-visible-change="onVisible" title="日志跟踪">
|
||||||
|
<div ref="logContainer" class="log-container">
|
||||||
|
<pre><template v-for="item in $store.state.logs">{{item+"\n"}}</template></pre>
|
||||||
|
</div>
|
||||||
|
<div slot="footer">
|
||||||
|
自动滚动
|
||||||
|
<Switch v-model="autoScroll" />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from "vuex";
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
autoScroll: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchLogs", "stopFetchLogs"]),
|
||||||
|
onVisible(visible) {
|
||||||
|
if (visible) {
|
||||||
|
this.fetchLogs();
|
||||||
|
} else {
|
||||||
|
this.stopFetchLogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
if (this.autoScroll) {
|
||||||
|
this.$refs.logContainer.scrollTop = this.$refs.logContainer.offsetHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.log-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 360px;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal v-bind="$attrs" draggable v-on="$listeners" title="录制的视频">
|
<Modal v-bind="$attrs" draggable v-on="$listeners" title="录制的视频" @on-visible-change="onVisible" :z-index="900">
|
||||||
<List>
|
<List>
|
||||||
<ListItem v-for="item in data" :key="item">
|
<ListItem v-for="item in data" :key="item">
|
||||||
<ListItemMeta :title="item.Path">
|
<ListItemMeta :title="item.Path">
|
||||||
@@ -39,7 +39,8 @@ export default {
|
|||||||
{ streamPath: item.Path.replace(".flv", "") },
|
{ streamPath: item.Path.replace(".flv", "") },
|
||||||
x => {
|
x => {
|
||||||
if (x == "success") {
|
if (x == "success") {
|
||||||
this.$Message.success("开始发布");
|
this.onVisible(true)
|
||||||
|
this.$Message.success("删除成功");
|
||||||
} else {
|
} else {
|
||||||
this.$Message.error(x);
|
this.$Message.error(x);
|
||||||
}
|
}
|
||||||
@@ -90,16 +91,18 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return value + "ms";
|
return value + "ms";
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onVisible(visible){
|
||||||
mounted() {
|
if(visible){
|
||||||
window.ajax.getJSON(
|
window.ajax.getJSON(
|
||||||
"//" + location.host + "/api/record/flv/list",
|
"//" + location.host + "/api/record/flv/list",
|
||||||
{},
|
{},
|
||||||
x => {
|
x => {
|
||||||
this.data = x;
|
this.data = x;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -3,40 +3,55 @@ import Vuex from 'vuex'
|
|||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
let summaryES = null
|
let summaryES = null
|
||||||
|
let logsES = null
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
summary:{
|
summary: {
|
||||||
NetWork:[],
|
NetWork: [],
|
||||||
Rooms:[],
|
Rooms: [],
|
||||||
Memory:{
|
Memory: {
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0
|
Usage: 0
|
||||||
},
|
},
|
||||||
CPUUsage:0,
|
CPUUsage: 0,
|
||||||
HardDisk:{
|
HardDisk: {
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0
|
Usage: 0
|
||||||
}
|
}
|
||||||
}
|
}, logs: []
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
update(state,payload){
|
update(state, payload) {
|
||||||
Object.assign(state,payload)
|
Object.assign(state, payload)
|
||||||
|
},
|
||||||
|
addLog(state, payload) {
|
||||||
|
state.logs.push(payload)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchSummary({commit}){
|
fetchSummary({ commit }) {
|
||||||
summaryES = new EventSource(
|
summaryES = new EventSource(
|
||||||
"//" + location.host + "/api/summary"
|
"//" + location.host + "/api/summary"
|
||||||
);
|
);
|
||||||
summaryES.onmessage = evt=>{
|
summaryES.onmessage = evt => {
|
||||||
if (!evt.data) return
|
if (!evt.data) return
|
||||||
let summary = JSON.parse(evt.data)
|
let summary = JSON.parse(evt.data)
|
||||||
commit("update",{summary})
|
commit("update", { summary })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stopFetchSummary(){
|
fetchLogs({ commit }) {
|
||||||
|
logsES = new EventSource(
|
||||||
|
"//" + location.host + "/api/logs"
|
||||||
|
)
|
||||||
|
logsES.onmessage = evt => {
|
||||||
|
if (!evt.data) return
|
||||||
|
commit("addLog", evt.data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopFetchLogs() {
|
||||||
|
logsES.close()
|
||||||
|
},
|
||||||
|
stopFetchSummary() {
|
||||||
summaryES.close()
|
summaryES.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -1,261 +1,273 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<ButtonGroup vertical>
|
<ButtonGroup vertical>
|
||||||
<Button icon="ios-folder" @click="showRecords=true"></Button>
|
<Button icon="ios-folder" @click="showRecords=true"></Button>
|
||||||
<Button icon="md-bug"></Button>
|
<Button icon="md-bug" @click="showLogs=true"></Button>
|
||||||
<Button icon="md-settings"></Button>
|
<Button icon="md-settings" @click="showConfig=true"></Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
|
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
|
||||||
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
|
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
|
||||||
<StartTime slot="extra" :value="item.StartTime"></StartTime>
|
<StartTime slot="extra" :value="item.StartTime"></StartTime>
|
||||||
<p>
|
<p>
|
||||||
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
|
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
|
||||||
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
|
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
|
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
|
||||||
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
|
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
|
||||||
</p>
|
</p>
|
||||||
<ButtonGroup size="small">
|
<ButtonGroup size="small">
|
||||||
<Button
|
<Button
|
||||||
@click="onShowDetail(item)"
|
@click="onShowDetail(item)"
|
||||||
icon="ios-people"
|
icon="ios-people"
|
||||||
>{{item.SubscriberInfo?item.SubscriberInfo.length:0}}</Button>
|
>{{item.SubscriberInfo?item.SubscriberInfo.length:0}}
|
||||||
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
|
</Button>
|
||||||
<Button
|
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
|
||||||
@click="stopRecord(item)"
|
<Button
|
||||||
class="recording"
|
@click="stopRecord(item)"
|
||||||
v-if="isRecording(item)"
|
class="recording"
|
||||||
icon="ios-radio-button-on"
|
v-if="isRecording(item)"
|
||||||
></Button>
|
icon="ios-radio-button-on"
|
||||||
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
|
></Button>
|
||||||
</ButtonGroup>
|
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
|
||||||
</Card>
|
</ButtonGroup>
|
||||||
<div v-if="Rooms.length==0" class="empty">
|
</Card>
|
||||||
<Icon type="md-wine" size="50" />没有任何房间
|
<div v-if="Rooms.length==0" class="empty">
|
||||||
|
<Icon type="md-wine" size="50"/>
|
||||||
|
没有任何房间
|
||||||
|
</div>
|
||||||
|
<div class="status">
|
||||||
|
<Alert>带宽消耗 📥:{{totalInNetSpeed}} 📤:{{totalOutNetSpeed}}</Alert>
|
||||||
|
<Alert
|
||||||
|
:type="memoryStatus"
|
||||||
|
>内存使用:{{networkFormat(Memory.Used,"M")}} 占比:{{Memory.Usage.toFixed(2)}}%
|
||||||
|
</Alert>
|
||||||
|
<Alert :type="cpuStatus">CPU使用:{{CPUUsage.toFixed(2)}}%</Alert>
|
||||||
|
<Alert
|
||||||
|
:type="hardDiskStatus"
|
||||||
|
>磁盘使用:{{networkFormat(HardDisk.Used,"M")}} 占比:{{HardDisk.Usage.toFixed(2)}}%
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
|
||||||
|
<Records v-model="showRecords"/>
|
||||||
|
<Logs v-model="showLogs"/>
|
||||||
|
<Config v-model="showConfig"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
|
||||||
<Alert>带宽消耗 📥:{{totalInNetSpeed}} 📤:{{totalOutNetSpeed}}</Alert>
|
|
||||||
<Alert
|
|
||||||
:type="memoryStatus"
|
|
||||||
>内存使用:{{networkFormat(Memory.Used,"M")}} 占比:{{Memory.Usage.toFixed(2)}}%</Alert>
|
|
||||||
<Alert :type="cpuStatus">CPU使用:{{CPUUsage.toFixed(2)}}%</Alert>
|
|
||||||
<Alert
|
|
||||||
:type="hardDiskStatus"
|
|
||||||
>磁盘使用:{{networkFormat(HardDisk.Used,"M")}} 占比:{{HardDisk.Usage.toFixed(2)}}%</Alert>
|
|
||||||
</div>
|
|
||||||
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
|
|
||||||
<Records v-model="showRecords" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from "vuex";
|
import {mapActions, mapState} from "vuex";
|
||||||
import Jessibuca from "../components/Jessibuca";
|
import Jessibuca from "../components/Jessibuca";
|
||||||
import StartTime from "../components/StartTime";
|
import StartTime from "../components/StartTime";
|
||||||
import Records from "../components/Records";
|
import Records from "../components/Records";
|
||||||
const uintInc = {
|
import Logs from "../components/Logs";
|
||||||
"": "K",
|
import Config from "../components/Config"
|
||||||
K: "M",
|
|
||||||
M: "G",
|
const uintInc = {
|
||||||
G: null
|
"": "K",
|
||||||
};
|
K: "M",
|
||||||
const SoundFormat = {
|
M: "G",
|
||||||
0: "Linear PCM, platform endian",
|
G: null
|
||||||
1: "ADPCM",
|
|
||||||
2: "MP3",
|
|
||||||
3: "Linear PCM, little endian",
|
|
||||||
4: "Nellymoser 16kHz mono",
|
|
||||||
5: "Nellymoser 8kHz mono",
|
|
||||||
6: "Nellymoser",
|
|
||||||
7: "G.711 A-law logarithmic PCM",
|
|
||||||
8: "G.711 mu-law logarithmic PCM",
|
|
||||||
9: "reserved",
|
|
||||||
10: "AAC",
|
|
||||||
11: "Speex",
|
|
||||||
14: "MP3 8Khz",
|
|
||||||
15: "Device-specific sound"
|
|
||||||
};
|
|
||||||
const CodecID = {
|
|
||||||
1: "JPEG (currently unused)",
|
|
||||||
2: "Sorenson H.263",
|
|
||||||
3: "Screen video",
|
|
||||||
4: "On2 VP6",
|
|
||||||
5: "On2 VP6 with alpha channel",
|
|
||||||
6: "Screen video version 2",
|
|
||||||
7: "AVC",
|
|
||||||
12: "H265"
|
|
||||||
};
|
|
||||||
export default {
|
|
||||||
name: "Console",
|
|
||||||
components: {
|
|
||||||
Jessibuca,
|
|
||||||
StartTime,
|
|
||||||
Records
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showPreview: false,
|
|
||||||
showRecords: false,
|
|
||||||
typeMap: {
|
|
||||||
FlvFile:"🎥",
|
|
||||||
TS: "🎬",
|
|
||||||
HLS: "🍎",
|
|
||||||
"": "⏳",
|
|
||||||
Match365: "🏆",
|
|
||||||
RTMP: "🚠"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
const SoundFormat = {
|
||||||
computed: {
|
0: "Linear PCM, platform endian",
|
||||||
...mapState({
|
1: "ADPCM",
|
||||||
Rooms: state => state.summary.Rooms || [],
|
2: "MP3",
|
||||||
Memory: state => state.summary.Memory,
|
3: "Linear PCM, little endian",
|
||||||
CPUUsage: state => state.summary.CPUUsage,
|
4: "Nellymoser 16kHz mono",
|
||||||
HardDisk: state => state.summary.HardDisk,
|
5: "Nellymoser 8kHz mono",
|
||||||
cpuStatus: state => {
|
6: "Nellymoser",
|
||||||
if (state.summary.CPUUsage > 99) return "error";
|
7: "G.711 A-law logarithmic PCM",
|
||||||
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
8: "G.711 mu-law logarithmic PCM",
|
||||||
},
|
9: "reserved",
|
||||||
memoryStatus(state) {
|
10: "AAC",
|
||||||
if (state.summary.CPUUsage > 99) return "error";
|
11: "Speex",
|
||||||
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
14: "MP3 8Khz",
|
||||||
},
|
15: "Device-specific sound"
|
||||||
hardDiskStatus(state) {
|
};
|
||||||
if (state.summary.CPUUsage > 99) return "error";
|
const CodecID = {
|
||||||
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
1: "JPEG (currently unused)",
|
||||||
},
|
2: "Sorenson H.263",
|
||||||
totalInNetSpeed(state) {
|
3: "Screen video",
|
||||||
return (
|
4: "On2 VP6",
|
||||||
this.networkFormat(
|
5: "On2 VP6 with alpha channel",
|
||||||
state.summary.NetWork.reduce((aac, c) => aac + c.ReceiveSpeed, 0)
|
6: "Screen video version 2",
|
||||||
) + "/S"
|
7: "AVC",
|
||||||
);
|
12: "H265"
|
||||||
},
|
};
|
||||||
totalOutNetSpeed(state) {
|
export default {
|
||||||
return (
|
name: "Console",
|
||||||
this.networkFormat(
|
components: {
|
||||||
state.summary.NetWork.reduce((aac, c) => aac + c.SentSpeed, 0)
|
Jessibuca,
|
||||||
) + "/S"
|
StartTime,
|
||||||
);
|
Records,
|
||||||
}
|
Logs, Config
|
||||||
})
|
},
|
||||||
},
|
data() {
|
||||||
methods: {
|
return {
|
||||||
...mapActions(["fetchSummary", "stopFetchSummary"]),
|
showPreview: false,
|
||||||
preview(item) {
|
showRecords: false,
|
||||||
this.$refs.jessibuca.play(
|
showLogs: false,
|
||||||
"ws://" + location.hostname + ":8080/" + item.StreamPath
|
showConfig: false,
|
||||||
);
|
typeMap: {
|
||||||
this.showPreview = true;
|
FlvFile: "🎥",
|
||||||
},
|
TS: "🎬",
|
||||||
onShowDetail() {
|
HLS: "🍎",
|
||||||
// this.showDetail = true
|
"": "⏳",
|
||||||
// this.currentSub = item
|
Match365: "🏆",
|
||||||
},
|
RTMP: "🚠"
|
||||||
networkFormat(value, unit = "") {
|
}
|
||||||
if (value > 1024 && uintInc[unit]) {
|
};
|
||||||
return this.networkFormat(value / 1024, uintInc[unit]);
|
},
|
||||||
}
|
computed: {
|
||||||
return value.toFixed(2).replace(".00", "") + unit + "B";
|
...mapState({
|
||||||
},
|
Rooms: state => state.summary.Rooms || [],
|
||||||
SoundFormat(soundFormat) {
|
Memory: state => state.summary.Memory,
|
||||||
return SoundFormat[soundFormat];
|
CPUUsage: state => state.summary.CPUUsage,
|
||||||
},
|
HardDisk: state => state.summary.HardDisk,
|
||||||
CodecID(codec) {
|
cpuStatus: state => {
|
||||||
return CodecID[codec];
|
if (state.summary.CPUUsage > 99) return "error";
|
||||||
},
|
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
||||||
SoundRate(rate) {
|
},
|
||||||
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
|
memoryStatus(state) {
|
||||||
},
|
if (state.summary.CPUUsage > 99) return "error";
|
||||||
record(item) {
|
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
||||||
window.ajax.get(
|
},
|
||||||
"//" + location.host + "/api/record/flv",
|
hardDiskStatus(state) {
|
||||||
{ streamPath: item.StreamPath },
|
if (state.summary.CPUUsage > 99) return "error";
|
||||||
x => {
|
return state.summary.CPUUsage > 50 ? "warning" : "success";
|
||||||
if (x == "success") {
|
},
|
||||||
this.$Message.success("开始录制");
|
totalInNetSpeed(state) {
|
||||||
} else {
|
return (
|
||||||
this.$Message.error(x);
|
this.networkFormat(
|
||||||
}
|
state.summary.NetWork.reduce((aac, c) => aac + c.ReceiveSpeed, 0)
|
||||||
|
) + "/S"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
totalOutNetSpeed(state) {
|
||||||
|
return (
|
||||||
|
this.networkFormat(
|
||||||
|
state.summary.NetWork.reduce((aac, c) => aac + c.SentSpeed, 0)
|
||||||
|
) + "/S"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchSummary", "stopFetchSummary"]),
|
||||||
|
preview(item) {
|
||||||
|
this.$refs.jessibuca.play(
|
||||||
|
"ws://" + location.hostname + ":8080/" + item.StreamPath
|
||||||
|
);
|
||||||
|
this.showPreview = true;
|
||||||
|
},
|
||||||
|
onShowDetail() {
|
||||||
|
// this.showDetail = true
|
||||||
|
// this.currentSub = item
|
||||||
|
},
|
||||||
|
networkFormat(value, unit = "") {
|
||||||
|
if (value > 1024 && uintInc[unit]) {
|
||||||
|
return this.networkFormat(value / 1024, uintInc[unit]);
|
||||||
|
}
|
||||||
|
return value.toFixed(2).replace(".00", "") + unit + "B";
|
||||||
|
},
|
||||||
|
SoundFormat(soundFormat) {
|
||||||
|
return SoundFormat[soundFormat];
|
||||||
|
},
|
||||||
|
CodecID(codec) {
|
||||||
|
return CodecID[codec];
|
||||||
|
},
|
||||||
|
SoundRate(rate) {
|
||||||
|
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
|
||||||
|
},
|
||||||
|
record(item) {
|
||||||
|
window.ajax.get(
|
||||||
|
"//" + location.host + "/api/record/flv",
|
||||||
|
{streamPath: item.StreamPath},
|
||||||
|
x => {
|
||||||
|
if (x == "success") {
|
||||||
|
this.$Message.success("开始录制");
|
||||||
|
} else {
|
||||||
|
this.$Message.error(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stopRecord(item) {
|
||||||
|
window.ajax.get(
|
||||||
|
"//" + location.host + "/api/record/flv/stop",
|
||||||
|
{streamPath: item.StreamPath},
|
||||||
|
x => {
|
||||||
|
if (x == "success") {
|
||||||
|
this.$Message.success("停止录制");
|
||||||
|
} else {
|
||||||
|
this.$Message.error(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isRecording(item) {
|
||||||
|
return (
|
||||||
|
item.SubscriberInfo &&
|
||||||
|
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchSummary();
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.stopFetchSummary();
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
},
|
|
||||||
stopRecord(item) {
|
|
||||||
window.ajax.get(
|
|
||||||
"//" + location.host + "/api/record/flv/stop",
|
|
||||||
{ streamPath: item.StreamPath },
|
|
||||||
x => {
|
|
||||||
if (x == "success") {
|
|
||||||
this.$Message.success("停止录制");
|
|
||||||
} else {
|
|
||||||
this.$Message.error(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isRecording(item) {
|
|
||||||
return (
|
|
||||||
item.SubscriberInfo &&
|
|
||||||
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchSummary();
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this.stopFetchSummary();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@keyframes recording {
|
@keyframes recording {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording {
|
.recording {
|
||||||
animation: recording 1s infinite;
|
animation: recording 1s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
padding-bottom: 30px;
|
padding-bottom: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room {
|
.room {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
color: #eb5e46;
|
color: #eb5e46;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 5px;
|
left: 5px;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status > div {
|
.status > div {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@@ -3,11 +3,17 @@ package monica
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ConfigRaw []byte
|
||||||
|
|
||||||
func Run(configFile string) (err error) {
|
func Run(configFile string) (err error) {
|
||||||
if _, err = toml.DecodeFile(configFile, cg); err == nil {
|
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = toml.Decode(string(ConfigRaw), cg); err == nil {
|
||||||
for name, config := range plugins {
|
for name, config := range plugins {
|
||||||
if cfg, ok := cg.Plugins[name]; ok {
|
if cfg, ok := cg.Plugins[name]; ok {
|
||||||
b, _ := json.Marshal(cfg)
|
b, _ := json.Marshal(cfg)
|
||||||
|
@@ -84,24 +84,34 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
func run() {
|
func run() {
|
||||||
http.HandleFunc("/api/stop", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/api/stop", stopPublish)
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
|
|
||||||
if b, ok := AllRoom.Load(streamPath); ok {
|
|
||||||
b.(*Room).Cancel()
|
|
||||||
w.Write([]byte("success"))
|
|
||||||
} else {
|
|
||||||
w.Write([]byte("no query stream"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.Write([]byte("no such stream"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
http.HandleFunc("/api/summary", summary)
|
http.HandleFunc("/api/summary", summary)
|
||||||
|
http.HandleFunc("/api/logs", watchLogs)
|
||||||
|
http.HandleFunc("/api/config", getConfig)
|
||||||
http.HandleFunc("/", website)
|
http.HandleFunc("/", website)
|
||||||
log.Printf("server gateway start at %s", config.ListenAddr)
|
log.Printf("server gateway start at %s", config.ListenAddr)
|
||||||
log.Fatal(http.ListenAndServe(config.ListenAddr, nil))
|
log.Fatal(http.ListenAndServe(config.ListenAddr, nil))
|
||||||
}
|
}
|
||||||
|
func getConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(ConfigRaw)
|
||||||
|
}
|
||||||
|
func watchLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
AddWriter(NewSSE(w, r.Context()))
|
||||||
|
<-r.Context().Done()
|
||||||
|
}
|
||||||
|
func stopPublish(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
|
||||||
|
if b, ok := AllRoom.Load(streamPath); ok {
|
||||||
|
b.(*Room).Cancel()
|
||||||
|
w.Write([]byte("success"))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("no query stream"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("no such stream"))
|
||||||
|
}
|
||||||
|
}
|
||||||
func website(w http.ResponseWriter, r *http.Request) {
|
func website(w http.ResponseWriter, r *http.Request) {
|
||||||
filePath := r.URL.Path
|
filePath := r.URL.Path
|
||||||
if filePath == "/" {
|
if filePath == "/" {
|
||||||
|
Reference in New Issue
Block a user