This commit is contained in:
李宇翔
2020-04-20 20:00:23 +08:00
parent f52662249e
commit 9829d63037
10 changed files with 320 additions and 379 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019-present, dexter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

12
main.go
View File

@@ -27,12 +27,10 @@ type FlvFileInfo struct {
func init() {
InstallPlugin(&PluginConfig{
Name: "Record",
Type: PLUGIN_SUBSCRIBER,
Config: &config,
Version: "1.0.0",
UI: CurrentDir("dashboard", "ui", "plugin-record.min.js"),
Run: run,
Name: "Record",
Type: PLUGIN_SUBSCRIBER,
Config: &config,
Run: run,
})
}
func run() {
@@ -52,7 +50,7 @@ func run() {
})
http.HandleFunc("/record/flv", func(writer http.ResponseWriter, r *http.Request) {
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
if err := SaveFlv(streamPath, r.URL.Query().Get("append") != ""); err != nil {
if err := SaveFlv(streamPath, r.URL.Query().Get("append") == "true"); err != nil {
writer.Write([]byte(err.Error()))
} else {
writer.Write([]byte("success"))

View File

@@ -87,17 +87,6 @@ module.exports =
/************************************************************************/
/******/ ({
/***/ "034f":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("85ec");
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__);
/* unused harmony reexport * */
/* unused harmony default export */ var _unused_webpack_default_export = (_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0___default.a);
/***/ }),
/***/ "0e05":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
@@ -109,14 +98,25 @@ module.exports =
/***/ }),
/***/ "85ec":
/***/ "39a8":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("b3f8");
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0__);
/* unused harmony reexport * */
/* unused harmony default export */ var _unused_webpack_default_export = (_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0___default.a);
/***/ }),
/***/ "a189":
/***/ (function(module, exports, __webpack_require__) {
// extracted by mini-css-extract-plugin
/***/ }),
/***/ "a189":
/***/ "b3f8":
/***/ (function(module, exports, __webpack_require__) {
// extracted by mini-css-extract-plugin
@@ -190,12 +190,12 @@ if (typeof window !== 'undefined') {
// Indicate to webpack that this file can be concatenated
/* harmony default export */ var setPublicPath = (null);
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=655db994&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-tabs',{attrs:{"value":_vm.active1,"indicator-color":"#80deea","inverse":"","center":""},on:{"update:value":function($event){_vm.active1=$event}}},[_c('mu-tab',[_vm._v("直播流")]),_c('mu-tab',[_vm._v("录制的视频")])],1),(_vm.Rooms.length==0 && _vm.active1==0)?_c('div',{staticClass:"empty"},[_c('Icon',{attrs:{"type":"md-wine","size":"50"}}),_vm._v("没有任何房间 ")],1):(_vm.active1==0)?_vm._l((_vm.Rooms),function(item){return _c('mu-card',{key:item.StreamPath,staticClass:"room"},[_c('mu-card-title',{attrs:{"title":item.StreamPath,"sub-title":item.StartTime}}),_c('mu-card-text',[_c('p',[_vm._v(" "+_vm._s(_vm.SoundFormat(item.AudioInfo.SoundFormat))+" "+_vm._s(item.AudioInfo.PacketCount)+" "+_vm._s(_vm.SoundRate(item.AudioInfo.SoundRate))+" 声道:"+_vm._s(item.AudioInfo.SoundType)+" ")]),_c('p',[_vm._v(" "+_vm._s(_vm.CodecID(item.VideoInfo.CodecID))+" "+_vm._s(item.VideoInfo.PacketCount)+" "+_vm._s(item.VideoInfo.SPSInfo.Width)+"x"+_vm._s(item.VideoInfo.SPSInfo.Height)+" ")])]),_c('mu-card-actions',[(_vm.isRecording(item))?_c('mu-button',{staticClass:"recording",attrs:{"icon":""},on:{"click":function($event){return _vm.stopRecord(item)}}},[_c('mu-icon',{attrs:{"value":"fiber_manual_record"}})],1):_c('mu-button',{attrs:{"icon":""},on:{"click":function($event){return _vm.record(item)}}},[_c('mu-icon',{attrs:{"value":"fiber_manual_record"}})],1)],1)],1)}):_vm._e(),(_vm.active1==1)?_c('Records',{ref:"recordsPanel"}):_vm._e()],2)}
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=3ce37488&scoped=true&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-tabs',{attrs:{"value":_vm.active1,"indicator-color":"#80deea","inverse":"","center":""},on:{"update:value":function($event){_vm.active1=$event}}},[_c('mu-tab',[_vm._v("直播流")]),_c('mu-tab',[_vm._v("录制的视频")])],1),(_vm.active1==0)?_c('mu-data-table',{attrs:{"columns":_vm.columns,"data":_vm.$store.state.Room,"min-col-width":50},on:{"row-click":function (i,r){ return _vm.isRecording(r)?_vm.stopRecord(r):_vm.record(r); }},scopedSlots:_vm._u([{key:"default",fn:function(scope){return [(_vm.isRecording(scope.row))?_c('td',{staticClass:"is-center"}):_c('td',{staticClass:"is-center"}),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.StreamPath))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.Type||"await"))]),_c('td',{staticClass:"is-center"},[_c('StartTime',{attrs:{"value":scope.row.StartTime}})],1),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.SoundFormat(scope.row.AudioInfo.SoundFormat)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.SoundRate(scope.row.AudioInfo.SoundRate)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.AudioInfo.SoundType))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.CodecID(scope.row.VideoInfo.CodecID)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.VideoInfo.SPSInfo.Width)+"x"+_vm._s(scope.row.VideoInfo.SPSInfo.Height))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.AudioInfo.PacketCount)+"/"+_vm._s(scope.row.VideoInfo.PacketCount))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.getSubscriberCount(scope.row)))])]}}],null,false,1124011921)}):_vm._e(),(_vm.active1==1)?_c('Records',{ref:"recordsPanel"}):_vm._e()],1)}
var staticRenderFns = []
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=655db994&
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=3ce37488&scoped=true&
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/Records.vue?vue&type=template&id=7c800264&scoped=true&
var Recordsvue_type_template_id_7c800264_scoped_true_render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"records"},_vm._l((_vm.data),function(item){return _c('mu-card',{key:item},[_c('mu-card-title',{attrs:{"title":item.Path,"sub-title":_vm.unitFormat(item.Size)+' '+_vm.toDurationStr(item.Duration)}}),_c('mu-card-actions',[_c('mu-button',{attrs:{"icon":"","small":""},on:{"click":function($event){return _vm.play(item)}}},[_c('mu-icon',{attrs:{"value":"play_arrow"}})],1),_c('mu-button',{attrs:{"icon":"","small":""},on:{"click":function($event){return _vm.deleteFlv(item)}}},[_c('mu-icon',{attrs:{"value":"delete_forever"}})],1)],1)],1)}),1)}
@@ -440,41 +440,7 @@ var component = normalizeComponent(
//
//
//
//
//
//
//
//
//
//
let roomsES = null;
const SoundFormat = {
0: "Linear PCM, platform endian",
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"
};
/* harmony default export */ var Appvue_type_script_lang_js_ = ({
components: {
@@ -482,55 +448,81 @@ const CodecID = {
},
data() {
return {
Rooms: [],
typeMap: {
Receiver: "📡",
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
columns: [
{
title: "房间",
name: "StreamPath",
sortable: true
},
{
title: "类型",
name: "Type",
sortable: true
},
{
title: "开始时间",
name: "StartTime",
sortable: true
},
{
title: "音频格式",
name: "AudioInfo"
},
{
title: "采样率",
name: "AudioInfo"
},
{
title: "声道",
name: "AudioInfo"
},
{
title: "视频格式",
name: "VideoInfo"
},
{
title: "分辨率",
name: "VideoInfo"
},
{
title: "数据包",
name: ""
},
{
title: "订阅者",
name: "Subscribes"
}
]
};
},
methods: {
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
this.$Modal.confirm({
title: "提示",
content:
"<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
let append = false;
this.$confirm(
h =>
h("mu-switch", {
props: {
label: "追加模式"
},
on: {
change(value) {
append = value;
}
}
);
},
onCancel: () => {
window.ajax.get(
"/record/flv",
}),
"是否开始录制"
).then(result => {
if (result) {
this.ajax.get(
"/record/flv?append=" + append,
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
this.$toast.success(
"开始录制" + (append ? "(追加模式)" : "")
);
} else {
this.$Message.error(x);
this.$toast.error(x);
}
}
);
@@ -538,51 +530,33 @@ const CodecID = {
});
},
stopRecord(item) {
window.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
this.$confirm("是否停止录制", "提示").then(result => {
this.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$toast.success("停止录制");
} else {
this.$toast.error(x);
}
}
}
);
);
});
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
fetchRooms() {
roomsES = new EventSource("/api/summary");
roomsES.onmessage = evt => {
if (!evt.data) return;
let summary = JSON.parse(evt.data);
this.Rooms = (summary && summary.Rooms) || [];
this.Rooms.sort((a, b) =>
a.StreamPath > b.StreamPath ? 1 : -1
);
};
},
onClickTab(name) {
this.$refs.recordsPanel.onVisible(name == "recordsPanel");
}
},
mounted() {
this.fetchRooms();
},
destroyed() {
roomsES.close();
}
});
// CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js&
/* harmony default export */ var src_Appvue_type_script_lang_js_ = (Appvue_type_script_lang_js_);
// EXTERNAL MODULE: ./src/App.vue?vue&type=style&index=0&lang=css&
var Appvue_type_style_index_0_lang_css_ = __webpack_require__("034f");
// EXTERNAL MODULE: ./src/App.vue?vue&type=style&index=0&id=3ce37488&scoped=true&lang=css&
var Appvue_type_style_index_0_id_3ce37488_scoped_true_lang_css_ = __webpack_require__("39a8");
// CONCATENATED MODULE: ./src/App.vue
@@ -599,7 +573,7 @@ var App_component = normalizeComponent(
staticRenderFns,
false,
null,
null,
"3ce37488",
null
)

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
.records[data-v-7c800264]{display:flex;flex-wrap:wrap;padding:0 15px}.records>[data-v-7c800264]{width:200px}@-webkit-keyframes recording{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording{-webkit-animation:recording 1s infinite;animation:recording 1s infinite}.layout{padding-bottom:30px;display:flex;flex-wrap:wrap}.room{width:250px;margin:10px;text-align:left}.empty{color:#ffc107;width:100%;min-height:500px;display:flex;justify-content:center;align-items:center}.status{position:fixed;display:flex;left:5px;bottom:10px}.status>div{margin:0 5px}
.records[data-v-7c800264]{display:flex;flex-wrap:wrap;padding:0 15px}.records>[data-v-7c800264]{width:200px}@-webkit-keyframes recording-data-v-3ce37488{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-3ce37488{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-3ce37488]{-webkit-animation:recording-data-v-3ce37488 1s infinite;animation:recording-data-v-3ce37488 1s infinite}.layout[data-v-3ce37488]{padding-bottom:30px;display:flex;flex-wrap:wrap}.room[data-v-3ce37488]{width:250px;margin:10px;text-align:left}.empty[data-v-3ce37488]{color:#ffc107;width:100%;min-height:500px;display:flex;justify-content:center;align-items:center}.status[data-v-3ce37488]{position:fixed;display:flex;left:5px;bottom:10px}.status>div[data-v-3ce37488]{margin:0 5px}

View File

@@ -96,17 +96,6 @@ return /******/ (function(modules) { // webpackBootstrap
/************************************************************************/
/******/ ({
/***/ "034f":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("85ec");
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__);
/* unused harmony reexport * */
/* unused harmony default export */ var _unused_webpack_default_export = (_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0___default.a);
/***/ }),
/***/ "0e05":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
@@ -118,14 +107,25 @@ return /******/ (function(modules) { // webpackBootstrap
/***/ }),
/***/ "85ec":
/***/ "39a8":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("b3f8");
/* harmony import */ var _node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0__);
/* unused harmony reexport * */
/* unused harmony default export */ var _unused_webpack_default_export = (_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_App_vue_vue_type_style_index_0_id_3ce37488_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_0___default.a);
/***/ }),
/***/ "a189":
/***/ (function(module, exports, __webpack_require__) {
// extracted by mini-css-extract-plugin
/***/ }),
/***/ "a189":
/***/ "b3f8":
/***/ (function(module, exports, __webpack_require__) {
// extracted by mini-css-extract-plugin
@@ -199,12 +199,12 @@ if (typeof window !== 'undefined') {
// Indicate to webpack that this file can be concatenated
/* harmony default export */ var setPublicPath = (null);
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=655db994&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-tabs',{attrs:{"value":_vm.active1,"indicator-color":"#80deea","inverse":"","center":""},on:{"update:value":function($event){_vm.active1=$event}}},[_c('mu-tab',[_vm._v("直播流")]),_c('mu-tab',[_vm._v("录制的视频")])],1),(_vm.Rooms.length==0 && _vm.active1==0)?_c('div',{staticClass:"empty"},[_c('Icon',{attrs:{"type":"md-wine","size":"50"}}),_vm._v("没有任何房间 ")],1):(_vm.active1==0)?_vm._l((_vm.Rooms),function(item){return _c('mu-card',{key:item.StreamPath,staticClass:"room"},[_c('mu-card-title',{attrs:{"title":item.StreamPath,"sub-title":item.StartTime}}),_c('mu-card-text',[_c('p',[_vm._v(" "+_vm._s(_vm.SoundFormat(item.AudioInfo.SoundFormat))+" "+_vm._s(item.AudioInfo.PacketCount)+" "+_vm._s(_vm.SoundRate(item.AudioInfo.SoundRate))+" 声道:"+_vm._s(item.AudioInfo.SoundType)+" ")]),_c('p',[_vm._v(" "+_vm._s(_vm.CodecID(item.VideoInfo.CodecID))+" "+_vm._s(item.VideoInfo.PacketCount)+" "+_vm._s(item.VideoInfo.SPSInfo.Width)+"x"+_vm._s(item.VideoInfo.SPSInfo.Height)+" ")])]),_c('mu-card-actions',[(_vm.isRecording(item))?_c('mu-button',{staticClass:"recording",attrs:{"icon":""},on:{"click":function($event){return _vm.stopRecord(item)}}},[_c('mu-icon',{attrs:{"value":"fiber_manual_record"}})],1):_c('mu-button',{attrs:{"icon":""},on:{"click":function($event){return _vm.record(item)}}},[_c('mu-icon',{attrs:{"value":"fiber_manual_record"}})],1)],1)],1)}):_vm._e(),(_vm.active1==1)?_c('Records',{ref:"recordsPanel"}):_vm._e()],2)}
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/App.vue?vue&type=template&id=3ce37488&scoped=true&
var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('mu-tabs',{attrs:{"value":_vm.active1,"indicator-color":"#80deea","inverse":"","center":""},on:{"update:value":function($event){_vm.active1=$event}}},[_c('mu-tab',[_vm._v("直播流")]),_c('mu-tab',[_vm._v("录制的视频")])],1),(_vm.active1==0)?_c('mu-data-table',{attrs:{"columns":_vm.columns,"data":_vm.$store.state.Room,"min-col-width":50},on:{"row-click":function (i,r){ return _vm.isRecording(r)?_vm.stopRecord(r):_vm.record(r); }},scopedSlots:_vm._u([{key:"default",fn:function(scope){return [(_vm.isRecording(scope.row))?_c('td',{staticClass:"is-center"}):_c('td',{staticClass:"is-center"}),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.StreamPath))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.Type||"await"))]),_c('td',{staticClass:"is-center"},[_c('StartTime',{attrs:{"value":scope.row.StartTime}})],1),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.SoundFormat(scope.row.AudioInfo.SoundFormat)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.SoundRate(scope.row.AudioInfo.SoundRate)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.AudioInfo.SoundType))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.CodecID(scope.row.VideoInfo.CodecID)))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.VideoInfo.SPSInfo.Width)+"x"+_vm._s(scope.row.VideoInfo.SPSInfo.Height))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(scope.row.AudioInfo.PacketCount)+"/"+_vm._s(scope.row.VideoInfo.PacketCount))]),_c('td',{staticClass:"is-center"},[_vm._v(_vm._s(_vm.getSubscriberCount(scope.row)))])]}}],null,false,1124011921)}):_vm._e(),(_vm.active1==1)?_c('Records',{ref:"recordsPanel"}):_vm._e()],1)}
var staticRenderFns = []
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=655db994&
// CONCATENATED MODULE: ./src/App.vue?vue&type=template&id=3ce37488&scoped=true&
// CONCATENATED MODULE: ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"5b04a8fd-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/components/Records.vue?vue&type=template&id=7c800264&scoped=true&
var Recordsvue_type_template_id_7c800264_scoped_true_render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"records"},_vm._l((_vm.data),function(item){return _c('mu-card',{key:item},[_c('mu-card-title',{attrs:{"title":item.Path,"sub-title":_vm.unitFormat(item.Size)+' '+_vm.toDurationStr(item.Duration)}}),_c('mu-card-actions',[_c('mu-button',{attrs:{"icon":"","small":""},on:{"click":function($event){return _vm.play(item)}}},[_c('mu-icon',{attrs:{"value":"play_arrow"}})],1),_c('mu-button',{attrs:{"icon":"","small":""},on:{"click":function($event){return _vm.deleteFlv(item)}}},[_c('mu-icon',{attrs:{"value":"delete_forever"}})],1)],1)],1)}),1)}
@@ -449,41 +449,7 @@ var component = normalizeComponent(
//
//
//
//
//
//
//
//
//
//
let roomsES = null;
const SoundFormat = {
0: "Linear PCM, platform endian",
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"
};
/* harmony default export */ var Appvue_type_script_lang_js_ = ({
components: {
@@ -491,55 +457,81 @@ const CodecID = {
},
data() {
return {
Rooms: [],
typeMap: {
Receiver: "📡",
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
columns: [
{
title: "房间",
name: "StreamPath",
sortable: true
},
{
title: "类型",
name: "Type",
sortable: true
},
{
title: "开始时间",
name: "StartTime",
sortable: true
},
{
title: "音频格式",
name: "AudioInfo"
},
{
title: "采样率",
name: "AudioInfo"
},
{
title: "声道",
name: "AudioInfo"
},
{
title: "视频格式",
name: "VideoInfo"
},
{
title: "分辨率",
name: "VideoInfo"
},
{
title: "数据包",
name: ""
},
{
title: "订阅者",
name: "Subscribes"
}
]
};
},
methods: {
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
this.$Modal.confirm({
title: "提示",
content:
"<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
let append = false;
this.$confirm(
h =>
h("mu-switch", {
props: {
label: "追加模式"
},
on: {
change(value) {
append = value;
}
}
);
},
onCancel: () => {
window.ajax.get(
"/record/flv",
}),
"是否开始录制"
).then(result => {
if (result) {
this.ajax.get(
"/record/flv?append=" + append,
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
this.$toast.success(
"开始录制" + (append ? "(追加模式)" : "")
);
} else {
this.$Message.error(x);
this.$toast.error(x);
}
}
);
@@ -547,51 +539,33 @@ const CodecID = {
});
},
stopRecord(item) {
window.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
this.$confirm("是否停止录制", "提示").then(result => {
this.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$toast.success("停止录制");
} else {
this.$toast.error(x);
}
}
}
);
);
});
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
fetchRooms() {
roomsES = new EventSource("/api/summary");
roomsES.onmessage = evt => {
if (!evt.data) return;
let summary = JSON.parse(evt.data);
this.Rooms = (summary && summary.Rooms) || [];
this.Rooms.sort((a, b) =>
a.StreamPath > b.StreamPath ? 1 : -1
);
};
},
onClickTab(name) {
this.$refs.recordsPanel.onVisible(name == "recordsPanel");
}
},
mounted() {
this.fetchRooms();
},
destroyed() {
roomsES.close();
}
});
// CONCATENATED MODULE: ./src/App.vue?vue&type=script&lang=js&
/* harmony default export */ var src_Appvue_type_script_lang_js_ = (Appvue_type_script_lang_js_);
// EXTERNAL MODULE: ./src/App.vue?vue&type=style&index=0&lang=css&
var Appvue_type_style_index_0_lang_css_ = __webpack_require__("034f");
// EXTERNAL MODULE: ./src/App.vue?vue&type=style&index=0&id=3ce37488&scoped=true&lang=css&
var Appvue_type_style_index_0_id_3ce37488_scoped_true_lang_css_ = __webpack_require__("39a8");
// CONCATENATED MODULE: ./src/App.vue
@@ -608,7 +582,7 @@ var App_component = normalizeComponent(
staticRenderFns,
false,
null,
null,
"3ce37488",
null
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,64 +4,30 @@
<mu-tab>直播流</mu-tab>
<mu-tab>录制的视频</mu-tab>
</mu-tabs>
<div v-if="Rooms.length==0 && active1==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
</div>
<template v-else-if="active1==0">
<mu-card v-for="item in Rooms" :key="item.StreamPath" class="room">
<mu-card-title :title="item.StreamPath" :sub-title="item.StartTime" />
<mu-card-text>
<p>
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
</p>
<p>
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
</p>
</mu-card-text>
<mu-card-actions>
<mu-button icon @click="stopRecord(item)" class="recording" v-if="isRecording(item)">
<mu-icon value="fiber_manual_record" />
</mu-button>
<mu-button icon @click="record(item)" v-else>
<mu-icon value="fiber_manual_record" />
</mu-button>
</mu-card-actions>
</mu-card>
</template>
<mu-data-table v-if="active1==0" :columns="columns" :data="$store.state.Room" :min-col-width="50"
@row-click="(i,r)=>isRecording(r)?stopRecord(r):record(r)">
<template slot-scope="scope">
<td class="is-center" v-if="isRecording(scope.row)"></td>
<td class="is-center" v-else></td>
<td class="is-center">{{scope.row.StreamPath}}</td>
<td class="is-center">{{scope.row.Type||"await"}}</td>
<td class="is-center">
<StartTime :value="scope.row.StartTime"></StartTime>
</td>
<td class="is-center">{{SoundFormat(scope.row.AudioInfo.SoundFormat)}}</td>
<td class="is-center">{{SoundRate(scope.row.AudioInfo.SoundRate)}}</td>
<td class="is-center">{{scope.row.AudioInfo.SoundType}}</td>
<td class="is-center">{{CodecID(scope.row.VideoInfo.CodecID)}}</td>
<td class="is-center">{{scope.row.VideoInfo.SPSInfo.Width}}x{{scope.row.VideoInfo.SPSInfo.Height}}</td>
<td class="is-center">{{scope.row.AudioInfo.PacketCount}}/{{scope.row.VideoInfo.PacketCount}}</td>
<td class="is-center">{{getSubscriberCount(scope.row)}}</td>
</template>
</mu-data-table>
<Records ref="recordsPanel" v-if="active1==1" />
</div>
</template>
<script>
let roomsES = null;
const SoundFormat = {
0: "Linear PCM, platform endian",
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"
};
import Records from "./components/Records";
export default {
components: {
@@ -69,55 +35,81 @@ export default {
},
data() {
return {
Rooms: [],
typeMap: {
Receiver: "📡",
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
Match365: "🏆",
RTMP: "🚠"
}
columns: [
{
title: "房间",
name: "StreamPath",
sortable: true
},
{
title: "类型",
name: "Type",
sortable: true
},
{
title: "开始时间",
name: "StartTime",
sortable: true
},
{
title: "音频格式",
name: "AudioInfo"
},
{
title: "采样率",
name: "AudioInfo"
},
{
title: "声道",
name: "AudioInfo"
},
{
title: "视频格式",
name: "VideoInfo"
},
{
title: "分辨率",
name: "VideoInfo"
},
{
title: "数据包",
name: ""
},
{
title: "订阅者",
name: "Subscribes"
}
]
};
},
methods: {
SoundFormat(soundFormat) {
return SoundFormat[soundFormat];
},
CodecID(codec) {
return CodecID[codec];
},
SoundRate(rate) {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
this.$Modal.confirm({
title: "提示",
content:
"<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
let append = false;
this.$confirm(
h =>
h("mu-switch", {
props: {
label: "追加模式"
},
on: {
change(value) {
append = value;
}
}
);
},
onCancel: () => {
window.ajax.get(
"/record/flv",
}),
"是否开始录制"
).then(result => {
if (result) {
this.ajax.get(
"/record/flv?append=" + append,
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
this.$toast.success(
"开始录制" + (append ? "(追加模式)" : "")
);
} else {
this.$Message.error(x);
this.$toast.error(x);
}
}
);
@@ -125,49 +117,31 @@ export default {
});
},
stopRecord(item) {
window.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("停止录制");
} else {
this.$Message.error(x);
this.$confirm("是否停止录制", "提示").then(result => {
this.ajax.get(
"/record/flv/stop",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$toast.success("停止录制");
} else {
this.$toast.error(x);
}
}
}
);
);
});
},
isRecording(item) {
return (
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
fetchRooms() {
roomsES = new EventSource("/api/summary");
roomsES.onmessage = evt => {
if (!evt.data) return;
let summary = JSON.parse(evt.data);
this.Rooms = (summary && summary.Rooms) || [];
this.Rooms.sort((a, b) =>
a.StreamPath > b.StreamPath ? 1 : -1
);
};
},
onClickTab(name) {
this.$refs.recordsPanel.onVisible(name == "recordsPanel");
}
},
mounted() {
this.fetchRooms();
},
destroyed() {
roomsES.close();
}
};
</script>
<style>
<style scoped>
@keyframes recording {
0% {
opacity: 0.2;