@linkshell/mac 0.1.5 → 0.1.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/README.md CHANGED
@@ -20,13 +20,19 @@ Encoder initialization/operation failures fall back to stock H.264; `--encoder s
20
20
  explicit comparison path. `playout-delay` 0/0 asks for prompt display, not zero end-to-end delay.
21
21
  The attempted `MaxFrameDelayCount=0` property is not supported by the hardware in the recorded
22
22
  checks and does not establish zero encoder queueing.
23
+ The adapter now admits at most two raw frames to VideoToolbox at once. If both are pending,
24
+ it skips new raw input and retains any key-frame request for the next accepted frame. It never
25
+ discards already-compressed reference frames to enforce this bound.
23
26
 
24
27
  The media peer is an iOS native receiver or a web player, independently of the Node host's
25
28
  werift bulk-data connection. iOS normally decodes H.264 through VideoToolbox into a latest-frame
26
29
  mailbox, then maps the pixel buffer to Metal. Its transparent WebView still owns gestures,
27
30
  controls and the cursor. It requests a ceiling of 120 fps, bounded by both displays, power,
28
31
  thermal state and adaptation; standard receivers retain the 60/30 defaults. No video frames
29
- cross JS. The native module shares the app's JitsiWebRTC M124 framework; this sender uses M154,
32
+ cross JS. Display-link callbacks, texture mapping and GPU submission now run on a dedicated
33
+ iOS render thread; UIKit publishes small layout snapshots. This removes dependence on UI
34
+ event processing, without establishing a phone latency or sustained-frame-rate result.
35
+ The native module shares the app's JitsiWebRTC M124 framework; this sender uses M154,
30
36
  so codec/extension support must be established by the actual negotiation.
31
37
 
32
38
  Failed media falls back to the host's H.264 byte stream. iOS first tries the standard WebView
@@ -179,12 +185,57 @@ already, and stop if it is not rather than have the system ask.
179
185
  | `app-smoke.mjs` | Opens the app, asks for an offer, says what it holds. Part of `check`. |
180
186
  | `loopback.mjs` | The video track with no viewer: the app answers its own offer, and both ends' numbers are printed. Without `--fps`, at the app's own choice of frame rate; `--fps 30,60` holds each of those. `--narrow <bit/s>:<from second>:<for seconds>` holds the bandwidth estimate down for a while, prints every second, and says what each change of frame rate cost; `--timeline`, `--seconds 10`, `--motion`, `--still`, `--encoder own\|stock`, `--json <file>`; the rest in its header. |
181
187
  | `flexfec-check.mjs` | A short functional check over its own loopback UDP proxy: 60 ms each way, every twentieth original video packet dropped. Checks real FlexFEC packet receipt, then successful video without FlexFEC when the test receiver declines it. `--json <file>` saves the evidence. No system network changes or latency claims. |
188
+ | `network-check.mjs` | A 48-second congestion/recovery scenario: an owned UDP proxy changes media capacity from 20 to 0.7 to 20 Mbps, with 10 ms propagation each way and a 200 ms router queue. Includes RTX/FEC in the budget. `--screen`, `--maxFps`, `--maxWidth`, `--app`, repeatable `--trial`, `--json`; passing proves traversal and decoding, not acceptable latency or iPhone performance. |
189
+ | `media-lab.mjs` | Loopback-only encrypted UDP experiment using the same capture, VT H.264 and native decoder. `--network` uses the congestion scenario above; `--no-ltr` checks recovery without acknowledged long-term references. Requires `--loopback --media-lab` in the helper. Not a remote transport. |
190
+ | `codec-check.mjs` | Encode-only H.264/HEVC hardware comparison with fixed `--fps`, `--bitrate`, `--maxWidth` and raw input bound `--pending`. No transport, decoder, picture-quality or display result. HEVC is not enabled for shipping media. |
182
191
  | `setup-shots.mjs` | The setup window drawn into PNG files in every state, light and dark, Chinese and English (24 of them, in `build/setup-shots` or the directory named), with nothing shown and nothing asked of the system. For looking at the window after changing it. |
183
192
  | `app.mjs`, `h264.mjs`, `pointer-shapes.swift` | Shared by the above: opening the app, reading H.264, changing the pointer's picture. |
184
193
 
185
194
  The host's own tests run its real code against this app: `packages/host/test/app.test.ts`, and
186
195
  `packages/host/scripts/screen-check.ts` with a real viewer page.
187
196
 
197
+ ## Media experiments
198
+
199
+ The default remains H.264 over WebRTC. The alternative transport is a laboratory, gated behind
200
+ `--loopback --media-lab`: both sockets bind to `127.0.0.1`, share a fresh in-process key, and
201
+ authenticate every packet with ChaCha20-Poly1305 using direction-specific HKDF keys. A
202
+ 256-packet replay window, 1200-byte datagrams, at most eight assembling frames / 4 MiB / 800 ms,
203
+ and bounded raw inputs keep malformed or delayed input from making queues grow indefinitely.
204
+ There is no remote handshake, pairing integration, NAT traversal or iOS receiver for it.
205
+
206
+ Frame deadlines abandon an expired prediction chain; only an IDR or a refresh based on a
207
+ successfully decoded, acknowledged LTR may resume it. Recovery waits for a decoded ACK before
208
+ admitting speculative deltas. Quiet probes avoid an application-limited feedback deadlock.
209
+ The experimental controller uses delivery feedback, never the proxy's configured capacity.
210
+ These mechanisms are implemented and tested, but the full-screen weak-network comparison
211
+ showed longer freezes than WebRTC. They have not been adopted in the product path.
212
+
213
+ Run the built helper through these tools, selecting an actual display index:
214
+
215
+ ```sh
216
+ TMPDIR=/tmp node tools/loopback.mjs --screen 1 --maxFps 120 --motion --media-probe --seconds 15
217
+ TMPDIR=/tmp node tools/network-check.mjs --screen 1 --maxFps 120 --full-screen-motion --json /tmp/rtc.json
218
+ TMPDIR=/tmp node tools/media-lab.mjs --screen 1 --maxFps 120 --full-screen-motion --network --deadline-ms 300 --json /tmp/udp.json
219
+ TMPDIR=/tmp node tools/codec-check.mjs --screen 1 --fps 120 --codec hevc --bitrate 5000000 --seconds 8
220
+ ```
221
+
222
+ `--media-probe` carries encoder-input monotonic time in an H.264 SEI and matches it at decode,
223
+ instead of assuming sender/receiver RTP timestamps have the same origin. It is loopback-only
224
+ and disabled in normal use; reported time excludes capture waiting and physical presentation.
225
+ `network-check.mjs` enables it automatically. Use `--quality` on the two network tools in a
226
+ separate run to compare sampled, frame-matched luma grids (about 1/31 frames plus recovery
227
+ frames, video/full-range normalized); this adds CPU work and does not measure chroma or text
228
+ readability. `--full-screen-motion` covers the chosen screen with a repeatable scrolling
229
+ fixture and closes it with the helper. No synthetic input or system network settings change.
230
+
231
+ `--budget-keyframes` on the WebRTC diagnostic tool limits requested recovery key-frame quality
232
+ using the previous key frame's size and 0.2 seconds of target bitrate. It is off by default;
233
+ smaller recovery frames can still mean worse pictures. `--trial WebRTC-FrameDropper=Disabled`
234
+ is also experimental: more decoded frames in a narrow link did not ensure fresh content.
235
+ Compare delivered updates, age, freezes, bitrate and quality together. Conditions, binary
236
+ hashes and results are recorded in the [architecture experiment section](../../docs/v2/screen-realtime.md).
237
+ Remove owned temporary JSON/log/build outputs after recording the useful results.
238
+
188
239
  ## Command line
189
240
 
190
241
  | Mode | |
@@ -205,7 +256,10 @@ The host's own tests run its real code against this app: `packages/host/test/app
205
256
  | `--no-playout-delay` | Leaves out the field trial that sends playout-delay 0. |
206
257
  | `--no-flexfec` | Disables FlexFEC advertisement and sending for an explicit comparison. |
207
258
  | `--loopback-no-flexfec` | Makes the test receiver decline FlexFEC through codec preferences, to check an unsupported viewer's path. |
208
- | `--loopback-network` | Only with `--loopback`: passes its ICE candidates to `flexfec-check.mjs`, which supplies proxy candidates. The proxy exists only while that tool runs. |
259
+ | `--loopback-network` | Only with `--loopback`: passes its ICE candidates to `flexfec-check.mjs` or `network-check.mjs`, which supplies proxy candidates. The proxy exists only while that tool runs. |
260
+ | `--media-probe`, `--media-quality` | Loopback-only frame timing, and optional sampled luma comparison. Normal sessions do neither. |
261
+ | `--media-lab`, `--lab-codec-only` | Loopback-only alternative media lab, or encode-only codec probe. Use the tools above; these are not host/phone modes. |
262
+ | `--full-screen-motion`, `--budget-keyframes` | Full-display diagnostic workload; optional experimental key-frame budget, never enabled by default. |
209
263
  | `--trial <name>=<value>` | Another libwebrtc field trial (repeatable). |
210
264
  | `--rtc-log` | libwebrtc's log, as `log` messages. |
211
265
 
@@ -411,14 +465,15 @@ reduces rate when the full picture/rate cannot be carried:
411
465
  - Start at the receiver/source ceiling, normally 120 or 60, with lower stages at 60 and 30
412
466
  where applicable. A source below these rates bounds the starting rate further. Explicit
413
467
  `fps` is a fixed internal override.
414
- - Reduce after 3 consecutive troubled seconds: estimated bandwidth below the full-rate
415
- ceiling × 0.2 × current/full, sent pixel share below 0.9, or encoded output below 0.75 of
416
- the requested active input while capture supplies at least 0.8 of the current rate.
417
- Packet loss alone and time per encoded frame are not independent triggers.
418
- - Raise to the next stage after 10 good seconds: full picture, no quality limitation,
419
- sufficient encoded output and estimated bandwidth at least the full-rate ceiling × 0.4
420
- × next/full. If a raised rate fails again within 30 seconds, the next wait doubles, up
421
- to 160 seconds.
468
+ - Reduce after 3 consecutive troubled seconds: sent pixel share below 0.9, encoded output
469
+ below 0.75 of the requested active input, or average packet send wait above 50 ms.
470
+ Output/queue triggers require capture to supply at least 0.8 of the current rate. A low
471
+ bandwidth estimate, packet loss alone and time per encoded frame are not independent triggers.
472
+ - Probe the next stage after 10 good seconds: full picture, no quality limitation,
473
+ sufficient encoded output and average packet send wait at most 10 ms. Missing delivery
474
+ statistics don't count as health. No fraction of the maximum bitrate is reserved for a
475
+ probe: low-complexity desktop content can sustain a higher rate on less bandwidth.
476
+ If a raised rate fails again within 30 seconds, the next wait doubles, up to 160 seconds.
422
477
  - The first 4 seconds and the 4 after a change are settling periods.
423
478
 
424
479
  Rate changes update ScreenCaptureKit, sender parameters and the bitrate ceiling without
@@ -427,6 +482,11 @@ the sender can adapt them again later. An increase eases the first second's bitr
427
482
  half the previous observed bitrate, with a 1 Mbit/s floor. Neither change guarantees physical
428
483
  presentation cadence.
429
484
 
485
+ The queue thresholds identify sustained pressure; they do not cap the age of individual
486
+ packets already inside WebRTC. A quiet screen's occasional large frame does not lower the
487
+ frame-rate ceiling. `--rtc-log` also logs the bitrate and input-rate estimates supplied to
488
+ the custom encoder, to diagnose overshoot when motion resumes or the rate changes.
489
+
430
490
  Other settings include capture rate slack (1.1×), capture surface queue depth (5), static-frame
431
491
  repeats (0.1 s for a second, then 0.5 s), cursor position/shape polling (60/5 Hz), playout-delay
432
492
  0/0 and FlexFEC field trials. Surface pool sizes are not measured queued-frame counts.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkshell/mac",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "LinkShell.app: the Mac side of the screen — capture, encoding, WebRTC and input, under the app's own permissions.",
5
5
  "license": "MIT",
6
6
  "repository": {