openvibers/openvibe.live Commits
Lockfile, and the two tests that were failing for reasons of their own
Changes
@@@ -24,6 +24,7 @@"mediasoup-client": "^3.15.7","multer": "^1.4.5-lts.1","node-media-server": "^2.7.1",++ "onnxruntime-node": "^1.20.0","openvibe-shared": "file:./vendor/openvibe-shared","sdp-transform": "^3.0.0","sharp": "^0.33.0",@@@ -829,6 +830,15 @@"node": ">= 0.6"}},++ "node_modules/adm-zip": {++ "version": "0.5.18",++ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz",++ "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==",++ "license": "MIT",++ "engines": {++ "node": ">=12.0"++ }++ },"node_modules/ansi-styles": {"version": "4.3.0","resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",@@@ -1302,6 +1312,40 @@"node": ">=4.0.0"}},++ "node_modules/define-data-property": {++ "version": "1.1.4",++ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",++ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",++ "license": "MIT",++ "dependencies": {++ "es-define-property": "^1.0.0",++ "es-errors": "^1.3.0",++ "gopd": "^1.0.1"++ },++ "engines": {++ "node": ">= 0.4"++ },++ "funding": {++ "url": "https://github.com/sponsors/ljharb"++ }++ },++ "node_modules/define-properties": {++ "version": "1.2.1",++ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",++ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",++ "license": "MIT",++ "dependencies": {++ "define-data-property": "^1.0.1",++ "has-property-descriptors": "^1.0.0",++ "object-keys": "^1.1.1"++ },++ "engines": {++ "node": ">= 0.4"++ },++ "funding": {++ "url": "https://github.com/sponsors/ljharb"++ }++ },"node_modules/depd": {"version": "2.0.0","resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",@@@ -1451,6 +1495,18 @@"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==","license": "MIT"},++ "node_modules/escape-string-regexp": {++ "version": "4.0.0",++ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",++ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",++ "license": "MIT",++ "engines": {++ "node": ">=10"++ },++ "funding": {++ "url": "https://github.com/sponsors/sindresorhus"++ }++ },"node_modules/etag": {"version": "1.8.1","resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",@@@ -1718,6 +1774,37 @@"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==","license": "MIT"},++ "node_modules/global-agent": {++ "version": "4.1.3",++ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.3.tgz",++ "integrity": "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==",++ "license": "BSD-3-Clause",++ "dependencies": {++ "globalthis": "^1.0.2",++ "matcher": "^4.0.0",++ "semver": "^7.3.5",++ "serialize-error": "^8.1.0"++ },++ "engines": {++ "node": ">=10.0"++ }++ },++ "node_modules/globalthis": {++ "version": "1.0.4",++ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",++ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",++ "license": "MIT",++ "dependencies": {++ "define-properties": "^1.2.1",++ "gopd": "^1.0.1"++ },++ "engines": {++ "node": ">= 0.4"++ },++ "funding": {++ "url": "https://github.com/sponsors/ljharb"++ }++ },"node_modules/gopd": {"version": "1.2.0","resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",@@@ -1778,6 +1865,18 @@"node": ">=8"}},++ "node_modules/has-property-descriptors": {++ "version": "1.0.2",++ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",++ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",++ "license": "MIT",++ "dependencies": {++ "es-define-property": "^1.0.0"++ },++ "funding": {++ "url": "https://github.com/sponsors/ljharb"++ }++ },"node_modules/has-symbols": {"version": "1.1.0","resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",@@@ -2050,6 +2149,21 @@"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==","license": "MIT"},++ "node_modules/matcher": {++ "version": "4.0.0",++ "resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz",++ "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",++ "license": "MIT",++ "dependencies": {++ "escape-string-regexp": "^4.0.0"++ },++ "engines": {++ "node": ">=10"++ },++ "funding": {++ "url": "https://github.com/sponsors/sindresorhus"++ }++ },"node_modules/math-intrinsics": {"version": "1.1.0","resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",@@@ -2475,6 +2589,15 @@"url": "https://github.com/sponsors/ljharb"}},++ "node_modules/object-keys": {++ "version": "1.1.1",++ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",++ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",++ "license": "MIT",++ "engines": {++ "node": ">= 0.4"++ }++ },"node_modules/on-finished": {"version": "2.4.1","resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",@@@ -2496,6 +2619,29 @@"wrappy": "1"}},++ "node_modules/onnxruntime-common": {++ "version": "1.27.0",++ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.27.0.tgz",++ "integrity": "sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==",++ "license": "MIT"++ },++ "node_modules/onnxruntime-node": {++ "version": "1.27.0",++ "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.27.0.tgz",++ "integrity": "sha512-QEzGwrvNBgv4uPVdnbHsOGG4G6T96mdlcFI8aAKPjMU8wOPpVocPXb6k3QGkaZagVTv2G9Bnnbo6Z3JdXr1fQw==",++ "hasInstallScript": true,++ "license": "MIT",++ "os": [++ "win32",++ "darwin",++ "linux"++ ],++ "dependencies": {++ "adm-zip": "^0.5.16",++ "global-agent": "^4.1.3",++ "onnxruntime-common": "1.27.0"++ }++ },"node_modules/openvibe-shared": {"resolved": "vendor/openvibe-shared","link": true@@@ -2754,6 +2900,21 @@"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","license": "MIT"},++ "node_modules/serialize-error": {++ "version": "8.1.0",++ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",++ "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",++ "license": "MIT",++ "dependencies": {++ "type-fest": "^0.20.2"++ },++ "engines": {++ "node": ">=10"++ },++ "funding": {++ "url": "https://github.com/sponsors/sindresorhus"++ }++ },"node_modules/serve-static": {"version": "1.16.3","resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",@@@ -3123,6 +3284,18 @@"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==","license": "Unlicense"},++ "node_modules/type-fest": {++ "version": "0.20.2",++ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",++ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",++ "license": "(MIT OR CC0-1.0)",++ "engines": {++ "node": ">=10"++ },++ "funding": {++ "url": "https://github.com/sponsors/sindresorhus"++ }++ },"node_modules/type-is": {"version": "1.6.18","resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",@@@ -12,7 +12,7 @@* F. WHIP ICE disconnect: grace timer + explicit producer-removed emit in cleanupSession* G. Broadcast-server: ICE-state filter + stale-source path + watch-queued message* H. Client: sfu-source-unavailable and watch-queued handled without P2P offer timeout-- * I. Client: frozen-video detector starts after play, stops on transport change++ * I. Client: frozen-video detector starts after play, escalates keyframe -> rebuild* J. Broadcaster auto-publishes into SFU and legacy P2P is explicitly gated* K. Server startup logs TURN / announced-IP diagnostics for SFU viewers*/@@@ -53,9 +53,12 @@ assert.ok(sfuSrc.includes("consumer.kind === 'video'") && sfuSrc.includes('requestKeyFrame'),'webrtc-sfu.js must guard requestKeyFrame() behind kind === video');++// Anchored on the shape of the line, not its exact wording: the retry that now sends a++// second keyframe request numbers each attempt, and pinning the old sentence made this++// fail on a change that strengthened the very behaviour it is here to protect.assert.ok(-- sfuSrc.includes('Keyframe requested for video consumer'),-- 'webrtc-sfu.js must log keyframe request for diagnostics'++ /Keyframe request[^\n]*sent for video consumer/.test(sfuSrc),++ 'webrtc-sfu.js must log the keyframe request for diagnostics');console.log('OK B: webrtc-sfu.js consume() calls requestKeyFrame() for video consumers');@@@ -200,12 +203,24 @@ console.log('OK H: sfu-source-unavailable and watch-queued handled without P2P o// ── I: frozen-video detector in SFU path ───────────────────────assert.ok(-- playerSrc.includes('_sfuFrozenInterval') && playerSrc.includes('_frozenTicks'),++ playerSrc.includes('_sfuFrozenInterval'),'stream-player.js must have a frozen-video detector (_sfuFrozenInterval)');++// The detector used to tick to 30s and log. It now escalates in two stages, because a++// frozen picture with audio still playing is usually one missing keyframe: ask for one++// first, and only rebuild the transport if that does not unstick it. Assert the two++// stages rather than the old tick counter and its wording.assert.ok(-- playerSrc.includes('video frozen for ~30s'),-- 'stream-player.js frozen detector must log after 30s of frozen video'++ /STALL_TRIGGER_MS\s*=\s*\d+/.test(playerSrc) && /STALL_ESCALATE_MS\s*=\s*\d+/.test(playerSrc),++ 'frozen detector must have a trigger threshold and a later escalation threshold'++);++assert.ok(++ /requesting keyframe via re-watch/.test(playerSrc),++ 'stage 1 must ask for a keyframe before anything more disruptive'++);++assert.ok(++ /still frozen after keyframe nudge — rebuilding receive transport/.test(playerSrc),++ 'stage 2 must rebuild the receive transport when the keyframe did not help');assert.ok(playerSrc.includes('player._sfuFrozenInterval = null') &&@@@ -217,7 +232,7 @@ assert.ok(playerSrc.includes('Intentionally NOT calling _startWatchOfferTimeout()'),'stream-player.js frozen-video re-watch must NOT call _startWatchOfferTimeout()');--console.log('OK I: frozen-video detector (30s post-play check) present in stream-player.js');++console.log('OK I: frozen-video detector nudges with a keyframe, then rebuilds the transport');// ── J: broadcaster auto-publishes into SFU, P2P rollback gated ──assert.ok(@@@ -1,4 +1,15 @@const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++++// cleanupSession looks the stream up to decide what to tear down, so requiring the WHIP++// handler pulls in the database. Point it at a throwaway file with a real schema BEFORE++// that require: the test used to open whatever data/live.db happened to be in the working++// tree, which passed on a developed checkout and threw "no such table: streams" on a++// fresh one — a property of the machine, not of the code under test.++process.env.DB_PATH = path.join(os.tmpdir(), `ov-whip-cleanup-${process.pid}.db`);++require('../server/db/database').initDb();const webrtcSFU = require('../server/streaming/webrtc-sfu');const whipHandler = require('../server/streaming/whip-handler');@@@ -37,4 +48,7 @@ assert.strictEqual(room.transports.has('whip-test-transport-1'), false, 'transpoassert.strictEqual(producerClosed.value, true, 'producer should be closed');assert.strictEqual(transportClosed.value, true, 'transport should be closed');++try { fs.unlinkSync(process.env.DB_PATH); } catch { /* */ }++for (const ext of ['-wal', '-shm']) { try { fs.unlinkSync(process.env.DB_PATH + ext); } catch { /* */ } }++console.log('✅ WHIP cleanup session regression test passed');
@@ -24,6 +24,7 @@
@@ -24,6 +24,7 @@
"mediasoup-client": "^3.15.7",
"mediasoup-client": "^3.15.7",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"node-media-server": "^2.7.1",
"node-media-server": "^2.7.1",
+ "onnxruntime-node": "^1.20.0",
"openvibe-shared": "file:./vendor/openvibe-shared",
"openvibe-shared": "file:./vendor/openvibe-shared",
"sdp-transform": "^3.0.0",
"sdp-transform": "^3.0.0",
"sharp": "^0.33.0",
"sharp": "^0.33.0",
@@ -829,6 +830,15 @@
@@ -829,6 +830,15 @@
"node": ">= 0.6"
"node": ">= 0.6"
}
}
},
},
+ "node_modules/adm-zip": {
+ "version": "0.5.18",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz",
+ "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
"node_modules/ansi-styles": {
"node_modules/ansi-styles": {
"version": "4.3.0",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -1302,6 +1312,40 @@
@@ -1302,6 +1312,40 @@
"node": ">=4.0.0"
"node": ">=4.0.0"
}
}
},
},
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/depd": {
"node_modules/depd": {
"version": "2.0.0",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1451,6 +1495,18 @@
@@ -1451,6 +1495,18 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
"license": "MIT"
},
},
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/etag": {
"node_modules/etag": {
"version": "1.8.1",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1718,6 +1774,37 @@
@@ -1718,6 +1774,37 @@
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
"license": "MIT"
},
},
+ "node_modules/global-agent": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.3.tgz",
+ "integrity": "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "globalthis": "^1.0.2",
+ "matcher": "^4.0.0",
+ "semver": "^7.3.5",
+ "serialize-error": "^8.1.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gopd": {
"node_modules/gopd": {
"version": "1.2.0",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1778,6 +1865,18 @@
@@ -1778,6 +1865,18 @@
"node": ">=8"
"node": ">=8"
}
}
},
},
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-symbols": {
"node_modules/has-symbols": {
"version": "1.1.0",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -2050,6 +2149,21 @@
@@ -2050,6 +2149,21 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
"license": "MIT"
},
},
+ "node_modules/matcher": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz",
+ "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/math-intrinsics": {
"node_modules/math-intrinsics": {
"version": "1.1.0",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2475,6 +2589,15 @@
@@ -2475,6 +2589,15 @@
"url": "https://github.com/sponsors/ljharb"
"url": "https://github.com/sponsors/ljharb"
}
}
},
},
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/on-finished": {
"node_modules/on-finished": {
"version": "2.4.1",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -2496,6 +2619,29 @@
@@ -2496,6 +2619,29 @@
"wrappy": "1"
"wrappy": "1"
}
}
},
},
+ "node_modules/onnxruntime-common": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.27.0.tgz",
+ "integrity": "sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==",
+ "license": "MIT"
+ },
+ "node_modules/onnxruntime-node": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.27.0.tgz",
+ "integrity": "sha512-QEzGwrvNBgv4uPVdnbHsOGG4G6T96mdlcFI8aAKPjMU8wOPpVocPXb6k3QGkaZagVTv2G9Bnnbo6Z3JdXr1fQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "os": [
+ "win32",
+ "darwin",
+ "linux"
+ ],
+ "dependencies": {
+ "adm-zip": "^0.5.16",
+ "global-agent": "^4.1.3",
+ "onnxruntime-common": "1.27.0"
+ }
+ },
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
"resolved": "vendor/openvibe-shared",
"resolved": "vendor/openvibe-shared",
"link": true
"link": true
@@ -2754,6 +2900,21 @@
@@ -2754,6 +2900,21 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
"license": "MIT"
},
},
+ "node_modules/serialize-error": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
+ "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/serve-static": {
"node_modules/serve-static": {
"version": "1.16.3",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
@@ -3123,6 +3284,18 @@
@@ -3123,6 +3284,18 @@
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"license": "Unlicense"
"license": "Unlicense"
},
},
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/type-is": {
"node_modules/type-is": {
"version": "1.6.18",
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -12,7 +12,7 @@
@@ -12,7 +12,7 @@
* F. WHIP ICE disconnect: grace timer + explicit producer-removed emit in cleanupSession
* F. WHIP ICE disconnect: grace timer + explicit producer-removed emit in cleanupSession
* G. Broadcast-server: ICE-state filter + stale-source path + watch-queued message
* G. Broadcast-server: ICE-state filter + stale-source path + watch-queued message
* H. Client: sfu-source-unavailable and watch-queued handled without P2P offer timeout
* H. Client: sfu-source-unavailable and watch-queued handled without P2P offer timeout
- * I. Client: frozen-video detector starts after play, stops on transport change
+ * I. Client: frozen-video detector starts after play, escalates keyframe -> rebuild
* J. Broadcaster auto-publishes into SFU and legacy P2P is explicitly gated
* J. Broadcaster auto-publishes into SFU and legacy P2P is explicitly gated
* K. Server startup logs TURN / announced-IP diagnostics for SFU viewers
* K. Server startup logs TURN / announced-IP diagnostics for SFU viewers
*/
*/
@@ -53,9 +53,12 @@ assert.ok(
@@ -53,9 +53,12 @@ assert.ok(
sfuSrc.includes("consumer.kind === 'video'") && sfuSrc.includes('requestKeyFrame'),
sfuSrc.includes("consumer.kind === 'video'") && sfuSrc.includes('requestKeyFrame'),
'webrtc-sfu.js must guard requestKeyFrame() behind kind === video'
'webrtc-sfu.js must guard requestKeyFrame() behind kind === video'
);
);
+// Anchored on the shape of the line, not its exact wording: the retry that now sends a
+// second keyframe request numbers each attempt, and pinning the old sentence made this
+// fail on a change that strengthened the very behaviour it is here to protect.
assert.ok(
assert.ok(
- sfuSrc.includes('Keyframe requested for video consumer'),
- 'webrtc-sfu.js must log keyframe request for diagnostics'
+ /Keyframe request[^\n]*sent for video consumer/.test(sfuSrc),
+ 'webrtc-sfu.js must log the keyframe request for diagnostics'
);
);
console.log('OK B: webrtc-sfu.js consume() calls requestKeyFrame() for video consumers');
console.log('OK B: webrtc-sfu.js consume() calls requestKeyFrame() for video consumers');
@@ -200,12 +203,24 @@ console.log('OK H: sfu-source-unavailable and watch-queued handled without P2P o
@@ -200,12 +203,24 @@ console.log('OK H: sfu-source-unavailable and watch-queued handled without P2P o
// ── I: frozen-video detector in SFU path ───────────────────────
// ── I: frozen-video detector in SFU path ───────────────────────
assert.ok(
assert.ok(
- playerSrc.includes('_sfuFrozenInterval') && playerSrc.includes('_frozenTicks'),
+ playerSrc.includes('_sfuFrozenInterval'),
'stream-player.js must have a frozen-video detector (_sfuFrozenInterval)'
'stream-player.js must have a frozen-video detector (_sfuFrozenInterval)'
);
);
+// The detector used to tick to 30s and log. It now escalates in two stages, because a
+// frozen picture with audio still playing is usually one missing keyframe: ask for one
+// first, and only rebuild the transport if that does not unstick it. Assert the two
+// stages rather than the old tick counter and its wording.
assert.ok(
assert.ok(
- playerSrc.includes('video frozen for ~30s'),
- 'stream-player.js frozen detector must log after 30s of frozen video'
+ /STALL_TRIGGER_MS\s*=\s*\d+/.test(playerSrc) && /STALL_ESCALATE_MS\s*=\s*\d+/.test(playerSrc),
+ 'frozen detector must have a trigger threshold and a later escalation threshold'
+);
+assert.ok(
+ /requesting keyframe via re-watch/.test(playerSrc),
+ 'stage 1 must ask for a keyframe before anything more disruptive'
+);
+assert.ok(
+ /still frozen after keyframe nudge — rebuilding receive transport/.test(playerSrc),
+ 'stage 2 must rebuild the receive transport when the keyframe did not help'
);
);
assert.ok(
assert.ok(
playerSrc.includes('player._sfuFrozenInterval = null') &&
playerSrc.includes('player._sfuFrozenInterval = null') &&
@@ -217,7 +232,7 @@ assert.ok(
@@ -217,7 +232,7 @@ assert.ok(
playerSrc.includes('Intentionally NOT calling _startWatchOfferTimeout()'),
playerSrc.includes('Intentionally NOT calling _startWatchOfferTimeout()'),
'stream-player.js frozen-video re-watch must NOT call _startWatchOfferTimeout()'
'stream-player.js frozen-video re-watch must NOT call _startWatchOfferTimeout()'
);
);
-console.log('OK I: frozen-video detector (30s post-play check) present in stream-player.js');
+console.log('OK I: frozen-video detector nudges with a keyframe, then rebuilds the transport');
// ── J: broadcaster auto-publishes into SFU, P2P rollback gated ──
// ── J: broadcaster auto-publishes into SFU, P2P rollback gated ──
assert.ok(
assert.ok(
@@ -1,4 +1,15 @@
@@ -1,4 +1,15 @@
const assert = require('assert');
const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+// cleanupSession looks the stream up to decide what to tear down, so requiring the WHIP
+// handler pulls in the database. Point it at a throwaway file with a real schema BEFORE
+// that require: the test used to open whatever data/live.db happened to be in the working
+// tree, which passed on a developed checkout and threw "no such table: streams" on a
+// fresh one — a property of the machine, not of the code under test.
+process.env.DB_PATH = path.join(os.tmpdir(), `ov-whip-cleanup-${process.pid}.db`);
+require('../server/db/database').initDb();
const webrtcSFU = require('../server/streaming/webrtc-sfu');
const webrtcSFU = require('../server/streaming/webrtc-sfu');
const whipHandler = require('../server/streaming/whip-handler');
const whipHandler = require('../server/streaming/whip-handler');
@@ -37,4 +48,7 @@ assert.strictEqual(room.transports.has('whip-test-transport-1'), false, 'transpo
@@ -37,4 +48,7 @@ assert.strictEqual(room.transports.has('whip-test-transport-1'), false, 'transpo
assert.strictEqual(producerClosed.value, true, 'producer should be closed');
assert.strictEqual(producerClosed.value, true, 'producer should be closed');
assert.strictEqual(transportClosed.value, true, 'transport should be closed');
assert.strictEqual(transportClosed.value, true, 'transport should be closed');
+try { fs.unlinkSync(process.env.DB_PATH); } catch { /* */ }
+for (const ext of ['-wal', '-shm']) { try { fs.unlinkSync(process.env.DB_PATH + ext); } catch { /* */ } }
+
console.log('✅ WHIP cleanup session regression test passed');
console.log('✅ WHIP cleanup session regression test passed');