fix: skip an unconfigured go2rtc under the Auto player

With its player left on Auto, a monitor with go2rtc enabled had
selectNextPlayer() pick go2rtc_webrtc first, and with ZM_GO2RTC_PATH
empty select_go2rtc() then alerted "ZM_GO2RTC_PATH is empty", on the
montage for every such monitor, although nobody had chosen go2rtc.

The fallback chain already skipped go2rtc for a monitor without it
enabled; skip it when ZM_GO2RTC_PATH is empty too. A monitor or browser
that chose go2rtc reaches select_go2rtc() directly and still gets the
alert.

A node test drives selectPlayer() through the restart chain for Auto
without a path, Auto with one, and go2rtc chosen explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Isaac ConnorandClaude Opus 5.5 committed 2026-10-05 18:36:33 -04:00
1 parent a41eac5d95
commit 575e5484b3
2 files changed
+125 -1

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
'use strict';
// A monitor with go2rtc enabled and its player left on Auto tried go2rtc first,
// and with ZM_GO2RTC_PATH empty select_go2rtc() alerted "ZM_GO2RTC_PATH is empty"
// on every page that showed it, montage included, although nobody had asked for
// go2rtc. Under Auto an unconfigured go2rtc is now skipped for the next player;
// the alert stays for a monitor or browser that chose go2rtc explicitly.
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const src = fs.readFileSync(
path.join(__dirname, '../../web/js/MonitorStream.js'), 'utf8');
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(' ok ' + name);
passed++;
} catch (e) {
console.error(' FAIL ' + name);
console.error(' ' + (e.stack || e.message));
failed++;
}
}
function makeChainable() {
const proxy = new Proxy(function() {}, {
get: (target, prop) => (prop === 'then' ? undefined : proxy),
apply: () => proxy,
});
return proxy;
}
function loadMonitorStream(go2rtcPath) {
const globals = {
ZM_GO2RTC_PATH: go2rtcPath,
parseInt, Number, Object, Math, String, Array, JSON,
alerts: [],
alert(msg) {
this.alerts.push(msg);
},
$j: makeChainable(),
$: makeChainable(),
console: {log() {}, warn() {}, error() {}, debug() {}},
currentView: 'watch',
getCookie: () => 'true',
setCookie: () => {},
document: {
getElementById: () => null,
querySelector: () => null,
createElement: () => ({}),
},
setTimeout: () => 0,
clearTimeout: () => 0,
setInterval: () => 0,
clearInterval: () => 0,
};
const sandbox = new Proxy(globals, {
has: () => true, // make every bare identifier resolve against this object
get: (target, prop) => (prop === Symbol.unscopables ? undefined : target[prop]),
});
globals.window = sandbox;
vm.createContext(sandbox);
vm.runInContext(src, sandbox, {filename: 'MonitorStream.js'});
assert.strictEqual(typeof globals.MonitorStream, 'function',
'MonitorStream.js did not define MonitorStream');
return globals;
}
// Build a go2rtc-enabled monitor and record which player selectPlayer() starts.
function select(go2rtcPath, defaultPlayer) {
const sandbox = loadMonitorStream(go2rtcPath);
const monitor = new sandbox.MonitorStream({
id: 2, name: 'test', connKey: null, url: '', url_to_zms: '',
width: 640, height: 480, Go2RTCEnabled: true, DefaultPlayer: defaultPlayer,
});
const started = [];
monitor.isActive = true;
monitor.select_go2rtc = () => started.push('go2rtc');
monitor.select_janus = () => started.push('janus');
monitor.select_rtsp2web = () => started.push('rtsp2web');
monitor.select_zms = () => started.push('zms');
// selectNextPlayer() switches by setting this.player and restarting, and
// restart() comes back through selectPlayer() with the new player.
let depth = 0;
monitor.restart = () => {
started.push('restart:' + monitor.player);
if (++depth < 10) monitor.selectPlayer('default');
};
monitor.selectPlayer('default');
return {started, alerts: sandbox.alerts};
}
console.log('MonitorStream player selection with go2rtc unconfigured');
test('Auto with no ZM_GO2RTC_PATH does not try go2rtc or alert', () => {
const {started, alerts} = select('', '');
assert.ok(!started.includes('go2rtc'), 'started go2rtc: ' + started.join(','));
assert.deepStrictEqual(alerts, []);
assert.ok(started.includes('zms'), 'did not fall back to zms: ' + started.join(','));
});
test('go2rtc chosen for the monitor still tries go2rtc', () => {
const {started} = select('', 'go2rtc');
assert.deepStrictEqual(started, ['go2rtc']);
});
test('Auto with ZM_GO2RTC_PATH set uses go2rtc', () => {
const {started, alerts} = select('http://localhost:1984', '');
assert.ok(started.includes('go2rtc'), 'did not start go2rtc: ' + started.join(','));
assert.deepStrictEqual(alerts, []);
});
console.log('\n' + passed + ' passed, ' + failed + ' failed');
process.exit(failed ? 1 : 0);
+4 -1
View File
@@ -2319,7 +2319,10 @@ function MonitorStream(monitorData) {
while (idx !== -1 && idx + 1 < keys.length) {
const nextKey = keys[++idx];
const nextName = this.playerPriority[nextKey]['name'];
if (nextName.indexOf('go2rtc') !== -1 && !this.Go2RTCEnabled) continue;
// Skip an unconfigured go2rtc here rather than letting select_go2rtc() alert:
// this is the Auto path, so nobody chose go2rtc. A monitor or browser that
// did choose it reaches select_go2rtc() directly and still gets the alert.
if (nextName.indexOf('go2rtc') !== -1 && (!this.Go2RTCEnabled || !ZM_GO2RTC_PATH)) continue;
if (nextName.indexOf('rtsp2web') !== -1 && !this.RTSP2WebEnabled) continue;
if (nextName.indexOf('janus') !== -1 && !this.janusEnabled) continue;
if (this.selectedPlayer === 'go2rtc' && nextName.indexOf('go2rtc') === -1 && nextName.indexOf('zms') === -1 ) continue;