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 +294 -281
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/RvmsVideo.vue +27 -12
- package/src/components/RvmsVideoPlayer.vue +29 -14
package/dist/lib/index.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
const
|
|
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
|
-
},
|
|
8
|
+
}, te = {
|
|
9
9
|
key: 1,
|
|
10
10
|
class: "rvms-overlay"
|
|
11
|
-
},
|
|
11
|
+
}, ne = {
|
|
12
12
|
key: 2,
|
|
13
13
|
class: "rvms-overlay"
|
|
14
|
-
},
|
|
14
|
+
}, re = {
|
|
15
15
|
key: 3,
|
|
16
16
|
class: "rvms-overlay"
|
|
17
|
-
},
|
|
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
|
-
},
|
|
20
|
+
}, ae = {
|
|
21
21
|
key: 4,
|
|
22
22
|
class: "rvms-overlay"
|
|
23
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
29
|
+
}, ue = {
|
|
30
30
|
key: 5,
|
|
31
31
|
style: { position: "absolute", bottom: "12px", right: "12px", display: "flex", gap: "6px" }
|
|
32
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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(
|
|
54
|
-
const s =
|
|
55
|
-
function
|
|
56
|
-
|
|
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
|
|
59
|
-
function
|
|
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
|
|
63
|
-
|
|
64
|
-
const e = new WebSocket(s.token ? `/ws/alarms?token=${encodeURIComponent(s.token)}` :
|
|
65
|
-
|
|
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 && (
|
|
70
|
+
r.events && (z.value = r.events, r.events.forEach((p) => f("alarm", p)));
|
|
71
71
|
break;
|
|
72
72
|
case "alarm":
|
|
73
|
-
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
|
-
|
|
81
|
+
L.value = null, setTimeout(P, 3e3);
|
|
82
82
|
}, e.onerror = () => {
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
function
|
|
86
|
-
if (
|
|
85
|
+
function M() {
|
|
86
|
+
if (L.value) {
|
|
87
87
|
try {
|
|
88
|
-
|
|
88
|
+
L.value.onclose = null, L.value.close();
|
|
89
89
|
} catch {
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
L.value = null;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
async function
|
|
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
|
-
|
|
101
|
+
z.value = r;
|
|
102
102
|
}
|
|
103
103
|
} catch {
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
let
|
|
107
|
-
function
|
|
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
|
|
119
|
+
function $() {
|
|
120
120
|
var t;
|
|
121
|
-
const e =
|
|
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,
|
|
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
|
|
150
|
+
function j(e) {
|
|
151
151
|
if (!l.value) return;
|
|
152
152
|
if (!("MediaSource" in window)) {
|
|
153
|
-
|
|
153
|
+
m.value = "MediaSource Extensions not supported", a.value = "error", f("stream-status", "error");
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
-
a.value = "connecting",
|
|
157
|
-
const t =
|
|
158
|
-
|
|
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
|
|
162
|
-
t.cancelled || t.mediaSource !== r || (a.value = "buffering",
|
|
161
|
+
const p = () => {
|
|
162
|
+
t.cancelled || t.mediaSource !== r || (a.value = "buffering", f("stream-status", "buffering"), D(t));
|
|
163
163
|
};
|
|
164
|
-
t.sourceOpenHandler =
|
|
165
|
-
const
|
|
166
|
-
|
|
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
|
|
168
|
+
if (typeof y.data == "string") {
|
|
169
169
|
try {
|
|
170
|
-
const
|
|
171
|
-
|
|
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
|
|
176
|
+
const k = new Uint8Array(y.data);
|
|
177
177
|
if (!t.sourceBuffer) {
|
|
178
|
-
t.preBuffer.push(
|
|
178
|
+
t.preBuffer.push(k), D(t);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
t.appendQueue.push(
|
|
182
|
-
|
|
182
|
+
k.buffer.slice(k.byteOffset, k.byteOffset + k.byteLength)
|
|
183
183
|
), o(t);
|
|
184
|
-
},
|
|
185
|
-
t.cancelled || a.value !== "error" && (
|
|
186
|
-
},
|
|
187
|
-
t.cancelled || (a.value === "connecting" || a.value === "buffering" ? (
|
|
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 = (
|
|
192
|
-
let r =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
199
|
+
if (p === "hvcC") return "hev1.1.6.L93.B0";
|
|
200
200
|
}
|
|
201
|
-
return
|
|
201
|
+
return null;
|
|
202
202
|
}
|
|
203
|
-
function
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
220
|
+
let k;
|
|
214
221
|
try {
|
|
215
|
-
|
|
216
|
-
} catch (
|
|
217
|
-
|
|
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
|
-
|
|
221
|
-
const
|
|
227
|
+
k.mode = "segments";
|
|
228
|
+
const O = () => {
|
|
222
229
|
e.cancelled || o(e);
|
|
223
230
|
};
|
|
224
|
-
e.updateEndHandler =
|
|
225
|
-
e.cancelled || (
|
|
226
|
-
}), e.sourceBuffer =
|
|
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((
|
|
230
|
-
let
|
|
231
|
-
for (const
|
|
232
|
-
r.set(
|
|
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
|
|
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
|
|
241
|
-
if (
|
|
247
|
+
const p = e.appendQueue.shift();
|
|
248
|
+
if (p)
|
|
242
249
|
try {
|
|
243
|
-
t.appendBuffer(
|
|
244
|
-
} catch (
|
|
245
|
-
const
|
|
246
|
-
if (e.cancelled || (
|
|
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 (
|
|
249
|
-
const
|
|
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) <
|
|
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(
|
|
261
|
+
e.appendQueue.unshift(p);
|
|
255
262
|
} else
|
|
256
|
-
|
|
263
|
+
m.value = `appendBuffer failed: ${O.message}`, a.value = "error", $();
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
|
-
async function
|
|
260
|
-
if (
|
|
261
|
-
|
|
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",
|
|
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:
|
|
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
|
-
|
|
283
|
+
j(r), Q();
|
|
277
284
|
} catch (e) {
|
|
278
|
-
|
|
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
|
|
282
|
-
b.value = !1,
|
|
288
|
+
function g() {
|
|
289
|
+
b.value = !1, $(), a.value = "idle", m.value = null, f("stream-status", "idle");
|
|
283
290
|
}
|
|
284
|
-
function
|
|
291
|
+
function v(e) {
|
|
285
292
|
return e || "";
|
|
286
293
|
}
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
}),
|
|
290
|
-
|
|
291
|
-
}),
|
|
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 &&
|
|
301
|
+
b.value && s.nvrId && s.deviceId && u();
|
|
295
302
|
}
|
|
296
|
-
),
|
|
303
|
+
), U({ start: u, stop: g }), (e, t) => (c(), d("div", {
|
|
297
304
|
class: "rvms-player-wrapper",
|
|
298
|
-
style:
|
|
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" ? (
|
|
315
|
+
a.value === "idle" ? (c(), d("div", {
|
|
309
316
|
key: 0,
|
|
310
317
|
class: "rvms-overlay",
|
|
311
318
|
style: { cursor: "pointer" },
|
|
312
|
-
onClick:
|
|
319
|
+
onClick: u
|
|
313
320
|
}, [
|
|
314
|
-
(
|
|
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" ? (
|
|
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" ? (
|
|
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" ? (
|
|
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
|
-
|
|
342
|
+
m.value ? (c(), d("span", oe, _(m.value), 1)) : B("", !0),
|
|
336
343
|
i("button", {
|
|
337
344
|
class: "rvms-btn",
|
|
338
|
-
onClick:
|
|
345
|
+
onClick: u
|
|
339
346
|
}, " Retry ")
|
|
340
|
-
])) : a.value === "ended" ? (
|
|
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:
|
|
351
|
+
onClick: u
|
|
345
352
|
}, " Reconnect ")
|
|
346
|
-
])) :
|
|
347
|
-
i("div",
|
|
348
|
-
a.value !== "idle" ? (
|
|
353
|
+
])) : B("", !0),
|
|
354
|
+
i("div", le, [
|
|
355
|
+
a.value !== "idle" ? (c(), d("span", {
|
|
349
356
|
key: 0,
|
|
350
|
-
style:
|
|
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:
|
|
364
|
+
background: C.mode === "live" && a.value === "playing" ? "#dc2626" : "#334155",
|
|
358
365
|
color: "#fff"
|
|
359
366
|
})
|
|
360
|
-
},
|
|
361
|
-
w.value ? (
|
|
362
|
-
|
|
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
|
-
|
|
365
|
-
])])) :
|
|
371
|
+
G(" Alarms ", -1)
|
|
372
|
+
])])) : B("", !0)
|
|
366
373
|
]),
|
|
367
|
-
a.value !== "idle" ? (
|
|
374
|
+
a.value !== "idle" ? (c(), d("div", ue, [
|
|
368
375
|
i("button", {
|
|
369
376
|
class: "rvms-btn",
|
|
370
377
|
title: "Stop stream",
|
|
371
|
-
onClick:
|
|
378
|
+
onClick: g
|
|
372
379
|
}, " ⏹ Stop "),
|
|
373
380
|
i("button", {
|
|
374
381
|
class: "rvms-btn",
|
|
375
|
-
title: `Switch to ${
|
|
376
|
-
onClick:
|
|
377
|
-
},
|
|
378
|
-
])) :
|
|
379
|
-
|
|
380
|
-
(
|
|
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: (
|
|
385
|
-
onMouseenter: t[0] || (t[0] = (
|
|
386
|
-
onMouseleave: t[1] || (t[1] = (
|
|
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 ? (
|
|
395
|
+
r.snapshotUrl ? (c(), d("img", {
|
|
389
396
|
key: 0,
|
|
390
|
-
src:
|
|
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,
|
|
395
|
-
i("div",
|
|
396
|
-
i("div",
|
|
397
|
-
i("span",
|
|
398
|
-
r.channel ? (
|
|
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",
|
|
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,
|
|
404
|
-
])) :
|
|
410
|
+
], 40, fe))), 128))
|
|
411
|
+
])) : B("", !0)
|
|
405
412
|
], 4));
|
|
406
413
|
}
|
|
407
|
-
}),
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
434
|
+
}, Oe = {
|
|
428
435
|
key: 5,
|
|
429
436
|
style: { position: "absolute", bottom: "8px", right: "8px", display: "flex", gap: "6px", "z-index": "11" }
|
|
430
|
-
},
|
|
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(
|
|
442
|
-
const x =
|
|
443
|
-
let
|
|
444
|
-
function
|
|
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
|
|
463
|
+
function R() {
|
|
457
464
|
var o;
|
|
458
|
-
const n =
|
|
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,
|
|
487
|
+
if (n.sourceBuffer = null, n.mediaSource = null, n.appendQueue.length = 0, n.preBuffer.length = 0, L = null, f.value)
|
|
481
488
|
try {
|
|
482
|
-
|
|
489
|
+
f.value.pause(), f.value.removeAttribute("src"), f.value.load();
|
|
483
490
|
} catch {
|
|
484
491
|
}
|
|
485
492
|
}
|
|
486
493
|
}
|
|
487
|
-
function
|
|
488
|
-
if (!
|
|
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,
|
|
494
|
-
const o =
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
o.mediaSource =
|
|
498
|
-
const
|
|
499
|
-
o.cancelled || o.mediaSource !==
|
|
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 =
|
|
502
|
-
const
|
|
503
|
-
|
|
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"),
|
|
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
|
-
),
|
|
521
|
-
},
|
|
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
|
-
},
|
|
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
|
|
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
|
|
532
|
-
if (
|
|
533
|
-
const
|
|
534
|
-
|
|
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
|
-
|
|
543
|
+
if (g === "hvcC") return "hev1.1.6.L93.B0";
|
|
537
544
|
}
|
|
538
|
-
return
|
|
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
|
|
544
|
-
for (const
|
|
545
|
-
const e =
|
|
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
|
-
|
|
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(
|
|
565
|
+
n.sourceBuffer = n.mediaSource.addSourceBuffer(u);
|
|
553
566
|
} catch {
|
|
554
|
-
for (const
|
|
567
|
+
for (const v of o)
|
|
555
568
|
try {
|
|
556
|
-
n.sourceBuffer = n.mediaSource.addSourceBuffer(`video/mp4; codecs="${
|
|
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
|
|
578
|
+
for (const v of n.preBuffer)
|
|
566
579
|
try {
|
|
567
|
-
n.sourceBuffer.appendBuffer(
|
|
580
|
+
n.sourceBuffer.appendBuffer(v);
|
|
568
581
|
} catch {
|
|
569
582
|
}
|
|
570
583
|
n.preBuffer.length = 0;
|
|
571
|
-
const
|
|
572
|
-
|
|
584
|
+
const g = () => {
|
|
585
|
+
T(n);
|
|
573
586
|
};
|
|
574
|
-
n.updateEndHandler =
|
|
587
|
+
n.updateEndHandler = g, n.sourceBuffer.addEventListener("updateend", g);
|
|
575
588
|
}
|
|
576
|
-
function
|
|
577
|
-
var
|
|
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
|
|
593
|
+
const u = n.appendQueue.shift();
|
|
581
594
|
try {
|
|
582
|
-
o.appendBuffer(
|
|
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 || (
|
|
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 = ((
|
|
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) <
|
|
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(
|
|
606
|
+
n.appendQueue.unshift(u);
|
|
594
607
|
} else
|
|
595
|
-
a.value = `appendBuffer failed: ${t.message}`, l.value = "error",
|
|
608
|
+
a.value = `appendBuffer failed: ${t.message}`, l.value = "error", R();
|
|
596
609
|
}
|
|
597
610
|
}
|
|
598
|
-
async function
|
|
599
|
-
if (b.value === "playback" && !
|
|
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,
|
|
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:
|
|
622
|
+
profile: I.value
|
|
610
623
|
});
|
|
611
624
|
if (b.value === "playback") {
|
|
612
|
-
let
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
n.set("from", new Date(
|
|
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
|
|
620
|
-
|
|
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
|
|
626
|
-
w.value = !1,
|
|
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
|
-
|
|
642
|
+
b.value = b.value === "live" ? "playback" : "live", w.value && $();
|
|
633
643
|
}
|
|
634
|
-
function
|
|
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 && (
|
|
649
|
+
o.value && (E.value = new Date(o.value).toISOString());
|
|
637
650
|
}
|
|
638
|
-
return
|
|
639
|
-
|
|
640
|
-
}),
|
|
651
|
+
return q(() => {
|
|
652
|
+
j();
|
|
653
|
+
}), K(
|
|
641
654
|
() => [x.nvrId, x.channelId].join("|"),
|
|
642
655
|
() => {
|
|
643
|
-
w.value && x.nvrId && x.channelId &&
|
|
656
|
+
w.value && x.nvrId && x.channelId && $();
|
|
644
657
|
}
|
|
645
|
-
), (n, o) => (
|
|
658
|
+
), (n, o) => (c(), d("div", {
|
|
646
659
|
class: "rvms-video",
|
|
647
|
-
style:
|
|
648
|
-
width:
|
|
649
|
-
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:
|
|
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" ? (
|
|
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:
|
|
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" ? (
|
|
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",
|
|
691
|
-
])) : l.value === "error" ? (
|
|
692
|
-
i("span",
|
|
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:
|
|
708
|
+
onClick: $
|
|
696
709
|
}, " Retry ")
|
|
697
|
-
])) : l.value === "ended" ? (
|
|
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:
|
|
714
|
+
onClick: $
|
|
702
715
|
}, "Reconnect")
|
|
703
|
-
])) :
|
|
704
|
-
i("div",
|
|
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:
|
|
709
|
-
style:
|
|
721
|
+
onClick: V,
|
|
722
|
+
style: H({
|
|
710
723
|
background: b.value === "live" ? "#dc2626" : "#2563eb",
|
|
711
724
|
pointerEvents: "auto"
|
|
712
725
|
})
|
|
713
|
-
},
|
|
726
|
+
}, _(b.value === "live" ? "🔴 LIVE" : "🔵 PLAYBACK"), 13, $e),
|
|
714
727
|
i("button", {
|
|
715
728
|
class: "rvms-chip",
|
|
716
|
-
title: `Switch to ${
|
|
717
|
-
onClick:
|
|
729
|
+
title: `Switch to ${I.value === "main" ? "sub" : "main"} stream`,
|
|
730
|
+
onClick: W,
|
|
718
731
|
style: { pointerEvents: "auto" }
|
|
719
|
-
},
|
|
720
|
-
|
|
721
|
-
b.value === "playback" ? (
|
|
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:
|
|
725
|
-
onInput:
|
|
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,
|
|
730
|
-
b.value === "playback" &&
|
|
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" ? (
|
|
745
|
+
l.value !== "idle" ? (c(), d("div", Le, [
|
|
733
746
|
i("span", {
|
|
734
|
-
style:
|
|
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
|
-
},
|
|
744
|
-
])) :
|
|
745
|
-
l.value !== "idle" ? (
|
|
746
|
-
w.value ? (
|
|
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:
|
|
751
|
-
}, " ⏹ Stop ")) : (
|
|
763
|
+
onClick: j
|
|
764
|
+
}, " ⏹ Stop ")) : (c(), d("button", {
|
|
752
765
|
key: 1,
|
|
753
766
|
class: "rvms-btn",
|
|
754
767
|
title: "Start stream",
|
|
755
|
-
onClick:
|
|
768
|
+
onClick: $
|
|
756
769
|
}, " ▶ Play "))
|
|
757
|
-
])) :
|
|
770
|
+
])) : B("", !0)
|
|
758
771
|
], 4));
|
|
759
772
|
}
|
|
760
|
-
}),
|
|
761
|
-
const x =
|
|
762
|
-
for (const [s,
|
|
763
|
-
x[s] =
|
|
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
|
|
778
|
+
}, He = /* @__PURE__ */ Re(ze, [["__scopeId", "data-v-ac4e493f"]]);
|
|
766
779
|
export {
|
|
767
780
|
He as RvmsVideo,
|
|
768
781
|
Ue as RvmsVideoPlayer
|
package/dist/lib/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@keyframes rvms-pulse{0%,to{opacity:1}50%{opacity:.4}}.rvms-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#00000080;font-size:14px}.rvms-btn{padding:4px 12px;border:1px solid rgba(255,255,255,.2);border-radius:4px;background:#1e293bcc;color:#e2e8f0;font-size:11px;font-weight:600;cursor:pointer;pointer-events:auto;transition:background .15s}.rvms-btn:hover{background:#334155e6}@keyframes rvms-spin-
|
|
1
|
+
@keyframes rvms-pulse{0%,to{opacity:1}50%{opacity:.4}}.rvms-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#00000080;font-size:14px}.rvms-btn{padding:4px 12px;border:1px solid rgba(255,255,255,.2);border-radius:4px;background:#1e293bcc;color:#e2e8f0;font-size:11px;font-weight:600;cursor:pointer;pointer-events:auto;transition:background .15s}.rvms-btn:hover{background:#334155e6}@keyframes rvms-spin-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
|
@@ -244,39 +244,54 @@ function connectStream(wsUrl: string): void {
|
|
|
244
244
|
|
|
245
245
|
// ── MSE helpers ──────────────────────────────────────────────────────────────
|
|
246
246
|
|
|
247
|
-
function
|
|
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
|
-
|
|
254
|
+
return `avc1.${hex2(profile)}${hex2(compat)}${hex2(level)}`;
|
|
257
255
|
}
|
|
258
256
|
}
|
|
259
|
-
if (tag === 'hvcC')
|
|
260
|
-
if (tag === 'mp4a') audioCodec = 'mp4a.40.2';
|
|
257
|
+
if (tag === 'hvcC') return 'hev1.1.6.L93.B0';
|
|
261
258
|
}
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
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
|
|
273
|
-
if (
|
|
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
|
|
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
|
|
373
|
-
function
|
|
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
|
-
|
|
380
|
+
return `avc1.${hex2(profile)}${hex2(compat)}${hex2(level)}`;
|
|
383
381
|
}
|
|
384
382
|
}
|
|
385
|
-
if (tag === 'hvcC')
|
|
386
|
-
if (tag === 'mp4a') audioCodec = 'mp4a.40.2'; // AAC audio present
|
|
383
|
+
if (tag === 'hvcC') return 'hev1.1.6.L93.B0';
|
|
387
384
|
}
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
398
|
-
if (!
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
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}`;
|