Skip to content

Latest commit

 

History

History
138 lines (108 loc) · 10.3 KB

File metadata and controls

138 lines (108 loc) · 10.3 KB

SimpleBroadcastingSystem (校园广播系统)

变更记录 (Changelog)

时间 变更内容
2026-05-14 10:00:50 初始创建:完成全量扫描,生成根级 CLAUDE.md 及 .claude/index.json
2026-07-07 v1.2.0:新增独占广播模式(AudioSessionGuard 持续静音其他所有程序)与设备隔离状态提示
2026-07-08 v1.3.0:批量音量、连续测试、批量星期设置与多选删除;播放会话 ID 防止 PlaybackStopped 串扰;审查修复(循环续播不再弹模态框、连续测试跳过缺失文件、切设备恢复失败自动复位、清理 AudioManager 死代码)
2026-07-08 v1.4.0:新增兼容/标准输出模式(WaveOut / WASAPI 共享)与设备 ID 寻址;标准模式失败关闭且不回落默认设备;音量改在音频流层调节,避免改写系统主音量;定时广播设备缺失不弹模态框,隔离提示识别标准模式设备不可用状态
2026-07-08 v1.5.0:新增 WASAPI 独占输出模式,复用设备 ID 寻址;标准/独占模式非播放切换与设备选择执行真实试开,播放中切换先停旧流再重开目标设备,失败保持原模式或失败关闭且不回落默认设备;独占播放按设备支持格式探测并重采样;音量继续只在 AudioFileReader.Volume 流层调节
2026-07-09 v1.6.0:新增底部全局播放控制栏,测试播放/连续测试支持暂停、继续、拖动定位和轨道点击定位,定时广播仅只读显示进度并保留停止入口;解析文件夹导入后台读取时长并统一保存,长时长统一显示为 hh:mm:ss;输出工具栏分组、关闭窗口先确认,系统关机/注销时跳过退出确认;暂停中切换输出模式/设备/方案先自动停止;独占重采样 seek 重建 provider
2026-07-09 v1.7.0:新增无人值守可靠性能力:广播播放日志与只读查看窗口、启动/手动方案文件体检、值守锁定开关与持久化;收紧独占 seek 的 PlaybackStopped 退订时序,新增键盘 seek 去抖和导入/体检互斥门控;继续保持定时广播无模态、日志失败不影响播放、体检不碰输出设备、失败关闭不回落默认设备、音量只在 AudioFileReader.Volume 调节
2026-07-09 测试基建:拆出跨平台 src/SchoolBroadcastSystem.Core(net8.0,链接编译共享源码),调度匹配纯逻辑抽到 BroadcastScheduleMatcher,新增 tests/ 下 xUnit 逻辑测试;BroadcastLogger 写线程改为首次写日志时懒启动;广播项排序改按解析后时间(不可解析排最后);CI 发布前强制先跑测试
2026-07-09 v1.7.1:工具栏重构与输出模式提示——新增「工具/帮助」菜单栏承接批量音量/解析文件夹导入/方案体检/播放日志/恢复全部声音,工具栏第一行只保留高频操作(添加/编辑/删除、测试播放/连续测试、值守锁定),第二行输出设置精简为「输出:」前缀 + 模式/设备下拉 + 独占广播勾选(标签缩短)+ 两个「?」按钮,第三行设备隔离警告独立成行;新增只读 OutputModeHelpWindow 展示三种输出模式对比表;OutputModeOption 加 Description,下拉项悬停提示与 ComboBox 自身 ToolTip 跟随当前模式(初始化即赋值);导入/体检进度文本改显示在底部状态栏 ToolProgressTextBlock;不改任何音频播放/调度逻辑

项目愿景

一个基于 C# WPF (.NET 8.0) 的桌面校园广播定时播放系统。用户可以配置多个广播方案(Profile),每个方案包含一组定时广播项(BroadcastItem),系统通过 Quartz.NET 调度器每秒检查当前时间并自动播放匹配的音频文件。适用于学校上下课铃声、课间音乐、通知广播等场景。

架构总览

WPF 主工程位于根目录,扁平结构、无分层模块划分。纯逻辑源码(数据模型、配置、日志、调度匹配)以链接编译方式进入 src/SchoolBroadcastSystem.Core(net8.0 跨平台),主工程对这些文件 Compile Remove 后引用 Core 项目;单元测试位于 tests/。新增共享源码文件时需同时改两处:主工程 csproj 的 Compile Remove 与 Core 工程 csproj 的 Compile Include。

技术栈:

  • 框架:.NET 8.0-windows, WPF
  • 音频播放:NAudio 2.2.1(兼容模式 WaveOutEvent;标准模式 WasapiOut 共享输出;独占模式 WasapiOut 独占输出;音量统一在 AudioFileReader 流层调节)
  • 任务调度:Quartz 3.14.0 (每秒轮询检查广播时间)
  • 配置持久化:Newtonsoft.Json 13.0.3 (JSON 文件序列化)
  • 数据验证:System.ComponentModel.DataAnnotations
  • CI/CD:GitHub Actions (PublishSingleFile, win-x64)

核心流程:

  1. 应用启动 -> 加载 broadcast_config.json -> 初始化 Quartz.NET 调度器
  2. 调度器每秒触发 CheckBroadcastJob
  3. Job 在 UI 线程上调用 MainWindow.CheckBroadcastSchedule(),比较当前时间与所有 BroadcastItem 的 TimeString
  4. 时间匹配且星期匹配时,调用 AudioManager.PlayFile() 播放音频
  5. 支持循环播放(LoopCount),播放结束事件驱动下一次循环

模块结构图

graph TD
    A["SimpleBroadcastingSystem"] --> B["数据模型层"]
    A --> C["业务逻辑层"]
    A --> D["UI 层"]
    A --> E["基础设施层"]

    B --> B1["BroadcastItem.cs"]
    B --> B2["BroadcastProfile.cs"]

    C --> C1["BroadcastScheduler.cs"]
    C --> C2["BroadcastJobs.cs"]
    C --> C3["AudioManager.cs"]
    C --> C4["AudioSessionGuard.cs"]

    D --> D1["MainWindow.xaml/.cs"]
    D --> D2["AddEditBroadcastWindow.xaml/.cs"]
    D --> D3["ProfileManagementWindow.xaml/.cs"]
    D --> D4["InputDialog.xaml/.cs"]
    D --> D5["BooleanConverters.cs"]

    E --> E1["AppConfig.cs / ConfigManager"]
    E --> E2["App.xaml/.cs"]
    E --> E3["AssemblyInfo.cs"]
Loading

模块索引

模块 职责 入口文件
数据模型层 定义 BroadcastItem(广播项)和 BroadcastProfile(广播方案)数据结构 BroadcastItem.cs, BroadcastProfile.cs
调度器 Quartz.NET 调度管理,每秒轮询检查广播时间匹配 BroadcastScheduler.cs
调度匹配 纯函数的时间/星期匹配、下次广播计算、排序(跨平台可测,BroadcastScheduler 委托它) BroadcastScheduleMatcher.cs
定时任务 Quartz.NET IJob 实现,在 UI 线程触发广播检查 BroadcastJobs.cs
音频管理 NAudio 封装,负责兼容/标准/独占输出模式、音频设备管理、播放、停止、流级音量控制 AudioManager.cs
音频会话守护 独占广播模式:静音本进程以外全部程序的声音(已运行 + 新启动),周期兜底重扫,退出保持静音 AudioSessionGuard.cs
配置管理 JSON 配置文件的加载、保存、方案同步 AppConfig.cs
主窗口 核心 UI,广播列表管理、方案切换、播放状态显示 MainWindow.xaml/.cs
添加/编辑窗口 广播项的新增与编辑对话框 AddEditBroadcastWindow.xaml/.cs
方案管理窗口 广播方案的新建、重命名、删除、激活 ProfileManagementWindow.xaml/.cs
输入对话框 通用文本输入弹窗 InputDialog.xaml/.cs
输出模式说明窗口 静态只读说明:三种输出模式对比表 + 补充说明,零业务逻辑 OutputModeHelpWindow.xaml/.cs
值转换器 BooleanToVisibility / BooleanToFontWeight WPF 转换器 BooleanConverters.cs

运行与开发

环境要求

  • .NET 8.0 SDK
  • Windows 操作系统(WPF 应用)

构建与运行

# 还原依赖
dotnet restore

# 调试运行
dotnet run

# 发布单文件可执行程序
dotnet publish SchoolBroadcastSystem.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ./publish

配置文件

  • 运行时配置存储在 broadcast_config.json(应用目录下),包含广播项列表、音频设备选择、方案列表
  • 首次运行时自动生成默认配置

测试策略

  • 测试项目:tests/SchoolBroadcastSystem.Tests(xUnit,net8.0,macOS/Linux 可跑),只引用 src/SchoolBroadcastSystem.Core,不碰 WPF 运行时
  • 覆盖范围:BroadcastScheduleMatcher 调度匹配与排序、ConfigManager 序列化往返与损坏配置恢复、BroadcastLogger tail 读取
  • 运行:dotnet test WpfApp1.sln;CI(release.yml)在发布前强制先跑测试,测试不过不发版
  • 测试串行执行(TestAssembly.cs 全局关闭并行),因 ConfigManager.ConfigFilePath 是静态可写的测试缝
  • 注意:定时触发的生产路径是 MainWindow.CheckBroadcastSchedule 里的 TimeString 字符串比较,未走 Matcher;要给触发逻辑加测试,需先把该路径接到 Matcher 并真机验证

编码规范

  • 命名空间:WpfApp1(与项目名不一致,为历史遗留)
  • 数据模型实现 INotifyPropertyChanged 接口
  • 配置使用 Newtonsoft.Json 序列化
  • 调度使用 Quartz.NET,Job 通过 Dispatcher.InvokeAsync 回到 UI 线程
  • 无依赖注入,无接口抽象层,直接实例化

AI 使用指引

  • 主工程源代码位于根目录;src/ 为跨平台 Core 工程(链接编译根目录共享文件),tests/ 为 xUnit 测试
  • 广播调度逻辑核心在 BroadcastScheduler.cs 的 CheckBroadcastSchedule() 方法(通过 MainWindow 调用)
  • 音频播放核心在 AudioManager.cs 的 PlayFile() 方法
  • 配置持久化核心在 AppConfig.cs 的 ConfigManager 类
  • 修改广播项属性时需注意 PropertyChanged 事件链:BroadcastItem -> MainWindow.BroadcastItem_PropertyChanged -> SaveConfiguration
  • 方案切换逻辑在 MainWindow.SwitchToProfile(),涉及广播列表重建和事件处理器重绑定

推荐下一步

  1. 统一命名空间 -- 项目名 SchoolBroadcastSystem 但命名空间为 WpfApp1,建议统一
  2. 提取接口 -- AudioManager 和 BroadcastScheduler 无接口抽象,不利于测试和替换
  3. HasTimeConflict 未实现 -- AddEditBroadcastWindow.HasTimeConflict() 始终返回 false,为占位实现
  4. 定时触发路径接入 Matcher -- MainWindow.CheckBroadcastSchedule 仍是 TimeString 字符串比较,接入 BroadcastScheduleMatcher 后触发逻辑才可被测试覆盖(改动播放路径,需真机验证)