livedesk 0.1.37 → 0.1.39

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.
@@ -2657,6 +2657,19 @@ export function createRemoteHub(options = {}) {
2657
2657
  return false;
2658
2658
  }
2659
2659
 
2660
+ const commandId = safeString(message.commandId, 128);
2661
+ const activeCommandId = safeString(device.activeLiveStream.commandId, 128);
2662
+ if (commandId && activeCommandId && commandId !== activeCommandId) {
2663
+ emitRemoteEvent('RemoteLiveStreamOpenIgnored', device, {
2664
+ reason: 'stale-live-stream-generation',
2665
+ streamId,
2666
+ commandId,
2667
+ activeCommandId,
2668
+ transport
2669
+ });
2670
+ return false;
2671
+ }
2672
+
2660
2673
  const transfer = buildRemoteFrameTransferDescriptorFromMessage(message, device.activeLiveStream.mode || DEFAULT_REMOTE_FRAME_MODE);
2661
2674
  const openedAt = safeString(message.openedAt, 80) || device.lastSeenAt || new Date().toISOString();
2662
2675
  device.activeLiveStream = {
@@ -2697,6 +2710,7 @@ export function createRemoteHub(options = {}) {
2697
2710
  : safeString(framePayload, MAX_STREAM_BASE64_CHARS + 1);
2698
2711
  const frameSeq = Number(message.frameSeq);
2699
2712
  const streamId = safeString(message.streamId, 128) || 'live';
2713
+ const commandId = safeString(message.commandId, 128);
2700
2714
  if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
2701
2715
  device.counters.liveFramesDropped += 1;
2702
2716
  emitRemoteEvent('RemoteFrameDropped', device, {
@@ -2707,6 +2721,19 @@ export function createRemoteHub(options = {}) {
2707
2721
  });
2708
2722
  return false;
2709
2723
  }
2724
+ const activeCommandId = safeString(device.activeLiveStream.commandId, 128);
2725
+ if (commandId && activeCommandId && commandId !== activeCommandId) {
2726
+ device.counters.liveFramesDropped += 1;
2727
+ emitRemoteEvent('RemoteFrameDropped', device, {
2728
+ reason: 'stale-live-stream-generation-frame',
2729
+ streamId,
2730
+ commandId,
2731
+ activeCommandId,
2732
+ frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
2733
+ transport
2734
+ });
2735
+ return false;
2736
+ }
2710
2737
 
2711
2738
  const byteLength = normalizeFrameByteLength(framePayload, frameData);
2712
2739
  if ((!Buffer.isBuffer(framePayload) && !frameData)
@@ -2745,7 +2772,7 @@ export function createRemoteHub(options = {}) {
2745
2772
  device.latestLiveFrame = {
2746
2773
  streamId,
2747
2774
  frameSeq,
2748
- commandId: safeString(message.commandId, 128),
2775
+ commandId,
2749
2776
  width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
2750
2777
  height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
2751
2778
  mimeType,
@@ -3659,6 +3686,41 @@ export function createRemoteHub(options = {}) {
3659
3686
  });
3660
3687
  }
3661
3688
 
3689
+ function makeStableLiveStreamId(deviceId) {
3690
+ const hash = crypto.createHash('sha1').update(String(deviceId || 'device')).digest('hex').slice(0, 16);
3691
+ return `live-${hash}`;
3692
+ }
3693
+
3694
+ function normalizeLiveStreamStartOptions(options = {}) {
3695
+ const fps = clampNumber(options.fps, 1, 24, 8);
3696
+ const maxWidth = clampNumber(options.maxWidth, 320, 2560, 640);
3697
+ const maxHeight = clampNumber(options.maxHeight, 180, 1440, 360);
3698
+ const quality = clampNumber(options.quality, 20, 95, 45);
3699
+ const monitorIndex = normalizeMonitorIndex(options.monitorIndex ?? options.screenIndex ?? options.displayIndex);
3700
+ const transfer = buildRemoteFrameTransferDescriptor(options.mode || options.frameMode || DEFAULT_REMOTE_FRAME_MODE);
3701
+ return { fps, maxWidth, maxHeight, quality, monitorIndex, transfer };
3702
+ }
3703
+
3704
+ function liveStreamStartStillPending(activeLiveStream) {
3705
+ if (!activeLiveStream?.active || activeLiveStream.open === true) {
3706
+ return false;
3707
+ }
3708
+ const startedAt = Date.parse(activeLiveStream.startedAt || '');
3709
+ return Number.isFinite(startedAt) && Date.now() - startedAt < 5000;
3710
+ }
3711
+
3712
+ function liveStreamMatchesOptions(activeLiveStream, normalized) {
3713
+ if (!activeLiveStream?.active) {
3714
+ return false;
3715
+ }
3716
+ return activeLiveStream.frameMode === normalized.transfer.frameMode
3717
+ && Number(activeLiveStream.fps || 0) === normalized.fps
3718
+ && Number(activeLiveStream.maxWidth || 0) === normalized.maxWidth
3719
+ && Number(activeLiveStream.maxHeight || 0) === normalized.maxHeight
3720
+ && Number(activeLiveStream.quality || 0) === normalized.quality
3721
+ && Number(activeLiveStream.monitorIndex || 0) === normalized.monitorIndex;
3722
+ }
3723
+
3662
3724
  function startLiveStream(deviceId, options = {}) {
3663
3725
  const device = devices.get(String(deviceId || ''));
3664
3726
  if (device?.synthetic === true && device.connected) {
@@ -3667,11 +3729,25 @@ export function createRemoteHub(options = {}) {
3667
3729
  }
3668
3730
 
3669
3731
  const now = new Date().toISOString();
3670
- const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
3671
- const fps = clampNumber(options.fps, 1, 24, 2);
3672
- const monitorIndex = normalizeMonitorIndex(options.monitorIndex ?? options.screenIndex ?? options.displayIndex);
3732
+ const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
3733
+ const normalized = normalizeLiveStreamStartOptions({ fps: 2, ...options });
3734
+ const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
3673
3735
  const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
3674
- const transfer = buildRemoteFrameTransferDescriptor(options.mode || DEFAULT_REMOTE_FRAME_MODE);
3736
+ if (device.activeLiveStream?.streamId === streamId
3737
+ && liveStreamMatchesOptions(device.activeLiveStream, normalized)
3738
+ && (device.activeLiveStream.open === true || liveStreamStartStillPending(device.activeLiveStream))) {
3739
+ return {
3740
+ ok: true,
3741
+ commandId: device.activeLiveStream.commandId,
3742
+ streamId,
3743
+ fps,
3744
+ mode: transfer.mode,
3745
+ frameMode: transfer.frameMode,
3746
+ monitorIndex,
3747
+ synthetic: true,
3748
+ reused: true
3749
+ };
3750
+ }
3675
3751
  device.counters.commandsSent += 1;
3676
3752
  device.activeLiveStream = {
3677
3753
  streamId,
@@ -3690,6 +3766,9 @@ export function createRemoteHub(options = {}) {
3690
3766
  transferProtocolVersion: transfer.transferProtocolVersion,
3691
3767
  handshake: transfer.handshake,
3692
3768
  fps,
3769
+ maxWidth,
3770
+ maxHeight,
3771
+ quality,
3693
3772
  monitorIndex,
3694
3773
  monitorCount: 1,
3695
3774
  startedAt: now,
@@ -3702,8 +3781,8 @@ export function createRemoteHub(options = {}) {
3702
3781
  device.counters.liveStreamsStarted += 1;
3703
3782
  applySyntheticLiveFrame(device, streamId, {
3704
3783
  commandId,
3705
- maxWidth: options.maxWidth,
3706
- maxHeight: options.maxHeight,
3784
+ maxWidth,
3785
+ maxHeight,
3707
3786
  monitorIndex,
3708
3787
  fps
3709
3788
  });
@@ -3725,11 +3804,42 @@ export function createRemoteHub(options = {}) {
3725
3804
  }
3726
3805
 
3727
3806
  const now = new Date().toISOString();
3728
- const streamId = safeString(options.streamId, 128) || `live-${Date.now()}`;
3729
- const fps = clampNumber(options.fps, 1, 24, 8);
3730
- const monitorIndex = normalizeMonitorIndex(options.monitorIndex ?? options.screenIndex ?? options.displayIndex);
3807
+ const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId);
3808
+ const normalized = normalizeLiveStreamStartOptions(options);
3809
+ const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer } = normalized;
3731
3810
  const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
3732
- const transfer = buildRemoteFrameTransferDescriptor(options.mode || DEFAULT_REMOTE_FRAME_MODE);
3811
+ if (device.activeLiveStream?.streamId === streamId
3812
+ && liveStreamMatchesOptions(device.activeLiveStream, normalized)
3813
+ && (device.activeLiveStream.open === true || liveStreamStartStillPending(device.activeLiveStream))) {
3814
+ emitRemoteEvent('RemoteLiveStreamStartReused', device, {
3815
+ streamId,
3816
+ fps,
3817
+ mode: transfer.mode,
3818
+ frameMode: transfer.frameMode,
3819
+ monitorIndex
3820
+ });
3821
+ return {
3822
+ ok: true,
3823
+ commandId: device.activeLiveStream.commandId,
3824
+ streamId,
3825
+ fps,
3826
+ mode: transfer.mode,
3827
+ frameMode: transfer.frameMode,
3828
+ monitorIndex,
3829
+ reused: true
3830
+ };
3831
+ }
3832
+ if (device.activeLiveStream?.active && device.activeLiveStream.streamId && device.activeLiveStream.streamId !== streamId) {
3833
+ sendCommand(deviceId, {
3834
+ command: 'stream.stop',
3835
+ commandId: crypto.randomUUID(),
3836
+ payload: {
3837
+ streamId: device.activeLiveStream.streamId,
3838
+ reason: 'live-stream-replaced',
3839
+ requestedAt: now
3840
+ }
3841
+ });
3842
+ }
3733
3843
  const result = sendCommand(deviceId, {
3734
3844
  command: 'stream.start',
3735
3845
  commandId,
@@ -3745,9 +3855,9 @@ export function createRemoteHub(options = {}) {
3745
3855
  transferProtocolVersion: transfer.transferProtocolVersion,
3746
3856
  handshake: transfer.handshake,
3747
3857
  fps,
3748
- maxWidth: clampNumber(options.maxWidth, 320, 2560, 640),
3749
- maxHeight: clampNumber(options.maxHeight, 180, 1440, 360),
3750
- quality: clampNumber(options.quality, 20, 95, 45),
3858
+ maxWidth,
3859
+ maxHeight,
3860
+ quality,
3751
3861
  monitorIndex,
3752
3862
  requestedAt: now
3753
3863
  }
@@ -3774,6 +3884,9 @@ export function createRemoteHub(options = {}) {
3774
3884
  transferProtocolVersion: transfer.transferProtocolVersion,
3775
3885
  handshake: transfer.handshake,
3776
3886
  fps,
3887
+ maxWidth,
3888
+ maxHeight,
3889
+ quality,
3777
3890
  monitorIndex,
3778
3891
  monitorCount: 1,
3779
3892
  startedAt: now,
package/hub/src/server.js CHANGED
@@ -205,8 +205,7 @@ function updateFrameSubscription(ws, payload = {}) {
205
205
  : ws.liveDeskLiveOptions.monitorIndex;
206
206
  const result = remoteHub.startLiveStream(deviceId, {
207
207
  ...ws.liveDeskLiveOptions,
208
- monitorIndex,
209
- streamId: `livedesk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
208
+ monitorIndex
210
209
  });
211
210
  if (result?.ok) {
212
211
  started.push({ deviceId, streamId: result.streamId, fps: result.fps });
@@ -245,6 +244,7 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
245
244
  deviceId: frameEvent.deviceId || frame.deviceId || '',
246
245
  frameSeq: Number(frame.frameSeq || 0) || 0,
247
246
  streamId: frame.streamId || '',
247
+ commandId: frame.commandId || '',
248
248
  width: Number(frame.width || 0) || 0,
249
249
  height: Number(frame.height || 0) || 0,
250
250
  mimeType: frameEvent.mimeType || frame.mimeType || frame.format || 'image/jpeg',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "node": ">=20"
31
31
  },
32
32
  "dependencies": {
33
- "@livedesk/client": "0.1.35",
33
+ "@livedesk/client": "0.1.37",
34
34
  "cors": "^2.8.5",
35
35
  "express": "^4.21.2",
36
36
  "ws": "^8.18.3"