replicas-computer 0.2.4 → 0.2.6
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/assets/desktop/replicas-chrome +6 -0
- package/assets/desktop/start-services.sh +10 -1
- package/dist/index.js +43 -22
- package/package.json +1 -1
|
@@ -39,6 +39,8 @@ JSON
|
|
|
39
39
|
touch "$MARKER"
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
|
+
# password-store skips a keyring D-Bus probe that stalls first launch for seconds
|
|
43
|
+
# on a headless image; the network flags keep first-run downloads off the critical path.
|
|
42
44
|
exec /usr/bin/google-chrome-stable \
|
|
43
45
|
--user-data-dir="$PROFILE_DIR" \
|
|
44
46
|
--profile-directory=Default \
|
|
@@ -46,6 +48,10 @@ exec /usr/bin/google-chrome-stable \
|
|
|
46
48
|
--no-default-browser-check \
|
|
47
49
|
--disable-search-engine-choice-screen \
|
|
48
50
|
--disable-sync \
|
|
51
|
+
--password-store=basic \
|
|
52
|
+
--disable-component-update \
|
|
53
|
+
--disable-background-networking \
|
|
54
|
+
--no-pings \
|
|
49
55
|
--remote-debugging-address=127.0.0.1 \
|
|
50
56
|
--remote-debugging-port="$DEBUG_PORT" \
|
|
51
57
|
--start-maximized \
|
|
@@ -384,8 +384,17 @@ fi
|
|
|
384
384
|
|
|
385
385
|
kill_pids "$(legacy_stream_websockify_pids)"
|
|
386
386
|
clear_bridge_pid_if_unhealthy stream has_stream stream_listener_pids stream_pids
|
|
387
|
+
# Forwarded only when set, so the stream server keeps its own default.
|
|
387
388
|
start_bg stream \
|
|
388
389
|
"has_stream" \
|
|
389
|
-
"exec env REPLICAS_DESKTOP_DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_WIDTH=$WIDTH REPLICAS_DESKTOP_HEIGHT=$HEIGHT REPLICAS_DESKTOP_STREAM_FPS=$
|
|
390
|
+
"exec env REPLICAS_DESKTOP_DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_WIDTH=$WIDTH REPLICAS_DESKTOP_HEIGHT=$HEIGHT ${REPLICAS_DESKTOP_STREAM_FPS:+REPLICAS_DESKTOP_STREAM_FPS=$REPLICAS_DESKTOP_STREAM_FPS }REPLICAS_DESKTOP_STREAM_PORT=$STREAM_PORT REPLICAS_DESKTOP_STREAM_WEBROOT=${REPLICAS_DESKTOP_STREAM_WEBROOT:-$REPLICAS_DESKTOP_TEMPLATES/stream} replicas-computer stream"
|
|
391
|
+
|
|
392
|
+
# Takes Chrome's cold-start cost at boot instead of on the user's first click;
|
|
393
|
+
# later launches join this instance through the profile singleton.
|
|
394
|
+
if [ "${REPLICAS_DESKTOP_CHROME_PREWARM:-1}" = "1" ] && command -v replicas-chrome >/dev/null 2>&1; then
|
|
395
|
+
start_bg chrome-prewarm \
|
|
396
|
+
"curl -fsS --max-time 1 http://127.0.0.1:${REPLICAS_DESKTOP_CHROME_DEBUG_PORT:-9222}/json/version" \
|
|
397
|
+
"DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_CHROME_PROFILE_DIR=\"$CHROME_PROFILE_DIR\" exec replicas-chrome --no-startup-window"
|
|
398
|
+
fi
|
|
390
399
|
|
|
391
400
|
exit 0
|
package/dist/index.js
CHANGED
|
@@ -2694,11 +2694,13 @@ async function computerLaunchCommand(app, args) {
|
|
|
2694
2694
|
const fullArgs = [...baseArgs.slice(1), ...args];
|
|
2695
2695
|
const expectsNewChromePage = args.some((arg) => !arg.startsWith("-"));
|
|
2696
2696
|
const existingChromePageIds = /* @__PURE__ */ new Set();
|
|
2697
|
+
let chromeAlreadyRunning = false;
|
|
2697
2698
|
if (app === "chrome") {
|
|
2698
2699
|
try {
|
|
2699
2700
|
for (const page of await getChromePages()) {
|
|
2700
2701
|
if (page.id) existingChromePageIds.add(page.id);
|
|
2701
2702
|
}
|
|
2703
|
+
chromeAlreadyRunning = true;
|
|
2702
2704
|
} catch {
|
|
2703
2705
|
}
|
|
2704
2706
|
}
|
|
@@ -2729,10 +2731,12 @@ async function computerLaunchCommand(app, args) {
|
|
|
2729
2731
|
}
|
|
2730
2732
|
}
|
|
2731
2733
|
if (readyPage) break;
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2734
|
+
if (!chromeAlreadyRunning) {
|
|
2735
|
+
try {
|
|
2736
|
+
if (child.pid) process.kill(child.pid, 0);
|
|
2737
|
+
} catch {
|
|
2738
|
+
fail(`Chrome exited before its desktop control channel became ready.`);
|
|
2739
|
+
}
|
|
2736
2740
|
}
|
|
2737
2741
|
await sleep(100);
|
|
2738
2742
|
}
|
|
@@ -2921,7 +2925,7 @@ async function computerStatusCommand() {
|
|
|
2921
2925
|
}
|
|
2922
2926
|
|
|
2923
2927
|
// src/version.ts
|
|
2924
|
-
var COMPUTER_USE_VERSION = "0.2.
|
|
2928
|
+
var COMPUTER_USE_VERSION = "0.2.6";
|
|
2925
2929
|
var COMPUTER_USE_PROTOCOL_VERSION = 1;
|
|
2926
2930
|
|
|
2927
2931
|
// src/program.ts
|
|
@@ -3165,6 +3169,7 @@ import { spawn as spawn4 } from "child_process";
|
|
|
3165
3169
|
import { createSocket } from "dgram";
|
|
3166
3170
|
import { createReadStream, existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
3167
3171
|
import { createServer as createServer2 } from "http";
|
|
3172
|
+
import { availableParallelism } from "os";
|
|
3168
3173
|
import { dirname as dirname6, extname, resolve as resolve2 } from "path";
|
|
3169
3174
|
import { fileURLToPath } from "url";
|
|
3170
3175
|
|
|
@@ -3471,20 +3476,28 @@ var H264RtpAssembler = class {
|
|
|
3471
3476
|
import WebSocket2, { WebSocketServer } from "ws";
|
|
3472
3477
|
var width = Number.parseInt(process.env.REPLICAS_DESKTOP_WIDTH || String(DESKTOP_VIEWER_WIDTH), 10);
|
|
3473
3478
|
var height = Number.parseInt(process.env.REPLICAS_DESKTOP_HEIGHT || String(DESKTOP_VIEWER_HEIGHT), 10);
|
|
3474
|
-
var
|
|
3479
|
+
var cores = availableParallelism();
|
|
3480
|
+
var fps = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_FPS || "", 10) || 60;
|
|
3481
|
+
var x264Preset = cores >= 4 ? "superfast" : "ultrafast";
|
|
3475
3482
|
var port = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_PORT || String(DESKTOP_STREAM_PORT), 10);
|
|
3483
|
+
var configuredTopKbps = (() => {
|
|
3484
|
+
const raw = process.env.REPLICAS_DESKTOP_STREAM_VIDEO_BITRATE?.trim();
|
|
3485
|
+
const match = raw?.match(/^(\d+(?:\.\d+)?)([MmKk]?)$/);
|
|
3486
|
+
if (!match) return 2e4;
|
|
3487
|
+
const value = Number.parseFloat(match[1]);
|
|
3488
|
+
return Math.max(1, Math.round(match[2].toLowerCase() === "m" ? value * 1e3 : value));
|
|
3489
|
+
})();
|
|
3476
3490
|
var qualityLadder = [
|
|
3477
|
-
{ fps,
|
|
3478
|
-
{ fps: Math.max(1, Math.round(fps / 2)),
|
|
3479
|
-
{ fps: Math.max(1, Math.round(fps / 2)),
|
|
3480
|
-
{ fps: Math.max(1, Math.round(fps / 4)),
|
|
3491
|
+
{ fps, kbps: configuredTopKbps, crf: 18 },
|
|
3492
|
+
{ fps: Math.max(1, Math.round(fps / 2)), kbps: 6e3, crf: 20 },
|
|
3493
|
+
{ fps: Math.max(1, Math.round(fps / 2)), kbps: 3e3, crf: 23 },
|
|
3494
|
+
{ fps: Math.max(1, Math.round(fps / 4)), kbps: 1200, crf: 26 }
|
|
3481
3495
|
];
|
|
3482
3496
|
var keyframeMs = 250;
|
|
3483
3497
|
var probeIntervalMs = 250;
|
|
3484
3498
|
var targetBacklogMs = 120;
|
|
3485
3499
|
var congestedBacklogMs = 250;
|
|
3486
3500
|
var congestedDecodeQueue = 3;
|
|
3487
|
-
var probeTimeoutMs = 2e3;
|
|
3488
3501
|
var fallbackBufferedBytes = 512 * 1024;
|
|
3489
3502
|
var maximumBufferedBytes = 4 * 1024 * 1024;
|
|
3490
3503
|
var display = process.env.REPLICAS_DESKTOP_DISPLAY?.replace(/^([^:])/, ":$1") || ":99";
|
|
@@ -3559,9 +3572,9 @@ async function runMediaServer() {
|
|
|
3559
3572
|
let videoBytes = 0;
|
|
3560
3573
|
let audioBytes = 0;
|
|
3561
3574
|
const queueMs = (state) => {
|
|
3575
|
+
if (!state.probeAnswered) return 0;
|
|
3562
3576
|
const outstanding = state.probeSentAt ? Date.now() - state.probeSentAt : 0;
|
|
3563
|
-
|
|
3564
|
-
return Math.max(state.probeAckedMs, outstanding);
|
|
3577
|
+
return Math.max(0, Math.max(state.probeAckedMs, outstanding) - state.baselineMs);
|
|
3565
3578
|
};
|
|
3566
3579
|
const broadcast = (packet, keyframe = false) => {
|
|
3567
3580
|
for (const [client, state] of clients) {
|
|
@@ -3597,7 +3610,7 @@ async function runMediaServer() {
|
|
|
3597
3610
|
audioBytes += rtp.payload.byteLength;
|
|
3598
3611
|
broadcast(mediaPacket(3 /* Audio */, audioClock.microseconds(rtp.timestamp), rtp.payload));
|
|
3599
3612
|
});
|
|
3600
|
-
const superviseEncoder = (label, prepare, buildArgs) => {
|
|
3613
|
+
const superviseEncoder = (label, prepare, buildArgs, niceness = 0) => {
|
|
3601
3614
|
let child;
|
|
3602
3615
|
let retiring;
|
|
3603
3616
|
let restart;
|
|
@@ -3609,7 +3622,8 @@ async function runMediaServer() {
|
|
|
3609
3622
|
if (retiring) return;
|
|
3610
3623
|
clearTimeout(restart);
|
|
3611
3624
|
prepare();
|
|
3612
|
-
const
|
|
3625
|
+
const argv = niceness > 0 ? ["nice", "-n", String(niceness), "ffmpeg", ...buildArgs()] : ["ffmpeg", ...buildArgs()];
|
|
3626
|
+
const spawned = spawn4(argv[0], argv.slice(1), { env: { ...process.env, DISPLAY: display }, stdio: ["ignore", "ignore", "pipe"] });
|
|
3613
3627
|
spawned.stderr?.on("data", (chunk) => process.stderr.write(`[desktop-stream] ${chunk}`));
|
|
3614
3628
|
child = spawned;
|
|
3615
3629
|
spawned.once("exit", (code, signal) => {
|
|
@@ -3697,7 +3711,7 @@ async function runMediaServer() {
|
|
|
3697
3711
|
"-c:v",
|
|
3698
3712
|
"libx264",
|
|
3699
3713
|
"-preset",
|
|
3700
|
-
|
|
3714
|
+
x264Preset,
|
|
3701
3715
|
"-tune",
|
|
3702
3716
|
"zerolatency",
|
|
3703
3717
|
"-profile:v",
|
|
@@ -3717,9 +3731,9 @@ async function runMediaServer() {
|
|
|
3717
3731
|
"-crf",
|
|
3718
3732
|
String(quality.crf),
|
|
3719
3733
|
"-maxrate",
|
|
3720
|
-
quality.
|
|
3734
|
+
`${quality.kbps}k`,
|
|
3721
3735
|
"-bufsize",
|
|
3722
|
-
|
|
3736
|
+
`${Math.round(quality.kbps * keyframeMs / 1e3)}k`,
|
|
3723
3737
|
"-x264-params",
|
|
3724
3738
|
`repeat-headers=1:sliced-threads=1:slices=4:keyint=${keyframeInterval}:min-keyint=${keyframeInterval}:scenecut=0`,
|
|
3725
3739
|
"-fps_mode",
|
|
@@ -3732,7 +3746,7 @@ async function runMediaServer() {
|
|
|
3732
3746
|
"96",
|
|
3733
3747
|
`rtp://127.0.0.1:${videoPort}?pkt_size=1200&connect=1&rtcpport=${videoPort}`
|
|
3734
3748
|
];
|
|
3735
|
-
});
|
|
3749
|
+
}, 10);
|
|
3736
3750
|
const audioEncoder = superviseEncoder("audio", () => audioClock.reset(), () => [
|
|
3737
3751
|
"-hide_banner",
|
|
3738
3752
|
"-loglevel",
|
|
@@ -3788,7 +3802,7 @@ async function runMediaServer() {
|
|
|
3788
3802
|
rung = next;
|
|
3789
3803
|
congestedSeconds = 0;
|
|
3790
3804
|
comfortableSeconds = 0;
|
|
3791
|
-
process.stderr.write(`[desktop-stream] quality rung ${rung} (${qualityLadder[rung].fps} fps, ${qualityLadder[rung].
|
|
3805
|
+
process.stderr.write(`[desktop-stream] quality rung ${rung} (${qualityLadder[rung].fps} fps, ${qualityLadder[rung].kbps}k)
|
|
3792
3806
|
`);
|
|
3793
3807
|
videoEncoder.replace();
|
|
3794
3808
|
};
|
|
@@ -3843,6 +3857,7 @@ async function runMediaServer() {
|
|
|
3843
3857
|
audioBytes,
|
|
3844
3858
|
clientStats: [...clients.values()].flatMap((client) => client.stats ? [client.stats] : []),
|
|
3845
3859
|
clientQueueMs: [...clients.values()].map((client) => Math.round(queueMs(client))),
|
|
3860
|
+
clientBaselineMs: [...clients.values()].map((client) => Number.isFinite(client.baselineMs) ? Math.round(client.baselineMs) : null),
|
|
3846
3861
|
qualityRung: rung,
|
|
3847
3862
|
width,
|
|
3848
3863
|
height,
|
|
@@ -3857,7 +3872,11 @@ async function runMediaServer() {
|
|
|
3857
3872
|
response.end("Not found");
|
|
3858
3873
|
return;
|
|
3859
3874
|
}
|
|
3860
|
-
|
|
3875
|
+
if (extname(path) === ".html") {
|
|
3876
|
+
startEncoders();
|
|
3877
|
+
if (clients.size === 0) stopEncodersSoon();
|
|
3878
|
+
}
|
|
3879
|
+
response.setHeader("cache-control", extname(path) === ".html" ? "no-store" : "private, max-age=3600");
|
|
3861
3880
|
response.setHeader("content-type", contentTypes[extname(path)] || "application/octet-stream");
|
|
3862
3881
|
createReadStream(path).pipe(response);
|
|
3863
3882
|
});
|
|
@@ -3875,7 +3894,8 @@ async function runMediaServer() {
|
|
|
3875
3894
|
input: new InputBridge(display, width, height),
|
|
3876
3895
|
probeSentAt: 0,
|
|
3877
3896
|
probeAckedMs: 0,
|
|
3878
|
-
probeAnswered: false
|
|
3897
|
+
probeAnswered: false,
|
|
3898
|
+
baselineMs: Number.POSITIVE_INFINITY
|
|
3879
3899
|
});
|
|
3880
3900
|
client.send(JSON.stringify(config));
|
|
3881
3901
|
startEncoders();
|
|
@@ -3895,6 +3915,7 @@ async function runMediaServer() {
|
|
|
3895
3915
|
state.probeAckedMs = Date.now() - state.probeSentAt;
|
|
3896
3916
|
state.probeSentAt = 0;
|
|
3897
3917
|
state.probeAnswered = true;
|
|
3918
|
+
state.baselineMs = Math.min(state.baselineMs, state.probeAckedMs);
|
|
3898
3919
|
} else if (message.type === "stats") {
|
|
3899
3920
|
const state = clients.get(client);
|
|
3900
3921
|
if (!state) return;
|