流式传输

This commit is contained in:
yinsx
2025-12-25 15:36:35 +08:00
parent e56f47076c
commit 14bfdcbf51
19 changed files with 1191 additions and 65 deletions

View File

@ -10,6 +10,10 @@ class BlendShapeAnimator {
this.idleAnimations = {};
this.blendShapeScale = config.blendShapeScale || 1.0;
this.dataFps = config.dataFps || 30;
this.isStreaming = false;
this.streamingComplete = true;
this.streamingWaitStart = null;
this.streamingStallMs = 0;
// 空闲动画参数
this.blinkParams = config.blinkParams || {
@ -36,6 +40,14 @@ class BlendShapeAnimator {
this.enabledExpressions = new Set();
this.expressionDurations = {};
// 播放动画时禁用的 blendshape 列表(由空闲动画控制)
this.disabledShapesInAnimation = config.disabledShapesInAnimation || [
'eyeblinkleft', 'eyeblinkright', // 眨眼
'browdownleft', 'browdownright', // 眉毛下
'browinnerup', // 眉毛内上
'browouterupleft', 'browouterupright' // 眉毛外上
];
// 状态标志
this.isBlinkEnabled = false;
this.isEyeLookEnabled = false;
@ -63,6 +75,43 @@ class BlendShapeAnimator {
this.animationShapeNames = this._collectAnimationShapeNames(this.animationFrames);
}
appendAnimationFrames(frames) {
if (!Array.isArray(frames) || frames.length === 0) {
return;
}
this.animationFrames.push(...frames);
const newNames = this._collectAnimationShapeNames(frames);
if (newNames.length === 0) {
return;
}
const existingNames = new Set(this.animationShapeNames);
newNames.forEach(name => {
if (!existingNames.has(name)) {
existingNames.add(name);
this.animationShapeNames.push(name);
}
});
}
startStreaming() {
console.log('Starting streaming mode');
this.isStreaming = true;
this.streamingComplete = false;
this.streamingWaitStart = null;
this.streamingStallMs = 0;
}
endStreaming() {
console.log('Ending streaming mode');
this.streamingComplete = true;
this.isStreaming = false;
this.streamingWaitStart = null;
this.streamingStallMs = 0;
}
// 播放动画
playAnimation() {
if (this.animationFrames.length === 0) {
@ -75,15 +124,26 @@ class BlendShapeAnimator {
return;
}
// 停止随机表情
// 停止并立即重置眼球移动
if (this.isEyeLookEnabled) {
this._stopRandomEyeLook();
this._immediateResetEyeLook();
}
// 停止并立即重置随机表情
if (this.isExpressionEnabled && window.ExpressionLibrary) {
window.ExpressionLibrary.randomPlayer.stop();
this._immediateResetExpressions();
}
// 注意:不停止眨眼,让眨眼继续运行
this.stopAnimation(false);
this.isPlaying = true;
this.currentFrameIndex = 0;
this.animationStartTime = performance.now();
this.streamingWaitStart = null;
this.streamingStallMs = 0;
this._animateFrame();
this.onStatusChange('info', '播放中...');
@ -94,6 +154,11 @@ class BlendShapeAnimator {
this.isPlaying = false;
this._resetAnimationInfluences();
// 恢复眼球移动
if (resumeExpressions && this.isEyeLookEnabled) {
this._startRandomEyeLook();
}
// 恢复随机表情
if (resumeExpressions && this.isExpressionEnabled && window.ExpressionLibrary) {
window.ExpressionLibrary.randomPlayer.start();
@ -130,12 +195,35 @@ class BlendShapeAnimator {
if (!this.isPlaying) return;
const now = performance.now();
if (this.streamingWaitStart !== null && this.animationFrames.length > this.currentFrameIndex + 1) {
this.streamingStallMs += now - this.streamingWaitStart;
this.streamingWaitStart = null;
}
const frameDuration = 1000 / this.dataFps;
const elapsed = now - this.animationStartTime;
const elapsed = now - this.animationStartTime - this.streamingStallMs;
const exactFrame = elapsed / frameDuration;
const targetFrameIndex = Math.floor(exactFrame);
if (targetFrameIndex >= this.animationFrames.length) {
if (this.isStreaming && !this.streamingComplete) {
if (this.streamingWaitStart === null) {
this.streamingWaitStart = now;
console.log(`Waiting for more frames... (current: ${this.animationFrames.length}, target: ${targetFrameIndex})`);
}
const waitTime = now - this.streamingWaitStart;
if (waitTime > 30000) {
console.warn('Streaming timeout after 30s, stopping animation');
this.stopAnimation();
return;
}
if (waitTime > 1000 && Math.floor(waitTime / 1000) !== Math.floor((waitTime - 16) / 1000)) {
console.log(`Still waiting... ${Math.floor(waitTime / 1000)}s`);
}
requestAnimationFrame(() => this._animateFrame());
return;
}
console.log(`Animation complete. Total frames: ${this.animationFrames.length}`);
this.stopAnimation();
return;
}
@ -152,6 +240,11 @@ class BlendShapeAnimator {
: Object.keys(currentBlendShapes);
for (const key of shapeNames) {
// 跳过禁用列表中的 blendshape让空闲动画继续控制它们
if (this.disabledShapesInAnimation.includes(key.toLowerCase())) {
continue;
}
const currentValue = currentBlendShapes[key] || 0;
const nextValue = nextBlendShapes[key] || 0;
const interpolatedValue = this._lerp(currentValue, nextValue, smoothProgress);
@ -357,6 +450,38 @@ class BlendShapeAnimator {
});
}
_immediateResetEyeLook() {
const eyeLookShapes = [
'eyelookupleft', 'eyelookupright',
'eyelookdownleft', 'eyelookdownright',
'eyelookinleft', 'eyelookinright',
'eyelookoutleft', 'eyelookoutright'
];
if (!this.morphTargetAdapter) return;
eyeLookShapes.forEach(name => {
this.morphTargetAdapter.setInfluence(name, 0);
delete this.idleAnimations[name];
});
}
_immediateResetExpressions() {
if (!this.morphTargetAdapter || !window.ExpressionLibrary) return;
// 获取所有表情的 blendshape 名称并立即重置
const expressions = window.ExpressionLibrary.expressions;
for (const exprKey in expressions) {
const expr = expressions[exprKey];
if (expr.shapes) {
for (const shapeName in expr.shapes) {
this.morphTargetAdapter.setInfluence(shapeName, 0);
delete this.idleAnimations[shapeName];
}
}
}
}
// 随机表情控制
toggleRandomExpression(enabled) {
if (!window.ExpressionLibrary) {