This commit is contained in:
zpc 2025-03-29 02:09:13 +08:00
parent 71db19a4be
commit 185c9b6739
4 changed files with 223 additions and 125 deletions

View File

@ -583,7 +583,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_lastSoundTime = DateTime.Now;
_lastSpeechTime = DateTime.Now; // 初始化最后说话时间
_silenceTimer = new Timer(CheckSilence, recordingCompletionSource, 1000, 1000);
// 启动风铃提示计时器
_windChimeTimer = new Timer(CheckWindChimePrompt, null, 1000, 1000);
@ -871,12 +871,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_silenceTimer?.Change(Timeout.Infinite, Timeout.Infinite);
_silenceTimer?.Dispose();
_silenceTimer = null;
// 停止风铃提示音计时器
_windChimeTimer?.Change(Timeout.Infinite, Timeout.Infinite);
_windChimeTimer?.Dispose();
_windChimeTimer = null;
// 停止风铃提示音
_ = StopWindChime(false);
@ -946,23 +946,23 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
}
if (DateTime.Now.Subtract(_lasHasoudDateTime).TotalSeconds > 1)
{
Console.WriteLine($"当前音量:{maxVolume},设置的音量灵敏度:{_config.Recording.SilenceThreshold},是否已监测到在说话中:{_isSpeaking}");
Console.WriteLine($"当前音量:{maxVolume},设置的音量灵敏度:{_config.Recording.SilenceThreshold},是否已监测到在说话中:{_isSpeaking},风铃提示音:{_windChimeDevice?.Volume ?? 0}bgm:{_backgroundMusicDevice?.Volume ?? 0}");
_lasHasoudDateTime = DateTime.Now;
_isSpeaking = false;
}
// 如果监测到说话,更新最后说话时间
if (isSpeaking)
{
_lastSpeechTime = DateTime.Now;
// 如果风铃声正在播放,平滑停止它
if (_isWindChimePlaying)
{
_ = StopWindChime(true); // 平滑淡出
}
}
// 如果最大音量超过阈值,则认为有声音
return isSpeaking;
}
@ -979,7 +979,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
// 确保背景音乐被停止
StopBackgroundMusic();
// 确保风铃提示音被停止
_ = StopWindChime(false);
@ -1304,7 +1304,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
if (_keyToneDevices.TryAdd(digit, (device, reader)))
{
device.Play();
// 确保至少播放最小时长
@ -1361,7 +1361,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
device = new WaveOutEvent();
reader = new AudioFileReader(audioPath);
// 如果没有指定音量,根据音频文件类型设置音量
if (volume == null)
{
@ -1396,9 +1396,9 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
device.Volume = Math.Clamp(volume.Value, 0.0f, 1.0f);
}
device.Init(reader);
// 如果需要循环播放,创建一个循环播放的任务
if (loop && maxDuration.HasValue)
{
@ -1519,14 +1519,15 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_backgroundMusicDevice = new WaveOutEvent();
_backgroundMusicReader = new AudioFileReader(musicFilePath);
// 设置音量默认为10%
_backgroundMusicReader.Volume = _config.Recording.BackgroundMusicVolume;
// 初始化设备
_backgroundMusicDevice.Init(_backgroundMusicReader);
// 设置音量到设备而非Reader对象确保独立控制默认为10%
_backgroundMusicDevice.Volume = Math.Clamp(_config.Recording.BackgroundMusicVolume, 0.0f, 1.0f);
// 标记背景音乐开始播放
_isBackgroundMusicPlaying = true;
Console.WriteLine($"开始播放背景音乐,音量: {_backgroundMusicReader.Volume * 100}%");
Console.WriteLine($"开始播放背景音乐,音量: {_backgroundMusicDevice.Volume * 100}%");
// 循环播放背景音乐
while (!_backgroundMusicCts.Token.IsCancellationRequested)
@ -1538,10 +1539,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
_backgroundMusicReader.Position = 0;
}
// 设置音量到设备而非Reader对象确保独立控制默认为10%
_backgroundMusicDevice.Volume = Math.Clamp(_config.Recording.BackgroundMusicVolume, 0.0f, 1.0f);
// 如果没有在播放,则开始播放
if (_backgroundMusicDevice.PlaybackState != PlaybackState.Playing)
{
_backgroundMusicDevice.Play();
}
@ -1625,8 +1628,8 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
double secondsSinceLastSpeech = (DateTime.Now - _lastSpeechTime).TotalSeconds;
// 如果超过风铃提示时间但未达到挂断时间,并且风铃没在播放,开始播放风铃
if (secondsSinceLastSpeech >= windChimePromptSeconds &&
secondsSinceLastSpeech < silenceTimeoutSeconds &&
if (secondsSinceLastSpeech >= windChimePromptSeconds &&
secondsSinceLastSpeech < silenceTimeoutSeconds &&
!_isWindChimePlaying)
{
Console.WriteLine($"检测到{windChimePromptSeconds}秒无声音,播放风铃提示音...");
@ -1670,14 +1673,17 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
return;
}
// 初始化播放设备
// 初始化播放设备(创建完全独立的音频流)
_windChimeDevice = new WaveOutEvent();
_windChimeReader = new AudioFileReader(windChimeFilePath);
_windChimeDevice.Init(_windChimeReader);
_windChimeDevice.Volume = Math.Clamp(1.0f, 0.0f, 1.0f); // 初始音量设为最大
// 设置初始音量
float initialVolume = 0.8f; // 使用稍低于最大的音量,避免音量混合问题
_windChimeDevice.Volume = Math.Clamp(initialVolume, 0.0f, 1.0f);
_isWindChimePlaying = true;
Console.WriteLine("开始播放风铃提示音");
Console.WriteLine($"开始播放风铃提示音,初始音量: {_windChimeDevice.Volume * 100}%");
// 开始播放
_windChimeDevice.Play();
@ -1696,13 +1702,20 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
await StopWindChime(true); // 平滑淡出
break;
}
// 如果到达音频文件末尾,循环播放
if (_windChimeReader != null && _windChimeReader.Position >= _windChimeReader.Length)
{
_windChimeReader.Position = 0;
}
// 确保背景音乐音量不受风铃音量影响
if (_isBackgroundMusicPlaying && _backgroundMusicDevice != null)
{
// 重新应用背景音乐的原始音量设置
_backgroundMusicDevice.Volume = Math.Clamp(_config.Recording.BackgroundMusicVolume, 0.0f, 1.0f);
}
await Task.Delay(100, token);
}
}
@ -1754,13 +1767,20 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
float ratio = 1.0f - ((float)i / steps);
_windChimeDevice.Volume = startVolume * ratio;
// 确保背景音乐音量不受影响
if (_isBackgroundMusicPlaying && _backgroundMusicDevice != null)
{
_backgroundMusicDevice.Volume = Math.Clamp(_config.Recording.BackgroundMusicVolume, 0.0f, 1.0f);
}
await Task.Delay(stepDelay);
}
}
// 正式停止
_windChimeCts?.Cancel();
if (_windChimeDevice != null)
{
_windChimeDevice.Stop();
@ -1776,6 +1796,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_isWindChimePlaying = false;
Console.WriteLine("风铃提示音已停止");
// 再次确保背景音乐音量恢复正常
if (_isBackgroundMusicPlaying && _backgroundMusicDevice != null)
{
_backgroundMusicDevice.Volume = Math.Clamp(_config.Recording.BackgroundMusicVolume, 0.0f, 1.0f);
}
}
catch (Exception ex)
{

View File

@ -26,12 +26,12 @@
"Channels": 1,
"BufferMilliseconds": 50,
"SilenceThreshold": 0.1,
"SilenceTimeoutSeconds": 15,
"SilenceTimeoutSeconds": 60,
"AllowUserHangup": true,
"UploadRecordingToServer": false,
"EnableBackgroundMusic": true,
"BackgroundMusicFile": "bj.mp3",
"BackgroundMusicVolume": 0.3,
"BackgroundMusicVolume": 0.15,
"WindChimePromptSeconds": 7.5,
"WindChimeFadeOutMs": 3000
},

View File

@ -164,7 +164,7 @@
let currentSentenceIndex = 0; // 当前句子索引
let isAnimating = false; // 动画状态标志
// 设置总页数为1000页实际上是循环使用有限的内容
var numberOfPages = 11;
var numberOfPages = 1000;
/**
* 检查文字容器是否需要翻页
@ -202,11 +202,6 @@
if (isAnimating) return;
isAnimating = true;
// 获取渐变相关配置
const fadeChars = CONFIG.rightContainer.fadeChars || 4; // 渐变字符数量
const fadeStepTime = CONFIG.rightContainer.fadeStepTime || 500; // 过渡时间
const fadeDelayFactor = CONFIG.rightContainer.fadeDelayFactor || 0.25; // 透明度递减因子
// 清空容器并设置初始可见度
$(selector).html('').css('opacity', 1);
@ -215,8 +210,7 @@
const charSpan = $('<span></span>').text(text.charAt(i));
charSpan.css({
'opacity': 0,
'display': 'inline-block', // 确保每个字符是独立的块
'transition': `opacity ${fadeStepTime}ms ease-in-out` // 平滑的透明度过渡效果
'display': 'inline-block' // 确保每个字符是独立的块
});
$(selector).append(charSpan);
}
@ -225,68 +219,57 @@
const chars = $(selector).find('span');
const totalChars = chars.length;
// 设置初始不透明度,形成从左到右的渐变效果
// 左侧字符较深,右侧字符较浅
const setInitialOpacity = () => {
for (let i = 0; i < totalChars; i++) {
// 计算不透明度:第一个字符不透明度最高,往后逐渐降低
let position = i / totalChars; // 字符在文本中的相对位置 (0-1)
let opacity = Math.max(0, 0.8 - (position * fadeChars * 0.2));
// 超出可见范围的字符完全透明
if (i >= fadeChars) {
opacity = 0;
}
// 设置不透明度
$(chars[i]).css('opacity', opacity);
}
};
// 执行渐变动画
const animateText = () => {
let progress = 0;
// 确保speed值是合理的至少有2秒用于总体动画
const totalDuration = Math.max(2000, speed);
const step = () => {
if (progress >= 1) {
// 完成动画
chars.css('opacity', 1);
isAnimating = false;
if (callback) callback();
// 等待随机4-7秒后请求下一条文本
const delay = Math.floor(Math.random() * 3000) + 4000; // 4000-7000毫秒
console.log(`文本显示完成,将在${delay/1000}秒后请求下一条文本`);
setTimeout(requestNextText, delay);
// 每个字符显示的间隔时间
const charInterval = Math.max(100, totalDuration / totalChars / 3);
console.log(charInterval, totalDuration / totalChars / 3);
// 单个字符的动画时间,要足够长以观察到渐变效果
const charAnimDuration = 1500; // 固定为1.5秒,确保足够慢以观察到渐变
// 逐个字符渐显
function animateChar(index) {
if (index >= totalChars) {
// 所有字符都已经开始动画
setTimeout(() => {
isAnimating = false;
if (callback) callback();
// 等待随机4-7秒后请求下一条文本
const delay = Math.floor(Math.random() * 3000) + 3000; // 4000-6000毫秒
console.log(`文本显示完成,将在${delay/1000}秒后请求下一条文本`);
setTimeout(requestNextText, delay);
}, charAnimDuration); // 确保所有动画完成
return;
}
// 更新每个字符的透明度
for (let i = 0; i < totalChars; i++) {
let targetOpacity = 1; // 目标不透明度(完全显示)
let currentPosition = i / totalChars; // 字符的相对位置
// 计算当前动画进度下该位置的字符应有的透明度
// 小于当前进度的字符应该更不透明
let opacity = targetOpacity * Math.max(0, 1 - Math.max(0, (currentPosition - progress) * 3));
// 设置不透明度
$(chars[i]).css('opacity', opacity);
}
// 获取当前字符
const char = $(chars[index]);
// 增加进度
progress += 0.02;
// 使用jQuery的animate实现平滑的从0到1的渐变
char.animate(
{ opacity: 1 },
charAnimDuration, // 固定的较长动画时间
'swing', // 使用jQuery内置的swing缓动函数
function() {
// 动画完成回调(可选)
}
);
// 继续下一步动画
setTimeout(step, speed / 50);
};
// 安排下一个字符的渐变
setTimeout(() => {
animateChar(index + 1);
}, charInterval);
}
// 开始动画
step();
// 开始第一个字符的渐变
animateChar(0);
};
// 设置初始不透明度并开始动画
setInitialOpacity();
// 开始动画
setTimeout(animateText, 50);
}
@ -395,6 +378,8 @@
// SignalR连接变量
let hubConnection;
let reconnectAttempts = 0; // 记录重连尝试次数
const MAX_RECONNECT_ATTEMPTS = 5; // 最大重连尝试次数
// 初始化SignalR连接
function initializeSignalRConnection() {
@ -404,6 +389,21 @@
.withAutomaticReconnect()
.build();
// 注册重连事件处理
hubConnection.onreconnecting(error => {
console.log("SignalR重新连接中: " + (error ? error.message : "未知错误"));
reconnectAttempts++; // 增加重连尝试次数
console.log(`重连尝试次数: ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`);
});
hubConnection.onreconnected(connectionId => {
console.log("SignalR已重新连接ID: " + connectionId);
// 重连成功,重置尝试次数
reconnectAttempts = 0;
// 重连成功后重新注册客户端
registerAsDisplayClient();
});
// 接收显示文本的处理函数
hubConnection.on("ReceiveDisplayText", function (text) {
console.log("收到显示文本:", text);
@ -424,46 +424,100 @@
});
// 启动连接
hubConnection.start()
.then(function () {
console.log("SignalR连接成功");
// 注册为显示客户端
hubConnection.invoke("RegisterClient", 3, "Display")
.then(function() {
console.log("注册为显示客户端成功");
// 获取显示配置
return hubConnection.invoke("GetDisplayConfig");
})
.then(function(configJson) {
if(configJson) {
console.log("已获取显示配置");
try {
const config = JSON.parse(configJson);
updateConfig(config);
} catch (error) {
console.error("解析显示配置失败:", error);
}
}
// 开始请求第一条显示文本
requestNextText();
})
.catch(function(err) {
console.error("客户端操作失败:", err);
});
})
.catch(function (err) {
console.error("SignalR连接失败:", err);
// 5秒后重试
setTimeout(initializeSignalRConnection, 5000);
});
startConnection();
// 连接关闭的处理
hubConnection.onclose(function() {
console.log("SignalR连接已关闭尝试重新连接...");
setTimeout(initializeSignalRConnection, 5000);
reconnectAttempts++; // 增加失败计数
console.log(`关闭后重连尝试次数: ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`);
// 检查是否达到最大尝试次数
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.error("达到最大重连次数,刷新页面...");
// 显示刷新提示
if (!document.getElementById('reconnect-alert')) {
const alert = document.createElement('div');
alert.id = 'reconnect-alert';
alert.style.cssText = 'position:fixed;top:10px;left:50%;transform:translateX(-50%);background:rgba(255,0,0,0.7);color:white;padding:10px;border-radius:5px;z-index:9999;';
alert.innerHTML = '连接服务器失败3秒后自动刷新页面...';
document.body.appendChild(alert);
}
// 3秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 3000);
return;
}
setTimeout(startConnection, 5000);
});
}
// 启动连接函数
function startConnection() {
hubConnection.start()
.then(function () {
console.log("SignalR连接成功");
// 连接成功,重置尝试次数
reconnectAttempts = 0;
// 注册为显示客户端
registerAsDisplayClient();
})
.catch(function (err) {
console.error("SignalR连接失败:", err);
reconnectAttempts++; // 增加失败计数
console.log(`连接尝试次数: ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`);
// 检查是否达到最大尝试次数
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
console.error("达到最大重连次数,刷新页面...");
// 显示刷新提示
if (!document.getElementById('reconnect-alert')) {
const alert = document.createElement('div');
alert.id = 'reconnect-alert';
alert.style.cssText = 'position:fixed;top:10px;left:50%;transform:translateX(-50%);background:rgba(255,0,0,0.7);color:white;padding:10px;border-radius:5px;z-index:9999;';
alert.innerHTML = '连接服务器失败3秒后自动刷新页面...';
document.body.appendChild(alert);
}
// 3秒后刷新页面
setTimeout(() => {
window.location.reload();
}, 3000);
return;
}
// 5秒后重试
setTimeout(startConnection, 5000);
});
}
// 注册为显示客户端
function registerAsDisplayClient() {
hubConnection.invoke("RegisterClient", 3, "Display")
.then(function() {
console.log("注册为显示客户端成功");
// 获取显示配置
return hubConnection.invoke("GetDisplayConfig");
})
.then(function(configJson) {
if(configJson) {
console.log("已获取显示配置");
try {
const config = JSON.parse(configJson);
updateConfig(config);
} catch (error) {
console.error("解析显示配置失败:", error);
}
}
// 开始请求第一条显示文本
requestNextText();
})
.catch(function(err) {
console.error("客户端操作失败:", err);
});
}
// 更新配置
function updateConfig(newConfig) {
// 更新左侧容器配置
@ -702,17 +756,35 @@
* 请求下一条文本
*/
function requestNextText() {
if (hubConnection && hubConnection.state === signalR.HubConnectionState.Connected) {
if (!hubConnection) {
console.warn("SignalR连接对象不存在正在初始化连接...");
initializeSignalRConnection();
return;
}
if (hubConnection.state === signalR.HubConnectionState.Connected) {
console.log("正在向服务器请求下一条文本...");
hubConnection.invoke("GetNextDisplayText")
.catch(function(err) {
console.error("请求下一条文本失败:", err);
// 如果请求失败,稍后重试
setTimeout(requestNextText, 5000);
// 检查是否是因为连接断开导致的错误
if (hubConnection.state !== signalR.HubConnectionState.Connected) {
console.warn("连接已断开,尝试重新连接后请求文本");
// 如果请求失败是因为连接已断开,先尝试重连
startConnection();
} else {
// 其他错误,稍后重试
setTimeout(requestNextText, 5000);
}
});
} else if (hubConnection.state === signalR.HubConnectionState.Reconnecting) {
console.warn("SignalR正在重连中等待重连完成后自动请求文本");
// 不需额外操作,重连成功后会自动请求文本
} else {
console.warn("SignalR连接未就绪无法请求下一条文本");
// 如果连接未就绪,稍后重试
console.warn("SignalR连接未就绪状态:", hubConnection.state);
// 尝试重新连接
startConnection();
// 3秒后重试
setTimeout(requestNextText, 3000);
}
}

View File

@ -8,9 +8,9 @@
"fontSize": "40px",
"fontWeight": "bolder",
"fontStyle": "italic",
"typewriterSpeed": 6000,
"typewriterSpeed": 18000,
"fadeChars": 1,
"fadeStepTime": 1000,
"fadeStepTime": 3000,
"fadeDelayFactor": 0.20
},
"waterEffect": {