rvms-vue 0.1.9 → 0.1.11
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
|
@@ -97,6 +97,15 @@ const to = '2025-01-15T11:00:00Z';
|
|
|
97
97
|
|
|
98
98
|
## Components
|
|
99
99
|
|
|
100
|
+
All components accept an optional `token` prop for authenticated backends:
|
|
101
|
+
```vue
|
|
102
|
+
<RvmsVideoPlayer token="your-jwt-token" ... />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The token is sent as:
|
|
106
|
+
- `Authorization: Bearer <token>` header for REST calls (`/api/alarms/recent`)
|
|
107
|
+
- `?token=<token>` query parameter for WebSocket URLs (`/ws/stream`, `/ws/alarms`)
|
|
108
|
+
|
|
100
109
|
### `RvmsVideoPlayer`
|
|
101
110
|
|
|
102
111
|
Full-featured player with alarm overlay. Supports live and playback. Exposes `start()` / `stop()` via template ref.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent as N, ref as
|
|
1
|
+
import { defineComponent as N, ref as k, onMounted as q, onBeforeUnmount as F, watch as J, openBlock as d, createElementBlock as f, normalizeStyle as j, createElementVNode as i, toDisplayString as B, createCommentVNode as S, createTextVNode as K, Fragment as Y, renderList as G } from "vue";
|
|
2
2
|
const X = {
|
|
3
3
|
width: "64",
|
|
4
4
|
height: "64",
|
|
@@ -46,21 +46,22 @@ const X = {
|
|
|
46
46
|
to: {},
|
|
47
47
|
showAlarms: { type: Boolean, default: !0 },
|
|
48
48
|
width: { default: "100%" },
|
|
49
|
-
height: { default: "auto" }
|
|
49
|
+
height: { default: "auto" },
|
|
50
|
+
token: {}
|
|
50
51
|
},
|
|
51
52
|
emits: ["alarm", "stream-status", "alarm-click"],
|
|
52
|
-
setup($, { expose:
|
|
53
|
-
const
|
|
53
|
+
setup($, { expose: P, emit: x }) {
|
|
54
|
+
const s = $, v = x, l = k(null), a = k("idle"), g = k(null), w = k(null), h = k(!1), C = k(s.initialProfile);
|
|
54
55
|
function I() {
|
|
55
56
|
C.value = C.value === "main" ? "sub" : "main", h.value && m();
|
|
56
57
|
}
|
|
57
|
-
const E =
|
|
58
|
-
function
|
|
58
|
+
const E = k(null), L = k([]);
|
|
59
|
+
function U(e) {
|
|
59
60
|
return `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}${e}`;
|
|
60
61
|
}
|
|
61
62
|
function D() {
|
|
62
63
|
M();
|
|
63
|
-
const e = new WebSocket(
|
|
64
|
+
const e = new WebSocket(s.token ? `/ws/alarms?token=${encodeURIComponent(s.token)}` : U("/ws/alarms"));
|
|
64
65
|
E.value = e, e.onmessage = (t) => {
|
|
65
66
|
try {
|
|
66
67
|
const r = JSON.parse(t.data);
|
|
@@ -92,15 +93,17 @@ const X = {
|
|
|
92
93
|
}
|
|
93
94
|
async function A() {
|
|
94
95
|
try {
|
|
95
|
-
const e =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const e = {};
|
|
97
|
+
s.token && (e.Authorization = `Bearer ${s.token}`);
|
|
98
|
+
const t = await fetch("/api/alarms/recent", { headers: e });
|
|
99
|
+
if (t.ok) {
|
|
100
|
+
const r = await t.json();
|
|
101
|
+
L.value = r;
|
|
99
102
|
}
|
|
100
103
|
} catch {
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
|
-
let
|
|
106
|
+
let H = null;
|
|
104
107
|
function O() {
|
|
105
108
|
return {
|
|
106
109
|
cancelled: !1,
|
|
@@ -115,7 +118,7 @@ const X = {
|
|
|
115
118
|
}
|
|
116
119
|
function z() {
|
|
117
120
|
var t;
|
|
118
|
-
const e =
|
|
121
|
+
const e = H;
|
|
119
122
|
if (e) {
|
|
120
123
|
e.cancelled = !0;
|
|
121
124
|
try {
|
|
@@ -137,7 +140,7 @@ const X = {
|
|
|
137
140
|
e.mediaSource.endOfStream();
|
|
138
141
|
} catch {
|
|
139
142
|
}
|
|
140
|
-
if (e.sourceBuffer = null, e.mediaSource = null, e.appendQueue.length = 0, e.preBuffer.length = 0,
|
|
143
|
+
if (e.sourceBuffer = null, e.mediaSource = null, e.appendQueue.length = 0, e.preBuffer.length = 0, H = null, l.value)
|
|
141
144
|
try {
|
|
142
145
|
l.value.pause(), l.value.removeAttribute("src"), l.value.load();
|
|
143
146
|
} catch {
|
|
@@ -152,15 +155,15 @@ const X = {
|
|
|
152
155
|
}
|
|
153
156
|
a.value = "connecting", g.value = null, w.value = null, v("stream-status", "connecting");
|
|
154
157
|
const t = O();
|
|
155
|
-
|
|
158
|
+
H = t;
|
|
156
159
|
const r = new MediaSource();
|
|
157
160
|
t.mediaSource = r, l.value.src = URL.createObjectURL(r);
|
|
158
161
|
const p = () => {
|
|
159
162
|
t.cancelled || t.mediaSource !== r || (a.value = "buffering", v("stream-status", "buffering"), T(t));
|
|
160
163
|
};
|
|
161
164
|
t.sourceOpenHandler = p, r.addEventListener("sourceopen", p);
|
|
162
|
-
const
|
|
163
|
-
|
|
165
|
+
const u = new WebSocket(e);
|
|
166
|
+
u.binaryType = "arraybuffer", t.ws = u, u.onmessage = (b) => {
|
|
164
167
|
if (t.cancelled) return;
|
|
165
168
|
if (typeof b.data == "string") {
|
|
166
169
|
try {
|
|
@@ -178,19 +181,19 @@ const X = {
|
|
|
178
181
|
t.appendQueue.push(
|
|
179
182
|
_.buffer.slice(_.byteOffset, _.byteOffset + _.byteLength)
|
|
180
183
|
), o(t);
|
|
181
|
-
},
|
|
184
|
+
}, u.onerror = () => {
|
|
182
185
|
t.cancelled || a.value !== "error" && (g.value = "WebSocket error", a.value = "error", v("stream-status", "error"));
|
|
183
|
-
},
|
|
186
|
+
}, u.onclose = (b) => {
|
|
184
187
|
t.cancelled || (a.value === "connecting" || a.value === "buffering" ? (g.value = b.reason || `Stream closed (code ${b.code})`, a.value = "error", v("stream-status", "error")) : a.value === "playing" && (a.value = "ended", v("stream-status", "ended")));
|
|
185
188
|
};
|
|
186
189
|
}
|
|
187
190
|
function V(e) {
|
|
188
|
-
const t = (
|
|
191
|
+
const t = (u) => u.toString(16).padStart(2, "0").toUpperCase();
|
|
189
192
|
let r = null, p = null;
|
|
190
|
-
for (let
|
|
191
|
-
const b = String.fromCharCode(e[
|
|
193
|
+
for (let u = 0; u + 7 < e.length; u++) {
|
|
194
|
+
const b = String.fromCharCode(e[u], e[u + 1], e[u + 2], e[u + 3]);
|
|
192
195
|
if (b === "avcC") {
|
|
193
|
-
const _ = e[
|
|
196
|
+
const _ = e[u + 5], R = e[u + 6], Q = e[u + 7];
|
|
194
197
|
_ !== void 0 && R !== void 0 && Q !== void 0 && (r = `avc1.${t(_)}${t(R)}${t(Q)}`);
|
|
195
198
|
}
|
|
196
199
|
b === "hvcC" && (r = "hev1.1.6.L93.B0"), b === "mp4a" && (p = "mp4a.40.2");
|
|
@@ -207,30 +210,30 @@ const X = {
|
|
|
207
210
|
g.value = `Codec not supported: ${p}`, a.value = "error", z();
|
|
208
211
|
return;
|
|
209
212
|
}
|
|
210
|
-
let
|
|
213
|
+
let u;
|
|
211
214
|
try {
|
|
212
|
-
|
|
215
|
+
u = e.mediaSource.addSourceBuffer(p);
|
|
213
216
|
} catch (_) {
|
|
214
217
|
g.value = `addSourceBuffer failed: ${_.message}`, a.value = "error", z();
|
|
215
218
|
return;
|
|
216
219
|
}
|
|
217
|
-
|
|
220
|
+
u.mode = "segments";
|
|
218
221
|
const b = () => {
|
|
219
222
|
e.cancelled || o(e);
|
|
220
223
|
};
|
|
221
|
-
e.updateEndHandler = b,
|
|
224
|
+
e.updateEndHandler = b, u.addEventListener("updateend", b), u.addEventListener("error", () => {
|
|
222
225
|
e.cancelled || (g.value = "SourceBuffer error", a.value = "error");
|
|
223
|
-
}), e.sourceBuffer =
|
|
226
|
+
}), e.sourceBuffer = u, e.appendQueue.unshift(t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength)), e.preBuffer.length = 0, o(e);
|
|
224
227
|
}
|
|
225
228
|
function n(e) {
|
|
226
|
-
const t = e.reduce((
|
|
229
|
+
const t = e.reduce((u, b) => u + b.byteLength, 0), r = new Uint8Array(t);
|
|
227
230
|
let p = 0;
|
|
228
|
-
for (const
|
|
229
|
-
r.set(
|
|
231
|
+
for (const u of e)
|
|
232
|
+
r.set(u, p), p += u.byteLength;
|
|
230
233
|
return r;
|
|
231
234
|
}
|
|
232
235
|
function o(e) {
|
|
233
|
-
var
|
|
236
|
+
var u;
|
|
234
237
|
if (e.cancelled) return;
|
|
235
238
|
const t = e.sourceBuffer, r = e.mediaSource;
|
|
236
239
|
if (!t || !r || r.readyState !== "open" || t.updating) return;
|
|
@@ -241,7 +244,7 @@ const X = {
|
|
|
241
244
|
} catch (b) {
|
|
242
245
|
const _ = b;
|
|
243
246
|
if (_.name === "QuotaExceededError") {
|
|
244
|
-
const R = ((
|
|
247
|
+
const R = ((u = l.value) == null ? void 0 : u.currentTime) ?? 0, Q = Math.max(0, R - 5);
|
|
245
248
|
try {
|
|
246
249
|
t.buffered.length > 0 && t.buffered.start(0) < Q && t.remove(t.buffered.start(0), Q);
|
|
247
250
|
} catch {
|
|
@@ -252,20 +255,22 @@ const X = {
|
|
|
252
255
|
}
|
|
253
256
|
}
|
|
254
257
|
async function m() {
|
|
255
|
-
if (z(), h.value = !0, !
|
|
258
|
+
if (z(), h.value = !0, !s.nvrId || !s.deviceId) {
|
|
256
259
|
g.value = "nvrId and deviceId are required", a.value = "error", h.value = !1, v("stream-status", "error");
|
|
257
260
|
return;
|
|
258
261
|
}
|
|
259
262
|
a.value = "connecting", g.value = null, w.value = null, v("stream-status", "connecting");
|
|
260
263
|
try {
|
|
261
264
|
const e = new URLSearchParams({
|
|
262
|
-
nvrId:
|
|
263
|
-
deviceId:
|
|
264
|
-
mode:
|
|
265
|
+
nvrId: s.nvrId,
|
|
266
|
+
deviceId: s.deviceId,
|
|
267
|
+
mode: s.mode,
|
|
265
268
|
profile: C.value
|
|
266
269
|
});
|
|
267
|
-
|
|
268
|
-
const
|
|
270
|
+
s.from && e.set("from", s.from), s.to && e.set("to", s.to);
|
|
271
|
+
const t = location.protocol === "https:" ? "wss:" : "ws:";
|
|
272
|
+
s.token && e.set("token", s.token);
|
|
273
|
+
const r = `${t}//${location.host}/ws/stream?${e.toString()}`;
|
|
269
274
|
W(r), A();
|
|
270
275
|
} catch (e) {
|
|
271
276
|
g.value = e.message, a.value = "error", h.value = !1, v("stream-status", "error"), console.error("[RvmsVideoPlayer] start failed:", e.message);
|
|
@@ -282,15 +287,15 @@ const X = {
|
|
|
282
287
|
}), F(() => {
|
|
283
288
|
M(), y();
|
|
284
289
|
}), J(
|
|
285
|
-
() => [
|
|
290
|
+
() => [s.nvrId, s.deviceId, s.mode].join("|"),
|
|
286
291
|
() => {
|
|
287
|
-
h.value &&
|
|
292
|
+
h.value && s.nvrId && s.deviceId && m();
|
|
288
293
|
}
|
|
289
|
-
),
|
|
294
|
+
), P({ start: m, stop: y }), (e, t) => (d(), f("div", {
|
|
290
295
|
class: "rvms-player-wrapper",
|
|
291
296
|
style: j({ width: $.width, height: $.height, position: "relative", background: "#000", overflow: "hidden", borderRadius: "8px" })
|
|
292
297
|
}, [
|
|
293
|
-
|
|
298
|
+
i("video", {
|
|
294
299
|
ref_key: "videoEl",
|
|
295
300
|
ref: l,
|
|
296
301
|
muted: "",
|
|
@@ -305,7 +310,7 @@ const X = {
|
|
|
305
310
|
onClick: m
|
|
306
311
|
}, [
|
|
307
312
|
(d(), f("svg", X, [...t[2] || (t[2] = [
|
|
308
|
-
|
|
313
|
+
i("circle", {
|
|
309
314
|
cx: "32",
|
|
310
315
|
cy: "32",
|
|
311
316
|
r: "28",
|
|
@@ -313,31 +318,31 @@ const X = {
|
|
|
313
318
|
"stroke-width": "2",
|
|
314
319
|
fill: "rgba(0,0,0,0.3)"
|
|
315
320
|
}, null, -1),
|
|
316
|
-
|
|
321
|
+
i("polygon", {
|
|
317
322
|
points: "26,20 26,44 46,32",
|
|
318
323
|
fill: "rgba(255,255,255,0.85)"
|
|
319
324
|
}, null, -1)
|
|
320
325
|
])])),
|
|
321
|
-
t[3] || (t[3] =
|
|
326
|
+
t[3] || (t[3] = i("span", { style: { color: "#94a3b8", "font-size": "0.85rem" } }, "Click to play", -1))
|
|
322
327
|
])) : a.value === "connecting" ? (d(), f("div", Z, [...t[4] || (t[4] = [
|
|
323
|
-
|
|
328
|
+
i("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Connecting…", -1)
|
|
324
329
|
])])) : a.value === "buffering" ? (d(), f("div", ee, [...t[5] || (t[5] = [
|
|
325
|
-
|
|
330
|
+
i("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Buffering…", -1)
|
|
326
331
|
])])) : a.value === "error" ? (d(), f("div", te, [
|
|
327
|
-
t[6] || (t[6] =
|
|
332
|
+
t[6] || (t[6] = i("span", { style: { color: "#f87171", "font-weight": "600" } }, "Stream error", -1)),
|
|
328
333
|
g.value ? (d(), f("span", ne, B(g.value), 1)) : S("", !0),
|
|
329
|
-
|
|
334
|
+
i("button", {
|
|
330
335
|
class: "rvms-btn",
|
|
331
336
|
onClick: m
|
|
332
337
|
}, " Retry ")
|
|
333
338
|
])) : a.value === "ended" ? (d(), f("div", re, [
|
|
334
|
-
t[7] || (t[7] =
|
|
335
|
-
|
|
339
|
+
t[7] || (t[7] = i("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
|
|
340
|
+
i("button", {
|
|
336
341
|
class: "rvms-btn",
|
|
337
342
|
onClick: m
|
|
338
343
|
}, " Reconnect ")
|
|
339
344
|
])) : S("", !0),
|
|
340
|
-
|
|
345
|
+
i("div", oe, [
|
|
341
346
|
a.value !== "idle" ? (d(), f("span", {
|
|
342
347
|
key: 0,
|
|
343
348
|
style: j({
|
|
@@ -353,17 +358,17 @@ const X = {
|
|
|
353
358
|
}, B($.mode === "live" ? "LIVE" : "PLAYBACK"), 5)) : S("", !0),
|
|
354
359
|
w.value ? (d(), f("span", ae, B(w.value), 1)) : S("", !0),
|
|
355
360
|
E.value ? (d(), f("span", le, [...t[8] || (t[8] = [
|
|
356
|
-
|
|
361
|
+
i("span", { style: { width: "6px", height: "6px", "border-radius": "50%", background: "#22c55e", display: "inline-block" } }, null, -1),
|
|
357
362
|
K(" Alarms ", -1)
|
|
358
363
|
])])) : S("", !0)
|
|
359
364
|
]),
|
|
360
365
|
a.value !== "idle" ? (d(), f("div", se, [
|
|
361
|
-
|
|
366
|
+
i("button", {
|
|
362
367
|
class: "rvms-btn",
|
|
363
368
|
title: "Stop stream",
|
|
364
369
|
onClick: y
|
|
365
370
|
}, " ⏹ Stop "),
|
|
366
|
-
|
|
371
|
+
i("button", {
|
|
367
372
|
class: "rvms-btn",
|
|
368
373
|
title: `Switch to ${C.value === "main" ? "sub" : "main"} stream`,
|
|
369
374
|
onClick: I
|
|
@@ -385,13 +390,13 @@ const X = {
|
|
|
385
390
|
style: { width: "32px", height: "24px", "border-radius": "2px", "object-fit": "cover", background: "#1e293b" },
|
|
386
391
|
loading: "lazy"
|
|
387
392
|
}, null, 8, de)) : S("", !0),
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
393
|
+
i("div", fe, [
|
|
394
|
+
i("div", pe, [
|
|
395
|
+
i("span", ve, B(r.type), 1),
|
|
391
396
|
r.channel ? (d(), f("span", me, " ch" + B(r.channel), 1)) : S("", !0)
|
|
392
397
|
]),
|
|
393
|
-
|
|
394
|
-
t[9] || (t[9] =
|
|
398
|
+
i("div", ge, B(new Date(r.timestamp).toLocaleString()), 1),
|
|
399
|
+
t[9] || (t[9] = i("div", { style: { color: "#60a5fa", "font-size": "9px", "margin-top": "1px" } }, " Click to view playback ", -1))
|
|
395
400
|
])
|
|
396
401
|
], 40, ce))), 128))
|
|
397
402
|
])) : S("", !0)
|
|
@@ -403,10 +408,10 @@ const X = {
|
|
|
403
408
|
}, he = { style: { color: "#e2e8f0", "font-size": "0.85rem" } }, be = {
|
|
404
409
|
key: 2,
|
|
405
410
|
style: { position: "absolute", inset: "0", display: "flex", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "12px", background: "rgba(0, 0, 0, 0.5)", "z-index": "10" }
|
|
406
|
-
}, xe = { style: { color: "#ef4444", "font-size": "0.85rem", "text-align": "center", padding: "0 16px" } },
|
|
411
|
+
}, xe = { style: { color: "#ef4444", "font-size": "0.85rem", "text-align": "center", padding: "0 16px" } }, ke = {
|
|
407
412
|
key: 3,
|
|
408
413
|
style: { position: "absolute", inset: "0", display: "flex", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "12px", background: "rgba(0, 0, 0, 0.4)", "z-index": "10" }
|
|
409
|
-
},
|
|
414
|
+
}, Se = { style: { position: "absolute", top: "8px", left: "8px", right: "8px", display: "flex", gap: "6px", "align-items": "center", "flex-wrap": "wrap", "z-index": "11", "pointer-events": "none" } }, we = ["title"], Be = ["title"], $e = {
|
|
410
415
|
key: 0,
|
|
411
416
|
class: "rvms-chip",
|
|
412
417
|
style: { background: "rgba(51, 65, 85, 0.8)", color: "#cbd5e1", "font-family": "monospace" }
|
|
@@ -427,11 +432,12 @@ const X = {
|
|
|
427
432
|
channelId: {},
|
|
428
433
|
initialProfile: { default: "main" },
|
|
429
434
|
width: { default: "100%" },
|
|
430
|
-
height: { default: "auto" }
|
|
435
|
+
height: { default: "auto" },
|
|
436
|
+
token: {}
|
|
431
437
|
},
|
|
432
438
|
emits: ["status-change"],
|
|
433
|
-
setup($, { emit:
|
|
434
|
-
const
|
|
439
|
+
setup($, { emit: P }) {
|
|
440
|
+
const x = $, s = P, v = k(null), l = k("idle"), a = k(null), g = k(null), w = k(!1), h = k("live"), C = k(x.initialProfile), I = k("");
|
|
435
441
|
let E = null;
|
|
436
442
|
function L() {
|
|
437
443
|
return {
|
|
@@ -445,7 +451,7 @@ const X = {
|
|
|
445
451
|
sourceOpenHandler: null
|
|
446
452
|
};
|
|
447
453
|
}
|
|
448
|
-
function
|
|
454
|
+
function U() {
|
|
449
455
|
var o;
|
|
450
456
|
const n = E;
|
|
451
457
|
if (n) {
|
|
@@ -479,16 +485,16 @@ const X = {
|
|
|
479
485
|
function D(n) {
|
|
480
486
|
if (!v.value) return;
|
|
481
487
|
if (!("MediaSource" in window)) {
|
|
482
|
-
a.value = "MediaSource Extensions not supported", l.value = "error",
|
|
488
|
+
a.value = "MediaSource Extensions not supported", l.value = "error", s("status-change", "error");
|
|
483
489
|
return;
|
|
484
490
|
}
|
|
485
|
-
l.value = "connecting", a.value = null, g.value = null,
|
|
491
|
+
l.value = "connecting", a.value = null, g.value = null, s("status-change", "connecting");
|
|
486
492
|
const o = L();
|
|
487
493
|
E = o;
|
|
488
494
|
const m = new MediaSource();
|
|
489
495
|
o.mediaSource = m, v.value.src = URL.createObjectURL(m);
|
|
490
496
|
const y = () => {
|
|
491
|
-
o.cancelled || o.mediaSource !== m || (l.value = "buffering",
|
|
497
|
+
o.cancelled || o.mediaSource !== m || (l.value = "buffering", s("status-change", "buffering"), A(o));
|
|
492
498
|
};
|
|
493
499
|
o.sourceOpenHandler = y, m.addEventListener("sourceopen", y);
|
|
494
500
|
const c = new WebSocket(n);
|
|
@@ -497,7 +503,7 @@ const X = {
|
|
|
497
503
|
if (typeof e.data == "string") {
|
|
498
504
|
try {
|
|
499
505
|
const r = JSON.parse(e.data);
|
|
500
|
-
r.type === "error" && r.message && (a.value = r.message, l.value = "error",
|
|
506
|
+
r.type === "error" && r.message && (a.value = r.message, l.value = "error", s("status-change", "error"), U());
|
|
501
507
|
} catch {
|
|
502
508
|
}
|
|
503
509
|
return;
|
|
@@ -509,11 +515,11 @@ const X = {
|
|
|
509
515
|
}
|
|
510
516
|
o.appendQueue.push(
|
|
511
517
|
t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength)
|
|
512
|
-
),
|
|
518
|
+
), H(o);
|
|
513
519
|
}, c.onerror = () => {
|
|
514
|
-
o.cancelled || l.value !== "error" && (a.value = "WebSocket error", l.value = "error",
|
|
520
|
+
o.cancelled || l.value !== "error" && (a.value = "WebSocket error", l.value = "error", s("status-change", "error"));
|
|
515
521
|
}, c.onclose = (e) => {
|
|
516
|
-
o.cancelled || (l.value === "connecting" || l.value === "buffering" ? (a.value = e.reason || `Stream closed (code ${e.code})`, l.value = "error",
|
|
522
|
+
o.cancelled || (l.value === "connecting" || l.value === "buffering" ? (a.value = e.reason || `Stream closed (code ${e.code})`, l.value = "error", s("status-change", "error")) : l.value === "playing" && (l.value = "ended", s("status-change", "ended")));
|
|
517
523
|
};
|
|
518
524
|
}
|
|
519
525
|
function M(n) {
|
|
@@ -561,17 +567,17 @@ const X = {
|
|
|
561
567
|
}
|
|
562
568
|
n.preBuffer.length = 0;
|
|
563
569
|
const y = () => {
|
|
564
|
-
|
|
570
|
+
H(n);
|
|
565
571
|
};
|
|
566
572
|
n.updateEndHandler = y, n.sourceBuffer.addEventListener("updateend", y);
|
|
567
573
|
}
|
|
568
|
-
function
|
|
574
|
+
function H(n) {
|
|
569
575
|
var y;
|
|
570
576
|
const o = n.sourceBuffer;
|
|
571
577
|
if (!o || o.updating || n.appendQueue.length === 0) return;
|
|
572
578
|
const m = n.appendQueue.shift();
|
|
573
579
|
try {
|
|
574
|
-
o.appendBuffer(m), l.value !== "playing" && (l.value = "playing",
|
|
580
|
+
o.appendBuffer(m), l.value !== "playing" && (l.value = "playing", s("status-change", "playing"));
|
|
575
581
|
} catch (c) {
|
|
576
582
|
const e = c;
|
|
577
583
|
if (e.name === "QuotaExceededError") {
|
|
@@ -582,19 +588,19 @@ const X = {
|
|
|
582
588
|
}
|
|
583
589
|
n.appendQueue.unshift(m);
|
|
584
590
|
} else
|
|
585
|
-
a.value = `appendBuffer failed: ${e.message}`, l.value = "error",
|
|
591
|
+
a.value = `appendBuffer failed: ${e.message}`, l.value = "error", U();
|
|
586
592
|
}
|
|
587
593
|
}
|
|
588
594
|
async function O() {
|
|
589
|
-
if (h.value === "playback" && !I.value && (I.value = (/* @__PURE__ */ new Date()).toISOString()),
|
|
590
|
-
a.value = "nvrId and channelId are required", l.value = "error", w.value = !1,
|
|
595
|
+
if (h.value === "playback" && !I.value && (I.value = (/* @__PURE__ */ new Date()).toISOString()), U(), w.value = !0, !x.nvrId || !x.channelId) {
|
|
596
|
+
a.value = "nvrId and channelId are required", l.value = "error", w.value = !1, s("status-change", "error");
|
|
591
597
|
return;
|
|
592
598
|
}
|
|
593
|
-
l.value = "connecting", a.value = null, g.value = null,
|
|
599
|
+
l.value = "connecting", a.value = null, g.value = null, s("status-change", "connecting");
|
|
594
600
|
try {
|
|
595
601
|
const n = new URLSearchParams({
|
|
596
|
-
nvrId:
|
|
597
|
-
deviceId:
|
|
602
|
+
nvrId: x.nvrId,
|
|
603
|
+
deviceId: x.channelId,
|
|
598
604
|
mode: h.value,
|
|
599
605
|
profile: C.value
|
|
600
606
|
});
|
|
@@ -604,14 +610,16 @@ const X = {
|
|
|
604
610
|
const c = new Date(y).getTime();
|
|
605
611
|
n.set("from", new Date(c - 3e4).toISOString()), n.set("to", new Date(c + 3e4).toISOString());
|
|
606
612
|
}
|
|
607
|
-
const
|
|
613
|
+
const o = location.protocol === "https:" ? "wss:" : "ws:";
|
|
614
|
+
x.token && n.set("token", x.token);
|
|
615
|
+
const m = `${o}//${location.host}/ws/stream?${n.toString()}`;
|
|
608
616
|
D(m);
|
|
609
617
|
} catch (n) {
|
|
610
|
-
a.value = n.message, l.value = "error", w.value = !1,
|
|
618
|
+
a.value = n.message, l.value = "error", w.value = !1, s("status-change", "error");
|
|
611
619
|
}
|
|
612
620
|
}
|
|
613
621
|
function z() {
|
|
614
|
-
w.value = !1,
|
|
622
|
+
w.value = !1, U(), l.value = "idle", a.value = null, s("status-change", "idle");
|
|
615
623
|
}
|
|
616
624
|
function W() {
|
|
617
625
|
h.value = h.value === "live" ? "playback" : "live", w.value && O();
|
|
@@ -626,9 +634,9 @@ const X = {
|
|
|
626
634
|
return F(() => {
|
|
627
635
|
z();
|
|
628
636
|
}), J(
|
|
629
|
-
() => [
|
|
637
|
+
() => [x.nvrId, x.channelId].join("|"),
|
|
630
638
|
() => {
|
|
631
|
-
w.value &&
|
|
639
|
+
w.value && x.nvrId && x.channelId && O();
|
|
632
640
|
}
|
|
633
641
|
), (n, o) => (d(), f("div", {
|
|
634
642
|
class: "rvms-video",
|
|
@@ -641,7 +649,7 @@ const X = {
|
|
|
641
649
|
borderRadius: "8px"
|
|
642
650
|
})
|
|
643
651
|
}, [
|
|
644
|
-
|
|
652
|
+
i("video", {
|
|
645
653
|
ref_key: "videoEl",
|
|
646
654
|
ref: v,
|
|
647
655
|
muted: "",
|
|
@@ -653,13 +661,13 @@ const X = {
|
|
|
653
661
|
style: { position: "absolute", inset: "0", display: "flex", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "12px", background: "rgba(0, 0, 0, 0.5)", cursor: "pointer", "z-index": "10" },
|
|
654
662
|
onClick: O
|
|
655
663
|
}, [...o[0] || (o[0] = [
|
|
656
|
-
|
|
664
|
+
i("svg", {
|
|
657
665
|
width: "64",
|
|
658
666
|
height: "64",
|
|
659
667
|
viewBox: "0 0 64 64",
|
|
660
668
|
fill: "none"
|
|
661
669
|
}, [
|
|
662
|
-
|
|
670
|
+
i("circle", {
|
|
663
671
|
cx: "32",
|
|
664
672
|
cy: "32",
|
|
665
673
|
r: "30",
|
|
@@ -667,30 +675,30 @@ const X = {
|
|
|
667
675
|
"stroke-width": "3",
|
|
668
676
|
fill: "rgba(255,255,255,0.15)"
|
|
669
677
|
}),
|
|
670
|
-
|
|
678
|
+
i("polygon", {
|
|
671
679
|
points: "26,20 26,44 46,32",
|
|
672
680
|
fill: "rgba(255,255,255,0.9)"
|
|
673
681
|
})
|
|
674
682
|
], -1),
|
|
675
|
-
|
|
683
|
+
i("span", { style: { color: "#e2e8f0", "font-size": "0.9rem", "font-weight": "600" } }, "Click to play", -1)
|
|
676
684
|
])])) : l.value === "connecting" || l.value === "buffering" ? (d(), f("div", ye, [
|
|
677
|
-
o[1] || (o[1] =
|
|
678
|
-
|
|
685
|
+
o[1] || (o[1] = i("div", { class: "rvms-spinner" }, null, -1)),
|
|
686
|
+
i("span", he, B(l.value === "connecting" ? "Connecting…" : "Buffering…"), 1)
|
|
679
687
|
])) : l.value === "error" ? (d(), f("div", be, [
|
|
680
|
-
|
|
681
|
-
|
|
688
|
+
i("span", xe, B(a.value || "Stream error"), 1),
|
|
689
|
+
i("button", {
|
|
682
690
|
class: "rvms-btn",
|
|
683
691
|
onClick: O
|
|
684
692
|
}, " Retry ")
|
|
685
|
-
])) : l.value === "ended" ? (d(), f("div",
|
|
686
|
-
o[2] || (o[2] =
|
|
687
|
-
|
|
693
|
+
])) : l.value === "ended" ? (d(), f("div", ke, [
|
|
694
|
+
o[2] || (o[2] = i("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
|
|
695
|
+
i("button", {
|
|
688
696
|
class: "rvms-btn",
|
|
689
697
|
onClick: O
|
|
690
698
|
}, "Reconnect")
|
|
691
699
|
])) : S("", !0),
|
|
692
|
-
|
|
693
|
-
|
|
700
|
+
i("div", Se, [
|
|
701
|
+
i("button", {
|
|
694
702
|
class: "rvms-chip",
|
|
695
703
|
title: `Switch to ${h.value === "live" ? "playback" : "live"} mode`,
|
|
696
704
|
onClick: W,
|
|
@@ -699,7 +707,7 @@ const X = {
|
|
|
699
707
|
pointerEvents: "auto"
|
|
700
708
|
})
|
|
701
709
|
}, B(h.value === "live" ? "🔴 LIVE" : "🔵 PLAYBACK"), 13, we),
|
|
702
|
-
|
|
710
|
+
i("button", {
|
|
703
711
|
class: "rvms-chip",
|
|
704
712
|
title: `Switch to ${C.value === "main" ? "sub" : "main"} stream`,
|
|
705
713
|
onClick: V,
|
|
@@ -718,7 +726,7 @@ const X = {
|
|
|
718
726
|
h.value === "playback" && I.value ? (d(), f("span", Ce, B(new Date(I.value).toLocaleString()), 1)) : S("", !0)
|
|
719
727
|
]),
|
|
720
728
|
l.value !== "idle" ? (d(), f("div", Ie, [
|
|
721
|
-
|
|
729
|
+
i("span", {
|
|
722
730
|
style: j({
|
|
723
731
|
padding: "2px 8px",
|
|
724
732
|
borderRadius: "4px",
|
|
@@ -745,13 +753,13 @@ const X = {
|
|
|
745
753
|
])) : S("", !0)
|
|
746
754
|
], 4));
|
|
747
755
|
}
|
|
748
|
-
}), Oe = ($,
|
|
749
|
-
const
|
|
750
|
-
for (const [
|
|
751
|
-
|
|
752
|
-
return
|
|
753
|
-
},
|
|
756
|
+
}), Oe = ($, P) => {
|
|
757
|
+
const x = $.__vccOpts || $;
|
|
758
|
+
for (const [s, v] of P)
|
|
759
|
+
x[s] = v;
|
|
760
|
+
return x;
|
|
761
|
+
}, Ue = /* @__PURE__ */ Oe(Le, [["__scopeId", "data-v-bfc7094e"]]);
|
|
754
762
|
export {
|
|
755
|
-
|
|
763
|
+
Ue as RvmsVideo,
|
|
756
764
|
Re as RvmsVideoPlayer
|
|
757
765
|
};
|
package/dist/lib/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@keyframes rvms-pulse{0%,to{opacity:1}50%{opacity:.4}}.rvms-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#00000080;font-size:14px}.rvms-btn{padding:4px 12px;border:1px solid rgba(255,255,255,.2);border-radius:4px;background:#1e293bcc;color:#e2e8f0;font-size:11px;font-weight:600;cursor:pointer;pointer-events:auto;transition:background .15s}.rvms-btn:hover{background:#334155e6}@keyframes rvms-spin-
|
|
1
|
+
@keyframes rvms-pulse{0%,to{opacity:1}50%{opacity:.4}}.rvms-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#00000080;font-size:14px}.rvms-btn{padding:4px 12px;border:1px solid rgba(255,255,255,.2);border-radius:4px;background:#1e293bcc;color:#e2e8f0;font-size:11px;font-weight:600;cursor:pointer;pointer-events:auto;transition:background .15s}.rvms-btn:hover{background:#334155e6}@keyframes rvms-spin-bfc7094e{to{transform:rotate(360deg)}}.rvms-spinner[data-v-bfc7094e]{width:32px;height:32px;border:3px solid rgba(255,255,255,.2);border-top-color:#60a5fa;border-radius:50%;animation:rvms-spin-bfc7094e .8s linear infinite}.rvms-btn[data-v-bfc7094e]{padding:6px 14px;border:none;border-radius:6px;background:#0f172ad9;color:#e2e8f0;font-size:.8rem;font-weight:700;cursor:pointer;transition:background .15s;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.rvms-btn[data-v-bfc7094e]:hover{background:#1e293bf2}.rvms-chip[data-v-bfc7094e]{padding:2px 8px;border:none;border-radius:4px;font-size:10px;font-weight:700;color:#fff;cursor:pointer;transition:opacity .15s;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);text-transform:uppercase;letter-spacing:.05em}.rvms-chip[data-v-bfc7094e]:hover{opacity:.85}.rvms-datetime[data-v-bfc7094e]{padding:2px 6px;border:1px solid rgba(255,255,255,.2);border-radius:4px;background:#0f172ad9;color:#e2e8f0;font-size:11px;cursor:pointer;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.rvms-datetime[data-v-bfc7094e]:focus{outline:none;border-color:#60a5fa}
|
package/package.json
CHANGED
|
@@ -65,6 +65,8 @@ interface Props {
|
|
|
65
65
|
initialProfile?: StreamProfile;
|
|
66
66
|
width?: string;
|
|
67
67
|
height?: string;
|
|
68
|
+
/** Optional auth token passed as Bearer header to REST and ?token= to WS. */
|
|
69
|
+
token?: string;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -382,6 +384,7 @@ async function start(): Promise<void> {
|
|
|
382
384
|
|
|
383
385
|
// Build proxied WebSocket URL (same origin — goes through Vite/nginx proxy to example backend)
|
|
384
386
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
387
|
+
if (props.token) params.set('token', props.token);
|
|
385
388
|
const wsUrl = `${proto}//${location.host}/ws/stream?${params.toString()}`;
|
|
386
389
|
|
|
387
390
|
connectStream(wsUrl);
|
|
@@ -77,6 +77,8 @@ interface Props {
|
|
|
77
77
|
showAlarms?: boolean;
|
|
78
78
|
width?: string;
|
|
79
79
|
height?: string;
|
|
80
|
+
/** Optional auth token passed as Bearer header to REST and ?token= to WS. */
|
|
81
|
+
token?: string;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -134,7 +136,7 @@ function beWsUrl(path: string): string {
|
|
|
134
136
|
function connectAlarmWs(): void {
|
|
135
137
|
disconnectAlarmWs();
|
|
136
138
|
|
|
137
|
-
const ws = new WebSocket(beWsUrl('/ws/alarms'));
|
|
139
|
+
const ws = new WebSocket(props.token ? `/ws/alarms?token=${encodeURIComponent(props.token)}` : beWsUrl('/ws/alarms'));
|
|
138
140
|
alarmWsRef.value = ws;
|
|
139
141
|
|
|
140
142
|
ws.onmessage = (event) => {
|
|
@@ -198,7 +200,9 @@ function disconnectAlarmWs(): void {
|
|
|
198
200
|
/** Fetch recent alarms via REST as fallback / initial data. */
|
|
199
201
|
async function fetchRecentAlarms(): Promise<void> {
|
|
200
202
|
try {
|
|
201
|
-
const
|
|
203
|
+
const headers: Record<string, string> = {};
|
|
204
|
+
if (props.token) headers.Authorization = `Bearer ${props.token}`;
|
|
205
|
+
const res = await fetch('/api/alarms/recent', { headers });
|
|
202
206
|
if (res.ok) {
|
|
203
207
|
const alarms = (await res.json()) as AlarmEvent[];
|
|
204
208
|
recentAlarms.value = alarms;
|
|
@@ -512,6 +516,7 @@ async function start(): Promise<void> {
|
|
|
512
516
|
if (props.to) params.set('to', props.to);
|
|
513
517
|
|
|
514
518
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
519
|
+
if (props.token) params.set('token', props.token);
|
|
515
520
|
const wsUrl = `${proto}//${location.host}/ws/stream?${params.toString()}`;
|
|
516
521
|
|
|
517
522
|
// ── Step 2: Connect to stream WebSocket via BE proxy ──────────────────
|