diff --git a/ShengShengBuXi.ConsoleApp/AutoRestart.bat b/ShengShengBuXi.ConsoleApp/AutoRestart.bat index 8a9b661..dbc870f 100644 --- a/ShengShengBuXi.ConsoleApp/AutoRestart.bat +++ b/ShengShengBuXi.ConsoleApp/AutoRestart.bat @@ -3,7 +3,7 @@ chcp 936 setlocal enabledelayedexpansion :: Debug mode -set DEBUG=1 +set DEBUG=0 :: Configuration set APP_NAME=ShengShengBuXi.ConsoleApp.exe @@ -62,8 +62,6 @@ if !EXIT_CODE! neq 0 ( echo [WARNING] Program exited abnormally >> "%LOG_FILE%" echo [WARNING] Program exited with code: !EXIT_CODE! echo [INFO] Please check log file: %LOG_FILE% - echo Press any key to continue... - pause ) :: Restart delay diff --git a/ShengShengBuXi/Hubs/AudioHub.cs b/ShengShengBuXi/Hubs/AudioHub.cs index 67f0f5c..acbec4a 100644 --- a/ShengShengBuXi/Hubs/AudioHub.cs +++ b/ShengShengBuXi/Hubs/AudioHub.cs @@ -93,6 +93,10 @@ namespace ShengShengBuXi.Hubs private static string _displayConfig = "{}"; private static object _displayConfigLock = new object(); + // 控屏开关设置 + private static bool _manualScreenControlEnabled = false; + private static readonly string _screenControlSettingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config/screen_control.json"); + // 管理员配置保存路径 private static readonly string ConfigDirectory = Path.Combine(Directory.GetCurrentDirectory(), "config"); private static readonly string DisplayConfigPath = Path.Combine(ConfigDirectory, "display.json"); @@ -119,6 +123,9 @@ namespace ShengShengBuXi.Hubs // 加载显示配置 LoadDisplayConfig(); + + // 加载控屏设置 + LoadScreenControlSetting(); } // 加载显示配置 @@ -180,6 +187,51 @@ namespace ShengShengBuXi.Hubs } } + // 加载控屏设置 + private static void LoadScreenControlSetting() + { + try + { + if (File.Exists(_screenControlSettingPath)) + { + string json = File.ReadAllText(_screenControlSettingPath); + var setting = JsonConvert.DeserializeObject(json); + _manualScreenControlEnabled = setting?.IsManual ?? false; + Console.WriteLine($"加载控屏设置成功,当前模式: {(_manualScreenControlEnabled ? "手动" : "自动")}"); + } + else + { + // 默认设置为自动 + _manualScreenControlEnabled = false; + + // 保存默认设置 + SaveScreenControlSetting(); + Console.WriteLine("创建默认控屏设置成功"); + } + } + catch (Exception ex) + { + Console.WriteLine($"加载控屏设置出错: {ex.Message}"); + _manualScreenControlEnabled = false; + } + } + + // 保存控屏设置 + private static bool SaveScreenControlSetting() + { + try + { + string json = JsonConvert.SerializeObject(new { IsManual = _manualScreenControlEnabled }); + File.WriteAllText(_screenControlSettingPath, json); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"保存控屏设置出错: {ex.Message}"); + return false; + } + } + /// /// 初始化音频Hub /// @@ -987,6 +1039,19 @@ namespace ShengShengBuXi.Hubs return; } + // 在手动控屏模式下,只处理真实用户的消息 + if (_manualScreenControlEnabled && !highestPriority.Value.IsRealUser) + { + _logger.LogInformation("当前为手动控屏模式,跳过非真实用户消息"); + // 从队列中移除该消息,但不发送 + if (_displayTextQueue.TryRemove(highestPriority.Key, out _)) + { + // 返回空表示没有要显示的文本 + await Clients.Caller.SendAsync("ReceiveDisplayText", ""); + } + return; + } + // 从队列中移除该消息 if (_displayTextQueue.TryRemove(highestPriority.Key, out var textToDisplay)) { @@ -1591,9 +1656,9 @@ namespace ShengShengBuXi.Hubs } // 使用简单的序列化方式,将对象转换为单行文本 - string entry = JsonConvert.SerializeObject(new + string entry = JsonConvert.SerializeObject(new RealUserDisplayRecord { - displayText.Text, + Text = displayText.Text, Timestamp = displayText.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"), }); @@ -1607,5 +1672,112 @@ namespace ShengShengBuXi.Hubs _logger.LogError($"保存真实用户显示文本失败: {ex.Message}"); } } + + /// + /// 获取当前控屏设置 + /// + /// 是否为手动控屏模式 + public async Task GetScreenControlSetting() + { + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试获取控屏设置: {Context.ConnectionId}"); + throw new HubException("请先注册客户端"); + } + + _logger.LogInformation($"客户端获取当前控屏设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + return _manualScreenControlEnabled; + } + + /// + /// 更新控屏设置 + /// + /// 是否为手动控屏模式 + /// 更新结果 + public async Task UpdateScreenControlSetting(bool isManual) + { + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试更新控屏设置: {Context.ConnectionId}"); + throw new HubException("请先注册客户端"); + } + + if (clientInfo.ClientType != ClientType.Monitor && clientInfo.ClientType != ClientType.WebAdmin) + { + _logger.LogWarning($"非监控端或管理端尝试更新控屏设置: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + throw new HubException("只有监控端或管理端可以更新控屏设置"); + } + + // 设置模式 + _manualScreenControlEnabled = isManual; + + // 保存设置 + bool success = SaveScreenControlSetting(); + + if (success) + { + // 通知所有监控客户端更新设置 + await Clients.Group("monitor").SendAsync("ScreenControlSettingChanged", isManual); + + _logger.LogInformation($"控屏设置已更新为: {(isManual ? "手动" : "自动")}"); + } + + return success; + } + + /// + /// 获取真实用户聊天记录 + /// + /// 用户聊天记录列表 + public async Task> GetRealUserChatRecords() + { + if (!_clients.TryGetValue(Context.ConnectionId, out var clientInfo)) + { + _logger.LogWarning($"未注册的客户端尝试获取真实用户聊天记录: {Context.ConnectionId}"); + throw new HubException("请先注册客户端"); + } + + if (clientInfo.ClientType != ClientType.WebAdmin) + { + _logger.LogWarning($"非管理端尝试获取真实用户聊天记录: {Context.ConnectionId}, 类型: {clientInfo.ClientType}"); + throw new HubException("只有管理端可以获取真实用户聊天记录"); + } + + var records = new List(); + + try + { + if (File.Exists(_realUserDisplayLogsPath)) + { + var lines = await File.ReadAllLinesAsync(_realUserDisplayLogsPath); + foreach (var line in lines) + { + try + { + if (!string.IsNullOrWhiteSpace(line)) + { + var record = JsonConvert.DeserializeObject(line); + records.Add(record); + } + } + catch (Exception ex) + { + _logger.LogError($"解析真实用户聊天记录行失败: {ex.Message}, 行内容: {line}"); + } + } + } + else + { + _logger.LogWarning($"真实用户聊天记录文件不存在: {_realUserDisplayLogsPath}"); + } + } + catch (Exception ex) + { + _logger.LogError($"获取真实用户聊天记录失败: {ex.Message}"); + throw new HubException($"获取真实用户聊天记录失败: {ex.Message}"); + } + + return records; + } } } \ No newline at end of file diff --git a/ShengShengBuXi/Models/RealUserDisplayRecord.cs b/ShengShengBuXi/Models/RealUserDisplayRecord.cs new file mode 100644 index 0000000..daa97e9 --- /dev/null +++ b/ShengShengBuXi/Models/RealUserDisplayRecord.cs @@ -0,0 +1,20 @@ +using System; + +namespace ShengShengBuXi.Models +{ + /// + /// 真实用户显示记录模型 + /// + public class RealUserDisplayRecord + { + /// + /// 显示文本内容 + /// + public string Text { get; set; } + + /// + /// 显示时间戳 + /// + public string Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/ShengShengBuXi/Pages/Admin.cshtml b/ShengShengBuXi/Pages/Admin.cshtml index cdb67ee..6c79a10 100644 --- a/ShengShengBuXi/Pages/Admin.cshtml +++ b/ShengShengBuXi/Pages/Admin.cshtml @@ -43,6 +43,9 @@ +
@@ -419,6 +422,32 @@
+ + +
+
+
+ +
+ +
+ + + + + + + + + + + + + +
序号时间内容
暂无用户记录
+
+
+
@@ -526,6 +555,7 @@ // 获取客户端列表 //getClientList(); setTimeout(getRecentRecordings, 1000); + setTimeout(getRealUserRecords, 1500); }) .catch(function(err) { log("注册为管理员客户端失败: " + err); @@ -1098,9 +1128,17 @@ // 录音保存 connection.on("AudioSavedToFile", (filePath) => { - log("录音已保存: " + filePath); - addRecordingToList(filePath); - showMessage("新录音: " + filePath, "success"); + log("录音保存到文件: " + filePath); + showMessage("录音已保存到文件", "success"); + + // 刷新录音列表 + setTimeout(getRecentRecordings, 1000); + }); + + // 监听用户记录响应 + connection.on("ReceiveRealUserRecords", (records) => { + log("接收到真实用户记录,数量:" + (records ? records.length : 0)); + updateUserRecordsList(records); }); // 配置更新结果 @@ -1261,47 +1299,6 @@ } } - // 添加录音到列表 - function addRecordingToList(filePath) { - const tableBody = document.getElementById("recording-list"); - - // 如果是"暂无录音"行,则清空 - if (tableBody.innerHTML.includes("暂无录音")) { - tableBody.innerHTML = ""; - } - - // 从文件路径获取文件名 - const fileName = filePath.split('/').pop(); - - // 创建新行 - const row = document.createElement("tr"); - row.className = "table-success"; - - const timestamp = new Date().toLocaleString(); - - row.innerHTML = ` - ${fileName} - 未知 - ${timestamp} - - 播放 - 下载 - - `; - - // 添加到顶部 - if (tableBody.firstChild) { - tableBody.insertBefore(row, tableBody.firstChild); - } else { - tableBody.appendChild(row); - } - - // 恢复正常样式 - setTimeout(() => { - row.className = ""; - }, 3000); - } - // 更新录音列表 function updateRecordingsList(recordings) { log("接收到录音列表,数量:" + (recordings ? recordings.length : 0)); @@ -1548,6 +1545,62 @@ showMessage("已重置为默认配置,请点击保存按钮保存", "info"); } + // 获取真实用户记录 + function getRealUserRecords() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + showMessage("无法获取用户记录:未连接到服务器", "warning"); + return; + } + + log("正在获取真实用户记录列表..."); + + connection.invoke("GetRealUserChatRecords") + .then((records) => { + log("已成功获取真实用户记录,数量:" + (records ? records.length : 0)); + updateUserRecordsList(records); + }) + .catch(err => { + log("获取真实用户记录失败: " + err); + showMessage("获取真实用户记录失败: " + err, "danger"); + }); + } + + // 更新用户记录列表 + function updateUserRecordsList(records) { + const recordsList = document.getElementById("user-records-list"); + if (!recordsList) return; + + if (!records || records.length === 0) { + recordsList.innerHTML = `暂无用户记录`; + return; + } + + // 清空列表 + recordsList.innerHTML = ""; + + // 倒序显示记录,最新的在最上面 + records.reverse().forEach((record, index) => { + const row = document.createElement("tr"); + + // 序号列 + const indexCell = document.createElement("td"); + indexCell.textContent = index + 1; + row.appendChild(indexCell); + + // 时间列 + const timeCell = document.createElement("td"); + timeCell.textContent = record.timestamp || "未知时间"; + row.appendChild(timeCell); + + // 内容列 + const contentCell = document.createElement("td"); + contentCell.textContent = record.text || ""; + row.appendChild(contentCell); + + recordsList.appendChild(row); + }); + } + // 页面加载完成后初始化 document.addEventListener("DOMContentLoaded", function() { log("页面已加载"); diff --git a/ShengShengBuXi/Pages/Index.cshtml b/ShengShengBuXi/Pages/Index.cshtml index 8f88435..f3917a1 100644 --- a/ShengShengBuXi/Pages/Index.cshtml +++ b/ShengShengBuXi/Pages/Index.cshtml @@ -328,6 +328,15 @@ */ function displayText(text) { if (isAnimating) return; + + // 如果接收到空文本,表示在手动控屏模式下跳过了非真实用户消息 + if (!text) { + console.log("接收到空文本,请求下一条文本"); + // 延迟一段时间后请求下一条文本 + const delay = Math.floor(Math.random() * 3000) + 3000; // 4000-6000毫秒 + setTimeout(requestNextText, delay); + return; + } var currentPage = $("#magazine").turn("page"); let r_id = '#r_container_' + (currentPage + 1); diff --git a/ShengShengBuXi/Pages/Monitor.cshtml b/ShengShengBuXi/Pages/Monitor.cshtml index d28c8fe..d11f401 100644 --- a/ShengShengBuXi/Pages/Monitor.cshtml +++ b/ShengShengBuXi/Pages/Monitor.cshtml @@ -50,6 +50,15 @@ id="volumeControl" style="width: 100px;"> + + +
+ + + + + +
@@ -212,6 +221,9 @@ // 当前播放的音频元素和按钮引用 let currentAudio = null; let currentPlayButton = null; + + // 控屏开关状态 + let isManualScreenControl = false; // 实时音频相关变量 let audioContext = null; @@ -356,6 +368,9 @@ // 获取当前音频流设置 getServerAudioStreamingSetting(); + + // 获取当前控屏设置 + getServerScreenControlSetting(); // 设置定时刷新显示列表 if (refreshDisplayInterval) { @@ -470,6 +485,23 @@ showMessage(message, "danger"); } }); + + // 控屏设置更新消息 + connection.on("ScreenControlSettingChanged", (isManual) => { + log(`服务器控屏设置已更改为: ${isManual ? "手动" : "自动"}`); + updateScreenControlUI(isManual); + }); + + // 控屏设置更新结果 + connection.on("ScreenControlSettingUpdated", (success, message) => { + if (success) { + log(`控屏设置更新成功: ${message}`); + showMessage(message, "success"); + } else { + log(`控屏设置更新失败: ${message}`); + showMessage(message, "danger"); + } + }); } // 更新通话状态 @@ -910,24 +942,108 @@ }); } - // 页面加载完成后初始化 - document.addEventListener("DOMContentLoaded", function () { - log("页面已加载"); + // 设置音频流开关监听器 + function setupAudioStreamingListeners() { + const audioStreamingButtons = document.querySelectorAll('input[name="audioStreaming"]'); + audioStreamingButtons.forEach(radio => { + radio.addEventListener('change', function () { + const enabled = parseInt(this.value) === 1; + isAudioStreamEnabled = enabled; + log(`音频流已切换为: ${enabled ? "开启" : "关闭"}`); - // 添加Bootstrap Icons样式 - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css'; - document.head.appendChild(link); + // 如果开启了音频流,初始化音频上下文 + if (enabled && callInProgress) { + initAudioContext(); + } + // 调用服务器端方法更新音频流设置 + if (connection && connection.state === signalR.HubConnectionState.Connected) { + connection.invoke("UpdateAudioStreaming", enabled) + .then(() => { + showMessage(`已${enabled ? "开启" : "关闭"}音频传输`, "success"); + }) + .catch(err => { + log(`更新音频流设置失败: ${err}`); + showMessage("切换音频传输失败", "danger"); + + // 恢复原来的选择 + const currentValue = enabled ? 0 : 1; + document.getElementById(`audioStreaming${currentValue}`).checked = true; + isAudioStreamEnabled = !enabled; + }); + } else { + log("无法更新音频流设置:未连接到服务器"); + showMessage("无法更新音频流设置:未连接到服务器", "warning"); + } + }); + }); + } + + // 设置控屏开关监听器 + function setupScreenControlListeners() { + const screenControlButtons = document.querySelectorAll('input[name="screenControl"]'); + screenControlButtons.forEach(radio => { + radio.addEventListener('change', function () { + const isManual = parseInt(this.value) === 1; + isManualScreenControl = isManual; + log(`控屏模式已切换为: ${isManual ? "手动" : "自动"}`); + + // 调用服务器端方法更新控屏设置 + if (connection && connection.state === signalR.HubConnectionState.Connected) { + connection.invoke("UpdateScreenControlSetting", isManual) + .then(() => { + showMessage(`已切换为${isManual ? "手动" : "自动"}控屏模式`, "success"); + }) + .catch(err => { + log(`更新控屏设置失败: ${err}`); + showMessage("切换控屏模式失败", "danger"); + + // 恢复原来的选择 + const currentValue = isManual ? 0 : 1; + document.getElementById(`screenControl${currentValue === 0 ? 'Auto' : 'Manual'}`).checked = true; + }); + } + }); + }); + } + + // 设置音量控制监听器 + function setupVolumeControlListener() { + const volumeControl = document.getElementById('volumeControl'); + if (volumeControl) { + volumeControl.addEventListener('input', function (e) { + currentVolume = parseFloat(e.target.value); + log(`音量已调整为: ${currentVolume * 100}%`); + + // 如果已经创建了增益节点,立即应用音量设置 + if (audioGainNode) { + audioGainNode.gain.value = currentVolume; + } + + // 同时调整已播放的录音音量 + if (currentAudio) { + currentAudio.volume = currentVolume; + } + }); + } + } + + // 初始化页面 + document.addEventListener('DOMContentLoaded', function () { // 初始化SignalR连接 initSignalR(); - // 设置显示模式和音频流监听器 + // 设置显示模式监听器 setupDisplayModeListeners(); + // 设置音频流开关监听器 + setupAudioStreamingListeners(); + + // 设置控屏开关监听器 + setupScreenControlListeners(); + // 设置音量控制监听器 - setupVolumeControl(); + setupVolumeControlListener(); // 初始化工具提示 setTimeout(initTooltips, 1000); @@ -1660,5 +1776,35 @@ log(`已添加文本到监控列表: ${shortText}...`); } + + // 更新控屏UI + function updateScreenControlUI(isManual) { + // 更新单选按钮状态 + document.getElementById(isManual ? 'screenControlManual' : 'screenControlAuto').checked = true; + isManualScreenControl = isManual; + + log(`UI控屏模式已更新为: ${isManual ? "手动" : "自动"}`); + } + + // 获取服务器当前的控屏设置 + function getServerScreenControlSetting() { + if (!connection || connection.state !== signalR.HubConnectionState.Connected) { + log("无法获取控屏设置:未连接"); + return; + } + + log("获取服务器当前控屏设置..."); + + connection.invoke("GetScreenControlSetting") + .then(isManual => { + log(`获取到服务器控屏设置: ${isManual ? "手动" : "自动"}`); + updateScreenControlUI(isManual); + }) + .catch(err => { + log(`获取控屏设置失败: ${err}`); + // 默认设置为自动 + updateScreenControlUI(false); + }); + } }