
0. 开个新系列趣味项目工作、生活、课程设计中我们总会冒出一些奇奇怪怪的点子——它们未必实用却足够有趣完全可以用代码把它们实现出来。这个系列就是为那些爱玩又爱折腾的童鞋准备的。别让那些奇思妙想只停留在脑海里动手把它写出来那才是正经事呢。第一篇先从一个很接地气的点子开始随机播放一首歌让大家来猜歌名。触发的条件有两种一个是按钮点击播放另一个是手机触碰NFC读卡器播放。1. 描述下需求在 Windows 上选好本地音乐目录手机或 NFC 标签靠近读卡器时程序随机挑一首播放。歌名先隐藏等大家猜好后点击按钮显示歌名公布答案。2. 整体规划程序是一个 Windows 桌面应用WPF .NET 9逻辑可以简单画成这样手机/标签 ──碰── USB NFC 读卡器 │ ▼ NfcWatcherPC/SC 监听 │ 防抖 / UID 校验 ▼ PlaybackService随机选歌 播放 │ ▼ 主界面 / 托盘封面、进度、猜歌模式几个模块分工MusicLibrary扫目录收集 mp3 / wav / flac / m4a 等NfcWatcher盯着 PC/SC 读卡器卡片出现就读 UIDPlaybackService随机抽歌、控制暂停停止、读进度TrayService最小化到托盘后台继续待命3. 简单说说NFCNFCNear Field Communication近场通信就是一种让两个设备靠近到几厘米内就能交换数据的短距离无线技术。目前绝大多数手机都自带NFC功能。本次用它是一个小亮点手机刷一下或者NFC卡片刷一下就触发播放对于一些场景来说是很酷的事情。在 Windows 上常见 USB的NFC读卡器比如 ACR122U走的是PC/SC标准系统把读卡器当「智能卡读卡器」来管程序用库去监听「有没有卡靠近」。本项目用的是PCSC/PCSC.Iso7816这一套。NfcWatcher干的活可以概括成枚举读卡器监听卡片插入 / 出现发 APDU 读 UID手机当 NFC 标签贴近时一般也能读到防抖同一张卡短时间内重复触发直接忽略防抖为什么重要因为「贴一下」在硬件层常常是连续多次事件。没有防抖我们的程序就会像机关枪一样连发触碰播放。4. 再来说说随机播放「随机」听起来简单Random抽一首就行。但是细节是魔鬼我们在实现中要注意刚播过的歌又抽到体感很差像假随机格式五花八门mp3、flac、m4a播放器要尽量都支持所以PlaybackService里做了个策略记最近若干首路径抽到「刚播过」的就再抽。播放层用NAudio。界面上还有个开关显示歌名 / 隐藏歌名。显示正常听歌封面、标题、歌手都显示隐藏标题变成「??? 猜歌中」封面也藏起来5. NAudio 简介在 .NET 桌面端本地音频播放常见方案里NAudio是用得最多的开源库之一。简单理解成三层概念在本项目里对应干什么AudioFileReader读文件打开 mp3/wav/flac 等解码成 PCM还能设音量、SeekIWavePlayer本项目用WaveOutEvent播出去把 PCM 推给声卡PlaybackStopped事件播完回调区分「自然播完」和「手动 Stop」最小播放代码如下usingvarreadernewAudioFileReader(D:\music\demo.mp3){Volume0.8f};usingvarplayernewWaveOutEvent();player.Init(reader);player.Play();就这几行一首歌就能播放出来。本项目的PlaybackService本质上就是在这个骨架外增加了随机选曲、最近播放去重、暂停/停止、进度条 Seek、以及线程安全的状态上报。为什么不直接用系统自带的MediaPlayer相比NAudio在格式支持、Seek、音量、播完事件这些细节上NAudio 更可控些所以我选择了它。6. 核心逻辑代码路径都在src/RandomSongNfc/Services/下。6.1 扫目录MusicLibrary先把本地目录扫成曲目列表。扩展名白名单 递归扫描privatestaticreadonlyHashSetstringSupportedExtensionsnew(StringComparer.OrdinalIgnoreCase){.mp3,.wav,.flac,.m4a,.aac,.wma,.ogg};varfilesDirectory.EnumerateFiles(directory,*.*,SearchOption.AllDirectories).Where(pathSupportedExtensions.Contains(Path.GetExtension(path))).OrderBy(pathpath,StringComparer.OrdinalIgnoreCase).ToList();每首歌再用 TagLibSharp 读标题、歌手、封面读失败就回退到文件名。6.2 随机抽歌PlayRandom()的核心不是Next()本身而是最近播放队列privatereadonlyQueuestring_recentPathsnew();privateconstintRecentHistorySize10;publicTrackInfoPlayRandom(){vartracks_library.Tracks;// 先排除最近播过的varcandidatestracks.Where(t!_recentPaths.Contains(t.FilePath)).ToList();// 全排除光了清空历史if(candidates.Count0){candidatestracks.ToList();_recentPaths.Clear();}varpickcandidates[_random.Next(candidates.Count)];Play(pick);returnpick;}短时间内不太容易抽到刚听过的那几首曲库播一轮「新鲜的」之后再自然放开。6.3 播放AudioFileReader WaveOutEvent切歌时先暂停后播放新歌并且用锁保护——UI 线程、定时器、NFC 回调都可能碰播放器publicvoidPlay(TrackInfotrack){lock(_gate){StopInternal();_readernewAudioFileReader(track.FilePath){Volume_volume};_playernewWaveOutEvent();_player.Init(_reader);_player.PlaybackStoppedOnPlaybackStopped;_currenttrack;_player.Play();_recentPaths.Enqueue(track.FilePath);while(_recentPaths.CountRecentHistorySize)_recentPaths.Dequeue();}RaiseStatusChanged();}暂停/继续if(_player.PlaybackStateNAudio.Wave.PlaybackState.Playing)_player.Pause();elseif(_player.PlaybackStateNAudio.Wave.PlaybackState.Paused)_player.Play();6.4 NFC 触发插卡 → 读 UID → 防抖 → 通知播放监听用 PC/SC 的CardInserted。贴卡后先读 UID常见的FF CA 00 00取 UID APDU再决定接不接受这次触碰privatevoidOnCardInserted(objectsender,CardStatusEventArgsargs){varuidTryReadUid(args.ReaderName)??UNKNOWN;LastUiduid;if(!ShouldAcceptTap(uid))return;TapDetected?.Invoke(this,newNfcTapEventArgs{ReaderNameargs.ReaderName,Uiduid});}防抖 可选白名单在ShouldAcceptTap// 未授权直接丢if(requireAuth!authorized.Contains(NormalizeUid(uid)))returnfalse;// 同一 UID 在防抖窗口内重复到达 → 忽略if((now-lastTap).TotalMillisecondsdebounceMsstring.Equals(lastUid,uid,StringComparison.OrdinalIgnoreCase))returnfalse;主窗口收到TapDetected后再调PlayRandom()。读 UID 的 APDUPC/SC 读卡器上很常见varapdunewCommandApdu(IsoCase.Case2Short,isoReader.ActiveProtocol){CLA0xFF,Instruction(InstructionCode)0xCA,// GET DATAP10x00,P20x00,Le0x00};varresponseisoReader.Transmit(apdu);成功就把返回字节格式化成十六进制 UID 字符串。7. 怎么用硬件侧准备一台 Windows 10/11 x64 PC插上 USB NFC 读卡器装好驱动设备管理器里应出现「智能卡读卡器」下载发布包直接运行RandomSongNfc.exe自包含单文件不用再装 .NET如果没有也不要紧就直接手动点击按钮来播放。软件侧点「浏览…」选本地音乐目录顶部小圆点变绿 读卡器已连接手机背面或 NFC 标签贴一下读卡器 → 随机开播想常驻关窗口会进托盘右键托盘图标可退出其他勾选「播完自动随机下一首」随机播放器了属于是勾选「播放中忽略 NFC」听歌时别人乱碰也不切歌嗯不播放完不能打断8. 最后如果你在移动办公中遇到扫描场景可以使用我的小程序「程序员Linc表格识别」主打高清免费无水印的扫描最新版本的扫描功能已经支持了导出增强锐化、黑白、加深、灰度等PDF文件上去就可以用嘎嘎好使。欢迎大家试用。本趣味项目的开源仓库在这里欢迎做二创https://gitee.com/lincyang/random-song-playback-via-nfc如果想直接获取exe文件# 网盘获取 公众号聊天输入猜歌名欢迎拿走用、改、提建议。有其他趣味项目选题请在评论区讨论#趣味项目 #NFC #随机播放 #WPF #dotnet #课程设计