livedesk 0.1.154 → 0.1.155
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 +170 -126
- package/hub/src/server.js +19 -1
- package/package.json +2 -2
- package/web/dist/assets/index-5E1Hzr2T.css +1 -0
- package/web/dist/assets/index-BMpQiIOC.js +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Ciy01vvN.css +0 -1
- package/web/dist/assets/index-FZaetL6U.js +0 -10
package/hub/src/remote-hub.js
CHANGED
|
@@ -1849,17 +1849,18 @@ export function createRemoteHub(options = {}) {
|
|
|
1849
1849
|
}
|
|
1850
1850
|
|
|
1851
1851
|
function applySyntheticLiveFrame(device, streamId, options = {}) {
|
|
1852
|
-
|
|
1852
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
1853
|
+
if (!streamState?.active) {
|
|
1853
1854
|
return null;
|
|
1854
1855
|
}
|
|
1855
1856
|
|
|
1856
|
-
const frame = makeSyntheticFrame(device, streamId ||
|
|
1857
|
-
commandId: options.commandId ||
|
|
1857
|
+
const frame = makeSyntheticFrame(device, streamId || streamState.streamId, streamState.mode || DEFAULT_REMOTE_FRAME_MODE, {
|
|
1858
|
+
commandId: options.commandId || streamState.commandId,
|
|
1858
1859
|
width: options.maxWidth || 960,
|
|
1859
1860
|
height: options.maxHeight || 540,
|
|
1860
|
-
monitorIndex: options.monitorIndex ??
|
|
1861
|
-
monitorCount: options.monitorCount ??
|
|
1862
|
-
fps: options.fps ||
|
|
1861
|
+
monitorIndex: options.monitorIndex ?? streamState.monitorIndex ?? 0,
|
|
1862
|
+
monitorCount: options.monitorCount ?? streamState.monitorCount ?? 1,
|
|
1863
|
+
fps: options.fps || streamState.fps
|
|
1863
1864
|
});
|
|
1864
1865
|
device.latestLiveFrame = frame;
|
|
1865
1866
|
rememberRecentFramePayload(device, 'live', frame);
|
|
@@ -1871,9 +1872,10 @@ export function createRemoteHub(options = {}) {
|
|
|
1871
1872
|
mimeType: frame.mimeType,
|
|
1872
1873
|
byteLength: frame.byteLength
|
|
1873
1874
|
});
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1875
|
+
streamState.latestFrame = frame;
|
|
1876
|
+
streamState.lastFrameAt = frame.receivedAt;
|
|
1877
|
+
streamState.lastFrameSeq = frame.frameSeq;
|
|
1878
|
+
streamState.framesReceived = (streamState.framesReceived || 0) + 1;
|
|
1877
1879
|
device.lastSeenAt = frame.receivedAt;
|
|
1878
1880
|
device.counters.liveFramesReceived += 1;
|
|
1879
1881
|
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
@@ -2645,11 +2647,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2645
2647
|
closeFrameSocket(device, reason);
|
|
2646
2648
|
device.disconnectedAt = new Date().toISOString();
|
|
2647
2649
|
device.lastDisconnectReason = reason;
|
|
2648
|
-
|
|
2649
|
-
device.activeLiveStream.active = false;
|
|
2650
|
-
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
2651
|
-
device.activeLiveStream.stopReason = reason;
|
|
2652
|
-
}
|
|
2650
|
+
deactivateDeviceLiveStreams(device, reason, device.disconnectedAt);
|
|
2653
2651
|
failAllPendingTasks(device, 'device-disconnected');
|
|
2654
2652
|
logWarn('remote', `device disconnected ${device.deviceName} (${deviceId}): ${reason}`);
|
|
2655
2653
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
|
|
@@ -3127,7 +3125,8 @@ export function createRemoteHub(options = {}) {
|
|
|
3127
3125
|
|
|
3128
3126
|
function applyLiveStreamOpen(device, message, transport = 'json') {
|
|
3129
3127
|
const streamId = safeString(message.streamId, 128) || 'live';
|
|
3130
|
-
|
|
3128
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
3129
|
+
if (!streamState?.active) {
|
|
3131
3130
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
3132
3131
|
reason: 'stale-live-stream-open',
|
|
3133
3132
|
streamId,
|
|
@@ -3137,7 +3136,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3137
3136
|
}
|
|
3138
3137
|
|
|
3139
3138
|
const commandId = safeString(message.commandId, 128);
|
|
3140
|
-
const activeCommandId = safeString(
|
|
3139
|
+
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3141
3140
|
if (commandId && activeCommandId && commandId !== activeCommandId) {
|
|
3142
3141
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
3143
3142
|
reason: 'stale-live-stream-generation',
|
|
@@ -3149,10 +3148,10 @@ export function createRemoteHub(options = {}) {
|
|
|
3149
3148
|
return false;
|
|
3150
3149
|
}
|
|
3151
3150
|
|
|
3152
|
-
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message,
|
|
3151
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
3153
3152
|
const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
|
|
3154
|
-
|
|
3155
|
-
...
|
|
3153
|
+
const nextStreamState = {
|
|
3154
|
+
...streamState,
|
|
3156
3155
|
open: true,
|
|
3157
3156
|
openedAt,
|
|
3158
3157
|
openTransport: transport,
|
|
@@ -3165,14 +3164,15 @@ export function createRemoteHub(options = {}) {
|
|
|
3165
3164
|
transferProtocol: transfer.transferProtocol,
|
|
3166
3165
|
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3167
3166
|
handshake: transfer.handshake,
|
|
3168
|
-
width: Number.isFinite(Number(message.width)) ? Number(message.width) : Number(
|
|
3169
|
-
height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(
|
|
3170
|
-
sourceWidth: Number.isFinite(Number(message.sourceWidth)) ? Number(message.sourceWidth) : Number(
|
|
3171
|
-
sourceHeight: Number.isFinite(Number(message.sourceHeight)) ? Number(message.sourceHeight) : Number(
|
|
3172
|
-
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(
|
|
3173
|
-
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(
|
|
3174
|
-
openedFrameSeq: Number.isFinite(Number(message.frameSeq)) ? Number(message.frameSeq) : Number(
|
|
3167
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : Number(streamState.width || 0),
|
|
3168
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(streamState.height || 0),
|
|
3169
|
+
sourceWidth: Number.isFinite(Number(message.sourceWidth)) ? Number(message.sourceWidth) : Number(streamState.sourceWidth || 0),
|
|
3170
|
+
sourceHeight: Number.isFinite(Number(message.sourceHeight)) ? Number(message.sourceHeight) : Number(streamState.sourceHeight || 0),
|
|
3171
|
+
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
|
|
3172
|
+
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
|
|
3173
|
+
openedFrameSeq: Number.isFinite(Number(message.frameSeq)) ? Number(message.frameSeq) : Number(streamState.openedFrameSeq || 0)
|
|
3175
3174
|
};
|
|
3175
|
+
setDeviceLiveStream(device, nextStreamState);
|
|
3176
3176
|
emitRemoteEvent('RemoteLiveStreamOpened', device, {
|
|
3177
3177
|
streamId,
|
|
3178
3178
|
mode: transfer.mode,
|
|
@@ -3192,7 +3192,8 @@ export function createRemoteHub(options = {}) {
|
|
|
3192
3192
|
const frameSeq = Number(message.frameSeq);
|
|
3193
3193
|
const streamId = safeString(message.streamId, 128) || 'live';
|
|
3194
3194
|
const commandId = safeString(message.commandId, 128);
|
|
3195
|
-
|
|
3195
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
3196
|
+
if (!streamState?.active) {
|
|
3196
3197
|
device.counters.liveFramesDropped += 1;
|
|
3197
3198
|
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
3198
3199
|
reason: 'stale-live-stream-frame',
|
|
@@ -3202,7 +3203,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3202
3203
|
});
|
|
3203
3204
|
return false;
|
|
3204
3205
|
}
|
|
3205
|
-
const activeCommandId = safeString(
|
|
3206
|
+
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3206
3207
|
if (commandId && activeCommandId && commandId !== activeCommandId) {
|
|
3207
3208
|
device.counters.liveFramesDropped += 1;
|
|
3208
3209
|
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
@@ -3234,21 +3235,21 @@ export function createRemoteHub(options = {}) {
|
|
|
3234
3235
|
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
3235
3236
|
const payload = buildFramePayloadBuffer(framePayload, frameData);
|
|
3236
3237
|
const contentHash = buildFrameContentHash(message, payload);
|
|
3237
|
-
const sameContentStreak = computeSameContentStreak(
|
|
3238
|
-
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message,
|
|
3239
|
-
if (
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3238
|
+
const sameContentStreak = computeSameContentStreak(streamState.latestFrame, contentHash);
|
|
3239
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
3240
|
+
if (streamState.open !== true) {
|
|
3241
|
+
streamState.open = true;
|
|
3242
|
+
streamState.openedAt = device.lastSeenAt;
|
|
3243
|
+
streamState.openTransport = `${transport}-implicit`;
|
|
3244
|
+
streamState.mode = transfer.mode;
|
|
3245
|
+
streamState.frameMode = transfer.frameMode;
|
|
3246
|
+
streamState.frameProfile = transfer.frameProfile;
|
|
3247
|
+
streamState.encoding = transfer.encoding;
|
|
3248
|
+
streamState.codec = transfer.codec;
|
|
3249
|
+
streamState.compression = transfer.compression;
|
|
3250
|
+
streamState.transferProtocol = transfer.transferProtocol;
|
|
3251
|
+
streamState.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
3252
|
+
streamState.handshake = transfer.handshake;
|
|
3252
3253
|
}
|
|
3253
3254
|
device.latestLiveFrame = {
|
|
3254
3255
|
streamId,
|
|
@@ -3284,12 +3285,12 @@ export function createRemoteHub(options = {}) {
|
|
|
3284
3285
|
hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,
|
|
3285
3286
|
hardwareEncoder: safeString(message.hardwareEncoder, 80),
|
|
3286
3287
|
platformProfile: safeString(message.platformProfile, 80),
|
|
3287
|
-
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(
|
|
3288
|
-
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(
|
|
3288
|
+
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
|
|
3289
|
+
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
|
|
3289
3290
|
transferProtocol: transfer.transferProtocol,
|
|
3290
3291
|
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3291
3292
|
handshake: transfer.handshake,
|
|
3292
|
-
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) :
|
|
3293
|
+
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : streamState.fps,
|
|
3293
3294
|
capturedAt,
|
|
3294
3295
|
receivedAt: device.lastSeenAt,
|
|
3295
3296
|
byteLength,
|
|
@@ -3303,6 +3304,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3303
3304
|
payload,
|
|
3304
3305
|
accessToken: createFrameAccessToken()
|
|
3305
3306
|
};
|
|
3307
|
+
streamState.latestFrame = device.latestLiveFrame;
|
|
3306
3308
|
rememberRecentFramePayload(device, 'live', device.latestLiveFrame);
|
|
3307
3309
|
emitFrame({
|
|
3308
3310
|
kind: 'live',
|
|
@@ -3312,24 +3314,25 @@ export function createRemoteHub(options = {}) {
|
|
|
3312
3314
|
mimeType: device.latestLiveFrame.mimeType,
|
|
3313
3315
|
byteLength: device.latestLiveFrame.byteLength
|
|
3314
3316
|
});
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3317
|
+
streamState.lastFrameAt = device.lastSeenAt;
|
|
3318
|
+
streamState.lastFrameSeq = frameSeq;
|
|
3319
|
+
streamState.framesReceived = (streamState.framesReceived || 0) + 1;
|
|
3320
|
+
streamState.mode = transfer.mode;
|
|
3321
|
+
streamState.frameMode = transfer.frameMode;
|
|
3322
|
+
streamState.frameProfile = transfer.frameProfile;
|
|
3323
|
+
streamState.encoding = transfer.encoding;
|
|
3324
|
+
streamState.codec = transfer.codec;
|
|
3325
|
+
streamState.compression = transfer.compression;
|
|
3326
|
+
streamState.transferProtocol = transfer.transferProtocol;
|
|
3327
|
+
streamState.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
3328
|
+
streamState.handshake = transfer.handshake;
|
|
3329
|
+
streamState.width = device.latestLiveFrame.width;
|
|
3330
|
+
streamState.height = device.latestLiveFrame.height;
|
|
3331
|
+
streamState.sourceWidth = device.latestLiveFrame.sourceWidth;
|
|
3332
|
+
streamState.sourceHeight = device.latestLiveFrame.sourceHeight;
|
|
3333
|
+
streamState.monitorIndex = device.latestLiveFrame.monitorIndex;
|
|
3334
|
+
streamState.monitorCount = device.latestLiveFrame.monitorCount;
|
|
3335
|
+
ensureDeviceLiveStreams(device).set(streamId, streamState);
|
|
3333
3336
|
device.counters.liveFramesReceived += 1;
|
|
3334
3337
|
return true;
|
|
3335
3338
|
}
|
|
@@ -4105,11 +4108,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4105
4108
|
device.connected = false;
|
|
4106
4109
|
device.disconnectedAt = new Date().toISOString();
|
|
4107
4110
|
device.lastDisconnectReason = reason;
|
|
4108
|
-
|
|
4109
|
-
device.activeLiveStream.active = false;
|
|
4110
|
-
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
4111
|
-
device.activeLiveStream.stopReason = reason;
|
|
4112
|
-
}
|
|
4111
|
+
deactivateDeviceLiveStreams(device, reason, device.disconnectedAt);
|
|
4113
4112
|
failAllPendingTasks(device, 'device-disconnected');
|
|
4114
4113
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason, synthetic: true });
|
|
4115
4114
|
return true;
|
|
@@ -4432,20 +4431,69 @@ export function createRemoteHub(options = {}) {
|
|
|
4432
4431
|
});
|
|
4433
4432
|
}
|
|
4434
4433
|
|
|
4435
|
-
function
|
|
4434
|
+
function ensureDeviceLiveStreams(device) {
|
|
4435
|
+
if (!(device?.activeLiveStreams instanceof Map)) {
|
|
4436
|
+
Object.defineProperty(device, 'activeLiveStreams', {
|
|
4437
|
+
value: new Map(),
|
|
4438
|
+
enumerable: false,
|
|
4439
|
+
configurable: true,
|
|
4440
|
+
writable: true
|
|
4441
|
+
});
|
|
4442
|
+
if (device?.activeLiveStream?.streamId) {
|
|
4443
|
+
device.activeLiveStreams.set(device.activeLiveStream.streamId, device.activeLiveStream);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return device.activeLiveStreams;
|
|
4447
|
+
}
|
|
4448
|
+
|
|
4449
|
+
function getDeviceLiveStream(device, streamId) {
|
|
4450
|
+
const id = safeString(streamId, 128);
|
|
4451
|
+
if (!id) {
|
|
4452
|
+
return device?.activeLiveStream || null;
|
|
4453
|
+
}
|
|
4454
|
+
return ensureDeviceLiveStreams(device).get(id)
|
|
4455
|
+
|| (device?.activeLiveStream?.streamId === id ? device.activeLiveStream : null);
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
function setDeviceLiveStream(device, stream) {
|
|
4459
|
+
if (!device || !stream?.streamId) {
|
|
4460
|
+
return stream;
|
|
4461
|
+
}
|
|
4462
|
+
ensureDeviceLiveStreams(device).set(stream.streamId, stream);
|
|
4463
|
+
device.activeLiveStream = stream;
|
|
4464
|
+
return stream;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
function deactivateDeviceLiveStreams(device, reason, stoppedAt = new Date().toISOString()) {
|
|
4468
|
+
const streams = ensureDeviceLiveStreams(device);
|
|
4469
|
+
for (const stream of streams.values()) {
|
|
4470
|
+
stream.active = false;
|
|
4471
|
+
stream.stoppedAt = stoppedAt;
|
|
4472
|
+
stream.stopReason = reason;
|
|
4473
|
+
}
|
|
4474
|
+
if (device?.activeLiveStream) {
|
|
4475
|
+
device.activeLiveStream.active = false;
|
|
4476
|
+
device.activeLiveStream.stoppedAt = stoppedAt;
|
|
4477
|
+
device.activeLiveStream.stopReason = reason;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
function makeStableLiveStreamId(deviceId, purpose = 'wall') {
|
|
4436
4482
|
const hash = crypto.createHash('sha1').update(String(deviceId || 'device')).digest('hex').slice(0, 16);
|
|
4437
|
-
|
|
4483
|
+
const role = safeString(purpose, 24).toLowerCase().replace(/[^a-z0-9_-]+/g, '-') || 'wall';
|
|
4484
|
+
return `${role}-${hash}`;
|
|
4438
4485
|
}
|
|
4439
4486
|
|
|
4440
4487
|
function normalizeLiveStreamStartOptions(options = {}) {
|
|
4441
4488
|
const transfer = buildRemoteFrameTransferDescriptor(options.mode || options.frameMode || DEFAULT_REMOTE_FRAME_MODE);
|
|
4442
|
-
const maxFps =
|
|
4489
|
+
const maxFps = 30;
|
|
4443
4490
|
const fps = clampNumber(options.fps, 1, maxFps, 8);
|
|
4444
4491
|
const maxWidth = clampNumber(options.maxWidth, 320, 3840, 640);
|
|
4445
4492
|
const maxHeight = clampNumber(options.maxHeight, 180, 2160, 360);
|
|
4446
4493
|
const quality = clampNumber(options.quality, 20, 95, 45);
|
|
4447
4494
|
const monitorIndex = normalizeMonitorIndex(options.monitorIndex ?? options.screenIndex ?? options.displayIndex);
|
|
4448
|
-
|
|
4495
|
+
const streamPurpose = safeString(options.streamPurpose || options.purpose || 'wall', 24) || 'wall';
|
|
4496
|
+
return { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose };
|
|
4449
4497
|
}
|
|
4450
4498
|
|
|
4451
4499
|
function liveStreamStartStillPending(activeLiveStream) {
|
|
@@ -4506,16 +4554,17 @@ export function createRemoteHub(options = {}) {
|
|
|
4506
4554
|
}
|
|
4507
4555
|
|
|
4508
4556
|
const now = new Date().toISOString();
|
|
4509
|
-
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
|
|
4510
4557
|
const normalized = normalizeLiveStreamStartOptions({ fps: 2, ...options });
|
|
4511
|
-
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
|
|
4558
|
+
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
4559
|
+
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
4560
|
+
const activeLiveStream = getDeviceLiveStream(device, streamId);
|
|
4512
4561
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4513
|
-
if (
|
|
4514
|
-
&& liveStreamMatchesOptions(
|
|
4515
|
-
&& (
|
|
4562
|
+
if (activeLiveStream
|
|
4563
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4564
|
+
&& (activeLiveStream.open === true || liveStreamStartStillPending(activeLiveStream))) {
|
|
4516
4565
|
return {
|
|
4517
4566
|
ok: true,
|
|
4518
|
-
commandId:
|
|
4567
|
+
commandId: activeLiveStream.commandId,
|
|
4519
4568
|
streamId,
|
|
4520
4569
|
fps,
|
|
4521
4570
|
mode: transfer.mode,
|
|
@@ -4526,7 +4575,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4526
4575
|
};
|
|
4527
4576
|
}
|
|
4528
4577
|
device.counters.commandsSent += 1;
|
|
4529
|
-
device
|
|
4578
|
+
setDeviceLiveStream(device, {
|
|
4530
4579
|
streamId,
|
|
4531
4580
|
commandId,
|
|
4532
4581
|
active: true,
|
|
@@ -4553,8 +4602,9 @@ export function createRemoteHub(options = {}) {
|
|
|
4553
4602
|
stopReason: '',
|
|
4554
4603
|
lastFrameAt: '',
|
|
4555
4604
|
lastFrameSeq: 0,
|
|
4556
|
-
framesReceived: 0
|
|
4557
|
-
|
|
4605
|
+
framesReceived: 0,
|
|
4606
|
+
streamPurpose
|
|
4607
|
+
});
|
|
4558
4608
|
device.counters.liveStreamsStarted += 1;
|
|
4559
4609
|
applySyntheticLiveFrame(device, streamId, {
|
|
4560
4610
|
commandId,
|
|
@@ -4581,30 +4631,31 @@ export function createRemoteHub(options = {}) {
|
|
|
4581
4631
|
}
|
|
4582
4632
|
|
|
4583
4633
|
const now = new Date().toISOString();
|
|
4584
|
-
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
|
|
4585
4634
|
const normalized = normalizeLiveStreamStartOptions(options);
|
|
4586
|
-
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
|
|
4635
|
+
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
4636
|
+
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
4637
|
+
const activeLiveStream = getDeviceLiveStream(device, streamId);
|
|
4587
4638
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4588
|
-
if (
|
|
4639
|
+
if (activeLiveStream
|
|
4589
4640
|
&& options.forceRestart !== true
|
|
4590
4641
|
&& options.reuseExisting === true
|
|
4591
|
-
&& liveStreamMatchesOptions(
|
|
4592
|
-
&& liveStreamIsReusable(
|
|
4642
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4643
|
+
&& liveStreamIsReusable(activeLiveStream)) {
|
|
4593
4644
|
return {
|
|
4594
4645
|
ok: true,
|
|
4595
|
-
commandId:
|
|
4646
|
+
commandId: activeLiveStream.commandId,
|
|
4596
4647
|
streamId,
|
|
4597
|
-
fps: Number(
|
|
4598
|
-
mode:
|
|
4599
|
-
frameMode:
|
|
4600
|
-
monitorIndex: Number(
|
|
4648
|
+
fps: Number(activeLiveStream.fps || fps),
|
|
4649
|
+
mode: activeLiveStream.mode || transfer.mode,
|
|
4650
|
+
frameMode: activeLiveStream.frameMode || transfer.frameMode,
|
|
4651
|
+
monitorIndex: Number(activeLiveStream.monitorIndex || monitorIndex),
|
|
4601
4652
|
reused: true
|
|
4602
4653
|
};
|
|
4603
4654
|
}
|
|
4604
|
-
if (
|
|
4655
|
+
if (activeLiveStream
|
|
4605
4656
|
&& options.forceRestart !== true
|
|
4606
|
-
&& liveStreamMatchesOptions(
|
|
4607
|
-
&& liveStreamIsReusable(
|
|
4657
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4658
|
+
&& liveStreamIsReusable(activeLiveStream)) {
|
|
4608
4659
|
if (options.silentReuse !== true) {
|
|
4609
4660
|
emitRemoteEvent('RemoteLiveStreamStartReused', device, {
|
|
4610
4661
|
streamId,
|
|
@@ -4616,7 +4667,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4616
4667
|
}
|
|
4617
4668
|
return {
|
|
4618
4669
|
ok: true,
|
|
4619
|
-
commandId:
|
|
4670
|
+
commandId: activeLiveStream.commandId,
|
|
4620
4671
|
streamId,
|
|
4621
4672
|
fps,
|
|
4622
4673
|
mode: transfer.mode,
|
|
@@ -4625,7 +4676,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4625
4676
|
reused: true
|
|
4626
4677
|
};
|
|
4627
4678
|
}
|
|
4628
|
-
if (options.forceRestart === true &&
|
|
4679
|
+
if (options.forceRestart === true && activeLiveStream) {
|
|
4629
4680
|
emitRemoteEvent('RemoteLiveStreamForcedRestart', device, {
|
|
4630
4681
|
streamId,
|
|
4631
4682
|
fps,
|
|
@@ -4635,30 +4686,19 @@ export function createRemoteHub(options = {}) {
|
|
|
4635
4686
|
reason: safeString(options.restartReason || 'browser-stale', 128)
|
|
4636
4687
|
});
|
|
4637
4688
|
}
|
|
4638
|
-
if (
|
|
4639
|
-
&& liveStreamMatchesOptions(
|
|
4640
|
-
&&
|
|
4641
|
-
&& !liveStreamIsReusable(
|
|
4689
|
+
if (activeLiveStream
|
|
4690
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4691
|
+
&& activeLiveStream.open === true
|
|
4692
|
+
&& !liveStreamIsReusable(activeLiveStream)) {
|
|
4642
4693
|
emitRemoteEvent('RemoteLiveStreamStaleRestart', device, {
|
|
4643
4694
|
streamId,
|
|
4644
4695
|
fps,
|
|
4645
4696
|
mode: transfer.mode,
|
|
4646
4697
|
frameMode: transfer.frameMode,
|
|
4647
4698
|
monitorIndex,
|
|
4648
|
-
idleMs: getLiveStreamIdleMs(
|
|
4649
|
-
staleAfterMs: getLiveStreamFreshWindowMs(
|
|
4650
|
-
lastFrameAt:
|
|
4651
|
-
});
|
|
4652
|
-
}
|
|
4653
|
-
if (device.activeLiveStream?.active && device.activeLiveStream.streamId && device.activeLiveStream.streamId !== streamId) {
|
|
4654
|
-
sendCommand(deviceId, {
|
|
4655
|
-
command: 'stream.stop',
|
|
4656
|
-
commandId: crypto.randomUUID(),
|
|
4657
|
-
payload: {
|
|
4658
|
-
streamId: device.activeLiveStream.streamId,
|
|
4659
|
-
reason: 'live-stream-replaced',
|
|
4660
|
-
requestedAt: now
|
|
4661
|
-
}
|
|
4699
|
+
idleMs: getLiveStreamIdleMs(activeLiveStream),
|
|
4700
|
+
staleAfterMs: getLiveStreamFreshWindowMs(activeLiveStream),
|
|
4701
|
+
lastFrameAt: activeLiveStream.lastFrameAt || ''
|
|
4662
4702
|
});
|
|
4663
4703
|
}
|
|
4664
4704
|
const result = sendCommand(deviceId, {
|
|
@@ -4680,6 +4720,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4680
4720
|
maxHeight,
|
|
4681
4721
|
quality,
|
|
4682
4722
|
monitorIndex,
|
|
4723
|
+
streamPurpose,
|
|
4683
4724
|
requestedAt: now
|
|
4684
4725
|
}
|
|
4685
4726
|
});
|
|
@@ -4688,7 +4729,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4688
4729
|
return result;
|
|
4689
4730
|
}
|
|
4690
4731
|
|
|
4691
|
-
device
|
|
4732
|
+
setDeviceLiveStream(device, {
|
|
4692
4733
|
streamId,
|
|
4693
4734
|
commandId,
|
|
4694
4735
|
active: true,
|
|
@@ -4715,8 +4756,9 @@ export function createRemoteHub(options = {}) {
|
|
|
4715
4756
|
stopReason: '',
|
|
4716
4757
|
lastFrameAt: '',
|
|
4717
4758
|
lastFrameSeq: 0,
|
|
4718
|
-
framesReceived: 0
|
|
4719
|
-
|
|
4759
|
+
framesReceived: 0,
|
|
4760
|
+
streamPurpose
|
|
4761
|
+
});
|
|
4720
4762
|
device.counters.liveStreamsStarted += 1;
|
|
4721
4763
|
emitRemoteEvent('RemoteLiveStreamStarted', device, {
|
|
4722
4764
|
streamId,
|
|
@@ -4736,10 +4778,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4736
4778
|
const streamId = safeString(options.streamId, 128) || device.activeLiveStream?.streamId || '';
|
|
4737
4779
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4738
4780
|
device.counters.commandsSent += 1;
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4781
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
4782
|
+
if (streamState) {
|
|
4783
|
+
streamState.active = false;
|
|
4784
|
+
streamState.stoppedAt = new Date().toISOString();
|
|
4785
|
+
streamState.stopReason = 'manager-request';
|
|
4743
4786
|
}
|
|
4744
4787
|
device.counters.liveStreamsStopped += 1;
|
|
4745
4788
|
emitRemoteEvent('RemoteLiveStreamStopped', device, {
|
|
@@ -4769,10 +4812,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4769
4812
|
return result;
|
|
4770
4813
|
}
|
|
4771
4814
|
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4815
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
4816
|
+
if (streamState) {
|
|
4817
|
+
streamState.active = false;
|
|
4818
|
+
streamState.stoppedAt = new Date().toISOString();
|
|
4819
|
+
streamState.stopReason = 'manager-request';
|
|
4776
4820
|
}
|
|
4777
4821
|
device.counters.liveStreamsStopped += 1;
|
|
4778
4822
|
emitRemoteEvent('RemoteLiveStreamStopped', device, {
|
package/hub/src/server.js
CHANGED
|
@@ -398,7 +398,7 @@ function normalizeTransferChunk(body = {}) {
|
|
|
398
398
|
|
|
399
399
|
function normalizeLiveOptions(payload = {}) {
|
|
400
400
|
const mode = String(payload.frameMode || payload.mode || 'mode3-h264-hw').trim() || 'mode3-h264-hw';
|
|
401
|
-
const maxFps =
|
|
401
|
+
const maxFps = 30;
|
|
402
402
|
return {
|
|
403
403
|
fps: clampNumber(payload.fps, 1, maxFps, 2),
|
|
404
404
|
maxWidth: clampNumber(payload.maxWidth, 320, 3840, 640),
|
|
@@ -408,6 +408,7 @@ function normalizeLiveOptions(payload = {}) {
|
|
|
408
408
|
monitorSelections: normalizeMonitorSelections(payload.monitorSelections),
|
|
409
409
|
forceRestart: /^(1|true|yes|on)$/i.test(String(payload.forceRestart ?? '')),
|
|
410
410
|
reuseExisting: /^(1|true|yes|on)$/i.test(String(payload.reuseExisting ?? '')),
|
|
411
|
+
streamPurpose: String(payload.streamPurpose || payload.purpose || 'wall').trim().slice(0, 24) || 'wall',
|
|
411
412
|
mode,
|
|
412
413
|
frameMode: mode
|
|
413
414
|
};
|
|
@@ -607,6 +608,14 @@ function updateFrameSubscription(ws, payload = {}) {
|
|
|
607
608
|
registerFrameClient(ws);
|
|
608
609
|
ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
|
|
609
610
|
ws.liveDeskLiveOptions = normalizeLiveOptions(payload);
|
|
611
|
+
if (!(ws.liveDeskStreamIdsByDeviceId instanceof Map)) {
|
|
612
|
+
ws.liveDeskStreamIdsByDeviceId = new Map();
|
|
613
|
+
}
|
|
614
|
+
for (const subscribedDeviceId of ws.liveDeskStreamIdsByDeviceId.keys()) {
|
|
615
|
+
if (!ws.liveDeskDeviceIds.has(subscribedDeviceId)) {
|
|
616
|
+
ws.liveDeskStreamIdsByDeviceId.delete(subscribedDeviceId);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
610
619
|
sendJson(ws, {
|
|
611
620
|
type: 'RemoteFrameSubscription',
|
|
612
621
|
timestamp: new Date().toISOString(),
|
|
@@ -666,8 +675,10 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '',
|
|
|
666
675
|
silentReuse: reason === 'watchdog' && liveOptions.forceRestart !== true
|
|
667
676
|
});
|
|
668
677
|
if (result?.ok) {
|
|
678
|
+
ws.liveDeskStreamIdsByDeviceId.set(deviceId, result.streamId);
|
|
669
679
|
started.push({ deviceId, streamId: result.streamId, fps: result.fps, reused: result.reused === true });
|
|
670
680
|
} else {
|
|
681
|
+
ws.liveDeskStreamIdsByDeviceId.delete(deviceId);
|
|
671
682
|
skipped.push({ deviceId, reason: result?.error || 'start-failed' });
|
|
672
683
|
}
|
|
673
684
|
}
|
|
@@ -836,6 +847,12 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
836
847
|
if (client.readyState !== client.OPEN) {
|
|
837
848
|
continue;
|
|
838
849
|
}
|
|
850
|
+
const expectedStreamId = client.liveDeskStreamIdsByDeviceId instanceof Map
|
|
851
|
+
? client.liveDeskStreamIdsByDeviceId.get(deviceId)
|
|
852
|
+
: '';
|
|
853
|
+
if (expectedStreamId && String(frame.streamId || '') !== expectedStreamId) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
839
856
|
const lane = ensureFrameClientSendLane(client);
|
|
840
857
|
if (client.liveDeskFrameBackpressured && isH264 && !isKeyFrame) {
|
|
841
858
|
lane.dropped += 1;
|
|
@@ -964,6 +981,7 @@ function configureMode4Atlas(ws, payload = {}) {
|
|
|
964
981
|
quality: 60,
|
|
965
982
|
mode: 'mode2-lzo',
|
|
966
983
|
frameMode: 'mode2-lzo',
|
|
984
|
+
streamPurpose: 'atlas',
|
|
967
985
|
monitorIndex: Number(monitorSelections[deviceId] || 0),
|
|
968
986
|
reuseExisting: false,
|
|
969
987
|
silentReuse: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.155",
|
|
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.104",
|
|
35
35
|
"cors": "^2.8.5",
|
|
36
36
|
"express": "^4.21.2",
|
|
37
37
|
"ffmpeg-static": "^5.3.0",
|