virtual-human-cf 1.0.7 → 1.0.11
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/README.md +17 -3
- package/dist/components/VirtualHumanPersona.vue.d.ts +19 -0
- package/dist/style.css +1 -1
- package/dist/virtual-human-cf.es.js +139 -186
- package/dist/virtual-human-cf.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/VirtualHumanEventAdapter.vue +5 -1
- package/src/components/VirtualHumanPersona.vue +12 -151
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
- 暗黑模式支持
|
|
12
12
|
- 响应式设计
|
|
13
13
|
|
|
14
|
+
## 更新日志
|
|
15
|
+
|
|
16
|
+
### 1.0.10
|
|
17
|
+
- 新增VirtualHumanPersona组件styles属性,用于控制视频容器 宽高 绝对定位位置等
|
|
18
|
+
|
|
14
19
|
## 安装
|
|
15
20
|
|
|
16
21
|
```bash
|
|
@@ -53,6 +58,7 @@ import { VirtualHumanPersona, VirtualHumanEventAdapter } from 'virtual-human-cf'
|
|
|
53
58
|
| isDark | Boolean | false | 否 | 是否暗黑模式 |
|
|
54
59
|
| screenClientId | String | - | 否 | 屏幕客户端 ID |
|
|
55
60
|
| wsUrl | String | - | 否 | WebSocket 连接地址 |
|
|
61
|
+
| styles | Object | - | 否 | 组件样式,包含 width、height、left、bottom 等属性 |
|
|
56
62
|
|
|
57
63
|
#### Events
|
|
58
64
|
|
|
@@ -73,6 +79,7 @@ import { VirtualHumanPersona, VirtualHumanEventAdapter } from 'virtual-human-cf'
|
|
|
73
79
|
:is-dark="false"
|
|
74
80
|
:screen-client-id="clientId"
|
|
75
81
|
:ws-url="websocketUrl"
|
|
82
|
+
:styles="styles"
|
|
76
83
|
@update:isPlaying="onPlayingChange"
|
|
77
84
|
@ended="onEnded"
|
|
78
85
|
/>
|
|
@@ -86,7 +93,12 @@ const videoUrl = ref('https://example.com/video.mp4');
|
|
|
86
93
|
const playing = ref(false);
|
|
87
94
|
const clientId = ref('screen-123');
|
|
88
95
|
const websocketUrl = ref('ws://localhost:8080/ws');
|
|
89
|
-
|
|
96
|
+
const styles = ref({
|
|
97
|
+
width:'400px',
|
|
98
|
+
height:'500px',
|
|
99
|
+
left: '16px',
|
|
100
|
+
bottom:'16px'
|
|
101
|
+
})
|
|
90
102
|
const onPlayingChange = (isPlaying) => {
|
|
91
103
|
console.log('播放状态改变:', isPlaying);
|
|
92
104
|
};
|
|
@@ -119,8 +131,9 @@ const onEnded = () => {
|
|
|
119
131
|
| error | 错误事件 | 错误信息 |
|
|
120
132
|
| playComplete | 播放完成,数字人对话结束,关闭数字人 | - |
|
|
121
133
|
|
|
122
|
-
```
|
|
123
|
-
eventNotifaction
|
|
134
|
+
```js
|
|
135
|
+
// eventNotifaction 载荷数据
|
|
136
|
+
{
|
|
124
137
|
|
|
125
138
|
"type": "send_event",
|
|
126
139
|
// 自定义事件名称
|
|
@@ -192,6 +205,7 @@ const onError = (error) => {
|
|
|
192
205
|
:is-dark="isDarkMode"
|
|
193
206
|
:screen-client-id="activeScreenClientId"
|
|
194
207
|
:ws-url="websocketUrl"
|
|
208
|
+
:styles="styles"
|
|
195
209
|
@update:isPlaying="onPersonaPlayingUpdate"
|
|
196
210
|
@update:visible="onPersonaVisibleUpdate"
|
|
197
211
|
@ended="onVideoEnded"
|
|
@@ -27,6 +27,15 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
27
27
|
type: StringConstructor;
|
|
28
28
|
required: false;
|
|
29
29
|
};
|
|
30
|
+
styles: {
|
|
31
|
+
type: ObjectConstructor;
|
|
32
|
+
default: () => {
|
|
33
|
+
width: string;
|
|
34
|
+
height: string;
|
|
35
|
+
left: string;
|
|
36
|
+
bottom: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
30
39
|
}>, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
31
40
|
ended: (...args: any[]) => void;
|
|
32
41
|
"update:isPlaying": (...args: any[]) => void;
|
|
@@ -60,6 +69,15 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
60
69
|
type: StringConstructor;
|
|
61
70
|
required: false;
|
|
62
71
|
};
|
|
72
|
+
styles: {
|
|
73
|
+
type: ObjectConstructor;
|
|
74
|
+
default: () => {
|
|
75
|
+
width: string;
|
|
76
|
+
height: string;
|
|
77
|
+
left: string;
|
|
78
|
+
bottom: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
63
81
|
}>> & Readonly<{
|
|
64
82
|
onEnded?: ((...args: any[]) => any) | undefined;
|
|
65
83
|
"onUpdate:isPlaying"?: ((...args: any[]) => any) | undefined;
|
|
@@ -69,5 +87,6 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
69
87
|
isPlaying: boolean;
|
|
70
88
|
muted: boolean;
|
|
71
89
|
isDark: boolean;
|
|
90
|
+
styles: Record<string, any>;
|
|
72
91
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
73
92
|
export default _default;
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.fade-enter-active[data-v-
|
|
1
|
+
.fade-enter-active[data-v-7325596e],.fade-leave-active[data-v-7325596e]{transition:opacity .5s ease,transform .5s ease}.fade-enter-from[data-v-7325596e],.fade-leave-to[data-v-7325596e]{opacity:0;transform:translateY(10px)}.virtual-human-container[data-v-7325596e]{position:fixed;z-index:2147483647;overflow:visible}.video-wrapper[data-v-7325596e]{position:relative;width:100%;height:100%;border-radius:1rem;overflow:hidden;box-shadow:0 10px 25px #0000001a;background:#fff;border:2px solid transparent;transition:background-color .3s ease,box-shadow .3s ease,border-color .3s ease}.video-wrapper[data-v-7325596e]:hover{border-color:#409eff}.virtual-human-container.is-dark .video-wrapper[data-v-7325596e]{background:#1f2937;box-shadow:0 10px 25px #00000080}.persona-video[data-v-7325596e]{width:100%;height:100%;object-fit:cover;transform-origin:center;transition:transform .1s ease-out}.resize-handle[data-v-7325596e]{position:absolute;width:20px;height:20px;background:#00000080;border:2px solid rgba(255,255,255,.8);border-radius:4px;cursor:pointer;z-index:20;opacity:0;pointer-events:none;transition:opacity .3s ease,background .2s,border-color .2s}.video-wrapper:hover .resize-handle[data-v-7325596e]{opacity:1;pointer-events:auto}.resize-handle[data-v-7325596e]:hover{background:#000000b3;border-color:#fff}.resize-handle.top-left[data-v-7325596e]{top:10px;left:10px;cursor:nwse-resize}.resize-handle.top-right[data-v-7325596e]{top:10px;right:10px;cursor:nesw-resize}.resize-handle.bottom-left[data-v-7325596e]{bottom:10px;left:10px;cursor:nesw-resize}.resize-handle.bottom-right[data-v-7325596e]{bottom:10px;right:10px;cursor:nwse-resize}.overlay[data-v-7325596e]{position:absolute;top:1rem;right:1rem;z-index:10}.status-badge[data-v-7325596e]{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-7325596e]{background:#ef4444cc}.status-badge.playing[data-v-7325596e]{background:#22c55ecc}.dot[data-v-7325596e]{width:6px;height:6px;border-radius:50%;background-color:#fff}@keyframes pulse-7325596e{0%,to{opacity:1}50%{opacity:.5}}.animate-pulse[data-v-7325596e]{animation:pulse-7325596e 2s cubic-bezier(.4,0,.6,1) infinite}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
const
|
|
3
|
-
key: 0,
|
|
4
|
-
class: "status-badge paused"
|
|
5
|
-
}, oe = {
|
|
6
|
-
key: 1,
|
|
7
|
-
class: "status-badge playing"
|
|
8
|
-
}, re = /* @__PURE__ */ j({
|
|
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({
|
|
9
3
|
__name: "VirtualHumanPersona",
|
|
10
4
|
props: {
|
|
11
5
|
// 视频源URL
|
|
@@ -43,141 +37,100 @@ const ae = ["src", "muted"], se = { class: "overlay" }, le = {
|
|
|
43
37
|
wsUrl: {
|
|
44
38
|
type: String,
|
|
45
39
|
required: !1
|
|
40
|
+
},
|
|
41
|
+
styles: {
|
|
42
|
+
type: Object,
|
|
43
|
+
default: () => ({
|
|
44
|
+
width: "400px",
|
|
45
|
+
height: "500px",
|
|
46
|
+
left: "16px",
|
|
47
|
+
bottom: "16px"
|
|
48
|
+
})
|
|
46
49
|
}
|
|
47
50
|
},
|
|
48
51
|
emits: ["update:isPlaying", "ended", "update:visible"],
|
|
49
|
-
setup(
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
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
55
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
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) => {
|
|
57
60
|
try {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
} catch (
|
|
61
|
-
console.error("[VirtualHumanPersona] Failed to parse message:",
|
|
61
|
+
const c = JSON.parse(u.data);
|
|
62
|
+
m(c);
|
|
63
|
+
} catch (c) {
|
|
64
|
+
console.error("[VirtualHumanPersona] Failed to parse message:", u.data, c);
|
|
62
65
|
}
|
|
63
|
-
},
|
|
64
|
-
console.error("[VirtualHumanPersona] WebSocket error:",
|
|
65
|
-
},
|
|
66
|
-
|
|
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");
|
|
67
70
|
};
|
|
68
|
-
} catch (
|
|
69
|
-
console.error("[VirtualHumanPersona] Failed to initialize WebSocket:",
|
|
71
|
+
} catch (i) {
|
|
72
|
+
console.error("[VirtualHumanPersona] Failed to initialize WebSocket:", i);
|
|
70
73
|
}
|
|
71
|
-
},
|
|
72
|
-
const { type:
|
|
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
77
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
|
|
79
|
-
}),
|
|
80
|
-
|
|
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());
|
|
81
84
|
});
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
}, d = (o, e) => {
|
|
89
|
-
e.preventDefault(), I.value = !0, h.value = o;
|
|
90
|
-
const a = "clientX" in e ? e.clientX : e.touches[0].clientX, P = "clientY" in e ? e.clientY : e.touches[0].clientY;
|
|
91
|
-
M.value = a, A.value = P, k.value = y.value, s.value = g.value, l.value = w.value, u.value = z.value, document.addEventListener("mousemove", $), document.addEventListener("mouseup", N), document.addEventListener("touchmove", $, { passive: !1 }), document.addEventListener("touchend", N);
|
|
92
|
-
}, $ = (o) => {
|
|
93
|
-
if (!I.value) return;
|
|
94
|
-
o.preventDefault();
|
|
95
|
-
const e = "clientX" in o ? o.clientX : o.touches[0].clientX, a = "clientY" in o ? o.clientY : o.touches[0].clientY, P = e - M.value, x = a - A.value, D = 200, F = 250, q = 800, O = 1e3;
|
|
96
|
-
let f = k.value, m = s.value, H = l.value, V = u.value;
|
|
97
|
-
h.value === "bottom-right" ? (f = k.value + P, V = u.value - x, m = s.value + x) : h.value === "bottom-left" ? (H = l.value + P, f = k.value - P, V = u.value - x, m = s.value + x) : h.value === "top-right" ? (f = k.value + P, m = s.value - x) : h.value === "top-left" && (H = l.value + P, f = k.value - P, m = s.value - x), f < D && (H !== l.value && (H -= D - f), f = D), m < F && (V !== u.value && (V -= F - m), m = F), f > q && (H !== l.value && (H += f - q), f = q), m > O && (V !== u.value && (V += m - O), m = O), y.value = f, g.value = m, w.value = H, z.value = V;
|
|
98
|
-
}, N = () => {
|
|
99
|
-
I.value = !1, h.value = null, document.removeEventListener("mousemove", $), document.removeEventListener("mouseup", N), document.removeEventListener("touchmove", $), document.removeEventListener("touchend", N);
|
|
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");
|
|
100
91
|
};
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
}),
|
|
104
|
-
|
|
105
|
-
}), (
|
|
106
|
-
default:
|
|
107
|
-
|
|
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", {
|
|
108
99
|
key: 0,
|
|
109
|
-
class:
|
|
110
|
-
style:
|
|
111
|
-
width: y.value + "px",
|
|
112
|
-
height: g.value + "px",
|
|
113
|
-
left: w.value + "px",
|
|
114
|
-
bottom: z.value + "px"
|
|
115
|
-
})
|
|
100
|
+
class: N(["virtual-human-container", { "is-dark": r.isDark }]),
|
|
101
|
+
style: F(r.styles)
|
|
116
102
|
}, [
|
|
117
|
-
|
|
103
|
+
U("div", {
|
|
118
104
|
class: "video-wrapper",
|
|
119
105
|
ref_key: "wrapperRef",
|
|
120
|
-
ref:
|
|
106
|
+
ref: h
|
|
121
107
|
}, [
|
|
122
|
-
|
|
108
|
+
U("video", {
|
|
123
109
|
ref_key: "videoRef",
|
|
124
|
-
ref:
|
|
125
|
-
src:
|
|
110
|
+
ref: a,
|
|
111
|
+
src: r.videoSrc,
|
|
126
112
|
class: "persona-video",
|
|
127
|
-
muted:
|
|
113
|
+
muted: r.muted,
|
|
128
114
|
playsinline: "",
|
|
129
115
|
loop: "",
|
|
130
116
|
autoPlay: "",
|
|
131
|
-
disablePictureInPicture:
|
|
132
|
-
onPlay:
|
|
133
|
-
onPause:
|
|
134
|
-
onEnded:
|
|
135
|
-
}, null, 40,
|
|
136
|
-
c.visible ? (S(), E("div", {
|
|
137
|
-
key: 0,
|
|
138
|
-
class: "resize-handle top-left",
|
|
139
|
-
onMousedown: e[0] || (e[0] = (a) => d("top-left", a)),
|
|
140
|
-
onTouchstart: e[1] || (e[1] = (a) => d("top-left", a))
|
|
141
|
-
}, null, 32)) : T("", !0),
|
|
142
|
-
c.visible ? (S(), E("div", {
|
|
143
|
-
key: 1,
|
|
144
|
-
class: "resize-handle top-right",
|
|
145
|
-
onMousedown: e[2] || (e[2] = (a) => d("top-right", a)),
|
|
146
|
-
onTouchstart: e[3] || (e[3] = (a) => d("top-right", a))
|
|
147
|
-
}, null, 32)) : T("", !0),
|
|
148
|
-
c.visible ? (S(), E("div", {
|
|
149
|
-
key: 2,
|
|
150
|
-
class: "resize-handle bottom-left",
|
|
151
|
-
onMousedown: e[4] || (e[4] = (a) => d("bottom-left", a)),
|
|
152
|
-
onTouchstart: e[5] || (e[5] = (a) => d("bottom-left", a))
|
|
153
|
-
}, null, 32)) : T("", !0),
|
|
154
|
-
c.visible ? (S(), E("div", {
|
|
155
|
-
key: 3,
|
|
156
|
-
class: "resize-handle bottom-right",
|
|
157
|
-
onMousedown: e[6] || (e[6] = (a) => d("bottom-right", a)),
|
|
158
|
-
onTouchstart: e[7] || (e[7] = (a) => d("bottom-right", a))
|
|
159
|
-
}, null, 32)) : T("", !0),
|
|
160
|
-
R("div", se, [
|
|
161
|
-
c.isPlaying ? (S(), E("div", oe, [...e[9] || (e[9] = [
|
|
162
|
-
R("span", { class: "dot animate-pulse" }, null, -1),
|
|
163
|
-
J(" 播放中 ", -1)
|
|
164
|
-
])])) : (S(), E("div", le, [...e[8] || (e[8] = [
|
|
165
|
-
R("span", { class: "dot" }, null, -1),
|
|
166
|
-
J(" 暂停中 ", -1)
|
|
167
|
-
])]))
|
|
168
|
-
])
|
|
117
|
+
disablePictureInPicture: !1,
|
|
118
|
+
onPlay: v,
|
|
119
|
+
onPause: S,
|
|
120
|
+
onEnded: k
|
|
121
|
+
}, null, 40, M)
|
|
169
122
|
], 512)
|
|
170
123
|
], 6)) : T("", !0)
|
|
171
124
|
]),
|
|
172
125
|
_: 1
|
|
173
126
|
}));
|
|
174
127
|
}
|
|
175
|
-
}),
|
|
176
|
-
const
|
|
177
|
-
for (const [
|
|
178
|
-
|
|
179
|
-
return
|
|
180
|
-
},
|
|
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-7325596e"]]), J = /* @__PURE__ */ H({
|
|
181
134
|
__name: "VirtualHumanEventAdapter",
|
|
182
135
|
props: {
|
|
183
136
|
// 屏幕客户端ID
|
|
@@ -192,114 +145,114 @@ const ae = ["src", "muted"], se = { class: "overlay" }, le = {
|
|
|
192
145
|
}
|
|
193
146
|
},
|
|
194
147
|
emits: ["eventNotifaction", "end", "pause", "connected", "error", "playComplete"],
|
|
195
|
-
setup(
|
|
196
|
-
const
|
|
197
|
-
let
|
|
198
|
-
const
|
|
199
|
-
|
|
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)({
|
|
200
153
|
sampleRate: 24e3
|
|
201
|
-
})),
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
},
|
|
205
|
-
if (
|
|
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)
|
|
206
159
|
try {
|
|
207
|
-
const
|
|
208
|
-
for (let
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
for (let
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
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();
|
|
218
171
|
};
|
|
219
|
-
} catch (
|
|
220
|
-
console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:",
|
|
172
|
+
} catch (o) {
|
|
173
|
+
console.error("[VirtualHumanEventAdapter] Failed to decode and play audio:", o);
|
|
221
174
|
}
|
|
222
|
-
},
|
|
175
|
+
}, u = (s) => {
|
|
223
176
|
switch (s) {
|
|
224
177
|
case "play":
|
|
225
|
-
|
|
178
|
+
f = !1, m = 0, d = 0, v = !1, t && t.state === "suspended" && t.resume();
|
|
226
179
|
break;
|
|
227
180
|
case "resume":
|
|
228
|
-
|
|
181
|
+
v = !1, t && t.state === "suspended" && t.resume();
|
|
229
182
|
break;
|
|
230
183
|
case "pause":
|
|
231
|
-
|
|
184
|
+
v = !0, t && t.state === "running" && t.suspend();
|
|
232
185
|
break;
|
|
233
186
|
case "stop":
|
|
234
|
-
|
|
187
|
+
v = !1, t && (t.close(), t = null), d = 0, f = !1, m = 0;
|
|
235
188
|
break;
|
|
236
189
|
case "tts_complete":
|
|
237
|
-
|
|
190
|
+
f = !0, k();
|
|
238
191
|
break;
|
|
239
192
|
default:
|
|
240
193
|
console.warn(`[VirtualHumanEventAdapter] Unknown control action: ${s}`);
|
|
241
194
|
}
|
|
242
|
-
},
|
|
243
|
-
|
|
195
|
+
}, c = () => {
|
|
196
|
+
a.value && a.value.close();
|
|
244
197
|
try {
|
|
245
|
-
const s = new URL(
|
|
246
|
-
s.searchParams.append("sessionId",
|
|
247
|
-
|
|
248
|
-
},
|
|
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) => {
|
|
249
202
|
try {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
} catch (
|
|
253
|
-
console.error("[VirtualHumanEventAdapter] Failed to parse message:",
|
|
203
|
+
const l = JSON.parse(o.data);
|
|
204
|
+
x(l);
|
|
205
|
+
} catch (l) {
|
|
206
|
+
console.error("[VirtualHumanEventAdapter] Failed to parse message:", o.data, l);
|
|
254
207
|
}
|
|
255
|
-
},
|
|
256
|
-
console.error("[VirtualHumanEventAdapter] WebSocket error:",
|
|
257
|
-
},
|
|
258
|
-
|
|
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");
|
|
259
212
|
};
|
|
260
213
|
} catch (s) {
|
|
261
|
-
console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:", s),
|
|
214
|
+
console.error("[VirtualHumanEventAdapter] Failed to initialize WebSocket:", s), n("error", s);
|
|
262
215
|
}
|
|
263
|
-
},
|
|
264
|
-
const { type:
|
|
265
|
-
switch (
|
|
216
|
+
}, x = (s) => {
|
|
217
|
+
const { type: o, payload: l, action: y } = s;
|
|
218
|
+
switch (o) {
|
|
266
219
|
case "audio":
|
|
267
|
-
const
|
|
268
|
-
|
|
220
|
+
const w = (l == null ? void 0 : l.data) || s.data;
|
|
221
|
+
w && i(w);
|
|
269
222
|
break;
|
|
270
223
|
case "send_event":
|
|
271
|
-
s.event &&
|
|
224
|
+
s.event && (console.log("adapter send_event:", s), n("eventNotifaction", s));
|
|
272
225
|
break;
|
|
273
226
|
case "control":
|
|
274
|
-
|
|
227
|
+
y && (console.log("adapter control:", y), u(y));
|
|
275
228
|
break;
|
|
276
229
|
case "end":
|
|
277
|
-
|
|
230
|
+
console.log("adapter end:", l), n("end", l);
|
|
278
231
|
break;
|
|
279
232
|
case "pause":
|
|
280
|
-
|
|
233
|
+
console.log("adapter pause:", l), n("pause", l);
|
|
281
234
|
break;
|
|
282
235
|
default:
|
|
283
|
-
console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${
|
|
236
|
+
console.warn(`[VirtualHumanEventAdapter] Unknown message type: ${o}`);
|
|
284
237
|
}
|
|
285
238
|
};
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
}),
|
|
289
|
-
|
|
290
|
-
}),
|
|
291
|
-
|
|
292
|
-
}),
|
|
293
|
-
|
|
294
|
-
}), (s,
|
|
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");
|
|
295
248
|
}
|
|
296
|
-
}),
|
|
297
|
-
|
|
298
|
-
},
|
|
299
|
-
install:
|
|
249
|
+
}), L = (r) => {
|
|
250
|
+
r.component("VirtualHumanPersona", O), r.component("VirtualHumanEventAdapter", J);
|
|
251
|
+
}, G = {
|
|
252
|
+
install: L
|
|
300
253
|
};
|
|
301
254
|
export {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
255
|
+
J as VirtualHumanEventAdapter,
|
|
256
|
+
O as VirtualHumanPersona,
|
|
257
|
+
G as default
|
|
305
258
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
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:!1,onPlay:g,onPause:S,onEnded:k},null,40,H)],512)],6)):n.createCommentVNode("",!0)]),_:1}))}}),[["__scopeId","data-v-7325596e"]]),A=n.defineComponent({__name:"VirtualHumanEventAdapter",props:{screenClientId:{type:String,required:!0},wsUrl:{type:String,required:!0}},emits:["eventNotifaction","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,h=new Uint8Array(i);for(let y=0;y<i;y++)h[y]=l.charCodeAt(y);const v=new Int16Array(h.buffer),V=new Float32Array(v.length);for(let y=0;y<v.length;y++)V[y]=v[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:h}=s;switch(l){case"audio":const v=(i==null?void 0:i.data)||s.data;v&&c(v);break;case"send_event":s.event&&(console.log("adapter send_event:",s),a("eventNotifaction",s));break;case"control":h&&(console.log("adapter control:",h),u(h));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"}})});
|
package/package.json
CHANGED
|
@@ -175,7 +175,7 @@ const connectWebSocket = () => {
|
|
|
175
175
|
|
|
176
176
|
const handleMessage = (msg: any) => {
|
|
177
177
|
const { type, payload, action } = msg;
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
switch (type) {
|
|
180
180
|
// 接收音频
|
|
181
181
|
case 'audio':
|
|
@@ -187,21 +187,25 @@ const handleMessage = (msg: any) => {
|
|
|
187
187
|
// 接收事件通知
|
|
188
188
|
case 'send_event':
|
|
189
189
|
if (msg.event) {
|
|
190
|
+
console.log("adapter send_event:",msg)
|
|
190
191
|
emit('eventNotifaction', msg);
|
|
191
192
|
}
|
|
192
193
|
break;
|
|
193
194
|
// 控制指令接口
|
|
194
195
|
case 'control':
|
|
195
196
|
if (action) {
|
|
197
|
+
console.log("adapter control:",action)
|
|
196
198
|
handleControlMessage(action);
|
|
197
199
|
}
|
|
198
200
|
break;
|
|
199
201
|
case 'end':
|
|
200
202
|
// 触发数字人对话结束事件
|
|
203
|
+
console.log("adapter end:",payload)
|
|
201
204
|
emit('end', payload);
|
|
202
205
|
break;
|
|
203
206
|
case 'pause':
|
|
204
207
|
// 触发暂停播放事件
|
|
208
|
+
console.log("adapter pause:",payload)
|
|
205
209
|
emit('pause', payload);
|
|
206
210
|
break;
|
|
207
211
|
default:
|
|
@@ -4,12 +4,7 @@
|
|
|
4
4
|
v-if="visible"
|
|
5
5
|
class="virtual-human-container"
|
|
6
6
|
:class="{ 'is-dark': isDark }"
|
|
7
|
-
:style="
|
|
8
|
-
width: containerWidth + 'px',
|
|
9
|
-
height: containerHeight + 'px',
|
|
10
|
-
left: containerLeft + 'px',
|
|
11
|
-
bottom: containerBottom + 'px'
|
|
12
|
-
}"
|
|
7
|
+
:style="styles"
|
|
13
8
|
>
|
|
14
9
|
<div class="video-wrapper" ref="wrapperRef">
|
|
15
10
|
<video
|
|
@@ -20,49 +15,11 @@
|
|
|
20
15
|
playsinline
|
|
21
16
|
loop
|
|
22
17
|
autoPlay
|
|
23
|
-
disablePictureInPicture="false"
|
|
18
|
+
:disablePictureInPicture="false"
|
|
24
19
|
@play="handlePlay"
|
|
25
20
|
@pause="handlePause"
|
|
26
21
|
@ended="handleEnded"
|
|
27
22
|
></video>
|
|
28
|
-
|
|
29
|
-
<!-- 缩放控制点 - 四个角 -->
|
|
30
|
-
<div
|
|
31
|
-
v-if="visible"
|
|
32
|
-
class="resize-handle top-left"
|
|
33
|
-
@mousedown="startResize('top-left', $event)"
|
|
34
|
-
@touchstart="startResize('top-left', $event)"
|
|
35
|
-
></div>
|
|
36
|
-
<div
|
|
37
|
-
v-if="visible"
|
|
38
|
-
class="resize-handle top-right"
|
|
39
|
-
@mousedown="startResize('top-right', $event)"
|
|
40
|
-
@touchstart="startResize('top-right', $event)"
|
|
41
|
-
></div>
|
|
42
|
-
<div
|
|
43
|
-
v-if="visible"
|
|
44
|
-
class="resize-handle bottom-left"
|
|
45
|
-
@mousedown="startResize('bottom-left', $event)"
|
|
46
|
-
@touchstart="startResize('bottom-left', $event)"
|
|
47
|
-
></div>
|
|
48
|
-
<div
|
|
49
|
-
v-if="visible"
|
|
50
|
-
class="resize-handle bottom-right"
|
|
51
|
-
@mousedown="startResize('bottom-right', $event)"
|
|
52
|
-
@touchstart="startResize('bottom-right', $event)"
|
|
53
|
-
></div>
|
|
54
|
-
|
|
55
|
-
<!-- UI Overlay for controls or status -->
|
|
56
|
-
<div class="overlay">
|
|
57
|
-
<div v-if="!isPlaying" class="status-badge paused">
|
|
58
|
-
<span class="dot"></span>
|
|
59
|
-
暂停中
|
|
60
|
-
</div>
|
|
61
|
-
<div v-else class="status-badge playing">
|
|
62
|
-
<span class="dot animate-pulse"></span>
|
|
63
|
-
播放中
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
23
|
</div>
|
|
67
24
|
</div>
|
|
68
25
|
</Transition>
|
|
@@ -106,6 +63,15 @@ const props = defineProps({
|
|
|
106
63
|
wsUrl: {
|
|
107
64
|
type: String,
|
|
108
65
|
required: false,
|
|
66
|
+
},
|
|
67
|
+
styles: {
|
|
68
|
+
type: Object,
|
|
69
|
+
default: () => ({
|
|
70
|
+
width:'400px',
|
|
71
|
+
height:'500px',
|
|
72
|
+
left: '16px',
|
|
73
|
+
bottom:'16px'
|
|
74
|
+
})
|
|
109
75
|
}
|
|
110
76
|
});
|
|
111
77
|
|
|
@@ -117,20 +83,6 @@ const wrapperRef = ref<HTMLDivElement | null>(null);
|
|
|
117
83
|
const ws = ref<WebSocket | null>(null);
|
|
118
84
|
const isConnected = ref(false);
|
|
119
85
|
|
|
120
|
-
// 缩放相关状态
|
|
121
|
-
const containerWidth = ref(400);
|
|
122
|
-
const containerHeight = ref(500);
|
|
123
|
-
const containerLeft = ref(16);
|
|
124
|
-
const containerBottom = ref(16);
|
|
125
|
-
|
|
126
|
-
const isResizing = ref(false);
|
|
127
|
-
const resizeHandle = ref<string | null>(null);
|
|
128
|
-
const startX = ref(0);
|
|
129
|
-
const startY = ref(0);
|
|
130
|
-
const startW = ref(0);
|
|
131
|
-
const startH = ref(0);
|
|
132
|
-
const startL = ref(0);
|
|
133
|
-
const startB = ref(0);
|
|
134
86
|
|
|
135
87
|
const connectWebSocket = () => {
|
|
136
88
|
if (ws.value) {
|
|
@@ -180,6 +132,7 @@ const handleMessage = (msg: any) => {
|
|
|
180
132
|
} else if (action === 'pause') {
|
|
181
133
|
emit('update:isPlaying', false);
|
|
182
134
|
// 暂停时不隐藏视频
|
|
135
|
+
emit('update:visible', true);
|
|
183
136
|
} else if (action === 'stop') {
|
|
184
137
|
emit('update:isPlaying', false);
|
|
185
138
|
emit('update:visible', false);
|
|
@@ -229,99 +182,7 @@ const handleEnded = () => {
|
|
|
229
182
|
emit('ended');
|
|
230
183
|
};
|
|
231
184
|
|
|
232
|
-
// 缩放功能
|
|
233
|
-
const startResize = (handle: string, event: MouseEvent | TouchEvent) => {
|
|
234
|
-
event.preventDefault();
|
|
235
|
-
isResizing.value = true;
|
|
236
|
-
resizeHandle.value = handle;
|
|
237
|
-
|
|
238
|
-
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
|
|
239
|
-
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
|
|
240
|
-
startX.value = clientX;
|
|
241
|
-
startY.value = clientY;
|
|
242
|
-
|
|
243
|
-
startW.value = containerWidth.value;
|
|
244
|
-
startH.value = containerHeight.value;
|
|
245
|
-
startL.value = containerLeft.value;
|
|
246
|
-
startB.value = containerBottom.value;
|
|
247
|
-
|
|
248
|
-
document.addEventListener('mousemove', handleResize);
|
|
249
|
-
document.addEventListener('mouseup', stopResize);
|
|
250
|
-
document.addEventListener('touchmove', handleResize, { passive: false });
|
|
251
|
-
document.addEventListener('touchend', stopResize);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const handleResize = (event: MouseEvent | TouchEvent) => {
|
|
255
|
-
if (!isResizing.value) return;
|
|
256
|
-
event.preventDefault();
|
|
257
|
-
|
|
258
|
-
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
|
|
259
|
-
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
|
|
260
|
-
|
|
261
|
-
const deltaX = clientX - startX.value;
|
|
262
|
-
const deltaY = clientY - startY.value;
|
|
263
|
-
|
|
264
|
-
const minWidth = 200;
|
|
265
|
-
const minHeight = 250;
|
|
266
|
-
const maxWidth = 800;
|
|
267
|
-
const maxHeight = 1000;
|
|
268
|
-
|
|
269
|
-
let newW = startW.value;
|
|
270
|
-
let newH = startH.value;
|
|
271
|
-
let newL = startL.value;
|
|
272
|
-
let newB = startB.value;
|
|
273
|
-
|
|
274
|
-
if (resizeHandle.value === 'bottom-right') {
|
|
275
|
-
newW = startW.value + deltaX;
|
|
276
|
-
newB = startB.value - deltaY;
|
|
277
|
-
newH = startH.value + deltaY;
|
|
278
|
-
} else if (resizeHandle.value === 'bottom-left') {
|
|
279
|
-
newL = startL.value + deltaX;
|
|
280
|
-
newW = startW.value - deltaX;
|
|
281
|
-
newB = startB.value - deltaY;
|
|
282
|
-
newH = startH.value + deltaY;
|
|
283
|
-
} else if (resizeHandle.value === 'top-right') {
|
|
284
|
-
newW = startW.value + deltaX;
|
|
285
|
-
newH = startH.value - deltaY;
|
|
286
|
-
} else if (resizeHandle.value === 'top-left') {
|
|
287
|
-
newL = startL.value + deltaX;
|
|
288
|
-
newW = startW.value - deltaX;
|
|
289
|
-
newH = startH.value - deltaY;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// 约束最小宽高
|
|
293
|
-
if (newW < minWidth) {
|
|
294
|
-
if (newL !== startL.value) newL -= (minWidth - newW);
|
|
295
|
-
newW = minWidth;
|
|
296
|
-
}
|
|
297
|
-
if (newH < minHeight) {
|
|
298
|
-
if (newB !== startB.value) newB -= (minHeight - newH);
|
|
299
|
-
newH = minHeight;
|
|
300
|
-
}
|
|
301
|
-
// 约束最大宽高
|
|
302
|
-
if (newW > maxWidth) {
|
|
303
|
-
if (newL !== startL.value) newL += (newW - maxWidth);
|
|
304
|
-
newW = maxWidth;
|
|
305
|
-
}
|
|
306
|
-
if (newH > maxHeight) {
|
|
307
|
-
if (newB !== startB.value) newB += (newH - maxHeight);
|
|
308
|
-
newH = maxHeight;
|
|
309
|
-
}
|
|
310
185
|
|
|
311
|
-
containerWidth.value = newW;
|
|
312
|
-
containerHeight.value = newH;
|
|
313
|
-
containerLeft.value = newL;
|
|
314
|
-
containerBottom.value = newB;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const stopResize = () => {
|
|
318
|
-
isResizing.value = false;
|
|
319
|
-
resizeHandle.value = null;
|
|
320
|
-
document.removeEventListener('mousemove', handleResize);
|
|
321
|
-
document.removeEventListener('mouseup', stopResize);
|
|
322
|
-
document.removeEventListener('touchmove', handleResize);
|
|
323
|
-
document.removeEventListener('touchend', stopResize);
|
|
324
|
-
};
|
|
325
186
|
|
|
326
187
|
onMounted(() => {
|
|
327
188
|
if (props.isPlaying && videoRef.value) {
|