MainForm.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  1. using System.CodeDom;
  2. using System.ComponentModel;
  3. using System.IO;
  4. using System.Text;
  5. using System.Threading.Channels;
  6. using Bmp.Core.Common.EventBus;
  7. using Bmp.Core.Common.Utility;
  8. using Bmp.Core.Common.Win32;
  9. using Bmp.Core.NAudioExt;
  10. using Bmp.Core.Playback.Inputs;
  11. using Bmp.Core.Playback.Outputs;
  12. using Bmp.WinForms.SaveLoad;
  13. using Bmp.WinForms.SaveLoad.Models;
  14. using EnumsNET;
  15. using Microsoft.Extensions.Logging.Abstractions;
  16. using NAudio.Wave;
  17. using AsioOut = Bmp.Core.Playback.Outputs.NAudioASIO.AsioOut;
  18. namespace Bmp.WinForms
  19. {
  20. public partial class MainForm : Form
  21. {
  22. private const string EMOJI_X = "❌";
  23. private const string EMOJI_WARN = "⚠";
  24. private const string EMOJI_PLAY_BIG = "▶";
  25. private const string EMOJI_PLAY_SMALL = "⏵";
  26. private static readonly IReadOnlyCollection<string> SupportedDropTypes = new[] { DataFormats.FileDrop, DataFormats.Text };
  27. private readonly Channel<string> _pendingAddToList = Channel.CreateUnbounded<string>();
  28. private IServiceProvider? _serviceProvider;
  29. private ILogger<MainForm> _logger = new Logger<MainForm>(NullLoggerFactory.Instance);
  30. private IEventBus? _eventBus;
  31. private SaveLoadService? _sl;
  32. private bool _isRunning;
  33. private ListViewItem? _currentListViewItem;
  34. private IOutputDeviceInfo? _selectedOutputDevice;
  35. private WaveStream? _inputSource;
  36. private VisualizeDataMiddleWrap? _visualizeDataMiddleWrap;
  37. private IWavePlayer? _outputDevice;
  38. private bool _nativeDsd;
  39. private UIPlaybackState _playbackState;
  40. private bool _trackBarHolding;
  41. public MainForm()
  42. {
  43. Initialize();
  44. InitializeComponent();
  45. }
  46. private void Initialize()
  47. {
  48. Text = Const.AppTitle;
  49. var eIcon = IconExtractor.GetMainIcon();
  50. if (null != eIcon)
  51. {
  52. Icon = eIcon;
  53. }
  54. }
  55. public void SetServiceProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
  56. // ----------------- playback control -----------------
  57. private async Task LoadItemAsync(ListViewItem item)
  58. {
  59. MainPanel.Enabled = false;
  60. await StopAsync();
  61. _playbackState = UIPlaybackState.Loading;
  62. _currentListViewItem = item;
  63. var finalState = EMOJI_PLAY_BIG;
  64. var balloonShow = false;
  65. ToolTipIcon? balloonIcon = null;
  66. string? balloonTitle = null;
  67. string? balloonContent = null;
  68. bool? flagPlayOk = null;
  69. try
  70. {
  71. var meta = await Task.Run(() => InputSourceProvider.ReadMeta(item.Name));
  72. if (meta.Error == null)
  73. {
  74. var fileName = Path.GetFileNameWithoutExtension(item.Name);
  75. if (Uri.TryCreate(item.Name, UriKind.Absolute, out var url))
  76. {
  77. fileName = Path.GetFileNameWithoutExtension(url.LocalPath);
  78. }
  79. item.SubItems[TitleColumnHeader.Index].Text = meta.Title ?? fileName;
  80. if (!string.IsNullOrEmpty(meta.Artist)) item.SubItems[TitleColumnHeader.Index].Text += " - " + meta.Artist;
  81. if (meta.Album != null && meta.TrackNo.HasValue)
  82. {
  83. item.SubItems[TitleColumnHeader.Index].Text =
  84. $"{meta.Album} #{meta.TrackNo:00} - " + item.SubItems[TitleColumnHeader.Index].Text;
  85. }
  86. item.SubItems[DurColumnHeader.Index].Text = meta.Duration.ToString("hh\\:mm\\:ss");
  87. var localVarPath = Uri.UnescapeDataString( item.Name);
  88. item.ToolTipText = localVarPath;
  89. if (meta.RawTags != null)
  90. {
  91. foreach (var pair in meta.RawTags.OrderBy(p => p.Key))
  92. {
  93. item.ToolTipText += $"{Environment.NewLine}【{pair.Key}】{pair.Value.Trim()}";
  94. }
  95. }
  96. }
  97. else
  98. {
  99. finalState = EMOJI_WARN;
  100. item.ToolTipText =
  101. item.Name
  102. + Environment.NewLine
  103. + "无法获取元数据"
  104. + Environment.NewLine
  105. + meta.Error.Message;
  106. }
  107. try
  108. {
  109. await ReloadSource();
  110. }
  111. catch (Exception e)
  112. {
  113. finalState = EMOJI_X;
  114. item.ToolTipText =
  115. item.Name
  116. + Environment.NewLine
  117. + "无法加载文件"
  118. + Environment.NewLine
  119. + e.Message;
  120. _playbackState = UIPlaybackState.Error;
  121. return;
  122. }
  123. flagPlayOk = await PlayAsync();
  124. }
  125. catch (Exception ex)
  126. {
  127. finalState = EMOJI_X;
  128. _playbackState = UIPlaybackState.Error;
  129. Console.WriteLine(ex);
  130. }
  131. finally
  132. {
  133. MainPanel.Enabled = true;
  134. if (flagPlayOk == null)
  135. {
  136. item.SubItems[StateColumnHeader.Index].Text = finalState;
  137. if (balloonShow)
  138. {
  139. SettingButtonToolTip.ToolTipIcon = balloonIcon ?? ToolTipIcon.None;
  140. SettingButtonToolTip.ToolTipTitle = balloonTitle;
  141. if (balloonContent != null)
  142. {
  143. SettingButtonToolTip.Show(balloonContent, SettingButton);
  144. SettingButtonToolTip.Show(balloonContent, SettingButton);
  145. }
  146. }
  147. else
  148. {
  149. SettingButtonToolTip.Hide(SettingButton);
  150. }
  151. }
  152. }
  153. }
  154. private async Task ReloadSource()
  155. {
  156. _inputSource = await Task.Run(() => InputSourceProvider.CreateWaveStream(_currentListViewItem!.Name, _sl?.State?.DecodeDsdToPcm == true));
  157. }
  158. private async Task DeInitOutputDeviceAsync()
  159. {
  160. var local = _outputDevice;
  161. _outputDevice = null;
  162. if (local != null)
  163. {
  164. local.PlaybackStopped -= _outputDevice_PlaybackStopped;
  165. local.Stop();
  166. if (local is AsioOut ao)
  167. {
  168. //try reset device
  169. if (_nativeDsd) ao.SetNativeDsd(false);
  170. ao.Driver.SetSampleRate(44100);
  171. }
  172. local.Dispose();
  173. }
  174. }
  175. private async Task<bool> ReInitOutputDevice()
  176. {
  177. await DeInitOutputDeviceAsync();
  178. var finalState = EMOJI_PLAY_BIG;
  179. bool balloonShow = false;
  180. ToolTipIcon? balloonIcon = null;
  181. string? balloonTitle = null;
  182. string? balloonContent = null;
  183. try
  184. {
  185. if (_inputSource is DsdAbstractSourceStream)
  186. {
  187. _nativeDsd = true;
  188. if (_sl?.State?.SelectedDsdAsioOutputDeviceId == null)
  189. {
  190. finalState = EMOJI_X;
  191. balloonShow = true;
  192. balloonIcon = ToolTipIcon.Error;
  193. balloonTitle = "未指定 DSD 输出设备";
  194. balloonContent = "在设置菜单选择 DSD 输出设备";
  195. _playbackState = UIPlaybackState.Error;
  196. return false;
  197. }
  198. var allDevices = OutputDeviceProvider.GetAllSupportedDevices();
  199. IOutputDeviceInfo? selectedDevice = null;
  200. selectedDevice = allDevices.FirstOrDefault(p => p.Id == _sl?.State?.SelectedDsdAsioOutputDeviceId);
  201. if (selectedDevice == null)
  202. {
  203. //TODO: 消息机制 错误 找不到指定的输出设备
  204. balloonShow = true;
  205. balloonIcon = ToolTipIcon.Warning;
  206. balloonTitle = "找不到指定的 DSD 输出设备";
  207. balloonContent = "在设置菜单选择 DSD 输出设备";
  208. _playbackState = UIPlaybackState.Error;
  209. return false;
  210. }
  211. _selectedOutputDevice = selectedDevice;
  212. }
  213. else
  214. {
  215. _nativeDsd = false;
  216. if (_sl?.State?.SelectedPcmOutputDeviceId == null)
  217. {
  218. //TODO: 消息机制 错误 未指定输出设备
  219. finalState = EMOJI_X;
  220. balloonShow = true;
  221. balloonIcon = ToolTipIcon.Error;
  222. balloonTitle = "未指定 PCM 输出设备";
  223. balloonContent = "在设置菜单选择 PCM 输出设备";
  224. _playbackState = UIPlaybackState.Error;
  225. return false;
  226. }
  227. var allDevices = OutputDeviceProvider.GetAllSupportedDevices();
  228. IOutputDeviceInfo? selectedDevice = null;
  229. selectedDevice = allDevices.FirstOrDefault(p => p.Id == _sl.State.SelectedPcmOutputDeviceId);
  230. if (selectedDevice == null)
  231. {
  232. //TODO: 消息机制 错误 找不到指定的输出设备
  233. balloonShow = true;
  234. balloonIcon = ToolTipIcon.Warning;
  235. balloonTitle = "找不到指定的 PCM 输出设备";
  236. balloonContent = "在设置菜单选择 PCM 输出设备";
  237. _playbackState = UIPlaybackState.Error;
  238. return false;
  239. }
  240. _selectedOutputDevice = selectedDevice;
  241. }
  242. _visualizeDataMiddleWrap = new VisualizeDataMiddleWrap(_inputSource!);
  243. _visualizeDataMiddleWrap.DataTransferred += _visualizeDataMiddleWrap_DataTransferred;
  244. IWavePlayer od;
  245. try
  246. {
  247. od = OutputDeviceProvider.CreateWavePlayer(_selectedOutputDevice, _nativeDsd);
  248. }
  249. catch (Exception e)
  250. {
  251. //TODO: 消息机制 错误 无法打开设备
  252. finalState = EMOJI_X;
  253. balloonShow = true;
  254. balloonIcon = ToolTipIcon.Error;
  255. balloonTitle = "无法启动播放";
  256. balloonContent = "无法打开输出设备" + Environment.NewLine + e.Message;
  257. _playbackState = UIPlaybackState.Error;
  258. return false;
  259. }
  260. try
  261. {
  262. od.Init(_visualizeDataMiddleWrap);
  263. }
  264. catch (Exception e)
  265. {
  266. //TODO: 消息机制 错误 无法初始化设备
  267. finalState = EMOJI_X;
  268. balloonShow = true;
  269. balloonIcon = ToolTipIcon.Error;
  270. balloonTitle = "无法启动播放";
  271. balloonContent = "无法初始化输出设备" + Environment.NewLine + e.Message;
  272. return false;
  273. }
  274. _outputDevice = od;
  275. _outputDevice.PlaybackStopped += _outputDevice_PlaybackStopped;
  276. }
  277. catch (Exception e)
  278. {
  279. //TODO: 异常处理 ReInitOutputDevice
  280. Console.WriteLine(e);
  281. throw;
  282. }
  283. finally
  284. {
  285. _currentListViewItem!.SubItems[StateColumnHeader.Index].Text = finalState;
  286. if (balloonShow)
  287. {
  288. SettingButtonToolTip.ToolTipIcon = balloonIcon ?? ToolTipIcon.None;
  289. SettingButtonToolTip.ToolTipTitle = balloonTitle;
  290. if (balloonContent != null)
  291. {
  292. SettingButtonToolTip.Show(balloonContent, SettingButton);
  293. SettingButtonToolTip.Show(balloonContent, SettingButton);
  294. }
  295. }
  296. else
  297. {
  298. SettingButtonToolTip.Hide(SettingButton);
  299. }
  300. }
  301. return true;
  302. }
  303. private async Task StopAsync()
  304. {
  305. _playbackState = UIPlaybackState.Stopped;
  306. await DeInitOutputDeviceAsync();
  307. if (_inputSource != null) await _inputSource.DisposeAsync();
  308. _inputSource = null;
  309. if (_currentListViewItem != null)
  310. {
  311. var subItem = _currentListViewItem.SubItems[StateColumnHeader.Index];
  312. if (subItem.Text == EMOJI_PLAY_BIG) subItem.Text = "";
  313. }
  314. }
  315. private async Task<bool> PlayAsync()
  316. {
  317. if (_inputSource == null) return false;
  318. _playbackState = UIPlaybackState.Playing;
  319. if (await ReInitOutputDevice())
  320. {
  321. try
  322. {
  323. _outputDevice!.Play();
  324. }
  325. catch (Exception e)
  326. {
  327. //TODO: 异常处理 PlayAsync
  328. Console.WriteLine(e);
  329. _playbackState = UIPlaybackState.Error;
  330. }
  331. }
  332. return _playbackState == UIPlaybackState.Playing;
  333. }
  334. private async Task PauseAsync()
  335. {
  336. _playbackState = UIPlaybackState.Paused;
  337. await DeInitOutputDeviceAsync();
  338. }
  339. private async Task TrackPrevAsync()
  340. {
  341. //TODO: 上一曲(按播放模式)
  342. if (_currentListViewItem is not { ListView: { } }) return;
  343. var nextIndex = _currentListViewItem.Index - 1;
  344. if (nextIndex > 0) await LoadItemAsync(MainListView.Items[nextIndex]);
  345. }
  346. private async Task TrackNextAsync()
  347. {
  348. //TODO: 下一曲(按播放模式)
  349. if (_currentListViewItem?.ListView == null) return;
  350. var nextIndex = _currentListViewItem.Index + 1;
  351. if (MainListView.Items.Count > nextIndex) await LoadItemAsync(MainListView.Items[nextIndex]);
  352. else _playbackState = UIPlaybackState.Stopped; //列表最后一项时
  353. }
  354. // ----------------- playback event -----------------
  355. private void _visualizeDataMiddleWrap_DataTransferred(object? sender, VisualizeDataEventArgs e)
  356. {
  357. //TODO: 视觉效果
  358. }
  359. private async void _outputDevice_PlaybackStopped(object? sender, StoppedEventArgs e)
  360. {
  361. if (_playbackState == UIPlaybackState.Seeking) return;
  362. if (_playbackState == UIPlaybackState.Paused) return;
  363. if (e.Exception == null)
  364. {
  365. //TODO: 正常播放结束 下一曲(按播放模式)
  366. await TrackNextAsync();
  367. }
  368. else
  369. {
  370. //TODO: 消息机制 因为发生错误而停止
  371. _playbackState = UIPlaybackState.Error;
  372. }
  373. }
  374. // ----------------- UI func -----------------
  375. private void SaveState()
  376. {
  377. if (_sl == null) return;
  378. if (_sl.State != null)
  379. {
  380. _sl.State.FormPosition = Location;
  381. _sl.State.FormSize = Size;
  382. _sl.State.Playlist = MainListView.Items.Cast<ListViewItem>().Select(p => new SaveLoadPlaylistItem
  383. {
  384. Path = p.Name,
  385. Title = p.SubItems[TitleColumnHeader.Index].Text,
  386. Duration = p.SubItems[DurColumnHeader.Index].Text,
  387. ToolTip = p.ToolTipText
  388. }).ToArray();
  389. }
  390. _sl.Save();
  391. }
  392. private async void StartProcessPendingAddQueue()
  393. {
  394. var reader = _pendingAddToList.Reader;
  395. while (_isRunning)
  396. {
  397. var inputPath1 = await reader.ReadAsync();
  398. foreach (var path in InputSourceProvider.ExpandPaths(inputPath1))
  399. {
  400. var item = new ListViewItem();
  401. item.Name = path;
  402. item.ToolTipText = Uri.UnescapeDataString(path);
  403. var localVarPath = path;
  404. if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uri) && uri.IsFile == false) localVarPath = uri.ToString();
  405. item.Text = Path.GetFileName(localVarPath);
  406. if (string.IsNullOrWhiteSpace(item.Text)) item.Text = localVarPath;
  407. for (var i = 0; i < MainListView.Columns.Count - 1; i++) item.SubItems.Add("");
  408. MainListView.Items.Add(item);
  409. }
  410. }
  411. }
  412. private void AddDebugContent(int i)
  413. {
  414. //TODO: UI playlist group by Album, but move sort ???
  415. var group = new ListViewGroup
  416. {
  417. Header = "Grrr" + i,
  418. Subtitle = "Srrr" + i,
  419. TitleImageIndex = 0
  420. };
  421. MainListView.Groups.Add(group);
  422. MainListView.Items.Add(new ListViewItem { Group = group, Text = "Brrr1", });
  423. MainListView.Items.Add(new ListViewItem { Group = group, Text = "Brrr2", });
  424. MainListView.Items.Add(new ListViewItem { Group = group, Text = "Brrr3", });
  425. }
  426. private void ShowSettingContextMenu(int? expandIndex = null)
  427. {
  428. var ctx = new ContextMenuStrip();
  429. var topItem = ctx.Items.Add(" ---------------- 设置 ---------------- ");
  430. topItem.Alignment = ToolStripItemAlignment.Right;
  431. topItem.TextAlign = ContentAlignment.MiddleCenter;
  432. topItem.Enabled = false;
  433. ctx.Items.Add("-");
  434. var allSupportedDevices = OutputDeviceProvider.GetAllSupportedDevices();
  435. var selectedPcmDevice = allSupportedDevices.FirstOrDefault(p => p.Id == _sl?.State?.SelectedPcmOutputDeviceId);
  436. var selectedDsdDevice = allSupportedDevices.FirstOrDefault(p => p.Id == _sl?.State?.SelectedDsdAsioOutputDeviceId);
  437. var pcmOutputSelect = new ToolStripMenuItem($"PCM 输出{(selectedPcmDevice == null ? "(未选择)" : "")}");
  438. ctx.Items.Add(pcmOutputSelect);
  439. var dsdOutputSelect = new ToolStripMenuItem($"DSD 输出{(selectedDsdDevice == null ? "(未选择)" : "")}");
  440. ctx.Items.Add(dsdOutputSelect);
  441. var dsdToPcm = new ToolStripMenuItem("软解码成PCM");
  442. if (_sl?.State?.DecodeDsdToPcm == true) dsdToPcm.CheckState = CheckState.Indeterminate;
  443. dsdOutputSelect.DropDownItems.Add(dsdToPcm);
  444. dsdToPcm.Click += delegate
  445. {
  446. if (_sl?.State == null) return;
  447. _sl.State.DecodeDsdToPcm = true;
  448. _sl.State.SelectedDsdAsioOutputDeviceId = null;
  449. ShowSettingContextMenu();
  450. };
  451. foreach (var deviceInfo in allSupportedDevices)
  452. {
  453. var pcmDeviceItem = new ToolStripMenuItem(deviceInfo.DisplayName);
  454. pcmOutputSelect.DropDownItems.Add(pcmDeviceItem);
  455. if (selectedPcmDevice == deviceInfo) pcmDeviceItem.CheckState = CheckState.Indeterminate;
  456. pcmDeviceItem.Click += delegate
  457. {
  458. if (_sl?.State == null) return;
  459. _sl.State.SelectedPcmOutputDeviceId = deviceInfo.Id;
  460. _sl.State.OutputDeviceLatency = null;
  461. ShowSettingContextMenu();
  462. };
  463. if (deviceInfo.Type == OutputType.ASIO)
  464. {
  465. var dsdDeviceItem = new ToolStripMenuItem(deviceInfo.DisplayName);
  466. dsdOutputSelect.DropDownItems.Add(dsdDeviceItem);
  467. if (selectedDsdDevice == deviceInfo) dsdDeviceItem.CheckState = CheckState.Indeterminate;
  468. dsdDeviceItem.Click += delegate
  469. {
  470. if (_sl?.State == null) return;
  471. _sl.State.SelectedDsdAsioOutputDeviceId = deviceInfo.Id;
  472. _sl.State.DecodeDsdToPcm = false;
  473. ShowSettingContextMenu();
  474. };
  475. }
  476. }
  477. ctx.Items.Add("-");
  478. if (selectedPcmDevice == null)
  479. {
  480. ctx.Items.Add("(未选择 PCM 输出设备)").Enabled = false;
  481. }
  482. else
  483. {
  484. if (selectedPcmDevice.CanSetLatency)
  485. {
  486. const int mulCount = 4;
  487. var defaultLatency = selectedPcmDevice.Latency;
  488. var selectedLatency = _sl?.State?.OutputDeviceLatency ?? defaultLatency;
  489. var latencyMenu = new ToolStripMenuItem("PCM 输出缓冲大小:" + (_sl.State?.OutputDeviceLatency ?? defaultLatency));
  490. ctx.Items.Add(latencyMenu);
  491. void SetLatency(int latency)
  492. {
  493. if (_sl.State == null) return;
  494. _sl.State.OutputDeviceLatency = latency;
  495. ShowSettingContextMenu();
  496. //ShowSettingContextMenu(ctx.Items.IndexOf(latencyMenu));
  497. }
  498. for (var i = 0; i < mulCount; i++)
  499. {
  500. var latency = defaultLatency * (i + 1);
  501. var latencyItem = new ToolStripMenuItem(latency.ToString());
  502. latencyMenu.DropDownItems.Add(latencyItem);
  503. if (selectedLatency == latency) latencyItem.CheckState = CheckState.Indeterminate;
  504. latencyItem.Click += delegate { SetLatency(latency); };
  505. }
  506. }
  507. if (selectedPcmDevice.HasControlPanel)
  508. {
  509. var toolStripItem = ctx.Items.Add("调出 PCM 设置面板");
  510. if (_playbackState == UIPlaybackState.Playing)
  511. {
  512. toolStripItem.Enabled = false;
  513. toolStripItem.Text += " (播放时不可用)";
  514. }
  515. toolStripItem.Click += delegate
  516. {
  517. try
  518. {
  519. selectedPcmDevice.ShowControlPanel();
  520. }
  521. catch (Exception e)
  522. {
  523. //TODO: 消息机制 错误 调出ASIO设置面板
  524. MessageBox.Show("无法调出 PCM 设置面板" + Environment.NewLine + e.Message, null, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0, false);
  525. }
  526. };
  527. }
  528. }
  529. if (selectedDsdDevice == null)
  530. {
  531. if (_sl?.State?.DecodeDsdToPcm == false)
  532. {
  533. ctx.Items.Add("(未选择 DSD 输出设备)").Enabled = false;
  534. }
  535. }
  536. else if (selectedDsdDevice.HasControlPanel)
  537. {
  538. var toolStripItem = ctx.Items.Add("调出 DSD 设置面板");
  539. if (_playbackState == UIPlaybackState.Playing)
  540. {
  541. toolStripItem.Enabled = false;
  542. toolStripItem.Text += " (播放时不可用)";
  543. }
  544. toolStripItem.Click += delegate
  545. {
  546. try
  547. {
  548. selectedDsdDevice.ShowControlPanel();
  549. }
  550. catch (Exception e)
  551. {
  552. //TODO: 消息机制 错误 调出ASIO设置面板
  553. MessageBox.Show("无法调出 DSD 设置面板" + Environment.NewLine + e.Message, null, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0, false);
  554. }
  555. };
  556. }
  557. ctx.Items.Add("-");
  558. ctx.Items.Add("立即保存状态").Click += delegate { SaveState(); };
  559. if (expandIndex.HasValue) ctx.Opened += delegate
  560. {
  561. if (ctx.Items[expandIndex.Value] is ToolStripMenuItem menuItem) menuItem.ShowDropDown();
  562. };
  563. ctx.Show(SettingButton, Point.Empty);
  564. #if DEBUG
  565. ctx.Items.Add("-");
  566. var dbgItem = new ToolStripMenuItem("调试");
  567. ctx.Items.Add(dbgItem);
  568. dbgItem.DropDownItems.Add("清除选中 PCM 设备").Click += delegate { if (_sl is { State: { } }) _sl.State.SelectedPcmOutputDeviceId = null; };
  569. dbgItem.DropDownItems.Add("清除选中 DSD 设备").Click += delegate { if (_sl is { State: { } }) _sl.State.SelectedDsdAsioOutputDeviceId = null; };
  570. #endif
  571. }
  572. // ----------------- UI event: Form -----------------
  573. private void MainForm_Shown(object sender, EventArgs e)
  574. {
  575. if (_serviceProvider == null) throw new InvalidOperationException($"Must set {nameof(ServiceProvider)} before show!");
  576. _eventBus = _serviceProvider.GetRequiredService<IEventBus>();
  577. _sl = _serviceProvider.GetRequiredService<SaveLoadService>();
  578. _logger = _serviceProvider.GetRequiredService<ILogger<MainForm>>();
  579. //var lvi = new ListViewItem();
  580. //AlbumImageList.Images.Add(this.Icon);
  581. //AddContent(1);
  582. //AddContent(2);
  583. MainListView.Items.Clear();
  584. if (_sl != null)
  585. {
  586. if (_sl.State != null)
  587. {
  588. if (_sl.State.FormPosition.HasValue) Location = _sl.State.FormPosition.Value;
  589. Application.DoEvents();
  590. if (_sl.State.FormSize.HasValue) Size = _sl.State.FormSize.Value;
  591. if (_sl.State.Playlist != null)
  592. {
  593. foreach (var playlistItem in _sl.State.Playlist)
  594. {
  595. var item = new ListViewItem();
  596. for (var i = 0; i < MainListView.Columns.Count - 1; i++) item.SubItems.Add("");
  597. item.Name = playlistItem.Path;
  598. item.SubItems[TitleColumnHeader.Index].Text = playlistItem.Title;
  599. item.SubItems[DurColumnHeader.Index].Text = playlistItem.Duration;
  600. item.ToolTipText = playlistItem.ToolTip;
  601. MainListView.Items.Add(item);
  602. }
  603. }
  604. }
  605. }
  606. _isRunning = true;
  607. StartProcessPendingAddQueue();
  608. }
  609. private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
  610. {
  611. _isRunning = false;
  612. SaveState();
  613. }
  614. // ----------------- UI event: ListView -----------------
  615. private void MainListView_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
  616. {
  617. if (MainListView.Columns[e.ColumnIndex] == StateColumnHeader)
  618. {
  619. e.Cancel = true;
  620. e.NewWidth = StateColumnHeader.Width;
  621. }
  622. if (MainListView.Columns[e.ColumnIndex] == DurColumnHeader)
  623. {
  624. e.Cancel = true;
  625. e.NewWidth = DurColumnHeader.Width;
  626. }
  627. }
  628. private async void MainListView_ItemActivate(object sender, EventArgs e)
  629. {
  630. if (MainListView.SelectedItems.Count > 0)
  631. {
  632. var item = MainListView.SelectedItems[0];
  633. await LoadItemAsync(item);
  634. }
  635. }
  636. private void MainListView_SizeChanged(object sender, EventArgs e)
  637. {
  638. TitleColumnHeader.Width = MainListView.Width - DurColumnHeader.Width - StateColumnHeader.Width - 50;
  639. }
  640. private void MainListView_KeyDown(object sender, KeyEventArgs e)
  641. {
  642. if (e.KeyCode == Keys.Delete)
  643. {
  644. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  645. foreach (var item in selectedItems) MainListView.Items.Remove(item);
  646. }
  647. }
  648. private void MainListView_ItemDrag(object sender, ItemDragEventArgs e)
  649. {
  650. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  651. if (selectedItems.Length > 0) MainListView.DoDragDrop(selectedItems, DragDropEffects.Move);
  652. }
  653. private void MainListView_DragEnter(object sender, DragEventArgs e)
  654. {
  655. e.Effect = e.Data?.GetData(typeof(ListViewItem[])) is ListViewItem[]? DragDropEffects.Move
  656. : e.Data?.GetFormats().Any(SupportedDropTypes.Contains) == true
  657. ? DragDropEffects.Link
  658. : DragDropEffects.None;
  659. }
  660. private void MainListView_DragOver(object sender, DragEventArgs e)
  661. {
  662. if (e.Data?.GetDataPresent(typeof(ListViewItem[])) == true)
  663. {
  664. var point = MainListView.PointToClient(new Point(e.X, e.Y));
  665. var targetIndex = MainListView.InsertionMark.NearestIndex(point);
  666. if (targetIndex == -1)
  667. {
  668. MainListView.InsertionMark.Index = -1;
  669. }
  670. else if (targetIndex >= 0 && targetIndex < MainListView.Items.Count)
  671. {
  672. MainListView.InsertionMark.Index = targetIndex;
  673. }
  674. }
  675. }
  676. private void MainListView_DragLeave(object sender, EventArgs e)
  677. {
  678. MainListView.InsertionMark.Index = -1;
  679. }
  680. private void MainListView_DragDrop(object sender, DragEventArgs e)
  681. {
  682. MainListView.InsertionMark.Index = -1;
  683. if (e.Data?.GetDataPresent(typeof(ListViewItem[])) == true)
  684. {
  685. var items = e.Data.GetData(typeof(ListViewItem[])) as ListViewItem[];
  686. var point = MainListView.PointToClient(new Point(e.X, e.Y));
  687. var targetIndex = MainListView.InsertionMark.NearestIndex(point);
  688. //BUG: when move down index -= 1?
  689. if (targetIndex >= 0 && targetIndex < MainListView.Items.Count)
  690. {
  691. foreach (var item in items!)
  692. {
  693. MainListView.Items.Remove(item);
  694. }
  695. targetIndex = Math.Min(targetIndex, MainListView.Items.Count);
  696. for (int i = 0; i < items.Length; i++)
  697. {
  698. MainListView.Items.Insert(targetIndex + i, items[i]);
  699. }
  700. }
  701. return;
  702. }
  703. var listAllPath = new List<string>();
  704. if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
  705. {
  706. var arrFilePath = (string[]?)e.Data.GetData(DataFormats.FileDrop);
  707. if (arrFilePath == null) return;
  708. foreach (var s in arrFilePath)
  709. {
  710. if (Directory.Exists(s))
  711. {
  712. listAllPath.AddRange(Directory.GetFiles(s, "*", SearchOption.AllDirectories));
  713. }
  714. else
  715. {
  716. listAllPath.Add(s);
  717. }
  718. }
  719. }
  720. if (e.Data?.GetDataPresent(DataFormats.Text) == true)
  721. {
  722. var text = (string?)e.Data.GetData(DataFormats.Text);
  723. if (text == null) return;
  724. listAllPath.Add(text);
  725. }
  726. var writer = _pendingAddToList.Writer;
  727. foreach (var path in listAllPath)
  728. {
  729. writer.WriteAsync(path);
  730. }
  731. }
  732. private void MainContextMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e)
  733. {
  734. MainContextMenu.Items.Clear();
  735. var moveTop = new ToolStripMenuItem("置顶");
  736. var moveUp = new ToolStripMenuItem("上移");
  737. var moveDown = new ToolStripMenuItem("下移");
  738. var moveBottom = new ToolStripMenuItem("置底");
  739. var clearSelected = new ToolStripMenuItem("清空选中");
  740. var clearAll = new ToolStripMenuItem("清空全部");
  741. MainContextMenu.Items.Add(moveTop);
  742. MainContextMenu.Items.Add(moveUp);
  743. MainContextMenu.Items.Add(moveDown);
  744. MainContextMenu.Items.Add(moveBottom);
  745. MainContextMenu.Items.Add("-");
  746. MainContextMenu.Items.Add(clearSelected);
  747. MainContextMenu.Items.Add(clearAll);
  748. var isAnyContain = MainListView.Items.Count != 0;
  749. var isAnySelected = MainListView.SelectedItems.Count != 0;
  750. var isMultiSelected = MainListView.SelectedItems.Count > 1;
  751. var isAllSelected = MainListView.SelectedItems.Count == MainListView.Items.Count;
  752. var isTopSelected = MainListView.SelectedIndices.Contains(0);
  753. var isBottomSelected = MainListView.SelectedIndices.Contains(MainListView.Items.Count - 1);
  754. moveTop.Enabled = isAnySelected && !isAllSelected && !isTopSelected;
  755. moveUp.Enabled = isAnySelected && !isAllSelected && !isTopSelected;
  756. moveDown.Enabled = isAnySelected && !isAllSelected && !isBottomSelected;
  757. moveBottom.Enabled = isAnySelected && !isAllSelected && !isBottomSelected;
  758. clearAll.Enabled = isAnyContain;
  759. clearSelected.Enabled = isAnySelected;
  760. moveTop.Click += delegate
  761. {
  762. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  763. var targetIndex = 0;
  764. foreach (var item in selectedItems) MainListView.Items.Remove(item);
  765. for (int i = 0; i < selectedItems.Length; i++) MainListView.Items.Insert(targetIndex + i, selectedItems[i]);
  766. };
  767. moveUp.Click += delegate
  768. {
  769. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  770. var targetIndex = selectedItems[0].Index - 1;
  771. foreach (var item in selectedItems) MainListView.Items.Remove(item);
  772. for (int i = 0; i < selectedItems.Length; i++) MainListView.Items.Insert(targetIndex + i, selectedItems[i]);
  773. };
  774. moveDown.Click += delegate
  775. {
  776. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  777. ListViewItem nextItem = null;
  778. for (int i = selectedItems[0].Index; i < MainListView.Items.Count; i++)
  779. {
  780. if (MainListView.Items[i].Selected == false)
  781. {
  782. nextItem = MainListView.Items[i];
  783. break;
  784. }
  785. }
  786. if (nextItem == null) return;
  787. foreach (var item in selectedItems) MainListView.Items.Remove(item);
  788. var targetIndex = nextItem.Index + 1;
  789. if (targetIndex > MainListView.Items.Count) targetIndex = MainListView.Items.Count;
  790. for (int i = 0; i < selectedItems.Length; i++) MainListView.Items.Insert(targetIndex + i, selectedItems[i]);
  791. };
  792. moveBottom.Click += delegate
  793. {
  794. var selectedItems = MainListView.SelectedItems.Cast<ListViewItem>().ToArray();
  795. foreach (var item in selectedItems) MainListView.Items.Remove(item);
  796. var targetIndex = MainListView.Items.Count;
  797. for (int i = 0; i < selectedItems.Length; i++) MainListView.Items.Insert(targetIndex + i, selectedItems[i]);
  798. };
  799. clearSelected.Click += delegate
  800. {
  801. foreach (ListViewItem selectedItem in MainListView.SelectedItems.Cast<ListViewItem>().ToArray()) MainListView.Items.Remove(selectedItem);
  802. };
  803. clearAll.Click += delegate
  804. {
  805. MainListView.Items.Clear();
  806. };
  807. }
  808. // ----------------- UI event: Buttons -----------------
  809. private void SettingButton_Click(object sender, EventArgs e) => ShowSettingContextMenu();
  810. private void StopButton_Click(object sender, EventArgs e) => StopAsync();
  811. private async void PlayButton_Click(object sender, EventArgs e)
  812. {
  813. if (_playbackState == UIPlaybackState.Paused)
  814. {
  815. await PlayAsync();
  816. return;
  817. }
  818. var selectedItem = MainListView.SelectedItems.Cast<ListViewItem>().FirstOrDefault();
  819. if (selectedItem != null) await LoadItemAsync(selectedItem);
  820. }
  821. private async void PauseButton_Click(object sender, EventArgs e) => await PauseAsync();
  822. private async void PrevButton_Click(object sender, EventArgs e) => await TrackPrevAsync();
  823. private async void NextButton_Click(object sender, EventArgs e) => await TrackNextAsync();
  824. // ----------------- UI event: Others -----------------
  825. private void UpdateTimer_Tick(object sender, EventArgs e)
  826. {
  827. var sb = new StringBuilder();
  828. //播放状态
  829. sb.Append(_playbackState.AsString(EnumFormat.Description));
  830. //进度
  831. var dur = _inputSource?.TotalTime;
  832. var cur = _inputSource?.CurrentTime;
  833. if (dur.HasValue && cur.HasValue)
  834. {
  835. sb.Append(" | ");
  836. var tsDur = dur.Value;
  837. var tsCur = cur.Value;
  838. sb.Append($"{tsCur:hh\\:mm\\:ss}.{tsCur.Milliseconds / 100:0}/{tsDur:hh\\:mm\\:ss}.{tsDur.Milliseconds / 100:0}");
  839. var durMs = (int)tsDur.TotalMilliseconds;
  840. var curMs = (int)cur.Value.TotalMilliseconds;
  841. if (_trackBarHolding == false)
  842. {
  843. SeekTrackBar.Maximum = durMs;
  844. if (curMs > durMs) curMs = durMs;
  845. SeekTrackBar.Value = curMs;
  846. }
  847. }
  848. if (_inputSource != null)
  849. {
  850. sb.Append(" | ");
  851. if (_nativeDsd) sb.Append("DSD ");
  852. var format = _inputSource.WaveFormat;
  853. var rawBit = _inputSource is IHaveBitPerRawSample bpr ? bpr.BitPerRawSample : null;
  854. var decBit = format.BitsPerSample;
  855. if (format.Encoding == WaveFormatEncoding.IeeeFloat)
  856. {
  857. sb.Append("IeeeFloat ");
  858. }
  859. if (format.Encoding == WaveFormatEncoding.Pcm)
  860. {
  861. sb.Append("PCM ");
  862. }
  863. if (rawBit.HasValue && rawBit != 0 && rawBit != decBit)
  864. {
  865. sb.Append($"{decBit}({rawBit})bit");
  866. }
  867. else
  868. {
  869. sb.Append($"{decBit}bit");
  870. }
  871. var unit = "K";
  872. var sampleRate = format.SampleRate;
  873. if (sampleRate > 1000000)
  874. {
  875. sampleRate /= 1000;
  876. unit = "M";
  877. }
  878. sb.Append(sampleRate % 1000 == 0
  879. ? $"/{sampleRate / 1000.0}{unit}Hz"
  880. : $"/{Math.Floor((float)sampleRate / 100) / 10.0:0.0}{unit}Hz");
  881. }
  882. if (_selectedOutputDevice != null && _playbackState == UIPlaybackState.Playing)
  883. {
  884. sb.Append(" | ");
  885. if (_outputDevice is AsioOut asioOut)
  886. {
  887. try
  888. {
  889. var type = asioOut.Driver.Capabilities.OutputChannelInfos.Select(p => (object)p.type).FirstOrDefault();
  890. if (type != null) sb.Append($"{type} ");
  891. }
  892. catch (Exception exception)
  893. {
  894. //TODO: 异常处理 UpdateTimer_Tick asioOut.Driver.Capabilities.OutputChannelInfos
  895. Console.WriteLine(exception);
  896. }
  897. }
  898. sb.Append($"{_selectedOutputDevice.DisplayName}");
  899. }
  900. StatusBarLabel.Text = sb.ToString();
  901. }
  902. private void SeekTrackBar_MouseDown(object sender, MouseEventArgs e)
  903. {
  904. _trackBarHolding = true;
  905. }
  906. private async void SeekTrackBar_MouseUp(object sender, MouseEventArgs e)
  907. {
  908. MainPanel.Enabled = false;
  909. if (_inputSource != null)
  910. {
  911. try
  912. {
  913. await PauseAsync();
  914. _playbackState = UIPlaybackState.Seeking;
  915. var ms = SeekTrackBar.Value;
  916. var seekTo = TimeSpan.FromMilliseconds(ms);
  917. await Task.Run(() => _inputSource.CurrentTime = seekTo);
  918. //read a cluster for detect seek pos
  919. _inputSource.ReadBytes(_inputSource.BlockAlign);
  920. //if content not support seek, reload and read&discard until target pos
  921. if (seekTo - _inputSource.CurrentTime > TimeSpan.FromMilliseconds(500))
  922. {
  923. await ReloadSource();
  924. await Task.Run(() =>
  925. {
  926. do
  927. {
  928. var count = _inputSource.ReadBytes(_inputSource.BlockAlign);
  929. if (count.Length == 0) break;
  930. } while (_inputSource != null && _inputSource.CurrentTime < seekTo && _playbackState == UIPlaybackState.Seeking);
  931. });
  932. }
  933. await PlayAsync();
  934. }
  935. catch (Exception exception)
  936. {
  937. //TODO: 异常处理
  938. Console.WriteLine(exception);
  939. _playbackState = UIPlaybackState.Error;
  940. }
  941. }
  942. MainPanel.Enabled = true;
  943. _trackBarHolding = false;
  944. }
  945. }
  946. internal enum UIPlaybackState
  947. {
  948. [Description("就绪")]
  949. Ready,
  950. [Description("正在加载")]
  951. Loading,
  952. [Description("正在播放")]
  953. Playing,
  954. [Description("正在跳转")]
  955. Seeking,
  956. [Description("暂停")]
  957. Paused,
  958. [Description("停止")]
  959. Stopped,
  960. [Description("错误")]
  961. Error,
  962. }
  963. }