ModMusic.vb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. Public Module ModMusic
  2. #Region "播放列表"
  3. ''' <summary>
  4. ''' 接下来要播放的音乐文件路径。未初始化时为 Nothing。
  5. ''' </summary>
  6. Public MusicToplayList As List(Of String) = Nothing
  7. ''' <summary>
  8. ''' 全部音乐文件路径。未初始化时为 Nothing。
  9. ''' </summary>
  10. Public MusicAllList As List(Of String) = Nothing
  11. ''' <summary>
  12. ''' 初始化音乐播放列表。
  13. ''' </summary>
  14. ''' <param name="ForceReload">强制全部重新载入列表。</param>
  15. ''' <param name="IgnoreFirst">在重载列表时避免让某项成为第一项。</param>
  16. Private Sub MusicListInit(ForceReload As Boolean, Optional IgnoreFirst As String = "")
  17. If ForceReload Then MusicAllList = Nothing
  18. Try
  19. '初始化全部可用音乐列表
  20. If MusicAllList Is Nothing Then
  21. MusicAllList = New List(Of String)
  22. Directory.CreateDirectory(Path & "PCL\Musics\")
  23. For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*")
  24. '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
  25. Dim Extend As String = File.Split(".").Last.ToLower
  26. If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "png") Then
  27. MusicAllList.Add(File)
  28. End If
  29. Next
  30. End If
  31. '打乱顺序播放
  32. MusicToplayList = RandomChaos(New List(Of String)(MusicAllList))
  33. If Not IgnoreFirst = "" AndAlso Not MusicToplayList.Count = 0 AndAlso MusicToplayList(0) = IgnoreFirst Then
  34. '若需要避免成为第一项的为第一项,则将它放在最后
  35. MusicToplayList.RemoveAt(0)
  36. MusicToplayList.Add(IgnoreFirst)
  37. End If
  38. Catch ex As Exception
  39. Log(ex, "初始化音乐列表失败", LogLevel.Feedback)
  40. End Try
  41. End Sub
  42. ''' <summary>
  43. ''' 获取下一首播放的音乐路径并将其从列表中移除。
  44. ''' </summary>
  45. Private Function DequeueNextMusicAddress() As String
  46. '初始化,确保存在音乐
  47. If MusicAllList Is Nothing Then MusicListInit(False)
  48. If MusicAllList.Count = 0 Then Throw New Exception("在没有音乐时尝试获取音乐路径")
  49. '出列下一个音乐,如果出列结束则生成新列表
  50. DequeueNextMusicAddress = MusicToplayList(0)
  51. MusicToplayList.RemoveAt(0)
  52. If MusicToplayList.Count = 0 Then MusicListInit(False, DequeueNextMusicAddress)
  53. End Function
  54. #End Region
  55. #Region "UI 控制"
  56. ''' <summary>
  57. ''' 刷新背景音乐按钮 UI 与设置页 UI。
  58. ''' </summary>
  59. Private Sub MusicRefreshUI()
  60. RunInUi(Sub()
  61. Try
  62. If MusicAllList.Count = 0 Then
  63. '无背景音乐
  64. FrmMain.BtnExtraMusic.Show = False
  65. Else
  66. '有背景音乐
  67. FrmMain.BtnExtraMusic.Show = True
  68. Dim ToolTipText As String = "正在播放:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
  69. If MusicState = MusicStates.Pause Then
  70. FrmMain.BtnExtraMusic.Logo = Logo.IconPlay
  71. FrmMain.BtnExtraMusic.LogoScale = 0.8
  72. If MusicAllList.Count > 1 Then ToolTipText += vbCrLf & "左键播放,右键播放下一曲。"
  73. Else
  74. FrmMain.BtnExtraMusic.Logo = Logo.IconMusic
  75. FrmMain.BtnExtraMusic.LogoScale = 1
  76. If MusicAllList.Count > 1 Then ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。"
  77. End If
  78. FrmMain.BtnExtraMusic.ToolTip = ToolTipText
  79. ToolTipService.SetVerticalOffset(FrmMain.BtnExtraMusic, If(ToolTipText.Contains(vbLf), 10, 16))
  80. End If
  81. If FrmSetupUI IsNot Nothing Then FrmSetupUI.MusicRefreshUI()
  82. Catch ex As Exception
  83. Log(ex, "刷新背景音乐 UI 失败", LogLevel.Feedback)
  84. End Try
  85. End Sub)
  86. End Sub
  87. ''' <summary>
  88. ''' 让音乐在暂停、播放间切换,并显示提示文本。
  89. ''' </summary>
  90. Public Sub MusicControlPause()
  91. If MusicNAudio Is Nothing Then
  92. Hint("音乐播放尚未开始!", HintType.Critical)
  93. Else
  94. Select Case MusicState
  95. Case MusicStates.Pause
  96. MusicResume()
  97. Case MusicStates.Play
  98. MusicPause()
  99. Case Else
  100. Hint("音乐目前为停止状态!", HintType.Critical)
  101. End Select
  102. End If
  103. End Sub
  104. ''' <summary>
  105. ''' 播放下一曲,并显示提示文本。
  106. ''' </summary>
  107. Public Sub MusicControlNext()
  108. If MusicAllList.Count = 1 Then
  109. Hint("播放列表中仅有一首歌曲!")
  110. Else
  111. Dim Address As String = DequeueNextMusicAddress()
  112. MusicStartPlay(Address)
  113. Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Finish)
  114. MusicRefreshUI()
  115. End If
  116. End Sub
  117. #End Region
  118. #Region "主状态控制"
  119. ''' <summary>
  120. ''' 获取当前的音乐播放状态。
  121. ''' </summary>
  122. Public ReadOnly Property MusicState As MusicStates
  123. Get
  124. If MusicNAudio Is Nothing Then Return MusicStates.Stop
  125. Select Case MusicNAudio.PlaybackState
  126. Case 0 'NAudio.Wave.PlaybackState.Stopped
  127. Return MusicStates.Stop
  128. Case 2 'NAudio.Wave.PlaybackState.Paused
  129. Return MusicStates.Pause
  130. Case Else
  131. Return MusicStates.Play
  132. End Select
  133. End Get
  134. End Property
  135. Public Enum MusicStates
  136. [Stop]
  137. Play
  138. Pause
  139. End Enum
  140. '重载与开始
  141. ''' <summary>
  142. ''' 重载播放列表并尝试开始播放背景音乐。
  143. ''' </summary>
  144. ''' <param name="ShowHint">是否显示刷新提示。</param>
  145. Public Sub MusicRefreshPlay(ShowHint As Boolean, Optional IsFirstLoad As Boolean = False)
  146. Try
  147. MusicListInit(True)
  148. If MusicAllList.Count = 0 Then
  149. If MusicNAudio Is Nothing Then
  150. If ShowHint Then Hint("未检测到可用的背景音乐!", HintType.Critical)
  151. Else
  152. MusicNAudio = Nothing
  153. If ShowHint Then Hint("背景音乐已清除!", HintType.Finish)
  154. End If
  155. Else
  156. Dim Address As String = DequeueNextMusicAddress()
  157. Try
  158. MusicStartPlay(Address, IsFirstLoad)
  159. If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False)
  160. Catch
  161. End Try
  162. End If
  163. MusicRefreshUI()
  164. Catch ex As Exception
  165. Log(ex, "刷新背景音乐播放失败", LogLevel.Feedback)
  166. End Try
  167. End Sub
  168. ''' <summary>
  169. ''' 开始播放音乐。
  170. ''' </summary>
  171. Private Sub MusicStartPlay(Address As String, Optional IsFirstLoad As Boolean = False)
  172. Log("[Music] 播放开始:" & Address)
  173. MusicCurrent = Address
  174. RunInNewThread(Sub() MusicLoop(IsFirstLoad), "Music", ThreadPriority.BelowNormal)
  175. End Sub
  176. '播放与暂停
  177. ''' <summary>
  178. ''' 暂停音乐播放,返回是否成功切换了状态。
  179. ''' </summary>
  180. Public Function MusicPause() As Boolean
  181. If MusicState = MusicStates.Play Then
  182. RunInThread(Sub()
  183. MusicNAudio.Pause()
  184. MusicRefreshUI()
  185. Log("[Music] 已暂停播放")
  186. End Sub)
  187. Return True
  188. Else
  189. Return False
  190. End If
  191. End Function
  192. ''' <summary>
  193. ''' 继续音乐播放,返回是否成功切换了状态。
  194. ''' </summary>
  195. Public Function MusicResume() As Boolean
  196. If MusicState = MusicStates.Pause Then
  197. RunInThread(Sub()
  198. MusicNAudio.Play()
  199. MusicRefreshUI()
  200. Log("[Music] 已恢复播放")
  201. End Sub)
  202. Return True
  203. Else
  204. Return False
  205. End If
  206. End Function
  207. #End Region
  208. ''' <summary>
  209. ''' 当前正在播放的 NAudio.Wave.WaveOut。
  210. ''' </summary>
  211. Public MusicNAudio = Nothing
  212. ''' <summary>
  213. ''' 当前播放的音乐地址。
  214. ''' </summary>
  215. Private MusicCurrent As String = ""
  216. ''' <summary>
  217. ''' 在 MusicUuid 不变的前提下,持续播放某地址的音乐,且在播放结束后随机播放下一曲。
  218. ''' </summary>
  219. Private Sub MusicLoop(Optional IsFirstLoad As Boolean = False)
  220. Dim CurrentWave As NAudio.Wave.WaveOut = Nothing
  221. Dim Reader As NAudio.Wave.WaveStream = Nothing
  222. Try
  223. '开始播放
  224. CurrentWave = New NAudio.Wave.WaveOut()
  225. MusicNAudio = CurrentWave
  226. Reader = New NAudio.Wave.AudioFileReader(MusicCurrent)
  227. CurrentWave.Init(Reader)
  228. CurrentWave.Play()
  229. '第一次打开的暂停
  230. If IsFirstLoad AndAlso Not Setup.Get("UiMusicAuto") Then CurrentWave.Pause()
  231. MusicRefreshUI()
  232. '停止条件:播放完毕或变化
  233. Dim PreviousVolume = 0
  234. While CurrentWave.Equals(MusicNAudio) AndAlso Not CurrentWave.PlaybackState = NAudio.Wave.PlaybackState.Stopped
  235. If Setup.Get("UiMusicVolume") <> PreviousVolume Then
  236. '更新音量
  237. PreviousVolume = Setup.Get("UiMusicVolume")
  238. CurrentWave.Volume = PreviousVolume / 1000
  239. End If
  240. Thread.Sleep(50)
  241. End While
  242. '当前音乐已播放结束,继续下一曲
  243. If CurrentWave.PlaybackState = NAudio.Wave.PlaybackState.Stopped AndAlso MusicAllList.Count > 0 Then MusicStartPlay(DequeueNextMusicAddress)
  244. Catch ex As Exception
  245. If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
  246. Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL2 不支持播放音频属性在中途发生变化的音乐", HintType.Critical)
  247. ElseIf Not (MusicCurrent.ToLower.EndsWith(".wav") OrElse MusicCurrent.ToLower.EndsWith(".mp3") OrElse MusicCurrent.ToLower.EndsWith(".flac")) Then
  248. Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL2 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Critical)
  249. Else
  250. Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint)
  251. End If
  252. Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer)
  253. If MusicAllList.Count > 1 Then
  254. Thread.Sleep(1000)
  255. MusicStartPlay(DequeueNextMusicAddress)
  256. End If
  257. Finally
  258. If CurrentWave IsNot Nothing Then CurrentWave.Dispose()
  259. If Reader IsNot Nothing Then Reader.Dispose()
  260. MusicRefreshUI()
  261. End Try
  262. End Sub
  263. End Module