replicas-computer 0.3.11 → 0.3.13

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.
@@ -15,7 +15,7 @@
15
15
  <span id="status-spinner" aria-hidden="true"></span>
16
16
  <span id="status-text">connecting…</span>
17
17
  </div>
18
- <button id="audio-prompt" type="button">Enable audio</button>
18
+ <button id="audio-prompt" type="button" hidden>Enable audio</button>
19
19
  <output id="diagnostics"></output>
20
20
  <script type="module" src="./stream-viewer.js?v=__VERSION__"></script>
21
21
  </body>
package/dist/index.js CHANGED
@@ -2937,7 +2937,7 @@ async function computerStatusCommand() {
2937
2937
  }
2938
2938
 
2939
2939
  // src/version.ts
2940
- var COMPUTER_USE_VERSION = "0.3.11";
2940
+ var COMPUTER_USE_VERSION = "0.3.13";
2941
2941
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
2942
2942
 
2943
2943
  // src/program.ts
@@ -45,6 +45,7 @@ var DESKTOP_WEBRTC_FAILURE_REASONS = {
45
45
  video_stalled: "WebRTC video stopped arriving",
46
46
  full_chroma_undecodable: "WebRTC could not decode H.264 High 4:4:4"
47
47
  };
48
+ var DESKTOP_QUALITY_MESSAGE = "replicas-desktop-quality";
48
49
 
49
50
  // src/input-validation.ts
50
51
  var finite = (value) => typeof value === "number" && Number.isFinite(value);
@@ -100,6 +101,7 @@ diagnostics.classList.toggle("visible", showDiagnostics);
100
101
  var webrtcFrameTimeoutMs = 6e3;
101
102
  var webrtcSkipsAfterFailure = 3;
102
103
  var pointerIntervalMs = 4;
104
+ var qualityWindowMs = 3e4;
103
105
  var fullChromaFailureKey = "replicas-desktop-4:4:4-failed";
104
106
  var skipFullChroma = false;
105
107
  var fullChromaFailure;
@@ -151,6 +153,18 @@ var inboundAt = 0;
151
153
  var inboundHistory = [];
152
154
  var telemetry;
153
155
  var route = { path: "", codec: "", decoded: "", kbps: 0, displayedFps: 0 };
156
+ var qualitySamples = [];
157
+ var qualityWindow;
158
+ var qualityUnseen = { decoded: 0, presented: 0, samples: 0 };
159
+ var qualitySequence = 0;
160
+ var presentedAt = 0;
161
+ var socketDecodedFrames = 0;
162
+ var restartQuality = () => {
163
+ qualitySamples = [];
164
+ qualityWindow = void 0;
165
+ qualitySequence = 0;
166
+ presentedAt = 0;
167
+ };
154
168
  var state = {};
155
169
  globalThis.__replicasDesktop = state;
156
170
  var viewerEvents = [];
@@ -290,6 +304,7 @@ var configureVideo = async (next, preferHardware = true) => {
290
304
  activeVideoConfig = selectedConfig;
291
305
  const decoder = new VideoDecoder({
292
306
  output: (frame) => {
307
+ socketDecodedFrames += 1;
293
308
  const writer = videoWriter;
294
309
  if (!writer || (writer.desiredSize ?? 1) <= 0) {
295
310
  frame.close();
@@ -443,6 +458,7 @@ var useFallback = (reasonCode, failed = false) => {
443
458
  if (failed) webrtcSkips = webrtcSkipsAfterFailure;
444
459
  note("fallback to socket", reason);
445
460
  transport = "websocket";
461
+ restartQuality();
446
462
  transportReason = reason;
447
463
  route = { ...route, path: "proxy", codec: config.videoCodec };
448
464
  announceTransport();
@@ -624,6 +640,7 @@ var connect = () => {
624
640
  candidateErrors = 0;
625
641
  negotiationErrorName = "";
626
642
  lastFrameAt = 0;
643
+ restartQuality();
627
644
  route = { path: "", codec: "", decoded: "", kbps: 0, displayedFps: 0 };
628
645
  announceTransport();
629
646
  socket?.close();
@@ -685,6 +702,8 @@ var observeFrame = () => {
685
702
  presentedFrames += lastPresentedCount ? Math.max(0, metadata.presentedFrames - lastPresentedCount) : 1;
686
703
  lastPresentedCount = metadata.presentedFrames;
687
704
  lastFrameAt = performance.now();
705
+ if (presentedAt && qualityWindow) qualityWindow.longestGapMs = Math.max(qualityWindow.longestGapMs, lastFrameAt - Math.max(presentedAt, qualityWindow.at));
706
+ presentedAt = lastFrameAt;
688
707
  status.classList.remove("visible");
689
708
  observeFrame();
690
709
  });
@@ -777,6 +796,53 @@ var updateDiagnostics = (stats) => {
777
796
  viewerEvents.length ? `viewer events: ${describeEvents(viewerEvents.slice(-5))}` : ""
778
797
  ].filter(Boolean).join("\n");
779
798
  };
799
+ var reportQuality = (stats) => {
800
+ if (window.parent === window || !transport || !config || !telemetry) return;
801
+ const now = performance.now();
802
+ const current = qualityWindow ??= { at: now, presented: presentedFrames, freezes: inbound?.freezeCount ?? 0, longestGapMs: 0 };
803
+ const decoded = transport === "webrtc" ? inbound?.framesDecoded ?? 0 : socketDecodedFrames;
804
+ qualityUnseen = { decoded, presented: presentedFrames, samples: decoded > qualityUnseen.decoded && presentedFrames === qualityUnseen.presented ? qualityUnseen.samples + 1 : 0 };
805
+ if (qualityUnseen.samples >= 4) current.unseen = true;
806
+ qualitySamples.push({ stats, telemetry });
807
+ const windowMs = now - current.at;
808
+ if (windowMs < qualityWindowMs) return;
809
+ const samples = qualitySamples;
810
+ const rungs = samples.map((sample) => sample.telemetry.encoder.rung);
811
+ qualitySamples = [];
812
+ qualityWindow = { at: now, presented: presentedFrames, freezes: inbound?.freezeCount ?? 0, longestGapMs: 0, rung: rungs.at(-1) };
813
+ if (current.unseen) return;
814
+ const mean = (values) => {
815
+ const known = values.filter((value) => value !== void 0);
816
+ return known.length ? Math.round(known.reduce((sum, value) => sum + value, 0) * 10 / known.length) / 10 : void 0;
817
+ };
818
+ const displayedFps = Math.round((presentedFrames - current.presented) * 1e4 / windowMs) / 10;
819
+ const decodedFps = transport === "webrtc" ? mean(samples.map((sample) => sample.stats.videoFps)) : void 0;
820
+ const rtts = samples.map((sample) => sample.stats.rttMs);
821
+ window.parent.postMessage({
822
+ type: DESKTOP_QUALITY_MESSAGE,
823
+ transport,
824
+ path: route.path,
825
+ codec: route.codec,
826
+ profile: telemetry.encoder.profile,
827
+ serverVersion: config.serverVersion ?? "unknown",
828
+ sequence: ++qualitySequence,
829
+ windowMs: Math.round(windowMs),
830
+ displayedFps,
831
+ encoderFps: mean(samples.map((sample) => sample.telemetry.encoder.fps)) ?? 0,
832
+ longestFrameGapMs: Math.round(Math.max(current.longestGapMs, now - Math.max(presentedAt, current.at))),
833
+ qualityRung: mean(rungs) ?? 0,
834
+ worstQualityRung: Math.max(...rungs),
835
+ qualityDrops: rungs.filter((rung, index) => rung > (index ? rungs[index - 1] : current.rung ?? rung)).length,
836
+ decodedHeight: screen.videoHeight,
837
+ inputRttMs: mean(rtts) ?? 0,
838
+ worstInputRttMs: Math.round(Math.max(...rtts)),
839
+ cpuLoadPercent: mean(samples.map((sample) => sample.telemetry.cpu.load * 100 / sample.telemetry.cpu.cores)) ?? 0,
840
+ decodedFps,
841
+ receiveToDecodeMs: mean(samples.map((sample) => sample.stats.receiverProcessingMs)),
842
+ lossPercent: mean(samples.map((sample) => sample.stats.lossPercent)),
843
+ freezes: inbound ? Math.max(0, inbound.freezeCount - current.freezes) : void 0
844
+ }, "*");
845
+ };
780
846
  var inboundOf = (kind) => (stat) => stat.type === "inbound-rtp" && "kind" in stat && stat.kind === kind;
781
847
  var reportWebRtcStats = async (pc, stats) => {
782
848
  const entries = [...(await pc.getStats()).values()];
@@ -868,6 +934,7 @@ var reportWebRtcStats = async (pc, stats) => {
868
934
  if (pair?.availableIncomingBitrate) stats.availableKbps = pair.availableIncomingBitrate / 1e3;
869
935
  sendInput({ type: "stats", ...stats }, true);
870
936
  updateDiagnostics(stats);
937
+ reportQuality(stats);
871
938
  state.framesDropped = video?.framesDropped;
872
939
  };
873
940
  setInterval(() => {
@@ -915,6 +982,7 @@ setInterval(() => {
915
982
  else if (transport === "websocket") {
916
983
  sendInput({ type: "stats", ...stats });
917
984
  updateDiagnostics(stats);
985
+ reportQuality(stats);
918
986
  route.kbps = 0;
919
987
  }
920
988
  }, 250);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",