Skip to content

Commit f5fce0e

Browse files
authored
Merge pull request #28 from kyled-/nimble
Added nimble streamer in SRT receiver mode as a media server
2 parents 30fa4ae + bf5b5f5 commit f5fce0e

File tree

6 files changed

+221
-35
lines changed

6 files changed

+221
-35
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,40 @@ Modify the RTMP section in config.json like this to connect to a node-media-serv
144144
},
145145
```
146146

147+
## Using nimble SRT instead of nginx rtmp
148+
149+
Nimble must have [API access enabled](https://wmspanel.com/nimble/api) and be configured as a SRT receiver - see ["Set up receiving of SRT"](https://blog.wmspanel.com/2017/07/setup-srt-secure-reliable-transport-nimble-streamer.html) and have an outgoing stream ("Add outgoing stream" on same page)
150+
151+
Modify the RTMP section in config.json to this:
152+
153+
```
154+
"rtmp": {
155+
"server": "nimble",
156+
"stats": "http://nimble:8082",
157+
"id": "0.0.0.0:1234",
158+
"application": "live",
159+
"key": "srt"
160+
},
161+
```
162+
163+
- `stats`: URL to nimble API
164+
- `id`: UDP listener ID (Usually IP:Port)
165+
- `application`: Outgoing stream "Application Name"
166+
- `key`: Outgoing stream "Stream Name"
167+
168+
---
169+
170+
Switches on low bitrate or high RTT (high RTT seems to be a more accurate way of determining if the stream is bad with this)
171+
172+
You can change the high RTT trigger value inside config.json:
173+
174+
```
175+
"obs": {
176+
...
177+
"highRttTrigger": 2500,
178+
},
179+
```
180+
147181
## Help it won't change scenes
148182

149183
It will only change scenes when OBS is set on a scene that's in the config.

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"lowBitrateScene": "lowbitratedetected",
88
"refreshScene": "REFRESH",
99
"lowBitrateTrigger": 1000,
10+
"highRttTrigger": 2500,
1011
"refreshSceneInterval": 10000,
1112
"requestMs": 2000,
1213
"onlySwitchWhenStreaming": true

lib/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/components/ObsSwitcher.js

Lines changed: 113 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
2121

2222
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2323

24+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
25+
26+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
27+
28+
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
29+
30+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
31+
2432
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
2533

2634
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
@@ -58,6 +66,8 @@ function (_EventEmitter) {
5866
function ObsSwitcher(address, password, low, normal, offline, lowBitrateTrigger) {
5967
var _this;
6068

69+
var highRttTrigger = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 2500;
70+
6171
_classCallCheck(this, ObsSwitcher);
6272

6373
_this = _possibleConstructorReturn(this, _getPrototypeOf(ObsSwitcher).call(this));
@@ -69,6 +79,7 @@ function (_EventEmitter) {
6979
_this.normalScene = normal;
7080
_this.offlineScene = offline;
7181
_this.lowBitrateTrigger = lowBitrateTrigger;
82+
_this.highRttTrigger = highRttTrigger;
7283
_this.bitrate = null;
7384
_this.nginxVideoMeta = null;
7485
_this.streamStatus = null;
@@ -113,7 +124,7 @@ function (_EventEmitter) {
113124
var _switchSceneIfNecessary = _asyncToGenerator(
114125
/*#__PURE__*/
115126
regeneratorRuntime.mark(function _callee() {
116-
var bitrate, _ref, currentScene, canSwitch;
127+
var _ref, _ref2, bitrate, rtt, _ref3, currentScene, canSwitch;
117128

118129
return regeneratorRuntime.wrap(function _callee$(_context) {
119130
while (1) {
@@ -131,32 +142,48 @@ function (_EventEmitter) {
131142
return this.getBitrate();
132143

133144
case 4:
134-
bitrate = _context.sent;
135-
_context.next = 7;
145+
_ref = _context.sent;
146+
_ref2 = _slicedToArray(_ref, 2);
147+
bitrate = _ref2[0];
148+
rtt = _ref2[1];
149+
_context.next = 10;
136150
return this.canSwitch();
137151

138-
case 7:
139-
_ref = _context.sent;
140-
currentScene = _ref.currentScene;
141-
canSwitch = _ref.canSwitch;
152+
case 10:
153+
_ref3 = _context.sent;
154+
currentScene = _ref3.currentScene;
155+
canSwitch = _ref3.canSwitch;
142156

143157
if (bitrate !== null) {
144158
this.isLive = true;
145-
this.isLive && canSwitch && (bitrate === 0 && currentScene.name !== this.previousScene && (this.obs.setCurrentScene({
146-
"scene-name": this.previousScene
147-
}), this.switchSceneEmit("live", this.previousScene), log.info("Stream went online switching to scene: \"".concat(this.previousScene, "\""))), bitrate <= this.lowBitrateTrigger && currentScene.name !== this.lowBitrateScene && bitrate !== 0 && (this.obs.setCurrentScene({
148-
"scene-name": this.lowBitrateScene
149-
}), this.previousScene = this.lowBitrateScene, this.switchSceneEmit("lowBitrateScene"), log.info("Low bitrate detected switching to scene: \"".concat(this.lowBitrateScene, "\""))), bitrate > this.lowBitrateTrigger && currentScene.name !== this.normalScene && (this.obs.setCurrentScene({
150-
"scene-name": this.normalScene
151-
}), this.previousScene = this.normalScene, this.switchSceneEmit("normalScene"), log.info("Switching to normal scene: \"".concat(this.normalScene, "\""))));
159+
160+
if (_config.default.rtmp.server === "nimble") {
161+
this.isLive && canSwitch && (bitrate === 0 && currentScene.name !== this.previousScene && (this.obs.setCurrentScene({
162+
"scene-name": this.previousScene
163+
}), this.switchSceneEmit("live", this.previousScene), log.info("Stream went online switching to scene: \"".concat(this.previousScene, "\""))), (rtt < this.highRttTrigger || rtt >= this.highRttTrigger) && bitrate <= this.lowBitrateTrigger && currentScene.name !== this.lowBitrateScene && bitrate !== 0 && (this.obs.setCurrentScene({
164+
"scene-name": this.lowBitrateScene
165+
}), this.previousScene = this.lowBitrateScene, this.switchSceneEmit("lowBitrateScene"), log.info("Low bitrate detected switching to scene: \"".concat(this.lowBitrateScene, "\""))), rtt >= this.highRttTrigger && bitrate > this.lowBitrateTrigger && currentScene.name !== this.lowBitrateScene && bitrate !== 0 && (this.obs.setCurrentScene({
166+
"scene-name": this.lowBitrateScene
167+
}), this.previousScene = this.lowBitrateScene, this.switchSceneEmit("lowBitrateScene"), log.info("High RTT detected switching to scene: \"".concat(this.lowBitrateScene, "\""))), rtt < this.highRttTrigger && bitrate > this.lowBitrateTrigger && currentScene.name !== this.normalScene && (this.obs.setCurrentScene({
168+
"scene-name": this.normalScene
169+
}), this.previousScene = this.normalScene, this.switchSceneEmit("normalScene"), log.info("Switching to normal scene: \"".concat(this.normalScene, "\""))));
170+
} else {
171+
this.isLive && canSwitch && (bitrate === 0 && currentScene.name !== this.previousScene && (this.obs.setCurrentScene({
172+
"scene-name": this.previousScene
173+
}), this.switchSceneEmit("live", this.previousScene), log.info("Stream went online switching to scene: \"".concat(this.previousScene, "\""))), bitrate <= this.lowBitrateTrigger && currentScene.name !== this.lowBitrateScene && bitrate !== 0 && (this.obs.setCurrentScene({
174+
"scene-name": this.lowBitrateScene
175+
}), this.previousScene = this.lowBitrateScene, this.switchSceneEmit("lowBitrateScene"), log.info("Low bitrate detected switching to scene: \"".concat(this.lowBitrateScene, "\""))), bitrate > this.lowBitrateTrigger && currentScene.name !== this.normalScene && (this.obs.setCurrentScene({
176+
"scene-name": this.normalScene
177+
}), this.previousScene = this.normalScene, this.switchSceneEmit("normalScene"), log.info("Switching to normal scene: \"".concat(this.normalScene, "\""))));
178+
}
152179
} else {
153180
this.isLive = false;
154181
canSwitch && currentScene.name !== this.offlineScene && (this.obs.setCurrentScene({
155182
"scene-name": this.offlineScene
156183
}), this.switchSceneEmit("offlineScene"), this.streamStatus = null, log.warn("Error receiving current bitrate or stream is offline. Switching to offline scene: \"".concat(this.offlineScene, "\"")));
157184
}
158185

159-
case 11:
186+
case 14:
160187
case "end":
161188
return _context.stop();
162189
}
@@ -195,15 +222,15 @@ function (_EventEmitter) {
195222
regeneratorRuntime.mark(function _callee2() {
196223
var _this2 = this;
197224

198-
var _config$rtmp, server, stats, application, key, response, data, _response, _data;
225+
var _config$rtmp, server, stats, application, key, id, response, data, _response, _data, srtresponse, srtdata, srtreceiver, publish, rtmpresponse, rtmpdata, rtmpstream;
199226

200227
return regeneratorRuntime.wrap(function _callee2$(_context2) {
201228
while (1) {
202229
switch (_context2.prev = _context2.next) {
203230
case 0:
204-
_config$rtmp = _config.default.rtmp, server = _config$rtmp.server, stats = _config$rtmp.stats, application = _config$rtmp.application, key = _config$rtmp.key;
231+
_config$rtmp = _config.default.rtmp, server = _config$rtmp.server, stats = _config$rtmp.stats, application = _config$rtmp.application, key = _config$rtmp.key, id = _config$rtmp.id;
205232
_context2.t0 = server;
206-
_context2.next = _context2.t0 === "nginx" ? 4 : _context2.t0 === "node-media-server" ? 18 : 32;
233+
_context2.next = _context2.t0 === "nginx" ? 4 : _context2.t0 === "node-media-server" ? 18 : _context2.t0 === "nimble" ? 32 : 61;
207234
break;
208235

209236
case 4:
@@ -243,7 +270,7 @@ function (_EventEmitter) {
243270
log.error("[NGINX] Error fetching stats");
244271

245272
case 17:
246-
return _context2.abrupt("break", 34);
273+
return _context2.abrupt("break", 63);
247274

248275
case 18:
249276
_context2.prev = 18;
@@ -267,21 +294,79 @@ function (_EventEmitter) {
267294
log.error("[NMS] Error fetching stats, is the API http server running?");
268295

269296
case 31:
270-
return _context2.abrupt("break", 34);
297+
return _context2.abrupt("break", 63);
271298

272299
case 32:
300+
_context2.prev = 32;
301+
_context2.next = 35;
302+
return (0, _nodeFetch.default)(stats + "/manage/srt_receiver_stats");
303+
304+
case 35:
305+
srtresponse = _context2.sent;
306+
_context2.next = 38;
307+
return srtresponse.json();
308+
309+
case 38:
310+
srtdata = _context2.sent;
311+
srtreceiver = srtdata.SrtReceivers.filter(function (receiver) {
312+
return receiver.id == id;
313+
});
314+
publish = srtreceiver[0].state;
315+
316+
if (!(publish == "disconnected")) {
317+
_context2.next = 46;
318+
break;
319+
}
320+
321+
this.bitrate = null;
322+
this.rtt = null;
323+
_context2.next = 55;
324+
break;
325+
326+
case 46:
327+
_context2.next = 48;
328+
return (0, _nodeFetch.default)(stats + "/manage/rtmp_status");
329+
330+
case 48:
331+
rtmpresponse = _context2.sent;
332+
_context2.next = 51;
333+
return rtmpresponse.json();
334+
335+
case 51:
336+
rtmpdata = _context2.sent;
337+
rtmpstream = rtmpdata.filter(function (rtmp) {
338+
return rtmp.app == application;
339+
})[0].streams.filter(function (stream) {
340+
return stream.strm == key;
341+
});
342+
this.bitrate = Math.round(rtmpstream[0].bandwidth / 1024);
343+
this.rtt = srtreceiver[0].stats.link.rtt;
344+
345+
case 55:
346+
_context2.next = 60;
347+
break;
348+
349+
case 57:
350+
_context2.prev = 57;
351+
_context2.t3 = _context2["catch"](32);
352+
log.error("[NIMBLE] Error fetching stats: " + _context2.t3);
353+
354+
case 60:
355+
return _context2.abrupt("break", 63);
356+
357+
case 61:
273358
log.error("[STATS] Something went wrong at getting the RTMP server, did you enter the correct name in the config?");
274-
return _context2.abrupt("break", 34);
359+
return _context2.abrupt("break", 63);
275360

276-
case 34:
277-
return _context2.abrupt("return", this.bitrate);
361+
case 63:
362+
return _context2.abrupt("return", [this.bitrate, this.rtt]);
278363

279-
case 35:
364+
case 64:
280365
case "end":
281366
return _context2.stop();
282367
}
283368
}
284-
}, _callee2, this, [[4, 14], [18, 28]]);
369+
}, _callee2, this, [[4, 14], [18, 28], [32, 57]]);
285370
}));
286371

287372
function getBitrate() {
@@ -332,7 +417,7 @@ function (_EventEmitter) {
332417
var _streamStopped = _asyncToGenerator(
333418
/*#__PURE__*/
334419
regeneratorRuntime.mark(function _callee3() {
335-
var _ref2, canSwitch;
420+
var _ref4, canSwitch;
336421

337422
return regeneratorRuntime.wrap(function _callee3$(_context3) {
338423
while (1) {
@@ -345,8 +430,8 @@ function (_EventEmitter) {
345430
return this.canSwitch();
346431

347432
case 5:
348-
_ref2 = _context3.sent;
349-
canSwitch = _ref2.canSwitch;
433+
_ref4 = _context3.sent;
434+
canSwitch = _ref4.canSwitch;
350435

351436
if (canSwitch) {
352437
this.obs.setCurrentScene({

src/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const obs = new ObsSwitcher(
1919
config.obs.lowBitrateScene,
2020
config.obs.normalScene,
2121
config.obs.offlineScene,
22-
config.obs.lowBitrateTrigger
22+
config.obs.lowBitrateTrigger,
23+
config.obs.highRttTrigger
2324
);
2425

2526
if (config.twitchChat.enable) {

0 commit comments

Comments
 (0)