rvms-vue 0.1.12 → 0.1.13

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