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