rvms-vue 0.1.12 → 0.1.14

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