Files

473 lines
15 KiB
C#
Raw Permalink Normal View History

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
{
/// <summary>
/// 当前正在播放的 NAudio.Wave.WaveOutEvent。
/// </summary>
public static WaveOutEvent musicNAudio;
/// <summary>
/// 当前播放的音乐地址。
/// </summary>
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 播放列表
/// <summary>
/// 接下来要播放的音乐文件路径。未初始化时为 Nothing。
/// </summary>
public static List<string> musicWaitingList;
/// <summary>
/// 全部音乐文件路径。未初始化时为 Nothing。
/// </summary>
public static List<string> musicAllList;
/// <summary>
/// 初始化音乐播放列表。
/// </summary>
/// <param name="forceReload">强制全部重新载入列表。</param>
/// <param name="preventFirst">在重载列表时避免让某项成为第一项。</param>
private static void MusicListInit(bool forceReload, string preventFirst = null)
{
if (forceReload)
musicAllList = null;
try
{
if (musicAllList is null)
{
musicAllList = new List<string>();
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<string>(musicAllList));
else
musicWaitingList = new List<string>(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"));
}
}
/// <summary>
/// 获取下一首播放的音乐路径并将其从列表中移除。
/// 如果没有,可能会返回 Nothing。
/// </summary>
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 控制
/// <summary>
/// 刷新背景音乐按钮 UI 与设置页 UI。
/// </summary>
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
}