livedesk 0.1.154 → 0.1.156
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 +190 -126
- package/hub/src/server.js +31 -2
- package/package.json +2 -2
- package/web/dist/assets/index-D5DWz11p.css +1 -0
- package/web/dist/assets/index-DvP-nQCR.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
|
@@ -551,6 +551,9 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
551
551
|
const keyCode = Number(input.keyCode ?? input.KeyCode);
|
|
552
552
|
const location = Number(input.location ?? input.Location);
|
|
553
553
|
const monitorIndex = Number(input.monitorIndex ?? input.MonitorIndex ?? input.screenIndex ?? input.ScreenIndex ?? input.displayIndex ?? input.DisplayIndex);
|
|
554
|
+
const inputSeq = Number(input.inputSeq ?? input.InputSeq);
|
|
555
|
+
const issuedAtEpochMs = Number(input.issuedAtEpochMs ?? input.IssuedAtEpochMs);
|
|
556
|
+
const hubReceivedAtEpochMs = Number(input.hubReceivedAtEpochMs ?? input.HubReceivedAtEpochMs);
|
|
554
557
|
return {
|
|
555
558
|
type,
|
|
556
559
|
monitorIndex: Number.isFinite(monitorIndex) ? normalizeMonitorIndex(monitorIndex) : 0,
|
|
@@ -570,6 +573,9 @@ function normalizeRemoteInputEvent(value = {}) {
|
|
|
570
573
|
altKey: input.altKey === true || input.AltKey === true,
|
|
571
574
|
metaKey: input.metaKey === true || input.MetaKey === true || input.commandKey === true || input.CommandKey === true,
|
|
572
575
|
controlLeaseId: safeString(input.controlLeaseId || input.ControlLeaseId, 128),
|
|
576
|
+
inputSeq: Number.isSafeInteger(inputSeq) && inputSeq > 0 ? inputSeq : 0,
|
|
577
|
+
issuedAtEpochMs: Number.isFinite(issuedAtEpochMs) ? issuedAtEpochMs : 0,
|
|
578
|
+
hubReceivedAtEpochMs: Number.isFinite(hubReceivedAtEpochMs) ? hubReceivedAtEpochMs : 0,
|
|
573
579
|
issuedAt: safeString(input.issuedAt || input.IssuedAt, 80)
|
|
574
580
|
};
|
|
575
581
|
}
|
|
@@ -1849,17 +1855,18 @@ export function createRemoteHub(options = {}) {
|
|
|
1849
1855
|
}
|
|
1850
1856
|
|
|
1851
1857
|
function applySyntheticLiveFrame(device, streamId, options = {}) {
|
|
1852
|
-
|
|
1858
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
1859
|
+
if (!streamState?.active) {
|
|
1853
1860
|
return null;
|
|
1854
1861
|
}
|
|
1855
1862
|
|
|
1856
|
-
const frame = makeSyntheticFrame(device, streamId ||
|
|
1857
|
-
commandId: options.commandId ||
|
|
1863
|
+
const frame = makeSyntheticFrame(device, streamId || streamState.streamId, streamState.mode || DEFAULT_REMOTE_FRAME_MODE, {
|
|
1864
|
+
commandId: options.commandId || streamState.commandId,
|
|
1858
1865
|
width: options.maxWidth || 960,
|
|
1859
1866
|
height: options.maxHeight || 540,
|
|
1860
|
-
monitorIndex: options.monitorIndex ??
|
|
1861
|
-
monitorCount: options.monitorCount ??
|
|
1862
|
-
fps: options.fps ||
|
|
1867
|
+
monitorIndex: options.monitorIndex ?? streamState.monitorIndex ?? 0,
|
|
1868
|
+
monitorCount: options.monitorCount ?? streamState.monitorCount ?? 1,
|
|
1869
|
+
fps: options.fps || streamState.fps
|
|
1863
1870
|
});
|
|
1864
1871
|
device.latestLiveFrame = frame;
|
|
1865
1872
|
rememberRecentFramePayload(device, 'live', frame);
|
|
@@ -1871,9 +1878,10 @@ export function createRemoteHub(options = {}) {
|
|
|
1871
1878
|
mimeType: frame.mimeType,
|
|
1872
1879
|
byteLength: frame.byteLength
|
|
1873
1880
|
});
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1881
|
+
streamState.latestFrame = frame;
|
|
1882
|
+
streamState.lastFrameAt = frame.receivedAt;
|
|
1883
|
+
streamState.lastFrameSeq = frame.frameSeq;
|
|
1884
|
+
streamState.framesReceived = (streamState.framesReceived || 0) + 1;
|
|
1877
1885
|
device.lastSeenAt = frame.receivedAt;
|
|
1878
1886
|
device.counters.liveFramesReceived += 1;
|
|
1879
1887
|
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
@@ -2645,11 +2653,7 @@ export function createRemoteHub(options = {}) {
|
|
|
2645
2653
|
closeFrameSocket(device, reason);
|
|
2646
2654
|
device.disconnectedAt = new Date().toISOString();
|
|
2647
2655
|
device.lastDisconnectReason = reason;
|
|
2648
|
-
|
|
2649
|
-
device.activeLiveStream.active = false;
|
|
2650
|
-
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
2651
|
-
device.activeLiveStream.stopReason = reason;
|
|
2652
|
-
}
|
|
2656
|
+
deactivateDeviceLiveStreams(device, reason, device.disconnectedAt);
|
|
2653
2657
|
failAllPendingTasks(device, 'device-disconnected');
|
|
2654
2658
|
logWarn('remote', `device disconnected ${device.deviceName} (${deviceId}): ${reason}`);
|
|
2655
2659
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason });
|
|
@@ -3127,7 +3131,8 @@ export function createRemoteHub(options = {}) {
|
|
|
3127
3131
|
|
|
3128
3132
|
function applyLiveStreamOpen(device, message, transport = 'json') {
|
|
3129
3133
|
const streamId = safeString(message.streamId, 128) || 'live';
|
|
3130
|
-
|
|
3134
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
3135
|
+
if (!streamState?.active) {
|
|
3131
3136
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
3132
3137
|
reason: 'stale-live-stream-open',
|
|
3133
3138
|
streamId,
|
|
@@ -3137,7 +3142,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3137
3142
|
}
|
|
3138
3143
|
|
|
3139
3144
|
const commandId = safeString(message.commandId, 128);
|
|
3140
|
-
const activeCommandId = safeString(
|
|
3145
|
+
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3141
3146
|
if (commandId && activeCommandId && commandId !== activeCommandId) {
|
|
3142
3147
|
emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
|
|
3143
3148
|
reason: 'stale-live-stream-generation',
|
|
@@ -3149,10 +3154,10 @@ export function createRemoteHub(options = {}) {
|
|
|
3149
3154
|
return false;
|
|
3150
3155
|
}
|
|
3151
3156
|
|
|
3152
|
-
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message,
|
|
3157
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
3153
3158
|
const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
|
|
3154
|
-
|
|
3155
|
-
...
|
|
3159
|
+
const nextStreamState = {
|
|
3160
|
+
...streamState,
|
|
3156
3161
|
open: true,
|
|
3157
3162
|
openedAt,
|
|
3158
3163
|
openTransport: transport,
|
|
@@ -3165,14 +3170,15 @@ export function createRemoteHub(options = {}) {
|
|
|
3165
3170
|
transferProtocol: transfer.transferProtocol,
|
|
3166
3171
|
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3167
3172
|
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(
|
|
3173
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : Number(streamState.width || 0),
|
|
3174
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : Number(streamState.height || 0),
|
|
3175
|
+
sourceWidth: Number.isFinite(Number(message.sourceWidth)) ? Number(message.sourceWidth) : Number(streamState.sourceWidth || 0),
|
|
3176
|
+
sourceHeight: Number.isFinite(Number(message.sourceHeight)) ? Number(message.sourceHeight) : Number(streamState.sourceHeight || 0),
|
|
3177
|
+
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
|
|
3178
|
+
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
|
|
3179
|
+
openedFrameSeq: Number.isFinite(Number(message.frameSeq)) ? Number(message.frameSeq) : Number(streamState.openedFrameSeq || 0)
|
|
3175
3180
|
};
|
|
3181
|
+
setDeviceLiveStream(device, nextStreamState);
|
|
3176
3182
|
emitRemoteEvent('RemoteLiveStreamOpened', device, {
|
|
3177
3183
|
streamId,
|
|
3178
3184
|
mode: transfer.mode,
|
|
@@ -3192,7 +3198,8 @@ export function createRemoteHub(options = {}) {
|
|
|
3192
3198
|
const frameSeq = Number(message.frameSeq);
|
|
3193
3199
|
const streamId = safeString(message.streamId, 128) || 'live';
|
|
3194
3200
|
const commandId = safeString(message.commandId, 128);
|
|
3195
|
-
|
|
3201
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
3202
|
+
if (!streamState?.active) {
|
|
3196
3203
|
device.counters.liveFramesDropped += 1;
|
|
3197
3204
|
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
3198
3205
|
reason: 'stale-live-stream-frame',
|
|
@@ -3202,7 +3209,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3202
3209
|
});
|
|
3203
3210
|
return false;
|
|
3204
3211
|
}
|
|
3205
|
-
const activeCommandId = safeString(
|
|
3212
|
+
const activeCommandId = safeString(streamState.commandId, 128);
|
|
3206
3213
|
if (commandId && activeCommandId && commandId !== activeCommandId) {
|
|
3207
3214
|
device.counters.liveFramesDropped += 1;
|
|
3208
3215
|
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
@@ -3234,21 +3241,21 @@ export function createRemoteHub(options = {}) {
|
|
|
3234
3241
|
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
3235
3242
|
const payload = buildFramePayloadBuffer(framePayload, frameData);
|
|
3236
3243
|
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
|
-
|
|
3244
|
+
const sameContentStreak = computeSameContentStreak(streamState.latestFrame, contentHash);
|
|
3245
|
+
const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, streamState.mode || DEFAULT_REMOTE_FRAME_MODE);
|
|
3246
|
+
if (streamState.open !== true) {
|
|
3247
|
+
streamState.open = true;
|
|
3248
|
+
streamState.openedAt = device.lastSeenAt;
|
|
3249
|
+
streamState.openTransport = `${transport}-implicit`;
|
|
3250
|
+
streamState.mode = transfer.mode;
|
|
3251
|
+
streamState.frameMode = transfer.frameMode;
|
|
3252
|
+
streamState.frameProfile = transfer.frameProfile;
|
|
3253
|
+
streamState.encoding = transfer.encoding;
|
|
3254
|
+
streamState.codec = transfer.codec;
|
|
3255
|
+
streamState.compression = transfer.compression;
|
|
3256
|
+
streamState.transferProtocol = transfer.transferProtocol;
|
|
3257
|
+
streamState.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
3258
|
+
streamState.handshake = transfer.handshake;
|
|
3252
3259
|
}
|
|
3253
3260
|
device.latestLiveFrame = {
|
|
3254
3261
|
streamId,
|
|
@@ -3284,12 +3291,12 @@ export function createRemoteHub(options = {}) {
|
|
|
3284
3291
|
hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,
|
|
3285
3292
|
hardwareEncoder: safeString(message.hardwareEncoder, 80),
|
|
3286
3293
|
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(
|
|
3294
|
+
monitorIndex: Number.isFinite(Number(message.monitorIndex)) ? normalizeMonitorIndex(message.monitorIndex) : Number(streamState.monitorIndex || 0),
|
|
3295
|
+
monitorCount: Number.isFinite(Number(message.monitorCount)) ? clampNumber(message.monitorCount, 1, 64, 1) : Number(streamState.monitorCount || 1),
|
|
3289
3296
|
transferProtocol: transfer.transferProtocol,
|
|
3290
3297
|
transferProtocolVersion: transfer.transferProtocolVersion,
|
|
3291
3298
|
handshake: transfer.handshake,
|
|
3292
|
-
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) :
|
|
3299
|
+
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : streamState.fps,
|
|
3293
3300
|
capturedAt,
|
|
3294
3301
|
receivedAt: device.lastSeenAt,
|
|
3295
3302
|
byteLength,
|
|
@@ -3303,6 +3310,7 @@ export function createRemoteHub(options = {}) {
|
|
|
3303
3310
|
payload,
|
|
3304
3311
|
accessToken: createFrameAccessToken()
|
|
3305
3312
|
};
|
|
3313
|
+
streamState.latestFrame = device.latestLiveFrame;
|
|
3306
3314
|
rememberRecentFramePayload(device, 'live', device.latestLiveFrame);
|
|
3307
3315
|
emitFrame({
|
|
3308
3316
|
kind: 'live',
|
|
@@ -3312,24 +3320,25 @@ export function createRemoteHub(options = {}) {
|
|
|
3312
3320
|
mimeType: device.latestLiveFrame.mimeType,
|
|
3313
3321
|
byteLength: device.latestLiveFrame.byteLength
|
|
3314
3322
|
});
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3323
|
+
streamState.lastFrameAt = device.lastSeenAt;
|
|
3324
|
+
streamState.lastFrameSeq = frameSeq;
|
|
3325
|
+
streamState.framesReceived = (streamState.framesReceived || 0) + 1;
|
|
3326
|
+
streamState.mode = transfer.mode;
|
|
3327
|
+
streamState.frameMode = transfer.frameMode;
|
|
3328
|
+
streamState.frameProfile = transfer.frameProfile;
|
|
3329
|
+
streamState.encoding = transfer.encoding;
|
|
3330
|
+
streamState.codec = transfer.codec;
|
|
3331
|
+
streamState.compression = transfer.compression;
|
|
3332
|
+
streamState.transferProtocol = transfer.transferProtocol;
|
|
3333
|
+
streamState.transferProtocolVersion = transfer.transferProtocolVersion;
|
|
3334
|
+
streamState.handshake = transfer.handshake;
|
|
3335
|
+
streamState.width = device.latestLiveFrame.width;
|
|
3336
|
+
streamState.height = device.latestLiveFrame.height;
|
|
3337
|
+
streamState.sourceWidth = device.latestLiveFrame.sourceWidth;
|
|
3338
|
+
streamState.sourceHeight = device.latestLiveFrame.sourceHeight;
|
|
3339
|
+
streamState.monitorIndex = device.latestLiveFrame.monitorIndex;
|
|
3340
|
+
streamState.monitorCount = device.latestLiveFrame.monitorCount;
|
|
3341
|
+
ensureDeviceLiveStreams(device).set(streamId, streamState);
|
|
3333
3342
|
device.counters.liveFramesReceived += 1;
|
|
3334
3343
|
return true;
|
|
3335
3344
|
}
|
|
@@ -3600,6 +3609,18 @@ export function createRemoteHub(options = {}) {
|
|
|
3600
3609
|
|
|
3601
3610
|
if (state.inputOnly) {
|
|
3602
3611
|
device.inputLastSeenAt = new Date().toISOString();
|
|
3612
|
+
if (message.type === 'input.applied') {
|
|
3613
|
+
emitEvent('RemoteInputApplied', {
|
|
3614
|
+
deviceId: device.deviceId,
|
|
3615
|
+
inputSeq: Number(message.inputSeq || 0) || 0,
|
|
3616
|
+
inputType: safeString(message.inputType, 48),
|
|
3617
|
+
issuedAtEpochMs: Number(message.issuedAtEpochMs || 0) || 0,
|
|
3618
|
+
hubReceivedAtEpochMs: Number(message.hubReceivedAtEpochMs || 0) || 0,
|
|
3619
|
+
hubForwardedAtEpochMs: Number(message.hubForwardedAtEpochMs || 0) || 0,
|
|
3620
|
+
agentApplyMs: Number(message.agentApplyMs || 0) || 0,
|
|
3621
|
+
hubAcknowledgedAtEpochMs: Date.now()
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3603
3624
|
return;
|
|
3604
3625
|
}
|
|
3605
3626
|
if (state.frameOnly) {
|
|
@@ -4105,11 +4126,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4105
4126
|
device.connected = false;
|
|
4106
4127
|
device.disconnectedAt = new Date().toISOString();
|
|
4107
4128
|
device.lastDisconnectReason = reason;
|
|
4108
|
-
|
|
4109
|
-
device.activeLiveStream.active = false;
|
|
4110
|
-
device.activeLiveStream.stoppedAt = device.disconnectedAt;
|
|
4111
|
-
device.activeLiveStream.stopReason = reason;
|
|
4112
|
-
}
|
|
4129
|
+
deactivateDeviceLiveStreams(device, reason, device.disconnectedAt);
|
|
4113
4130
|
failAllPendingTasks(device, 'device-disconnected');
|
|
4114
4131
|
emitRemoteEvent('RemoteDeviceDisconnected', device, { reason, synthetic: true });
|
|
4115
4132
|
return true;
|
|
@@ -4203,6 +4220,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4203
4220
|
type: 'input.control',
|
|
4204
4221
|
payload: {
|
|
4205
4222
|
...normalized,
|
|
4223
|
+
hubForwardedAtEpochMs: Date.now(),
|
|
4206
4224
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
4207
4225
|
}
|
|
4208
4226
|
});
|
|
@@ -4219,6 +4237,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4219
4237
|
command: 'input.control',
|
|
4220
4238
|
payload: {
|
|
4221
4239
|
...normalized,
|
|
4240
|
+
hubForwardedAtEpochMs: Date.now(),
|
|
4222
4241
|
issuedAt: normalized.issuedAt || new Date().toISOString()
|
|
4223
4242
|
}
|
|
4224
4243
|
});
|
|
@@ -4432,20 +4451,69 @@ export function createRemoteHub(options = {}) {
|
|
|
4432
4451
|
});
|
|
4433
4452
|
}
|
|
4434
4453
|
|
|
4435
|
-
function
|
|
4454
|
+
function ensureDeviceLiveStreams(device) {
|
|
4455
|
+
if (!(device?.activeLiveStreams instanceof Map)) {
|
|
4456
|
+
Object.defineProperty(device, 'activeLiveStreams', {
|
|
4457
|
+
value: new Map(),
|
|
4458
|
+
enumerable: false,
|
|
4459
|
+
configurable: true,
|
|
4460
|
+
writable: true
|
|
4461
|
+
});
|
|
4462
|
+
if (device?.activeLiveStream?.streamId) {
|
|
4463
|
+
device.activeLiveStreams.set(device.activeLiveStream.streamId, device.activeLiveStream);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
return device.activeLiveStreams;
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
function getDeviceLiveStream(device, streamId) {
|
|
4470
|
+
const id = safeString(streamId, 128);
|
|
4471
|
+
if (!id) {
|
|
4472
|
+
return device?.activeLiveStream || null;
|
|
4473
|
+
}
|
|
4474
|
+
return ensureDeviceLiveStreams(device).get(id)
|
|
4475
|
+
|| (device?.activeLiveStream?.streamId === id ? device.activeLiveStream : null);
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
function setDeviceLiveStream(device, stream) {
|
|
4479
|
+
if (!device || !stream?.streamId) {
|
|
4480
|
+
return stream;
|
|
4481
|
+
}
|
|
4482
|
+
ensureDeviceLiveStreams(device).set(stream.streamId, stream);
|
|
4483
|
+
device.activeLiveStream = stream;
|
|
4484
|
+
return stream;
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
function deactivateDeviceLiveStreams(device, reason, stoppedAt = new Date().toISOString()) {
|
|
4488
|
+
const streams = ensureDeviceLiveStreams(device);
|
|
4489
|
+
for (const stream of streams.values()) {
|
|
4490
|
+
stream.active = false;
|
|
4491
|
+
stream.stoppedAt = stoppedAt;
|
|
4492
|
+
stream.stopReason = reason;
|
|
4493
|
+
}
|
|
4494
|
+
if (device?.activeLiveStream) {
|
|
4495
|
+
device.activeLiveStream.active = false;
|
|
4496
|
+
device.activeLiveStream.stoppedAt = stoppedAt;
|
|
4497
|
+
device.activeLiveStream.stopReason = reason;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
function makeStableLiveStreamId(deviceId, purpose = 'wall') {
|
|
4436
4502
|
const hash = crypto.createHash('sha1').update(String(deviceId || 'device')).digest('hex').slice(0, 16);
|
|
4437
|
-
|
|
4503
|
+
const role = safeString(purpose, 24).toLowerCase().replace(/[^a-z0-9_-]+/g, '-') || 'wall';
|
|
4504
|
+
return `${role}-${hash}`;
|
|
4438
4505
|
}
|
|
4439
4506
|
|
|
4440
4507
|
function normalizeLiveStreamStartOptions(options = {}) {
|
|
4441
4508
|
const transfer = buildRemoteFrameTransferDescriptor(options.mode || options.frameMode || DEFAULT_REMOTE_FRAME_MODE);
|
|
4442
|
-
const maxFps =
|
|
4509
|
+
const maxFps = 30;
|
|
4443
4510
|
const fps = clampNumber(options.fps, 1, maxFps, 8);
|
|
4444
4511
|
const maxWidth = clampNumber(options.maxWidth, 320, 3840, 640);
|
|
4445
4512
|
const maxHeight = clampNumber(options.maxHeight, 180, 2160, 360);
|
|
4446
4513
|
const quality = clampNumber(options.quality, 20, 95, 45);
|
|
4447
4514
|
const monitorIndex = normalizeMonitorIndex(options.monitorIndex ?? options.screenIndex ?? options.displayIndex);
|
|
4448
|
-
|
|
4515
|
+
const streamPurpose = safeString(options.streamPurpose || options.purpose || 'wall', 24) || 'wall';
|
|
4516
|
+
return { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose };
|
|
4449
4517
|
}
|
|
4450
4518
|
|
|
4451
4519
|
function liveStreamStartStillPending(activeLiveStream) {
|
|
@@ -4506,16 +4574,17 @@ export function createRemoteHub(options = {}) {
|
|
|
4506
4574
|
}
|
|
4507
4575
|
|
|
4508
4576
|
const now = new Date().toISOString();
|
|
4509
|
-
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
|
|
4510
4577
|
const normalized = normalizeLiveStreamStartOptions({ fps: 2, ...options });
|
|
4511
|
-
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
|
|
4578
|
+
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
4579
|
+
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
4580
|
+
const activeLiveStream = getDeviceLiveStream(device, streamId);
|
|
4512
4581
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4513
|
-
if (
|
|
4514
|
-
&& liveStreamMatchesOptions(
|
|
4515
|
-
&& (
|
|
4582
|
+
if (activeLiveStream
|
|
4583
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4584
|
+
&& (activeLiveStream.open === true || liveStreamStartStillPending(activeLiveStream))) {
|
|
4516
4585
|
return {
|
|
4517
4586
|
ok: true,
|
|
4518
|
-
commandId:
|
|
4587
|
+
commandId: activeLiveStream.commandId,
|
|
4519
4588
|
streamId,
|
|
4520
4589
|
fps,
|
|
4521
4590
|
mode: transfer.mode,
|
|
@@ -4526,7 +4595,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4526
4595
|
};
|
|
4527
4596
|
}
|
|
4528
4597
|
device.counters.commandsSent += 1;
|
|
4529
|
-
device
|
|
4598
|
+
setDeviceLiveStream(device, {
|
|
4530
4599
|
streamId,
|
|
4531
4600
|
commandId,
|
|
4532
4601
|
active: true,
|
|
@@ -4553,8 +4622,9 @@ export function createRemoteHub(options = {}) {
|
|
|
4553
4622
|
stopReason: '',
|
|
4554
4623
|
lastFrameAt: '',
|
|
4555
4624
|
lastFrameSeq: 0,
|
|
4556
|
-
framesReceived: 0
|
|
4557
|
-
|
|
4625
|
+
framesReceived: 0,
|
|
4626
|
+
streamPurpose
|
|
4627
|
+
});
|
|
4558
4628
|
device.counters.liveStreamsStarted += 1;
|
|
4559
4629
|
applySyntheticLiveFrame(device, streamId, {
|
|
4560
4630
|
commandId,
|
|
@@ -4581,30 +4651,31 @@ export function createRemoteHub(options = {}) {
|
|
|
4581
4651
|
}
|
|
4582
4652
|
|
|
4583
4653
|
const now = new Date().toISOString();
|
|
4584
|
-
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
|
|
4585
4654
|
const normalized = normalizeLiveStreamStartOptions(options);
|
|
4586
|
-
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
|
|
4655
|
+
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
4656
|
+
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
4657
|
+
const activeLiveStream = getDeviceLiveStream(device, streamId);
|
|
4587
4658
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4588
|
-
if (
|
|
4659
|
+
if (activeLiveStream
|
|
4589
4660
|
&& options.forceRestart !== true
|
|
4590
4661
|
&& options.reuseExisting === true
|
|
4591
|
-
&& liveStreamMatchesOptions(
|
|
4592
|
-
&& liveStreamIsReusable(
|
|
4662
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4663
|
+
&& liveStreamIsReusable(activeLiveStream)) {
|
|
4593
4664
|
return {
|
|
4594
4665
|
ok: true,
|
|
4595
|
-
commandId:
|
|
4666
|
+
commandId: activeLiveStream.commandId,
|
|
4596
4667
|
streamId,
|
|
4597
|
-
fps: Number(
|
|
4598
|
-
mode:
|
|
4599
|
-
frameMode:
|
|
4600
|
-
monitorIndex: Number(
|
|
4668
|
+
fps: Number(activeLiveStream.fps || fps),
|
|
4669
|
+
mode: activeLiveStream.mode || transfer.mode,
|
|
4670
|
+
frameMode: activeLiveStream.frameMode || transfer.frameMode,
|
|
4671
|
+
monitorIndex: Number(activeLiveStream.monitorIndex || monitorIndex),
|
|
4601
4672
|
reused: true
|
|
4602
4673
|
};
|
|
4603
4674
|
}
|
|
4604
|
-
if (
|
|
4675
|
+
if (activeLiveStream
|
|
4605
4676
|
&& options.forceRestart !== true
|
|
4606
|
-
&& liveStreamMatchesOptions(
|
|
4607
|
-
&& liveStreamIsReusable(
|
|
4677
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4678
|
+
&& liveStreamIsReusable(activeLiveStream)) {
|
|
4608
4679
|
if (options.silentReuse !== true) {
|
|
4609
4680
|
emitRemoteEvent('RemoteLiveStreamStartReused', device, {
|
|
4610
4681
|
streamId,
|
|
@@ -4616,7 +4687,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4616
4687
|
}
|
|
4617
4688
|
return {
|
|
4618
4689
|
ok: true,
|
|
4619
|
-
commandId:
|
|
4690
|
+
commandId: activeLiveStream.commandId,
|
|
4620
4691
|
streamId,
|
|
4621
4692
|
fps,
|
|
4622
4693
|
mode: transfer.mode,
|
|
@@ -4625,7 +4696,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4625
4696
|
reused: true
|
|
4626
4697
|
};
|
|
4627
4698
|
}
|
|
4628
|
-
if (options.forceRestart === true &&
|
|
4699
|
+
if (options.forceRestart === true && activeLiveStream) {
|
|
4629
4700
|
emitRemoteEvent('RemoteLiveStreamForcedRestart', device, {
|
|
4630
4701
|
streamId,
|
|
4631
4702
|
fps,
|
|
@@ -4635,30 +4706,19 @@ export function createRemoteHub(options = {}) {
|
|
|
4635
4706
|
reason: safeString(options.restartReason || 'browser-stale', 128)
|
|
4636
4707
|
});
|
|
4637
4708
|
}
|
|
4638
|
-
if (
|
|
4639
|
-
&& liveStreamMatchesOptions(
|
|
4640
|
-
&&
|
|
4641
|
-
&& !liveStreamIsReusable(
|
|
4709
|
+
if (activeLiveStream
|
|
4710
|
+
&& liveStreamMatchesOptions(activeLiveStream, normalized)
|
|
4711
|
+
&& activeLiveStream.open === true
|
|
4712
|
+
&& !liveStreamIsReusable(activeLiveStream)) {
|
|
4642
4713
|
emitRemoteEvent('RemoteLiveStreamStaleRestart', device, {
|
|
4643
4714
|
streamId,
|
|
4644
4715
|
fps,
|
|
4645
4716
|
mode: transfer.mode,
|
|
4646
4717
|
frameMode: transfer.frameMode,
|
|
4647
4718
|
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
|
-
}
|
|
4719
|
+
idleMs: getLiveStreamIdleMs(activeLiveStream),
|
|
4720
|
+
staleAfterMs: getLiveStreamFreshWindowMs(activeLiveStream),
|
|
4721
|
+
lastFrameAt: activeLiveStream.lastFrameAt || ''
|
|
4662
4722
|
});
|
|
4663
4723
|
}
|
|
4664
4724
|
const result = sendCommand(deviceId, {
|
|
@@ -4680,6 +4740,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4680
4740
|
maxHeight,
|
|
4681
4741
|
quality,
|
|
4682
4742
|
monitorIndex,
|
|
4743
|
+
streamPurpose,
|
|
4683
4744
|
requestedAt: now
|
|
4684
4745
|
}
|
|
4685
4746
|
});
|
|
@@ -4688,7 +4749,7 @@ export function createRemoteHub(options = {}) {
|
|
|
4688
4749
|
return result;
|
|
4689
4750
|
}
|
|
4690
4751
|
|
|
4691
|
-
device
|
|
4752
|
+
setDeviceLiveStream(device, {
|
|
4692
4753
|
streamId,
|
|
4693
4754
|
commandId,
|
|
4694
4755
|
active: true,
|
|
@@ -4715,8 +4776,9 @@ export function createRemoteHub(options = {}) {
|
|
|
4715
4776
|
stopReason: '',
|
|
4716
4777
|
lastFrameAt: '',
|
|
4717
4778
|
lastFrameSeq: 0,
|
|
4718
|
-
framesReceived: 0
|
|
4719
|
-
|
|
4779
|
+
framesReceived: 0,
|
|
4780
|
+
streamPurpose
|
|
4781
|
+
});
|
|
4720
4782
|
device.counters.liveStreamsStarted += 1;
|
|
4721
4783
|
emitRemoteEvent('RemoteLiveStreamStarted', device, {
|
|
4722
4784
|
streamId,
|
|
@@ -4736,10 +4798,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4736
4798
|
const streamId = safeString(options.streamId, 128) || device.activeLiveStream?.streamId || '';
|
|
4737
4799
|
const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
|
|
4738
4800
|
device.counters.commandsSent += 1;
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4801
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
4802
|
+
if (streamState) {
|
|
4803
|
+
streamState.active = false;
|
|
4804
|
+
streamState.stoppedAt = new Date().toISOString();
|
|
4805
|
+
streamState.stopReason = 'manager-request';
|
|
4743
4806
|
}
|
|
4744
4807
|
device.counters.liveStreamsStopped += 1;
|
|
4745
4808
|
emitRemoteEvent('RemoteLiveStreamStopped', device, {
|
|
@@ -4769,10 +4832,11 @@ export function createRemoteHub(options = {}) {
|
|
|
4769
4832
|
return result;
|
|
4770
4833
|
}
|
|
4771
4834
|
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4835
|
+
const streamState = getDeviceLiveStream(device, streamId);
|
|
4836
|
+
if (streamState) {
|
|
4837
|
+
streamState.active = false;
|
|
4838
|
+
streamState.stoppedAt = new Date().toISOString();
|
|
4839
|
+
streamState.stopReason = 'manager-request';
|
|
4776
4840
|
}
|
|
4777
4841
|
device.counters.liveStreamsStopped += 1;
|
|
4778
4842
|
emitRemoteEvent('RemoteLiveStreamStopped', device, {
|