livedesk 0.1.246 → 0.1.248
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/remote-hub.js +99 -8
- package/hub/src/server.js +25 -11
- package/package.json +2 -2
- package/web/dist/assets/index-Dsgv1pBs.js +16 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-DYwJ9jdl.js +0 -16
package/hub/src/remote-hub.js
CHANGED
|
@@ -24,6 +24,7 @@ const RECENT_LIVE_FRAME_CACHE_LIMIT = 1;
|
|
|
24
24
|
const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 2 * 1024 * 1024;
|
|
25
25
|
const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 8 * 1024 * 1024;
|
|
26
26
|
const LIVE_STREAM_PENDING_REUSE_MS = 5000;
|
|
27
|
+
const LIVE_STREAM_REPLACEMENT_PENDING_MS = 7000;
|
|
27
28
|
const LIVE_STREAM_MIN_FRESH_MS = 3000;
|
|
28
29
|
const LIVE_STREAM_MAX_FRESH_MS = 12000;
|
|
29
30
|
const REMOTE_PROTOCOL_VERSION = 2;
|
|
@@ -3342,21 +3343,30 @@ export function createRemoteHub(options = {}) {
|
|
|
3342
3343
|
|
|
3343
3344
|
const commandId = safeString(message.commandId, 128);
|
|
3344
3345
|
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3345
|
-
|
|
3346
|
+
const pendingCommandId = safeString(streamState.pendingCommandId, 128);
|
|
3347
|
+
const isActiveGeneration = !commandId || !activeCommandId || commandId === activeCommandId;
|
|
3348
|
+
const isPendingGeneration = !!pendingCommandId && commandId === pendingCommandId;
|
|
3349
|
+
if (!isActiveGeneration && !isPendingGeneration) {
|
|
3346
3350
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
3347
3351
|
reason: 'stale-live-stream-generation',
|
|
3348
3352
|
streamId,
|
|
3349
3353
|
commandId,
|
|
3350
3354
|
activeCommandId,
|
|
3355
|
+
pendingCommandId,
|
|
3351
3356
|
transport
|
|
3352
3357
|
});
|
|
3353
3358
|
return false;
|
|
3354
3359
|
}
|
|
3355
3360
|
|
|
3361
|
+
const activatingPendingGeneration = isPendingGeneration && commandId !== activeCommandId;
|
|
3356
3362
|
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
3357
3363
|
const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
|
|
3358
3364
|
const nextStreamState = {
|
|
3359
3365
|
...streamState,
|
|
3366
|
+
commandId: activatingPendingGeneration ? commandId : streamState.commandId,
|
|
3367
|
+
pendingCommandId: activatingPendingGeneration ? '' : streamState.pendingCommandId,
|
|
3368
|
+
pendingStartedAt: activatingPendingGeneration ? '' : streamState.pendingStartedAt,
|
|
3369
|
+
pendingRestartToken: activatingPendingGeneration ? '' : streamState.pendingRestartToken,
|
|
3360
3370
|
open: true,
|
|
3361
3371
|
openedAt,
|
|
3362
3372
|
openTransport: transport,
|
|
@@ -3380,6 +3390,9 @@ export function createRemoteHub(options = {}) {
|
|
|
3380
3390
|
setDeviceLiveStream(device, nextStreamState);
|
|
3381
3391
|
emitRemoteEvent('RemoteLiveStreamOpened', device, {
|
|
3382
3392
|
streamId,
|
|
3393
|
+
commandId: nextStreamState.commandId,
|
|
3394
|
+
previousCommandId: activatingPendingGeneration ? activeCommandId : '',
|
|
3395
|
+
handoff: activatingPendingGeneration,
|
|
3383
3396
|
mode: transfer.mode,
|
|
3384
3397
|
frameMode: transfer.frameMode,
|
|
3385
3398
|
frameProfile: transfer.frameProfile,
|
|
@@ -3409,13 +3422,16 @@ export function createRemoteHub(options = {}) {
|
|
|
3409
3422
|
return false;
|
|
3410
3423
|
}
|
|
3411
3424
|
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3412
|
-
|
|
3425
|
+
const pendingCommandId = safeString(streamState.pendingCommandId, 128);
|
|
3426
|
+
const isActiveGeneration = !commandId || !activeCommandId || commandId === activeCommandId;
|
|
3427
|
+
if (!isActiveGeneration) {
|
|
3413
3428
|
device.counters.liveFramesDropped += 1;
|
|
3414
3429
|
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
3415
3430
|
reason: 'stale-live-stream-generation-frame',
|
|
3416
3431
|
streamId,
|
|
3417
3432
|
commandId,
|
|
3418
3433
|
activeCommandId,
|
|
3434
|
+
pendingCommandId,
|
|
3419
3435
|
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
3420
3436
|
transport
|
|
3421
3437
|
});
|
|
@@ -3965,7 +3981,11 @@ export function createRemoteHub(options = {}) {
|
|
|
3965
3981
|
{
|
|
3966
3982
|
const commandId = safeString(message.commandId, 128);
|
|
3967
3983
|
const error = safeString(message.error, 500);
|
|
3968
|
-
const
|
|
3984
|
+
const result = message.result ?? null;
|
|
3985
|
+
if (error || result?.ok === false || result?.status === 'failed') {
|
|
3986
|
+
failPendingLiveStream(device, commandId, error || result?.error || 'stream-start-failed');
|
|
3987
|
+
}
|
|
3988
|
+
const task = applyTaskResult(device, commandId, result, error);
|
|
3969
3989
|
if (task) {
|
|
3970
3990
|
emitRemoteEvent('RemoteTaskResult', device, {
|
|
3971
3991
|
commandId,
|
|
@@ -4842,6 +4862,29 @@ export function createRemoteHub(options = {}) {
|
|
|
4842
4862
|
return stream;
|
|
4843
4863
|
}
|
|
4844
4864
|
|
|
4865
|
+
function failPendingLiveStream(device, commandId, error = 'stream-start-failed') {
|
|
4866
|
+
const key = safeString(commandId, 128);
|
|
4867
|
+
if (!device || !key) {
|
|
4868
|
+
return false;
|
|
4869
|
+
}
|
|
4870
|
+
const streams = ensureDeviceLiveStreams(device);
|
|
4871
|
+
for (const stream of streams.values()) {
|
|
4872
|
+
if (safeString(stream?.pendingCommandId, 128) !== key) {
|
|
4873
|
+
continue;
|
|
4874
|
+
}
|
|
4875
|
+
stream.pendingCommandId = '';
|
|
4876
|
+
stream.pendingStartedAt = '';
|
|
4877
|
+
stream.pendingRestartToken = '';
|
|
4878
|
+
emitRemoteEvent('RemoteLiveStreamRestartFailed', device, {
|
|
4879
|
+
streamId: stream.streamId,
|
|
4880
|
+
commandId: key,
|
|
4881
|
+
error: safeString(error, 500) || 'stream-start-failed'
|
|
4882
|
+
});
|
|
4883
|
+
return true;
|
|
4884
|
+
}
|
|
4885
|
+
return false;
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4845
4888
|
function deactivateDeviceLiveStreams(device, reason, stoppedAt = new Date().toISOString()) {
|
|
4846
4889
|
const streams = ensureDeviceLiveStreams(device);
|
|
4847
4890
|
for (const stream of streams.values()) {
|
|
@@ -4893,6 +4936,14 @@ export function createRemoteHub(options = {}) {
|
|
|
4893
4936
|
return Number.isFinite(startedAt) && Date.now() - startedAt < LIVE_STREAM_PENDING_REUSE_MS;
|
|
4894
4937
|
}
|
|
4895
4938
|
|
|
4939
|
+
function liveStreamReplacementStillPending(activeLiveStream) {
|
|
4940
|
+
if (!activeLiveStream?.active || !safeString(activeLiveStream.pendingCommandId, 128)) {
|
|
4941
|
+
return false;
|
|
4942
|
+
}
|
|
4943
|
+
const startedAt = Date.parse(activeLiveStream.pendingStartedAt || '');
|
|
4944
|
+
return Number.isFinite(startedAt) && Date.now() - startedAt < LIVE_STREAM_REPLACEMENT_PENDING_MS;
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4896
4947
|
function getLiveStreamFreshWindowMs(activeLiveStream) {
|
|
4897
4948
|
const fps = Number(activeLiveStream?.fps || 0);
|
|
4898
4949
|
const frameMs = Number.isFinite(fps) && fps > 0 ? 1000 / fps : 500;
|
|
@@ -5026,6 +5077,30 @@ export function createRemoteHub(options = {}) {
|
|
|
5026
5077
|
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
5027
5078
|
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
5028
5079
|
const activeLiveStream = getDeviceLiveStream(device, streamId);
|
|
5080
|
+
if (activeLiveStream?.pendingCommandId) {
|
|
5081
|
+
if (liveStreamReplacementStillPending(activeLiveStream)) {
|
|
5082
|
+
emitRemoteEvent('RemoteLiveStreamRestartPending', device, {
|
|
5083
|
+
streamId,
|
|
5084
|
+
commandId: activeLiveStream.pendingCommandId,
|
|
5085
|
+
activeCommandId: activeLiveStream.commandId,
|
|
5086
|
+
reason: safeString(options.restartReason || 'duplicate-restart', 128)
|
|
5087
|
+
});
|
|
5088
|
+
return {
|
|
5089
|
+
ok: true,
|
|
5090
|
+
commandId: activeLiveStream.pendingCommandId,
|
|
5091
|
+
streamId,
|
|
5092
|
+
fps: Number(activeLiveStream.fps || fps),
|
|
5093
|
+
mode: activeLiveStream.mode || transfer.mode,
|
|
5094
|
+
frameMode: activeLiveStream.frameMode || transfer.frameMode,
|
|
5095
|
+
monitorIndex: Number(activeLiveStream.monitorIndex || monitorIndex),
|
|
5096
|
+
pending: true,
|
|
5097
|
+
reused: true
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
activeLiveStream.pendingCommandId = '';
|
|
5101
|
+
activeLiveStream.pendingStartedAt = '';
|
|
5102
|
+
activeLiveStream.pendingRestartToken = '';
|
|
5103
|
+
}
|
|
5029
5104
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
5030
5105
|
if (activeLiveStream
|
|
5031
5106
|
&& options.forceRestart !== true
|
|
@@ -5120,13 +5195,21 @@ export function createRemoteHub(options = {}) {
|
|
|
5120
5195
|
return result;
|
|
5121
5196
|
}
|
|
5122
5197
|
|
|
5198
|
+
const previousCommandId = safeString(activeLiveStream?.commandId, 128);
|
|
5199
|
+
const replacingActiveStream = activeLiveStream?.active === true
|
|
5200
|
+
&& !!previousCommandId
|
|
5201
|
+
&& previousCommandId !== commandId;
|
|
5123
5202
|
setDeviceLiveStream(device, {
|
|
5203
|
+
...(activeLiveStream || {}),
|
|
5124
5204
|
streamId,
|
|
5125
|
-
commandId,
|
|
5205
|
+
commandId: replacingActiveStream ? previousCommandId : commandId,
|
|
5206
|
+
pendingCommandId: replacingActiveStream ? commandId : '',
|
|
5207
|
+
pendingStartedAt: replacingActiveStream ? now : '',
|
|
5208
|
+
pendingRestartToken: replacingActiveStream ? safeString(options.restartToken, 128) : '',
|
|
5126
5209
|
active: true,
|
|
5127
|
-
open: false,
|
|
5128
|
-
openedAt: '',
|
|
5129
|
-
openTransport: '',
|
|
5210
|
+
open: replacingActiveStream ? activeLiveStream.open === true : false,
|
|
5211
|
+
openedAt: replacingActiveStream ? activeLiveStream.openedAt || '' : '',
|
|
5212
|
+
openTransport: replacingActiveStream ? activeLiveStream.openTransport || '' : '',
|
|
5130
5213
|
mode: transfer.mode,
|
|
5131
5214
|
frameMode: transfer.frameMode,
|
|
5132
5215
|
frameProfile: transfer.frameProfile,
|
|
@@ -5142,7 +5225,7 @@ export function createRemoteHub(options = {}) {
|
|
|
5142
5225
|
quality,
|
|
5143
5226
|
monitorIndex,
|
|
5144
5227
|
monitorCount: 1,
|
|
5145
|
-
startedAt: now,
|
|
5228
|
+
startedAt: replacingActiveStream ? activeLiveStream.startedAt || now : now,
|
|
5146
5229
|
stoppedAt: '',
|
|
5147
5230
|
stopReason: '',
|
|
5148
5231
|
lastFrameAt: '',
|
|
@@ -5154,6 +5237,8 @@ export function createRemoteHub(options = {}) {
|
|
|
5154
5237
|
emitRemoteEvent('RemoteLiveStreamStarted', device, {
|
|
5155
5238
|
streamId,
|
|
5156
5239
|
commandId,
|
|
5240
|
+
previousCommandId,
|
|
5241
|
+
pending: replacingActiveStream,
|
|
5157
5242
|
fps,
|
|
5158
5243
|
mode: transfer.mode,
|
|
5159
5244
|
frameMode: transfer.frameMode,
|
|
@@ -5183,6 +5268,9 @@ export function createRemoteHub(options = {}) {
|
|
|
5183
5268
|
device.counters.commandsSent += 1;
|
|
5184
5269
|
if (streamState) {
|
|
5185
5270
|
streamState.active = false;
|
|
5271
|
+
streamState.pendingCommandId = '';
|
|
5272
|
+
streamState.pendingStartedAt = '';
|
|
5273
|
+
streamState.pendingRestartToken = '';
|
|
5186
5274
|
streamState.stoppedAt = new Date().toISOString();
|
|
5187
5275
|
streamState.stopReason = 'manager-request';
|
|
5188
5276
|
}
|
|
@@ -5215,6 +5303,9 @@ export function createRemoteHub(options = {}) {
|
|
|
5215
5303
|
|
|
5216
5304
|
if (streamState) {
|
|
5217
5305
|
streamState.active = false;
|
|
5306
|
+
streamState.pendingCommandId = '';
|
|
5307
|
+
streamState.pendingStartedAt = '';
|
|
5308
|
+
streamState.pendingRestartToken = '';
|
|
5218
5309
|
streamState.stoppedAt = new Date().toISOString();
|
|
5219
5310
|
streamState.stopReason = 'manager-request';
|
|
5220
5311
|
}
|
package/hub/src/server.js
CHANGED
|
@@ -38,8 +38,9 @@ const webIndexPath = resolve(webDistPath, 'index.html');
|
|
|
38
38
|
const packageInfo = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
39
39
|
const httpHost = process.env.LIVEDESK_HUB_HTTP_HOST || '127.0.0.1';
|
|
40
40
|
const httpPort = Number(process.env.LIVEDESK_HUB_HTTP_PORT || process.env.PORT || 5179);
|
|
41
|
-
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 64 * 1024 * 1024);
|
|
42
|
-
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 256);
|
|
41
|
+
const frameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_BACKPRESSURE_BYTES', 64 * 1024 * 1024);
|
|
42
|
+
const frameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_QUEUE_PACKETS', 256);
|
|
43
|
+
const controlFrameClientQueuePackets = readPositiveIntegerEnv('LIVEDESK_CONTROL_FRAME_WS_QUEUE_PACKETS', 4);
|
|
43
44
|
const frameClientDrainBudgetPackets = readPositiveIntegerEnv('LIVEDESK_FRAME_WS_DRAIN_BUDGET_PACKETS', 64);
|
|
44
45
|
const frameStreamStopGraceMs = readPositiveIntegerEnv('LIVEDESK_FRAME_STREAM_STOP_GRACE_MS', 1500);
|
|
45
46
|
const mode5FrameBackpressureBytes = readPositiveIntegerEnv('LIVEDESK_MODE5_WS_BACKPRESSURE_BYTES', 2 * 1024 * 1024);
|
|
@@ -1128,7 +1129,7 @@ function registerFrameClient(ws) {
|
|
|
1128
1129
|
}
|
|
1129
1130
|
}
|
|
1130
1131
|
|
|
1131
|
-
function ensureFrameClientSendLane(ws) {
|
|
1132
|
+
function ensureFrameClientSendLane(ws) {
|
|
1132
1133
|
if (!ws.liveDeskFrameSendLane) {
|
|
1133
1134
|
ws.liveDeskFrameSendLane = {
|
|
1134
1135
|
queue: [],
|
|
@@ -1137,8 +1138,14 @@ function ensureFrameClientSendLane(ws) {
|
|
|
1137
1138
|
awaitingKeyFrames: new Set()
|
|
1138
1139
|
};
|
|
1139
1140
|
}
|
|
1140
|
-
return ws.liveDeskFrameSendLane;
|
|
1141
|
-
}
|
|
1141
|
+
return ws.liveDeskFrameSendLane;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function frameClientQueueLimit(ws) {
|
|
1145
|
+
return ws?.liveDeskLiveOptions?.streamPurpose === 'control'
|
|
1146
|
+
? controlFrameClientQueuePackets
|
|
1147
|
+
: frameClientQueuePackets;
|
|
1148
|
+
}
|
|
1142
1149
|
|
|
1143
1150
|
function dropQueuedFrameForLane(lane) {
|
|
1144
1151
|
const deltaIndex = lane.queue.findIndex(item => item?.isKeyFrame !== true);
|
|
@@ -1243,7 +1250,7 @@ function enqueueFramePacketForClient(ws, packet, meta) {
|
|
|
1243
1250
|
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
1244
1251
|
}
|
|
1245
1252
|
}
|
|
1246
|
-
while (lane.queue.length >=
|
|
1253
|
+
while (lane.queue.length >= frameClientQueueLimit(ws)) {
|
|
1247
1254
|
dropQueuedFrameForLane(lane);
|
|
1248
1255
|
ws.liveDeskFrameBackpressureDrops = Number(ws.liveDeskFrameBackpressureDrops || 0) + 1;
|
|
1249
1256
|
}
|
|
@@ -2866,9 +2873,14 @@ audioWss.on('connection', (ws, req) => {
|
|
|
2866
2873
|
});
|
|
2867
2874
|
});
|
|
2868
2875
|
|
|
2869
|
-
inputWss.on('connection', ws => {
|
|
2870
|
-
inputClients.add(ws);
|
|
2871
|
-
ws.liveDeskInputClientId = `riws-${++inputClientSeq}`;
|
|
2876
|
+
inputWss.on('connection', ws => {
|
|
2877
|
+
inputClients.add(ws);
|
|
2878
|
+
ws.liveDeskInputClientId = `riws-${++inputClientSeq}`;
|
|
2879
|
+
try {
|
|
2880
|
+
ws._socket?.setNoDelay?.(true);
|
|
2881
|
+
} catch {
|
|
2882
|
+
// Best-effort latency hint for browser input sockets.
|
|
2883
|
+
}
|
|
2872
2884
|
ws.on('message', data => {
|
|
2873
2885
|
let payload;
|
|
2874
2886
|
try {
|
|
@@ -2882,8 +2894,10 @@ inputWss.on('connection', ws => {
|
|
|
2882
2894
|
? { ...payload.input, hubReceivedAtEpochMs: Date.now() }
|
|
2883
2895
|
: { ...payload, hubReceivedAtEpochMs: Date.now() };
|
|
2884
2896
|
const result = remoteHub.sendInputControl(deviceId, input);
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2897
|
+
const inputType = String(input?.type || '').toLowerCase();
|
|
2898
|
+
const fireAndForget = payload?.fireAndForget === true
|
|
2899
|
+
|| inputType === 'pointermove'
|
|
2900
|
+
|| inputType === 'wheel';
|
|
2887
2901
|
if (fireAndForget && result?.ok) {
|
|
2888
2902
|
return;
|
|
2889
2903
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.248",
|
|
4
4
|
"description": "LiveDesk Hub and client launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
34
|
-
"@livedesk/client": "0.1.
|
|
34
|
+
"@livedesk/client": "0.1.134",
|
|
35
35
|
"@openai/codex-sdk": "0.144.5",
|
|
36
36
|
"cors": "^2.8.5",
|
|
37
37
|
"express": "^4.21.2",
|