virtual-human-cf 1.2.0 → 1.3.0

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.
@@ -1,5 +1,5 @@
1
- import { defineComponent as H, ref as g, watch as C, onMounted as E, onUnmounted as _, openBlock as A, createBlock as B, Transition as W, withCtx as $, createElementBlock as R, normalizeStyle as F, normalizeClass as N, createElementVNode as U, createCommentVNode as T, renderSlot as q } from "vue";
2
- const M = ["src", "muted"], z = /* @__PURE__ */ H({
1
+ import { defineComponent as $, ref as b, watch as C, onMounted as M, onUnmounted as R, openBlock as W, createBlock as J, Transition as L, withCtx as j, createElementBlock as G, normalizeStyle as K, normalizeClass as Q, createElementVNode as F, createCommentVNode as X, renderSlot as Y } from "vue";
2
+ const Z = ["src", "muted"], ee = /* @__PURE__ */ $({
3
3
  __name: "VirtualHumanPersona",
4
4
  props: {
5
5
  // 视频源URL
@@ -49,88 +49,88 @@ const M = ["src", "muted"], z = /* @__PURE__ */ H({
49
49
  }
50
50
  },
51
51
  emits: ["update:isPlaying", "ended", "update:visible"],
52
- setup(r, { emit: b }) {
53
- const e = r, n = b, a = g(null), h = g(null), t = g(null), d = g(!1), f = () => {
54
- if (t.value && t.value.close(), !(!e.wsUrl || !e.screenClientId))
52
+ setup(c, { emit: S }) {
53
+ const n = c, a = S, s = b(null), P = b(null), e = b(null), p = b(!1), d = () => {
54
+ if (e.value && e.value.close(), !(!n.wsUrl || !n.screenClientId))
55
55
  try {
56
- const i = new URL(e.wsUrl);
57
- i.searchParams.append("sessionId", e.screenClientId + "-persona"), t.value = new WebSocket(i.toString()), t.value.onopen = () => {
58
- d.value = !0, console.log(`[VirtualHumanPersona] Connected to ${e.wsUrl} for session ${e.screenClientId}-persona`);
59
- }, t.value.onmessage = (u) => {
56
+ const l = new URL(n.wsUrl);
57
+ l.searchParams.append("sessionId", n.screenClientId + "-persona"), e.value = new WebSocket(l.toString()), e.value.onopen = () => {
58
+ p.value = !0, console.log(`[VirtualHumanPersona] Connected to ${n.wsUrl} for session ${n.screenClientId}-persona`);
59
+ }, e.value.onmessage = (i) => {
60
60
  try {
61
- const c = JSON.parse(u.data);
62
- m(c);
63
- } catch (c) {
64
- console.error("[VirtualHumanPersona] Failed to parse message:", u.data, c);
61
+ const u = JSON.parse(i.data);
62
+ v(u);
63
+ } catch (u) {
64
+ console.error("[VirtualHumanPersona] Failed to parse message:", i.data, u);
65
65
  }
66
- }, t.value.onerror = (u) => {
67
- console.error("[VirtualHumanPersona] WebSocket error:", u);
68
- }, t.value.onclose = () => {
69
- d.value = !1, console.log("[VirtualHumanPersona] WebSocket disconnected");
66
+ }, e.value.onerror = (i) => {
67
+ console.error("[VirtualHumanPersona] WebSocket error:", i);
68
+ }, e.value.onclose = () => {
69
+ p.value = !1, console.log("[VirtualHumanPersona] WebSocket disconnected");
70
70
  };
71
- } catch (i) {
72
- console.error("[VirtualHumanPersona] Failed to initialize WebSocket:", i);
71
+ } catch (l) {
72
+ console.error("[VirtualHumanPersona] Failed to initialize WebSocket:", l);
73
73
  }
74
- }, m = (i) => {
75
- const { type: u, action: c } = i;
76
- u === "control" && (c === "play" || c === "resume" ? (n("update:isPlaying", !0), n("update:visible", !0)) : c === "pause" ? (n("update:isPlaying", !1), n("update:visible", !0)) : c === "stop" && (n("update:isPlaying", !1), n("update:visible", !1), a.value && (a.value.currentTime = 0)));
74
+ }, v = (l) => {
75
+ const { type: i, action: u } = l;
76
+ i === "control" && (u === "play" || u === "resume" ? (a("update:isPlaying", !0), a("update:visible", !0)) : u === "pause" ? (a("update:isPlaying", !1), a("update:visible", !0)) : u === "stop" && (a("update:isPlaying", !1), a("update:visible", !1), s.value && (s.value.currentTime = 0)));
77
77
  };
78
- C(() => e.screenClientId, () => {
79
- e.screenClientId && e.wsUrl && f();
80
- }), C(() => e.wsUrl, () => {
81
- e.screenClientId && e.wsUrl && f();
82
- }), C(() => e.isPlaying, (i) => {
83
- a.value && (i ? a.value.play().catch((u) => console.error("Video play failed:", u)) : a.value.pause());
78
+ C(() => n.screenClientId, () => {
79
+ n.screenClientId && n.wsUrl && d();
80
+ }), C(() => n.wsUrl, () => {
81
+ n.screenClientId && n.wsUrl && d();
82
+ }), C(() => n.isPlaying, (l) => {
83
+ s.value && (l ? s.value.play().catch((i) => console.error("Video play failed:", i)) : s.value.pause());
84
84
  });
85
- const v = () => {
86
- e.isPlaying || n("update:isPlaying", !0);
87
- }, S = () => {
88
- e.isPlaying && n("update:isPlaying", !1);
89
- }, k = () => {
90
- n("update:isPlaying", !1), n("ended");
85
+ const y = () => {
86
+ n.isPlaying || a("update:isPlaying", !0);
87
+ }, w = () => {
88
+ n.isPlaying && a("update:isPlaying", !1);
89
+ }, h = () => {
90
+ a("update:isPlaying", !1), a("ended");
91
91
  };
92
- return E(() => {
93
- e.isPlaying && a.value && a.value.play().catch((i) => console.error("Video play failed:", i)), e.screenClientId && e.wsUrl && f();
94
- }), _(() => {
95
- t.value && t.value.close();
96
- }), (i, u) => (A(), B(W, { name: "fade" }, {
97
- default: $(() => [
98
- r.visible ? (A(), R("div", {
92
+ return M(() => {
93
+ n.isPlaying && s.value && s.value.play().catch((l) => console.error("Video play failed:", l)), n.screenClientId && n.wsUrl && d();
94
+ }), R(() => {
95
+ e.value && e.value.close();
96
+ }), (l, i) => (W(), J(L, { name: "fade" }, {
97
+ default: j(() => [
98
+ c.visible ? (W(), G("div", {
99
99
  key: 0,
100
- class: N(["virtual-human-container", { "is-dark": r.isDark }]),
101
- style: F(r.styles)
100
+ class: Q(["virtual-human-container", { "is-dark": c.isDark }]),
101
+ style: K(c.styles)
102
102
  }, [
103
- U("div", {
103
+ F("div", {
104
104
  class: "video-wrapper",
105
105
  ref_key: "wrapperRef",
106
- ref: h
106
+ ref: P
107
107
  }, [
108
- U("video", {
108
+ F("video", {
109
109
  ref_key: "videoRef",
110
- ref: a,
111
- src: r.videoSrc,
110
+ ref: s,
111
+ src: c.videoSrc,
112
112
  class: "persona-video",
113
- muted: r.muted,
113
+ muted: c.muted,
114
114
  playsinline: "",
115
115
  loop: "",
116
116
  autoPlay: "",
117
117
  disablePictureInPicture: "",
118
- onPlay: v,
119
- onPause: S,
120
- onEnded: k
121
- }, null, 40, M)
118
+ onPlay: y,
119
+ onPause: w,
120
+ onEnded: h
121
+ }, null, 40, Z)
122
122
  ], 512)
123
- ], 6)) : T("", !0)
123
+ ], 6)) : X("", !0)
124
124
  ]),
125
125
  _: 1
126
126
  }));
127
127
  }
128
- }), D = (r, b) => {
129
- const e = r.__vccOpts || r;
130
- for (const [n, a] of b)
131
- e[n] = a;
132
- return e;
133
- }, O = /* @__PURE__ */ D(z, [["__scopeId", "data-v-bf23f155"]]), J = /* @__PURE__ */ H({
128
+ }), te = (c, S) => {
129
+ const n = c.__vccOpts || c;
130
+ for (const [a, s] of S)
131
+ n[a] = s;
132
+ return n;
133
+ }, ne = /* @__PURE__ */ te(ee, [["__scopeId", "data-v-bf23f155"]]), ae = /* @__PURE__ */ $({
134
134
  __name: "VirtualHumanEventAdapter",
135
135
  props: {
136
136
  // 屏幕客户端ID
@@ -145,114 +145,157 @@ const M = ["src", "muted"], z = /* @__PURE__ */ H({
145
145
  }
146
146
  },
147
147
  emits: ["eventNotifaction", "controlEvent", "end", "pause", "connected", "error", "playComplete"],
148
- setup(r, { emit: b }) {
149
- const e = r, n = b, a = g(null), h = g(!1);
150
- let t = null, d = 0, f = !1, m = 0, v = !1;
151
- const S = () => {
152
- t || (t = new (window.AudioContext || window.webkitAudioContext)({
148
+ setup(c, { emit: S }) {
149
+ const n = c, a = S, s = b(null), P = b(!1);
150
+ let e = null, p = 0, d = !1, v = 0, y = !1;
151
+ const w = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map();
152
+ let i = null;
153
+ const u = (t) => {
154
+ if (typeof t == "number" && Number.isFinite(t)) return t;
155
+ if (typeof t == "string" && t.trim() !== "") {
156
+ const o = Number(t);
157
+ if (Number.isFinite(o)) return o;
158
+ }
159
+ return null;
160
+ }, _ = () => {
161
+ i !== null && (window.clearInterval(i), i = null);
162
+ }, B = () => {
163
+ _(), w.clear(), h.clear(), l.clear();
164
+ }, N = () => {
165
+ for (const [t] of w)
166
+ if (!h.has(t) && l.has(t)) return !0;
167
+ return !1;
168
+ }, I = () => {
169
+ if (!(!e || e.state !== "running")) {
170
+ for (const [t, o] of l) {
171
+ if (h.has(t)) continue;
172
+ const r = w.get(t);
173
+ r && e.currentTime + 5e-3 >= o && (h.add(t), w.delete(t), a("eventNotifaction", r));
174
+ }
175
+ N() || _();
176
+ }
177
+ }, x = () => {
178
+ i === null && N() && (i = window.setInterval(() => {
179
+ I();
180
+ }, 30));
181
+ }, q = () => {
182
+ e || (e = new (window.AudioContext || window.webkitAudioContext)({
153
183
  sampleRate: 24e3
154
- })), t.state === "suspended" && !v && t.resume();
155
- }, k = () => {
156
- f && m === 0 && (n("playComplete", e.screenClientId), f = !1);
157
- }, i = (s) => {
158
- if (S(), !!t)
184
+ })), e.state === "suspended" && !y && e.resume();
185
+ }, T = () => {
186
+ d && v === 0 && (a("playComplete", n.screenClientId), d = !1);
187
+ }, z = (t, o) => {
188
+ if (q(), !!e)
159
189
  try {
160
- const o = window.atob(s), l = o.length, y = new Uint8Array(l);
161
- for (let p = 0; p < l; p++)
162
- y[p] = o.charCodeAt(p);
163
- const w = new Int16Array(y.buffer), V = new Float32Array(w.length);
164
- for (let p = 0; p < w.length; p++)
165
- V[p] = w[p] / 32768;
166
- const I = t.createBuffer(1, V.length, 24e3);
167
- I.getChannelData(0).set(V);
168
- const P = t.createBufferSource();
169
- P.buffer = I, P.connect(t.destination), d < t.currentTime && (d = t.currentTime), P.start(d), d += I.duration, m++, P.onended = () => {
170
- m--, k();
190
+ const r = window.atob(t), g = r.length, k = new Uint8Array(g);
191
+ for (let f = 0; f < g; f++)
192
+ k[f] = r.charCodeAt(f);
193
+ const m = new Int16Array(k.buffer), A = new Float32Array(m.length);
194
+ for (let f = 0; f < m.length; f++)
195
+ A[f] = m[f] / 32768;
196
+ const U = e.createBuffer(1, A.length, 24e3);
197
+ U.getChannelData(0).set(A);
198
+ const E = e.createBufferSource();
199
+ E.buffer = U, E.connect(e.destination);
200
+ const H = p < e.currentTime ? e.currentTime : p;
201
+ o !== null && !l.has(o) && (l.set(o, H), I(), x()), E.start(H), p = H + U.duration, v++, E.onended = () => {
202
+ v--, T();
171
203
  };
172
- } catch (o) {
173
- console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:", o);
204
+ } catch (r) {
205
+ console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:", r);
174
206
  }
175
- }, u = (s) => {
176
- switch (s) {
207
+ }, D = (t) => {
208
+ switch (t) {
177
209
  case "play":
178
- f = !1, m = 0, d = 0, v = !1, t && t.state === "suspended" && t.resume();
210
+ B(), d = !1, v = 0, p = 0, y = !1, e && e.state === "suspended" && e.resume();
179
211
  break;
180
212
  case "resume":
181
- v = !1, t && t.state === "suspended" && t.resume();
213
+ y = !1, e && e.state === "suspended" && e.resume(), I(), x();
182
214
  break;
183
215
  case "pause":
184
- v = !0, t && t.state === "running" && t.suspend();
216
+ y = !0, e && e.state === "running" && e.suspend();
185
217
  break;
186
218
  case "stop":
187
- v = !1, t && (t.close(), t = null), d = 0, f = !1, m = 0;
219
+ B(), y = !1, e && (e.close(), e = null), p = 0, d = !1, v = 0;
188
220
  break;
189
221
  case "tts_complete":
190
- f = !0, k();
222
+ d = !0, T();
191
223
  break;
192
224
  default:
193
- console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${s}`);
225
+ console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${t}`);
194
226
  }
195
- }, c = () => {
196
- a.value && a.value.close();
227
+ }, V = () => {
228
+ s.value && s.value.close();
197
229
  try {
198
- const s = new URL(e.wsUrl);
199
- s.searchParams.append("sessionId", e.screenClientId + "-event"), a.value = new WebSocket(s.toString()), a.value.onopen = () => {
200
- h.value = !0, n("connected"), console.log(`[VirtualHumanEventAdapter] Connected to ${e.wsUrl} for session ${e.screenClientId}-event`);
201
- }, a.value.onmessage = (o) => {
230
+ const t = new URL(n.wsUrl);
231
+ t.searchParams.append("sessionId", n.screenClientId + "-event"), s.value = new WebSocket(t.toString()), s.value.onopen = () => {
232
+ P.value = !0, a("connected"), console.log(`[VirtualHumanEventAdapter] Connected to ${n.wsUrl} for session ${n.screenClientId}-event`);
233
+ }, s.value.onmessage = (o) => {
202
234
  try {
203
- const l = JSON.parse(o.data);
204
- x(l);
205
- } catch (l) {
206
- console.error("[VirtualHumanEventAdapter] Failed to parse message:", o.data, l);
235
+ const r = JSON.parse(o.data);
236
+ O(r);
237
+ } catch (r) {
238
+ console.error("[VirtualHumanEventAdapter] Failed to parse message:", o.data, r);
207
239
  }
208
- }, a.value.onerror = (o) => {
209
- console.error("[VirtualHumanEventAdapter] WebSocket error:", o), n("error", o);
210
- }, a.value.onclose = () => {
211
- h.value = !1, console.log("[VirtualHumanEventAdapter] WebSocket disconnected");
240
+ }, s.value.onerror = (o) => {
241
+ console.error("[VirtualHumanEventAdapter] WebSocket error:", o), a("error", o);
242
+ }, s.value.onclose = () => {
243
+ P.value = !1, console.log("[VirtualHumanEventAdapter] WebSocket disconnected");
212
244
  };
213
- } catch (s) {
214
- console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:", s), n("error", s);
245
+ } catch (t) {
246
+ console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:", t), a("error", t);
215
247
  }
216
- }, x = (s) => {
217
- const { type: o, payload: l, action: y } = s;
248
+ }, O = (t) => {
249
+ const { type: o, payload: r, action: g } = t;
218
250
  switch (o) {
219
251
  case "audio":
220
- const w = (l == null ? void 0 : l.data) || s.data;
221
- w && i(w);
252
+ const k = (r == null ? void 0 : r.data) || t.data;
253
+ if (k) {
254
+ const m = u((r == null ? void 0 : r.index) ?? t.index);
255
+ z(k, m);
256
+ }
222
257
  break;
223
258
  case "send_event":
224
- s.event && (console.log("adapter send_event:", s), n("eventNotifaction", s));
259
+ if (t.event) {
260
+ console.log("adapter send_event:", t);
261
+ const m = u(t.index);
262
+ if (m === null) {
263
+ a("eventNotifaction", t);
264
+ break;
265
+ }
266
+ w.set(m, t), I(), x();
267
+ }
225
268
  break;
226
269
  case "control":
227
- y && (console.log("adapter control:", y), n("controlEvent", y), u(y));
270
+ g && (console.log("adapter control:", g), a("controlEvent", g), D(g));
228
271
  break;
229
272
  case "end":
230
- console.log("adapter end:", l), n("end", l);
273
+ console.log("adapter end:", r), a("end", r);
231
274
  break;
232
275
  case "pause":
233
- console.log("adapter pause:", l), n("pause", l);
276
+ console.log("adapter pause:", r), a("pause", r);
234
277
  break;
235
278
  default:
236
279
  console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${o}`);
237
280
  }
238
281
  };
239
- return C(() => e.screenClientId, () => {
240
- e.screenClientId && e.wsUrl && c();
241
- }), C(() => e.wsUrl, () => {
242
- e.screenClientId && e.wsUrl && c();
243
- }), E(() => {
244
- e.screenClientId && e.wsUrl && c();
245
- }), _(() => {
246
- a.value && a.value.close(), t && t.close();
247
- }), (s, o) => q(s.$slots, "default");
282
+ return C(() => n.screenClientId, () => {
283
+ n.screenClientId && n.wsUrl && V();
284
+ }), C(() => n.wsUrl, () => {
285
+ n.screenClientId && n.wsUrl && V();
286
+ }), M(() => {
287
+ n.screenClientId && n.wsUrl && V();
288
+ }), R(() => {
289
+ s.value && s.value.close(), e && e.close();
290
+ }), (t, o) => Y(t.$slots, "default");
248
291
  }
249
- }), L = (r) => {
250
- r.component("VirtualHumanPersona", O), r.component("VirtualHumanEventAdapter", J);
251
- }, G = {
252
- install: L
292
+ }), re = (c) => {
293
+ c.component("VirtualHumanPersona", ne), c.component("VirtualHumanEventAdapter", ae);
294
+ }, oe = {
295
+ install: re
253
296
  };
254
297
  export {
255
- J as VirtualHumanEventAdapter,
256
- O as VirtualHumanPersona,
257
- G as default
298
+ ae as VirtualHumanEventAdapter,
299
+ ne as VirtualHumanPersona,
300
+ oe as default
258
301
  };
@@ -1 +1 @@
1
- (function(f,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(f=typeof globalThis<"u"?globalThis:f||self,n(f.VirtualHumanCf={},f.Vue))})(this,function(f,n){"use strict";const H=["src","muted"],U=((r,b)=>{const e=r.__vccOpts||r;for(const[a,o]of b)e[a]=o;return e})(n.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},styles:{type:Object,default:()=>({width:"400px",height:"500px",left:"16px",bottom:"16px"})}},emits:["update:isPlaying","ended","update:visible"],setup(r,{emit:b}){const e=r,a=b,o=n.ref(null),C=n.ref(null),t=n.ref(null),p=n.ref(!1),m=()=>{if(t.value&&t.value.close(),!(!e.wsUrl||!e.screenClientId))try{const c=new URL(e.wsUrl);c.searchParams.append("sessionId",e.screenClientId+"-persona"),t.value=new WebSocket(c.toString()),t.value.onopen=()=>{p.value=!0,console.log(`[VirtualHumanPersona] Connected to ${e.wsUrl} for session ${e.screenClientId}-persona`)},t.value.onmessage=u=>{try{const d=JSON.parse(u.data);w(d)}catch(d){console.error("[VirtualHumanPersona] Failed to parse message:",u.data,d)}},t.value.onerror=u=>{console.error("[VirtualHumanPersona] WebSocket error:",u)},t.value.onclose=()=>{p.value=!1,console.log("[VirtualHumanPersona] WebSocket disconnected")}}catch(c){console.error("[VirtualHumanPersona] Failed to initialize WebSocket:",c)}},w=c=>{const{type:u,action:d}=c;u==="control"&&(d==="play"||d==="resume"?(a("update:isPlaying",!0),a("update:visible",!0)):d==="pause"?(a("update:isPlaying",!1),a("update:visible",!0)):d==="stop"&&(a("update:isPlaying",!1),a("update:visible",!1),o.value&&(o.value.currentTime=0)))};n.watch(()=>e.screenClientId,()=>{e.screenClientId&&e.wsUrl&&m()}),n.watch(()=>e.wsUrl,()=>{e.screenClientId&&e.wsUrl&&m()}),n.watch(()=>e.isPlaying,c=>{o.value&&(c?o.value.play().catch(u=>console.error("Video play failed:",u)):o.value.pause())});const g=()=>{e.isPlaying||a("update:isPlaying",!0)},S=()=>{e.isPlaying&&a("update:isPlaying",!1)},k=()=>{a("update:isPlaying",!1),a("ended")};return n.onMounted(()=>{e.isPlaying&&o.value&&o.value.play().catch(c=>console.error("Video play failed:",c)),e.screenClientId&&e.wsUrl&&m()}),n.onUnmounted(()=>{t.value&&t.value.close()}),(c,u)=>(n.openBlock(),n.createBlock(n.Transition,{name:"fade"},{default:n.withCtx(()=>[r.visible?(n.openBlock(),n.createElementBlock("div",{key:0,class:n.normalizeClass(["virtual-human-container",{"is-dark":r.isDark}]),style:n.normalizeStyle(r.styles)},[n.createElementVNode("div",{class:"video-wrapper",ref_key:"wrapperRef",ref:C},[n.createElementVNode("video",{ref_key:"videoRef",ref:o,src:r.videoSrc,class:"persona-video",muted:r.muted,playsinline:"",loop:"",autoPlay:"",disablePictureInPicture:"",onPlay:g,onPause:S,onEnded:k},null,40,H)],512)],6)):n.createCommentVNode("",!0)]),_:1}))}}),[["__scopeId","data-v-bf23f155"]]),A=n.defineComponent({__name:"VirtualHumanEventAdapter",props:{screenClientId:{type:String,required:!0},wsUrl:{type:String,required:!0}},emits:["eventNotifaction","controlEvent","end","pause","connected","error","playComplete"],setup(r,{emit:b}){const e=r,a=b,o=n.ref(null),C=n.ref(!1);let t=null,p=0,m=!1,w=0,g=!1;const S=()=>{t||(t=new(window.AudioContext||window.webkitAudioContext)({sampleRate:24e3})),t.state==="suspended"&&!g&&t.resume()},k=()=>{m&&w===0&&(a("playComplete",e.screenClientId),m=!1)},c=s=>{if(S(),!!t)try{const l=window.atob(s),i=l.length,v=new Uint8Array(i);for(let y=0;y<i;y++)v[y]=l.charCodeAt(y);const h=new Int16Array(v.buffer),V=new Float32Array(h.length);for(let y=0;y<h.length;y++)V[y]=h[y]/32768;const I=t.createBuffer(1,V.length,24e3);I.getChannelData(0).set(V);const P=t.createBufferSource();P.buffer=I,P.connect(t.destination),p<t.currentTime&&(p=t.currentTime),P.start(p),p+=I.duration,w++,P.onended=()=>{w--,k()}}catch(l){console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:",l)}},u=s=>{switch(s){case"play":m=!1,w=0,p=0,g=!1,t&&t.state==="suspended"&&t.resume();break;case"resume":g=!1,t&&t.state==="suspended"&&t.resume();break;case"pause":g=!0,t&&t.state==="running"&&t.suspend();break;case"stop":g=!1,t&&(t.close(),t=null),p=0,m=!1,w=0;break;case"tts_complete":m=!0,k();break;default:console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${s}`)}},d=()=>{o.value&&o.value.close();try{const s=new URL(e.wsUrl);s.searchParams.append("sessionId",e.screenClientId+"-event"),o.value=new WebSocket(s.toString()),o.value.onopen=()=>{C.value=!0,a("connected"),console.log(`[VirtualHumanEventAdapter] Connected to ${e.wsUrl} for session ${e.screenClientId}-event`)},o.value.onmessage=l=>{try{const i=JSON.parse(l.data);E(i)}catch(i){console.error("[VirtualHumanEventAdapter] Failed to parse message:",l.data,i)}},o.value.onerror=l=>{console.error("[VirtualHumanEventAdapter] WebSocket error:",l),a("error",l)},o.value.onclose=()=>{C.value=!1,console.log("[VirtualHumanEventAdapter] WebSocket disconnected")}}catch(s){console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:",s),a("error",s)}},E=s=>{const{type:l,payload:i,action:v}=s;switch(l){case"audio":const h=(i==null?void 0:i.data)||s.data;h&&c(h);break;case"send_event":s.event&&(console.log("adapter send_event:",s),a("eventNotifaction",s));break;case"control":v&&(console.log("adapter control:",v),a("controlEvent",v),u(v));break;case"end":console.log("adapter end:",i),a("end",i);break;case"pause":console.log("adapter pause:",i),a("pause",i);break;default:console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${l}`)}};return n.watch(()=>e.screenClientId,()=>{e.screenClientId&&e.wsUrl&&d()}),n.watch(()=>e.wsUrl,()=>{e.screenClientId&&e.wsUrl&&d()}),n.onMounted(()=>{e.screenClientId&&e.wsUrl&&d()}),n.onUnmounted(()=>{o.value&&o.value.close(),t&&t.close()}),(s,l)=>n.renderSlot(s.$slots,"default")}}),_={install:r=>{r.component("VirtualHumanPersona",U),r.component("VirtualHumanEventAdapter",A)}};f.VirtualHumanEventAdapter=A,f.VirtualHumanPersona=U,f.default=_,Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(f,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],a):(f=typeof globalThis<"u"?globalThis:f||self,a(f.VirtualHumanCf={},f.Vue))})(this,function(f,a){"use strict";const $=["src","muted"],_=((d,k)=>{const n=d.__vccOpts||d;for(const[r,s]of k)n[r]=s;return n})(a.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},styles:{type:Object,default:()=>({width:"400px",height:"500px",left:"16px",bottom:"16px"})}},emits:["update:isPlaying","ended","update:visible"],setup(d,{emit:k}){const n=d,r=k,s=a.ref(null),P=a.ref(null),e=a.ref(null),y=a.ref(!1),p=()=>{if(e.value&&e.value.close(),!(!n.wsUrl||!n.screenClientId))try{const i=new URL(n.wsUrl);i.searchParams.append("sessionId",n.screenClientId+"-persona"),e.value=new WebSocket(i.toString()),e.value.onopen=()=>{y.value=!0,console.log(`[VirtualHumanPersona] Connected to ${n.wsUrl} for session ${n.screenClientId}-persona`)},e.value.onmessage=c=>{try{const u=JSON.parse(c.data);h(u)}catch(u){console.error("[VirtualHumanPersona] Failed to parse message:",c.data,u)}},e.value.onerror=c=>{console.error("[VirtualHumanPersona] WebSocket error:",c)},e.value.onclose=()=>{y.value=!1,console.log("[VirtualHumanPersona] WebSocket disconnected")}}catch(i){console.error("[VirtualHumanPersona] Failed to initialize WebSocket:",i)}},h=i=>{const{type:c,action:u}=i;c==="control"&&(u==="play"||u==="resume"?(r("update:isPlaying",!0),r("update:visible",!0)):u==="pause"?(r("update:isPlaying",!1),r("update:visible",!0)):u==="stop"&&(r("update:isPlaying",!1),r("update:visible",!1),s.value&&(s.value.currentTime=0)))};a.watch(()=>n.screenClientId,()=>{n.screenClientId&&n.wsUrl&&p()}),a.watch(()=>n.wsUrl,()=>{n.screenClientId&&n.wsUrl&&p()}),a.watch(()=>n.isPlaying,i=>{s.value&&(i?s.value.play().catch(c=>console.error("Video play failed:",c)):s.value.pause())});const v=()=>{n.isPlaying||r("update:isPlaying",!0)},g=()=>{n.isPlaying&&r("update:isPlaying",!1)},S=()=>{r("update:isPlaying",!1),r("ended")};return a.onMounted(()=>{n.isPlaying&&s.value&&s.value.play().catch(i=>console.error("Video play failed:",i)),n.screenClientId&&n.wsUrl&&p()}),a.onUnmounted(()=>{e.value&&e.value.close()}),(i,c)=>(a.openBlock(),a.createBlock(a.Transition,{name:"fade"},{default:a.withCtx(()=>[d.visible?(a.openBlock(),a.createElementBlock("div",{key:0,class:a.normalizeClass(["virtual-human-container",{"is-dark":d.isDark}]),style:a.normalizeStyle(d.styles)},[a.createElementVNode("div",{class:"video-wrapper",ref_key:"wrapperRef",ref:P},[a.createElementVNode("video",{ref_key:"videoRef",ref:s,src:d.videoSrc,class:"persona-video",muted:d.muted,playsinline:"",loop:"",autoPlay:"",disablePictureInPicture:"",onPlay:v,onPause:g,onEnded:S},null,40,$)],512)],6)):a.createCommentVNode("",!0)]),_:1}))}}),[["__scopeId","data-v-bf23f155"]]),B=a.defineComponent({__name:"VirtualHumanEventAdapter",props:{screenClientId:{type:String,required:!0},wsUrl:{type:String,required:!0}},emits:["eventNotifaction","controlEvent","end","pause","connected","error","playComplete"],setup(d,{emit:k}){const n=d,r=k,s=a.ref(null),P=a.ref(!1);let e=null,y=0,p=!1,h=0,v=!1;const g=new Map,S=new Set,i=new Map;let c=null;const u=t=>{if(typeof t=="number"&&Number.isFinite(t))return t;if(typeof t=="string"&&t.trim()!==""){const l=Number(t);if(Number.isFinite(l))return l}return null},T=()=>{c!==null&&(window.clearInterval(c),c=null)},N=()=>{T(),g.clear(),S.clear(),i.clear()},M=()=>{for(const[t]of g)if(!S.has(t)&&i.has(t))return!0;return!1},I=()=>{if(!(!e||e.state!=="running")){for(const[t,l]of i){if(S.has(t))continue;const o=g.get(t);o&&e.currentTime+.005>=l&&(S.add(t),g.delete(t),r("eventNotifaction",o))}M()||T()}},E=()=>{c===null&&M()&&(c=window.setInterval(()=>{I()},30))},R=()=>{e||(e=new(window.AudioContext||window.webkitAudioContext)({sampleRate:24e3})),e.state==="suspended"&&!v&&e.resume()},W=()=>{p&&h===0&&(r("playComplete",n.screenClientId),p=!1)},q=(t,l)=>{if(R(),!!e)try{const o=window.atob(t),b=o.length,C=new Uint8Array(b);for(let m=0;m<b;m++)C[m]=o.charCodeAt(m);const w=new Int16Array(C.buffer),A=new Float32Array(w.length);for(let m=0;m<w.length;m++)A[m]=w[m]/32768;const U=e.createBuffer(1,A.length,24e3);U.getChannelData(0).set(A);const V=e.createBufferSource();V.buffer=U,V.connect(e.destination);const H=y<e.currentTime?e.currentTime:y;l!==null&&!i.has(l)&&(i.set(l,H),I(),E()),V.start(H),y=H+U.duration,h++,V.onended=()=>{h--,W()}}catch(o){console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:",o)}},z=t=>{switch(t){case"play":N(),p=!1,h=0,y=0,v=!1,e&&e.state==="suspended"&&e.resume();break;case"resume":v=!1,e&&e.state==="suspended"&&e.resume(),I(),E();break;case"pause":v=!0,e&&e.state==="running"&&e.suspend();break;case"stop":N(),v=!1,e&&(e.close(),e=null),y=0,p=!1,h=0;break;case"tts_complete":p=!0,W();break;default:console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${t}`)}},x=()=>{s.value&&s.value.close();try{const t=new URL(n.wsUrl);t.searchParams.append("sessionId",n.screenClientId+"-event"),s.value=new WebSocket(t.toString()),s.value.onopen=()=>{P.value=!0,r("connected"),console.log(`[VirtualHumanEventAdapter] Connected to ${n.wsUrl} for session ${n.screenClientId}-event`)},s.value.onmessage=l=>{try{const o=JSON.parse(l.data);O(o)}catch(o){console.error("[VirtualHumanEventAdapter] Failed to parse message:",l.data,o)}},s.value.onerror=l=>{console.error("[VirtualHumanEventAdapter] WebSocket error:",l),r("error",l)},s.value.onclose=()=>{P.value=!1,console.log("[VirtualHumanEventAdapter] WebSocket disconnected")}}catch(t){console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:",t),r("error",t)}},O=t=>{const{type:l,payload:o,action:b}=t;switch(l){case"audio":const C=(o==null?void 0:o.data)||t.data;if(C){const w=u((o==null?void 0:o.index)??t.index);q(C,w)}break;case"send_event":if(t.event){console.log("adapter send_event:",t);const w=u(t.index);if(w===null){r("eventNotifaction",t);break}g.set(w,t),I(),E()}break;case"control":b&&(console.log("adapter control:",b),r("controlEvent",b),z(b));break;case"end":console.log("adapter end:",o),r("end",o);break;case"pause":console.log("adapter pause:",o),r("pause",o);break;default:console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${l}`)}};return a.watch(()=>n.screenClientId,()=>{n.screenClientId&&n.wsUrl&&x()}),a.watch(()=>n.wsUrl,()=>{n.screenClientId&&n.wsUrl&&x()}),a.onMounted(()=>{n.screenClientId&&n.wsUrl&&x()}),a.onUnmounted(()=>{s.value&&s.value.close(),e&&e.close()}),(t,l)=>a.renderSlot(t.$slots,"default")}}),F={install:d=>{d.component("VirtualHumanPersona",_),d.component("VirtualHumanEventAdapter",B)}};f.VirtualHumanEventAdapter=B,f.VirtualHumanPersona=_,f.default=F,Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virtual-human-cf",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Vue3 Digital Human Component Package by cf ",
5
5
  "main": "dist/virtual-human-cf.umd.js",
6
6
  "module": "dist/virtual-human-cf.es.js",
@@ -29,6 +29,67 @@ let nextStartTime = 0;
29
29
  let isTtsComplete = false;
30
30
  let activeSources = 0;
31
31
  let isIntentionallyPaused = false;
32
+ const pendingEventByIndex = new Map<number, any>();
33
+ const firedEventIndexSet = new Set<number>();
34
+ const plannedStartTimeByIndex = new Map<number, number>();
35
+ let eventSchedulerTimer: number | null = null;
36
+
37
+ const normalizeIndex = (value: unknown): number | null => {
38
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
39
+ if (typeof value === 'string' && value.trim() !== '') {
40
+ const n = Number(value);
41
+ if (Number.isFinite(n)) return n;
42
+ }
43
+ return null;
44
+ };
45
+
46
+ const stopEventScheduler = () => {
47
+ if (eventSchedulerTimer !== null) {
48
+ window.clearInterval(eventSchedulerTimer);
49
+ eventSchedulerTimer = null;
50
+ }
51
+ };
52
+
53
+ const clearEventState = () => {
54
+ stopEventScheduler();
55
+ pendingEventByIndex.clear();
56
+ firedEventIndexSet.clear();
57
+ plannedStartTimeByIndex.clear();
58
+ };
59
+
60
+ const hasSchedulableEvent = () => {
61
+ for (const [index] of pendingEventByIndex) {
62
+ if (!firedEventIndexSet.has(index) && plannedStartTimeByIndex.has(index)) return true;
63
+ }
64
+ return false;
65
+ };
66
+
67
+ const tickEventScheduler = () => {
68
+ if (!audioContext || audioContext.state !== 'running') return;
69
+
70
+ for (const [index, plannedStartTime] of plannedStartTimeByIndex) {
71
+ if (firedEventIndexSet.has(index)) continue;
72
+ const pendingEvent = pendingEventByIndex.get(index);
73
+ if (!pendingEvent) continue;
74
+
75
+ if (audioContext.currentTime + 0.005 >= plannedStartTime) {
76
+ firedEventIndexSet.add(index);
77
+ pendingEventByIndex.delete(index);
78
+ emit('eventNotifaction', pendingEvent);
79
+ }
80
+ }
81
+
82
+ if (!hasSchedulableEvent()) stopEventScheduler();
83
+ };
84
+
85
+ const ensureEventScheduler = () => {
86
+ if (eventSchedulerTimer !== null) return;
87
+ if (!hasSchedulableEvent()) return;
88
+
89
+ eventSchedulerTimer = window.setInterval(() => {
90
+ tickEventScheduler();
91
+ }, 30);
92
+ };
32
93
 
33
94
  const initAudioContext = () => {
34
95
  if (!audioContext) {
@@ -48,7 +109,7 @@ const checkPlayComplete = () => {
48
109
  }
49
110
  };
50
111
 
51
- const handleAudioMessage = (base64Data: string) => {
112
+ const handleAudioMessage = (base64Data: string, index: number | null) => {
52
113
  initAudioContext();
53
114
  if (!audioContext) return;
54
115
 
@@ -75,11 +136,15 @@ const handleAudioMessage = (base64Data: string) => {
75
136
  source.connect(audioContext.destination);
76
137
 
77
138
  // Keep track of the start time for seamless playback
78
- if (nextStartTime < audioContext.currentTime) {
79
- nextStartTime = audioContext.currentTime;
139
+ const startAt = nextStartTime < audioContext.currentTime ? audioContext.currentTime : nextStartTime;
140
+ if (index !== null && !plannedStartTimeByIndex.has(index)) {
141
+ plannedStartTimeByIndex.set(index, startAt);
142
+ tickEventScheduler();
143
+ ensureEventScheduler();
80
144
  }
81
- source.start(nextStartTime);
82
- nextStartTime += audioBuffer.duration;
145
+
146
+ source.start(startAt);
147
+ nextStartTime = startAt + audioBuffer.duration;
83
148
 
84
149
  activeSources++;
85
150
  source.onended = () => {
@@ -94,6 +159,7 @@ const handleAudioMessage = (base64Data: string) => {
94
159
  const handleControlMessage = (action: string) => {
95
160
  switch (action) {
96
161
  case 'play':
162
+ clearEventState();
97
163
  isTtsComplete = false;
98
164
  activeSources = 0;
99
165
  nextStartTime = 0;
@@ -107,6 +173,8 @@ const handleControlMessage = (action: string) => {
107
173
  if (audioContext && audioContext.state === 'suspended') {
108
174
  audioContext.resume();
109
175
  }
176
+ tickEventScheduler();
177
+ ensureEventScheduler();
110
178
  break;
111
179
  case 'pause':
112
180
  isIntentionallyPaused = true;
@@ -115,6 +183,7 @@ const handleControlMessage = (action: string) => {
115
183
  }
116
184
  break;
117
185
  case 'stop':
186
+ clearEventState();
118
187
  isIntentionallyPaused = false;
119
188
  if (audioContext) {
120
189
  audioContext.close();
@@ -181,14 +250,23 @@ const handleMessage = (msg: any) => {
181
250
  case 'audio':
182
251
  const base64Data = payload?.data || msg.data;
183
252
  if (base64Data) {
184
- handleAudioMessage(base64Data);
253
+ const index = normalizeIndex(payload?.index ?? msg.index);
254
+ handleAudioMessage(base64Data, index);
185
255
  }
186
256
  break;
187
257
  // 接收事件通知
188
258
  case 'send_event':
189
259
  if (msg.event) {
190
260
  console.log("adapter send_event:",msg)
191
- emit('eventNotifaction', msg);
261
+ const index = normalizeIndex(msg.index);
262
+ if (index === null) {
263
+ emit('eventNotifaction', msg);
264
+ break;
265
+ }
266
+
267
+ pendingEventByIndex.set(index, msg);
268
+ tickEventScheduler();
269
+ ensureEventScheduler();
192
270
  }
193
271
  break;
194
272
  // 控制指令接口