livedesk 0.1.82 → 0.1.84

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,14 +4009,16 @@ 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))) {
3980
- emitRemoteEvent('RemoteLiveStreamStartReused', device, {
3981
- streamId,
3982
- fps,
3983
- mode: transfer.mode,
3984
- frameMode: transfer.frameMode,
3985
- monitorIndex
3986
- });
4012
+ && liveStreamIsReusable(device.activeLiveStream)) {
4013
+ if (options.silentReuse !== true) {
4014
+ emitRemoteEvent('RemoteLiveStreamStartReused', device, {
4015
+ streamId,
4016
+ fps,
4017
+ mode: transfer.mode,
4018
+ frameMode: transfer.frameMode,
4019
+ monitorIndex
4020
+ });
4021
+ }
3987
4022
  return {
3988
4023
  ok: true,
3989
4024
  commandId: device.activeLiveStream.commandId,
@@ -3995,6 +4030,21 @@ export function createRemoteHub(options = {}) {
3995
4030
  reused: true
3996
4031
  };
3997
4032
  }
4033
+ if (device.activeLiveStream?.streamId === streamId
4034
+ && liveStreamMatchesOptions(device.activeLiveStream, normalized)
4035
+ && device.activeLiveStream.open === true
4036
+ && !liveStreamIsReusable(device.activeLiveStream)) {
4037
+ emitRemoteEvent('RemoteLiveStreamStaleRestart', device, {
4038
+ streamId,
4039
+ fps,
4040
+ mode: transfer.mode,
4041
+ frameMode: transfer.frameMode,
4042
+ monitorIndex,
4043
+ idleMs: getLiveStreamIdleMs(device.activeLiveStream),
4044
+ staleAfterMs: getLiveStreamFreshWindowMs(device.activeLiveStream),
4045
+ lastFrameAt: device.activeLiveStream.lastFrameAt || ''
4046
+ });
4047
+ }
3998
4048
  if (device.activeLiveStream?.active && device.activeLiveStream.streamId && device.activeLiveStream.streamId !== streamId) {
3999
4049
  sendCommand(deviceId, {
4000
4050
  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') {
@@ -238,7 +239,8 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '')
238
239
  : ws.liveDeskLiveOptions.monitorIndex;
239
240
  const result = remoteHub.startLiveStream(deviceId, {
240
241
  ...ws.liveDeskLiveOptions,
241
- monitorIndex
242
+ monitorIndex,
243
+ silentReuse: reason === 'watchdog'
242
244
  });
243
245
  if (result?.ok) {
244
246
  started.push({ deviceId, streamId: result.streamId, fps: result.fps, reused: result.reused === true });
@@ -246,6 +248,9 @@ function startFrameSubscriptionLive(ws, reason = 'subscribe', onlyDeviceId = '')
246
248
  skipped.push({ deviceId, reason: result?.error || 'start-failed' });
247
249
  }
248
250
  }
251
+ if (reason === 'watchdog' && !started.some(item => item.reused !== true)) {
252
+ return;
253
+ }
249
254
  sendJson(ws, {
250
255
  type: 'RemoteFrameLiveAutoStart',
251
256
  timestamp: new Date().toISOString(),
@@ -702,6 +707,16 @@ httpServer.on('upgrade', (req, socket, head) => {
702
707
  frameWss.on('connection', (ws, req) => {
703
708
  frameClients.add(ws);
704
709
  ws.liveDeskFrameClientId = `rfws-${++frameClientSeq}`;
710
+ const liveWatchdog = setInterval(() => {
711
+ if (ws.readyState === 1) {
712
+ startFrameSubscriptionLive(ws, 'watchdog');
713
+ }
714
+ }, FRAME_LIVE_WATCHDOG_MS);
715
+ liveWatchdog.unref?.();
716
+ const cleanup = () => {
717
+ clearInterval(liveWatchdog);
718
+ frameClients.delete(ws);
719
+ };
705
720
  try {
706
721
  const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
707
722
  updateFrameSubscription(ws, { deviceIds: parsed.searchParams.get('devices') || '' });
@@ -718,8 +733,8 @@ frameWss.on('connection', (ws, req) => {
718
733
  sendJson(ws, { type: 'RemoteFrameSubscriptionError', error: 'invalid-message' });
719
734
  }
720
735
  });
721
- ws.on('close', () => frameClients.delete(ws));
722
- ws.on('error', () => frameClients.delete(ws));
736
+ ws.on('close', cleanup);
737
+ ws.on('error', cleanup);
723
738
  sendJson(ws, {
724
739
  type: 'RemoteFrameSocketReady',
725
740
  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.84",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {