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.
- package/hub/src/mode4-atlas-worker.js +32 -7
- package/hub/src/mode4-atlas.js +2 -0
- package/hub/src/remote-hub.js +1 -0
- package/hub/src/server.js +16 -5
- package/package.json +2 -2
- package/web/dist/assets/index-FZaetL6U.js +10 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-CyDjd_HL.js +0 -10
|
@@ -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 =
|
|
14
|
-
const MAX_TILE_HEIGHT =
|
|
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(
|
|
58
|
-
const tileHeight = Math.max(2, Math.min(
|
|
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,
|
|
290
|
-
const height = clamp(message.height, 1,
|
|
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
|
}
|
package/hub/src/mode4-atlas.js
CHANGED
|
@@ -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 });
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -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:
|
|
954
|
-
maxHeight:
|
|
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:
|
|
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.
|
|
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.
|
|
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",
|