diff --git a/ShengShengBuXi/Hubs/AudioHub.cs b/ShengShengBuXi/Hubs/AudioHub.cs
index 4d9f27c..8bfc91a 100644
--- a/ShengShengBuXi/Hubs/AudioHub.cs
+++ b/ShengShengBuXi/Hubs/AudioHub.cs
@@ -73,35 +73,23 @@ namespace ShengShengBuXi.Hubs
///
/// 监控文本队列的持久化文件路径
///
- private static readonly string _monitorTextQueueFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "monitor_text_queue.json");
- // 预设句子
- private static readonly string[] _presetSentences = new string[]
- {
- "记得每到夏天傍晚,您就摇着蒲扇坐在藤椅里,把切好的西瓜最甜那块硬塞给我,自己却啃着靠近皮的白瓤,还笑着说:外公就爱这口,清爽。",
- "女儿啊 花开了 你否到了吗?",
- "外公,你在那边过的还好么大家都很想称,记得常回家看看",
- "外公,今天窗台上的茉莉开了,白盈盈的,就像以前您总别在中山装口袋上的那朵。",
- "外公,巷口那家老茶馆拆了,您最爱坐的靠窗位置再也找不到了,就像再也找不到您一样。",
- "整理旧物时发现您用红绳缠好的象棋,每一处磨损都藏着您教我'马走日'时手心的温度。",
- "今早闻到槐花香突然站住——您总说这味道像极了老家后山的夏天,现在我才懂得什么叫'睹物思人'。",
- "菜场看见卖菱角的老伯,想起您总把最嫩的剥好放我碗里,自己却嚼着发苦的老根。",
- "暴雨天膝盖又疼了吧?记得您总在这时候熬姜汤,说'老寒腿最懂天气预报'。",
- "您养的那盆君子兰今年抽了七支花箭,比您走那年还多两枝,定是替您来看我的。",
- "小满那天不自觉煮了两人份的腊肉饭,盛完才想起再没人把肥肉挑走给我留精瘦的。",
- "儿童节路过小学,梧桐树下空荡荡的——再没有举着冰糖葫芦等我的驼背身影了。",
- "冬至包饺子时手一抖,捏出您教的麦穗花边,滚水冒的蒸汽突然就迷了眼睛。",
- "昨夜梦见您穿着洗白的蓝布衫,在晒谷场对我笑,醒来枕巾湿了半边。",
- "蝉鸣最响的午后,恍惚听见竹椅吱呀声,转头却只看见墙上相框里的您。",
- "您走后,再没人把枇杷核仔细包进手帕,笑着说'留着明年给囡囡种'。",
- "整理遗物时数出38张火车票,全是往返我读书城市的,票根都磨出了毛边。",
- "清明雨把墓碑冲洗得发亮,就像您当年总把搪瓷缸擦得能照见人影。",
- "今天教女儿念'慈母手中线',她突然问:'太外公的诗集能读给我听吗?'",
- "翻到您留下的老照片,那件洗得发白的中山装上还别着茉莉花,仿佛还能闻到淡淡的清香。",
- "傍晚的蝉鸣声里,总错觉能听到您哼着那首走了调的小曲,在巷子口唤我回家吃饭。",
- "您种的葡萄藤今年结了好多串,可再没有人像您那样,把最紫的摘下来悄悄塞进我口袋。",
- "下雨天膝盖隐隐作痛时,总会想起您泡的姜茶,热气氤氲中您笑着说'老了才知道疼'。",
- "整理书房时,发现您用毛笔在旧日历背面写的家训,墨迹晕染处都是您颤抖的手印。"
- };
+ private static readonly string _monitorTextQueueFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config/monitor_text_queue.json");
+ ///
+ /// 预设句子文件路径
+ ///
+ private static readonly string _sentencesFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config/sentences.txt");
+ ///
+ /// 预设句子列表
+ ///
+ private static List _presetSentences = new List();
+ ///
+ /// 已显示过的预设句子队列及显示时间,用于防止短时间内重复显示
+ ///
+ private static readonly Queue<(string Text, DateTime DisplayTime)> _recentlyDisplayedSentences = new Queue<(string, DateTime)>();
+ ///
+ /// 防止重复显示的句子数量
+ ///
+ private static readonly int _sentenceHistoryLimit = 20;
///
/// 初始化音频Hub
@@ -136,12 +124,15 @@ namespace ShengShengBuXi.Hubs
_cleanupTimer = new Timer(CleanupOldProcessedPaths, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30));
}
+ // 从文件加载预设句子
+ LoadPresetSentencesFromFile();
+
// 从文件加载监控文本队列
LoadMonitorTextQueueFromFile();
// 初始化显示文本定时器
InitializeDisplayTextTimer();
-
+
// 注册应用程序域卸载事件,以便在应用关闭时保存数据
AppDomain.CurrentDomain.ProcessExit += (sender, e) => SaveMonitorTextQueueToFile();
AppDomain.CurrentDomain.DomainUnload += (sender, e) => SaveMonitorTextQueueToFile();
@@ -402,7 +393,7 @@ namespace ShengShengBuXi.Hubs
// 转发音频数据到管理端
if (_configurationService.CurrentConfig.Network.EnableAudioStreaming)
{
- // await Clients.Group("webadmin").SendAsync("ReceiveAudioData", Context.ConnectionId, audioData);
+ // await Clients.Group("webadmin").SendAsync("ReceiveAudioData", Context.ConnectionId, audioData);
// 转发音频到正在监听的显示端
var monitoringClients = _clients.Values
@@ -536,13 +527,13 @@ namespace ShengShengBuXi.Hubs
public async Task> GetMonitorTextList()
{
var result = _monitorTextQueue.Select(kvp => kvp.Value).OrderBy(it => it.Timestamp).ToList();
-
+
// 转换文件路径为URL路径
foreach (var item in result)
{
ConvertFilePathsToUrls(item);
}
-
+
return result;
}
@@ -552,14 +543,14 @@ namespace ShengShengBuXi.Hubs
/// 显示文本列表
public async Task> GetDisplayList()
{
- var result = _displayTextQueue.Select(kvp => kvp.Value).Where(it=>it.IsRealUser).OrderBy(it => it.Timestamp).ToList();
-
+ var result = _displayTextQueue.Select(kvp => kvp.Value).Where(it => it.IsRealUser).OrderBy(it => it.Timestamp).ToList();
+
// 转换文件路径为URL路径
foreach (var item in result)
{
ConvertFilePathsToUrls(item);
}
-
+
return result;
}
@@ -574,7 +565,7 @@ namespace ShengShengBuXi.Hubs
{
// 从完整路径中提取文件名
string fileName = Path.GetFileName(displayText.RecordingPath);
-
+
// 从路径中识别/recordings/目录
if (displayText.RecordingPath.Contains("recordings"))
{
@@ -588,13 +579,13 @@ namespace ShengShengBuXi.Hubs
displayText.RecordingPath = $"/recordings/{fileName}";
}
}
-
+
// 转换文本文件路径
if (!string.IsNullOrEmpty(displayText.TextFilePath))
{
// 从完整路径中提取文件名
string fileName = Path.GetFileName(displayText.TextFilePath);
-
+
// 从路径中识别/texts/目录
if (displayText.TextFilePath.Contains("texts"))
{
@@ -815,40 +806,311 @@ namespace ShengShengBuXi.Hubs
///
private void AddFakeTextToQueue()
{
- // 从预设句子中随机选择一条
- string randomText = _presetSentences[new Random().Next(_presetSentences.Length)];
- AddRecognizedTextToDisplay(randomText, false);
+ try
+ {
+ // 确保预设句子列表不为空
+ if (_presetSentences.Count == 0)
+ {
+ LoadPresetSentencesFromFile();
+
+ // 如果加载后仍然为空,使用默认句子
+ if (_presetSentences.Count == 0)
+ {
+ _presetSentences.Add("记得每到夏天傍晚,您就摇着蒲扇坐在藤椅里,把切好的西瓜最甜那块硬塞给我。");
+ _presetSentences.Add("时光匆匆流逝,思念却越来越深。");
+ }
+ }
+
+ // 清理超过24小时的历史记录
+ while (_recentlyDisplayedSentences.Count > 0 && DateTime.Now.Subtract(_recentlyDisplayedSentences.Peek().DisplayTime).TotalHours > 24)
+ {
+ _recentlyDisplayedSentences.Dequeue();
+ }
+
+ // 获取当前已显示过的句子集合
+ var recentTexts = _recentlyDisplayedSentences.Select(item => item.Text).ToHashSet();
+
+ // 筛选出未最近显示过的句子
+ var availableSentences = _presetSentences.Where(s => !recentTexts.Contains(s)).ToList();
+
+ // 如果可用句子为空(全部句子都显示过了),则使用全部句子但优先选择最早显示过的
+ if (availableSentences.Count == 0)
+ {
+ _logger.LogInformation("所有预设句子都已最近显示过,将选择最早显示的句子");
+ // 从队列中获取并移除最早显示的句子
+ var oldestSentence = _recentlyDisplayedSentences.Dequeue().Text;
+
+ AddRecognizedTextToDisplay(oldestSentence, false);
+
+ // 将这个句子重新添加到队列末尾,记录当前时间
+ _recentlyDisplayedSentences.Enqueue((oldestSentence, DateTime.Now));
+ }
+ else
+ {
+ // 从未最近显示过的句子中随机选择一条
+ string randomText = availableSentences[new Random().Next(availableSentences.Count)];
+ AddRecognizedTextToDisplay(randomText, false);
+
+ // 添加到已显示队列
+ _recentlyDisplayedSentences.Enqueue((randomText, DateTime.Now));
+
+ // 如果队列超出限制,移除最早的记录
+ while (_recentlyDisplayedSentences.Count > _sentenceHistoryLimit)
+ {
+ _recentlyDisplayedSentences.Dequeue();
+ }
+ }
+
+ _logger.LogInformation($"当前已显示过的句子数量: {_recentlyDisplayedSentences.Count}/{_sentenceHistoryLimit}");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"添加预设文本失败: {ex.Message}");
+ // 使用一个默认句子防止程序崩溃
+ AddRecognizedTextToDisplay("时光匆匆流逝,思念却越来越深。", false);
+ }
}
///
- /// 大屏客户端请求开始接收显示文本
+ /// 从文件加载预设句子
///
- /// 处理任务
- public async Task StartReceivingDisplayText()
+ private void LoadPresetSentencesFromFile()
+ {
+ try
+ {
+ _presetSentences.Clear();
+
+ // 检查文件是否存在
+ if (!File.Exists(_sentencesFilePath))
+ {
+ _logger.LogWarning($"预设句子文件不存在: {_sentencesFilePath},将创建默认文件");
+
+ // 创建目录(如果不存在)
+ Directory.CreateDirectory(Path.GetDirectoryName(_sentencesFilePath));
+
+ // 写入默认的预设句子
+ File.WriteAllLines(_sentencesFilePath, new string[] {
+ "记得每到夏天傍晚,您就摇着蒲扇坐在藤椅里,把切好的西瓜最甜那块硬塞给我,自己却啃着靠近皮的白瓤,还笑着说:外公就爱这口,清爽。",
+ "女儿啊 花开了 你否到了吗?",
+ "外公,你在那边过的还好么大家都很想称,记得常回家看看",
+ "外公,今天窗台上的茉莉开了,白盈盈的,就像以前您总别在中山装口袋上的那朵。",
+ "外公,巷口那家老茶馆拆了,您最爱坐的靠窗位置再也找不到了,就像再也找不到您一样。"
+ });
+ }
+
+ // 读取文件中的每一行作为一个预设句子
+ string[] lines = File.ReadAllLines(_sentencesFilePath);
+ foreach (string line in lines)
+ {
+ // 忽略空行
+ if (!string.IsNullOrWhiteSpace(line))
+ {
+ _presetSentences.Add(line.Trim());
+ }
+ }
+ _presetSentences = _presetSentences.OrderBy(x => Guid.NewGuid()).ToList();
+ _logger.LogInformation($"成功从文件加载预设句子: {_presetSentences.Count} 条");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"加载预设句子失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 从文件加载监控文本队列
+ ///
+ private void LoadMonitorTextQueueFromFile()
+ {
+ try
+ {
+ if (!File.Exists(_monitorTextQueueFilePath))
+ {
+ File.Create(_monitorTextQueueFilePath).Close();
+ _logger.LogInformation($"监控文本队列文件不存在: {_monitorTextQueueFilePath},手动创建");
+ //return;
+ }
+
+ string json = File.ReadAllText(_monitorTextQueueFilePath);
+ if (string.IsNullOrEmpty(json))
+ {
+ _logger.LogWarning("监控文本队列文件内容为空");
+ return;
+ }
+
+ var items = JsonConvert.DeserializeObject>(json);
+ if (items == null || !items.Any())
+ {
+ _logger.LogWarning("没有从文件中读取到监控文本队列项");
+ return;
+ }
+
+ // 清空现有队列,并添加从文件加载的项
+ _monitorTextQueue.Clear();
+ foreach (var item in items)
+ {
+ _monitorTextQueue.TryAdd(item.Id, item);
+ }
+
+ _logger.LogInformation($"成功从文件加载监控文本队列: {items.Count} 项");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"加载监控文本队列失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 保存监控文本队列到文件
+ ///
+ private void SaveMonitorTextQueueToFile()
+ {
+ try
+ {
+ if (_monitorTextQueue.IsEmpty)
+ {
+ _logger.LogInformation("监控文本队列为空,无需保存");
+ // 如果队列为空但文件存在,删除文件
+ if (File.Exists(_monitorTextQueueFilePath))
+ {
+ File.Delete(_monitorTextQueueFilePath);
+ _logger.LogInformation($"已删除空的监控文本队列文件: {_monitorTextQueueFilePath}");
+ }
+ return;
+ }
+
+ var items = _monitorTextQueue.Values.ToList();
+ string json = JsonConvert.SerializeObject(items, Formatting.Indented);
+
+ File.WriteAllText(_monitorTextQueueFilePath, json);
+ _logger.LogInformation($"成功保存监控文本队列到文件: {items.Count} 项");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"保存监控文本队列失败: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 获取当前音频传输设置
+ ///
+ /// 是否启用音频传输
+ public async Task GetAudioStreamingSetting()
{
if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
{
- _logger.LogWarning($"未注册的客户端尝试接收显示文本: {Context.ConnectionId}");
+ _logger.LogWarning($"未注册的客户端尝试获取音频传输设置: {Context.ConnectionId}");
+ throw new HubException("请先注册客户端");
+ }
+
+ _logger.LogInformation($"客户端获取当前音频传输设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
+ return _configurationService.CurrentConfig.Network.EnableAudioStreaming;
+ }
+
+ ///
+ /// 更新音频传输设置
+ ///
+ /// 是否启用音频传输
+ /// 处理任务
+ public async Task UpdateAudioStreaming(bool enabled)
+ {
+ if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
+ {
+ _logger.LogWarning($"未注册的客户端尝试更新音频传输设置: {Context.ConnectionId}");
await Clients.Caller.SendAsync("Error", "请先注册客户端");
return;
}
- if (clientInfo.ClientType != ClientType.Display)
+ if (clientInfo.ClientType != ClientType.Monitor && clientInfo.ClientType != ClientType.WebAdmin)
{
- _logger.LogWarning($"非显示端客户端尝试接收显示文本: {Context.ConnectionId}");
- await Clients.Caller.SendAsync("Error", "只有显示端可以接收显示文本");
+ _logger.LogWarning($"非监控或管理端客户端尝试更新音频传输设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
+ await Clients.Caller.SendAsync("Error", "只有监控或管理端客户端可以更新音频传输设置");
return;
}
- _logger.LogInformation($"显示端开始接收文本: {Context.ConnectionId}");
+ _logger.LogInformation($"更新音频传输设置: {Context.ConnectionId}, 新设置: {(enabled ? "开启" : "关闭")}");
- // 确保定时器已初始化
- InitializeDisplayTextTimer();
+ // 更新配置
+ _configurationService.CurrentConfig.Network.EnableAudioStreaming = enabled;
- // 如果队列为空,添加一条初始文本
- if (_displayTextQueue.IsEmpty)
+ // 通知其他客户端音频传输设置已更改
+ await Clients.Groups(new[] { "monitor", "webadmin" }).SendAsync("AudioStreamingChanged", enabled);
+
+ // 保存配置到文件
+ try
{
- AddFakeTextToQueue();
+ var success = _configurationService.SaveConfiguration();
+ if (success)
+ {
+ await Clients.Caller.SendAsync("AudioStreamingUpdated", true, $"音频传输设置已更新为: {(enabled ? "开启" : "关闭")}");
+ }
+ else
+ {
+ _logger.LogError("保存配置失败");
+ await Clients.Caller.SendAsync("AudioStreamingUpdated", false, "更新音频传输设置成功,但保存配置失败");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"保存配置到文件时出错: {ex.Message}");
+ await Clients.Caller.SendAsync("AudioStreamingUpdated", false, "更新音频传输设置成功,但保存配置失败");
+ }
+ }
+
+ ///
+ /// 释放资源
+ ///
+ /// 是否正在处理Dispose
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ // 保存监控文本队列到文件
+ SaveMonitorTextQueueToFile();
+
+ // 取消订阅所有事件
+ _speechToTextService.ResultReceived -= OnSpeechToTextResultReceived;
+ _audioProcessingService.SpeechToTextResultReceived -= OnSpeechToTextResultReceived;
+ _audioProcessingService.AudioSavedToFile -= OnAudioSavedToFile;
+ _configurationService.ConfigurationChanged -= OnConfigurationChanged;
+
+ _logger.LogDebug("已取消订阅所有事件");
+ }
+
+ _disposed = true;
+ }
+ }
+
+ ///
+ /// 释放资源
+ ///
+ public new void Dispose()
+ {
+ Dispose(true);
+ base.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// 配置变更事件处理
+ ///
+ /// 发送者
+ /// 配置
+ private async void OnConfigurationChanged(object sender, PhoneBoothConfig e)
+ {
+ try
+ {
+ _logger.LogInformation("配置已更新");
+
+ // 使用_hubContext替代Clients
+ await _hubContext.Clients.Group("controllers").SendAsync("ReceiveConfiguration", e);
+ await _hubContext.Clients.Group("webadmin").SendAsync("ReceiveConfiguration", e);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError($"处理配置变更事件时出错: {ex.Message}");
}
}
@@ -906,27 +1168,6 @@ namespace ShengShengBuXi.Hubs
}
}
- ///
- /// 配置变更事件处理
- ///
- /// 发送者
- /// 配置
- private async void OnConfigurationChanged(object sender, PhoneBoothConfig e)
- {
- try
- {
- _logger.LogInformation("配置已更新");
-
- // 使用_hubContext替代Clients
- await _hubContext.Clients.Group("controllers").SendAsync("ReceiveConfiguration", e);
- await _hubContext.Clients.Group("webadmin").SendAsync("ReceiveConfiguration", e);
- }
- catch (Exception ex)
- {
- _logger.LogError($"处理配置变更事件时出错: {ex.Message}");
- }
- }
-
///
/// 开始监听音频
///
@@ -1086,180 +1327,5 @@ namespace ShengShengBuXi.Hubs
_logger.LogInformation($"客户端获取当前显示模式: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
return _configurationService.CurrentConfig.DisplayType;
}
-
- ///
- /// 从文件加载监控文本队列
- ///
- private void LoadMonitorTextQueueFromFile()
- {
- try
- {
- if (!File.Exists(_monitorTextQueueFilePath))
- {
- _logger.LogInformation($"监控文本队列文件不存在: {_monitorTextQueueFilePath}");
- return;
- }
-
- string json = File.ReadAllText(_monitorTextQueueFilePath);
- if (string.IsNullOrEmpty(json))
- {
- _logger.LogWarning("监控文本队列文件内容为空");
- return;
- }
-
- var items = JsonConvert.DeserializeObject>(json);
- if (items == null || !items.Any())
- {
- _logger.LogWarning("没有从文件中读取到监控文本队列项");
- return;
- }
-
- // 清空现有队列,并添加从文件加载的项
- _monitorTextQueue.Clear();
- foreach (var item in items)
- {
- _monitorTextQueue.TryAdd(item.Id, item);
- }
-
- _logger.LogInformation($"成功从文件加载监控文本队列: {items.Count} 项");
- }
- catch (Exception ex)
- {
- _logger.LogError($"加载监控文本队列失败: {ex.Message}");
- }
- }
-
- ///
- /// 保存监控文本队列到文件
- ///
- private void SaveMonitorTextQueueToFile()
- {
- try
- {
- if (_monitorTextQueue.IsEmpty)
- {
- _logger.LogInformation("监控文本队列为空,无需保存");
- // 如果队列为空但文件存在,删除文件
- if (File.Exists(_monitorTextQueueFilePath))
- {
- File.Delete(_monitorTextQueueFilePath);
- _logger.LogInformation($"已删除空的监控文本队列文件: {_monitorTextQueueFilePath}");
- }
- return;
- }
-
- var items = _monitorTextQueue.Values.ToList();
- string json = JsonConvert.SerializeObject(items, Formatting.Indented);
-
- File.WriteAllText(_monitorTextQueueFilePath, json);
- _logger.LogInformation($"成功保存监控文本队列到文件: {items.Count} 项");
- }
- catch (Exception ex)
- {
- _logger.LogError($"保存监控文本队列失败: {ex.Message}");
- }
- }
-
- ///
- /// 获取当前音频传输设置
- ///
- /// 是否启用音频传输
- public async Task GetAudioStreamingSetting()
- {
- if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
- {
- _logger.LogWarning($"未注册的客户端尝试获取音频传输设置: {Context.ConnectionId}");
- throw new HubException("请先注册客户端");
- }
-
- _logger.LogInformation($"客户端获取当前音频传输设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
- return _configurationService.CurrentConfig.Network.EnableAudioStreaming;
- }
-
- ///
- /// 更新音频传输设置
- ///
- /// 是否启用音频传输
- /// 处理任务
- public async Task UpdateAudioStreaming(bool enabled)
- {
- if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo))
- {
- _logger.LogWarning($"未注册的客户端尝试更新音频传输设置: {Context.ConnectionId}");
- await Clients.Caller.SendAsync("Error", "请先注册客户端");
- return;
- }
-
- if (clientInfo.ClientType != ClientType.Monitor && clientInfo.ClientType != ClientType.WebAdmin)
- {
- _logger.LogWarning($"非监控或管理端客户端尝试更新音频传输设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}");
- await Clients.Caller.SendAsync("Error", "只有监控或管理端客户端可以更新音频传输设置");
- return;
- }
-
- _logger.LogInformation($"更新音频传输设置: {Context.ConnectionId}, 新设置: {(enabled ? "开启" : "关闭")}");
-
- // 更新配置
- _configurationService.CurrentConfig.Network.EnableAudioStreaming = enabled;
-
- // 通知其他客户端音频传输设置已更改
- await Clients.Groups(new[] { "monitor", "webadmin" }).SendAsync("AudioStreamingChanged", enabled);
-
- // 保存配置到文件
- try
- {
- var success = _configurationService.SaveConfiguration();
- if (success)
- {
- await Clients.Caller.SendAsync("AudioStreamingUpdated", true, $"音频传输设置已更新为: {(enabled ? "开启" : "关闭")}");
- }
- else
- {
- _logger.LogError("保存配置失败");
- await Clients.Caller.SendAsync("AudioStreamingUpdated", false, "更新音频传输设置成功,但保存配置失败");
- }
- }
- catch (Exception ex)
- {
- _logger.LogError($"保存配置到文件时出错: {ex.Message}");
- await Clients.Caller.SendAsync("AudioStreamingUpdated", false, "更新音频传输设置成功,但保存配置失败");
- }
- }
-
- ///
- /// 释放资源
- ///
- /// 是否正在处理Dispose
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- // 保存监控文本队列到文件
- SaveMonitorTextQueueToFile();
-
- // 取消订阅所有事件
- _speechToTextService.ResultReceived -= OnSpeechToTextResultReceived;
- _audioProcessingService.SpeechToTextResultReceived -= OnSpeechToTextResultReceived;
- _audioProcessingService.AudioSavedToFile -= OnAudioSavedToFile;
- _configurationService.ConfigurationChanged -= OnConfigurationChanged;
-
- _logger.LogDebug("已取消订阅所有事件");
- }
-
- _disposed = true;
- }
- }
-
- ///
- /// 释放资源
- ///
- public new void Dispose()
- {
- Dispose(true);
- base.Dispose();
- GC.SuppressFinalize(this);
- }
}
}
\ No newline at end of file
diff --git a/ShengShengBuXi/Pages/Index.cshtml b/ShengShengBuXi/Pages/Index.cshtml
index f4f7228..a5291ea 100644
--- a/ShengShengBuXi/Pages/Index.cshtml
+++ b/ShengShengBuXi/Pages/Index.cshtml
@@ -131,7 +131,7 @@
const CONFIG = {
// 左侧容器配置
leftContainer: {
- turnPageHeight: 0.7, // 左侧容器翻页的高度比例
+ turnPageHeight: 0.55, // 左侧容器翻页的高度比例
fontSize: '16px', // 左侧列表文字大小
typewriterSpeed: 50 // 左侧文字打字机速度(毫秒)
},
@@ -140,41 +140,24 @@
fontSize: '40px', // 右侧文字大小
fontWeight: '700', // 右侧文字粗细
fontStyle: 'italic', // 右侧文字样式
- typewriterSpeed: 50 // 右侧文字打字机速度(毫秒)
+ typewriterSpeed: 250 // 右侧文字打字机速度(毫秒),减慢到1/5
},
// 水波纹效果配置
waterEffect: {
- enabled: true, // 是否开启水波纹
- minInterval: 800, // 最小触发间隔(毫秒)
- maxInterval: 5000, // 最大触发间隔(毫秒)
- simultaneousDrops: 3 // 同时触发的波纹数量
+ enabled: true, // 是否开启水波纹
+ minInterval: 1600, // 最小触发间隔(毫秒),增加时间
+ maxInterval: 8000, // 最大触发间隔(毫秒),增加时间
+ simultaneousDrops: 2, // 同时触发的波纹数量,减少到2个
+ fadeOutSpeed: 2000, // 文字渐隐效果的速度(毫秒),原速度的2倍
+ centerBias: 0.6, // 涟漪靠近中心区域的偏好值(0-1),0为随机,1为只在中心区域
+ largeDrop: {
+ probability: 0.2, // 大涟漪出现的概率降低到0.2
+ size: 80 // 大涟漪的大小
+ }
},
// 预设的中文句子数组
sentences: [
- "记得每到夏天傍晚,您就摇着蒲扇坐在藤椅里,把切好的西瓜最甜那块硬塞给我,自己却啃着靠近皮的白瓤,还笑着说:外公就爱这口,清爽。",
- "女儿啊 花开了 你否到了吗?",
- "外公,你在那边过的还好么大家都很想称,记得常回家看看",
- "外公,今天窗台上的茉莉开了,白盈盈的,就像以前您总别在中山装口袋上的那朵。",
- "外公,巷口那家老茶馆拆了,您最爱坐的靠窗位置再也找不到了,就像再也找不到您一样。",
- "整理旧物时发现您用红绳缠好的象棋,每一处磨损都藏着您教我'马走日'时手心的温度。",
- "今早闻到槐花香突然站住——您总说这味道像极了老家后山的夏天,现在我才懂得什么叫'睹物思人'。",
- "菜场看见卖菱角的老伯,想起您总把最嫩的剥好放我碗里,自己却嚼着发苦的老根。",
- "暴雨天膝盖又疼了吧?记得您总在这时候熬姜汤,说'老寒腿最懂天气预报'。",
- "您养的那盆君子兰今年抽了七支花箭,比您走那年还多两枝,定是替您来看我的。",
- "小满那天不自觉煮了两人份的腊肉饭,盛完才想起再没人把肥肉挑走给我留精瘦的。",
- "儿童节路过小学,梧桐树下空荡荡的——再没有举着冰糖葫芦等我的驼背身影了。",
- "冬至包饺子时手一抖,捏出您教的麦穗花边,滚水冒的蒸汽突然就迷了眼睛。",
- "昨夜梦见您穿着洗白的蓝布衫,在晒谷场对我笑,醒来枕巾湿了半边。",
- "蝉鸣最响的午后,恍惚听见竹椅吱呀声,转头却只看见墙上相框里的您。",
- "您走后,再没人把枇杷核仔细包进手帕,笑着说'留着明年给囡囡种'。",
- "整理遗物时数出38张火车票,全是往返我读书城市的,票根都磨出了毛边。",
- "清明雨把墓碑冲洗得发亮,就像您当年总把搪瓷缸擦得能照见人影。",
- "今天教女儿念'慈母手中线',她突然问:'太外公的诗集能读给我听吗?'",
- "翻到您留下的老照片,那件洗得发白的中山装上还别着茉莉花,仿佛还能闻到淡淡的清香。",
- "傍晚的蝉鸣声里,总错觉能听到您哼着那首走了调的小曲,在巷子口唤我回家吃饭。",
- "您种的葡萄藤今年结了好多串,可再没有人像您那样,把最紫的摘下来悄悄塞进我口袋。",
- "下雨天膝盖隐隐作痛时,总会想起您泡的姜茶,热气氤氲中您笑着说'老了才知道疼'。",
- "整理书房时,发现您用毛笔在旧日历背面写的家训,墨迹晕染处都是您颤抖的手印。"
+
]
};
@@ -201,7 +184,7 @@
* @@param {Function} callback - 渐隐完成后的回调函数
*/
function fadeOutText(selector, callback) {
- $(selector).fadeOut(1000, function () {
+ $(selector).fadeOut(CONFIG.waterEffect.fadeOutSpeed, function () {
$(this).html('');
$(this).show();
if (callback) callback();
@@ -454,7 +437,7 @@
} else {
$('.water-effect').ripples({
resolution: 1024,
- dropRadius: 3,
+ dropRadius: 1.5,
perturbance: 0
});
@@ -464,9 +447,41 @@
*/
function createRippleTimer() {
return function randomRipple() {
- let x1 = Math.random() * $(".water-effect").width();
- let y1 = Math.random() * $(".water-effect").height();
- triggerRipple(".water-effect", x1, y1);
+ let width = $(".water-effect").width();
+ let height = $(".water-effect").height();
+
+ // 中心坐标
+ let centerX = width / 2;
+ let centerY = height / 2;
+
+ // 根据中心偏好生成随机位置
+ let randomX, randomY;
+
+ if (Math.random() > CONFIG.waterEffect.centerBias) {
+ // 随机位置
+ randomX = Math.random() * width;
+ randomY = Math.random() * height;
+
+ // 避免位于上1/3区域
+ if (randomY < height / 3) {
+ randomY += height / 3;
+ }
+ } else {
+ // 靠近中心位置
+ let radius = Math.min(width, height) * 0.3; // 中心区域半径
+ let angle = Math.random() * Math.PI * 2; // 随机角度
+ let distance = Math.random() * radius; // 随机距离,但不超过radius
+
+ randomX = centerX + Math.cos(angle) * distance;
+ randomY = centerY + Math.sin(angle) * distance;
+ }
+
+ // 决定是否创建大涟漪
+ let dropSize = Math.random() < CONFIG.waterEffect.largeDrop.probability ?
+ CONFIG.waterEffect.largeDrop.size : 50;
+
+ triggerRipple(".water-effect", randomX, randomY, dropSize);
+
let nextTime = Math.random() * (CONFIG.waterEffect.maxInterval - CONFIG.waterEffect.minInterval) + CONFIG.waterEffect.minInterval;
setTimeout(randomRipple, nextTime);
};
@@ -490,10 +505,34 @@
* @@param {string} selector - 目标元素选择器
* @@param {number} x - X坐标
* @@param {number} y - Y坐标
+ * @@param {number} size - 涟漪大小
*/
- function triggerRipple(selector, x, y) {
- $(selector).ripples("drop", x, y, 50, 0.3);
+ function triggerRipple(selector, x, y, size = 50) {
+ $(selector).ripples("drop", x, y, size, 0.15);
}
+
+ // 页面可见性变化检测,用于处理页面切换到后台时暂停涟漪效果
+ document.addEventListener('visibilitychange', function() {
+ if (document.hidden) {
+ // 页面不可见时,停止涟漪生成
+ console.log("页面切换到后台,停止涟漪生成");
+ // 可以添加额外逻辑来暂停或减少涟漪效果
+ } else {
+ // 页面可见时,可以恢复涟漪生成
+ console.log("页面回到前台,继续涟漪生成");
+ // 可以添加额外逻辑来恢复涟漪效果
+
+ // 如果需要,可以重置涟漪效果
+ if (CONFIG.waterEffect.enabled) {
+ $('.water-effect').ripples("destroy");
+ $('.water-effect').ripples({
+ resolution: 1024,
+ dropRadius: 1.5,
+ perturbance: 0
+ });
+ }
+ }
+ });