rvms-vue 0.1.4

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.
@@ -0,0 +1,757 @@
1
+ import { defineComponent as N, ref as x, onMounted as q, onBeforeUnmount as F, watch as J, openBlock as d, createElementBlock as f, normalizeStyle as j, createElementVNode as s, toDisplayString as B, createCommentVNode as S, createTextVNode as K, Fragment as Y, renderList as G } from "vue";
2
+ const X = {
3
+ width: "64",
4
+ height: "64",
5
+ viewBox: "0 0 64 64",
6
+ fill: "none",
7
+ style: { opacity: "0.8" }
8
+ }, Z = {
9
+ key: 1,
10
+ class: "rvms-overlay"
11
+ }, ee = {
12
+ key: 2,
13
+ class: "rvms-overlay"
14
+ }, te = {
15
+ key: 3,
16
+ class: "rvms-overlay"
17
+ }, ne = {
18
+ key: 0,
19
+ style: { color: "#fca5a5", "font-size": "0.8em", "text-align": "center", padding: "0 1em", "max-width": "80%" }
20
+ }, re = {
21
+ key: 4,
22
+ class: "rvms-overlay"
23
+ }, oe = { style: { position: "absolute", top: "8px", left: "8px", display: "flex", gap: "6px", "align-items": "center", "pointer-events": "none" } }, ae = {
24
+ key: 1,
25
+ style: { padding: "2px 6px", "border-radius": "4px", "font-size": "10px", "font-family": "monospace", background: "rgba(51, 65, 85, 0.8)", color: "#cbd5e1" }
26
+ }, le = {
27
+ key: 2,
28
+ style: { display: "inline-flex", "align-items": "center", gap: "4px", padding: "2px 8px", "border-radius": "4px", "font-size": "10px", background: "#065f46", color: "#6ee7b7" }
29
+ }, se = {
30
+ key: 5,
31
+ style: { position: "absolute", bottom: "12px", right: "12px", display: "flex", gap: "6px" }
32
+ }, ie = ["title"], ue = {
33
+ key: 6,
34
+ style: { position: "absolute", bottom: "48px", left: "8px", right: "8px", "max-height": "140px", "overflow-y": "auto", display: "flex", "flex-direction": "column", gap: "4px" }
35
+ }, ce = ["title", "onClick"], de = ["src"], fe = { style: { flex: "1", "min-width": "0" } }, pe = { style: { display: "flex", gap: "6px", "align-items": "baseline" } }, ve = { style: { color: "#fbbf24", "font-weight": "600", "text-transform": "uppercase", "font-size": "10px" } }, me = {
36
+ key: 0,
37
+ style: { color: "#94a3b8" }
38
+ }, ge = { style: { color: "#94a3b8", "font-size": "10px", "white-space": "nowrap", overflow: "hidden", "text-overflow": "ellipsis" } }, Re = /* @__PURE__ */ N({
39
+ __name: "RvmsVideoPlayer",
40
+ props: {
41
+ nvrId: {},
42
+ deviceId: {},
43
+ initialProfile: { default: "main" },
44
+ mode: { default: "live" },
45
+ from: {},
46
+ to: {},
47
+ showAlarms: { type: Boolean, default: !0 },
48
+ width: { default: "100%" },
49
+ height: { default: "auto" }
50
+ },
51
+ emits: ["alarm", "stream-status", "alarm-click"],
52
+ setup($, { expose: U, emit: k }) {
53
+ const u = $, v = k, l = x(null), a = x("idle"), g = x(null), w = x(null), h = x(!1), C = x(u.initialProfile);
54
+ function I() {
55
+ C.value = C.value === "main" ? "sub" : "main", h.value && m();
56
+ }
57
+ const E = x(null), L = x([]);
58
+ function H(e) {
59
+ return `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}${e}`;
60
+ }
61
+ function D() {
62
+ M();
63
+ const e = new WebSocket(H("/ws/alarms"));
64
+ E.value = e, e.onmessage = (t) => {
65
+ try {
66
+ const r = JSON.parse(t.data);
67
+ switch (r.type) {
68
+ case "snapshot":
69
+ r.events && (L.value = r.events, r.events.forEach((p) => v("alarm", p)));
70
+ break;
71
+ case "alarm":
72
+ r.event && (L.value.unshift(r.event), L.value.length > 50 && (L.value.length = 50), v("alarm", r.event));
73
+ break;
74
+ default:
75
+ break;
76
+ }
77
+ } catch {
78
+ }
79
+ }, e.onclose = () => {
80
+ E.value = null, setTimeout(D, 3e3);
81
+ }, e.onerror = () => {
82
+ };
83
+ }
84
+ function M() {
85
+ if (E.value) {
86
+ try {
87
+ E.value.onclose = null, E.value.close();
88
+ } catch {
89
+ }
90
+ E.value = null;
91
+ }
92
+ }
93
+ async function A() {
94
+ try {
95
+ const e = await fetch("/api/alarms/recent");
96
+ if (e.ok) {
97
+ const t = await e.json();
98
+ L.value = t;
99
+ }
100
+ } catch {
101
+ }
102
+ }
103
+ let P = null;
104
+ function O() {
105
+ return {
106
+ cancelled: !1,
107
+ ws: null,
108
+ mediaSource: null,
109
+ sourceBuffer: null,
110
+ preBuffer: [],
111
+ appendQueue: [],
112
+ updateEndHandler: null,
113
+ sourceOpenHandler: null
114
+ };
115
+ }
116
+ function z() {
117
+ var t;
118
+ const e = P;
119
+ if (e) {
120
+ e.cancelled = !0;
121
+ try {
122
+ (t = e.ws) == null || t.close();
123
+ } catch {
124
+ }
125
+ if (e.ws = null, e.sourceBuffer && e.updateEndHandler)
126
+ try {
127
+ e.sourceBuffer.removeEventListener("updateend", e.updateEndHandler);
128
+ } catch {
129
+ }
130
+ if (e.mediaSource && e.sourceOpenHandler)
131
+ try {
132
+ e.mediaSource.removeEventListener("sourceopen", e.sourceOpenHandler);
133
+ } catch {
134
+ }
135
+ if (e.sourceBuffer && e.mediaSource && e.mediaSource.readyState === "open" && !e.sourceBuffer.updating)
136
+ try {
137
+ e.mediaSource.endOfStream();
138
+ } catch {
139
+ }
140
+ if (e.sourceBuffer = null, e.mediaSource = null, e.appendQueue.length = 0, e.preBuffer.length = 0, P = null, l.value)
141
+ try {
142
+ l.value.pause(), l.value.removeAttribute("src"), l.value.load();
143
+ } catch {
144
+ }
145
+ }
146
+ }
147
+ function W(e) {
148
+ if (!l.value) return;
149
+ if (!("MediaSource" in window)) {
150
+ g.value = "MediaSource Extensions not supported", a.value = "error", v("stream-status", "error");
151
+ return;
152
+ }
153
+ a.value = "connecting", g.value = null, w.value = null, v("stream-status", "connecting");
154
+ const t = O();
155
+ P = t;
156
+ const r = new MediaSource();
157
+ t.mediaSource = r, l.value.src = URL.createObjectURL(r);
158
+ const p = () => {
159
+ t.cancelled || t.mediaSource !== r || (a.value = "buffering", v("stream-status", "buffering"), T(t));
160
+ };
161
+ t.sourceOpenHandler = p, r.addEventListener("sourceopen", p);
162
+ const i = new WebSocket(e);
163
+ i.binaryType = "arraybuffer", t.ws = i, i.onmessage = (b) => {
164
+ if (t.cancelled) return;
165
+ if (typeof b.data == "string") {
166
+ try {
167
+ const R = JSON.parse(b.data);
168
+ R.type === "error" && R.message && (g.value = R.message, a.value = "error", v("stream-status", "error"), z());
169
+ } catch {
170
+ }
171
+ return;
172
+ }
173
+ const _ = new Uint8Array(b.data);
174
+ if (!t.sourceBuffer) {
175
+ t.preBuffer.push(_), T(t);
176
+ return;
177
+ }
178
+ t.appendQueue.push(
179
+ _.buffer.slice(_.byteOffset, _.byteOffset + _.byteLength)
180
+ ), o(t);
181
+ }, i.onerror = () => {
182
+ t.cancelled || a.value !== "error" && (g.value = "WebSocket error", a.value = "error", v("stream-status", "error"));
183
+ }, i.onclose = (b) => {
184
+ 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
+ };
186
+ }
187
+ function V(e) {
188
+ const t = (i) => i.toString(16).padStart(2, "0").toUpperCase();
189
+ let r = null, p = null;
190
+ for (let i = 0; i + 7 < e.length; i++) {
191
+ const b = String.fromCharCode(e[i], e[i + 1], e[i + 2], e[i + 3]);
192
+ if (b === "avcC") {
193
+ const _ = e[i + 5], R = e[i + 6], Q = e[i + 7];
194
+ _ !== void 0 && R !== void 0 && Q !== void 0 && (r = `avc1.${t(_)}${t(R)}${t(Q)}`);
195
+ }
196
+ b === "hvcC" && (r = "hev1.1.6.L93.B0"), b === "mp4a" && (p = "mp4a.40.2");
197
+ }
198
+ return r ? p ? `${r}, ${p}` : r : null;
199
+ }
200
+ function T(e) {
201
+ if (e.cancelled || e.sourceBuffer || !e.mediaSource || e.mediaSource.readyState !== "open") return;
202
+ const t = n(e.preBuffer), r = V(t);
203
+ if (!r) return;
204
+ w.value = r;
205
+ const p = `video/mp4; codecs="${r}"`;
206
+ if (!MediaSource.isTypeSupported(p)) {
207
+ g.value = `Codec not supported: ${p}`, a.value = "error", z();
208
+ return;
209
+ }
210
+ let i;
211
+ try {
212
+ i = e.mediaSource.addSourceBuffer(p);
213
+ } catch (_) {
214
+ g.value = `addSourceBuffer failed: ${_.message}`, a.value = "error", z();
215
+ return;
216
+ }
217
+ i.mode = "segments";
218
+ const b = () => {
219
+ e.cancelled || o(e);
220
+ };
221
+ e.updateEndHandler = b, i.addEventListener("updateend", b), i.addEventListener("error", () => {
222
+ e.cancelled || (g.value = "SourceBuffer error", a.value = "error");
223
+ }), e.sourceBuffer = i, e.appendQueue.unshift(t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength)), e.preBuffer.length = 0, o(e);
224
+ }
225
+ function n(e) {
226
+ const t = e.reduce((i, b) => i + b.byteLength, 0), r = new Uint8Array(t);
227
+ let p = 0;
228
+ for (const i of e)
229
+ r.set(i, p), p += i.byteLength;
230
+ return r;
231
+ }
232
+ function o(e) {
233
+ var i;
234
+ if (e.cancelled) return;
235
+ const t = e.sourceBuffer, r = e.mediaSource;
236
+ if (!t || !r || r.readyState !== "open" || t.updating) return;
237
+ const p = e.appendQueue.shift();
238
+ if (p)
239
+ try {
240
+ t.appendBuffer(p), a.value !== "playing" && (a.value = "playing", v("stream-status", "playing"));
241
+ } catch (b) {
242
+ const _ = b;
243
+ if (_.name === "QuotaExceededError") {
244
+ const R = ((i = l.value) == null ? void 0 : i.currentTime) ?? 0, Q = Math.max(0, R - 5);
245
+ try {
246
+ t.buffered.length > 0 && t.buffered.start(0) < Q && t.remove(t.buffered.start(0), Q);
247
+ } catch {
248
+ }
249
+ e.appendQueue.unshift(p);
250
+ } else
251
+ g.value = `appendBuffer failed: ${_.message}`, a.value = "error", z();
252
+ }
253
+ }
254
+ async function m() {
255
+ if (z(), h.value = !0, !u.nvrId || !u.deviceId) {
256
+ g.value = "nvrId and deviceId are required", a.value = "error", h.value = !1, v("stream-status", "error");
257
+ return;
258
+ }
259
+ a.value = "connecting", g.value = null, w.value = null, v("stream-status", "connecting");
260
+ try {
261
+ const e = new URLSearchParams({
262
+ nvrId: u.nvrId,
263
+ deviceId: u.deviceId,
264
+ mode: u.mode,
265
+ profile: C.value
266
+ });
267
+ u.from && e.set("from", u.from), u.to && e.set("to", u.to);
268
+ const r = `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws/stream?${e.toString()}`;
269
+ W(r), A();
270
+ } catch (e) {
271
+ g.value = e.message, a.value = "error", h.value = !1, v("stream-status", "error"), console.error("[RvmsVideoPlayer] start failed:", e.message);
272
+ }
273
+ }
274
+ function y() {
275
+ h.value = !1, z(), a.value = "idle", g.value = null, v("stream-status", "idle");
276
+ }
277
+ function c(e) {
278
+ return e || "";
279
+ }
280
+ return q(() => {
281
+ D(), A();
282
+ }), F(() => {
283
+ M(), y();
284
+ }), J(
285
+ () => [u.nvrId, u.deviceId, u.mode].join("|"),
286
+ () => {
287
+ h.value && u.nvrId && u.deviceId && m();
288
+ }
289
+ ), U({ start: m, stop: y }), (e, t) => (d(), f("div", {
290
+ class: "rvms-player-wrapper",
291
+ style: j({ width: $.width, height: $.height, position: "relative", background: "#000", overflow: "hidden", borderRadius: "8px" })
292
+ }, [
293
+ s("video", {
294
+ ref_key: "videoEl",
295
+ ref: l,
296
+ muted: "",
297
+ playsinline: "",
298
+ controls: "",
299
+ style: { width: "100%", height: "100%", "object-fit": "contain", display: "block" }
300
+ }, null, 512),
301
+ a.value === "idle" ? (d(), f("div", {
302
+ key: 0,
303
+ class: "rvms-overlay",
304
+ style: { cursor: "pointer" },
305
+ onClick: m
306
+ }, [
307
+ (d(), f("svg", X, [...t[2] || (t[2] = [
308
+ s("circle", {
309
+ cx: "32",
310
+ cy: "32",
311
+ r: "28",
312
+ stroke: "rgba(255,255,255,0.7)",
313
+ "stroke-width": "2",
314
+ fill: "rgba(0,0,0,0.3)"
315
+ }, null, -1),
316
+ s("polygon", {
317
+ points: "26,20 26,44 46,32",
318
+ fill: "rgba(255,255,255,0.85)"
319
+ }, null, -1)
320
+ ])])),
321
+ t[3] || (t[3] = s("span", { style: { color: "#94a3b8", "font-size": "0.85rem" } }, "Click to play", -1))
322
+ ])) : a.value === "connecting" ? (d(), f("div", Z, [...t[4] || (t[4] = [
323
+ s("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Connecting…", -1)
324
+ ])])) : a.value === "buffering" ? (d(), f("div", ee, [...t[5] || (t[5] = [
325
+ s("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Buffering…", -1)
326
+ ])])) : a.value === "error" ? (d(), f("div", te, [
327
+ t[6] || (t[6] = s("span", { style: { color: "#f87171", "font-weight": "600" } }, "Stream error", -1)),
328
+ g.value ? (d(), f("span", ne, B(g.value), 1)) : S("", !0),
329
+ s("button", {
330
+ class: "rvms-btn",
331
+ onClick: m
332
+ }, " Retry ")
333
+ ])) : a.value === "ended" ? (d(), f("div", re, [
334
+ t[7] || (t[7] = s("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
335
+ s("button", {
336
+ class: "rvms-btn",
337
+ onClick: m
338
+ }, " Reconnect ")
339
+ ])) : S("", !0),
340
+ s("div", oe, [
341
+ a.value !== "idle" ? (d(), f("span", {
342
+ key: 0,
343
+ style: j({
344
+ padding: "2px 8px",
345
+ borderRadius: "4px",
346
+ fontSize: "10px",
347
+ fontWeight: 700,
348
+ textTransform: "uppercase",
349
+ letterSpacing: "0.05em",
350
+ background: $.mode === "live" && a.value === "playing" ? "#dc2626" : "#334155",
351
+ color: "#fff"
352
+ })
353
+ }, B($.mode === "live" ? "LIVE" : "PLAYBACK"), 5)) : S("", !0),
354
+ w.value ? (d(), f("span", ae, B(w.value), 1)) : S("", !0),
355
+ E.value ? (d(), f("span", le, [...t[8] || (t[8] = [
356
+ s("span", { style: { width: "6px", height: "6px", "border-radius": "50%", background: "#22c55e", display: "inline-block" } }, null, -1),
357
+ K(" Alarms ", -1)
358
+ ])])) : S("", !0)
359
+ ]),
360
+ a.value !== "idle" ? (d(), f("div", se, [
361
+ s("button", {
362
+ class: "rvms-btn",
363
+ title: "Stop stream",
364
+ onClick: y
365
+ }, " ⏹ Stop "),
366
+ s("button", {
367
+ class: "rvms-btn",
368
+ title: `Switch to ${C.value === "main" ? "sub" : "main"} stream`,
369
+ onClick: I
370
+ }, B(C.value === "main" ? "HD" : "SD"), 9, ie)
371
+ ])) : S("", !0),
372
+ $.showAlarms && L.value.length > 0 ? (d(), f("div", ue, [
373
+ (d(!0), f(Y, null, G(L.value.slice(0, 5), (r) => (d(), f("div", {
374
+ key: r.id,
375
+ title: (r.description || r.type) + " — click to view playback",
376
+ style: { display: "flex", "align-items": "center", gap: "8px", padding: "4px 8px", "border-radius": "4px", background: "rgba(0, 0, 0, 0.65)", "font-size": "11px", "backdrop-filter": "blur(4px)", cursor: "pointer", transition: "background 0.15s" },
377
+ onClick: (p) => v("alarm-click", r),
378
+ onMouseenter: t[0] || (t[0] = (p) => p.target.style.background = "rgba(51, 65, 85, 0.8)"),
379
+ onMouseleave: t[1] || (t[1] = (p) => p.target.style.background = "rgba(0, 0, 0, 0.65)")
380
+ }, [
381
+ r.snapshotUrl ? (d(), f("img", {
382
+ key: 0,
383
+ src: c(r.snapshotUrl),
384
+ alt: "snapshot",
385
+ style: { width: "32px", height: "24px", "border-radius": "2px", "object-fit": "cover", background: "#1e293b" },
386
+ loading: "lazy"
387
+ }, null, 8, de)) : S("", !0),
388
+ s("div", fe, [
389
+ s("div", pe, [
390
+ s("span", ve, B(r.type), 1),
391
+ r.channel ? (d(), f("span", me, " ch" + B(r.channel), 1)) : S("", !0)
392
+ ]),
393
+ s("div", ge, B(new Date(r.timestamp).toLocaleString()), 1),
394
+ t[9] || (t[9] = s("div", { style: { color: "#60a5fa", "font-size": "9px", "margin-top": "1px" } }, " Click to view playback ", -1))
395
+ ])
396
+ ], 40, ce))), 128))
397
+ ])) : S("", !0)
398
+ ], 4));
399
+ }
400
+ }), ye = {
401
+ key: 1,
402
+ style: { position: "absolute", inset: "0", display: "flex", "flex-direction": "column", "align-items": "center", "justify-content": "center", gap: "8px", background: "rgba(0, 0, 0, 0.4)", "z-index": "10" }
403
+ }, he = { style: { color: "#e2e8f0", "font-size": "0.85rem" } }, be = {
404
+ key: 2,
405
+ 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" } }, Se = {
407
+ key: 3,
408
+ 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
+ }, ke = { 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
+ key: 0,
411
+ class: "rvms-chip",
412
+ style: { background: "rgba(51, 65, 85, 0.8)", color: "#cbd5e1", "font-family": "monospace" }
413
+ }, _e = ["value"], Ce = {
414
+ key: 2,
415
+ class: "rvms-chip",
416
+ style: { background: "rgba(15, 23, 42, 0.85)", color: "#94a3b8", cursor: "default", "pointer-events": "auto" }
417
+ }, Ie = {
418
+ key: 4,
419
+ style: { position: "absolute", bottom: "44px", left: "8px", display: "flex", gap: "6px", "align-items": "center", "z-index": "11", "pointer-events": "none" }
420
+ }, Ee = {
421
+ key: 5,
422
+ style: { position: "absolute", bottom: "8px", right: "8px", display: "flex", gap: "6px", "z-index": "11" }
423
+ }, Le = /* @__PURE__ */ N({
424
+ __name: "RvmsVideo",
425
+ props: {
426
+ nvrId: {},
427
+ channelId: {},
428
+ initialProfile: { default: "main" },
429
+ width: { default: "100%" },
430
+ height: { default: "auto" }
431
+ },
432
+ emits: ["status-change"],
433
+ setup($, { emit: U }) {
434
+ const k = $, u = U, v = x(null), l = x("idle"), a = x(null), g = x(null), w = x(!1), h = x("live"), C = x(k.initialProfile), I = x("");
435
+ let E = null;
436
+ function L() {
437
+ return {
438
+ cancelled: !1,
439
+ ws: null,
440
+ mediaSource: null,
441
+ sourceBuffer: null,
442
+ preBuffer: [],
443
+ appendQueue: [],
444
+ updateEndHandler: null,
445
+ sourceOpenHandler: null
446
+ };
447
+ }
448
+ function H() {
449
+ var o;
450
+ const n = E;
451
+ if (n) {
452
+ n.cancelled = !0;
453
+ try {
454
+ (o = n.ws) == null || o.close();
455
+ } catch {
456
+ }
457
+ if (n.ws = null, n.sourceBuffer && n.updateEndHandler)
458
+ try {
459
+ n.sourceBuffer.removeEventListener("updateend", n.updateEndHandler);
460
+ } catch {
461
+ }
462
+ if (n.mediaSource && n.sourceOpenHandler)
463
+ try {
464
+ n.mediaSource.removeEventListener("sourceopen", n.sourceOpenHandler);
465
+ } catch {
466
+ }
467
+ if (n.sourceBuffer && n.mediaSource && n.mediaSource.readyState === "open" && !n.sourceBuffer.updating)
468
+ try {
469
+ n.mediaSource.endOfStream();
470
+ } catch {
471
+ }
472
+ if (n.sourceBuffer = null, n.mediaSource = null, n.appendQueue.length = 0, n.preBuffer.length = 0, E = null, v.value)
473
+ try {
474
+ v.value.pause(), v.value.removeAttribute("src"), v.value.load();
475
+ } catch {
476
+ }
477
+ }
478
+ }
479
+ function D(n) {
480
+ if (!v.value) return;
481
+ if (!("MediaSource" in window)) {
482
+ a.value = "MediaSource Extensions not supported", l.value = "error", u("status-change", "error");
483
+ return;
484
+ }
485
+ l.value = "connecting", a.value = null, g.value = null, u("status-change", "connecting");
486
+ const o = L();
487
+ E = o;
488
+ const m = new MediaSource();
489
+ o.mediaSource = m, v.value.src = URL.createObjectURL(m);
490
+ const y = () => {
491
+ o.cancelled || o.mediaSource !== m || (l.value = "buffering", u("status-change", "buffering"), A(o));
492
+ };
493
+ o.sourceOpenHandler = y, m.addEventListener("sourceopen", y);
494
+ const c = new WebSocket(n);
495
+ c.binaryType = "arraybuffer", o.ws = c, c.onmessage = (e) => {
496
+ if (o.cancelled) return;
497
+ if (typeof e.data == "string") {
498
+ try {
499
+ const r = JSON.parse(e.data);
500
+ r.type === "error" && r.message && (a.value = r.message, l.value = "error", u("status-change", "error"), H());
501
+ } catch {
502
+ }
503
+ return;
504
+ }
505
+ const t = new Uint8Array(e.data);
506
+ if (!o.sourceBuffer) {
507
+ o.preBuffer.push(t), A(o);
508
+ return;
509
+ }
510
+ o.appendQueue.push(
511
+ t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength)
512
+ ), P(o);
513
+ }, c.onerror = () => {
514
+ o.cancelled || l.value !== "error" && (a.value = "WebSocket error", l.value = "error", u("status-change", "error"));
515
+ }, c.onclose = (e) => {
516
+ o.cancelled || (l.value === "connecting" || l.value === "buffering" ? (a.value = e.reason || `Stream closed (code ${e.code})`, l.value = "error", u("status-change", "error")) : l.value === "playing" && (l.value = "ended", u("status-change", "ended")));
517
+ };
518
+ }
519
+ function M(n) {
520
+ const o = (c) => c.toString(16).padStart(2, "0").toUpperCase();
521
+ let m = null, y = null;
522
+ for (let c = 0; c + 7 < n.length; c++) {
523
+ const e = String.fromCharCode(n[c], n[c + 1], n[c + 2], n[c + 3]);
524
+ if (e === "avcC") {
525
+ const t = n[c + 5], r = n[c + 6], p = n[c + 7];
526
+ t !== void 0 && r !== void 0 && p !== void 0 && (m = `avc1.${o(t)}${o(r)}${o(p)}`);
527
+ }
528
+ e === "hvcC" && (m = "hev1.1.6.L93.B0"), e === "mp4a" && (y = "mp4a.40.2");
529
+ }
530
+ return m ? y ? `${m}, ${y}` : m : null;
531
+ }
532
+ function A(n) {
533
+ if (n.sourceBuffer || !n.mediaSource || n.mediaSource.readyState !== "open") return;
534
+ const o = ["avc1.64001F", "avc1.4D0028", "avc1.42001E", "hev1.1.6.L93.B0"];
535
+ let m = 'video/mp4; codecs="avc1.64001F"';
536
+ for (const c of n.preBuffer) {
537
+ const e = M(c);
538
+ if (e) {
539
+ g.value = e, m = `video/mp4; codecs="${e}"`;
540
+ break;
541
+ }
542
+ }
543
+ try {
544
+ n.sourceBuffer = n.mediaSource.addSourceBuffer(m);
545
+ } catch {
546
+ for (const c of o)
547
+ try {
548
+ n.sourceBuffer = n.mediaSource.addSourceBuffer(`video/mp4; codecs="${c}"`);
549
+ break;
550
+ } catch {
551
+ }
552
+ }
553
+ if (!n.sourceBuffer) {
554
+ a.value = "No compatible video codec", l.value = "error";
555
+ return;
556
+ }
557
+ for (const c of n.preBuffer)
558
+ try {
559
+ n.sourceBuffer.appendBuffer(c);
560
+ } catch {
561
+ }
562
+ n.preBuffer.length = 0;
563
+ const y = () => {
564
+ P(n);
565
+ };
566
+ n.updateEndHandler = y, n.sourceBuffer.addEventListener("updateend", y);
567
+ }
568
+ function P(n) {
569
+ var y;
570
+ const o = n.sourceBuffer;
571
+ if (!o || o.updating || n.appendQueue.length === 0) return;
572
+ const m = n.appendQueue.shift();
573
+ try {
574
+ o.appendBuffer(m), l.value !== "playing" && (l.value = "playing", u("status-change", "playing"));
575
+ } catch (c) {
576
+ const e = c;
577
+ if (e.name === "QuotaExceededError") {
578
+ const t = ((y = v.value) == null ? void 0 : y.currentTime) ?? 0, r = Math.max(0, t - 5);
579
+ try {
580
+ o.buffered.length > 0 && o.buffered.start(0) < r && o.remove(o.buffered.start(0), r);
581
+ } catch {
582
+ }
583
+ n.appendQueue.unshift(m);
584
+ } else
585
+ a.value = `appendBuffer failed: ${e.message}`, l.value = "error", H();
586
+ }
587
+ }
588
+ async function O() {
589
+ if (h.value === "playback" && !I.value && (I.value = (/* @__PURE__ */ new Date()).toISOString()), H(), w.value = !0, !k.nvrId || !k.channelId) {
590
+ a.value = "nvrId and channelId are required", l.value = "error", w.value = !1, u("status-change", "error");
591
+ return;
592
+ }
593
+ l.value = "connecting", a.value = null, g.value = null, u("status-change", "connecting");
594
+ try {
595
+ const n = new URLSearchParams({
596
+ nvrId: k.nvrId,
597
+ deviceId: k.channelId,
598
+ mode: h.value,
599
+ profile: C.value
600
+ });
601
+ if (h.value === "playback") {
602
+ let y = I.value;
603
+ y || (y = (/* @__PURE__ */ new Date()).toISOString(), I.value = y);
604
+ const c = new Date(y).getTime();
605
+ n.set("from", new Date(c - 3e4).toISOString()), n.set("to", new Date(c + 3e4).toISOString());
606
+ }
607
+ const m = `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws/stream?${n.toString()}`;
608
+ D(m);
609
+ } catch (n) {
610
+ a.value = n.message, l.value = "error", w.value = !1, u("status-change", "error");
611
+ }
612
+ }
613
+ function z() {
614
+ w.value = !1, H(), l.value = "idle", a.value = null, u("status-change", "idle");
615
+ }
616
+ function W() {
617
+ h.value = h.value === "live" ? "playback" : "live", w.value && O();
618
+ }
619
+ function V() {
620
+ C.value = C.value === "main" ? "sub" : "main", w.value && O();
621
+ }
622
+ function T(n) {
623
+ const o = n.target;
624
+ o.value && (I.value = new Date(o.value).toISOString());
625
+ }
626
+ return F(() => {
627
+ z();
628
+ }), J(
629
+ () => [k.nvrId, k.channelId].join("|"),
630
+ () => {
631
+ w.value && k.nvrId && k.channelId && O();
632
+ }
633
+ ), (n, o) => (d(), f("div", {
634
+ class: "rvms-video",
635
+ style: j({
636
+ width: $.width,
637
+ height: $.height,
638
+ position: "relative",
639
+ background: "#000",
640
+ overflow: "hidden",
641
+ borderRadius: "8px"
642
+ })
643
+ }, [
644
+ s("video", {
645
+ ref_key: "videoEl",
646
+ ref: v,
647
+ muted: "",
648
+ playsinline: "",
649
+ style: { width: "100%", height: "100%", display: "block", "object-fit": "contain" }
650
+ }, null, 512),
651
+ l.value === "idle" ? (d(), f("div", {
652
+ key: 0,
653
+ 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
+ onClick: O
655
+ }, [...o[0] || (o[0] = [
656
+ s("svg", {
657
+ width: "64",
658
+ height: "64",
659
+ viewBox: "0 0 64 64",
660
+ fill: "none"
661
+ }, [
662
+ s("circle", {
663
+ cx: "32",
664
+ cy: "32",
665
+ r: "30",
666
+ stroke: "rgba(255,255,255,0.8)",
667
+ "stroke-width": "3",
668
+ fill: "rgba(255,255,255,0.15)"
669
+ }),
670
+ s("polygon", {
671
+ points: "26,20 26,44 46,32",
672
+ fill: "rgba(255,255,255,0.9)"
673
+ })
674
+ ], -1),
675
+ s("span", { style: { color: "#e2e8f0", "font-size": "0.9rem", "font-weight": "600" } }, "Click to play", -1)
676
+ ])])) : l.value === "connecting" || l.value === "buffering" ? (d(), f("div", ye, [
677
+ o[1] || (o[1] = s("div", { class: "rvms-spinner" }, null, -1)),
678
+ s("span", he, B(l.value === "connecting" ? "Connecting…" : "Buffering…"), 1)
679
+ ])) : l.value === "error" ? (d(), f("div", be, [
680
+ s("span", xe, B(a.value || "Stream error"), 1),
681
+ s("button", {
682
+ class: "rvms-btn",
683
+ onClick: O
684
+ }, " Retry ")
685
+ ])) : l.value === "ended" ? (d(), f("div", Se, [
686
+ o[2] || (o[2] = s("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
687
+ s("button", {
688
+ class: "rvms-btn",
689
+ onClick: O
690
+ }, "Reconnect")
691
+ ])) : S("", !0),
692
+ s("div", ke, [
693
+ s("button", {
694
+ class: "rvms-chip",
695
+ title: `Switch to ${h.value === "live" ? "playback" : "live"} mode`,
696
+ onClick: W,
697
+ style: j({
698
+ background: h.value === "live" ? "#dc2626" : "#2563eb",
699
+ pointerEvents: "auto"
700
+ })
701
+ }, B(h.value === "live" ? "🔴 LIVE" : "🔵 PLAYBACK"), 13, we),
702
+ s("button", {
703
+ class: "rvms-chip",
704
+ title: `Switch to ${C.value === "main" ? "sub" : "main"} stream`,
705
+ onClick: V,
706
+ style: { pointerEvents: "auto" }
707
+ }, B(C.value === "main" ? "HD" : "SD"), 9, Be),
708
+ g.value ? (d(), f("span", $e, B(g.value), 1)) : S("", !0),
709
+ h.value === "playback" ? (d(), f("input", {
710
+ key: 1,
711
+ type: "datetime-local",
712
+ value: I.value ? I.value.slice(0, 16) : "",
713
+ onInput: T,
714
+ class: "rvms-datetime",
715
+ title: "Playback timestamp",
716
+ style: { "pointer-events": "auto" }
717
+ }, null, 40, _e)) : S("", !0),
718
+ h.value === "playback" && I.value ? (d(), f("span", Ce, B(new Date(I.value).toLocaleString()), 1)) : S("", !0)
719
+ ]),
720
+ l.value !== "idle" ? (d(), f("div", Ie, [
721
+ s("span", {
722
+ style: j({
723
+ padding: "2px 8px",
724
+ borderRadius: "4px",
725
+ fontSize: "10px",
726
+ fontWeight: 700,
727
+ textTransform: "uppercase",
728
+ background: l.value === "playing" ? "rgba(34, 197, 94, 0.8)" : "rgba(100, 116, 139, 0.6)",
729
+ color: l.value === "playing" ? "#052e16" : "#e2e8f0"
730
+ })
731
+ }, B(l.value), 5)
732
+ ])) : S("", !0),
733
+ l.value !== "idle" ? (d(), f("div", Ee, [
734
+ w.value ? (d(), f("button", {
735
+ key: 0,
736
+ class: "rvms-btn",
737
+ title: "Stop stream",
738
+ onClick: z
739
+ }, " ⏹ Stop ")) : (d(), f("button", {
740
+ key: 1,
741
+ class: "rvms-btn",
742
+ title: "Start stream",
743
+ onClick: O
744
+ }, " ▶ Play "))
745
+ ])) : S("", !0)
746
+ ], 4));
747
+ }
748
+ }), Oe = ($, U) => {
749
+ const k = $.__vccOpts || $;
750
+ for (const [u, v] of U)
751
+ k[u] = v;
752
+ return k;
753
+ }, He = /* @__PURE__ */ Oe(Le, [["__scopeId", "data-v-e485aa9d"]]);
754
+ export {
755
+ He as RvmsVideo,
756
+ Re as RvmsVideoPlayer
757
+ };
@@ -0,0 +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-e485aa9d{to{transform:rotate(360deg)}}.rvms-spinner[data-v-e485aa9d]{width:32px;height:32px;border:3px solid rgba(255,255,255,.2);border-top-color:#60a5fa;border-radius:50%;animation:rvms-spin-e485aa9d .8s linear infinite}.rvms-btn[data-v-e485aa9d]{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-e485aa9d]:hover{background:#1e293bf2}.rvms-chip[data-v-e485aa9d]{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-e485aa9d]:hover{opacity:.85}.rvms-datetime[data-v-e485aa9d]{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-e485aa9d]:focus{outline:none;border-color:#60a5fa}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "rvms-vue",
3
+ "version": "0.1.4",
4
+ "type": "module",
5
+ "description": "RVMS (Video-MS) Vue 3 components — RvmsVideoPlayer, RvmsVideo",
6
+ "main": "./dist/lib/index.js",
7
+ "types": "./dist/lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/lib/index.js",
11
+ "types": "./dist/lib/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/lib"
16
+ ],
17
+ "scripts": {
18
+ "dev": "vite",
19
+ "build": "vite build",
20
+ "build:lib": "vite build --config vite.lib.config.ts",
21
+ "preview": "vite preview",
22
+ "prepublishOnly": "npm run build:lib"
23
+ },
24
+ "dependencies": {
25
+ "vue": "^3.5.12"
26
+ },
27
+ "devDependencies": {
28
+ "@vitejs/plugin-vue": "^5.1.4",
29
+ "typescript": "^5.6.3",
30
+ "vite": "^5.4.8",
31
+ "vue-tsc": "^2.1.6"
32
+ },
33
+ "peerDependencies": {
34
+ "vue": "^3.x"
35
+ },
36
+ "license": "MIT"
37
+ }