feat(harmony): stream screens over HDC without a companion app

Replace per-frame file capture with the unchanged official Hypium agent, bounded JPEG decoding, independent control RPC and scoped session cleanup. Remove the HAP build and account-signing experiment. All 85 automated checks pass; moving-screen performance and integrated phone input remain unverified because the USB device went offline. Keep published 0.3.2 artifacts unchanged.
This commit is contained in:
2026-09-10 16:07:11 +08:00
parent ca382e6c5e
commit 3ce918564d
57 changed files with 1432 additions and 1524 deletions
+65 -3
View File
@@ -59,6 +59,7 @@ async function app(t) {
screenControl: false,
screenFrameWait: null,
screenFrameError: null,
screenUnchanged: false,
openedUrls: [],
selectedDirectory: "/fixture/folder",
configureError: null,
@@ -105,11 +106,23 @@ async function app(t) {
handler("hdc.screenFrame", async () => {
if (model.screenFrameWait) await model.screenFrameWait;
if (model.screenFrameError) throw model.screenFrameError;
if (model.screenUnchanged && model.screenSequence)
return {
unchanged: true,
width: 1200,
height: 2400,
sequence: model.screenSequence,
geometry: model.screenGeometry,
control: model.screenControl,
receivedFps: 0,
mbps: 0,
};
return {
dataUrl: "data:image/jpeg;base64,/9j/2Q==",
width: 1200,
height: 2400,
captureMs: 300,
receivedFps: 27,
mbps: 3.2,
sequence: ++model.screenSequence,
geometry: model.screenGeometry,
control: model.screenControl,
@@ -473,7 +486,7 @@ test("device disconnect clears app details and prevents stale target operations"
assert.equal($("app-launch").disabled, true);
});
test("screen viewing starts read-only, keeps refreshing and reports measured capture time", async (t) => {
test("screen viewing starts read-only, keeps refreshing and reports stream throughput", async (t) => {
const { $, click, tick, model } = await app(t);
model.hdcAvailable = true;
await click("tab-management");
@@ -482,7 +495,10 @@ test("screen viewing starts read-only, keeps refreshing and reports measured cap
await tick();
assert.equal($("screen-image").hidden, false);
assert.match($("screen-status").textContent, /仅观看/);
assert.match($("screen-stats").textContent, /1200 × 2400.*300 ms/);
assert.match(
$("screen-stats").textContent,
/1200 × 2400.*接收 27 帧\/秒.*3.2 Mbps/,
);
assert.equal($("screen-control").checked, false);
assert.equal($("app-install").disabled, true);
const before = model.screenSequence;
@@ -516,6 +532,52 @@ test("screen text and navigation require enabled control and use the displayed s
assert.equal($("app-install").disabled, false);
});
test("an unchanged live screen preserves its image and does not count repeated frames", async (t) => {
const { $, click, tick, model } = await app(t);
model.hdcAvailable = true;
await click("tab-management");
await click("screen-start");
await tick();
await click("screen-control");
const source = $("screen-image").src;
const sequence = model.screenSequence;
model.screenUnchanged = true;
await tick();
await tick();
assert.equal($("screen-image").src, source);
assert.equal($("screen-image").hidden, false);
assert.equal(model.screenSequence, sequence);
assert.equal($("screen-control").checked, true);
assert.match($("screen-stats").textContent, /接收 0 帧\/秒/);
assert.ok(!model.calls.includes("hdc.screenStop"));
});
test("control does not wait behind a pending video frame", async (t) => {
const { $, click, tick, model } = await app(t);
model.hdcAvailable = true;
await click("tab-management");
await click("screen-start");
await tick();
await click("screen-control");
let release;
model.screenFrameWait = new Promise((resolve) => {
release = resolve;
});
await tick();
$("screen-inputs").querySelector('[data-screen-key="home"]').click();
await settle();
assert.ok(
model.parameters.some(
([name, params]) =>
name === "hdc.screenInput" && params.input.key === "home",
),
);
release();
model.screenFrameWait = null;
await settle();
await click("screen-stop");
});
test("stopping during a pending frame discards that response and permits a fresh screen session", async (t) => {
const { $, click, tick, model } = await app(t);
model.hdcAvailable = true;