virtual-human-cf 1.0.1 → 1.0.3
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/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.fade-enter-active[data-v-
|
|
1
|
+
.fade-enter-active[data-v-54e42f84],.fade-leave-active[data-v-54e42f84]{transition:opacity .5s ease,transform .5s ease}.fade-enter-from[data-v-54e42f84],.fade-leave-to[data-v-54e42f84]{opacity:0;transform:translateY(10px)}.virtual-human-container[data-v-54e42f84]{position:fixed;left:16px;bottom:16px;width:400px;height:500px;z-index:2147483647;overflow:visible}.video-wrapper[data-v-54e42f84]{position:relative;width:400px;height:500px;border-radius:1rem;overflow:hidden;box-shadow:0 10px 25px #0000001a;background:#fff;transition:background-color .3s ease,box-shadow .3s ease}.virtual-human-container.is-dark .video-wrapper[data-v-54e42f84]{background:#1f2937;box-shadow:0 10px 25px #00000080}.persona-video[data-v-54e42f84]{width:100%;height:100%;object-fit:cover;transform-origin:center;transition:transform .1s ease-out}.resize-handle[data-v-54e42f84]{position:absolute;width:20px;height:20px;background:#00000080;border:2px solid rgba(255,255,255,.8);border-radius:4px;cursor:pointer;z-index:20;transition:background .2s,border-color .2s}.resize-handle[data-v-54e42f84]:hover{background:#000000b3;border-color:#fff}.resize-handle.top-left[data-v-54e42f84]{top:-10px;left:-10px;cursor:nwse-resize}.resize-handle.top-right[data-v-54e42f84]{top:-10px;right:-10px;cursor:nesw-resize}.resize-handle.bottom-left[data-v-54e42f84]{bottom:-10px;left:-10px;cursor:nesw-resize}.resize-handle.bottom-right[data-v-54e42f84]{bottom:-10px;right:-10px;cursor:nwse-resize}.overlay[data-v-54e42f84]{position:absolute;top:1rem;right:1rem;z-index:10}.status-badge[data-v-54e42f84]{display:flex;align-items:center;gap:.5rem;padding:.25rem .75rem;border-radius:9999px;font-size:.75rem;font-weight:500;color:#fff;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.status-badge.paused[data-v-54e42f84]{background:#ef4444cc}.status-badge.playing[data-v-54e42f84]{background:#22c55ecc}.dot[data-v-54e42f84]{width:6px;height:6px;border-radius:50%;background-color:#fff}@keyframes pulse-54e42f84{0%,to{opacity:1}50%{opacity:.5}}.animate-pulse[data-v-54e42f84]{animation:pulse-54e42f84 2s cubic-bezier(.4,0,.6,1) infinite}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
const
|
|
1
|
+
import { defineComponent as T, ref as v, watch as z, onMounted as W, onUnmounted as X, openBlock as h, createBlock as D, Transition as F, withCtx as q, createElementBlock as b, normalizeClass as N, createElementVNode as H, normalizeStyle as O, createCommentVNode as I, createTextVNode as L, renderSlot as J } from "vue";
|
|
2
|
+
const j = ["src", "muted"], G = { class: "overlay" }, K = {
|
|
3
3
|
key: 0,
|
|
4
4
|
class: "status-badge paused"
|
|
5
|
-
},
|
|
5
|
+
}, Q = {
|
|
6
6
|
key: 1,
|
|
7
7
|
class: "status-badge playing"
|
|
8
|
-
},
|
|
8
|
+
}, Z = /* @__PURE__ */ T({
|
|
9
9
|
__name: "VirtualHumanPersona",
|
|
10
10
|
props: {
|
|
11
11
|
// 视频源URL
|
|
@@ -16,7 +16,7 @@ const F = { class: "video-wrapper" }, T = ["src", "muted"], q = { class: "overla
|
|
|
16
16
|
// 是否可见
|
|
17
17
|
visible: {
|
|
18
18
|
type: Boolean,
|
|
19
|
-
default: !
|
|
19
|
+
default: !1
|
|
20
20
|
},
|
|
21
21
|
// 是否自动播放
|
|
22
22
|
isPlaying: {
|
|
@@ -46,90 +46,131 @@ const F = { class: "video-wrapper" }, T = ["src", "muted"], q = { class: "overla
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
emits: ["update:isPlaying", "ended", "update:visible"],
|
|
49
|
-
setup(
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
49
|
+
setup(i, { emit: P }) {
|
|
50
|
+
const t = i, s = P, l = v(null), k = v(null), n = v(null), m = v(!1), V = v(1), E = v(!1), p = v(null), C = v(0), x = v(0), o = v(1), u = () => {
|
|
51
|
+
if (n.value && n.value.close(), !(!t.wsUrl || !t.screenClientId))
|
|
52
52
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
53
|
+
const r = new URL(t.wsUrl);
|
|
54
|
+
r.searchParams.append("sessionId", t.screenClientId + "-persona"), n.value = new WebSocket(r.toString()), n.value.onopen = () => {
|
|
55
|
+
m.value = !0, console.log(`[VirtualHumanPersona] Connected to ${t.wsUrl} for session ${t.screenClientId}-persona`);
|
|
56
|
+
}, n.value.onmessage = (e) => {
|
|
57
57
|
try {
|
|
58
|
-
const a = JSON.parse(
|
|
59
|
-
|
|
58
|
+
const a = JSON.parse(e.data);
|
|
59
|
+
c(a);
|
|
60
60
|
} catch (a) {
|
|
61
|
-
console.error("[VirtualHumanPersona] Failed to parse message:",
|
|
61
|
+
console.error("[VirtualHumanPersona] Failed to parse message:", e.data, a);
|
|
62
62
|
}
|
|
63
|
-
},
|
|
64
|
-
console.error("[VirtualHumanPersona] WebSocket error:",
|
|
65
|
-
},
|
|
66
|
-
|
|
63
|
+
}, n.value.onerror = (e) => {
|
|
64
|
+
console.error("[VirtualHumanPersona] WebSocket error:", e);
|
|
65
|
+
}, n.value.onclose = () => {
|
|
66
|
+
m.value = !1, console.log("[VirtualHumanPersona] WebSocket disconnected");
|
|
67
67
|
};
|
|
68
|
-
} catch (
|
|
69
|
-
console.error("[VirtualHumanPersona] Failed to initialize WebSocket:",
|
|
68
|
+
} catch (r) {
|
|
69
|
+
console.error("[VirtualHumanPersona] Failed to initialize WebSocket:", r);
|
|
70
70
|
}
|
|
71
|
-
},
|
|
72
|
-
const { type:
|
|
73
|
-
|
|
71
|
+
}, c = (r) => {
|
|
72
|
+
const { type: e, action: a } = r;
|
|
73
|
+
e === "control" && (a === "play" ? (s("update:isPlaying", !0), s("update:visible", !0)) : a === "pause" ? s("update:isPlaying", !1) : a === "stop" && (s("update:isPlaying", !1), s("update:visible", !1), l.value && (l.value.currentTime = 0)));
|
|
74
74
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
|
|
79
|
-
}),
|
|
80
|
-
|
|
75
|
+
z(() => t.screenClientId, () => {
|
|
76
|
+
t.screenClientId && t.wsUrl && u();
|
|
77
|
+
}), z(() => t.wsUrl, () => {
|
|
78
|
+
t.screenClientId && t.wsUrl && u();
|
|
79
|
+
}), z(() => t.isPlaying, (r) => {
|
|
80
|
+
l.value && (r ? l.value.play().catch((e) => console.error("Video play failed:", e)) : l.value.pause());
|
|
81
81
|
});
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
},
|
|
82
|
+
const w = () => {
|
|
83
|
+
t.isPlaying || s("update:isPlaying", !0);
|
|
84
|
+
}, g = () => {
|
|
85
|
+
t.isPlaying && s("update:isPlaying", !1);
|
|
86
|
+
}, A = () => {
|
|
87
87
|
s("update:isPlaying", !1), s("ended");
|
|
88
|
+
}, f = (r, e) => {
|
|
89
|
+
E.value = !0, p.value = r, o.value = V.value;
|
|
90
|
+
const a = "clientX" in e ? e.clientX : e.touches[0].clientX, S = "clientY" in e ? e.clientY : e.touches[0].clientY;
|
|
91
|
+
C.value = a, x.value = S, document.addEventListener("mousemove", y), document.addEventListener("mouseup", d), document.addEventListener("touchmove", y), document.addEventListener("touchend", d);
|
|
92
|
+
}, y = (r) => {
|
|
93
|
+
if (!E.value || !k.value) return;
|
|
94
|
+
const e = "clientX" in r ? r.clientX : r.touches[0].clientX, a = "clientY" in r ? r.clientY : r.touches[0].clientY, S = e - C.value, M = a - x.value, B = k.value.getBoundingClientRect(), Y = 0.5, $ = 2;
|
|
95
|
+
let U = o.value;
|
|
96
|
+
const R = Math.min(B.width, B.height);
|
|
97
|
+
p.value === "bottom-right" ? U = o.value + (S + M) / R : p.value === "bottom-left" ? U = o.value + (-S + M) / R : p.value === "top-right" ? U = o.value + (S - M) / R : p.value === "top-left" && (U = o.value + (-S - M) / R), V.value = Math.max(Y, Math.min($, U));
|
|
98
|
+
}, d = () => {
|
|
99
|
+
E.value = !1, p.value = null, document.removeEventListener("mousemove", y), document.removeEventListener("mouseup", d), document.removeEventListener("touchmove", y), document.removeEventListener("touchend", d);
|
|
88
100
|
};
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
}),
|
|
92
|
-
|
|
93
|
-
}), (
|
|
94
|
-
default:
|
|
95
|
-
|
|
101
|
+
return W(() => {
|
|
102
|
+
t.isPlaying && l.value && l.value.play().catch((r) => console.error("Video play failed:", r)), t.screenClientId && t.wsUrl && u();
|
|
103
|
+
}), X(() => {
|
|
104
|
+
n.value && n.value.close();
|
|
105
|
+
}), (r, e) => (h(), D(F, { name: "fade" }, {
|
|
106
|
+
default: q(() => [
|
|
107
|
+
i.visible ? (h(), b("div", {
|
|
96
108
|
key: 0,
|
|
97
|
-
class:
|
|
109
|
+
class: N(["virtual-human-container", { "is-dark": i.isDark }])
|
|
98
110
|
}, [
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
H("div", {
|
|
112
|
+
class: "video-wrapper",
|
|
113
|
+
ref_key: "wrapperRef",
|
|
114
|
+
ref: k,
|
|
115
|
+
style: O({ transform: `scale(${V.value})`, transformOrigin: "left bottom" })
|
|
116
|
+
}, [
|
|
117
|
+
H("video", {
|
|
101
118
|
ref_key: "videoRef",
|
|
102
|
-
ref:
|
|
103
|
-
src:
|
|
119
|
+
ref: l,
|
|
120
|
+
src: i.videoSrc,
|
|
104
121
|
class: "persona-video",
|
|
105
|
-
muted:
|
|
122
|
+
muted: i.muted,
|
|
106
123
|
playsinline: "",
|
|
107
124
|
loop: "",
|
|
108
|
-
onPlay:
|
|
109
|
-
onPause:
|
|
110
|
-
onEnded:
|
|
111
|
-
}, null, 40,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
onPlay: w,
|
|
126
|
+
onPause: g,
|
|
127
|
+
onEnded: A
|
|
128
|
+
}, null, 40, j),
|
|
129
|
+
i.visible ? (h(), b("div", {
|
|
130
|
+
key: 0,
|
|
131
|
+
class: "resize-handle top-left",
|
|
132
|
+
onMousedown: e[0] || (e[0] = (a) => f("top-left", a)),
|
|
133
|
+
onTouchstart: e[1] || (e[1] = (a) => f("top-left", a))
|
|
134
|
+
}, null, 32)) : I("", !0),
|
|
135
|
+
i.visible ? (h(), b("div", {
|
|
136
|
+
key: 1,
|
|
137
|
+
class: "resize-handle top-right",
|
|
138
|
+
onMousedown: e[2] || (e[2] = (a) => f("top-right", a)),
|
|
139
|
+
onTouchstart: e[3] || (e[3] = (a) => f("top-right", a))
|
|
140
|
+
}, null, 32)) : I("", !0),
|
|
141
|
+
i.visible ? (h(), b("div", {
|
|
142
|
+
key: 2,
|
|
143
|
+
class: "resize-handle bottom-left",
|
|
144
|
+
onMousedown: e[4] || (e[4] = (a) => f("bottom-left", a)),
|
|
145
|
+
onTouchstart: e[5] || (e[5] = (a) => f("bottom-left", a))
|
|
146
|
+
}, null, 32)) : I("", !0),
|
|
147
|
+
i.visible ? (h(), b("div", {
|
|
148
|
+
key: 3,
|
|
149
|
+
class: "resize-handle bottom-right",
|
|
150
|
+
onMousedown: e[6] || (e[6] = (a) => f("bottom-right", a)),
|
|
151
|
+
onTouchstart: e[7] || (e[7] = (a) => f("bottom-right", a))
|
|
152
|
+
}, null, 32)) : I("", !0),
|
|
153
|
+
H("div", G, [
|
|
154
|
+
i.isPlaying ? (h(), b("div", Q, [...e[9] || (e[9] = [
|
|
155
|
+
H("span", { class: "dot animate-pulse" }, null, -1),
|
|
156
|
+
L(" 播放中 ", -1)
|
|
157
|
+
])])) : (h(), b("div", K, [...e[8] || (e[8] = [
|
|
158
|
+
H("span", { class: "dot" }, null, -1),
|
|
159
|
+
L(" 暂停中 ", -1)
|
|
119
160
|
])]))
|
|
120
161
|
])
|
|
121
|
-
])
|
|
122
|
-
], 2)) :
|
|
162
|
+
], 4)
|
|
163
|
+
], 2)) : I("", !0)
|
|
123
164
|
]),
|
|
124
165
|
_: 1
|
|
125
166
|
}));
|
|
126
167
|
}
|
|
127
|
-
}),
|
|
128
|
-
const
|
|
129
|
-
for (const [s,
|
|
130
|
-
|
|
131
|
-
return
|
|
132
|
-
},
|
|
168
|
+
}), _ = (i, P) => {
|
|
169
|
+
const t = i.__vccOpts || i;
|
|
170
|
+
for (const [s, l] of P)
|
|
171
|
+
t[s] = l;
|
|
172
|
+
return t;
|
|
173
|
+
}, ee = /* @__PURE__ */ _(Z, [["__scopeId", "data-v-54e42f84"]]), te = /* @__PURE__ */ T({
|
|
133
174
|
__name: "VirtualHumanEventAdapter",
|
|
134
175
|
props: {
|
|
135
176
|
// 屏幕客户端ID
|
|
@@ -144,32 +185,32 @@ const F = { class: "video-wrapper" }, T = ["src", "muted"], q = { class: "overla
|
|
|
144
185
|
}
|
|
145
186
|
},
|
|
146
187
|
emits: ["highlight", "showDialog", "end", "pause", "connected", "error"],
|
|
147
|
-
setup(
|
|
148
|
-
const
|
|
149
|
-
let n = null,
|
|
150
|
-
const
|
|
188
|
+
setup(i, { emit: P }) {
|
|
189
|
+
const t = i, s = P, l = v(null), k = v(!1);
|
|
190
|
+
let n = null, m = 0;
|
|
191
|
+
const V = () => {
|
|
151
192
|
n || (n = new (window.AudioContext || window.webkitAudioContext)({
|
|
152
193
|
sampleRate: 24e3
|
|
153
194
|
})), n.state === "suspended" && n.resume();
|
|
154
|
-
},
|
|
155
|
-
if (
|
|
195
|
+
}, E = (o) => {
|
|
196
|
+
if (V(), !!n)
|
|
156
197
|
try {
|
|
157
|
-
const
|
|
158
|
-
for (let d = 0; d <
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
for (let d = 0; d <
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
} catch (
|
|
168
|
-
console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:",
|
|
198
|
+
const u = window.atob(o), c = u.length, w = new Uint8Array(c);
|
|
199
|
+
for (let d = 0; d < c; d++)
|
|
200
|
+
w[d] = u.charCodeAt(d);
|
|
201
|
+
const g = new Int16Array(w.buffer), A = new Float32Array(g.length);
|
|
202
|
+
for (let d = 0; d < g.length; d++)
|
|
203
|
+
A[d] = g[d] / 32768;
|
|
204
|
+
const f = n.createBuffer(1, A.length, 24e3);
|
|
205
|
+
f.getChannelData(0).set(A);
|
|
206
|
+
const y = n.createBufferSource();
|
|
207
|
+
y.buffer = f, y.connect(n.destination), m < n.currentTime && (m = n.currentTime), y.start(m), m += f.duration;
|
|
208
|
+
} catch (u) {
|
|
209
|
+
console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:", u);
|
|
169
210
|
}
|
|
170
|
-
},
|
|
211
|
+
}, p = (o) => {
|
|
171
212
|
if (n)
|
|
172
|
-
switch (
|
|
213
|
+
switch (o) {
|
|
173
214
|
case "play":
|
|
174
215
|
n.state === "suspended" && n.resume();
|
|
175
216
|
break;
|
|
@@ -177,78 +218,78 @@ const F = { class: "video-wrapper" }, T = ["src", "muted"], q = { class: "overla
|
|
|
177
218
|
n.state === "running" && n.suspend();
|
|
178
219
|
break;
|
|
179
220
|
case "stop":
|
|
180
|
-
n.close(), n = null,
|
|
221
|
+
n.close(), n = null, m = 0;
|
|
181
222
|
break;
|
|
182
223
|
default:
|
|
183
|
-
console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${
|
|
224
|
+
console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${o}`);
|
|
184
225
|
}
|
|
185
|
-
},
|
|
186
|
-
|
|
226
|
+
}, C = () => {
|
|
227
|
+
l.value && l.value.close();
|
|
187
228
|
try {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
},
|
|
229
|
+
const o = new URL(t.wsUrl);
|
|
230
|
+
o.searchParams.append("sessionId", t.screenClientId + "-event"), l.value = new WebSocket(o.toString()), l.value.onopen = () => {
|
|
231
|
+
k.value = !0, s("connected"), console.log(`[VirtualHumanEventAdapter] Connected to ${t.wsUrl} for session ${t.screenClientId}-event`);
|
|
232
|
+
}, l.value.onmessage = (u) => {
|
|
192
233
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
} catch (
|
|
196
|
-
console.error("[VirtualHumanEventAdapter] Failed to parse message:",
|
|
234
|
+
const c = JSON.parse(u.data);
|
|
235
|
+
x(c);
|
|
236
|
+
} catch (c) {
|
|
237
|
+
console.error("[VirtualHumanEventAdapter] Failed to parse message:", u.data, c);
|
|
197
238
|
}
|
|
198
|
-
},
|
|
199
|
-
console.error("[VirtualHumanEventAdapter] WebSocket error:",
|
|
200
|
-
},
|
|
201
|
-
|
|
239
|
+
}, l.value.onerror = (u) => {
|
|
240
|
+
console.error("[VirtualHumanEventAdapter] WebSocket error:", u), s("error", u);
|
|
241
|
+
}, l.value.onclose = () => {
|
|
242
|
+
k.value = !1, console.log("[VirtualHumanEventAdapter] WebSocket disconnected");
|
|
202
243
|
};
|
|
203
|
-
} catch (
|
|
204
|
-
console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:",
|
|
244
|
+
} catch (o) {
|
|
245
|
+
console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:", o), s("error", o);
|
|
205
246
|
}
|
|
206
|
-
},
|
|
207
|
-
const { type:
|
|
208
|
-
switch (
|
|
247
|
+
}, x = (o) => {
|
|
248
|
+
const { type: u, payload: c, action: w } = o;
|
|
249
|
+
switch (console.log("msgmsg", o), u) {
|
|
209
250
|
case "audio":
|
|
210
|
-
const
|
|
211
|
-
|
|
251
|
+
const g = (c == null ? void 0 : c.data) || o.data;
|
|
252
|
+
g && E(g);
|
|
212
253
|
break;
|
|
213
254
|
case "dialog_event":
|
|
214
|
-
|
|
255
|
+
o.event && s(o.event, o.params);
|
|
215
256
|
break;
|
|
216
257
|
case "control":
|
|
217
|
-
|
|
258
|
+
w && p(w);
|
|
218
259
|
break;
|
|
219
260
|
case "highlight":
|
|
220
|
-
s("highlight",
|
|
261
|
+
s("highlight", c);
|
|
221
262
|
break;
|
|
222
263
|
case "showDialog":
|
|
223
|
-
s("showDialog",
|
|
264
|
+
s("showDialog", c);
|
|
224
265
|
break;
|
|
225
266
|
case "end":
|
|
226
|
-
s("end",
|
|
267
|
+
s("end", c);
|
|
227
268
|
break;
|
|
228
269
|
case "pause":
|
|
229
|
-
s("pause",
|
|
270
|
+
s("pause", c);
|
|
230
271
|
break;
|
|
231
272
|
default:
|
|
232
|
-
console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${
|
|
273
|
+
console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${u}`);
|
|
233
274
|
}
|
|
234
275
|
};
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
}),
|
|
238
|
-
|
|
239
|
-
}),
|
|
240
|
-
|
|
241
|
-
}),
|
|
242
|
-
|
|
243
|
-
}), (
|
|
276
|
+
return z(() => t.screenClientId, () => {
|
|
277
|
+
t.screenClientId && t.wsUrl && C();
|
|
278
|
+
}), z(() => t.wsUrl, () => {
|
|
279
|
+
t.screenClientId && t.wsUrl && C();
|
|
280
|
+
}), W(() => {
|
|
281
|
+
t.screenClientId && t.wsUrl && C();
|
|
282
|
+
}), X(() => {
|
|
283
|
+
l.value && l.value.close(), n && n.close();
|
|
284
|
+
}), (o, u) => J(o.$slots, "default");
|
|
244
285
|
}
|
|
245
|
-
}),
|
|
246
|
-
|
|
247
|
-
},
|
|
248
|
-
install:
|
|
286
|
+
}), ne = (i) => {
|
|
287
|
+
i.component("VirtualHumanPersona", ee), i.component("VirtualHumanEventAdapter", te);
|
|
288
|
+
}, oe = {
|
|
289
|
+
install: ne
|
|
249
290
|
};
|
|
250
291
|
export {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
292
|
+
te as VirtualHumanEventAdapter,
|
|
293
|
+
ee as VirtualHumanPersona,
|
|
294
|
+
oe as default
|
|
254
295
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(p,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(p=typeof globalThis<"u"?globalThis:p||self,e(p.VirtualHumanCf={},p.Vue))})(this,function(p,e){"use strict";const z=["src","muted"],N={class:"overlay"},T={key:0,class:"status-badge paused"},R={key:1,class:"status-badge playing"},I=((c,V)=>{const n=c.__vccOpts||c;for(const[s,r]of V)n[s]=r;return n})(e.defineComponent({__name:"VirtualHumanPersona",props:{videoSrc:{type:String,required:!0},visible:{type:Boolean,default:!1},isPlaying:{type:Boolean,default:!1},muted:{type:Boolean,default:!0},isDark:{type:Boolean,default:!1},screenClientId:{type:String,required:!1},wsUrl:{type:String,required:!1}},emits:["update:isPlaying","ended","update:visible"],setup(c,{emit:V}){const n=c,s=V,r=e.ref(null),v=e.ref(null),o=e.ref(null),g=e.ref(!1),E=e.ref(1),S=e.ref(!1),h=e.ref(null),b=e.ref(0),H=e.ref(0),l=e.ref(1),d=()=>{if(o.value&&o.value.close(),!(!n.wsUrl||!n.screenClientId))try{const i=new URL(n.wsUrl);i.searchParams.append("sessionId",n.screenClientId+"-persona"),o.value=new WebSocket(i.toString()),o.value.onopen=()=>{g.value=!0,console.log(`[VirtualHumanPersona] Connected to ${n.wsUrl} for session ${n.screenClientId}-persona`)},o.value.onmessage=t=>{try{const a=JSON.parse(t.data);u(a)}catch(a){console.error("[VirtualHumanPersona] Failed to parse message:",t.data,a)}},o.value.onerror=t=>{console.error("[VirtualHumanPersona] WebSocket error:",t)},o.value.onclose=()=>{g.value=!1,console.log("[VirtualHumanPersona] WebSocket disconnected")}}catch(i){console.error("[VirtualHumanPersona] Failed to initialize WebSocket:",i)}},u=i=>{const{type:t,action:a}=i;t==="control"&&(a==="play"?(s("update:isPlaying",!0),s("update:visible",!0)):a==="pause"?s("update:isPlaying",!1):a==="stop"&&(s("update:isPlaying",!1),s("update:visible",!1),r.value&&(r.value.currentTime=0)))};e.watch(()=>n.screenClientId,()=>{n.screenClientId&&n.wsUrl&&d()}),e.watch(()=>n.wsUrl,()=>{n.screenClientId&&n.wsUrl&&d()}),e.watch(()=>n.isPlaying,i=>{r.value&&(i?r.value.play().catch(t=>console.error("Video play failed:",t)):r.value.pause())});const k=()=>{n.isPlaying||s("update:isPlaying",!0)},y=()=>{n.isPlaying&&s("update:isPlaying",!1)},P=()=>{s("update:isPlaying",!1),s("ended")},m=(i,t)=>{S.value=!0,h.value=i,l.value=E.value;const a="clientX"in t?t.clientX:t.touches[0].clientX,C="clientY"in t?t.clientY:t.touches[0].clientY;b.value=a,H.value=C,document.addEventListener("mousemove",w),document.addEventListener("mouseup",f),document.addEventListener("touchmove",w),document.addEventListener("touchend",f)},w=i=>{if(!S.value||!v.value)return;const t="clientX"in i?i.clientX:i.touches[0].clientX,a="clientY"in i?i.clientY:i.touches[0].clientY,C=t-b.value,U=a-H.value,x=v.value.getBoundingClientRect(),W=.5,X=2;let B=l.value;const A=Math.min(x.width,x.height);h.value==="bottom-right"?B=l.value+(C+U)/A:h.value==="bottom-left"?B=l.value+(-C+U)/A:h.value==="top-right"?B=l.value+(C-U)/A:h.value==="top-left"&&(B=l.value+(-C-U)/A),E.value=Math.max(W,Math.min(X,B))},f=()=>{S.value=!1,h.value=null,document.removeEventListener("mousemove",w),document.removeEventListener("mouseup",f),document.removeEventListener("touchmove",w),document.removeEventListener("touchend",f)};return e.onMounted(()=>{n.isPlaying&&r.value&&r.value.play().catch(i=>console.error("Video play failed:",i)),n.screenClientId&&n.wsUrl&&d()}),e.onUnmounted(()=>{o.value&&o.value.close()}),(i,t)=>(e.openBlock(),e.createBlock(e.Transition,{name:"fade"},{default:e.withCtx(()=>[c.visible?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["virtual-human-container",{"is-dark":c.isDark}])},[e.createElementVNode("div",{class:"video-wrapper",ref_key:"wrapperRef",ref:v,style:e.normalizeStyle({transform:`scale(${E.value})`,transformOrigin:"left bottom"})},[e.createElementVNode("video",{ref_key:"videoRef",ref:r,src:c.videoSrc,class:"persona-video",muted:c.muted,playsinline:"",loop:"",onPlay:k,onPause:y,onEnded:P},null,40,z),c.visible?(e.openBlock(),e.createElementBlock("div",{key:0,class:"resize-handle top-left",onMousedown:t[0]||(t[0]=a=>m("top-left",a)),onTouchstart:t[1]||(t[1]=a=>m("top-left",a))},null,32)):e.createCommentVNode("",!0),c.visible?(e.openBlock(),e.createElementBlock("div",{key:1,class:"resize-handle top-right",onMousedown:t[2]||(t[2]=a=>m("top-right",a)),onTouchstart:t[3]||(t[3]=a=>m("top-right",a))},null,32)):e.createCommentVNode("",!0),c.visible?(e.openBlock(),e.createElementBlock("div",{key:2,class:"resize-handle bottom-left",onMousedown:t[4]||(t[4]=a=>m("bottom-left",a)),onTouchstart:t[5]||(t[5]=a=>m("bottom-left",a))},null,32)):e.createCommentVNode("",!0),c.visible?(e.openBlock(),e.createElementBlock("div",{key:3,class:"resize-handle bottom-right",onMousedown:t[6]||(t[6]=a=>m("bottom-right",a)),onTouchstart:t[7]||(t[7]=a=>m("bottom-right",a))},null,32)):e.createCommentVNode("",!0),e.createElementVNode("div",N,[c.isPlaying?(e.openBlock(),e.createElementBlock("div",R,[...t[9]||(t[9]=[e.createElementVNode("span",{class:"dot animate-pulse"},null,-1),e.createTextVNode(" 播放中 ",-1)])])):(e.openBlock(),e.createElementBlock("div",T,[...t[8]||(t[8]=[e.createElementVNode("span",{class:"dot"},null,-1),e.createTextVNode(" 暂停中 ",-1)])]))])],4)],2)):e.createCommentVNode("",!0)]),_:1}))}}),[["__scopeId","data-v-54e42f84"]]),M=e.defineComponent({__name:"VirtualHumanEventAdapter",props:{screenClientId:{type:String,required:!0},wsUrl:{type:String,required:!0}},emits:["highlight","showDialog","end","pause","connected","error"],setup(c,{emit:V}){const n=c,s=V,r=e.ref(null),v=e.ref(!1);let o=null,g=0;const E=()=>{o||(o=new(window.AudioContext||window.webkitAudioContext)({sampleRate:24e3})),o.state==="suspended"&&o.resume()},S=l=>{if(E(),!!o)try{const d=window.atob(l),u=d.length,k=new Uint8Array(u);for(let f=0;f<u;f++)k[f]=d.charCodeAt(f);const y=new Int16Array(k.buffer),P=new Float32Array(y.length);for(let f=0;f<y.length;f++)P[f]=y[f]/32768;const m=o.createBuffer(1,P.length,24e3);m.getChannelData(0).set(P);const w=o.createBufferSource();w.buffer=m,w.connect(o.destination),g<o.currentTime&&(g=o.currentTime),w.start(g),g+=m.duration}catch(d){console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:",d)}},h=l=>{if(o)switch(l){case"play":o.state==="suspended"&&o.resume();break;case"pause":o.state==="running"&&o.suspend();break;case"stop":o.close(),o=null,g=0;break;default:console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${l}`)}},b=()=>{r.value&&r.value.close();try{const l=new URL(n.wsUrl);l.searchParams.append("sessionId",n.screenClientId+"-event"),r.value=new WebSocket(l.toString()),r.value.onopen=()=>{v.value=!0,s("connected"),console.log(`[VirtualHumanEventAdapter] Connected to ${n.wsUrl} for session ${n.screenClientId}-event`)},r.value.onmessage=d=>{try{const u=JSON.parse(d.data);H(u)}catch(u){console.error("[VirtualHumanEventAdapter] Failed to parse message:",d.data,u)}},r.value.onerror=d=>{console.error("[VirtualHumanEventAdapter] WebSocket error:",d),s("error",d)},r.value.onclose=()=>{v.value=!1,console.log("[VirtualHumanEventAdapter] WebSocket disconnected")}}catch(l){console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:",l),s("error",l)}},H=l=>{const{type:d,payload:u,action:k}=l;switch(console.log("msgmsg",l),d){case"audio":const y=(u==null?void 0:u.data)||l.data;y&&S(y);break;case"dialog_event":l.event&&s(l.event,l.params);break;case"control":k&&h(k);break;case"highlight":s("highlight",u);break;case"showDialog":s("showDialog",u);break;case"end":s("end",u);break;case"pause":s("pause",u);break;default:console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${d}`)}};return e.watch(()=>n.screenClientId,()=>{n.screenClientId&&n.wsUrl&&b()}),e.watch(()=>n.wsUrl,()=>{n.screenClientId&&n.wsUrl&&b()}),e.onMounted(()=>{n.screenClientId&&n.wsUrl&&b()}),e.onUnmounted(()=>{r.value&&r.value.close(),o&&o.close()}),(l,d)=>e.renderSlot(l.$slots,"default")}}),L={install:c=>{c.component("VirtualHumanPersona",I),c.component("VirtualHumanEventAdapter",M)}};p.VirtualHumanEventAdapter=M,p.VirtualHumanPersona=I,p.default=L,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/package.json
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
class="virtual-human-container"
|
|
6
6
|
:class="{ 'is-dark': isDark }"
|
|
7
7
|
>
|
|
8
|
-
<div
|
|
8
|
+
<div
|
|
9
|
+
class="video-wrapper"
|
|
10
|
+
ref="wrapperRef"
|
|
11
|
+
:style="{ transform: `scale(${scale})`, transformOrigin: 'left bottom' }"
|
|
12
|
+
>
|
|
9
13
|
<video
|
|
10
14
|
ref="videoRef"
|
|
11
15
|
:src="videoSrc"
|
|
@@ -17,7 +21,33 @@
|
|
|
17
21
|
@pause="handlePause"
|
|
18
22
|
@ended="handleEnded"
|
|
19
23
|
></video>
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
<!-- 缩放控制点 - 四个角 -->
|
|
26
|
+
<div
|
|
27
|
+
v-if="visible"
|
|
28
|
+
class="resize-handle top-left"
|
|
29
|
+
@mousedown="startResize('top-left', $event)"
|
|
30
|
+
@touchstart="startResize('top-left', $event)"
|
|
31
|
+
></div>
|
|
32
|
+
<div
|
|
33
|
+
v-if="visible"
|
|
34
|
+
class="resize-handle top-right"
|
|
35
|
+
@mousedown="startResize('top-right', $event)"
|
|
36
|
+
@touchstart="startResize('top-right', $event)"
|
|
37
|
+
></div>
|
|
38
|
+
<div
|
|
39
|
+
v-if="visible"
|
|
40
|
+
class="resize-handle bottom-left"
|
|
41
|
+
@mousedown="startResize('bottom-left', $event)"
|
|
42
|
+
@touchstart="startResize('bottom-left', $event)"
|
|
43
|
+
></div>
|
|
44
|
+
<div
|
|
45
|
+
v-if="visible"
|
|
46
|
+
class="resize-handle bottom-right"
|
|
47
|
+
@mousedown="startResize('bottom-right', $event)"
|
|
48
|
+
@touchstart="startResize('bottom-right', $event)"
|
|
49
|
+
></div>
|
|
50
|
+
|
|
21
51
|
<!-- UI Overlay for controls or status -->
|
|
22
52
|
<div class="overlay">
|
|
23
53
|
<div v-if="!isPlaying" class="status-badge paused">
|
|
@@ -46,7 +76,7 @@ const props = defineProps({
|
|
|
46
76
|
// 是否可见
|
|
47
77
|
visible: {
|
|
48
78
|
type: Boolean,
|
|
49
|
-
default:
|
|
79
|
+
default: false,
|
|
50
80
|
},
|
|
51
81
|
// 是否自动播放
|
|
52
82
|
isPlaying: {
|
|
@@ -78,10 +108,19 @@ const props = defineProps({
|
|
|
78
108
|
const emit = defineEmits(['update:isPlaying', 'ended', 'update:visible']);
|
|
79
109
|
|
|
80
110
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
111
|
+
const wrapperRef = ref<HTMLDivElement | null>(null);
|
|
81
112
|
|
|
82
113
|
const ws = ref<WebSocket | null>(null);
|
|
83
114
|
const isConnected = ref(false);
|
|
84
115
|
|
|
116
|
+
// 缩放相关状态
|
|
117
|
+
const scale = ref(1);
|
|
118
|
+
const isResizing = ref(false);
|
|
119
|
+
const resizeHandle = ref<string | null>(null);
|
|
120
|
+
const startX = ref(0);
|
|
121
|
+
const startY = ref(0);
|
|
122
|
+
const startScale = ref(1);
|
|
123
|
+
|
|
85
124
|
const connectWebSocket = () => {
|
|
86
125
|
if (ws.value) {
|
|
87
126
|
ws.value.close();
|
|
@@ -129,7 +168,7 @@ const handleMessage = (msg: any) => {
|
|
|
129
168
|
emit('update:visible', true);
|
|
130
169
|
} else if (action === 'pause') {
|
|
131
170
|
emit('update:isPlaying', false);
|
|
132
|
-
|
|
171
|
+
// 暂停时不隐藏视频
|
|
133
172
|
} else if (action === 'stop') {
|
|
134
173
|
emit('update:isPlaying', false);
|
|
135
174
|
emit('update:visible', false);
|
|
@@ -179,6 +218,62 @@ const handleEnded = () => {
|
|
|
179
218
|
emit('ended');
|
|
180
219
|
};
|
|
181
220
|
|
|
221
|
+
// 缩放功能
|
|
222
|
+
const startResize = (handle: string, event: MouseEvent | TouchEvent) => {
|
|
223
|
+
isResizing.value = true;
|
|
224
|
+
resizeHandle.value = handle;
|
|
225
|
+
startScale.value = scale.value;
|
|
226
|
+
|
|
227
|
+
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
|
|
228
|
+
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
|
|
229
|
+
startX.value = clientX;
|
|
230
|
+
startY.value = clientY;
|
|
231
|
+
|
|
232
|
+
document.addEventListener('mousemove', handleResize);
|
|
233
|
+
document.addEventListener('mouseup', stopResize);
|
|
234
|
+
document.addEventListener('touchmove', handleResize);
|
|
235
|
+
document.addEventListener('touchend', stopResize);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const handleResize = (event: MouseEvent | TouchEvent) => {
|
|
239
|
+
if (!isResizing.value || !wrapperRef.value) return;
|
|
240
|
+
|
|
241
|
+
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
|
|
242
|
+
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
|
|
243
|
+
|
|
244
|
+
const deltaX = clientX - startX.value;
|
|
245
|
+
const deltaY = clientY - startY.value;
|
|
246
|
+
|
|
247
|
+
const rect = wrapperRef.value.getBoundingClientRect();
|
|
248
|
+
const minScale = 0.5;
|
|
249
|
+
const maxScale = 2;
|
|
250
|
+
|
|
251
|
+
// 根据拖动的角计算缩放比例
|
|
252
|
+
let newScale = startScale.value;
|
|
253
|
+
const baseSize = Math.min(rect.width, rect.height);
|
|
254
|
+
|
|
255
|
+
if (resizeHandle.value === 'bottom-right') {
|
|
256
|
+
newScale = startScale.value + (deltaX + deltaY) / baseSize;
|
|
257
|
+
} else if (resizeHandle.value === 'bottom-left') {
|
|
258
|
+
newScale = startScale.value + (-deltaX + deltaY) / baseSize;
|
|
259
|
+
} else if (resizeHandle.value === 'top-right') {
|
|
260
|
+
newScale = startScale.value + (deltaX - deltaY) / baseSize;
|
|
261
|
+
} else if (resizeHandle.value === 'top-left') {
|
|
262
|
+
newScale = startScale.value + (-deltaX - deltaY) / baseSize;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
scale.value = Math.max(minScale, Math.min(maxScale, newScale));
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const stopResize = () => {
|
|
269
|
+
isResizing.value = false;
|
|
270
|
+
resizeHandle.value = null;
|
|
271
|
+
document.removeEventListener('mousemove', handleResize);
|
|
272
|
+
document.removeEventListener('mouseup', stopResize);
|
|
273
|
+
document.removeEventListener('touchmove', handleResize);
|
|
274
|
+
document.removeEventListener('touchend', stopResize);
|
|
275
|
+
};
|
|
276
|
+
|
|
182
277
|
onMounted(() => {
|
|
183
278
|
if (props.isPlaying && videoRef.value) {
|
|
184
279
|
videoRef.value.play().catch(e => console.error('Video play failed:', e));
|
|
@@ -208,32 +303,78 @@ onUnmounted(() => {
|
|
|
208
303
|
}
|
|
209
304
|
|
|
210
305
|
.virtual-human-container {
|
|
306
|
+
position: fixed;
|
|
307
|
+
left: 16px;
|
|
308
|
+
bottom: 16px;
|
|
309
|
+
width: 400px;
|
|
310
|
+
height: 500px;
|
|
311
|
+
z-index: 2147483647;
|
|
312
|
+
overflow: visible;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.video-wrapper {
|
|
211
316
|
position: relative;
|
|
212
|
-
width:
|
|
213
|
-
|
|
317
|
+
width: 400px;
|
|
318
|
+
height: 500px;
|
|
214
319
|
border-radius: 1rem;
|
|
215
320
|
overflow: hidden;
|
|
216
321
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
217
322
|
background: #ffffff;
|
|
218
|
-
transition: background-color 0.3s ease;
|
|
323
|
+
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
|
219
324
|
}
|
|
220
325
|
|
|
221
|
-
.virtual-human-container.is-dark {
|
|
326
|
+
.virtual-human-container.is-dark .video-wrapper {
|
|
222
327
|
background: #1f2937;
|
|
223
328
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
|
224
329
|
}
|
|
225
330
|
|
|
226
|
-
.video-wrapper {
|
|
227
|
-
position: relative;
|
|
228
|
-
width: 100%;
|
|
229
|
-
aspect-ratio: 9 / 16; /* Portrait ratio for digital human */
|
|
230
|
-
background: #000;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
331
|
.persona-video {
|
|
234
332
|
width: 100%;
|
|
235
333
|
height: 100%;
|
|
236
334
|
object-fit: cover;
|
|
335
|
+
transform-origin: center;
|
|
336
|
+
transition: transform 0.1s ease-out;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.resize-handle {
|
|
340
|
+
position: absolute;
|
|
341
|
+
width: 20px;
|
|
342
|
+
height: 20px;
|
|
343
|
+
background: rgba(0, 0, 0, 0.5);
|
|
344
|
+
border: 2px solid rgba(255, 255, 255, 0.8);
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
z-index: 20;
|
|
348
|
+
transition: background 0.2s, border-color 0.2s;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.resize-handle:hover {
|
|
352
|
+
background: rgba(0, 0, 0, 0.7);
|
|
353
|
+
border-color: rgba(255, 255, 255, 1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.resize-handle.top-left {
|
|
357
|
+
top: -10px;
|
|
358
|
+
left: -10px;
|
|
359
|
+
cursor: nwse-resize;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.resize-handle.top-right {
|
|
363
|
+
top: -10px;
|
|
364
|
+
right: -10px;
|
|
365
|
+
cursor: nesw-resize;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.resize-handle.bottom-left {
|
|
369
|
+
bottom: -10px;
|
|
370
|
+
left: -10px;
|
|
371
|
+
cursor: nesw-resize;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.resize-handle.bottom-right {
|
|
375
|
+
bottom: -10px;
|
|
376
|
+
right: -10px;
|
|
377
|
+
cursor: nwse-resize;
|
|
237
378
|
}
|
|
238
379
|
|
|
239
380
|
.overlay {
|