MainForm.cs 50 KB

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