livedesk 0.1.152 → 0.1.154

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.
@@ -10,8 +10,8 @@ const DEFAULT_HEIGHT = 1080;
10
10
  const DEFAULT_FPS = 20;
11
11
  const MAX_DEVICES = 100;
12
12
  const FRAME_POOL_SIZE = 8;
13
- const MAX_TILE_WIDTH = 320;
14
- const MAX_TILE_HEIGHT = 180;
13
+ const MAX_TILE_WIDTH = 480;
14
+ const MAX_TILE_HEIGHT = 270;
15
15
 
16
16
  const rgb565Red = new Uint8Array(65_536);
17
17
  const rgb565Green = new Uint8Array(65_536);
@@ -33,6 +33,7 @@ let encoderCandidates = [];
33
33
  let encoderOutputSeen = false;
34
34
  let encoderRestartTimer = null;
35
35
  let encoderStartupTimer = null;
36
+ let inputWaitTimer = null;
36
37
  let outputBuffer = Buffer.alloc(0);
37
38
  let frameSeq = 0;
38
39
  let tickTimer = null;
@@ -51,11 +52,14 @@ function normalizeConfig(value) {
51
52
  .map(item => String(item || '').trim()).filter(Boolean))].slice(0, MAX_DEVICES);
52
53
  const maxWidth = clamp(value?.width, 640, 3840, DEFAULT_WIDTH);
53
54
  const maxHeight = clamp(value?.height, 360, 2160, DEFAULT_HEIGHT);
55
+ const sharpTiles = Number(value?.tileWidth) >= 480 || Number(value?.tileHeight) >= 270;
56
+ const sourceTileWidth = sharpTiles ? 480 : 320;
57
+ const sourceTileHeight = sharpTiles ? 270 : 180;
54
58
  const count = Math.max(1, deviceIds.length);
55
59
  const columns = Math.max(1, Math.ceil(Math.sqrt(count)));
56
60
  const rows = Math.max(1, Math.ceil(count / columns));
57
- const tileWidth = Math.max(2, Math.min(MAX_TILE_WIDTH, Math.floor(maxWidth / columns))) & ~1;
58
- const tileHeight = Math.max(2, Math.min(MAX_TILE_HEIGHT, Math.floor(maxHeight / rows))) & ~1;
61
+ const tileWidth = Math.max(2, Math.min(sourceTileWidth, Math.floor(maxWidth / columns))) & ~1;
62
+ const tileHeight = Math.max(2, Math.min(sourceTileHeight, Math.floor(maxHeight / rows))) & ~1;
59
63
  return {
60
64
  deviceIds,
61
65
  maxWidth,
@@ -66,6 +70,8 @@ function normalizeConfig(value) {
66
70
  rows,
67
71
  tileWidth,
68
72
  tileHeight,
73
+ sourceTileWidth,
74
+ sourceTileHeight,
69
75
  fps: clamp(value?.fps, 1, 30, DEFAULT_FPS)
70
76
  };
71
77
  }
@@ -222,7 +228,9 @@ function emitEncodedUnit(payload) {
222
228
  hardwareEncoder: encoder?.candidate?.name || '',
223
229
  layoutVersion,
224
230
  composeMs: lastComposeMs,
225
- poolStarved
231
+ poolStarved,
232
+ readyTileCount: latestTiles.size,
233
+ tileCount: config.deviceIds.length
226
234
  }
227
235
  });
228
236
  }
@@ -286,8 +294,8 @@ function renderTile(tileState, tileLayout) {
286
294
  function ingestFrame(message) {
287
295
  const deviceId = String(message.deviceId || '');
288
296
  if (!deviceId || !config.deviceIds.includes(deviceId)) return;
289
- const width = clamp(message.width, 1, 320, 0);
290
- const height = clamp(message.height, 1, 180, 0);
297
+ const width = clamp(message.width, 1, config.sourceTileWidth, 0);
298
+ const height = clamp(message.height, 1, config.sourceTileHeight, 0);
291
299
  const expectedLength = width * height * 2;
292
300
  if (!width || !height || Number(message.uncompressedByteLength) !== expectedLength) return;
293
301
  try {
@@ -295,6 +303,10 @@ function ingestFrame(message) {
295
303
  const rgb565 = decodeLzo1xBlock(new Uint8Array(compressed), expectedLength);
296
304
  const tileState = { width, height, rgb565, rendered: null, frameSeq: Number(message.frameSeq || 0) };
297
305
  latestTiles.set(deviceId, tileState);
306
+ if (config.deviceIds.length > 0 && config.deviceIds.every(id => latestTiles.has(id))) {
307
+ clearTimeout(inputWaitTimer);
308
+ inputWaitTimer = null;
309
+ }
298
310
  const tileLayout = layout.find(item => item.deviceId === deviceId);
299
311
  if (tileLayout) renderTile(tileState, tileLayout);
300
312
  } catch (error) {
@@ -303,6 +315,7 @@ function ingestFrame(message) {
303
315
  }
304
316
 
305
317
  function writeAtlasFrame() {
318
+ if (latestTiles.size === 0) return;
306
319
  const composeStartedAt = performance.now();
307
320
  const target = framePool.pop();
308
321
  const stdin = encoder?.child?.stdin;
@@ -354,6 +367,17 @@ function configure(value) {
354
367
  const encoderChanged = next.width !== config.width || next.height !== config.height || next.fps !== config.fps;
355
368
  config = next;
356
369
  latestTiles = new Map([...latestTiles].filter(([deviceId]) => config.deviceIds.includes(deviceId)));
370
+ clearTimeout(inputWaitTimer);
371
+ inputWaitTimer = config.deviceIds.length > 0 ? setTimeout(() => {
372
+ const missing = config.deviceIds.filter(deviceId => !latestTiles.has(deviceId));
373
+ if (missing.length > 0) {
374
+ send({
375
+ type: 'log',
376
+ level: 'warn',
377
+ message: `Mode 4 waiting for Mode 2 input from ${missing.length}/${config.deviceIds.length} devices: ${missing.slice(0, 8).join(', ')}`
378
+ });
379
+ }
380
+ }, 3000) : null;
357
381
  rebuildLayout();
358
382
  if (encoderChanged || !encoder) {
359
383
  stopEncoder();
@@ -374,6 +398,7 @@ function shutdown() {
374
398
  if (closed) return;
375
399
  closed = true;
376
400
  clearTimeout(tickTimer);
401
+ clearTimeout(inputWaitTimer);
377
402
  stopEncoder();
378
403
  setTimeout(() => process.exit(0), 50).unref?.();
379
404
  }
@@ -65,6 +65,8 @@ export class Mode4AtlasSession {
65
65
  deviceIds: this.deviceIds,
66
66
  width: Number(options.width || 1920),
67
67
  height: Number(options.height || 1080),
68
+ tileWidth: Number(options.tileWidth || 320),
69
+ tileHeight: Number(options.tileHeight || 180),
68
70
  fps: Number(options.fps || 20)
69
71
  };
70
72
  this.worker?.send({ type: 'configure', ...this.config });
@@ -4588,6 +4588,7 @@ export function createRemoteHub(options = {}) {
4588
4588
  if (device.activeLiveStream?.streamId === streamId
4589
4589
  && options.forceRestart !== true
4590
4590
  && options.reuseExisting === true
4591
+ && liveStreamMatchesOptions(device.activeLiveStream, normalized)
4591
4592
  && liveStreamIsReusable(device.activeLiveStream)) {
4592
4593
  return {
4593
4594
  ok: true,
package/hub/src/server.js CHANGED
@@ -775,7 +775,9 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
775
775
  sameContentStreak: Number(frame.sameContentStreak || 0) || 0,
776
776
  layoutVersion: Number(frame.layoutVersion || 0) || 0,
777
777
  atlasComposeMs: Number(frame.atlasComposeMs || 0) || 0,
778
- atlasPoolStarved: Number(frame.atlasPoolStarved || 0) || 0
778
+ atlasPoolStarved: Number(frame.atlasPoolStarved || 0) || 0,
779
+ atlasReadyTiles: Number(frame.atlasReadyTiles || 0) || 0,
780
+ atlasTileCount: Number(frame.atlasTileCount || 0) || 0
779
781
  };
780
782
  const metaBuffer = Buffer.from(JSON.stringify(metadata), 'utf8');
781
783
  const header = Buffer.allocUnsafe(4);
@@ -917,7 +919,9 @@ function sendMode4AtlasFrame(ws, output) {
917
919
  platformProfile: 'mode4-hub-atlas-worker',
918
920
  layoutVersion: Number(metadata.layoutVersion || 0),
919
921
  atlasComposeMs: Number(metadata.composeMs || 0),
920
- atlasPoolStarved: Number(metadata.poolStarved || 0)
922
+ atlasPoolStarved: Number(metadata.poolStarved || 0),
923
+ atlasReadyTiles: Number(metadata.readyTileCount || 0),
924
+ atlasTileCount: Number(metadata.tileCount || 0)
921
925
  }
922
926
  };
923
927
  const lane = ensureFrameClientSendLane(ws);
@@ -940,23 +944,28 @@ function sendMode4AtlasFrame(ws, output) {
940
944
  function configureMode4Atlas(ws, payload = {}) {
941
945
  const deviceIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId).slice(0, 100);
942
946
  const monitorSelections = normalizeMonitorSelections(payload.monitorSelections);
947
+ const sharpTiles = Number(payload.tileWidth) >= 480 || Number(payload.tileHeight) >= 270;
948
+ const tileWidth = sharpTiles ? 480 : 320;
949
+ const tileHeight = sharpTiles ? 270 : 180;
943
950
  ws.liveDeskAtlasDeviceIds = new Set(deviceIds);
944
951
  ws.liveDeskAtlasSession.configure({
945
952
  deviceIds,
946
953
  width: clampNumber(payload.width, 640, 3840, 1920),
947
954
  height: clampNumber(payload.height, 360, 2160, 1080),
955
+ tileWidth,
956
+ tileHeight,
948
957
  fps: clampNumber(payload.fps, 1, 30, 20)
949
958
  });
950
959
  for (const deviceId of deviceIds) {
951
960
  remoteHub.startLiveStream(deviceId, {
952
961
  fps: 8,
953
- maxWidth: 320,
954
- maxHeight: 180,
962
+ maxWidth: tileWidth,
963
+ maxHeight: tileHeight,
955
964
  quality: 60,
956
965
  mode: 'mode2-lzo',
957
966
  frameMode: 'mode2-lzo',
958
967
  monitorIndex: Number(monitorSelections[deviceId] || 0),
959
- reuseExisting: true,
968
+ reuseExisting: false,
960
969
  silentReuse: true
961
970
  });
962
971
  }
@@ -965,6 +974,8 @@ function configureMode4Atlas(ws, payload = {}) {
965
974
  deviceIds,
966
975
  inputMode: 'mode2-lzo',
967
976
  outputMode: 'mode4-h264-atlas',
977
+ tileWidth,
978
+ tileHeight,
968
979
  timestamp: new Date().toISOString()
969
980
  });
970
981
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.152",
3
+ "version": "0.1.154",
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.102",
34
+ "@livedesk/client": "0.1.103",
35
35
  "cors": "^2.8.5",
36
36
  "express": "^4.21.2",
37
37
  "ffmpeg-static": "^5.3.0",