@7365admin1/layer-common 4.57.2 → 4.57.3
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/CHANGELOG.md +10 -4
- package/package.json +1 -1
- package/utils/robot-control.ts +62 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.57.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c23d427: Robot live view: the PadBot S2's EZVIZ camera puts an H.264 access-unit delimiter in front of every H.265 frame, which made Chrome refuse every frame (decode error, black picture). The player now neutralises those units in each segment before playback, so the picture shows. The "couldn't be decoded" message no longer blames EZVIZ encryption (disproved).
|
|
8
|
+
|
|
3
9
|
## 4.57.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -1605,11 +1611,11 @@
|
|
|
1605
1611
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1606
1612
|
own rule from two endpoints the console already calls, unprojected:
|
|
1607
1613
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1614
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1615
|
+
GET /api/roles/id/:role that membership's role document
|
|
1610
1616
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1617
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1618
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1613
1619
|
|
|
1614
1620
|
`role.default` is the marker because it is the only property of a platform
|
|
1615
1621
|
staff role no API caller can set - `role.controller.ts` validates create and
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.57.
|
|
5
|
+
"version": "4.57.3",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
package/utils/robot-control.ts
CHANGED
|
@@ -38,11 +38,12 @@ export const LIVE_NO_PICTURE =
|
|
|
38
38
|
export const LIVE_DOWN = "The robot's camera isn't available right now.";
|
|
39
39
|
/**
|
|
40
40
|
* The decoder says it supports the codec but the frames still won't decode
|
|
41
|
-
* (MEDIA_ERR_DECODE).
|
|
42
|
-
*
|
|
41
|
+
* (MEDIA_ERR_DECODE). On the PadBot S2 (EZVIZ H3) that was the stray H.264
|
|
42
|
+
* delimiters `neutraliseStrayAud` now removes (NOT encryption - disproved
|
|
43
|
+
* 2026-09-26 by a software decode of the real stream); kept for anything else.
|
|
43
44
|
*/
|
|
44
45
|
export const LIVE_UNDECODABLE =
|
|
45
|
-
"The robot's camera video couldn't be decoded.
|
|
46
|
+
"The robot's camera video couldn't be decoded. Press Retry. If it keeps happening, contact Seven365 support.";
|
|
46
47
|
const MEDIA_ERR_DECODE = 3;
|
|
47
48
|
|
|
48
49
|
/** Wait before automatic reconnect attempt n (0-based): 2 s, 4 s, 8 s, 16 s, then 30 s. */
|
|
@@ -58,7 +59,62 @@ type THls = {
|
|
|
58
59
|
swapAudioCodec(): void;
|
|
59
60
|
destroy(): void;
|
|
60
61
|
};
|
|
61
|
-
export type THlsCtor = { isSupported(): boolean; new (config?: Record<string, unknown>): THls };
|
|
62
|
+
export type THlsCtor = { isSupported(): boolean; new (config?: Record<string, unknown>): THls; DefaultConfig?: { loader?: any } };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The EZVIZ camera on the PadBot S2 puts an H.264 access-unit delimiter
|
|
66
|
+
* (00 00 01 09 xx) in front of every H.265 frame. Read as H.265 that is a
|
|
67
|
+
* "TSA_N" slice with temporal id 0 - invalid. ffmpeg skips it; hls.js packs it
|
|
68
|
+
* into the frame and Chrome's decoder then refuses every frame
|
|
69
|
+
* (PIPELINE_ERROR_DECODE, 0 frames). Proven on the real stream 2026-09-26:
|
|
70
|
+
* with these units removed Chrome's own decoder decodes 40/40 frames.
|
|
71
|
+
*
|
|
72
|
+
* Rewrites each such header in place, inside the TS packets of video PIDs
|
|
73
|
+
* only, to an "unspecified" H.265 unit (type 48) that hls.js and decoders
|
|
74
|
+
* ignore. Same length, so the TS stays intact. A valid single-layer H.265
|
|
75
|
+
* header never matches (its temporal id is never 0). Returns the count.
|
|
76
|
+
*/
|
|
77
|
+
export function neutraliseStrayAud(data: ArrayBuffer | Uint8Array): number {
|
|
78
|
+
const b = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
79
|
+
const video = new Set<number>();
|
|
80
|
+
let n = 0;
|
|
81
|
+
for (let i = 0; i + 188 <= b.length; i += 188) {
|
|
82
|
+
if (b[i] !== 0x47) continue;
|
|
83
|
+
const afc = (b[i + 3]! >> 4) & 3;
|
|
84
|
+
if (!(afc & 1)) continue;
|
|
85
|
+
const pid = ((b[i + 1]! & 0x1f) << 8) | b[i + 2]!;
|
|
86
|
+
let p = i + 4;
|
|
87
|
+
if (afc & 2) p += 1 + b[p]!;
|
|
88
|
+
// A PES start names its stream: 0xE0-0xEF = video.
|
|
89
|
+
if (b[i + 1]! & 0x40 && b[p] === 0 && b[p + 1] === 0 && b[p + 2] === 1 && (b[p + 3]! & 0xf0) === 0xe0) video.add(pid);
|
|
90
|
+
if (!video.has(pid)) continue;
|
|
91
|
+
for (let k = p; k + 4 < i + 188; k++) {
|
|
92
|
+
if (b[k] === 0 && b[k + 1] === 0 && b[k + 2] === 1 && b[k + 3] === 0x09 && (b[k + 4]! & 7) === 0) {
|
|
93
|
+
b[k + 3] = 0x60;
|
|
94
|
+
b[k + 4] = 0x01;
|
|
95
|
+
n++;
|
|
96
|
+
k += 4;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** hls.js fragment loader that runs `neutraliseStrayAud` on every loaded segment. */
|
|
104
|
+
function strayAudLoader(Base: any) {
|
|
105
|
+
return class extends Base {
|
|
106
|
+
load(context: any, config: any, callbacks: any) {
|
|
107
|
+
const ok = callbacks.onSuccess;
|
|
108
|
+
super.load(context, config, {
|
|
109
|
+
...callbacks,
|
|
110
|
+
onSuccess: (response: any, ...rest: any[]) => {
|
|
111
|
+
if (response?.data instanceof ArrayBuffer) neutraliseStrayAud(response.data);
|
|
112
|
+
ok(response, ...rest);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
62
118
|
|
|
63
119
|
/** The inline panel streams only while it is switched on, the robot is online, the tab is visible and the panel is near the screen. */
|
|
64
120
|
export const liveViewActive = (s: { on: boolean; offline?: boolean; visible: boolean; inView: boolean }) =>
|
|
@@ -186,7 +242,8 @@ export function startLivePlayer(
|
|
|
186
242
|
if (!Hls.isSupported()) return native();
|
|
187
243
|
video.addEventListener("error", noteDecode);
|
|
188
244
|
let recoveries = 0;
|
|
189
|
-
const
|
|
245
|
+
const Base = Hls.DefaultConfig?.loader;
|
|
246
|
+
const h = (hls = new Hls({ enableWorker: true, lowLatencyMode: false, ...(Base ? { fLoader: strayAudLoader(Base) } : {}) }));
|
|
190
247
|
h.on("hlsError", (_e, data) => {
|
|
191
248
|
noteDecode();
|
|
192
249
|
if (!data.fatal) return;
|