using System.IO; using System.Windows.Controls; using NAudio; using NAudio.Wave; using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.Utils; namespace PCL; public static class ModMusic { /// /// 当前正在播放的 NAudio.Wave.WaveOutEvent。 /// public static WaveOutEvent musicNAudio; /// /// 当前播放的音乐地址。 /// private static string musicCurrent = ""; private static void MusicLoop(bool isFirstLoad = false) { WaveOutEvent currentWave = null; AudioFileReader reader = null; try { currentWave = new WaveOutEvent(); musicNAudio = currentWave; currentWave.DeviceNumber = -1; // 使用默认设备 reader = new AudioFileReader(musicCurrent); currentWave.Init(reader); currentWave.Play(); // 首次加载且用户未启用自动播放,则暂停 if (isFirstLoad && !Config.Preference.Music.StartOnStartup) currentWave.Pause(); MusicRefreshUI(); var lastVolume = Config.Preference.Music.Volume; currentWave.Volume = lastVolume / 1000.0f; // 播放主循环 while (currentWave.Equals(musicNAudio) && currentWave.PlaybackState != PlaybackState.Stopped) { // 音量动态更新 var currentVolume = Config.Preference.Music.Volume; if (currentVolume != lastVolume) { lastVolume = currentVolume; currentWave.Volume = currentVolume / 1000.0f; } // 更新进度条 if (reader.TotalTime.TotalMilliseconds > 0d) { var progress = reader.CurrentTime.TotalMilliseconds / reader.TotalTime.TotalMilliseconds; ModBase.RunInUi(() => ModMain.frmMain.BtnExtraMusic.Progress = progress); } Thread.Sleep(100); } // 播放结束,继续下一首 if (currentWave.PlaybackState == PlaybackState.Stopped && (musicAllList?.Any() is { } arg5 ? arg5 : (bool?)null).GetValueOrDefault()) MusicStartPlay(DequeueNextMusicAddress(), isFirstLoad); } catch (Exception ex) { ModBase.Log(ex, $"播放音乐出现内部错误({musicCurrent})", ModBase.LogLevel.Developer); // 错误处理:精准提示用户 var fileName = ModBase.GetFileNameFromPath(musicCurrent); if (ex is MmException) { var msg = ex.Message; if (msg.Contains("AlreadyAllocated")) HintService.Hint(Lang.Text("Music.Error.DeviceBusy"), HintType.Error); else if (msg.Contains("NoDriver") || msg.Contains("BadDeviceId")) HintService.Hint(Lang.Text("Music.Error.DeviceChanged"), HintType.Error); else ModBase.Log( ex, $"播放失败({fileName})", ModBase.LogLevel.Hint, userSummary: Lang.Text("Music.Error.PlaybackFailed", fileName)); } else if (ex.Message.Contains("Got a frame at sample rate") || ex.Message.Contains("does not support changes to")) { HintService.Hint( Lang.Text("Music.Error.PropertyChangeUnsupported", fileName), HintType.Error); } else if ((!musicCurrent.EndsWithF(".wav", true) && !musicCurrent.EndsWithF(".mp3", true) && !musicCurrent.EndsWithF(".flac", true)) || ex.Message.Contains("0xC00D36C4")) { HintService.Hint( Lang.Text("Music.Error.UnsupportedFormat", fileName), HintType.Error); } else { ModBase.Log( ex, $"播放失败({fileName})", ModBase.LogLevel.Hint, userSummary: Lang.Text("Music.Error.PlaybackFailed", fileName)); } // 移除无效文件 musicAllList?.Remove(musicCurrent); musicWaitingList?.Remove(musicCurrent); MusicRefreshUI(); Thread.Sleep(2000); // 尝试播放下一首 if (ex is FileNotFoundException) MusicRefreshPlay(true, isFirstLoad); else MusicStartPlay(DequeueNextMusicAddress(), isFirstLoad); } finally { currentWave?.Dispose(); reader?.Dispose(); MusicRefreshUI(); } } #region 播放列表 /// /// 接下来要播放的音乐文件路径。未初始化时为 Nothing。 /// public static List musicWaitingList; /// /// 全部音乐文件路径。未初始化时为 Nothing。 /// public static List musicAllList; /// /// 初始化音乐播放列表。 /// /// 强制全部重新载入列表。 /// 在重载列表时避免让某项成为第一项。 private static void MusicListInit(bool forceReload, string preventFirst = null) { if (forceReload) musicAllList = null; try { if (musicAllList is null) { musicAllList = new List(); var musicDir = Path.Combine(ModBase.exePath, "PCL", "Musics"); Directory.CreateDirectory(musicDir); foreach (var file in ModBase.EnumerateFiles(musicDir)) { var ext = file.Extension.ToLowerInvariant(); // 忽略非音频文件 if (new[] { ".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png" }.Contains(ext)) continue; musicAllList.Add(file.FullName); } } // 根据设置决定是否随机 if (Config.Preference.Music.ShufflePlayback) musicWaitingList = RandomUtils.Shuffle(new List(musicAllList)); else musicWaitingList = new List(musicAllList); // 避免 PreventFirst 成为第一项 if (preventFirst is not null && musicWaitingList.Count > 0 && string.Equals(musicWaitingList[0], preventFirst, StringComparison.OrdinalIgnoreCase)) { musicWaitingList.RemoveAt(0); musicWaitingList.Add(preventFirst); } } catch (Exception ex) { ModBase.Log( ex, "初始化音乐列表失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Music.Error.OperationFailed")); } } /// /// 获取下一首播放的音乐路径并将其从列表中移除。 /// 如果没有,可能会返回 Nothing。 /// private static string DequeueNextMusicAddress() { if (musicAllList is null || !musicAllList.Any() || !musicWaitingList.Any()) MusicListInit(false); if (musicWaitingList.Any()) { var nextMusic = musicWaitingList[0]; musicWaitingList.RemoveAt(0); if (!musicWaitingList.Any()) MusicListInit(false, nextMusic); return nextMusic; } return null; } #endregion #region UI 控制 /// /// 刷新背景音乐按钮 UI 与设置页 UI。 /// private static void MusicRefreshUI() { ModBase.RunInUi(() => { try { if ((musicAllList?.Any() is { } arg1 ? arg1 : null) == false) { ModMain.frmMain.BtnExtraMusic.Show = false; } else { ModMain.frmMain.BtnExtraMusic.Show = true; var fileName = ModBase.GetFileNameWithoutExtentionFromPath(musicCurrent); var isSingle = musicAllList.Count == 1; string tipText; if (MusicState == MusicStates.Pause) { ModMain.frmMain.BtnExtraMusic.SvgIcon = "lucide/play"; ModMain.frmMain.BtnExtraMusic.LogoScale = 0.8d; tipText = Lang.Text( isSingle ? "Music.ToolTip.Paused.Single" : "Music.ToolTip.Paused.Multiple", fileName); } else { ModMain.frmMain.BtnExtraMusic.SvgIcon = "lucide/music"; ModMain.frmMain.BtnExtraMusic.LogoScale = 1d; tipText = Lang.Text( isSingle ? "Music.ToolTip.Playing.Single" : "Music.ToolTip.Playing.Multiple", fileName); } ModMain.frmMain.BtnExtraMusic.ToolTip = tipText; ToolTipService.SetVerticalOffset(ModMain.frmMain.BtnExtraMusic, tipText.Contains("\n") ? 10 : 16); } ModMain.frmSetupUI?.MusicRefreshUI(); } catch (Exception ex) { ModBase.Log( ex, "刷新背景音乐 UI 失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Music.Error.OperationFailed")); } }); } public static void MusicControlPause() { if (musicNAudio is null) { HintService.Hint(Lang.Text("Music.Error.NotStarted"), HintType.Error); return; } switch (MusicState) { case MusicStates.Pause: { MusicResume(); break; } case MusicStates.Play: { MusicPause(); // Stop break; } default: { ModBase.Log("[Music] 音乐目前为停止状态,已强制尝试开始播放", ModBase.LogLevel.Debug); MusicRefreshPlay(false); break; } } } public static void MusicControlNext() { if (musicAllList?.Count is { } arg2 && arg2 == 1) { MusicStartPlay(musicCurrent); HintService.Hint(Lang.Text("Music.Hint.Replaying", ModBase.GetFileNameFromPath(musicCurrent)), HintType.Success); } else { var addr = DequeueNextMusicAddress(); if (addr is null) { HintService.Hint(Lang.Text("Music.Error.NoAvailable"), HintType.Error); } else { MusicStartPlay(addr); HintService.Hint(Lang.Text("Music.Hint.Playing", ModBase.GetFileNameFromPath(addr)), HintType.Success); } } MusicRefreshUI(); } #endregion #region 主状态控制 public static MusicStates MusicState { get { if (musicNAudio is null) return MusicStates.Stop; return musicNAudio.PlaybackState == PlaybackState.Paused ? MusicStates.Pause : musicNAudio.PlaybackState == PlaybackState.Stopped ? MusicStates.Stop : MusicStates.Play; } } public enum MusicStates { Stop, Play, Pause } public static void MusicRefreshPlay(bool showHint, bool isFirstLoad = false) { try { MusicListInit(true); if ((musicAllList?.Any() is { } arg3 ? arg3 : null) == false) { if (musicNAudio is not null) { musicNAudio = null; if (showHint) HintService.Hint(Lang.Text("Music.Hint.Cleared"), HintType.Success); } else if (showHint) { HintService.Hint(Lang.Text("Music.Error.NotDetected"), HintType.Error); } } else { var addr = DequeueNextMusicAddress(); if (addr is null) { if (showHint) HintService.Hint(Lang.Text("Music.Error.NoAvailable"), HintType.Error); } else { try { MusicStartPlay(addr, isFirstLoad); if (showHint) HintService.Hint( Lang.Text("Music.Hint.Refreshed", ModBase.GetFileNameFromPath(addr)), HintType.Success, false); } catch { // 容错:播放失败已在 MusicLoop 中处理 } } } MusicRefreshUI(); } catch (Exception ex) { ModBase.Log( ex, "刷新背景音乐播放失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Music.Error.OperationFailed")); } } private static void MusicStartPlay(string address, bool isFirstLoad = false) { if (string.IsNullOrEmpty(address)) return; ModBase.Log("[Music] 播放开始:" + address); musicCurrent = address; ModBase.RunInNewThread(() => MusicLoop(isFirstLoad), "Music", ThreadPriority.BelowNormal); } public static bool MusicPause() { if (MusicState != MusicStates.Play) { ModBase.Log($"[Music] 无需暂停播放,当前状态为 {MusicState}"); return false; } ModBase.RunInThread(() => { ModBase.Log("[Music] 已暂停播放"); musicNAudio?.Pause(); MusicRefreshUI(); }); return true; } public static bool MusicResume() { if (MusicState == MusicStates.Play || musicAllList.Count == 0) { ModBase.Log($"[Music] 无需继续播放,当前状态为 {MusicState}"); return false; } ModBase.RunInThread(() => { ModBase.Log("[Music] 已恢复播放"); try { musicNAudio?.Play(); } catch { // 参考 PR #5415:设备变更后需 Stop + Play musicNAudio?.Stop(); musicNAudio?.Play(); } MusicRefreshUI(); }); return true; } #endregion }