@7365admin1/layer-common 4.57.1 → 4.57.2
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/components/Robot/LiveView.vue +4 -2
- package/package.json +1 -1
- package/utils/robot-control.ts +24 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.57.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e901907: Robots: live view says honestly when the camera video cannot be decoded (MEDIA_ERR_DECODE, e.g. EZVIZ video encryption switched on) instead of retrying: at most two hls.js recoveries, then a plain message with Retry. The message no longer blames the browser when the decoder reports support.
|
|
8
|
+
|
|
3
9
|
## 4.57.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -1599,11 +1605,11 @@
|
|
|
1599
1605
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1600
1606
|
own rule from two endpoints the console already calls, unprojected:
|
|
1601
1607
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1608
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1609
|
+
GET /api/roles/id/:role that membership's role document
|
|
1604
1610
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1611
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1612
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1607
1613
|
|
|
1608
1614
|
`role.default` is the marker because it is the only property of a platform
|
|
1609
1615
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
LIVE_DOWN,
|
|
70
70
|
LIVE_NO_PICTURE,
|
|
71
71
|
LIVE_RETRY_LIMIT,
|
|
72
|
+
LIVE_UNDECODABLE,
|
|
72
73
|
liveRetryDelayMs,
|
|
73
74
|
liveVideoKind,
|
|
74
75
|
liveViewActive,
|
|
@@ -168,8 +169,9 @@ watch([videoEl, url], ([el, src]) => {
|
|
|
168
169
|
});
|
|
169
170
|
function onPlayerFail(message: string) {
|
|
170
171
|
stopPlayer = undefined;
|
|
171
|
-
if (message === LIVE_NO_PICTURE) {
|
|
172
|
-
//
|
|
172
|
+
if (message === LIVE_NO_PICTURE || message === LIVE_UNDECODABLE) {
|
|
173
|
+
// Can't be shown (browser codec / encrypted camera video): a fresh URL won't help,
|
|
174
|
+
// so no silent retries behind the message; Retry is the user's.
|
|
173
175
|
gen++;
|
|
174
176
|
clearTimeout(timer);
|
|
175
177
|
error.value = message;
|
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.2",
|
|
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
|
@@ -36,6 +36,14 @@ export function remintDelayMs(expiresAt: unknown, now: number = Date.now()): num
|
|
|
36
36
|
export const LIVE_NO_PICTURE =
|
|
37
37
|
"This robot's camera sends H.265 video, which this browser can't show. Try Microsoft Edge or Safari, or the PadBot app.";
|
|
38
38
|
export const LIVE_DOWN = "The robot's camera isn't available right now.";
|
|
39
|
+
/**
|
|
40
|
+
* The decoder says it supports the codec but the frames still won't decode
|
|
41
|
+
* (MEDIA_ERR_DECODE). Seen on the PadBot S2 (EZVIZ H3): EZVIZ device video
|
|
42
|
+
* encryption scrambles the frames, so plain HLS can't show them. It's not the browser.
|
|
43
|
+
*/
|
|
44
|
+
export const LIVE_UNDECODABLE =
|
|
45
|
+
"The robot's camera video couldn't be decoded. This usually means video encryption is switched on in the camera's EZVIZ app. Ask the administrator to turn it off, or contact PadBot.";
|
|
46
|
+
const MEDIA_ERR_DECODE = 3;
|
|
39
47
|
|
|
40
48
|
/** Wait before automatic reconnect attempt n (0-based): 2 s, 4 s, 8 s, 16 s, then 30 s. */
|
|
41
49
|
export const liveRetryDelayMs = (n: number) => Math.min(30_000, 2_000 * 2 ** n);
|
|
@@ -88,6 +96,8 @@ const CODEC_ERRORS = new Set(["manifestIncompatibleCodecsError", "bufferAddCodec
|
|
|
88
96
|
* `onFail` fires once, with LIVE_NO_PICTURE when the stream plays but no
|
|
89
97
|
* picture ever decodes (Chrome's native HLS takes H.265-in-TS audio but shows
|
|
90
98
|
* no video, videoWidth stays 0) or the codec is refused; LIVE_DOWN for
|
|
99
|
+
* LIVE_UNDECODABLE when the video element reported MEDIA_ERR_DECODE (after at
|
|
100
|
+
* most two hls.js recoveries - no endless retrying); LIVE_DOWN for
|
|
91
101
|
* everything else (network, expired URL, repeated media errors, a stall) -
|
|
92
102
|
* the caller fetches a fresh URL and starts again.
|
|
93
103
|
*/
|
|
@@ -113,6 +123,7 @@ export function startLivePlayer(
|
|
|
113
123
|
let pictured = false;
|
|
114
124
|
let lastTime = -1;
|
|
115
125
|
let still = 0;
|
|
126
|
+
let decodeFailed = false; // the video element reported MEDIA_ERR_DECODE at some point
|
|
116
127
|
|
|
117
128
|
const fail = (message: string) => {
|
|
118
129
|
if (stopped) return;
|
|
@@ -128,7 +139,8 @@ export function startLivePlayer(
|
|
|
128
139
|
opts.onPicture?.();
|
|
129
140
|
} else if (waited >= noPictureMs) {
|
|
130
141
|
// Time moving with no picture = the codec; nothing moving = no stream.
|
|
131
|
-
|
|
142
|
+
noteDecode();
|
|
143
|
+
return fail(decodeFailed ? LIVE_UNDECODABLE : video.currentTime > 0 ? LIVE_NO_PICTURE : LIVE_DOWN);
|
|
132
144
|
}
|
|
133
145
|
return;
|
|
134
146
|
}
|
|
@@ -138,12 +150,19 @@ export function startLivePlayer(
|
|
|
138
150
|
still = 0;
|
|
139
151
|
} else if ((still += tickMs) >= stallMs) fail(LIVE_DOWN);
|
|
140
152
|
}, tickMs);
|
|
141
|
-
const
|
|
153
|
+
const noteDecode = () => {
|
|
154
|
+
if (video.error?.code === MEDIA_ERR_DECODE) decodeFailed = true;
|
|
155
|
+
};
|
|
156
|
+
const onError = () => {
|
|
157
|
+
noteDecode();
|
|
158
|
+
fail(decodeFailed ? LIVE_UNDECODABLE : LIVE_DOWN);
|
|
159
|
+
};
|
|
142
160
|
|
|
143
161
|
function stop() {
|
|
144
162
|
stopped = true;
|
|
145
163
|
clearInterval(tick);
|
|
146
164
|
video.removeEventListener("error", onError);
|
|
165
|
+
video.removeEventListener("error", noteDecode);
|
|
147
166
|
hls?.destroy();
|
|
148
167
|
hls = undefined;
|
|
149
168
|
video.removeAttribute("src");
|
|
@@ -165,9 +184,11 @@ export function startLivePlayer(
|
|
|
165
184
|
.then((Hls) => {
|
|
166
185
|
if (stopped) return;
|
|
167
186
|
if (!Hls.isSupported()) return native();
|
|
187
|
+
video.addEventListener("error", noteDecode);
|
|
168
188
|
let recoveries = 0;
|
|
169
189
|
const h = (hls = new Hls({ enableWorker: true, lowLatencyMode: false }));
|
|
170
190
|
h.on("hlsError", (_e, data) => {
|
|
191
|
+
noteDecode();
|
|
171
192
|
if (!data.fatal) return;
|
|
172
193
|
if (CODEC_ERRORS.has(data.details ?? "")) return fail(LIVE_NO_PICTURE);
|
|
173
194
|
// The standard hls.js ladder: recover, then swap audio codec and recover, then give up.
|
|
@@ -175,7 +196,7 @@ export function startLivePlayer(
|
|
|
175
196
|
if (recoveries++ === 1) h.swapAudioCodec();
|
|
176
197
|
return h.recoverMediaError();
|
|
177
198
|
}
|
|
178
|
-
fail(LIVE_DOWN);
|
|
199
|
+
fail(decodeFailed ? LIVE_UNDECODABLE : LIVE_DOWN);
|
|
179
200
|
});
|
|
180
201
|
h.loadSource(url);
|
|
181
202
|
h.attachMedia(video);
|