MainForm.cs 39 KB

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