Compare commits

...

3 Commits

Author SHA1 Message Date
langhuihui
13c357493f 更新控制台功能 2020-02-01 15:52:54 +08:00
langhuihui
87776b9540 增加log查看和config查看 2020-01-31 14:22:21 +08:00
langhuihui
543d735c41 完善文档 2020-01-30 21:31:09 +08:00
46 changed files with 900 additions and 406 deletions

View File

@@ -1 +0,0 @@
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}@-webkit-keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-65ce2c22{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-65ce2c22]{-webkit-animation:recording-data-v-65ce2c22 1s infinite;animation:recording-data-v-65ce2c22 1s infinite}.layout[data-v-65ce2c22]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-65ce2c22]{width:250px;margin:10px;text-align:left}.empty[data-v-65ce2c22]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-65ce2c22],.status[data-v-65ce2c22]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-65ce2c22]{position:fixed;left:5px;bottom:10px}.status>div[data-v-65ce2c22]{margin:0 5px}

1
dashboard/dist/css/app.d549056a.css vendored Normal file
View File

@@ -0,0 +1 @@
#app,body,html{height:100%}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#184c18;position:relative}#app>div:first-child{position:absolute;top:10px;left:30px;font-size:x-large}.content{padding-top:60px}.feature-title[data-v-54efad41]{color:#eb5e46;font-weight:700;font-size:larger}p[data-v-54efad41]{margin:30px;font-size:20px}img[data-v-54efad41]{margin:20px}.root[data-v-e34eab40]{background:#d3d3d3}.root>img[data-v-e34eab40]{width:300px;margin:30px}.records[data-v-4eee1624]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 15px}.records>[data-v-4eee1624]{width:200px}.log-container{overflow-y:auto;max-height:500px}@-webkit-keyframes recording-data-v-7f7c92c8{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}@keyframes recording-data-v-7f7c92c8{0%{opacity:.2}50%{opacity:1}to{opacity:.2}}.recording[data-v-7f7c92c8]{-webkit-animation:recording-data-v-7f7c92c8 1s infinite;animation:recording-data-v-7f7c92c8 1s infinite}.layout[data-v-7f7c92c8]{padding-bottom:30px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.room[data-v-7f7c92c8]{width:250px;margin:10px;text-align:left}.empty[data-v-7f7c92c8]{color:#eb5e46;width:100%;min-height:500px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.empty[data-v-7f7c92c8],.status[data-v-7f7c92c8]{display:-webkit-box;display:-ms-flexbox;display:flex}.status[data-v-7f7c92c8]{position:fixed;left:5px;bottom:10px}.status>div[data-v-7f7c92c8]{margin:0 5px}

View File

@@ -7,11 +7,11 @@
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.83ce04b6.js"><link rel="prefetch" href="/docs/assets/js/2.142d04d2.js"><link rel="prefetch" href="/docs/assets/js/3.2b6c987b.js"><link rel="prefetch" href="/docs/assets/js/4.ad90d74a.js"><link rel="prefetch" href="/docs/assets/js/5.36121818.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.css">
<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">
</head>
<body>
<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.ad3166d6.js" defer></script>
<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>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{161: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\n1.0完成")])])])}],!1,null,null,null);e.default=i.exports}}]);
(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}}]);

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{164:function(t,n,e){"use strict";e.r(n);var s=e(0),c=Object(s.a)({},(function(){var t=this.$createElement;return(this._self._c||t)("div",{staticClass:"content"})}),[],!1,null,null,null);n.default=c.exports}}]);

View File

@@ -1 +0,0 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{165:function(s,a,t){"use strict";t.r(a);var e=t(0),n=Object(e.a)({},(function(){var s=this.$createElement;this._self._c;return this._m(0)}),[function(){var s=this,a=s.$createElement,t=s._self._c||a;return t("div",{staticClass:"content"},[t("h1",{attrs:{id:"jessica"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#jessica","aria-hidden":"true"}},[s._v("#")]),s._v(" Jessica")]),t("p",[s._v("该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放\nJessibua播放器已内置于源码中该播放器通过js解码H264并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。\n在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。")]),t("h2",{attrs:{id:"配置"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 配置")]),t("p",[s._v("目前仅有的配置是监听的端口号")]),t("div",{staticClass:"language-toml extra-class"},[t("pre",{pre:!0,attrs:{class:"language-toml"}},[t("code",[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),t("span",{pre:!0,attrs:{class:"token table class-name"}},[s._v("Plugins.Jessica")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token key property"}},[s._v("ListenAddr")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v('":8080"')]),s._v("\n")])])])])}],!1,null,null,null);a.default=n.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,13 +7,13 @@
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="preload" href="/docs/assets/js/2.142d04d2.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.83ce04b6.js"><link rel="prefetch" href="/docs/assets/js/3.2b6c987b.js"><link rel="prefetch" href="/docs/assets/js/4.ad90d74a.js"><link rel="prefetch" href="/docs/assets/js/5.36121818.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.css">
<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">
</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><div class="sidebar-group collapsable"><p class="sidebar-heading"><span>内置插件</span><span class="arrow right"></span></p><!----></div></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></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">&quot;github.com/langhuihui/monibuca/monica&quot;</span>
@@ -161,13 +161,29 @@
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>正在该函数中会向源服务器建立tcp连接然后发送特定命令表示需要拉流当我们接收到源服务器的数据的时候就调用PushVideo和PushAudio函数来广播音视频。</p><p>核心逻辑是调用InputStream的Publish以及PushVideo、PushAudio函数</p><h2 id="开发钩子插件"><a href="#开发钩子插件" aria-hidden="true" class="header-anchor">#</a> 开发钩子插件</h2></div><div class="page-edit"><!----><!----></div><div class="page-nav"><p class="inner"><span class="prev">
</code></pre></div><p>正在该函数中会向源服务器建立tcp连接然后发送特定命令表示需要拉流当我们接收到源服务器的数据的时候就调用PushVideo和PushAudio函数来广播音视频。</p><p>核心逻辑是调用InputStream的Publish以及PushVideo、PushAudio函数</p><h2 id="开发钩子插件"><a href="#开发钩子插件" aria-hidden="true" class="header-anchor">#</a> 开发钩子插件</h2><p>钩子插件就是在服务器的关键逻辑处插入的函数调用,方便扩展服务器的功能,比如对连接进行验证,或者触发一些特殊的发布者。
目前提供的钩子包括</p><ul><li>当发布者开始发布时 <code>OnPublishHooks.AddHook(onPublish)</code>
例如:</li></ul><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">onPublish</span><span class="token punctuation">(</span>r <span class="token operator">*</span>Room<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> v <span class="token operator">:=</span> <span class="token keyword">range</span> r<span class="token punctuation">.</span>Subscribers <span class="token punctuation">{</span>
<span class="token keyword">if</span> err <span class="token operator">:=</span> <span class="token function">CheckSign</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span>Sign<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
v<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>此时可以访问房间里面的订阅者,对其进行验证。</p><ul><li>当有订阅者订阅了某个流时,<code>OnSubscribeHooks.AddHook(onSubscribe)</code>
例如:</li></ul><div class="language-go extra-class"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">onSubscribe</span><span class="token punctuation">(</span>s <span class="token operator">*</span>OutputStream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> s<span class="token punctuation">.</span>Publisher <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">go</span> <span class="token function">PullUpStream</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>StreamPath<span class="token punctuation">)</span>
<span class="token punctuation">}</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="prev">
<a href="/docs/" class="prev router-link-active">
起步
</a></span><span class="next"><a href="/docs/history.html">
更新日志
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.ad3166d6.js" defer></script><script src="/docs/assets/js/2.142d04d2.js" defer></script>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/2.190ec46a.js" defer></script>
</body>
</html>

View File

@@ -7,20 +7,20 @@
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="preload" href="/docs/assets/js/3.2b6c987b.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.83ce04b6.js"><link rel="prefetch" href="/docs/assets/js/2.142d04d2.js"><link rel="prefetch" href="/docs/assets/js/4.ad90d74a.js"><link rel="prefetch" href="/docs/assets/js/5.36121818.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.css">
<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">
</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><div class="sidebar-group collapsable"><p class="sidebar-heading"><span>内置插件</span><span class="arrow right"></span></p><!----></div></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
1.0完成</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></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">
<a href="/docs/develop.html" class="prev">
插件开发
</a></span><span class="next"><a href="/docs/plugins/jessica.html">
Jessica
</a></span><span class="next"><a href="/docs/plugins.html">
内置插件
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.ad3166d6.js" defer></script><script src="/docs/assets/js/3.2b6c987b.js" defer></script>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/3.7646e76c.js" defer></script>
</body>
</html>

View File

@@ -7,13 +7,13 @@
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="preload" href="/docs/assets/js/1.83ce04b6.js" as="script"><link rel="prefetch" href="/docs/assets/js/2.142d04d2.js"><link rel="prefetch" href="/docs/assets/js/3.2b6c987b.js"><link rel="prefetch" href="/docs/assets/js/4.ad90d74a.js"><link rel="prefetch" href="/docs/assets/js/5.36121818.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.css">
<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">
</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><div class="sidebar-group collapsable"><p class="sidebar-heading"><span>内置插件</span><span class="arrow right"></span></p><!----></div></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></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
@@ -49,6 +49,6 @@ Monibuca 提供了可供定制化开发的插件机制,可以任意扩展其
插件开发
</a>
</span></p></div></div></div></div>
<script src="/docs/assets/js/app.ad3166d6.js" defer></script><script src="/docs/assets/js/1.83ce04b6.js" defer></script>
<script src="/docs/assets/js/app.d51c815b.js" defer></script><script src="/docs/assets/js/1.6babbc1d.js" defer></script>
</body>
</html>

44
dashboard/dist/docs/plugins.html vendored Normal file
View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>内置插件介绍 | Monibuca</title>
<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">
</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播放器来进行播放
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>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:8080&quot;</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>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:1935&quot;</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>
<span class="token key property">Path</span><span class="token punctuation">=</span><span class="token string">&quot;./resource&quot;</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>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:2020&quot;</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用来监听从服务器的请求。
从服务器的配置是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">&quot;localhost:2019&quot;</span>
<span class="token key property">ListenAddr</span> <span class="token punctuation">=</span> <span class="token string">&quot;:2019&quot;</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">&quot;:80&quot;</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>
<span class="token key property">Key</span><span class="token punctuation">=</span><span class="token string">&quot;www.monibuca.com&quot;</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>
</body>
</html>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="preload" href="/docs/assets/js/4.ad90d74a.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.83ce04b6.js"><link rel="prefetch" href="/docs/assets/js/2.142d04d2.js"><link rel="prefetch" href="/docs/assets/js/3.2b6c987b.js"><link rel="prefetch" href="/docs/assets/js/5.36121818.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.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><div class="sidebar-group collapsable"><p class="sidebar-heading"><span>内置插件</span><span class="arrow right"></span></p><!----></div></li></ul></div><div class="page"><div class="content"></div><div class="page-edit"><!----><!----></div><!----></div></div></div>
<script src="/docs/assets/js/app.ad3166d6.js" defer></script><script src="/docs/assets/js/4.ad90d74a.js" defer></script>
</body>
</html>

View File

@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Jessica | Monibuca</title>
<meta name="description" content="">
<link rel="preload" href="/docs/assets/css/styles.ad3166d6.css" as="style"><link rel="preload" href="/docs/assets/js/app.ad3166d6.js" as="script"><link rel="preload" href="/docs/assets/js/5.36121818.js" as="script"><link rel="prefetch" href="/docs/assets/js/1.83ce04b6.js"><link rel="prefetch" href="/docs/assets/js/2.142d04d2.js"><link rel="prefetch" href="/docs/assets/js/3.2b6c987b.js"><link rel="prefetch" href="/docs/assets/js/4.ad90d74a.js">
<link rel="stylesheet" href="/docs/assets/css/styles.ad3166d6.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><div class="sidebar-group collapsable"><p class="sidebar-heading open"><span>内置插件</span><span class="arrow down"></span></p><ul class="sidebar-group-items"><li><a href="/docs/plugins/jessica.html" class="active sidebar-link">Jessica</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/docs/plugins/jessica.html#配置" class="sidebar-link">配置</a></li></ul></li></ul></div></li></ul></div><div class="page"><div class="content"><h1 id="jessica"><a href="#jessica" aria-hidden="true" class="header-anchor">#</a> Jessica</h1><p>该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放
Jessibua播放器已内置于源码中该播放器通过js解码H264并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。</p><h2 id="配置"><a href="#配置" aria-hidden="true" class="header-anchor">#</a> 配置</h2><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">&quot;:8080&quot;</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.ad3166d6.js" defer></script><script src="/docs/assets/js/5.36121818.js" defer></script>
</body>
</html>

View File

@@ -21,10 +21,10 @@ importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox
self.__precacheManifest = [
{
"url": "404.html",
"revision": "a3d9e915fd09958cab2da343fcad58b0"
"revision": "75925fb89348802ecd737cdad2d5d801"
},
{
"url": "assets/css/styles.ad3166d6.css",
"url": "assets/css/styles.d51c815b.css",
"revision": "4a6b650244e5b709f84a81ad0565b485"
},
{
@@ -32,48 +32,40 @@ self.__precacheManifest = [
"revision": "83621669651b9a3d4bf64d1a670ad856"
},
{
"url": "assets/js/1.83ce04b6.js",
"revision": "3cabebb5c79c8280aee24ac6e4545650"
"url": "assets/js/1.6babbc1d.js",
"revision": "0142763a4e8630af56b66f5fa8320c5c"
},
{
"url": "assets/js/2.142d04d2.js",
"revision": "027f4f643a10a465692035fe692cd94f"
"url": "assets/js/2.190ec46a.js",
"revision": "8dceb01ded85f36cc30e0e18371fe5d4"
},
{
"url": "assets/js/3.2b6c987b.js",
"revision": "27a988ab518e3f04db65045269d55841"
"url": "assets/js/3.7646e76c.js",
"revision": "378c41587710afd31466293c83a6c738"
},
{
"url": "assets/js/4.ad90d74a.js",
"revision": "dcbfc54b67e9e6e33dbb2a303e842bdc"
"url": "assets/js/4.08fbc0d9.js",
"revision": "0ea387538f5b25ef46237e3dcc1c1694"
},
{
"url": "assets/js/5.36121818.js",
"revision": "550520f0f388530dfbc4cc40a3dee264"
},
{
"url": "assets/js/app.ad3166d6.js",
"revision": "fdeb68e4b7b08c2c7bdcaf7462f34b16"
"url": "assets/js/app.d51c815b.js",
"revision": "467373b055c3575c2082a1b9ab1769fd"
},
{
"url": "develop.html",
"revision": "f7461297bdc3ce303d517f0fc6dedd78"
"revision": "92b13eb27581e4dc7008cf4205e5c215"
},
{
"url": "history.html",
"revision": "c4ba2b13246f7a9e0039852fe381de6e"
"revision": "ef202ac3bf63e1bdef479a05d49d5a8d"
},
{
"url": "index.html",
"revision": "3371a6066d0c2229a521d0659f25b174"
"revision": "306f1a0a8ceadb0c85dae2342f0bd637"
},
{
"url": "plugins/index.html",
"revision": "511b5cef98368f5f0ab35e667766e367"
},
{
"url": "plugins/jessica.html",
"revision": "65ca2965d5d514441d64b4d5e38cd696"
"url": "plugins.html",
"revision": "ac2ce38679fb75f05e8e501f35d161bd"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();

View File

@@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Monibuca</title><script src=jessibuca/ajax.js></script><script src=jessibuca/renderer.js></script><link href=/css/app.b2f5eee5.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.d4cf578d.js rel=preload as=script><link href=/js/chunk-vendors.ae8ac63d.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.b2f5eee5.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ae8ac63d.js></script><script src=/js/app.d4cf578d.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.d549056a.css rel=preload as=style><link href=/css/chunk-vendors.22ebf426.css rel=preload as=style><link href=/js/app.d6cffcca.js rel=preload as=script><link href=/js/chunk-vendors.ae8ac63d.js rel=preload as=script><link href=/css/chunk-vendors.22ebf426.css rel=stylesheet><link href=/css/app.d549056a.css rel=stylesheet></head><body><noscript><strong>We're sorry but dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ae8ac63d.js></script><script src=/js/app.d6cffcca.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dashboard/dist/js/app.d6cffcca.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/dist/js/app.d6cffcca.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,14 +5,8 @@ module.exports = {
sidebar: [
['/', '起步'],
['/develop', '插件开发'],
[ '/history', '更新日志' ],
{
title: '内置插件',
path: '/plugins/',
children: [
'/plugins/jessica'
]
},
['/history', '更新日志'],
['/plugins', '内置插件']
]
},
title: 'Monibuca',

View File

@@ -176,3 +176,29 @@ func PullUpStream(streamPath string) {
## 开发钩子插件
钩子插件就是在服务器的关键逻辑处插入的函数调用,方便扩展服务器的功能,比如对连接进行验证,或者触发一些特殊的发布者。
目前提供的钩子包括
- 当发布者开始发布时 `OnPublishHooks.AddHook(onPublish)`
例如:
```go
func onPublish(r *Room) {
for _, v := range r.Subscribers {
if err := CheckSign(v.Sign); err != nil {
v.Cancel()
}
}
}
```
此时可以访问房间里面的订阅者,对其进行验证。
- 当有订阅者订阅了某个流时,`OnSubscribeHooks.AddHook(onSubscribe)`
例如:
```go
func onSubscribe(s *OutputStream) {
if s.Publisher == nil {
go PullUpStream(s.StreamPath)
}
}
```
拉取源服务器的流

View File

@@ -1,4 +1,4 @@
# 更新历史
- 2020/1/27
1.0完成
初步完成

104
dashboard/docs/plugins.md Normal file
View File

@@ -0,0 +1,104 @@
# 内置插件介绍
内置插件为Monibuca提供了许多基础功能当然你完全可以不采用内置插件而改用自己开发的插件也丝毫不会影响您使用Monibuca。
## Jessica插件
> 该插件源码位于plugins/jessica下
该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放
Jessibua播放器已内置于源码中该播放器通过js解码H264/H265并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.Jessica]
ListenAddr = ":8080"
```
### Flv格式支持
Jessica以及Jessibuca也支持采用WebSocket中传输Flv格式的方式进行通讯目前有部分CDN厂商已经支持这种方式进行传输。
>私有协议以及Flv格式的判断是通过URL后缀是否带有.flv来进行判断
## Rtmp插件
> 该插件源码位于plugins/rtmp下
实现了基本的rtmp传输协议包括接收来自OBS、ffmpeg等软件的推流以及来在Flash Player播放器的拉流。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.RTMP]
ListenAddr = ":1935"
```
## RecordFlv插件
> 该插件源码位于plugins/record下
实现了录制Flv文件的功能并且支持再次使用录制好的Flv文件作为发布者进行发布。在Monibuca的web界面的控制台中提供了对房间进行录制的操作按钮以及列出所有已经录制的文件的界面。
### 配置
配置中的Path 表示要保存的Flv文件的根路径可以使用相对路径或者绝对路径
```toml
[Plugins.RecordFlv]
Path="./resource"
```
## Http-Flv插件
> 该插件位于plugins/HDL下
实现了http-flv格式的拉流功能方便对接CDN厂商
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.HDL]
ListenAddr = ":2020"
```
## Cluster插件
> 该插件源码位于plugins/cluster下
实现了基本的集群功能,里面包含一对发布者和订阅者,分别在主从服务器中启用,进行连接。
起基本原理就是在主服务器启动端口监听从服务器收到播放请求时如果从服务器没有对应的发布者则向主服务器发起请求主服务器收到来自从服务器的请求时将该请求作为一个订阅者。从服务器则把tcp连接作为发布者实现视频流的传递过程。
### 配置
主服务器的配置是ListenAddr用来监听从服务器的请求。
从服务器的配置是Master,表示主服务器的地址。
当然服务器可以既是主也是从,即充当中转站。
```toml
[Plugins.Cluster]
Master = "localhost:2019"
ListenAddr = ":2019"
```
## HLS插件
> 该插件源码位于plugins/HLS下
该插件的作用是请求M3u8文件进行解码最终将TS视频流转码成裸的视频流进行发布。
注意该插件目前并没有实现生成HLS的功能。
## 网关插件
> 该插件位于plugins/gateway下
该插件是为web控制台界面提供api用来采集服务器的信息。
### 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.GateWay]
ListenAddr = ":80"
```
如果80端口有其他用途可以换成别的端口比如有nginx反向代理。
## 校验插件
> 该插件位于plugins/auth下
该插件提供了基本的验证功能,其原理是
订阅流提供一个签名签名只可以使用一次把签名进行AES CBC 解密如果得到的解密字符串的前面部分就是和Key相同则通过验证。
### 配置
Key代表用来加密的Key
```toml
[Plugins.Auth]
Key="www.monibuca.com"
```

View File

@@ -1,10 +0,0 @@
# Jessica
该插件为基于WebSocket协议传输音视频的订阅者音视频数据以裸数据的形式进行传输我们需要Jessibuca播放器来进行播放
Jessibua播放器已内置于源码中该播放器通过js解码H264并用canvas进行渲染可以运行在几乎所有的终端浏览器上面。
在Monibuca的Web界面中预览功能就是使用的Jessibuca播放器。
## 配置
目前仅有的配置是监听的端口号
```toml
[Plugins.Jessica]
ListenAddr = ":8080"
```

View File

@@ -14,22 +14,22 @@
内置插件
</template>
<MenuGroup title="发布者/订阅者">
<MenuItem name="cluster">集群</MenuItem>
<MenuItem name="rtmp">RTMP</MenuItem>
<MenuItem name="cluster" to="/docs/plugins.html#cluster插件">集群</MenuItem>
<MenuItem name="rtmp" to="/docs/plugins.html#rtmp插件">RTMP</MenuItem>
</MenuGroup>
<MenuGroup title="订阅者">
<MenuItem name="jessica">Jessica</MenuItem>
<MenuItem name="HDL">Http-Flv</MenuItem>
<MenuItem name="record">录制Flv</MenuItem>
<MenuItem name="jessica" to="/docs/plugins.html#jessica插件">Jessica</MenuItem>
<MenuItem name="HDL" to="/docs/plugins.html#http-flv插件">Http-Flv</MenuItem>
<MenuItem name="record" to="/docs/plugins.html#recordflv插件">录制Flv</MenuItem>
</MenuGroup>
<MenuGroup title="发布者">
<MenuItem name="HLS">HLS</MenuItem>
<MenuItem name="TS">TS</MenuItem>
<MenuItem name="HLS" to="/docs/plugins.html#hls插件">HLS</MenuItem>
<MenuItem name="TS" to="/docs/plugins.html#hls插件">TS</MenuItem>
</MenuGroup>
<MenuGroup title="钩子">
<MenuItem name="Auth">验证</MenuItem>
<MenuItem name="Auth" to="/docs/plugins.html#校验插件">验证</MenuItem>
<MenuItem name="QoS">QoS</MenuItem>
<MenuItem name="gateway">网关</MenuItem>
<MenuItem name="gateway" to="/docs/plugins.html#网关插件">网关</MenuItem>
</MenuGroup>
</Submenu>
<MenuItem name="4" to="about">

View File

@@ -0,0 +1,29 @@
<template>
<div style="padding:0 15px">
<pre>{{config}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
config: ""
};
},
methods: {
onVisible(visible) {
if (visible) {
window.ajax.get(
"//" + location.host + "/api/config",
{},
x => (this.config = x)
);
}
}
}
};
</script>
<style>
</style>

View File

@@ -1,50 +1,60 @@
<template>
<Modal
v-bind="$attrs" draggable
v-on="$listeners"
:title="url"
@on-ok="onClosePreview"
@on-cancel="onClosePreview">
<canvas id="canvas" width="488" height="275" style="background: black"/>
</Modal>
<Modal
v-bind="$attrs"
draggable
v-on="$listeners"
:title="url"
@on-ok="onClosePreview"
@on-cancel="onClosePreview"
>
<canvas id="canvas" width="488" height="275" style="background: black" />
<div slot="footer">
<Button v-if="audioEnabled" @click="turnOff" icon="md-volume-off" />
<Button v-else @click="turnOn" icon="md-volume-up"></Button>
</div>
</Modal>
</template>
<script>
let h5lc = null;
export default {
name: 'Jessibuca',
props: {
audioEnabled: Boolean,
},
data(){
return {
url:""
}
},
watch: {
audioEnabled(value){
h5lc.audioEnabled(value)
}
},
mounted() {
h5lc = new window.Jessibuca({
canvas: document.getElementById("canvas"),
decoder: "jessibuca/ff.js"
});
},
destroyed() {
this.onClosePreview()
h5lc.destroy()
},
methods: {
play(url){
this.url = url
h5lc.play(url)
},
onClosePreview() {
h5lc.close();
},
}
let h5lc = null;
export default {
name: "Jessibuca",
data() {
return {
audioEnabled: false,
url: ""
};
},
watch: {
audioEnabled(value) {
h5lc.audioEnabled(value);
}
},
mounted() {
h5lc = new window.Jessibuca({
canvas: document.getElementById("canvas"),
decoder: "jessibuca/ff.js"
});
},
destroyed() {
this.onClosePreview();
h5lc.destroy();
},
methods: {
play(url) {
this.url = url;
h5lc.play(url);
},
onClosePreview() {
h5lc.close();
},
turnOn() {
this.audioEnabled = true;
},
turnOff() {
this.audioEnabled = false;
}
}
};
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div style="padding:0 15px">
<div>
自动滚动
<Switch v-model="autoScroll" />
</div>
<div ref="logContainer" class="log-container">
<pre><template v-for="item in $store.state.logs">{{item+"\n"}}</template></pre>
</div>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
data() {
return {
autoScroll: true
};
},
mounted() {
this.fetchLogs();
},
destroyed() {
this.stopFetchLogs();
},
methods: {
...mapActions(["fetchLogs", "stopFetchLogs"])
},
updated() {
if (this.autoScroll) {
this.$refs.logContainer.scrollTop = this.$refs.logContainer.offsetHeight;
}
}
};
</script>
<style>
.log-container {
overflow-y: auto;
max-height: 500px;
}
</style>

View File

@@ -1,21 +1,14 @@
<template>
<Modal v-bind="$attrs" draggable v-on="$listeners" title="录制的视频">
<List>
<ListItem v-for="item in data" :key="item">
<ListItemMeta :title="item.Path">
<template slot="description">{{toSizeStr(item.Size)}} {{toDurationStr(item.Duration)}}</template>
</ListItemMeta>
<template slot="action">
<li>
<a href="javascript:void(0)" @click="play(item)">Play</a>
</li>
<li>
<a href="javascript:void(0)">Delete</a>
</li>
</template>
</ListItem>
</List>
</Modal>
<div class="records">
<Card v-for="item in data" :key="item">
<p slot="title">{{item.Path}}</p>
<div slot="extra">
<Button @click="play(item)" icon="md-play" size="small"></Button>
<Button @click="deleteFlv(item)" icon="ios-trash" size="small"></Button>
</div>
{{toSizeStr(item.Size)}} {{toDurationStr(item.Duration)}}
</Card>
</div>
</template>
<script>
@@ -39,13 +32,34 @@ export default {
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.$Message.success("开始发布");
this.onVisible(true);
this.$Message.success("删除成功");
} else {
this.$Message.error(x);
}
}
);
},
deleteFlv(item) {
this.$Modal.confirm({
title: "提示",
content: "<p>是否删除Flv文件</p>",
onOk: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv/delete",
{ streamPath: item.Path.replace(".flv", "") },
x => {
if (x == "success") {
this.$Message.success("开始发布");
} else {
this.$Message.error(x);
}
}
);
},
onCancel: () => {}
});
},
toSizeStr(value, unit = "") {
if (value > 1024 && uintInc[unit]) {
return this.toSizeStr(value / 1024, uintInc[unit]);
@@ -70,19 +84,29 @@ export default {
} else {
return value + "ms";
}
}
},
mounted() {
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
},
onVisible(visible) {
if (visible) {
window.ajax.getJSON(
"//" + location.host + "/api/record/flv/list",
{},
x => {
this.data = x;
}
);
}
);
}
}
};
</script>
<style>
<style scoped>
.records {
display: flex;
flex-wrap: wrap;
padding: 0 15px;
}
.records > * {
width: 200px;
}
</style>

View File

@@ -3,40 +3,57 @@ import Vuex from 'vuex'
Vue.use(Vuex)
let summaryES = null
let logsES = null
export default new Vuex.Store({
state: {
summary:{
NetWork:[],
Rooms:[],
Memory:{
summary: {
Address: location.hostname,
NetWork: [],
Rooms: [],
Memory: {
Used: 0,
Usage: 0
},
CPUUsage:0,
HardDisk:{
CPUUsage: 0,
HardDisk: {
Used: 0,
Usage: 0
}
}
},
Children: {}
}, logs: []
},
mutations: {
update(state,payload){
Object.assign(state,payload)
update(state, payload) {
Object.assign(state, payload)
},
addLog(state, payload) {
state.logs.push(payload)
}
},
actions: {
fetchSummary({commit}){
fetchSummary({ commit }) {
summaryES = new EventSource(
"//" + location.host + "/api/summary"
"//" + location.host + "/api/summary"
);
summaryES.onmessage = evt=>{
summaryES.onmessage = evt => {
if (!evt.data) return
let summary = JSON.parse(evt.data)
commit("update",{summary})
commit("update", { summary })
}
},
stopFetchSummary(){
fetchLogs({ commit }) {
logsES = new EventSource(
"//" + location.host + "/api/logs"
)
logsES.onmessage = evt => {
if (!evt.data) return
commit("addLog", evt.data)
}
},
stopFetchLogs() {
logsES.close()
},
stopFetchSummary() {
summaryES.close()
}
},

View File

@@ -1,39 +1,50 @@
<template>
<div class="layout">
<ButtonGroup vertical>
<Button icon="ios-folder" @click="showRecords=true"></Button>
<Button icon="md-bug"></Button>
<Button icon="md-settings"></Button>
</ButtonGroup>
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
<StartTime slot="extra" :value="item.StartTime"></StartTime>
<p>
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
</p>
<p>
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
</p>
<ButtonGroup size="small">
<Button
@click="onShowDetail(item)"
icon="ios-people"
>{{item.SubscriberInfo?item.SubscriberInfo.length:0}}</Button>
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
<Button
@click="stopRecord(item)"
class="recording"
v-if="isRecording(item)"
icon="ios-radio-button-on"
></Button>
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
</div>
<div style="text-align:left;">
<Tabs v-model="currentTab" @on-click="onChangeTab">
<TabPane label="直播流" icon="md-videocam">
<div class="layout">
<Card v-for="item in Rooms" :key="item.StreamPath" class="room">
<p slot="title">{{typeMap[item.Type]||item.Type}}{{item.StreamPath}}</p>
<StartTime slot="extra" :value="item.StartTime"></StartTime>
<p>
{{SoundFormat(item.AudioInfo.SoundFormat)}} {{item.AudioInfo.PacketCount}}
{{SoundRate(item.AudioInfo.SoundRate)}} 声道:{{item.AudioInfo.SoundType}}
</p>
<p>
{{CodecID(item.VideoInfo.CodecID)}} {{item.VideoInfo.PacketCount}}
{{item.VideoInfo.SPSInfo.Width}}x{{item.VideoInfo.SPSInfo.Height}}
</p>
<ButtonGroup size="small">
<Button
@click="onShowDetail(item)"
icon="ios-people"
>{{item.SubscriberInfo?item.SubscriberInfo.length:0}}</Button>
<Button v-if="item.Type" @click="preview(item)" icon="md-eye"></Button>
<Button
@click="stopRecord(item)"
class="recording"
v-if="isRecording(item)"
icon="ios-radio-button-on"
></Button>
<Button @click="record(item)" v-else icon="ios-radio-button-on"></Button>
</ButtonGroup>
</Card>
<div v-if="Rooms.length==0" class="empty">
<Icon type="md-wine" size="50" />没有任何房间
</div>
</div>
</TabPane>
<TabPane label="集群总览" icon="ios-cloud"></TabPane>
<TabPane label="录制的视频" icon="ios-folder" name="recordsPanel">
<Records ref="recordsPanel" />
</TabPane>
<TabPane label="日志跟踪" icon="md-bug">
<Logs />
</TabPane>
<TabPane label="查看配置" icon="md-settings" name="configPanel">
<Config ref="configPanel" />
</TabPane>
</Tabs>
<div class="status">
<Alert>带宽消耗 📥{{totalInNetSpeed}} 📤{{totalOutNetSpeed}}</Alert>
<Alert
@@ -45,7 +56,6 @@
>磁盘使用{{networkFormat(HardDisk.Used,"M")}} 占比{{HardDisk.Usage.toFixed(2)}}%</Alert>
</div>
<Jessibuca ref="jessibuca" v-model="showPreview"></Jessibuca>
<Records v-model="showRecords" />
</div>
</template>
@@ -54,6 +64,9 @@ import { mapActions, mapState } from "vuex";
import Jessibuca from "../components/Jessibuca";
import StartTime from "../components/StartTime";
import Records from "../components/Records";
import Logs from "../components/Logs";
import Config from "../components/Config";
const uintInc = {
"": "K",
K: "M",
@@ -91,14 +104,16 @@ export default {
components: {
Jessibuca,
StartTime,
Records
Records,
Logs,
Config
},
data() {
return {
showPreview: false,
showRecords: false,
currentTab: "",
typeMap: {
FlvFile:"🎥",
FlvFile: "🎥",
TS: "🎬",
HLS: "🍎",
"": "⏳",
@@ -169,17 +184,36 @@ export default {
return rate > 1000 ? rate / 1000 + "kHz" : rate + "Hz";
},
record(item) {
window.ajax.get(
"//" + location.host + "/api/record/flv",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
} else {
this.$Message.error(x);
}
this.$Modal.confirm({
title: "提示",
content: "<p>是否使用追加模式</p><small>选择取消将覆盖已有文件</small>",
onOk: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv?append=true",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制(追加模式)");
} else {
this.$Message.error(x);
}
}
);
},
onCancel: () => {
window.ajax.get(
"//" + location.host + "/api/record/flv",
{ streamPath: item.StreamPath },
x => {
if (x == "success") {
this.$Message.success("开始录制");
} else {
this.$Message.error(x);
}
}
);
}
);
});
},
stopRecord(item) {
window.ajax.get(
@@ -199,6 +233,15 @@ export default {
item.SubscriberInfo &&
item.SubscriberInfo.find(x => x.Type == "FlvRecord")
);
},
onChangeTab(name) {
switch (name) {
case "recordsPanel":
this.$refs.recordsPanel.onVisible(true);
break;
case "configPanel":
this.$refs.configPanel.onVisible(true);
}
}
},
mounted() {

View File

@@ -54,3 +54,16 @@ func (h OnDropHook) Trigger(s *OutputStream) {
h(s)
}
}
var OnSummaryHooks = make(OnSummaryHook, 0)
type OnSummaryHook []func(bool)
func (h OnSummaryHook) AddHook(hook func(bool)) {
OnSummaryHooks = append(h, hook)
}
func (h OnSummaryHook) Trigger(v bool) {
for _, h := range h {
h(v)
}
}

View File

@@ -3,11 +3,18 @@ package monica
import (
"encoding/json"
"github.com/BurntSushi/toml"
"io/ioutil"
"log"
)
var ConfigRaw []byte
func Run(configFile string) (err error) {
if _, err = toml.DecodeFile(configFile, cg); err == nil {
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
return
}
go Summary.StartSummary()
if _, err = toml.Decode(string(ConfigRaw), cg); err == nil {
for name, config := range plugins {
if cfg, ok := cg.Plugins[name]; ok {
b, _ := json.Marshal(cfg)

View File

@@ -111,11 +111,13 @@ func (r *Room) Run() {
case <-r.Done():
return
case <-update.C:
r.SubscriberInfo = make([]*SubscriberInfo, len(r.Subscribers))
i := 0
for _, v := range r.Subscribers {
r.SubscriberInfo[i] = &v.SubscriberInfo
i++
if Summary.Running() {
r.SubscriberInfo = make([]*SubscriberInfo, len(r.Subscribers))
i := 0
for _, v := range r.Subscribers {
r.SubscriberInfo[i] = &v.SubscriberInfo
i++
}
}
case s := <-r.Control:
switch v := s.(type) {

139
monica/summary.go Normal file
View File

@@ -0,0 +1,139 @@
package monica
import (
"time"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
)
var (
Summary = ServerSummary{}
)
type ServerSummary struct {
Address string
Memory struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
CPUUsage float64
HardDisk struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
NetWork []NetWorkInfo
Rooms []*RoomInfo
lastNetWork []NetWorkInfo
ref int
control chan bool
reportChan chan *ServerSummary
Children map[string]*ServerSummary
}
type NetWorkInfo struct {
Name string
Receive uint64
Sent uint64
ReceiveSpeed uint64
SentSpeed uint64
}
func (s *ServerSummary) StartSummary() {
ticker := time.NewTicker(time.Second)
s.control = make(chan bool)
s.reportChan = make(chan *ServerSummary)
for {
select {
case <-ticker.C:
if s.ref > 0 {
Summary.collect()
}
case v := <-s.control:
if v {
if s.ref++; s.ref == 1 {
OnSummaryHooks.Trigger(true)
}
} else {
if s.ref--; s.ref == 0 {
s.lastNetWork = nil
OnSummaryHooks.Trigger(false)
}
}
case report := <-s.reportChan:
s.Children[report.Address] = report
}
}
}
func (s *ServerSummary) Running() bool {
return s.ref > 0
}
func (s *ServerSummary) Add() {
s.control <- true
}
func (s *ServerSummary) Done() {
s.control <- false
}
func (s *ServerSummary) Report(slave *ServerSummary) {
s.reportChan <- slave
}
func (s *ServerSummary) collect() {
v, _ := mem.VirtualMemory()
//c, _ := cpu.Info()
cc, _ := cpu.Percent(time.Second, false)
d, _ := disk.Usage("/")
//n, _ := host.Info()
nv, _ := net.IOCounters(true)
//boottime, _ := host.BootTime()
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
s.Memory.Total = v.Total / 1024 / 1024
s.Memory.Free = v.Available / 1024 / 1024
s.Memory.Used = v.Used / 1024 / 1024
s.Memory.Usage = v.UsedPercent
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
//if len(c) > 1 {
// for _, sub_cpu := range c {
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
// }
//} else {
// sub_cpu := c[0]
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
//}
s.CPUUsage = cc[0]
s.HardDisk.Free = d.Free / 1024 / 1024 / 1024
s.HardDisk.Total = d.Total / 1024 / 1024 / 1024
s.HardDisk.Used = d.Used / 1024 / 1024 / 1024
s.HardDisk.Usage = d.UsedPercent
s.NetWork = make([]NetWorkInfo, len(nv))
for i, n := range nv {
s.NetWork[i].Name = n.Name
s.NetWork[i].Receive = n.BytesRecv
s.NetWork[i].Sent = n.BytesSent
if s.lastNetWork != nil {
s.NetWork[i].ReceiveSpeed = n.BytesRecv - s.lastNetWork[i].Receive
s.NetWork[i].SentSpeed = n.BytesSent - s.lastNetWork[i].Sent
}
}
s.lastNetWork = s.NetWork
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
//fmt.Printf(" SystemBoot:%v\n", btime)
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
//fmt.Printf(" Hostname : %v \n", n.Hostname)
s.Rooms = nil
AllRoom.Range(func(key interface{}, v interface{}) bool {
s.Rooms = append(s.Rooms, &v.(*Room).RoomInfo)
return true
})
return
}

View File

@@ -1,8 +1,15 @@
package cluster
import (
. "github.com/langhuihui/monibuca/monica"
"bufio"
"encoding/json"
"log"
"math/rand"
"net"
"sync"
"time"
. "github.com/langhuihui/monibuca/monica"
)
const (
@@ -11,12 +18,18 @@ const (
MSG_VIDEO
MSG_SUBSCRIBE
MSG_AUTH
MSG_SUMMARY
MSG_LOG
)
var config = struct {
Master string
ListenAddr string
}{}
var (
config = struct {
Master string
ListenAddr string
}{}
slaves = sync.Map{}
masterConn *net.TCPConn
)
func init() {
InstallPlugin(&PluginConfig{
@@ -29,12 +42,88 @@ func init() {
func run() {
if config.Master != "" {
OnSubscribeHooks.AddHook(onSubscribe)
addr, err := net.ResolveTCPAddr("tcp", config.Master)
if MayBeError(err) {
return
}
masterConn, err = net.DialTCP("tcp", nil, addr)
if MayBeError(err) {
return
}
go readMaster()
}
if config.ListenAddr != "" {
OnSummaryHooks.AddHook(onSummary)
log.Printf("server bare start at %s", config.ListenAddr)
log.Fatal(ListenBare(config.ListenAddr))
}
}
func readMaster() {
var err error
defer func() {
for {
time.Sleep(time.Second*5 + time.Duration(rand.Int63n(5))*time.Second)
addr, _ := net.ResolveTCPAddr("tcp", config.Master)
if masterConn, err = net.DialTCP("tcp", nil, addr); err == nil {
go readMaster()
return
}
}
}()
brw := bufio.NewReadWriter(bufio.NewReader(masterConn), bufio.NewWriter(masterConn))
//首次报告
if b, err := json.Marshal(Summary); err == nil {
_, err = masterConn.Write(b)
}
for {
cmd, err := brw.ReadByte()
if err != nil {
return
}
switch cmd {
case MSG_SUMMARY: //收到主服务器指令,进行采集和上报
if cmd, err = brw.ReadByte(); err != nil {
return
}
if cmd == 1 {
Summary.Add()
go onReport()
} else {
Summary.Done()
}
}
}
}
//定时上报
func onReport() {
for range time.NewTicker(time.Second).C {
if Summary.Running() {
if b, err := json.Marshal(Summary); err == nil {
data := make([]byte, len(b)+2)
data[0] = MSG_SUMMARY
copy(data[1:], b)
data[len(data)-1] = 0
_, err = masterConn.Write(data)
}
} else {
return
}
}
}
//通知从服务器需要上报或者关闭上报
func onSummary(start bool) {
slaves.Range(func(k, v interface{}) bool {
conn := v.(*net.TCPConn)
b := []byte{MSG_SUMMARY, 0}
if start {
b[1] = 1
}
conn.Write(b)
return true
})
}
func onSubscribe(s *OutputStream) {
if s.Publisher == nil {

View File

@@ -45,6 +45,7 @@ func (p *Receiver) readAVPacket(avType byte) (av *pool.AVPacket, err error) {
pool.RecycleSlice(buf)
return
}
func PullUpStream(streamPath string) {
addr, err := net.ResolveTCPAddr("tcp", config.Master)
if MayBeError(err) {

View File

@@ -3,13 +3,15 @@ package cluster
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/pool"
"net"
"strconv"
"strings"
"time"
. "github.com/langhuihui/monibuca/monica"
"github.com/langhuihui/monibuca/monica/pool"
)
func ListenBare(addr string) error {
@@ -49,6 +51,7 @@ func ListenBare(addr string) error {
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
connAddr := conn.RemoteAddr().String()
stream := OutputStream{
SendHandler: func(p *pool.SendPacket) error {
head := pool.GetSlice(9)
@@ -64,7 +67,7 @@ func process(conn net.Conn) {
}
return nil
}, SubscriberInfo: SubscriberInfo{
ID: conn.RemoteAddr().String(),
ID: connAddr,
Type: "Bare",
},
}
@@ -73,31 +76,36 @@ func process(conn net.Conn) {
if err != nil {
return
}
bytes, err := reader.ReadBytes(0)
if err != nil {
return
}
switch cmd {
case MSG_SUBSCRIBE:
if stream.Room != nil {
fmt.Printf("bare stream already exist from %s", conn.RemoteAddr())
return
}
bytes, err := reader.ReadBytes(0)
if MayBeError(err) {
return
}
streamName := string(bytes[0 : len(bytes)-1])
stream.Play(streamName)
go stream.Play(streamName)
case MSG_AUTH:
bytes, err := reader.ReadBytes(0)
if err != nil {
print(err)
return
}
sign := strings.Split(string(bytes[0:len(bytes)-1]), ",")
head := []byte{MSG_AUTH, 0}
head := []byte{MSG_AUTH, 2}
if len(sign) > 1 && AuthHooks.Trigger(sign[1]) == nil {
head[1] = 1
}
conn.Write(head)
conn.Write(bytes)
case MSG_SUMMARY: //收到从服务器发来报告,加入摘要中
var summary *ServerSummary
if err = json.Unmarshal(bytes, summary); err == nil {
summary.Address = connAddr
Summary.Report(summary)
if _, ok := slaves.Load(connAddr); !ok {
slaves.Store(connAddr, conn)
defer slaves.Delete(connAddr)
}
}
default:
fmt.Printf("bare receive unknown cmd:%d from %s", cmd, conn.RemoteAddr())
return

View File

@@ -3,11 +3,6 @@ package gateway
import (
"context"
"encoding/json"
. "github.com/langhuihui/monibuca/monica"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
"io/ioutil"
"log"
"mime"
@@ -16,6 +11,8 @@ import (
"path"
"runtime"
"time"
. "github.com/langhuihui/monibuca/monica"
)
var (
@@ -84,24 +81,34 @@ func init() {
})
}
func run() {
http.HandleFunc("/api/stop", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
if b, ok := AllRoom.Load(streamPath); ok {
b.(*Room).Cancel()
w.Write([]byte("success"))
} else {
w.Write([]byte("no query stream"))
}
} else {
w.Write([]byte("no such stream"))
}
})
http.HandleFunc("/api/stop", stopPublish)
http.HandleFunc("/api/summary", summary)
http.HandleFunc("/api/logs", watchLogs)
http.HandleFunc("/api/config", getConfig)
http.HandleFunc("/", website)
log.Printf("server gateway start at %s", config.ListenAddr)
log.Fatal(http.ListenAndServe(config.ListenAddr, nil))
}
func getConfig(w http.ResponseWriter, r *http.Request) {
w.Write(ConfigRaw)
}
func watchLogs(w http.ResponseWriter, r *http.Request) {
AddWriter(NewSSE(w, r.Context()))
<-r.Context().Done()
}
func stopPublish(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
if b, ok := AllRoom.Load(streamPath); ok {
b.(*Room).Cancel()
w.Write([]byte("success"))
} else {
w.Write([]byte("no query stream"))
}
} else {
w.Write([]byte("no such stream"))
}
}
func website(w http.ResponseWriter, r *http.Request) {
filePath := r.URL.Path
if filePath == "/" {
@@ -123,93 +130,19 @@ func website(w http.ResponseWriter, r *http.Request) {
}
func summary(w http.ResponseWriter, r *http.Request) {
sse := NewSSE(w, r.Context())
s := collect()
sse.WriteJSON(&s)
for range time.NewTicker(time.Second).C {
old := s
s = collect()
for i, v := range s.NetWork {
s.NetWork[i].ReceiveSpeed = v.Receive - old.NetWork[i].Receive
s.NetWork[i].SentSpeed = v.Sent - old.NetWork[i].Sent
}
AllRoom.Range(func(key interface{}, v interface{}) bool {
s.Rooms = append(s.Rooms, &v.(*Room).RoomInfo)
return true
})
if sse.WriteJSON(&s) != nil {
break
Summary.Add()
defer Summary.Done()
sse.WriteJSON(&Summary)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if sse.WriteJSON(&Summary) != nil {
return
}
case <-r.Context().Done():
return
}
}
}
type Summary struct {
Memory struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
CPUUsage float64
HardDisk struct {
Total uint64
Free uint64
Used uint64
Usage float64
}
NetWork []NetWorkInfo
Rooms []*RoomInfo
}
type NetWorkInfo struct {
Name string
Receive uint64
Sent uint64
ReceiveSpeed uint64
SentSpeed uint64
}
func collect() (s Summary) {
v, _ := mem.VirtualMemory()
//c, _ := cpu.Info()
cc, _ := cpu.Percent(time.Second, false)
d, _ := disk.Usage("/")
//n, _ := host.Info()
nv, _ := net.IOCounters(true)
//boottime, _ := host.BootTime()
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
s.Memory.Total = v.Total / 1024 / 1024
s.Memory.Free = v.Available / 1024 / 1024
s.Memory.Used = v.Used / 1024 / 1024
s.Memory.Usage = v.UsedPercent
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
//if len(c) > 1 {
// for _, sub_cpu := range c {
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
// }
//} else {
// sub_cpu := c[0]
// modelname := sub_cpu.ModelName
// cores := sub_cpu.Cores
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
//}
s.CPUUsage = cc[0]
s.HardDisk.Free = d.Free / 1024 / 1024 / 1024
s.HardDisk.Total = d.Total / 1024 / 1024 / 1024
s.HardDisk.Used = d.Used / 1024 / 1024 / 1024
s.HardDisk.Usage = d.UsedPercent
s.NetWork = make([]NetWorkInfo, len(nv))
for i, n := range nv {
s.NetWork[i].Name = n.Name
s.NetWork[i].Receive = n.BytesRecv
s.NetWork[i].Sent = n.BytesSent
}
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
//fmt.Printf(" SystemBoot:%v\n", btime)
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
//fmt.Printf(" Hostname : %v \n", n.Hostname)
return
}

View File

@@ -74,15 +74,31 @@ func run() {
w.Write([]byte("no such stream"))
}
})
http.HandleFunc("/api/record/flv/play", func(writer http.ResponseWriter, r *http.Request) {
http.HandleFunc("/api/record/flv/play", func(w http.ResponseWriter, r *http.Request) {
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
if err := PublishFlvFile(streamPath); err != nil {
writer.Write([]byte(err.Error()))
w.Write([]byte(err.Error()))
} else {
writer.Write([]byte("success"))
w.Write([]byte("success"))
}
} else {
writer.Write([]byte("no streamPath"))
w.Write([]byte("no streamPath"))
}
})
http.HandleFunc("/api/record/flv/delete", func(w http.ResponseWriter, r *http.Request) {
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
filePath := config.Path + streamPath + ".flv"
if PathExists(filePath) {
if err := os.Remove(filePath); err != nil {
w.Write([]byte(err.Error()))
} else {
w.Write([]byte("success"))
}
} else {
w.Write([]byte("no such file"))
}
} else {
w.Write([]byte("no streamPath"))
}
})
}