rvms-vue 0.1.8 → 0.1.10

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
@@ -53,7 +53,7 @@ function playRecording() {
53
53
  </template>
54
54
  ```
55
55
 
56
- ## Simple player (no alarms)
56
+ ## Simple player live
57
57
 
58
58
  ```vue
59
59
  <script setup>
@@ -70,8 +70,42 @@ import { RvmsVideo } from 'rvms-vue';
70
70
  </template>
71
71
  ```
72
72
 
73
+ ## Simple player — playback
74
+
75
+ ```vue
76
+ <script setup>
77
+ import { ref } from 'vue';
78
+ import { RvmsVideo } from 'rvms-vue';
79
+
80
+ const mode = ref('playback');
81
+ const from = '2025-01-15T10:00:00Z';
82
+ const to = '2025-01-15T11:00:00Z';
83
+ </script>
84
+
85
+ <template>
86
+ <RvmsVideo
87
+ nvr-id="a1b2c3d4-..."
88
+ channel-id="201"
89
+ :mode="mode"
90
+ :from="from"
91
+ :to="to"
92
+ width="100%"
93
+ height="480px"
94
+ />
95
+ </template>
96
+ ```
97
+
73
98
  ## Components
74
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
+
75
109
  ### `RvmsVideoPlayer`
76
110
 
77
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 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";
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: 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);
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 = x(null), L = x([]);
58
- function H(e) {
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(H("/ws/alarms"));
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 = await fetch("/api/alarms/recent");
96
- if (e.ok) {
97
- const t = await e.json();
98
- L.value = t;
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 P = null;
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 = P;
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, P = null, l.value)
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
- P = t;
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 i = new WebSocket(e);
163
- i.binaryType = "arraybuffer", t.ws = i, i.onmessage = (b) => {
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
- }, i.onerror = () => {
184
+ }, u.onerror = () => {
182
185
  t.cancelled || a.value !== "error" && (g.value = "WebSocket error", a.value = "error", v("stream-status", "error"));
183
- }, i.onclose = (b) => {
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 = (i) => i.toString(16).padStart(2, "0").toUpperCase();
191
+ const t = (u) => u.toString(16).padStart(2, "0").toUpperCase();
189
192
  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]);
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[i + 5], R = e[i + 6], Q = e[i + 7];
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 i;
213
+ let u;
211
214
  try {
212
- i = e.mediaSource.addSourceBuffer(p);
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
- i.mode = "segments";
220
+ u.mode = "segments";
218
221
  const b = () => {
219
222
  e.cancelled || o(e);
220
223
  };
221
- e.updateEndHandler = b, i.addEventListener("updateend", b), i.addEventListener("error", () => {
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 = i, e.appendQueue.unshift(t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength)), e.preBuffer.length = 0, o(e);
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((i, b) => i + b.byteLength, 0), r = new Uint8Array(t);
229
+ const t = e.reduce((u, b) => u + b.byteLength, 0), r = new Uint8Array(t);
227
230
  let p = 0;
228
- for (const i of e)
229
- r.set(i, p), p += i.byteLength;
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 i;
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 = ((i = l.value) == null ? void 0 : i.currentTime) ?? 0, Q = Math.max(0, R - 5);
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, !u.nvrId || !u.deviceId) {
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: u.nvrId,
263
- deviceId: u.deviceId,
264
- mode: u.mode,
265
+ nvrId: s.nvrId,
266
+ deviceId: s.deviceId,
267
+ mode: s.mode,
265
268
  profile: C.value
266
269
  });
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()}`;
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
- () => [u.nvrId, u.deviceId, u.mode].join("|"),
290
+ () => [s.nvrId, s.deviceId, s.mode].join("|"),
286
291
  () => {
287
- h.value && u.nvrId && u.deviceId && m();
292
+ h.value && s.nvrId && s.deviceId && m();
288
293
  }
289
- ), U({ start: m, stop: y }), (e, t) => (d(), f("div", {
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
- s("video", {
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
- s("circle", {
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
- s("polygon", {
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] = s("span", { style: { color: "#94a3b8", "font-size": "0.85rem" } }, "Click to play", -1))
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
- s("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Connecting…", -1)
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
- s("span", { style: { color: "#93c5fd", animation: "rvms-pulse 1.5s infinite" } }, "Buffering…", -1)
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] = s("span", { style: { color: "#f87171", "font-weight": "600" } }, "Stream error", -1)),
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
- s("button", {
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] = s("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
335
- s("button", {
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
- s("div", oe, [
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
- s("span", { style: { width: "6px", height: "6px", "border-radius": "50%", background: "#22c55e", display: "inline-block" } }, null, -1),
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
- s("button", {
366
+ i("button", {
362
367
  class: "rvms-btn",
363
368
  title: "Stop stream",
364
369
  onClick: y
365
370
  }, " ⏹ Stop "),
366
- s("button", {
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
- s("div", fe, [
389
- s("div", pe, [
390
- s("span", ve, B(r.type), 1),
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
- 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))
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" } }, Se = {
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
- }, 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 = {
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: 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("");
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 H() {
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", u("status-change", "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, u("status-change", "connecting");
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", u("status-change", "buffering"), A(o));
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", u("status-change", "error"), H());
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
- ), P(o);
518
+ ), H(o);
513
519
  }, c.onerror = () => {
514
- o.cancelled || l.value !== "error" && (a.value = "WebSocket error", l.value = "error", u("status-change", "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", u("status-change", "error")) : l.value === "playing" && (l.value = "ended", u("status-change", "ended")));
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
- P(n);
570
+ H(n);
565
571
  };
566
572
  n.updateEndHandler = y, n.sourceBuffer.addEventListener("updateend", y);
567
573
  }
568
- function P(n) {
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", u("status-change", "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", H();
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()), 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");
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, u("status-change", "connecting");
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: k.nvrId,
597
- deviceId: k.channelId,
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 m = `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws/stream?${n.toString()}`;
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, u("status-change", "error");
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, H(), l.value = "idle", a.value = null, u("status-change", "idle");
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
- () => [k.nvrId, k.channelId].join("|"),
637
+ () => [x.nvrId, x.channelId].join("|"),
630
638
  () => {
631
- w.value && k.nvrId && k.channelId && O();
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
- s("video", {
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
- s("svg", {
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
- s("circle", {
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
- s("polygon", {
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
- s("span", { style: { color: "#e2e8f0", "font-size": "0.9rem", "font-weight": "600" } }, "Click to play", -1)
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] = s("div", { class: "rvms-spinner" }, null, -1)),
678
- s("span", he, B(l.value === "connecting" ? "Connecting…" : "Buffering…"), 1)
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
- s("span", xe, B(a.value || "Stream error"), 1),
681
- s("button", {
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", Se, [
686
- o[2] || (o[2] = s("span", { style: { color: "#94a3b8" } }, "Stream ended", -1)),
687
- s("button", {
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
- s("div", ke, [
693
- s("button", {
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
- s("button", {
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
- s("span", {
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 = ($, 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"]]);
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
- He as RvmsVideo,
763
+ Ue as RvmsVideo,
756
764
  Re as RvmsVideoPlayer
757
765
  };
@@ -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-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}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rvms-vue",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "RVMS (Video-MS) Vue 3 components — RvmsVideoPlayer, RvmsVideo",
6
6
  "main": "./dist/lib/index.js",
@@ -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 res = await fetch('/api/alarms/recent');
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 ──────────────────