mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 20:52:29 +08:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
029abe3187 | ||
![]() |
40fba0d348 | ||
![]() |
73941d1e0b | ||
![]() |
709c2c6ac7 | ||
![]() |
f96bc11ddb | ||
![]() |
5563ddc0d2 | ||
![]() |
95657bd6df | ||
![]() |
b9e19e75c8 | ||
![]() |
eac623639d |
8
dashboard/dist/docs/404.html
vendored
8
dashboard/dist/docs/404.html
vendored
@@ -7,11 +7,11 @@
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
|
||||
<link rel="preload" href="/docs/assets/css/styles.1fc3f87c.css" as="style"><link rel="preload" href="/docs/assets/js/app.1fc3f87c.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.05f88c5b.js"><link rel="prefetch" href="/docs/assets/js/2.69b20946.js"><link rel="prefetch" href="/docs/assets/js/3.197b5253.js"><link rel="prefetch" href="/docs/assets/js/4.2a48a234.js"><link rel="prefetch" href="/docs/assets/js/5.bd73b45e.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.1fc3f87c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><div class="content"><h1>404</h1><blockquote>Looks like we've got some broken links.</blockquote><a href="/docs/" class="router-link-active">Take me home.</a></div></div></div>
|
||||
<script src="/docs/assets/js/app.d51c815b.js" defer></script>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><div class="content"><h1>404</h1><blockquote>There's nothing here.</blockquote><a href="/docs/" class="router-link-active">Take me home.</a></div></div></div>
|
||||
<script src="/docs/assets/js/app.1fc3f87c.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
1
dashboard/dist/docs/assets/js/1.05f88c5b.js
vendored
Normal file
1
dashboard/dist/docs/assets/js/1.05f88c5b.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/dist/docs/assets/js/1.6babbc1d.js
vendored
1
dashboard/dist/docs/assets/js/1.6babbc1d.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/docs/assets/js/2.69b20946.js
vendored
Normal file
1
dashboard/dist/docs/assets/js/2.69b20946.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
1
dashboard/dist/docs/assets/js/3.7646e76c.js
vendored
1
dashboard/dist/docs/assets/js/3.7646e76c.js
vendored
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{165:function(t,e,s){"use strict";s.r(e);var n=s(0),i=Object(n.a)({},(function(){var t=this.$createElement;this._self._c;return this._m(0)}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"content"},[e("h1",{attrs:{id:"更新历史"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#更新历史","aria-hidden":"true"}},[this._v("#")]),this._v(" 更新历史")]),e("ul",[e("li",[this._v("2020/1/27\n初步完成")])])])}],!1,null,null,null);e.default=i.exports}}]);
|
1
dashboard/dist/docs/assets/js/4.08fbc0d9.js
vendored
1
dashboard/dist/docs/assets/js/4.08fbc0d9.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/docs/assets/js/4.2a48a234.js
vendored
Normal file
1
dashboard/dist/docs/assets/js/4.2a48a234.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{162:function(t,s,e){"use strict";e.r(s);var i=e(0),n=Object(i.a)({},(function(){var t=this.$createElement;this._self._c;return this._m(0)}),[function(){var t=this.$createElement,s=this._self._c||t;return s("div",{staticClass:"content"},[s("h1",{attrs:{id:"更新历史"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#更新历史","aria-hidden":"true"}},[this._v("#")]),this._v(" 更新历史")]),s("ul",[s("li",[this._v("2020/2/20\n完成实例管理器")]),s("li",[this._v("2020/1/27\n完成核心架构")])])])}],!1,null,null,null);s.default=n.exports}}]);
|
1
dashboard/dist/docs/assets/js/5.bd73b45e.js
vendored
Normal file
1
dashboard/dist/docs/assets/js/5.bd73b45e.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
dashboard/dist/docs/assets/js/app.1fc3f87c.js
vendored
Normal file
8
dashboard/dist/docs/assets/js/app.1fc3f87c.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
30
dashboard/dist/docs/design.html
vendored
Normal file
30
dashboard/dist/docs/design.html
vendored
Normal file
File diff suppressed because one or more lines are too long
8
dashboard/dist/docs/develop.html
vendored
8
dashboard/dist/docs/develop.html
vendored
@@ -7,13 +7,13 @@
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/2.190ec46a.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
|
||||
<link rel="preload" href="/docs/assets/css/styles.1fc3f87c.css" as="style"><link rel="preload" href="/docs/assets/js/app.1fc3f87c.js" as="script"><link rel="preload" href="/docs/assets/js/3.197b5253.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.05f88c5b.js"><link rel="prefetch" href="/docs/assets/js/2.69b20946.js"><link rel="prefetch" href="/docs/assets/js/4.2a48a234.js"><link rel="prefetch" href="/docs/assets/js/5.bd73b45e.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.1fc3f87c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
|
||||
Monibuca
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="active sidebar-link">插件开发</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的定义" class="sidebar-link">插件的定义</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的安装" class="sidebar-link">插件的安装</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发订阅者插件" class="sidebar-link">开发订阅者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发发布者插件" class="sidebar-link">开发发布者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发钩子插件" class="sidebar-link">开发钩子插件</a></li></ul></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="插件开发"><a href="#插件开发" aria-hidden="true" class="header-anchor">#</a> 插件开发</h1><h2 id="插件的定义"><a href="#插件的定义" aria-hidden="true" class="header-anchor">#</a> 插件的定义</h2><p>所谓的插件,没有什么固定的规则,只需要完成<code>安装</code>操作即可。插件可以实现任意的功能扩展,最常见的是实现某种传输协议用来推流或者拉流</p><h2 id="插件的安装"><a href="#插件的安装" aria-hidden="true" class="header-anchor">#</a> 插件的安装</h2><p>下面是内置插件jessica的源码,代表了典型的插件安装</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> jessica
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="active sidebar-link">插件开发</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的定义" class="sidebar-link">插件的定义</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#插件的安装" class="sidebar-link">插件的安装</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发订阅者插件" class="sidebar-link">开发订阅者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发发布者插件" class="sidebar-link">开发发布者插件</a></li><li class="sidebar-sub-header"><a href="/docs/develop.html#开发钩子插件" class="sidebar-link">开发钩子插件</a></li></ul></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li><li><a href="/docs/design.html" class="sidebar-link">设计原理</a></li></ul></div><div class="page"><div class="content"><h1 id="插件开发"><a href="#插件开发" aria-hidden="true" class="header-anchor">#</a> 插件开发</h1><h2 id="插件的定义"><a href="#插件的定义" aria-hidden="true" class="header-anchor">#</a> 插件的定义</h2><p>所谓的插件,没有什么固定的规则,只需要完成<code>安装</code>操作即可。插件可以实现任意的功能扩展,最常见的是实现某种传输协议用来推流或者拉流</p><h2 id="插件的安装"><a href="#插件的安装" aria-hidden="true" class="header-anchor">#</a> 插件的安装</h2><p>下面是内置插件jessica的源码,代表了典型的插件安装</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> jessica
|
||||
|
||||
<span class="token keyword">import</span> <span class="token punctuation">(</span>
|
||||
<span class="token punctuation">.</span> <span class="token string">"github.com/langhuihui/monibuca/monica"</span>
|
||||
@@ -184,6 +184,6 @@
|
||||
更新日志
|
||||
</a> →
|
||||
</span></p></div></div></div></div>
|
||||
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/2.190ec46a.js" defer></script>
|
||||
<script src="/docs/assets/js/app.1fc3f87c.js" defer></script><script src="/docs/assets/js/3.197b5253.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
11
dashboard/dist/docs/history.html
vendored
11
dashboard/dist/docs/history.html
vendored
@@ -7,20 +7,21 @@
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/3.7646e76c.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
|
||||
<link rel="preload" href="/docs/assets/css/styles.1fc3f87c.css" as="style"><link rel="preload" href="/docs/assets/js/app.1fc3f87c.js" as="script"><link rel="preload" href="/docs/assets/js/4.2a48a234.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.05f88c5b.js"><link rel="prefetch" href="/docs/assets/js/2.69b20946.js"><link rel="prefetch" href="/docs/assets/js/3.197b5253.js"><link rel="prefetch" href="/docs/assets/js/5.bd73b45e.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.1fc3f87c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
|
||||
Monibuca
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="active sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="更新历史"><a href="#更新历史" aria-hidden="true" class="header-anchor">#</a> 更新历史</h1><ul><li>2020/1/27
|
||||
初步完成</li></ul></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="active sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li><li><a href="/docs/design.html" class="sidebar-link">设计原理</a></li></ul></div><div class="page"><div class="content"><h1 id="更新历史"><a href="#更新历史" aria-hidden="true" class="header-anchor">#</a> 更新历史</h1><ul><li>2020/2/20
|
||||
完成实例管理器</li><li>2020/1/27
|
||||
完成核心架构</li></ul></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
|
||||
← <a href="/docs/develop.html" class="prev">
|
||||
插件开发
|
||||
</a></span><span class="next"><a href="/docs/plugins.html">
|
||||
内置插件
|
||||
</a> →
|
||||
</span></p></div></div></div></div>
|
||||
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/3.7646e76c.js" defer></script>
|
||||
<script src="/docs/assets/js/app.1fc3f87c.js" defer></script><script src="/docs/assets/js/4.2a48a234.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
18
dashboard/dist/docs/index.html
vendored
18
dashboard/dist/docs/index.html
vendored
@@ -7,16 +7,20 @@
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/1.6babbc1d.js" as="script"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js"><link rel="prefetch" href="/docs/assets/js/4.08fbc0d9.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
|
||||
<link rel="preload" href="/docs/assets/css/styles.1fc3f87c.css" as="style"><link rel="preload" href="/docs/assets/js/app.1fc3f87c.js" as="script"><link rel="preload" href="/docs/assets/js/1.05f88c5b.js" as="script"><link rel="prefetch" href="/docs/assets/js/2.69b20946.js"><link rel="prefetch" href="/docs/assets/js/3.197b5253.js"><link rel="prefetch" href="/docs/assets/js/4.2a48a234.js"><link rel="prefetch" href="/docs/assets/js/5.bd73b45e.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.1fc3f87c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-exact-active router-link-active"><!----><span class="site-name">
|
||||
Monibuca
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="active sidebar-link">起步</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/#介绍" class="sidebar-link">介绍</a></li><li class="sidebar-sub-header"><a href="/docs/#启动" class="sidebar-link">启动</a></li><li class="sidebar-sub-header"><a href="/docs/#配置" class="sidebar-link">配置</a></li></ul></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li></ul></div><div class="page"><div class="content"><h1 id="monibuca快速起步"><a href="#monibuca快速起步" aria-hidden="true" class="header-anchor">#</a> Monibuca快速起步</h1><h2 id="介绍"><a href="#介绍" aria-hidden="true" class="header-anchor">#</a> 介绍</h2><p>Monibuca 是一个开源的流媒体服务器开发框架,适用于快速定制化开发流媒体服务器,可以对接CDN厂商,作为回源服务器,也可以自己搭建集群部署环境。
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="active sidebar-link">起步</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/#介绍" class="sidebar-link">介绍</a></li><li class="sidebar-sub-header"><a href="/docs/#使用实例管理器启动实例" class="sidebar-link">使用实例管理器启动实例</a></li><li class="sidebar-sub-header"><a href="/docs/#实例目录说明" class="sidebar-link">实例目录说明</a></li></ul></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="sidebar-link">内置插件</a></li><li><a href="/docs/design.html" class="sidebar-link">设计原理</a></li></ul></div><div class="page"><div class="content"><h1 id="monibuca快速起步"><a href="#monibuca快速起步" aria-hidden="true" class="header-anchor">#</a> Monibuca快速起步</h1><h2 id="介绍"><a href="#介绍" aria-hidden="true" class="header-anchor">#</a> 介绍</h2><p>Monibuca 是一个开源的流媒体服务器开发框架,适用于快速定制化开发流媒体服务器,可以对接CDN厂商,作为回源服务器,也可以自己搭建集群部署环境。
|
||||
丰富的内置插件提供了流媒体服务器的常见功能,例如rtmp server、http-flv、视频录制、QoS等。除此以外还内置了后台web界面,方便观察服务器运行的状态。
|
||||
也可以自己开发后台管理界面,通过api方式获取服务器的运行信息。
|
||||
Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其功能。</p><h2 id="启动"><a href="#启动" aria-hidden="true" class="header-anchor">#</a> 启动</h2><p>启用所有内置插件</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> main
|
||||
Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其功能。</p><h2 id="使用实例管理器启动实例"><a href="#使用实例管理器启动实例" aria-hidden="true" class="header-anchor">#</a> 使用实例管理器启动实例</h2><h3 id="step0-配置golang环境"><a href="#step0-配置golang环境" aria-hidden="true" class="header-anchor">#</a> step0 配置golang环境</h3><p>将GOPATH的bin目录加入环境变量PATH中,这样可以快速启动Monibuca实例管理器</p><h3 id="step1-安装monibuca"><a href="#step1-安装monibuca" aria-hidden="true" class="header-anchor">#</a> step1 安装Monibuca</h3><div class="language-bash extra-class"><pre class="language-bash"><code>go get github.com/langhuihui/monibuca
|
||||
</code></pre></div><p>安装完成后会在GOPATH的bin目录下生成monibuca可执行文件</p><h3 id="step2-启动monibuca实例管理器"><a href="#step2-启动monibuca实例管理器" aria-hidden="true" class="header-anchor">#</a> step2 启动monibuca实例管理器</h3><p>如果GOPATH的bin目录已经加入PATH环境变量,则可以直接执行</p><div class="language-bash extra-class"><pre class="language-bash"><code>monibuca
|
||||
</code></pre></div><p>程序默认监听8000端口,你也可以带上参数指定启动的端口</p><div class="language-bash extra-class"><pre class="language-bash"><code>monibuca -port <span class="token number">8001</span>
|
||||
</code></pre></div><h3 id="step3-创建实例"><a href="#step3-创建实例" aria-hidden="true" class="header-anchor">#</a> step3 创建实例</h3><p>浏览器打开上面的端口地址,出现实例管理器页面,点击创建标签页,按照提示选择实例放置的目录和插件,进行创建。
|
||||
完成后会在所在目录创建若干文件并运行该golang项目,如果选择了网关插件,则可以在该插件配置的端口下看到控制台页面。</p><h2 id="实例目录说明"><a href="#实例目录说明" aria-hidden="true" class="header-anchor">#</a> 实例目录说明</h2><ol><li>main.go</li><li>config.toml</li><li>restart.sh</li></ol><h3 id="main-go"><a href="#main-go" aria-hidden="true" class="header-anchor">#</a> main.go</h3><p>实例启动的主文件,初始化各类插件,然后调用配置文件启动引擎</p><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">package</span> main
|
||||
|
||||
<span class="token keyword">import</span> <span class="token punctuation">(</span>
|
||||
<span class="token punctuation">.</span> <span class="token string">"github.com/langhuihui/monibuca/monica"</span>
|
||||
@@ -27,7 +31,7 @@ Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其
|
||||
<span class="token function">Run</span><span class="token punctuation">(</span><span class="token string">"config.toml"</span><span class="token punctuation">)</span>
|
||||
<span class="token keyword">select</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre></div><h2 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h2><p>要使用<code>Monibuca</code>,需要编写一个<code>toml</code>格式的配置文件,通常可以放在程序的同级目录下例如:<code>config.toml</code>(名称不是必须为<code>config</code>)</p><p>该配置文件主要是为了定制各个插件的配置,例如监听端口号等,具体还是要看各个插件的设计。</p><blockquote><p>如果你编写了自己的插件,就必须在该配置文件中写入对自己插件的配置信息</p></blockquote><p>如果注释掉部分插件的配置,那么该插件就不会启用,典型的配置如下:</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
|
||||
</code></pre></div><p>可以修改该主文件,添加任意功能</p><h3 id="config-toml"><a href="#config-toml" aria-hidden="true" class="header-anchor">#</a> config.toml</h3><p>该配置文件主要是为了定制各个插件的配置,例如监听端口号等,具体还是要看各个插件的设计。</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>如果你编写了自己的插件,就必须在该配置文件中写入对自己插件的配置信息</p></div><p>如果注释掉部分插件的配置,那么该插件就不会启用,典型的配置如下:</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":2020"</span>
|
||||
<span class="token punctuation">[</span><span class="token table class-name">Plugins.Jessica</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":8080"</span>
|
||||
@@ -45,10 +49,10 @@ Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其
|
||||
<span class="token comment">#Path="./resouce"</span>
|
||||
<span class="token punctuation">[</span><span class="token table class-name">Plugins.QoS</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">Suffix</span> <span class="token punctuation">=</span> <span class="token punctuation">[</span><span class="token string">"high"</span><span class="token punctuation">,</span><span class="token string">"medium"</span><span class="token punctuation">,</span><span class="token string">"low"</span><span class="token punctuation">]</span>
|
||||
</code></pre></div><p>具体配置的含义,可以参考每个插件的说明</p></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><!----><span class="next"><a href="/docs/develop.html">
|
||||
</code></pre></div><p>具体配置的含义,可以参考每个插件的说明</p><h3 id="restart-sh"><a href="#restart-sh" aria-hidden="true" class="header-anchor">#</a> restart.sh</h3><p>该文件是一个用来重启实例的bash脚本,方便通过实例管理器重启,或者手工重启。</p></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><!----><span class="next"><a href="/docs/develop.html">
|
||||
插件开发
|
||||
</a> →
|
||||
</span></p></div></div></div></div>
|
||||
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/1.6babbc1d.js" defer></script>
|
||||
<script src="/docs/assets/js/app.1fc3f87c.js" defer></script><script src="/docs/assets/js/1.05f88c5b.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
37
dashboard/dist/docs/plugins.html
vendored
37
dashboard/dist/docs/plugins.html
vendored
@@ -7,38 +7,47 @@
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/docs/assets/css/styles.d51c815b.css" as="style"><link rel="preload" href="/docs/assets/js/app.d51c815b.js" as="script"><link rel="preload" href="/docs/assets/js/4.08fbc0d9.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.6babbc1d.js"><link rel="prefetch" href="/docs/assets/js/2.190ec46a.js"><link rel="prefetch" href="/docs/assets/js/3.7646e76c.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.d51c815b.css">
|
||||
<link rel="preload" href="/docs/assets/css/styles.1fc3f87c.css" as="style"><link rel="preload" href="/docs/assets/js/app.1fc3f87c.js" as="script"><link rel="preload" href="/docs/assets/js/5.bd73b45e.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.05f88c5b.js"><link rel="prefetch" href="/docs/assets/js/2.69b20946.js"><link rel="prefetch" href="/docs/assets/js/3.197b5253.js"><link rel="prefetch" href="/docs/assets/js/4.2a48a234.js">
|
||||
<link rel="stylesheet" href="/docs/assets/css/styles.1fc3f87c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/docs/" class="home-link router-link-active"><!----><span class="site-name">
|
||||
Monibuca
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="active sidebar-link">内置插件</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/plugins.html#jessica插件" class="sidebar-link">Jessica插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#rtmp插件" class="sidebar-link">Rtmp插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#recordflv插件" class="sidebar-link">RecordFlv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#http-flv插件" class="sidebar-link">Http-Flv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#cluster插件" class="sidebar-link">Cluster插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#hls插件" class="sidebar-link">HLS插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#网关插件" class="sidebar-link">网关插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#校验插件" class="sidebar-link">校验插件</a></li></ul></li></ul></div><div class="page"><div class="content"><h1 id="内置插件介绍"><a href="#内置插件介绍" aria-hidden="true" class="header-anchor">#</a> 内置插件介绍</h1><p>内置插件为Monibuca提供了许多基础功能,当然你完全可以不采用内置插件,而改用自己开发的插件,也丝毫不会影响您使用Monibuca。</p><h2 id="jessica插件"><a href="#jessica插件" aria-hidden="true" class="header-anchor">#</a> Jessica插件</h2><blockquote><p>该插件源码位于plugins/jessica下</p></blockquote><p>该插件为基于WebSocket协议传输音视频的订阅者,音视频数据以裸数据的形式进行传输,我们需要Jessibuca播放器来进行播放
|
||||
</span></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><ul class="sidebar-links"><li><a href="/docs/" class="sidebar-link">起步</a></li><li><a href="/docs/develop.html" class="sidebar-link">插件开发</a></li><li><a href="/docs/history.html" class="sidebar-link">更新日志</a></li><li><a href="/docs/plugins.html" class="active sidebar-link">内置插件</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/plugins.html#网关插件" class="sidebar-link">网关插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#日志分割插件" class="sidebar-link">日志分割插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#jessica插件" class="sidebar-link">Jessica插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#rtmp插件" class="sidebar-link">Rtmp插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#recordflv插件" class="sidebar-link">RecordFlv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#http-flv插件" class="sidebar-link">Http-Flv插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#cluster插件" class="sidebar-link">Cluster插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#hls插件" class="sidebar-link">HLS插件</a></li><li class="sidebar-sub-header"><a href="/docs/plugins.html#校验插件" class="sidebar-link">校验插件</a></li></ul></li><li><a href="/docs/design.html" class="sidebar-link">设计原理</a></li></ul></div><div class="page"><div class="content"><h1 id="内置插件介绍"><a href="#内置插件介绍" aria-hidden="true" class="header-anchor">#</a> 内置插件介绍</h1><p>内置插件为Monibuca提供了许多基础功能,当然你完全可以不采用内置插件,而改用自己开发的插件,也丝毫不会影响您使用Monibuca。</p><h2 id="网关插件"><a href="#网关插件" aria-hidden="true" class="header-anchor">#</a> 网关插件</h2><div class="tip custom-block"><p class="custom-block-title">源码位置</p><p>该插件位于plugins/gateway下</p></div><p>该插件是为web控制台界面提供api,用来采集服务器的信息。</p><h3 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.GateWay</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":80"</span>
|
||||
</code></pre></div><p>如果80端口有其他用途,可以换成别的端口,比如有nginx反向代理。</p><h2 id="日志分割插件"><a href="#日志分割插件" aria-hidden="true" class="header-anchor">#</a> 日志分割插件</h2><div class="tip custom-block"><p class="custom-block-title">源码位置</p><p>该插件源码位于plugins/logrotate下</p></div><h3 id="配置-2"><a href="#配置-2" aria-hidden="true" class="header-anchor">#</a> 配置</h3><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.LogRotate</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">Path</span> <span class="token punctuation">=</span> <span class="token string">"log"</span>
|
||||
<span class="token key property">Size</span> <span class="token punctuation">=</span> <span class="token number">0</span>
|
||||
<span class="token key property">Days</span> <span class="token punctuation">=</span> <span class="token number">1</span>
|
||||
</code></pre></div><p>其中Path代表生成日志的目录
|
||||
Size代表按大小分割,单位是字节,如果为0,则按时间分割
|
||||
Days代表按时间分割,单位是天,即24小时</p><h2 id="jessica插件"><a href="#jessica插件" aria-hidden="true" class="header-anchor">#</a> Jessica插件</h2><div class="tip custom-block"><p class="custom-block-title">源码位置</p><p>该插件源码位于plugins/jessica下</p></div><p>该插件为基于WebSocket协议传输音视频的订阅者,音视频数据以裸数据的形式进行传输,我们需要Jessibuca播放器来进行播放
|
||||
Jessibua播放器已内置于源码中,该播放器通过js解码H264/H265并用canvas进行渲染,可以运行在几乎所有的终端浏览器上面。
|
||||
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。</p><h3 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Jessica</span><span class="token punctuation">]</span>
|
||||
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。</p><h3 id="配置-3"><a href="#配置-3" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Jessica</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":8080"</span>
|
||||
</code></pre></div><h3 id="flv格式支持"><a href="#flv格式支持" aria-hidden="true" class="header-anchor">#</a> Flv格式支持</h3><p>Jessica以及Jessibuca也支持采用WebSocket中传输Flv格式的方式进行通讯,目前有部分CDN厂商已经支持这种方式进行传输。</p><blockquote><p>私有协议以及Flv格式的判断是通过URL后缀是否带有.flv来进行判断</p></blockquote><h2 id="rtmp插件"><a href="#rtmp插件" aria-hidden="true" class="header-anchor">#</a> Rtmp插件</h2><blockquote><p>该插件源码位于plugins/rtmp下</p></blockquote><p>实现了基本的rtmp传输协议,包括接收来自OBS、ffmpeg等软件的推流,以及来在Flash Player播放器的拉流。</p><h3 id="配置-2"><a href="#配置-2" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RTMP</span><span class="token punctuation">]</span>
|
||||
</code></pre></div><h3 id="flv格式支持"><a href="#flv格式支持" aria-hidden="true" class="header-anchor">#</a> Flv格式支持</h3><p>Jessica以及Jessibuca也支持采用WebSocket中传输Flv格式的方式进行通讯,目前有部分CDN厂商已经支持这种方式进行传输。</p><blockquote><p>私有协议以及Flv格式的判断是通过URL后缀是否带有.flv来进行判断</p></blockquote><h2 id="rtmp插件"><a href="#rtmp插件" aria-hidden="true" class="header-anchor">#</a> Rtmp插件</h2><blockquote><p>该插件源码位于plugins/rtmp下</p></blockquote><p>实现了基本的rtmp传输协议,包括接收来自OBS、ffmpeg等软件的推流,以及来在Flash Player播放器的拉流。</p><h3 id="配置-4"><a href="#配置-4" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RTMP</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":1935"</span>
|
||||
</code></pre></div><h2 id="recordflv插件"><a href="#recordflv插件" aria-hidden="true" class="header-anchor">#</a> RecordFlv插件</h2><blockquote><p>该插件源码位于plugins/record下</p></blockquote><p>实现了录制Flv文件的功能,并且支持再次使用录制好的Flv文件作为发布者进行发布。在Monibuca的web界面的控制台中提供了对房间进行录制的操作按钮,以及列出所有已经录制的文件的界面。</p><h3 id="配置-3"><a href="#配置-3" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>配置中的Path 表示要保存的Flv文件的根路径,可以使用相对路径或者绝对路径</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RecordFlv</span><span class="token punctuation">]</span>
|
||||
</code></pre></div><h2 id="recordflv插件"><a href="#recordflv插件" aria-hidden="true" class="header-anchor">#</a> RecordFlv插件</h2><blockquote><p>该插件源码位于plugins/record下</p></blockquote><p>实现了录制Flv文件的功能,并且支持再次使用录制好的Flv文件作为发布者进行发布。在Monibuca的web界面的控制台中提供了对房间进行录制的操作按钮,以及列出所有已经录制的文件的界面。</p><h3 id="配置-5"><a href="#配置-5" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>配置中的Path 表示要保存的Flv文件的根路径,可以使用相对路径或者绝对路径</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.RecordFlv</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">Path</span><span class="token punctuation">=</span><span class="token string">"./resource"</span>
|
||||
</code></pre></div><h2 id="http-flv插件"><a href="#http-flv插件" aria-hidden="true" class="header-anchor">#</a> Http-Flv插件</h2><blockquote><p>该插件位于plugins/HDL下</p></blockquote><p>实现了http-flv格式的拉流功能,方便对接CDN厂商</p><h3 id="配置-4"><a href="#配置-4" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
|
||||
</code></pre></div><h2 id="http-flv插件"><a href="#http-flv插件" aria-hidden="true" class="header-anchor">#</a> Http-Flv插件</h2><blockquote><p>该插件位于plugins/HDL下</p></blockquote><p>实现了http-flv格式的拉流功能,方便对接CDN厂商</p><h3 id="配置-6"><a href="#配置-6" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.HDL</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":2020"</span>
|
||||
</code></pre></div><h2 id="cluster插件"><a href="#cluster插件" aria-hidden="true" class="header-anchor">#</a> Cluster插件</h2><blockquote><p>该插件源码位于plugins/cluster下</p></blockquote><p>实现了基本的集群功能,里面包含一对发布者和订阅者,分别在主从服务器中启用,进行连接。
|
||||
起基本原理就是,在主服务器启动端口监听,从服务器收到播放请求时,如果从服务器没有对应的发布者,则向主服务器发起请求,主服务器收到来自从服务器的请求时,将该请求作为一个订阅者。从服务器则把tcp连接作为发布者,实现视频流的传递过程。</p><h3 id="配置-5"><a href="#配置-5" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>主服务器的配置是ListenAddr,用来监听从服务器的请求。
|
||||
起基本原理就是,在主服务器启动端口监听,从服务器收到播放请求时,如果从服务器没有对应的发布者,则向主服务器发起请求,主服务器收到来自从服务器的请求时,将该请求作为一个订阅者。从服务器则把tcp连接作为发布者,实现视频流的传递过程。</p><h3 id="配置-7"><a href="#配置-7" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>主服务器的配置是ListenAddr,用来监听从服务器的请求。
|
||||
从服务器的配置是Master,表示主服务器的地址。
|
||||
当然服务器可以既是主也是从,即充当中转站。</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Cluster</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">Master</span> <span class="token punctuation">=</span> <span class="token string">"localhost:2019"</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":2019"</span>
|
||||
</code></pre></div><h2 id="hls插件"><a href="#hls插件" aria-hidden="true" class="header-anchor">#</a> HLS插件</h2><blockquote><p>该插件源码位于plugins/HLS下</p></blockquote><p>该插件的作用是请求M3u8文件进行解码,最终将TS视频流转码成裸的视频流进行发布。
|
||||
注意:该插件目前并没有实现生成HLS的功能。</p><h2 id="网关插件"><a href="#网关插件" aria-hidden="true" class="header-anchor">#</a> 网关插件</h2><blockquote><p>该插件位于plugins/gateway下</p></blockquote><p>该插件是为web控制台界面提供api,用来采集服务器的信息。</p><h3 id="配置-6"><a href="#配置-6" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>目前仅有的配置是监听的端口号</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.GateWay</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">":80"</span>
|
||||
</code></pre></div><p>如果80端口有其他用途,可以换成别的端口,比如有nginx反向代理。</p><h2 id="校验插件"><a href="#校验插件" aria-hidden="true" class="header-anchor">#</a> 校验插件</h2><blockquote><p>该插件位于plugins/auth下</p></blockquote><p>该插件提供了基本的验证功能,其原理是
|
||||
订阅流提供一个签名,签名只可以使用一次,把签名进行AES CBC 解密,如果得到的解密字符串的前面部分就是和Key相同则通过验证。</p><h3 id="配置-7"><a href="#配置-7" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>Key代表用来加密的Key</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Auth</span><span class="token punctuation">]</span>
|
||||
注意:该插件目前并没有实现生成HLS的功能。</p><h2 id="校验插件"><a href="#校验插件" aria-hidden="true" class="header-anchor">#</a> 校验插件</h2><blockquote><p>该插件位于plugins/auth下</p></blockquote><p>该插件提供了基本的验证功能,其原理是
|
||||
订阅流提供一个签名,签名只可以使用一次,把签名进行AES CBC 解密,如果得到的解密字符串的前面部分就是和Key相同则通过验证。</p><h3 id="配置-8"><a href="#配置-8" aria-hidden="true" class="header-anchor">#</a> 配置</h3><p>Key代表用来加密的Key</p><div class="language-toml extra-class"><pre class="language-toml"><code><span class="token punctuation">[</span><span class="token table class-name">Plugins.Auth</span><span class="token punctuation">]</span>
|
||||
<span class="token key property">Key</span><span class="token punctuation">=</span><span class="token string">"www.monibuca.com"</span>
|
||||
</code></pre></div></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
|
||||
← <a href="/docs/history.html" class="prev">
|
||||
更新日志
|
||||
</a></span><!----></p></div></div></div></div>
|
||||
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/4.08fbc0d9.js" defer></script>
|
||||
</a></span><span class="next"><a href="/docs/design.html">
|
||||
设计原理
|
||||
</a> →
|
||||
</span></p></div></div></div></div>
|
||||
<script src="/docs/assets/js/app.1fc3f87c.js" defer></script><script src="/docs/assets/js/5.bd73b45e.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
40
dashboard/dist/docs/service-worker.js
vendored
40
dashboard/dist/docs/service-worker.js
vendored
@@ -21,10 +21,10 @@ importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox
|
||||
self.__precacheManifest = [
|
||||
{
|
||||
"url": "404.html",
|
||||
"revision": "75925fb89348802ecd737cdad2d5d801"
|
||||
"revision": "deb4e5a58824b45354e458242cfc5f68"
|
||||
},
|
||||
{
|
||||
"url": "assets/css/styles.d51c815b.css",
|
||||
"url": "assets/css/styles.1fc3f87c.css",
|
||||
"revision": "4a6b650244e5b709f84a81ad0565b485"
|
||||
},
|
||||
{
|
||||
@@ -32,40 +32,48 @@ self.__precacheManifest = [
|
||||
"revision": "83621669651b9a3d4bf64d1a670ad856"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/1.6babbc1d.js",
|
||||
"revision": "0142763a4e8630af56b66f5fa8320c5c"
|
||||
"url": "assets/js/1.05f88c5b.js",
|
||||
"revision": "afa91e5980d9ef9c164df65dfcc212f3"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/2.190ec46a.js",
|
||||
"revision": "8dceb01ded85f36cc30e0e18371fe5d4"
|
||||
"url": "assets/js/2.69b20946.js",
|
||||
"revision": "e49c74ff572f38586aa8d2d0295f6d67"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/3.7646e76c.js",
|
||||
"revision": "378c41587710afd31466293c83a6c738"
|
||||
"url": "assets/js/3.197b5253.js",
|
||||
"revision": "68528a936ba6abcb203ebf1e3b779f7b"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/4.08fbc0d9.js",
|
||||
"revision": "0ea387538f5b25ef46237e3dcc1c1694"
|
||||
"url": "assets/js/4.2a48a234.js",
|
||||
"revision": "84b7fc95074e5673dc9cbac5e43ac54b"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/app.d51c815b.js",
|
||||
"revision": "467373b055c3575c2082a1b9ab1769fd"
|
||||
"url": "assets/js/5.bd73b45e.js",
|
||||
"revision": "9e29fc1b0c76fdeee1a9b6073856ca62"
|
||||
},
|
||||
{
|
||||
"url": "assets/js/app.1fc3f87c.js",
|
||||
"revision": "3433613b6d3d61b3b5b1472588698b88"
|
||||
},
|
||||
{
|
||||
"url": "design.html",
|
||||
"revision": "d3141cee964d685ecdd1115018ebb5a5"
|
||||
},
|
||||
{
|
||||
"url": "develop.html",
|
||||
"revision": "92b13eb27581e4dc7008cf4205e5c215"
|
||||
"revision": "41466536941a8fefa23255ec78f400e2"
|
||||
},
|
||||
{
|
||||
"url": "history.html",
|
||||
"revision": "ef202ac3bf63e1bdef479a05d49d5a8d"
|
||||
"revision": "a30a702553aeb8c3654a7b51a26591de"
|
||||
},
|
||||
{
|
||||
"url": "index.html",
|
||||
"revision": "306f1a0a8ceadb0c85dae2342f0bd637"
|
||||
"revision": "e1a828d24054341e2439ac0a59d1f673"
|
||||
},
|
||||
{
|
||||
"url": "plugins.html",
|
||||
"revision": "ac2ce38679fb75f05e8e501f35d161bd"
|
||||
"revision": "20cdd78205e1bd15b6f5f0ad1b203ab0"
|
||||
}
|
||||
].concat(self.__precacheManifest || []);
|
||||
workbox.precaching.suppressWarnings();
|
||||
|
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.ce470878.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.017fb959.js rel=preload as=script><link href=/js/chunk-vendors.ebc28a73.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.ce470878.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.ebc28a73.js></script><script src=/js/app.017fb959.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.ce470878.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.16c0d7c9.js rel=preload as=script><link href=/js/chunk-vendors.ebc28a73.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.ce470878.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.ebc28a73.js></script><script src=/js/app.16c0d7c9.js></script></body></html>
|
19
dashboard/dist/jessibuca/renderer.js
vendored
19
dashboard/dist/jessibuca/renderer.js
vendored
@@ -1,4 +1,6 @@
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function Jessibuca(opt) {
|
||||
this.audioContext = new window.AudioContext()
|
||||
this.canvasElement = opt.canvas;
|
||||
this.contextOptions = opt.contextOptions;
|
||||
this.videoBuffer = opt.videoBuffer || 1
|
||||
@@ -65,9 +67,7 @@ function Jessibuca(opt) {
|
||||
}
|
||||
}
|
||||
};
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function _unlock() {
|
||||
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
|
||||
function _unlock(context) {
|
||||
context.resume();
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = context.createBuffer(1, 1, 22050);
|
||||
@@ -81,7 +81,7 @@ function _unlock() {
|
||||
// document.addEventListener("touchend", _unlock, true);
|
||||
Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
_unlock()
|
||||
_unlock(this.audioContext)
|
||||
this.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
this.audioContext.resume();
|
||||
@@ -89,6 +89,8 @@ Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
}
|
||||
Jessibuca.prototype.playAudio = function (data) {
|
||||
@@ -150,6 +152,7 @@ Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels)
|
||||
if (!context) return false;
|
||||
var resampled = samplerate < 22050;
|
||||
var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate);
|
||||
var _this = this
|
||||
var playNextBuffer = function () {
|
||||
isPlaying = false;
|
||||
console.log("~", audioBuffers.length)
|
||||
@@ -189,9 +192,12 @@ Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels)
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(context.destination);
|
||||
source.onended = playNextBuffer;
|
||||
//setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
|
||||
// source.onended = playNextBuffer;
|
||||
// setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
|
||||
source.start();
|
||||
if (!_this.audioInterval) {
|
||||
_this.audioInterval = setInterval(playNextBuffer, audioBuffer.duration * 1000 - 1);
|
||||
}
|
||||
};
|
||||
this.playAudio = playAudio;
|
||||
}
|
||||
@@ -452,6 +458,7 @@ Jessibuca.prototype.close = function () {
|
||||
if (this.audioInterval) {
|
||||
clearInterval(this.audioInterval)
|
||||
}
|
||||
delete this.playAudio
|
||||
this.decoderWorker.postMessage({ cmd: "close" })
|
||||
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
2
dashboard/dist/js/app.017fb959.js
vendored
2
dashboard/dist/js/app.017fb959.js
vendored
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.017fb959.js.map
vendored
1
dashboard/dist/js/app.017fb959.js.map
vendored
File diff suppressed because one or more lines are too long
2
dashboard/dist/js/app.16c0d7c9.js
vendored
Normal file
2
dashboard/dist/js/app.16c0d7c9.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dashboard/dist/js/app.16c0d7c9.js.map
vendored
Normal file
1
dashboard/dist/js/app.16c0d7c9.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -6,7 +6,8 @@ module.exports = {
|
||||
['/', '起步'],
|
||||
['/develop', '插件开发'],
|
||||
['/history', '更新日志'],
|
||||
['/plugins', '内置插件']
|
||||
['/plugins', '内置插件'],
|
||||
['/design', '设计原理']
|
||||
]
|
||||
},
|
||||
title: 'Monibuca',
|
||||
|
@@ -10,7 +10,7 @@ import upperFirst from 'lodash/upperFirst'
|
||||
|
||||
const requireComponent = require.context(
|
||||
// The relative path of the components folder
|
||||
'../../src/components',
|
||||
'../',
|
||||
// Whether or not to look in subfolders
|
||||
true,
|
||||
// The regular expression used to match base component filenames
|
||||
|
@@ -5,8 +5,39 @@ Monibuca 是一个开源的流媒体服务器开发框架,适用于快速定
|
||||
也可以自己开发后台管理界面,通过api方式获取服务器的运行信息。
|
||||
Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其功能。
|
||||
|
||||
## 启动
|
||||
启用所有内置插件
|
||||
## 使用实例管理器启动实例
|
||||
|
||||
### step0 配置golang环境
|
||||
|
||||
将GOPATH的bin目录加入环境变量PATH中,这样可以快速启动Monibuca实例管理器
|
||||
|
||||
### step1 安装Monibuca
|
||||
```bash
|
||||
go get github.com/langhuihui/monibuca
|
||||
```
|
||||
安装完成后会在GOPATH的bin目录下生成monibuca可执行文件
|
||||
|
||||
### step2 启动monibuca实例管理器
|
||||
如果GOPATH的bin目录已经加入PATH环境变量,则可以直接执行
|
||||
```bash
|
||||
monibuca
|
||||
```
|
||||
程序默认监听8000端口,你也可以带上参数指定启动的端口
|
||||
```bash
|
||||
monibuca -port 8001
|
||||
```
|
||||
### step3 创建实例
|
||||
浏览器打开上面的端口地址,出现实例管理器页面,点击创建标签页,按照提示选择实例放置的目录和插件,进行创建。
|
||||
完成后会在所在目录创建若干文件并运行该golang项目,如果选择了网关插件,则可以在该插件配置的端口下看到控制台页面。
|
||||
|
||||
## 实例目录说明
|
||||
|
||||
1. main.go
|
||||
2. config.toml
|
||||
3. restart.sh
|
||||
|
||||
### main.go
|
||||
实例启动的主文件,初始化各类插件,然后调用配置文件启动引擎
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -20,10 +51,9 @@ func main() {
|
||||
select {}
|
||||
}
|
||||
```
|
||||
可以修改该主文件,添加任意功能
|
||||
|
||||
## 配置
|
||||
|
||||
要使用`Monibuca`,需要编写一个`toml`格式的配置文件,通常可以放在程序的同级目录下例如:`config.toml`(名称不是必须为`config`)
|
||||
### config.toml
|
||||
|
||||
该配置文件主要是为了定制各个插件的配置,例如监听端口号等,具体还是要看各个插件的设计。
|
||||
|
||||
@@ -52,4 +82,7 @@ ListenAddr = ":81"
|
||||
[Plugins.QoS]
|
||||
Suffix = ["high","medium","low"]
|
||||
```
|
||||
具体配置的含义,可以参考每个插件的说明
|
||||
具体配置的含义,可以参考每个插件的说明
|
||||
|
||||
### restart.sh
|
||||
该文件是一个用来重启实例的bash脚本,方便通过实例管理器重启,或者手工重启。
|
@@ -1,4 +1,4 @@
|
||||
# Monibuca设计思想
|
||||
# Monibuca设计原理
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -16,7 +16,47 @@
|
||||
### 受到vue渐进式思想的影响
|
||||
vue渐进式框架的设计思想非常棒,那么是否可以用来设计流媒体服务器,使得流媒体服务器不只是一个服务器,而是一个开发框架,让开发者可以定制化自己的流媒体服务器呢?答案是肯定的。当然我们需要更多的抽象。
|
||||
|
||||
## 流媒体服务器的核心
|
||||
## 如何实现可扩展——插件化
|
||||
许多IDE和编辑器都依靠插件化技术得以拓展其功能,并形成其生态,例如vs、vs code、eclipse、jetbrains系列,当然vue作为一个前端框架也是设计了很不错的插件机制。这些都可以作为借鉴。
|
||||
|
||||
要实现流媒体服务器的插件化,就需要把核心功能和拓展功能分离,进行足够的抽象。
|
||||
|
||||
### 三大抽象概念
|
||||
1. 发布者(Publisher)
|
||||
2. 订阅者(Subscriber)
|
||||
3. 房间(Room)
|
||||
|
||||
#### 发布者(Publisher)
|
||||
发布者本质上就是输入流,其抽象行为就是将音频和视频数据压入**房间**中,换句话说,就是在恰当的时候调用**房间**的PushVideo和PushAudio函数
|
||||
::: tip 源码位置
|
||||
发布者定义位于monica/publisher.go中
|
||||
:::
|
||||
在发布者的定义中有一个**InputStream**的结构体,用来和**房间**进行互操作。
|
||||
所有具体的发布者都应该包含这个**InputStream**,以组合继承的方式成为发布者。
|
||||
该**InputStream**包含最核心功能就是Publish函数,这个函数的功能就是在**房间**里面设置发布者是自己,这个行为就是发布。形象的理解就是主播走进了房间。
|
||||
引擎不关心是谁走进了房间,也不关心进来的人会发布什么内容。
|
||||
::: tip 发布者插件
|
||||
所有实现了发布者具体功能的插件,就是发布者插件,这样一来,流媒体的媒体源可以是任意的形式,比如RTMP协议提供的推流,可以由FFMPEG、OBS发布。也可以是读取本地磁盘上的媒体文件,也可以来自源服务器的私有协议传输的内容。
|
||||
:::
|
||||
#### 订阅者(Subscriber)
|
||||
订阅者就是输出流,其抽象行为就是被动接收来自**房间**的音频和视频数据。
|
||||
::: tip 源码位置
|
||||
订阅者定义位于monica/subscriber.go中
|
||||
:::
|
||||
订阅者有两个函数sendVideo和sendAudio用于接收音频和视频数据。这个两个函数会对音视频做一些预处理,主要是实现丢包机制、时间戳和首屏渲染。具体的视频数据会共享读取。
|
||||
然后调用SendHandler将打包好的音视频数据发送到具体的订阅者那里。
|
||||
::: tip 订阅者插件
|
||||
订阅者插件,本质上就是SendHandler函数。具体可以将打包的数据以何种协议输出,还是写入文件,由插件实现。
|
||||
:::
|
||||
|
||||
#### 房间(Room)
|
||||
房间就是一个连接发布者和订阅者的地方。可以形象的理解为主播的房间,发布者是主播,订阅者就是粉丝观众。房间是引擎的核心,其重要逻辑包括:
|
||||
1. 房间的创建、查询、关闭
|
||||
2. 订阅者的加入和移除
|
||||
3. 发布者的进入和离开。
|
||||
::: tip 源码位置
|
||||
订阅者定义位于monica/room.go中
|
||||
:::
|
||||
|
||||
流媒体服务器的核心是**转发**二字。当你去研究一款流媒体服务器的时候,会有海量的代码阻碍你看清其核心逻辑。包括:
|
||||
|
||||
@@ -31,13 +71,21 @@ for _, v := range r.Subscribers {
|
||||
}
|
||||
```
|
||||
其他都是围绕这个for循环展开。所有的流媒体服务器代码里面都有这个for循环,写法稍有不同,但本质相同。
|
||||
::: tip 源码位置
|
||||
该核心逻辑位于monica/room.go中的Run函数内
|
||||
:::
|
||||
|
||||
### 核心概念
|
||||
## 如何实现高性能
|
||||
流媒体服务器对性能要求极为苛刻。因为流媒体服务器属于高速系统,会有并发的长连接请求,协议封包解包和音视频格式的编解码都消耗着CPU以及内存,如何尽可能的减少消耗是必须考虑的问题。
|
||||
|
||||
### 内存使用
|
||||
池化是一个不错的选择,所以尽量池化,在Monibuca中对`[]byte`类型,采用了[github.com/funny/slab](https://github.com/funny/slab)包来管理。其他结构体就用系统自带的pool包来池化对象。
|
||||
|
||||
基于这个循环,我们需要思考两个问题:
|
||||
1. 如何高效的循环(性能问题)
|
||||
2. 如何对接不同的协议(扩展性)
|
||||
|
||||
第一个问题,golang的性能算是很好的,那么重点就在于减少内存的分配上,池化是一个不错的选择,所以尽量池化,在Monibuca中对`[]byte`类型,采用了[github.com/funny/slab](https://github.com/funny/slab)包来管理。其他结构体就用系统自带的pool包来池化对象。对于协程的使用,在多次迭代后,已经使用了最少的协程来支持并发性。
|
||||
### 协程的使用
|
||||
golang自带的goroutine可以有效的减少线程的使用,并可以支持各种异步并发的情况。合理的创建goroutine很重要,这样才能尽可能高效利用CPU时间。
|
||||
在monibuca中,创建goroutine在如下场景中:
|
||||
1. 通讯协议建立的长连接对于一个goroutine
|
||||
2. 每个房间拥有一个goroutine用于接收指令和转发音视频数据
|
||||
3. 每一个插件会使用一个goroutine来执行插件的Run函数
|
||||
|
||||
由于引擎本身比较轻量化,更多的性能的优化需要插件提供者自由发挥了。
|
@@ -1,4 +1,5 @@
|
||||
# 更新历史
|
||||
|
||||
- 2020/2/20
|
||||
完成实例管理器
|
||||
- 2020/1/27
|
||||
初步完成
|
||||
完成核心架构
|
@@ -1,7 +1,42 @@
|
||||
# 内置插件介绍
|
||||
内置插件为Monibuca提供了许多基础功能,当然你完全可以不采用内置插件,而改用自己开发的插件,也丝毫不会影响您使用Monibuca。
|
||||
|
||||
## 网关插件
|
||||
::: tip 源码位置
|
||||
该插件位于plugins/gateway下
|
||||
:::
|
||||
|
||||
该插件是为web控制台界面提供api,用来采集服务器的信息。
|
||||
|
||||
### 配置
|
||||
目前仅有的配置是监听的端口号
|
||||
|
||||
```toml
|
||||
[Plugins.GateWay]
|
||||
ListenAddr = ":80"
|
||||
```
|
||||
如果80端口有其他用途,可以换成别的端口,比如有nginx反向代理。
|
||||
|
||||
## 日志分割插件
|
||||
::: tip 源码位置
|
||||
该插件源码位于plugins/logrotate下
|
||||
:::
|
||||
|
||||
### 配置
|
||||
```toml
|
||||
[Plugins.LogRotate]
|
||||
Path = "log"
|
||||
Size = 0
|
||||
Days = 1
|
||||
```
|
||||
其中Path代表生成日志的目录
|
||||
Size代表按大小分割,单位是字节,如果为0,则按时间分割
|
||||
Days代表按时间分割,单位是天,即24小时
|
||||
|
||||
## Jessica插件
|
||||
> 该插件源码位于plugins/jessica下
|
||||
::: tip 源码位置
|
||||
该插件源码位于plugins/jessica下
|
||||
:::
|
||||
|
||||
该插件为基于WebSocket协议传输音视频的订阅者,音视频数据以裸数据的形式进行传输,我们需要Jessibuca播放器来进行播放
|
||||
Jessibua播放器已内置于源码中,该播放器通过js解码H264/H265并用canvas进行渲染,可以运行在几乎所有的终端浏览器上面。
|
||||
@@ -76,19 +111,6 @@ ListenAddr = ":2019"
|
||||
该插件的作用是请求M3u8文件进行解码,最终将TS视频流转码成裸的视频流进行发布。
|
||||
注意:该插件目前并没有实现生成HLS的功能。
|
||||
|
||||
## 网关插件
|
||||
> 该插件位于plugins/gateway下
|
||||
|
||||
该插件是为web控制台界面提供api,用来采集服务器的信息。
|
||||
|
||||
### 配置
|
||||
目前仅有的配置是监听的端口号
|
||||
|
||||
```toml
|
||||
[Plugins.GateWay]
|
||||
ListenAddr = ":80"
|
||||
```
|
||||
如果80端口有其他用途,可以换成别的端口,比如有nginx反向代理。
|
||||
|
||||
## 校验插件
|
||||
> 该插件位于plugins/auth下
|
||||
|
@@ -1,4 +1,6 @@
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function Jessibuca(opt) {
|
||||
this.audioContext = new window.AudioContext()
|
||||
this.canvasElement = opt.canvas;
|
||||
this.contextOptions = opt.contextOptions;
|
||||
this.videoBuffer = opt.videoBuffer || 1
|
||||
@@ -65,9 +67,7 @@ function Jessibuca(opt) {
|
||||
}
|
||||
}
|
||||
};
|
||||
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
function _unlock() {
|
||||
var context = Jessibuca.prototype.audioContext = Jessibuca.prototype.audioContext || new window.AudioContext();
|
||||
function _unlock(context) {
|
||||
context.resume();
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = context.createBuffer(1, 1, 22050);
|
||||
@@ -81,7 +81,7 @@ function _unlock() {
|
||||
// document.addEventListener("touchend", _unlock, true);
|
||||
Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
_unlock()
|
||||
_unlock(this.audioContext)
|
||||
this.audioEnabled = function (flag) {
|
||||
if (flag) {
|
||||
this.audioContext.resume();
|
||||
@@ -89,6 +89,8 @@ Jessibuca.prototype.audioEnabled = function (flag) {
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
this.audioContext.suspend();
|
||||
}
|
||||
}
|
||||
Jessibuca.prototype.playAudio = function (data) {
|
||||
@@ -150,6 +152,7 @@ Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels)
|
||||
if (!context) return false;
|
||||
var resampled = samplerate < 22050;
|
||||
var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate);
|
||||
var _this = this
|
||||
var playNextBuffer = function () {
|
||||
isPlaying = false;
|
||||
console.log("~", audioBuffers.length)
|
||||
@@ -189,9 +192,12 @@ Jessibuca.prototype.initAudioPlay = function (frameCount, samplerate, channels)
|
||||
var source = context.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(context.destination);
|
||||
source.onended = playNextBuffer;
|
||||
//setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
|
||||
// source.onended = playNextBuffer;
|
||||
// setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
|
||||
source.start();
|
||||
if (!_this.audioInterval) {
|
||||
_this.audioInterval = setInterval(playNextBuffer, audioBuffer.duration * 1000 - 1);
|
||||
}
|
||||
};
|
||||
this.playAudio = playAudio;
|
||||
}
|
||||
@@ -452,6 +458,7 @@ Jessibuca.prototype.close = function () {
|
||||
if (this.audioInterval) {
|
||||
clearInterval(this.audioInterval)
|
||||
}
|
||||
delete this.playAudio
|
||||
this.decoderWorker.postMessage({ cmd: "close" })
|
||||
this.contextGL.clear(this.contextGL.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@
|
||||
>
|
||||
<canvas id="canvas" width="488" height="275" style="background: black" />
|
||||
<div slot="footer">
|
||||
音频缓冲:
|
||||
<InputNumber v-model="audioBuffer" size="small"></InputNumber>
|
||||
<!-- 音频缓冲:-->
|
||||
<!-- <InputNumber v-model="audioBuffer" size="small"></InputNumber>-->
|
||||
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-off" />
|
||||
<Button v-else @click="turnOn" icon="md-volume-up"></Button>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
audioEnabled: false,
|
||||
audioBuffer: 12,
|
||||
// audioBuffer: 12,
|
||||
url: ""
|
||||
};
|
||||
},
|
||||
@@ -32,9 +32,9 @@ export default {
|
||||
audioEnabled(value) {
|
||||
h5lc.audioEnabled(value);
|
||||
},
|
||||
audioBuffer(v) {
|
||||
h5lc.audioBuffer = v;
|
||||
}
|
||||
// audioBuffer(v) {
|
||||
// h5lc.audioBuffer = v;
|
||||
// }
|
||||
},
|
||||
mounted() {
|
||||
h5lc = new window.Jessibuca({
|
||||
|
304
main.go
304
main.go
@@ -1,53 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"github.com/langhuihui/monibuca/pm"
|
||||
)
|
||||
|
||||
type InstanceDesc struct {
|
||||
Name string
|
||||
Path string
|
||||
Plugins []string
|
||||
Config string
|
||||
}
|
||||
|
||||
var instances map[string]*InstanceDesc
|
||||
var instances = make(map[string]*pm.InstanceDesc)
|
||||
var instancesDir string
|
||||
|
||||
func main() {
|
||||
// log.SetOutput(os.Stdout)
|
||||
// configPath := flag.String("c", "config.toml", "configFile")
|
||||
// flag.Parse()
|
||||
// Run(*configPath)
|
||||
// select {}
|
||||
|
||||
println("start monibuca instance manager version:", Version)
|
||||
if MayBeError(readInstances()) {
|
||||
return
|
||||
}
|
||||
addr := flag.String("port", "8000", "http server port")
|
||||
flag.Parse()
|
||||
http.HandleFunc("/list", listInstance)
|
||||
http.HandleFunc("/create", initInstance)
|
||||
http.HandleFunc("/upgrade/engine", upgradeEngine)
|
||||
http.HandleFunc("/restart/instance", restartInstance)
|
||||
http.HandleFunc("/instance/listDir", listDir)
|
||||
http.HandleFunc("/instance/import", importInstance)
|
||||
http.HandleFunc("/instance/updateConfig", updateConfig)
|
||||
http.HandleFunc("/instance/list", listInstance)
|
||||
http.HandleFunc("/instance/create", initInstance)
|
||||
http.HandleFunc("/instance/restart", restartInstance)
|
||||
http.HandleFunc("/instance/shutdown", shutdownInstance)
|
||||
http.HandleFunc("/", website)
|
||||
fmt.Printf("start listen at %s", *addr)
|
||||
if err := http.ListenAndServe(":"+*addr, nil); err != nil {
|
||||
@@ -55,6 +49,98 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func listDir(w http.ResponseWriter, r *http.Request) {
|
||||
if input := r.URL.Query().Get("input"); input != "" {
|
||||
if dir, err := os.Open(filepath.Dir(input)); err == nil {
|
||||
var dirs []string
|
||||
if infos, err := dir.Readdir(0); err == nil {
|
||||
for _, info := range infos {
|
||||
if info.IsDir() {
|
||||
dirs = append(dirs, info.Name())
|
||||
}
|
||||
}
|
||||
if bytes, err := json.Marshal(dirs); err == nil {
|
||||
w.Write(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importInstance(w http.ResponseWriter, r *http.Request) {
|
||||
var e error
|
||||
defer func() {
|
||||
result := "success"
|
||||
if e != nil {
|
||||
result = e.Error()
|
||||
}
|
||||
w.Write([]byte(result))
|
||||
}()
|
||||
name := r.URL.Query().Get("name")
|
||||
if importPath := r.URL.Query().Get("path"); importPath != "" {
|
||||
if strings.HasSuffix(importPath, "/") {
|
||||
importPath = importPath[:len(importPath)-1]
|
||||
}
|
||||
f, err := os.Open(importPath)
|
||||
if e = err; err != nil {
|
||||
return
|
||||
}
|
||||
children, err := f.Readdir(0)
|
||||
if e = err; err == nil {
|
||||
var hasMain, hasConfig, hasMod, hasRestart bool
|
||||
for _, child := range children {
|
||||
switch child.Name() {
|
||||
case "main.go":
|
||||
hasMain = true
|
||||
case "config.toml":
|
||||
hasConfig = true
|
||||
case "go.mod":
|
||||
hasMod = true
|
||||
case "restart.sh", "restart.bat":
|
||||
hasRestart = true
|
||||
}
|
||||
}
|
||||
if hasMain && hasConfig && hasMod && hasRestart {
|
||||
if name == "" {
|
||||
_, name = path.Split(importPath)
|
||||
}
|
||||
config, err := ioutil.ReadFile(path.Join(importPath, "config.toml"))
|
||||
if e = err; err != nil {
|
||||
return
|
||||
}
|
||||
mainGo, err := ioutil.ReadFile(path.Join(importPath, "main.go"))
|
||||
if e = err; err != nil {
|
||||
return
|
||||
}
|
||||
reg, err := regexp.Compile("_ \"(.+)\"")
|
||||
if e = err; err != nil {
|
||||
return
|
||||
}
|
||||
instances[name] = &pm.InstanceDesc{
|
||||
Name: name,
|
||||
Path: importPath,
|
||||
Plugins: nil,
|
||||
Config: string(config),
|
||||
}
|
||||
for _, m := range reg.FindAllStringSubmatch(string(mainGo), -1) {
|
||||
instances[name].Plugins = append(instances[name].Plugins, m[1])
|
||||
}
|
||||
var file *os.File
|
||||
file, e = os.OpenFile(path.Join(instancesDir, name+".toml"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tomlEncoder := toml.NewEncoder(file)
|
||||
e = tomlEncoder.Encode(instances[name])
|
||||
} else {
|
||||
e = errors.New("路径中缺少文件")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("参数错误"))
|
||||
}
|
||||
}
|
||||
|
||||
func readInstances() error {
|
||||
if homeDir, err := Home(); err == nil {
|
||||
instancesDir = path.Join(homeDir, ".monibuca")
|
||||
@@ -65,7 +151,7 @@ func readInstances() error {
|
||||
return err
|
||||
} else {
|
||||
for _, configFile := range cs {
|
||||
des := new(InstanceDesc)
|
||||
des := new(pm.InstanceDesc)
|
||||
if _, err = toml.DecodeFile(path.Join(instancesDir, configFile.Name()), des); err == nil {
|
||||
instances[des.Name] = des
|
||||
} else {
|
||||
@@ -108,7 +194,7 @@ func listInstance(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
instanceDesc := new(InstanceDesc)
|
||||
instanceDesc := new(pm.InstanceDesc)
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
err := json.Unmarshal([]byte(r.URL.Query().Get("info")), instanceDesc)
|
||||
clearDir := r.URL.Query().Get("clear") != ""
|
||||
@@ -123,7 +209,7 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("1:参数解析成功!"))
|
||||
err = instanceDesc.createDir(sse, clearDir)
|
||||
err = instanceDesc.CreateDir(sse, clearDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -140,141 +226,67 @@ func initInstance(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
instances[instanceDesc.Name] = instanceDesc
|
||||
}
|
||||
func upgradeEngine(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
cmd := exec.Command("go", "get", "-u", "github.com/langhuihui/monibuca/monica")
|
||||
func shutdownInstance(w http.ResponseWriter, r *http.Request) {
|
||||
instanceName := r.URL.Query().Get("instance")
|
||||
cmd.Dir = instances[instanceName].Path
|
||||
err := sse.WriteExec(cmd)
|
||||
if err != nil {
|
||||
sse.Write([]byte(err.Error()))
|
||||
if instance, ok := instances[instanceName]; ok {
|
||||
if err := instance.ShutDownCmd().Run(); err == nil {
|
||||
w.Write([]byte("success"))
|
||||
} else {
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("no such instance"))
|
||||
}
|
||||
}
|
||||
func restartInstance(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
instanceName := r.URL.Query().Get("instance")
|
||||
cmd := exec.Command("sh", "restart.sh")
|
||||
cmd.Dir = path.Join(instancesDir, instanceName)
|
||||
cmd.Stderr = sse
|
||||
cmd.Stdout = sse
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
sse.Write([]byte(err.Error()))
|
||||
needUpdate := r.URL.Query().Get("update") != ""
|
||||
needBuild := r.URL.Query().Get("build") != ""
|
||||
if instance, ok := instances[instanceName]; ok {
|
||||
if needUpdate {
|
||||
if err := sse.WriteExec(instance.Command("go", "get", "-u")); err != nil {
|
||||
sse.WriteEvent("failed", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
if needBuild {
|
||||
if err := sse.WriteExec(instance.Command("go", "build")); err != nil {
|
||||
sse.WriteEvent("failed", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := sse.WriteExec(instance.RestartCmd()); err != nil {
|
||||
sse.WriteEvent("failed", []byte(err.Error()))
|
||||
return
|
||||
}
|
||||
sse.Write([]byte("success"))
|
||||
} else {
|
||||
sse.WriteEvent("failed", []byte("no such instance"))
|
||||
}
|
||||
}
|
||||
func (p *InstanceDesc) writeExecSSE(sse *util.SSE, cmd *exec.Cmd) error {
|
||||
cmd.Dir = p.Path
|
||||
return sse.WriteExec(cmd)
|
||||
}
|
||||
func (p *InstanceDesc) createDir(sse *util.SSE, clearDir bool) (err error) {
|
||||
if clearDir {
|
||||
os.RemoveAll(p.Path)
|
||||
|
||||
func updateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
instanceName := r.URL.Query().Get("instance")
|
||||
if instance, ok := instances[instanceName]; ok {
|
||||
f, err := os.OpenFile(path.Join(instance.Path, "config.toml"), os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(f, r.Body)
|
||||
if err != nil {
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write([]byte("success"))
|
||||
} else {
|
||||
w.Write([]byte("no such instance"))
|
||||
}
|
||||
err = os.MkdirAll(p.Path, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("2:目录创建成功!"))
|
||||
err = ioutil.WriteFile(path.Join(p.Path, "config.toml"), []byte(p.Config), 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var build bytes.Buffer
|
||||
build.WriteString(`package main
|
||||
import(
|
||||
"github.com/langhuihui/monibuca/monica"`)
|
||||
for _, plugin := range p.Plugins {
|
||||
build.WriteString("\n_ \"")
|
||||
build.WriteString(plugin)
|
||||
build.WriteString("\"")
|
||||
}
|
||||
build.WriteString("\n)\n")
|
||||
build.WriteString(`
|
||||
func main(){
|
||||
monica.Run("config.toml")
|
||||
select{}
|
||||
}
|
||||
`)
|
||||
err = ioutil.WriteFile(path.Join(p.Path, "main.go"), build.Bytes(), 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("3:文件创建成功!"))
|
||||
err = p.writeExecSSE(sse, exec.Command("go", "mod", "init", p.Name))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("4:go mod 初始化完成!"))
|
||||
err = p.writeExecSSE(sse, exec.Command("go", "build"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("5:go build 成功!"))
|
||||
build.Reset()
|
||||
build.WriteString("kill -9 `cat pid`\nnohup ./")
|
||||
binFile := strings.TrimSuffix(p.Path, "/")
|
||||
_, binFile = path.Split(binFile)
|
||||
build.WriteString(binFile)
|
||||
build.WriteString(" > log.txt & echo $! > pid\n")
|
||||
err = ioutil.WriteFile(path.Join(p.Path, "restart.sh"), build.Bytes(), 0777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command("sh", "restart.sh")
|
||||
cmd.Dir = p.Path
|
||||
cmd.Stderr = sse
|
||||
cmd.Stdout = sse
|
||||
err = cmd.Start()
|
||||
return
|
||||
}
|
||||
func Home() (string, error) {
|
||||
user, err := user.Current()
|
||||
if nil == err {
|
||||
if user, err := user.Current(); nil == err {
|
||||
return user.HomeDir, nil
|
||||
}
|
||||
|
||||
// cross compile support
|
||||
|
||||
if "windows" == runtime.GOOS {
|
||||
return homeWindows()
|
||||
}
|
||||
|
||||
// Unix-like system, so just assume Unix
|
||||
return homeUnix()
|
||||
}
|
||||
|
||||
func homeUnix() (string, error) {
|
||||
// First prefer the HOME environmental variable
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// If that fails, try the shell
|
||||
var stdout bytes.Buffer
|
||||
cmd := exec.Command("sh", "-c", "eval echo ~$USER")
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(stdout.String())
|
||||
if result == "" {
|
||||
return "", errors.New("blank output when reading home directory")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func homeWindows() (string, error) {
|
||||
drive := os.Getenv("HOMEDRIVE")
|
||||
path := os.Getenv("HOMEPATH")
|
||||
home := drive + path
|
||||
if drive == "" || path == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
|
||||
}
|
||||
|
||||
return home, nil
|
||||
return pm.HomeDir()
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ func (av *AVPacket) ADTS2ASC() (tagPacket *AVPacket) {
|
||||
tagPacket = NewAVPacket(FLV_TAG_TYPE_AUDIO)
|
||||
tagPacket.Payload = ADTSToAudioSpecificConfig(av.Payload)
|
||||
tagPacket.IsAACSequence = true
|
||||
ADTSLength := 7 + (int(av.Payload[1]&1) << 1)
|
||||
ADTSLength := 7 + ((1 - int(av.Payload[1]&1)) << 1)
|
||||
if len(av.Payload) > ADTSLength {
|
||||
av.Payload[0] = 0xAF
|
||||
av.Payload[1] = 0x01 //raw AAC
|
||||
|
@@ -2,16 +2,29 @@ package monica
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
var ConfigRaw []byte
|
||||
var Version = "0.2.1"
|
||||
var Version = "0.3.0"
|
||||
var EngineInfo = &struct {
|
||||
Version string
|
||||
StartTime time.Time
|
||||
}{Version, time.Now()}
|
||||
|
||||
func Run(configFile string) (err error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
ioutil.WriteFile("shutdown.bat", []byte(fmt.Sprintf("taskkill /pid %d -t -f", os.Getpid())), 0777)
|
||||
} else {
|
||||
ioutil.WriteFile("shutdown.sh", []byte(fmt.Sprintf("kill -9 %d", os.Getpid())), 0777)
|
||||
}
|
||||
log.Printf("start monibuca version:%s", Version)
|
||||
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
|
||||
log.Printf("read config file error: %v", err)
|
||||
|
@@ -35,6 +35,7 @@ func (ts *TS) run() {
|
||||
spsHead := []byte{0xE1, 0, 0}
|
||||
ppsHead := []byte{0x01, 0, 0}
|
||||
nalLength := []byte{0, 0, 0, 0}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ts.Done():
|
||||
@@ -45,9 +46,18 @@ func (ts *TS) run() {
|
||||
ts.TotalPesCount++
|
||||
switch tsPesPkt.PesPkt.Header.StreamID & 0xF0 {
|
||||
case mpegts.STREAM_ID_AUDIO:
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_AUDIO)
|
||||
av.Payload = tsPesPkt.PesPkt.Payload
|
||||
ts.PushAudio(av)
|
||||
data := tsPesPkt.PesPkt.Payload
|
||||
for remainLen := len(data); remainLen > 0; {
|
||||
// AACFrameLength(13)
|
||||
// xx xxxxxxxx xxx
|
||||
frameLen := (int(data[3]&3) << 11) | (int(data[4]) << 3) | (int(data[5]) >> 5)
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_AUDIO)
|
||||
av.Payload = data[:frameLen]
|
||||
ts.PushAudio(av)
|
||||
data = data[frameLen:remainLen]
|
||||
remainLen = remainLen - frameLen
|
||||
}
|
||||
|
||||
case mpegts.STREAM_ID_VIDEO:
|
||||
var err error
|
||||
av := avformat.NewAVPacket(avformat.FLV_TAG_TYPE_VIDEO)
|
||||
|
@@ -1,28 +1,27 @@
|
||||
package QoS
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
)
|
||||
|
||||
var (
|
||||
selectMap = map[string][]string{
|
||||
"low": {"low", "medium", "high"},
|
||||
"medium": {"medium", "low", "high"},
|
||||
"high": {"high", "medium", "low"},
|
||||
}
|
||||
)
|
||||
// var (
|
||||
// selectMap = map[string][]string{
|
||||
// "low": {"low", "medium", "high"},
|
||||
// "medium": {"medium", "low", "high"},
|
||||
// "high": {"high", "medium", "low"},
|
||||
// }
|
||||
// )
|
||||
|
||||
func getQualityName(name string, qualityLevel string) string {
|
||||
if qualityLevel == "" {
|
||||
return name
|
||||
}
|
||||
for _, l := range selectMap[qualityLevel] {
|
||||
if _, ok := AllRoom.Load(name + "/" + l); ok {
|
||||
return name + "/" + l
|
||||
}
|
||||
}
|
||||
return name + "/" + qualityLevel
|
||||
}
|
||||
// func getQualityName(name string, qualityLevel string) string {
|
||||
// for _, l := range selectMap[qualityLevel] {
|
||||
// if _, ok := AllRoom.Load(name + "/" + l); ok {
|
||||
// return name + "/" + l
|
||||
// }
|
||||
// }
|
||||
// return name + "/" + qualityLevel
|
||||
// }
|
||||
|
||||
var config = struct {
|
||||
Suffix []string
|
||||
@@ -39,8 +38,23 @@ func init() {
|
||||
func run() {
|
||||
OnDropHooks.AddHook(func(s *OutputStream) {
|
||||
if s.TotalDrop > s.TotalPacket>>2 {
|
||||
//TODO
|
||||
//s.Control<-&ChangeRoomCmd{s,AllRoom.Get()}
|
||||
var newStreamPath = ""
|
||||
for i, suf := range config.Suffix {
|
||||
if strings.HasSuffix(s.StreamPath, suf) {
|
||||
if i < len(config.Suffix)-1 {
|
||||
newStreamPath = s.StreamPath + "/" + config.Suffix[i+1]
|
||||
break
|
||||
}
|
||||
} else {
|
||||
newStreamPath = s.StreamPath + "/" + suf
|
||||
break
|
||||
}
|
||||
}
|
||||
if newStreamPath != "" {
|
||||
if _, ok := AllRoom.Load(newStreamPath); ok {
|
||||
s.Control <- &ChangeRoomCmd{s, AllRoom.Get(newStreamPath)}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
_ "github.com/langhuihui/monibuca/plugins/cluster"
|
||||
_ "github.com/langhuihui/monibuca/plugins/gateway"
|
||||
_ "github.com/langhuihui/monibuca/plugins/jessica"
|
||||
_ "github.com/langhuihui/monibuca/plugins/logrotate"
|
||||
_ "github.com/langhuihui/monibuca/plugins/record"
|
||||
_ "github.com/langhuihui/monibuca/plugins/rtmp"
|
||||
)
|
||||
|
@@ -15,8 +15,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
config = new(ListenerConfig)
|
||||
|
||||
config = new(ListenerConfig)
|
||||
startTime = time.Now()
|
||||
dashboardPath string
|
||||
)
|
||||
|
||||
@@ -99,7 +99,8 @@ func summary(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
func sysInfo(w http.ResponseWriter, r *http.Request) {
|
||||
bytes, err := json.Marshal(&struct{ Version string }{Version: Version})
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
bytes, err := json.Marshal(EngineInfo)
|
||||
if err == nil {
|
||||
_, err = w.Write(bytes)
|
||||
}
|
||||
|
78
plugins/logrotate/index.go
Normal file
78
plugins/logrotate/index.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package logrotate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var config = new(LogRotate)
|
||||
|
||||
type LogRotate struct {
|
||||
Path string
|
||||
Size int64
|
||||
Days int
|
||||
file *os.File
|
||||
currentSize int64
|
||||
createTime time.Time
|
||||
hours float64
|
||||
splitFunc func() bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "LogRotate",
|
||||
Type: PLUGIN_HOOK,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
if config.Size > 0 {
|
||||
config.splitFunc = config.splitBySize
|
||||
} else {
|
||||
if config.Days == 0 {
|
||||
config.Days = 1
|
||||
}
|
||||
config.hours = float64(config.Days) * 24
|
||||
config.splitFunc = config.splitByTime
|
||||
}
|
||||
config.createTime = time.Now()
|
||||
err := os.MkdirAll(config.Path, 0666)
|
||||
config.file, err = os.OpenFile(path.Join(config.Path, fmt.Sprintf("%s.log", config.createTime.Format("2006-01-02T15:04:05"))), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err == nil {
|
||||
stat, _ := config.file.Stat()
|
||||
config.currentSize = stat.Size()
|
||||
AddWriter(config)
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
func (l *LogRotate) splitBySize() bool {
|
||||
return l.currentSize >= l.Size
|
||||
}
|
||||
func (l *LogRotate) splitByTime() bool {
|
||||
return time.Since(l.createTime).Hours() > l.hours
|
||||
}
|
||||
func (l *LogRotate) Write(data []byte) (n int, err error) {
|
||||
n, err = l.file.Write(data)
|
||||
l.currentSize += int64(n)
|
||||
if err == nil {
|
||||
if l.splitFunc() {
|
||||
l.createTime = time.Now()
|
||||
if file, err := os.OpenFile(path.Join(l.Path, fmt.Sprintf("%s.log", l.createTime.Format("2006-01-02T15:04:05"))), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666); err == nil {
|
||||
l.file = file
|
||||
l.currentSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//func (l *LogRotate) FindLog(grep string) string{
|
||||
// cmd:=exec.Command("grep",fmt.Sprintf("\"%s\"",grep),l.Path)
|
||||
// err:=cmd.Run()
|
||||
//}
|
2
pm/dist/index.html
vendored
2
pm/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 Instance Manager</title><script src=ajax.js></script><link href=/css/app.200d2f8f.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.fd72a180.js rel=preload as=script><link href=/js/chunk-vendors.6b87e1b5.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.6b87e1b5.js></script><script src=/js/app.fd72a180.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 Instance Manager</title><script src=ajax.js></script><link href=/css/app.200d2f8f.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.13e2de5f.js rel=preload as=script><link href=/js/chunk-vendors.2e3b192a.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.200d2f8f.css rel=stylesheet></head><body><noscript><strong>We're sorry but pm doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2e3b192a.js></script><script src=/js/app.13e2de5f.js></script></body></html>
|
2
pm/dist/js/app.13e2de5f.js
vendored
Normal file
2
pm/dist/js/app.13e2de5f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pm/dist/js/app.13e2de5f.js.map
vendored
Normal file
1
pm/dist/js/app.13e2de5f.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
pm/dist/js/app.fd72a180.js
vendored
2
pm/dist/js/app.fd72a180.js
vendored
File diff suppressed because one or more lines are too long
1
pm/dist/js/app.fd72a180.js.map
vendored
1
pm/dist/js/app.fd72a180.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
pm/dist/js/chunk-vendors.2e3b192a.js.map
vendored
Normal file
1
pm/dist/js/chunk-vendors.2e3b192a.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
pm/dist/js/chunk-vendors.6b87e1b5.js.map
vendored
1
pm/dist/js/chunk-vendors.6b87e1b5.js.map
vendored
File diff suppressed because one or more lines are too long
71
pm/instance.go
Normal file
71
pm/instance.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package pm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InstanceDesc struct {
|
||||
Name string
|
||||
Path string
|
||||
Plugins []string
|
||||
Config string
|
||||
}
|
||||
|
||||
func (p *InstanceDesc) Command(name string, args ...string) (cmd *exec.Cmd) {
|
||||
cmd = exec.Command(name, args...)
|
||||
cmd.Dir = p.Path
|
||||
return
|
||||
}
|
||||
|
||||
func (p *InstanceDesc) CreateDir(sse *util.SSE, clearDir bool) (err error) {
|
||||
if clearDir {
|
||||
err = os.RemoveAll(p.Path)
|
||||
}
|
||||
if err = os.MkdirAll(p.Path, 0666); err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("2:目录创建成功!"))
|
||||
if err = ioutil.WriteFile(path.Join(p.Path, "config.toml"), []byte(p.Config), 0666); err != nil {
|
||||
return
|
||||
}
|
||||
var build bytes.Buffer
|
||||
build.WriteString(`package main
|
||||
import(
|
||||
"github.com/langhuihui/monibuca/monica"`)
|
||||
for _, plugin := range p.Plugins {
|
||||
build.WriteString("\n_ \"")
|
||||
build.WriteString(plugin)
|
||||
build.WriteString("\"")
|
||||
}
|
||||
build.WriteString("\n)\n")
|
||||
build.WriteString(`
|
||||
func main(){
|
||||
monica.Run("config.toml")
|
||||
select{}
|
||||
}
|
||||
`)
|
||||
if err = ioutil.WriteFile(path.Join(p.Path, "main.go"), build.Bytes(), 0666); err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("3:文件创建成功!"))
|
||||
if err = sse.WriteExec(p.Command("go", "mod", "init", p.Name)); err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("4:go mod 初始化完成!"))
|
||||
if err = sse.WriteExec(p.Command("go", "build")); err != nil {
|
||||
return
|
||||
}
|
||||
sse.WriteEvent("step", []byte("5:go build 成功!"))
|
||||
binFile := strings.TrimSuffix(p.Path, "/")
|
||||
_, binFile = path.Split(binFile)
|
||||
if err = p.CreateRestartFile(binFile); err != nil {
|
||||
return
|
||||
}
|
||||
return sse.WriteExec(p.RestartCmd())
|
||||
}
|
46
pm/linux.go
Normal file
46
pm/linux.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// +build !windows
|
||||
|
||||
package pm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HomeDir() (string, error) {
|
||||
// First prefer the HOME environmental variable
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// If that fails, try the shell
|
||||
var stdout bytes.Buffer
|
||||
cmd := exec.Command("sh", "-c", "eval echo ~$USER")
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(stdout.String())
|
||||
if result == "" {
|
||||
return "", errors.New("blank output when reading home directory")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
func (p *InstanceDesc) ShutDownCmd() *exec.Cmd {
|
||||
return p.Command("sh", "shutdown.sh")
|
||||
}
|
||||
func (p *InstanceDesc) RestartCmd() *exec.Cmd {
|
||||
return p.Command("sh", "restart.sh")
|
||||
}
|
||||
func (p *InstanceDesc) CreateRestartFile(binFile string) error {
|
||||
return ioutil.WriteFile(path.Join(p.Path, "restart.sh"), []byte(fmt.Sprintf(`./shutdown.sh
|
||||
%s &`, binFile)), 0777)
|
||||
}
|
5
pm/package-lock.json
generated
5
pm/package-lock.json
generated
@@ -979,6 +979,11 @@
|
||||
"@hapi/hoek": "8.5.0"
|
||||
}
|
||||
},
|
||||
"@iarna/toml": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npm.taobao.org/@iarna/toml/download/@iarna/toml-2.2.3.tgz",
|
||||
"integrity": "sha1-8GC/bqr65NVqfaxhiYCDiwaW4qs="
|
||||
},
|
||||
"@intervolga/optimize-cssnano-plugin": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npm.taobao.org/@intervolga/optimize-cssnano-plugin/download/@intervolga/optimize-cssnano-plugin-1.0.6.tgz",
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.3",
|
||||
"core-js": "^3.4.4",
|
||||
"view-design": "^4.0.0",
|
||||
"vue": "^2.6.10",
|
||||
@@ -37,7 +38,7 @@
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"rules": {"no-console": "off"},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal v-bind="$attrs" v-on="$listeners" :title="info.Path">
|
||||
<Modal v-bind="$attrs" v-on="$listeners" :title="info && info.Path">
|
||||
<Steps :current="currentStep" size="small" :status="status">
|
||||
<Step title="解析请求"></Step>
|
||||
<Step title="创建目录"></Step>
|
||||
@@ -14,8 +14,7 @@
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<Checkbox v-model="clearDir">安装前清空目录</Checkbox>
|
||||
<Button type="primary" @click="start">开始</Button>
|
||||
<Button type="success" @click="close" v-if="status=='finish'">完成</Button>
|
||||
<Button type="primary" @click="start" :loading="status=='process'">开始</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -27,36 +26,37 @@ export default {
|
||||
props: {
|
||||
info: Object
|
||||
},
|
||||
methods:{
|
||||
start(){
|
||||
eventSource = new EventSource(
|
||||
"/create?info=" + JSON.stringify(this.info)+(this.clearDir?"&clear=true":"")
|
||||
);
|
||||
eventSource.onopen = () => (this.log = "");
|
||||
eventSource.onmessage = evt => {
|
||||
this.log += evt.data + "\n";
|
||||
if (evt.data == "success") {
|
||||
this.status = "finish";
|
||||
eventSource.close();
|
||||
}
|
||||
};
|
||||
eventSource.addEventListener("exception", evt => {
|
||||
this.log += evt.data + "\n";
|
||||
this.status = "error";
|
||||
methods: {
|
||||
start() {
|
||||
this.status = "process";
|
||||
eventSource = new EventSource(
|
||||
"/instance/create?info=" +
|
||||
JSON.stringify(this.info) +
|
||||
(this.clearDir ? "&clear=true" : "")
|
||||
);
|
||||
eventSource.onopen = () => (this.log = "");
|
||||
eventSource.onmessage = evt => {
|
||||
this.log += evt.data + "\n";
|
||||
if (evt.data == "success") {
|
||||
this.status = "finish";
|
||||
eventSource.close();
|
||||
});
|
||||
eventSource.addEventListener("step", evt => {
|
||||
let [step, msg] = evt.data.split(":");
|
||||
this.currentStep = step | 0;
|
||||
this.log += msg + "\n";
|
||||
});
|
||||
},close(){
|
||||
this.$Modal.remove()
|
||||
}
|
||||
}
|
||||
};
|
||||
eventSource.addEventListener("exception", evt => {
|
||||
this.log += evt.data + "\n";
|
||||
this.status = "error";
|
||||
eventSource.close();
|
||||
});
|
||||
eventSource.addEventListener("step", evt => {
|
||||
let [step, msg] = evt.data.split(":");
|
||||
this.currentStep = step | 0;
|
||||
this.log += msg + "\n";
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
data() {
|
||||
return { clearDir: true, currentStep: 0, log: "", status: "process" };
|
||||
return { clearDir: true, currentStep: 0, log: "", status: "wait" };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
47
pm/src/components/ImportInstance.vue
Normal file
47
pm/src/components/ImportInstance.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<PathSelector v-model="instancePath" placeholder="输入实例所在的路径"></PathSelector>
|
||||
<i-input style="width: 300px;margin:40px auto" v-model="instanceName" :placeholder="defaultInstanceName" search enter-button="Import" @on-search="doImport">
|
||||
<span slot="prepend">实例名称</span>
|
||||
</i-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PathSelector from "./PathSelector"
|
||||
export default {
|
||||
name: "ImportInstance",
|
||||
components:{
|
||||
PathSelector
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
instancePath:"",
|
||||
instanceName:""
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
defaultInstanceName(){
|
||||
let path = this.instancePath.replace(/\\/g,"/")
|
||||
let s = path.split("/")
|
||||
if(path.endsWith("/")) s.pop()
|
||||
return s.pop()
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
doImport(){
|
||||
window.ajax.get("/instance/import?path="+this.instancePath+"&name="+this.instanceName).then(x=>{
|
||||
if(x=="success"){
|
||||
this.$Message.success("导入成功!")
|
||||
}else{
|
||||
this.$Message.error(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
156
pm/src/components/InstanceList.vue
Normal file
156
pm/src/components/InstanceList.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<List border>
|
||||
<ListItem v-for="item in instances" :key="item.Name">
|
||||
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
|
||||
<template v-if>{{typeof item.Info == "string"}}</template>
|
||||
<template v-else-if="item.Info">
|
||||
引擎版本:{{item.Info.Version}} <br>启动时间:
|
||||
<StartTime :value="item.Info.StartTime"></StartTime>
|
||||
</template>
|
||||
<template slot="action">
|
||||
<li @click="changeConfig(item)">
|
||||
<Icon type="ios-settings"/>
|
||||
修改配置
|
||||
</li>
|
||||
<li v-if="hasGateway(item)" @click="openGateway(item)">
|
||||
<Icon type="md-browsers"/>
|
||||
管理界面
|
||||
</li>
|
||||
<li @click="currentItem=item,showRestart=true">
|
||||
<Icon type="ios-refresh"/>
|
||||
重启
|
||||
</li>
|
||||
<li @click="shutdown(item)">
|
||||
<Icon type="ios-power"/>
|
||||
关闭
|
||||
</li>
|
||||
</template>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Modal v-model="showRestart" title="重启选项" @on-ok="restart">
|
||||
<Checkbox v-model="update">go get -u</Checkbox>
|
||||
<Checkbox v-model="build">go build</Checkbox>
|
||||
</Modal>
|
||||
<Modal v-model="showConfig" title="修改实例配置" @on-ok="submitConfigChange">
|
||||
<i-input type="textarea" v-model="currentConfig" :rows="20"></i-input>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import toml from "@iarna/toml"
|
||||
import StartTime from "./StartTime"
|
||||
|
||||
export default {
|
||||
name: "InstanceList",
|
||||
components: {StartTime},
|
||||
data() {
|
||||
return {
|
||||
instances: [],
|
||||
showRestart: false,
|
||||
update: false,
|
||||
build: false,
|
||||
showConfig: false,
|
||||
currentItem: null,
|
||||
currentConfig: ""
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.ajax.getJSON("/instance/list").then(x => {
|
||||
for (let name in x) {
|
||||
let instance = x[name]
|
||||
instance.Config = toml.parse(instance.Config)
|
||||
if (this.hasGateway(instance)) {
|
||||
window.ajax.getJSON(this.gateWayHref(instance) + "/api/sysInfo").then(x => {
|
||||
instance.Info = x
|
||||
}).catch(() => {
|
||||
instance.Info = "无法访问实例"
|
||||
})
|
||||
} else {
|
||||
instance.Info = "实例未配置网关插件"
|
||||
}
|
||||
this.instances.push(instance)
|
||||
}
|
||||
// this.instances = x;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
changeConfig(item) {
|
||||
this.showConfig = true
|
||||
this.currentItem = item
|
||||
this.currentConfig = toml.stringify(item.Config)
|
||||
},
|
||||
submitConfigChange() {
|
||||
try {
|
||||
this.currentItem.Config = toml.parse(this.currentConfig)
|
||||
window.ajax.post("/instance/updateConfig?instance=" + this.currentItem.Name, this.currentConfig).then(x => {
|
||||
if (x == "success") {
|
||||
this.$Message.success("更新成功!")
|
||||
} else {
|
||||
this.$Message.error(x)
|
||||
}
|
||||
}).catch(e => {
|
||||
this.$Message.error(e)
|
||||
})
|
||||
} catch (e) {
|
||||
this.$Message.error(e)
|
||||
}
|
||||
},
|
||||
openGateway(item) {
|
||||
window.open(this.gateWayHref(item), '_blank')
|
||||
},
|
||||
hasGateway(item) {
|
||||
return item.Config.Plugins.hasOwnProperty("GateWay")
|
||||
},
|
||||
gateWayHref(item) {
|
||||
return "http://" + location.hostname + ":" + item.Config.Plugins.GateWay.ListenAddr.split(":").pop()
|
||||
},
|
||||
restart() {
|
||||
let item = this.currentItem
|
||||
const msg = this.$Message.loading({
|
||||
content: 'restart ' + item.Name + '...',
|
||||
duration: 0
|
||||
});
|
||||
let arg = item.Name
|
||||
if (this.update) {
|
||||
arg += "&update=true"
|
||||
}
|
||||
if (this.build) {
|
||||
arg += "&build=true"
|
||||
}
|
||||
const es = new EventSource("/instance/restart?instance=" + arg)
|
||||
es.onmessage = evt => {
|
||||
if (evt.data == "success") {
|
||||
this.$Message.success("重启成功!")
|
||||
msg()
|
||||
} else {
|
||||
this.$Message.info(evt.data)
|
||||
}
|
||||
}
|
||||
es.addEventListener("failed", evt => {
|
||||
this.$Message.error(evt.data)
|
||||
msg()
|
||||
})
|
||||
es.onerror = e => {
|
||||
if (e && e.toString()) this.$Message.error(e);
|
||||
msg()
|
||||
es.close()
|
||||
}
|
||||
},
|
||||
shutdown(item) {
|
||||
window.ajax.get("/instance/shutdown?instance=" + item.Name).then(x => {
|
||||
if (x == "success") {
|
||||
this.$Message.success("已关闭实例")
|
||||
} else {
|
||||
this.$Message.error(x)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
66
pm/src/components/PathSelector.vue
Normal file
66
pm/src/components/PathSelector.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<i-input ref="input" v-bind="$attrs" v-on="$listeners" clearable @on-change="onInput">
|
||||
<Button slot="prepend" icon="md-arrow-round-up" @click="goUp"></Button>
|
||||
</i-input>
|
||||
<CellGroup @on-click="onSelectCand">
|
||||
<Cell v-for="item in candidate" :key="item" :title="item" :name="item"></Cell>
|
||||
</CellGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PathSelector",
|
||||
data() {
|
||||
return {
|
||||
candidate: [],
|
||||
lastInput: "",
|
||||
searching: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dir(){
|
||||
let paths = this.$refs.input.value.split("/");
|
||||
paths.pop();
|
||||
return paths.join("/");
|
||||
},
|
||||
goUp() {
|
||||
this.lastInput = this.$attrs.value = this.dir()
|
||||
this.$refs.input.$emit('input', this.$attrs.value)
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
onSelectCand(name) {
|
||||
this.lastInput = this.$attrs.value = this.dir()+"/"+name+"/"
|
||||
this.$refs.input.$emit('input', this.$attrs.value)
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
onInput(evt) {
|
||||
this.lastInput = evt.target.value
|
||||
this.search(this.lastInput)
|
||||
},
|
||||
search(v) {
|
||||
if(this.searching)return
|
||||
window.ajax.getJSON("/instance/listDir?input=" + v).then(x => {
|
||||
this.candidate = x
|
||||
if (this.lastInput != v) {
|
||||
this.search(this.lastInput)
|
||||
}else{
|
||||
this.searching = false
|
||||
}
|
||||
}).catch(e => {
|
||||
this.$Message.error(e)
|
||||
if (this.lastInput != v) {
|
||||
this.search(this.lastInput)
|
||||
}else{
|
||||
this.searching = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
18
pm/src/components/StartTime.vue
Normal file
18
pm/src/components/StartTime.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<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>
|
@@ -1,10 +1,40 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
defaultPlugins:{
|
||||
GateWay:[
|
||||
"gateway",'ListenAddr = ":8081"',"网关插件,提供各种API服务,包括信息采集和控制等,控制台页面展示(静态资源服务器)"
|
||||
],
|
||||
LogRotate:[
|
||||
"logrotate",`Path = "log"
|
||||
Size = 0
|
||||
Days = 1`,"日志分割插件,Size 代表按照字节数分割,0代表采用时间分割"
|
||||
],
|
||||
Jessica:[
|
||||
"jessica",'ListenAddr = ":8080"',"WebSocket协议订阅,采用私有协议,搭配Jessibuca播放器实现低延时播放"
|
||||
],
|
||||
Cluster:[
|
||||
"cluster",'Master = "localhost:2019"\nListenAddr = ":2019"',"集群插件,可以实现级联转发功能,Master代表上游服务器,ListenAdder代表源服务器监听端口,可只配置一项"
|
||||
],
|
||||
RTMP:[
|
||||
"rtmp",'ListenAddr = ":1935"',"rtmp协议实现,基本发布和订阅功能"
|
||||
],
|
||||
RecordFlv:[
|
||||
"record",'Path="./resource"',"录制视频流到flv文件"
|
||||
],
|
||||
HDL:[
|
||||
"HDL",'ListenAddr = ":2020"',"Http-flv格式实现,可以对接CDN厂商进行回源拉流"
|
||||
],
|
||||
Auth:[
|
||||
"auth",'Key = "www.monibuca.com"',"一个鉴权验证模块"
|
||||
],
|
||||
Qos:[
|
||||
"QoS",'Suffix = ["high","medium","low"]',"质量控制插件,可以动态改变订阅的不同的质量的流"
|
||||
]
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
|
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<img alt="Vue logo" src="../assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
|
||||
export default {
|
||||
name: 'home',
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -4,11 +4,7 @@
|
||||
<Content class="content">
|
||||
<Tabs value="name1">
|
||||
<TabPane label="实例" name="name1">
|
||||
<List border>
|
||||
<ListItem v-for="item in instances" :key="item.Name">
|
||||
<ListItemMeta :title="item.Name" :description="item.Path"></ListItemMeta>
|
||||
</ListItem>
|
||||
</List>
|
||||
<InstanceList></InstanceList>
|
||||
</TabPane>
|
||||
<TabPane label="创建" name="name2">
|
||||
<Steps :current="createStep">
|
||||
@@ -17,25 +13,23 @@
|
||||
<Step title="完成" content="完成实例创建"></Step>
|
||||
</Steps>
|
||||
<div style="margin:50px;width:auto">
|
||||
<i-input v-model="createPath" v-if="createStep==0">
|
||||
<Button slot="prepend" icon="md-arrow-round-up" @click="goUp"></Button>
|
||||
</i-input>
|
||||
<List v-else-if="createStep==1" border>
|
||||
<ListItem v-for="(item,name) in plugins" :key="name">
|
||||
<ListItemMeta :title="name" :description="item.Path"></ListItemMeta>
|
||||
{{item.Config}}
|
||||
<template slot="action">
|
||||
<li @click="removePlugin(name)">
|
||||
<Icon type="ios-trash" />移除
|
||||
</li>
|
||||
</template>
|
||||
</ListItem>
|
||||
</List>
|
||||
<PathSelector v-model="createPath" v-if="createStep==0"></PathSelector>
|
||||
<div style="display: flex;flex-wrap: wrap" v-else-if="createStep==1">
|
||||
<Card v-for="(item,name) in plugins" :key="name" style="width:200px;margin:5px">
|
||||
<Poptip :content="item.Description" slot="extra" width="200" word-wrap>
|
||||
<Icon size="18" type="ios-help-circle-outline" style="cursor:pointer"/>
|
||||
</Poptip>
|
||||
<Poptip :content="item.Path" trigger="hover" word-wrap slot="title">
|
||||
<Checkbox v-model="item.enabled" style="color: #eb5e46">{{name}}</Checkbox>
|
||||
</Poptip>
|
||||
<i-input type="textarea" v-model="item.Config" placeholder="请输入toml格式"></i-input>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h3>实例名称:</h3>
|
||||
<i-input
|
||||
v-model="instanceName"
|
||||
:placeholder="createPath.split('/').pop()"
|
||||
v-model="instanceName"
|
||||
:placeholder="createPath.split('/').pop()"
|
||||
></i-input>
|
||||
<h4>安装路径:</h4>
|
||||
<div>
|
||||
@@ -52,27 +46,28 @@
|
||||
</div>
|
||||
<ButtonGroup style="display:table;margin:50px auto;">
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep--"
|
||||
v-if="createStep!=0"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep--"
|
||||
v-if="createStep!=0"
|
||||
>
|
||||
<Icon type="ios-arrow-back"></Icon>上一步
|
||||
<Icon type="ios-arrow-back"></Icon>
|
||||
上一步
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
type="success"
|
||||
@click="showAddPlugin=true"
|
||||
v-if="createStep==1"
|
||||
size="large"
|
||||
type="success"
|
||||
@click="showAddPlugin=true"
|
||||
v-if="createStep==1"
|
||||
>
|
||||
+
|
||||
添加插件
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep++"
|
||||
v-if="createStep!=2"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="createStep++"
|
||||
v-if="createStep!=2"
|
||||
>
|
||||
下一步
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
@@ -81,7 +76,9 @@
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane label="导入" name="name3"></TabPane>
|
||||
<TabPane label="导入" name="name3">
|
||||
<ImportInstance></ImportInstance>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Content>
|
||||
<Modal v-model="showAddPlugin" title="添加Plugin" @on-ok="addPlugin">
|
||||
@@ -90,14 +87,9 @@
|
||||
<i-input v-model="formPlugin.Name" placeholder="插件名称必须和插件注册时的名称一致"></i-input>
|
||||
</FormItem>
|
||||
<FormItem label="插件包地址">
|
||||
<i-input v-model="formPlugin.Path">
|
||||
<Button slot="append" @click="showBuiltinPlugin=true">内置插件</Button>
|
||||
</i-input>
|
||||
<i-input v-model="formPlugin.Path"></i-input>
|
||||
</FormItem>
|
||||
<Alert
|
||||
type="show-icon"
|
||||
v-if="!Object.values(builtinPlugins).includes(formPlugin.Path)"
|
||||
>
|
||||
<Alert show-icon type="warning">
|
||||
如果该插件是私有仓库,请到服务器上输入:echo "machine {{privateHost}} login 用户名 password 密码" >> ~/.netrc
|
||||
并且添加环境变量GOPRIVATE={{privateHost}}
|
||||
</Alert>
|
||||
@@ -106,133 +98,98 @@
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Modal v-model="showBuiltinPlugin">
|
||||
<List>
|
||||
<ListItem v-for="(item,name) in builtinPlugins" :key="name">
|
||||
<ListItemMeta :title="name" :description="item"></ListItemMeta>
|
||||
<template slot="action">
|
||||
<li @click="addBuiltin(name,item)">
|
||||
<Icon type="ios-add" />添加
|
||||
</li>
|
||||
</template>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Modal>
|
||||
<CreateInstance v-model="showCreate" :info="createInfo"></CreateInstance>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreateInstance from "../components/CreateInstance";
|
||||
import CreateInstance from "../components/CreateInstance";
|
||||
import InstanceList from "../components/InstanceList";
|
||||
import ImportInstance from "../components/ImportInstance";
|
||||
import PathSelector from "../components/PathSelector"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreateInstance
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
instanceName: "",
|
||||
createStep: 0,
|
||||
showCreate: false,
|
||||
createInfo: null,
|
||||
createPath: "/opt/monibuca",
|
||||
instances: {},
|
||||
plugins: {},
|
||||
showAddPlugin: false,
|
||||
formPlugin: {},
|
||||
showBuiltinPlugin: false,
|
||||
builtinPlugins: {
|
||||
Auth: "github.com/langhuihui/monibuca/plugins/auth",
|
||||
Cluster: "github.com/langhuihui/monibuca/plugins/cluster",
|
||||
GateWay: "github.com/langhuihui/monibuca/plugins/gateway",
|
||||
HDL: "github.com/langhuihui/monibuca/plugins/HDL",
|
||||
Jessica: "github.com/langhuihui/monibuca/plugins/jessica",
|
||||
QoS: "github.com/langhuihui/monibuca/plugins/QoS",
|
||||
RecordFlv: "github.com/langhuihui/monibuca/plugins/record",
|
||||
RTMP: "github.com/langhuihui/monibuca/plugins/rtmp"
|
||||
},
|
||||
defaultConfig: {
|
||||
Auth: 'Key = "www.monibuca.com"',
|
||||
RecordFlv: 'Path="./resource"',
|
||||
QoS: 'Suffix = ["high","medium","low"]',
|
||||
Cluster: 'Master = "localhost:2019"\nListenAddr = ":2019"',
|
||||
GateWay: 'ListenAddr = ":8081"',
|
||||
RTMP: 'ListenAddr = ":1935"',
|
||||
Jessica: 'ListenAddr = ":8080"',
|
||||
HDL: 'ListenAddr = ":2020"'
|
||||
export default {
|
||||
components: {
|
||||
CreateInstance, InstanceList, ImportInstance, PathSelector
|
||||
},
|
||||
data() {
|
||||
let plugins = {}
|
||||
for (let name in this.$store.state.defaultPlugins) {
|
||||
plugins[name] = {
|
||||
Name: name,
|
||||
enabled: ["GateWay", "LogRotate", "Jessica"].includes(name),
|
||||
Path: "github.com/langhuihui/monibuca/plugins/" + this.$store.state.defaultPlugins[name][0],
|
||||
Config: this.$store.state.defaultPlugins[name][1],
|
||||
Description: this.$store.state.defaultPlugins[name][2],
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pluginStr() {
|
||||
return Object.values(this.plugins)
|
||||
.map(x => x.Path)
|
||||
.join("\n");
|
||||
},
|
||||
configStr() {
|
||||
return Object.values(this.plugins)
|
||||
.map(
|
||||
x => `[Plugins.${x.Name}]
|
||||
${x.Config || ""}`
|
||||
)
|
||||
.join("\n");
|
||||
},
|
||||
privateHost() {
|
||||
return (
|
||||
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) ||
|
||||
"仓库域名"
|
||||
);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.ajax.getJSON("/list").then(x => {
|
||||
this.instances = x;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
goUp() {
|
||||
let paths = this.createPath.split("/");
|
||||
paths.pop();
|
||||
this.createPath = paths.join("/");
|
||||
},
|
||||
createInstance() {
|
||||
this.showCreate = true;
|
||||
this.createInfo = {
|
||||
Name: this.instanceName || this.createPath.split("/").pop(),
|
||||
Path: this.createPath,
|
||||
Plugins: Object.values(this.plugins).map(x => x.Path),
|
||||
Config: this.configStr
|
||||
return {
|
||||
instanceName: "",
|
||||
createStep: 0,
|
||||
showCreate: false,
|
||||
createInfo: null,
|
||||
createPath: "/opt/monibuca",
|
||||
plugins,
|
||||
showAddPlugin: false,
|
||||
formPlugin: {},
|
||||
};
|
||||
},
|
||||
addPlugin() {
|
||||
this.plugins[this.formPlugin.Name] = this.formPlugin;
|
||||
this.formPlugin = {};
|
||||
computed: {
|
||||
pluginStr() {
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(x => x.Path)
|
||||
.join("\n");
|
||||
},
|
||||
configStr() {
|
||||
return Object.values(this.plugins).filter(x => x.enabled)
|
||||
.map(
|
||||
x => `[Plugins.${x.Name}]
|
||||
${x.Config || ""}`
|
||||
)
|
||||
.join("\n");
|
||||
},
|
||||
privateHost() {
|
||||
return (
|
||||
(this.formPlugin.Path && this.formPlugin.Path.split("/")[0]) ||
|
||||
"仓库域名"
|
||||
);
|
||||
}
|
||||
},
|
||||
removePlugin(name) {
|
||||
delete this.plugins[name];
|
||||
this.$forceUpdate();
|
||||
},
|
||||
addBuiltin(name, item) {
|
||||
this.formPlugin.Name = name;
|
||||
this.formPlugin.Path = item;
|
||||
this.formPlugin.Config = this.defaultConfig[name];
|
||||
this.showBuiltinPlugin = false;
|
||||
|
||||
methods: {
|
||||
goUp() {
|
||||
let paths = this.createPath.split("/");
|
||||
paths.pop();
|
||||
this.createPath = paths.join("/");
|
||||
},
|
||||
createInstance() {
|
||||
this.showCreate = true;
|
||||
this.createInfo = {
|
||||
Name: this.instanceName || this.createPath.split("/").pop(),
|
||||
Path: this.createPath,
|
||||
Plugins: Object.values(this.plugins).filter(x => x.enabled).map(x => x.Path),
|
||||
Config: this.configStr
|
||||
};
|
||||
},
|
||||
addPlugin() {
|
||||
this.plugins[this.formPlugin.Name] = this.formPlugin;
|
||||
this.formPlugin = {};
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
background: white;
|
||||
}
|
||||
.content {
|
||||
background: white;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.ivu-tabs .ivu-tabs-tabpane {
|
||||
padding: 20px;
|
||||
}
|
||||
.ivu-tabs .ivu-tabs-tabpane {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
37
pm/windows.go
Normal file
37
pm/windows.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// +build windows
|
||||
|
||||
package pm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
)
|
||||
|
||||
func HomeDir() (string, error) {
|
||||
drive := os.Getenv("HOMEDRIVE")
|
||||
path := os.Getenv("HOMEPATH")
|
||||
home := drive + path
|
||||
if drive == "" || path == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
|
||||
}
|
||||
|
||||
return home, nil
|
||||
}
|
||||
func (p *InstanceDesc) ShutDownCmd() *exec.Cmd {
|
||||
return p.Command("cmd", "/c", "shutdown.bat")
|
||||
}
|
||||
|
||||
func (p *InstanceDesc) RestartCmd() *exec.Cmd {
|
||||
return p.Command("cmd", "/c", "restart.bat")
|
||||
}
|
||||
func (p *InstanceDesc) CreateRestartFile(binFile string) error {
|
||||
return ioutil.WriteFile(path.Join(p.Path, "restart.bat"), []byte(fmt.Sprintf(`shutdown.bat
|
||||
start %s`, binFile)), 0777)
|
||||
}
|
Reference in New Issue
Block a user