livedesk 0.1.82 → 0.1.83

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.
@@ -22,6 +22,9 @@ const RECENT_THUMBNAIL_FRAME_CACHE_LIMIT = 4;
22
22
  const RECENT_LIVE_FRAME_CACHE_LIMIT = 80;
23
23
  const RECENT_THUMBNAIL_FRAME_CACHE_MAX_BYTES = 3 * 1024 * 1024;
24
24
  const RECENT_LIVE_FRAME_CACHE_MAX_BYTES = 24 * 1024 * 1024;
25
+ const LIVE_STREAM_PENDING_REUSE_MS = 5000;
26
+ const LIVE_STREAM_MIN_FRESH_MS = 3000;
27
+ const LIVE_STREAM_MAX_FRESH_MS = 12000;
25
28
  const REMOTE_PROTOCOL_VERSION = 2;
26
29
  const REMOTE_AGENT_PROTOCOL = 'mindexec.remote.agent';
27
30
  const REMOTE_FRAME_PROTOCOL = 'mindexec.remote.frame.v2';
@@ -3872,7 +3875,37 @@ export function createRemoteHub(options = {}) {
3872
3875
  return false;
3873
3876
  }
3874
3877
  const startedAt = Date.parse(activeLiveStream.startedAt || '');
3875
- return Number.isFinite(startedAt) && Date.now() - startedAt < 5000;
3878
+ return Number.isFinite(startedAt) && Date.now() - startedAt < LIVE_STREAM_PENDING_REUSE_MS;
3879
+ }
3880
+
3881
+ function getLiveStreamFreshWindowMs(activeLiveStream) {
3882
+ const fps = Number(activeLiveStream?.fps || 0);
3883
+ const frameMs = Number.isFinite(fps) && fps > 0 ? 1000 / fps : 500;
3884
+ return Math.max(
3885
+ LIVE_STREAM_MIN_FRESH_MS,
3886
+ Math.min(LIVE_STREAM_MAX_FRESH_MS, Math.round(frameMs * 20))
3887
+ );
3888
+ }
3889
+
3890
+ function getLiveStreamIdleMs(activeLiveStream) {
3891
+ const lastFrameAt = Date.parse(activeLiveStream?.lastFrameAt || activeLiveStream?.openedAt || activeLiveStream?.startedAt || '');
3892
+ if (!Number.isFinite(lastFrameAt)) {
3893
+ return Infinity;
3894
+ }
3895
+ return Math.max(0, Date.now() - lastFrameAt);
3896
+ }
3897
+
3898
+ function liveStreamIsReusable(activeLiveStream) {
3899
+ if (!activeLiveStream?.active) {
3900
+ return false;
3901
+ }
3902
+ if (liveStreamStartStillPending(activeLiveStream)) {
3903
+ return true;
3904
+ }
3905
+ if (activeLiveStream.open !== true) {
3906
+ return false;
3907
+ }
3908
+ return getLiveStreamIdleMs(activeLiveStream) <= getLiveStreamFreshWindowMs(activeLiveStream);
3876
3909
  }
3877
3910
 
3878
3911
  function liveStreamMatchesOptions(activeLiveStream, normalized) {
@@ -3976,7 +4009,7 @@ export function createRemoteHub(options = {}) {
3976
4009
  const commandId = safeString(options.commandId, 128) || crypto.randomUUID();
3977
4010
  if (device.activeLiveStream?.streamId === streamId
3978
4011
  && liveStreamMatchesOptions(device.activeLiveStream, normalized)
3979
- && (device.activeLiveStream.open === true || liveStreamStartStillPending(device.activeLiveStream))) {
4012
+ && liveStreamIsReusable(device.activeLiveStream)) {
3980
4013
  emitRemoteEvent('RemoteLiveStreamStartReused', device, {
3981
4014
  streamId,
3982
4015
  fps,
@@ -3995,6 +4028,21 @@ export function createRemoteHub(options = {}) {
3995
4028
  reused: true
3996
4029
  };
3997
4030
  }
4031
+ if (device.activeLiveStream?.streamId === streamId
4032
+ && liveStreamMatchesOptions(device.activeLiveStream, normalized)
4033
+ && device.activeLiveStream.open === true
4034
+ && !liveStreamIsReusable(device.activeLiveStream)) {
4035
+ emitRemoteEvent('RemoteLiveStreamStaleRestart', device, {
4036
+ streamId,
4037
+ fps,
4038
+ mode: transfer.mode,
4039
+ frameMode: transfer.frameMode,
4040
+ monitorIndex,
4041
+ idleMs: getLiveStreamIdleMs(device.activeLiveStream),
4042
+ staleAfterMs: getLiveStreamFreshWindowMs(device.activeLiveStream),
4043
+ lastFrameAt: device.activeLiveStream.lastFrameAt || ''
4044
+ });
4045
+ }
3998
4046
  if (device.activeLiveStream?.active && device.activeLiveStream.streamId && device.activeLiveStream.streamId !== streamId) {
3999
4047
  sendCommand(deviceId, {
4000
4048
  command: 'stream.stop',
package/hub/src/server.js CHANGED
@@ -27,6 +27,7 @@ const audioClients = new Set();
27
27
  let frameClientSeq = 0;
28
28
  let inputClientSeq = 0;
29
29
  let audioClientSeq = 0;
30
+ const FRAME_LIVE_WATCHDOG_MS = 2500;
30
31
 
31
32
  function handleRemoteHubEvent(type, event) {
32
33
  if (type !== 'RemoteDeviceConnected') {
@@ -246,6 +247,9 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '')
246
247
  skipped.push({ deviceId, reason: result?.error || 'start-failed' });
247
248
  }
248
249
  }
250
+ if (reason === 'watchdog' && !started.some(item => item.reused !== true)) {
251
+ return;
252
+ }
249
253
  sendJson(ws, {
250
254
  type: 'RemoteFrameLiveAutoStart',
251
255
  timestamp: new Date().toISOString(),
@@ -702,6 +706,16 @@ httpServer.on('upgrade', (req, socket, head) => {
702
706
  frameWss.on('connection', (ws, req) => {
703
707
  frameClients.add(ws);
704
708
  ws.liveDeskFrameClientId = `rfws-${++frameClientSeq}`;
709
+ const liveWatchdog = setInterval(() => {
710
+ if (ws.readyState === 1) {
711
+ startFrameSubscriptionLive(ws, 'watchdog');
712
+ }
713
+ }, FRAME_LIVE_WATCHDOG_MS);
714
+ liveWatchdog.unref?.();
715
+ const cleanup = () => {
716
+ clearInterval(liveWatchdog);
717
+ frameClients.delete(ws);
718
+ };
705
719
  try {
706
720
  const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
707
721
  updateFrameSubscription(ws, { deviceIds: parsed.searchParams.get('devices') || '' });
@@ -718,8 +732,8 @@ frameWss.on('connection', (ws, req) => {
718
732
  sendJson(ws, { type: 'RemoteFrameSubscriptionError', error: 'invalid-message' });
719
733
  }
720
734
  });
721
- ws.on('close', () => frameClients.delete(ws));
722
- ws.on('error', () => frameClients.delete(ws));
735
+ ws.on('close', cleanup);
736
+ ws.on('error', cleanup);
723
737
  sendJson(ws, {
724
738
  type: 'RemoteFrameSocketReady',
725
739
  protocol: 'livedesk.remote.frames.binary.v1',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.82",
3
+ "version": "0.1.83",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {