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