livedesk 0.1.367 → 0.1.369
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hub/src/mode4-atlas-pool.js +31 -8
- package/hub/src/mode4-atlas.js +5 -1
- package/hub/src/remote-hub.js +859 -357
- package/hub/src/server.js +131 -60
- package/package.json +5 -5
- package/web/dist/assets/index-CvOqfC-o.js +16 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-BGyMH9o5.js +0 -16
package/hub/src/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import { WebSocketServer } from 'ws';
|
|
11
11
|
import { createRemoteHub } from './remote-hub.js';
|
|
12
|
-
import { buildMode4AtlasSessionKey, Mode4AtlasPool } from './mode4-atlas-pool.js';
|
|
12
|
+
import { buildMode4AtlasSessionKey, Mode4AtlasPool, planMode4AtlasInputTransitions } from './mode4-atlas-pool.js';
|
|
13
13
|
import { createHubFilesystem } from './filesystem/hub-filesystem.js';
|
|
14
14
|
import { createHubTransferJobs } from './filesystem/transfer-jobs.js';
|
|
15
15
|
import { createHubSharedFolders } from './filesystem/shared-folders.js';
|
|
@@ -155,8 +155,8 @@ function readPositiveIntegerEnv(name, fallback) {
|
|
|
155
155
|
return Number.isFinite(value) && value > 0 ? Math.round(value) : fallback;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function handleRemoteHubEvent(type, event) {
|
|
159
|
-
liveDeskUpdateManager?.handleRemoteEvent(type, event);
|
|
158
|
+
function handleRemoteHubEvent(type, event) {
|
|
159
|
+
liveDeskUpdateManager?.handleRemoteEvent(type, event);
|
|
160
160
|
if (type === 'RemoteInputApplied') {
|
|
161
161
|
for (const ws of inputClients) {
|
|
162
162
|
sendJson(ws, {
|
|
@@ -166,16 +166,16 @@ function handleRemoteHubEvent(type, event) {
|
|
|
166
166
|
}
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
|
-
if (type === 'RemoteInputError') {
|
|
169
|
+
if (type === 'RemoteInputError') {
|
|
170
170
|
for (const ws of inputClients) {
|
|
171
171
|
sendJson(ws, {
|
|
172
172
|
type: 'RemoteInputError',
|
|
173
173
|
...event
|
|
174
174
|
});
|
|
175
|
-
}
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
|
|
179
179
|
connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
|
|
180
180
|
}
|
|
181
181
|
if (type !== 'RemoteDeviceConnected') {
|
|
@@ -1365,7 +1365,7 @@ function safeWebSocketSend(ws, payload, options = undefined) {
|
|
|
1365
1365
|
}
|
|
1366
1366
|
}
|
|
1367
1367
|
|
|
1368
|
-
function unregisterFrameClient(ws) {
|
|
1368
|
+
function unregisterFrameClient(ws) {
|
|
1369
1369
|
if (!ws) {
|
|
1370
1370
|
return;
|
|
1371
1371
|
}
|
|
@@ -1380,10 +1380,11 @@ function unregisterFrameClient(ws) {
|
|
|
1380
1380
|
if (clients.size === 0) {
|
|
1381
1381
|
frameClientsByDeviceId.delete(deviceId);
|
|
1382
1382
|
}
|
|
1383
|
-
}
|
|
1384
|
-
frameWildcardClients.delete(ws);
|
|
1385
|
-
ws.
|
|
1386
|
-
|
|
1383
|
+
}
|
|
1384
|
+
frameWildcardClients.delete(ws);
|
|
1385
|
+
ws.liveDeskExpectedStreamBindingsByDeviceId?.clear?.();
|
|
1386
|
+
ws.liveDeskFrameSendLane?.queue?.splice?.(0);
|
|
1387
|
+
}
|
|
1387
1388
|
|
|
1388
1389
|
function hasOtherFrameStreamOwner(ws, deviceId, streamId, streamPurpose = '') {
|
|
1389
1390
|
if (!streamId) {
|
|
@@ -1617,7 +1618,7 @@ function enqueueFramePacketForClient(ws, packet, meta) {
|
|
|
1617
1618
|
return true;
|
|
1618
1619
|
}
|
|
1619
1620
|
|
|
1620
|
-
function updateFrameSubscription(ws, payload = {}) {
|
|
1621
|
+
function updateFrameSubscription(ws, payload = {}) {
|
|
1621
1622
|
const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
|
|
1622
1623
|
? new Set(ws.liveDeskDeviceIds)
|
|
1623
1624
|
: new Set();
|
|
@@ -1627,14 +1628,18 @@ function updateFrameSubscription(ws, payload = {}) {
|
|
|
1627
1628
|
registerFrameClient(ws);
|
|
1628
1629
|
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
|
|
1629
1630
|
ws.liveDeskLiveOptions = normalizeLiveOptions(payload);
|
|
1630
|
-
if (!(ws.liveDeskStreamIdsByDeviceId instanceof Map)) {
|
|
1631
|
-
ws.liveDeskStreamIdsByDeviceId = new Map();
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1631
|
+
if (!(ws.liveDeskStreamIdsByDeviceId instanceof Map)) {
|
|
1632
|
+
ws.liveDeskStreamIdsByDeviceId = new Map();
|
|
1633
|
+
}
|
|
1634
|
+
if (!(ws.liveDeskExpectedStreamBindingsByDeviceId instanceof Map)) {
|
|
1635
|
+
ws.liveDeskExpectedStreamBindingsByDeviceId = new Map();
|
|
1636
|
+
}
|
|
1637
|
+
for (const subscribedDeviceId of ws.liveDeskStreamIdsByDeviceId.keys()) {
|
|
1638
|
+
if (!ws.liveDeskDeviceIds.has(subscribedDeviceId)) {
|
|
1639
|
+
ws.liveDeskStreamIdsByDeviceId.delete(subscribedDeviceId);
|
|
1640
|
+
ws.liveDeskExpectedStreamBindingsByDeviceId.delete(subscribedDeviceId);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1638
1643
|
sendJson(ws, {
|
|
1639
1644
|
type: 'RemoteFrameSubscription',
|
|
1640
1645
|
timestamp: new Date().toISOString(),
|
|
@@ -1688,15 +1693,46 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '',
|
|
|
1688
1693
|
reuseExisting: liveOptions.forceRestart !== true
|
|
1689
1694
|
&& (liveOptions.reuseExisting === true || reason === 'subscribe' || reason === 'watchdog'),
|
|
1690
1695
|
silentReuse: reason === 'watchdog' && liveOptions.forceRestart !== true
|
|
1691
|
-
});
|
|
1692
|
-
if (result?.ok) {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1696
|
+
});
|
|
1697
|
+
if (result?.ok) {
|
|
1698
|
+
const expectedBinding = {
|
|
1699
|
+
deviceId,
|
|
1700
|
+
sessionId: String(result.sessionId || device.sessionId || ''),
|
|
1701
|
+
streamId: String(result.streamId || ''),
|
|
1702
|
+
streamPurpose: String(result.streamPurpose || liveOptions.streamPurpose || 'wall'),
|
|
1703
|
+
commandId: String(result.commandId || ''),
|
|
1704
|
+
captureGeneration: Number(result.captureGeneration || 0),
|
|
1705
|
+
monitorIndex: Number(result.monitorIndex || 0),
|
|
1706
|
+
readySent: false
|
|
1707
|
+
};
|
|
1708
|
+
const activeStream = device?.activeLiveStream;
|
|
1709
|
+
const reusedFrameReady = result.reused === true
|
|
1710
|
+
&& result.ready === true
|
|
1711
|
+
&& Number(result.framesReceived ?? activeStream?.framesReceived ?? 0) > 0
|
|
1712
|
+
&& String(activeStream?.streamId || expectedBinding.streamId) === expectedBinding.streamId
|
|
1713
|
+
&& String(activeStream?.commandId || expectedBinding.commandId) === expectedBinding.commandId
|
|
1714
|
+
&& Number(activeStream?.captureGeneration || expectedBinding.captureGeneration) === expectedBinding.captureGeneration;
|
|
1715
|
+
expectedBinding.readySent = reusedFrameReady;
|
|
1716
|
+
ws.liveDeskStreamIdsByDeviceId.set(deviceId, result.streamId);
|
|
1717
|
+
ws.liveDeskExpectedStreamBindingsByDeviceId.set(deviceId, expectedBinding);
|
|
1718
|
+
cancelPendingFrameStreamStop(deviceId, result.streamId, String(liveOptions.streamPurpose || 'wall'));
|
|
1719
|
+
started.push({
|
|
1720
|
+
deviceId: expectedBinding.deviceId,
|
|
1721
|
+
sessionId: expectedBinding.sessionId,
|
|
1722
|
+
streamId: expectedBinding.streamId,
|
|
1723
|
+
streamPurpose: expectedBinding.streamPurpose,
|
|
1724
|
+
commandId: expectedBinding.commandId,
|
|
1725
|
+
captureGeneration: expectedBinding.captureGeneration,
|
|
1726
|
+
monitorIndex: expectedBinding.monitorIndex,
|
|
1727
|
+
ready: reusedFrameReady,
|
|
1728
|
+
fps: result.fps,
|
|
1729
|
+
reused: result.reused === true
|
|
1730
|
+
});
|
|
1731
|
+
} else {
|
|
1732
|
+
ws.liveDeskStreamIdsByDeviceId.delete(deviceId);
|
|
1733
|
+
ws.liveDeskExpectedStreamBindingsByDeviceId.delete(deviceId);
|
|
1734
|
+
skipped.push({ deviceId, reason: result?.error || 'start-failed' });
|
|
1735
|
+
}
|
|
1700
1736
|
}
|
|
1701
1737
|
if (reason === 'watchdog' && !started.some(item => item.reused !== true)) {
|
|
1702
1738
|
return;
|
|
@@ -1895,15 +1931,36 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
1895
1931
|
const frameSeq = Number(frame.frameSeq || 0) || 0;
|
|
1896
1932
|
let sharedPacket = null;
|
|
1897
1933
|
for (const client of targetClients) {
|
|
1898
|
-
if (client.readyState !== client.OPEN) {
|
|
1899
|
-
continue;
|
|
1900
|
-
}
|
|
1901
|
-
const
|
|
1902
|
-
? client.
|
|
1903
|
-
:
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
1906
|
-
|
|
1934
|
+
if (client.readyState !== client.OPEN) {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
const expectedBinding = client.liveDeskExpectedStreamBindingsByDeviceId instanceof Map
|
|
1938
|
+
? client.liveDeskExpectedStreamBindingsByDeviceId.get(deviceId)
|
|
1939
|
+
: null;
|
|
1940
|
+
if (!expectedBinding
|
|
1941
|
+
|| String(frame.streamId || '') !== expectedBinding.streamId
|
|
1942
|
+
|| String(frame.sessionId || '') !== expectedBinding.sessionId
|
|
1943
|
+
|| String(frame.commandId || '') !== expectedBinding.commandId
|
|
1944
|
+
|| Number(frame.captureGeneration || 0) !== expectedBinding.captureGeneration
|
|
1945
|
+
|| String(frame.streamPurpose || '') !== expectedBinding.streamPurpose
|
|
1946
|
+
|| Number(frame.monitorIndex || 0) !== expectedBinding.monitorIndex) {
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
if (!expectedBinding.readySent) {
|
|
1950
|
+
expectedBinding.readySent = sendJson(client, {
|
|
1951
|
+
type: 'RemoteFrameStreamReady',
|
|
1952
|
+
deviceId,
|
|
1953
|
+
sessionId: expectedBinding.sessionId,
|
|
1954
|
+
streamId: expectedBinding.streamId,
|
|
1955
|
+
streamPurpose: expectedBinding.streamPurpose,
|
|
1956
|
+
commandId: expectedBinding.commandId,
|
|
1957
|
+
captureGeneration: expectedBinding.captureGeneration,
|
|
1958
|
+
monitorIndex: expectedBinding.monitorIndex
|
|
1959
|
+
});
|
|
1960
|
+
if (!expectedBinding.readySent) {
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1907
1964
|
const lane = ensureFrameClientSendLane(client);
|
|
1908
1965
|
if (client.liveDeskFrameBackpressured && isH264 && !isKeyFrame) {
|
|
1909
1966
|
lane.dropped += 1;
|
|
@@ -2032,11 +2089,14 @@ function configureMode4Atlas(ws, payload = {}) {
|
|
|
2032
2089
|
const tileHeight = sharpTiles ? 270 : 180;
|
|
2033
2090
|
const previousInputDeviceIds = ws.liveDeskAtlasInputDeviceIds instanceof Set
|
|
2034
2091
|
? new Set(ws.liveDeskAtlasInputDeviceIds)
|
|
2035
|
-
: new Set(
|
|
2036
|
-
const
|
|
2092
|
+
: new Set();
|
|
2093
|
+
const previousInputOptions = ws.liveDeskAtlasInputOptions || {};
|
|
2037
2094
|
const atlasConfig = {
|
|
2038
2095
|
deviceIds,
|
|
2039
|
-
|
|
2096
|
+
// Composition ownership is independent from capture ownership. Keeping
|
|
2097
|
+
// every layout device ingestible lets a paused/reused input retain its
|
|
2098
|
+
// latest tile while only the changed device restarts capture.
|
|
2099
|
+
inputDeviceIds: deviceIds,
|
|
2040
2100
|
width: clampNumber(payload.width, 640, 3840, 1920),
|
|
2041
2101
|
height: clampNumber(payload.height, 360, 2160, 1080),
|
|
2042
2102
|
tileWidth,
|
|
@@ -2061,22 +2121,32 @@ function configureMode4Atlas(ws, payload = {}) {
|
|
|
2061
2121
|
ws.liveDeskAtlasDeviceIds = new Set(deviceIds);
|
|
2062
2122
|
ws.liveDeskAtlasInputDeviceIds = new Set(captureDeviceIds);
|
|
2063
2123
|
ws.liveDeskAtlasInputOptions = { tileWidth, tileHeight, monitorSelections };
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2124
|
+
const inputTransitions = planMode4AtlasInputTransitions({
|
|
2125
|
+
inputDeviceIds: [...previousInputDeviceIds],
|
|
2126
|
+
tileWidth: previousInputOptions.tileWidth,
|
|
2127
|
+
tileHeight: previousInputOptions.tileHeight,
|
|
2128
|
+
monitorSelections: previousInputOptions.monitorSelections
|
|
2129
|
+
}, {
|
|
2130
|
+
inputDeviceIds: captureDeviceIds,
|
|
2131
|
+
tileWidth,
|
|
2132
|
+
tileHeight,
|
|
2133
|
+
monitorSelections
|
|
2134
|
+
});
|
|
2135
|
+
for (const deviceId of inputTransitions.stop) {
|
|
2136
|
+
stopMode4AtlasInput(ws, deviceId, 'atlas-input-paused');
|
|
2137
|
+
}
|
|
2138
|
+
for (const deviceId of inputTransitions.start) {
|
|
2139
|
+
startMode4AtlasInput(ws, deviceId, 'atlas-input-started');
|
|
2140
|
+
}
|
|
2141
|
+
for (const deviceId of inputTransitions.restart) {
|
|
2142
|
+
startMode4AtlasInput(ws, deviceId, 'atlas-input-changed', {
|
|
2143
|
+
forceRestart: true
|
|
2075
2144
|
});
|
|
2076
2145
|
}
|
|
2077
|
-
sendJson(ws, {
|
|
2078
|
-
type: 'Mode4AtlasSubscription',
|
|
2079
|
-
|
|
2146
|
+
sendJson(ws, {
|
|
2147
|
+
type: 'Mode4AtlasSubscription',
|
|
2148
|
+
streamId: ws.liveDeskAtlasStreamId,
|
|
2149
|
+
deviceIds,
|
|
2080
2150
|
captureDeviceIds,
|
|
2081
2151
|
inputMode: 'mode2-lzo',
|
|
2082
2152
|
outputMode: 'mode4-h264-atlas',
|
|
@@ -3846,11 +3916,12 @@ inputWss.on('connection', ws => {
|
|
|
3846
3916
|
} catch {
|
|
3847
3917
|
sendJson(ws, { type: 'RemoteInputError', error: 'invalid-json' });
|
|
3848
3918
|
return;
|
|
3849
|
-
}
|
|
3919
|
+
}
|
|
3850
3920
|
const deviceId = String(payload?.deviceId || '').trim();
|
|
3921
|
+
const inputEventId = String(payload?.requestId || '').trim() || crypto.randomUUID();
|
|
3851
3922
|
const input = payload?.input && typeof payload.input === 'object'
|
|
3852
|
-
? { ...payload.input, hubConnectionId: ws.liveDeskInputClientId, hubReceivedAtEpochMs: Date.now() }
|
|
3853
|
-
: { ...payload, hubConnectionId: ws.liveDeskInputClientId, hubReceivedAtEpochMs: Date.now() };
|
|
3923
|
+
? { ...payload.input, inputEventId, hubConnectionId: ws.liveDeskInputClientId, hubReceivedAtEpochMs: Date.now() }
|
|
3924
|
+
: { ...payload, inputEventId, hubConnectionId: ws.liveDeskInputClientId, hubReceivedAtEpochMs: Date.now() };
|
|
3854
3925
|
const result = remoteHub.sendInputControl(deviceId, input);
|
|
3855
3926
|
const inputType = String(input?.type || '').toLowerCase();
|
|
3856
3927
|
const fireAndForget = payload?.fireAndForget === true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.369",
|
|
4
4
|
"buildFlavor": "production",
|
|
5
5
|
"description": "LiveDesk Hub and client launcher",
|
|
6
6
|
"type": "module",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"ws": "^8.18.3"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
52
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
53
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
54
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
51
|
+
"@livedesk/fast-linux-x64": "0.1.369",
|
|
52
|
+
"@livedesk/fast-osx-arm64": "0.1.369",
|
|
53
|
+
"@livedesk/fast-osx-x64": "0.1.369",
|
|
54
|
+
"@livedesk/fast-win-x64": "0.1.369"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|