|
@@ -1,5 +1,9 @@
|
|
|
using System.ComponentModel;
|
|
|
+using System.ComponentModel.DataAnnotations;
|
|
|
+using System.Diagnostics;
|
|
|
+using System.Diagnostics.Eventing.Reader;
|
|
|
using System.Drawing.Drawing2D;
|
|
|
+using System.Reflection;
|
|
|
using System.Text;
|
|
|
using System.Threading.Channels;
|
|
|
using Bmp.Core.Common.EventBus;
|
|
@@ -14,6 +18,7 @@ using Bmp.WinForms.SaveLoad;
|
|
|
using Bmp.WinForms.SaveLoad.Models;
|
|
|
using EnumsNET;
|
|
|
using NAudio.Wave;
|
|
|
+using Newtonsoft.Json.Linq;
|
|
|
using AsioOut = Bmp.Core.Lite.Playback.Outputs.NAudioASIO.AsioOut;
|
|
|
|
|
|
namespace Bmp.WinForms
|
|
@@ -24,8 +29,10 @@ namespace Bmp.WinForms
|
|
|
private const string EMOJI_WARN = "⚠";
|
|
|
private const string EMOJI_PLAY_BIG = "▶";
|
|
|
private const string EMOJI_PLAY_SMALL = "⏵";
|
|
|
+ private const string EMOJI_LOADING = "⏳";
|
|
|
|
|
|
private static readonly IReadOnlyCollection<string> SupportedDropTypes = new[] { DataFormats.FileDrop, DataFormats.Text };
|
|
|
+ private static readonly Random Rng = new();
|
|
|
|
|
|
private readonly Channel<string> _pendingAddToList = Channel.CreateUnbounded<string>();
|
|
|
|
|
@@ -58,6 +65,7 @@ namespace Bmp.WinForms
|
|
|
Text = Const.AppTitle;
|
|
|
var eIcon = IconExtractor.GetMainIcon();
|
|
|
if (null != eIcon) Icon = eIcon;
|
|
|
+ UpdatePlaylistModeButton();
|
|
|
}
|
|
|
|
|
|
// ----------------- playback control -----------------
|
|
@@ -70,7 +78,9 @@ namespace Bmp.WinForms
|
|
|
_playbackState = UIPlaybackState.Loading;
|
|
|
|
|
|
_currentListViewItem = item;
|
|
|
-
|
|
|
+ _currentListViewItem.EnsureVisible();
|
|
|
+ _currentListViewItem.SubItems[StateColumnHeader.Index].Text = EMOJI_LOADING;
|
|
|
+
|
|
|
var finalState = EMOJI_PLAY_BIG;
|
|
|
|
|
|
var balloonShow = false;
|
|
@@ -380,19 +390,142 @@ namespace Bmp.WinForms
|
|
|
|
|
|
private async Task TrackPrevAsync()
|
|
|
{
|
|
|
- //TODO: 上一曲(按播放模式)
|
|
|
- if (_currentListViewItem is not { ListView: { } }) return;
|
|
|
- var nextIndex = _currentListViewItem.Index - 1;
|
|
|
- if (nextIndex > 0) await LoadItemAsync(MainListView.Items[nextIndex]);
|
|
|
+ var itemsCount = MainListView.Items.Count;
|
|
|
+
|
|
|
+ if (itemsCount == 0)
|
|
|
+ {
|
|
|
+ //列表为空 下一个鬼,直接停止
|
|
|
+ await StopAsync();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var playlistMode = _saveLoadService.State.PlaylistMode;
|
|
|
+
|
|
|
+ int? nextIndex; // 下一项,null表示停止
|
|
|
+
|
|
|
+ //当前项已消失时
|
|
|
+ // 随机 go random brr
|
|
|
+ // 其他 回到第一项
|
|
|
+ if (_currentListViewItem?.ListView == null)
|
|
|
+ {
|
|
|
+ if (itemsCount > 2 && playlistMode == PlaylistMode.Random)
|
|
|
+ nextIndex = Rng.Next(itemsCount);
|
|
|
+ else
|
|
|
+ nextIndex = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var currentIndex = _currentListViewItem.Index;
|
|
|
+ switch (playlistMode)
|
|
|
+ {
|
|
|
+ default:
|
|
|
+ case PlaylistMode.Normal:
|
|
|
+ nextIndex = currentIndex - 1;
|
|
|
+ // 在第一项往前,无反应
|
|
|
+ if (nextIndex.Value < 0) return;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.LoopList:
|
|
|
+ nextIndex = currentIndex - 1;
|
|
|
+ // 最后一项播放完,跳到最后一项
|
|
|
+ if (nextIndex.Value < 0) nextIndex = itemsCount - 1;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.LoopTrack:
|
|
|
+ //保持不变
|
|
|
+ nextIndex = currentIndex;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.Random:
|
|
|
+ //go random brr
|
|
|
+ nextIndex = Rng.Next(itemsCount);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.Once:
|
|
|
+ //无条件停止
|
|
|
+ nextIndex = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nextIndex.HasValue)
|
|
|
+ {
|
|
|
+ await LoadItemAsync(MainListView.Items[nextIndex.Value]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ await StopAsync();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private async Task TrackNextAsync()
|
|
|
{
|
|
|
- //TODO: 下一曲(按播放模式)
|
|
|
- if (_currentListViewItem?.ListView == null) return;
|
|
|
- var nextIndex = _currentListViewItem.Index + 1;
|
|
|
- if (MainListView.Items.Count > nextIndex) await LoadItemAsync(MainListView.Items[nextIndex]);
|
|
|
- else _playbackState = UIPlaybackState.Stopped; //列表最后一项时
|
|
|
+ var itemsCount = MainListView.Items.Count;
|
|
|
+
|
|
|
+ if (itemsCount == 0)
|
|
|
+ {
|
|
|
+ //列表为空 下一个鬼,直接停止
|
|
|
+ await StopAsync();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int? nextIndex; // 下一项,null表示停止
|
|
|
+
|
|
|
+ var playlistMode = _saveLoadService.State.PlaylistMode;
|
|
|
+
|
|
|
+ //当前项已消失时
|
|
|
+ // 随机 go random brr
|
|
|
+ // 其他 回到第一项
|
|
|
+ if (_currentListViewItem?.ListView == null)
|
|
|
+ {
|
|
|
+ if (itemsCount > 2 && playlistMode == PlaylistMode.Random)
|
|
|
+ nextIndex = Rng.Next(itemsCount);
|
|
|
+ else
|
|
|
+ nextIndex = 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var currentIndex = _currentListViewItem.Index;
|
|
|
+ switch (playlistMode)
|
|
|
+ {
|
|
|
+ default:
|
|
|
+ case PlaylistMode.Normal:
|
|
|
+ nextIndex = currentIndex + 1;
|
|
|
+ // 最后一项播放完,停止
|
|
|
+ if (nextIndex.Value > itemsCount - 1) nextIndex = null;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.LoopList:
|
|
|
+ nextIndex = currentIndex + 1;
|
|
|
+ // 最后一项播放完,回到第一项
|
|
|
+ if (nextIndex.Value > itemsCount - 1) nextIndex = 0;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.LoopTrack:
|
|
|
+ //保持不变
|
|
|
+ nextIndex = currentIndex;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.Random:
|
|
|
+ //go random brr
|
|
|
+ nextIndex = Rng.Next(itemsCount);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PlaylistMode.Once:
|
|
|
+ //无条件停止
|
|
|
+ nextIndex = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nextIndex.HasValue)
|
|
|
+ {
|
|
|
+ await LoadItemAsync(MainListView.Items[nextIndex.Value]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ await StopAsync();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ----------------- playback event -----------------
|
|
@@ -409,7 +542,6 @@ namespace Bmp.WinForms
|
|
|
|
|
|
if (e.Exception == null)
|
|
|
{
|
|
|
- //TODO: 正常播放结束 下一曲(按播放模式)
|
|
|
await TrackNextAsync();
|
|
|
}
|
|
|
else
|
|
@@ -644,10 +776,22 @@ namespace Bmp.WinForms
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
+ private void UpdatePlaylistModeButton()
|
|
|
+ {
|
|
|
+ var value = _saveLoadService.State.PlaylistMode;
|
|
|
+
|
|
|
+ var type = value.GetType();
|
|
|
+ var name = Enum.GetName(type, value);
|
|
|
+ var d = type.GetField(name)?.GetCustomAttribute<DisplayAttribute>();
|
|
|
+
|
|
|
+ PlaylistModeButton.Text = d?.ShortName;
|
|
|
+ }
|
|
|
+
|
|
|
// ----------------- UI event: Form -----------------
|
|
|
|
|
|
private void MainForm_Shown(object sender, EventArgs e)
|
|
|
{
|
|
|
+ Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
|
|
|
//var lvi = new ListViewItem();
|
|
|
|
|
|
//AlbumImageList.Images.Add(this.Icon);
|
|
@@ -948,6 +1092,49 @@ namespace Bmp.WinForms
|
|
|
|
|
|
private async void NextButton_Click(object sender, EventArgs e) => await TrackNextAsync();
|
|
|
|
|
|
+ private void PlaylistModeButton_Paint(object sender, PaintEventArgs e)
|
|
|
+ {
|
|
|
+ // Draw the arrow glyph on the right side of the button
|
|
|
+ var arrowX = PlaylistModeButton.ClientRectangle.Width - 15;
|
|
|
+ var arrowY = PlaylistModeButton.ClientRectangle.Height / 2 + 12;
|
|
|
+
|
|
|
+ var arrowBrush = PlaylistModeButton.Enabled ? SystemBrushes.ControlText : SystemBrushes.ButtonShadow;
|
|
|
+ var arrows = new[]
|
|
|
+ {
|
|
|
+ new PointF(arrowX, arrowY),
|
|
|
+ new PointF(arrowX + 10, arrowY),
|
|
|
+ new PointF(arrowX + 5 , arrowY +5 )
|
|
|
+ };
|
|
|
+ e.Graphics.FillPolygon(arrowBrush, arrows);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void PlaylistModeButton_Click(object sender, EventArgs e)
|
|
|
+ {
|
|
|
+ var cm = new ContextMenuStrip { ShowItemToolTips = true };
|
|
|
+ foreach (var item in typeof(PlaylistMode).GetFields(BindingFlags.Public | BindingFlags.Static))
|
|
|
+ {
|
|
|
+ var attr = item.GetCustomAttribute<DisplayAttribute>();
|
|
|
+ if (attr == null) continue;
|
|
|
+ var mi = new ToolStripMenuItem
|
|
|
+ {
|
|
|
+ Text = attr.Name,
|
|
|
+ ToolTipText = attr.Description
|
|
|
+ };
|
|
|
+
|
|
|
+ var value = (PlaylistMode)item.GetRawConstantValue()!;
|
|
|
+ if (_saveLoadService.State.PlaylistMode == value) mi.CheckState = CheckState.Indeterminate;
|
|
|
+
|
|
|
+ mi.Click += delegate
|
|
|
+ {
|
|
|
+ _saveLoadService.State.PlaylistMode = value;
|
|
|
+ UpdatePlaylistModeButton();
|
|
|
+ };
|
|
|
+
|
|
|
+ cm.Items.Add(mi);
|
|
|
+ }
|
|
|
+ cm.Show(PlaylistModeButton, 0, PlaylistModeButton.Height);
|
|
|
+ }
|
|
|
+
|
|
|
// ----------------- UI event: Others -----------------
|
|
|
|
|
|
private void UpdateTimer_Tick(object sender, EventArgs e)
|
|
@@ -1114,27 +1301,24 @@ namespace Bmp.WinForms
|
|
|
MainPanel.Enabled = true;
|
|
|
_trackBarHolding = false;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- private void PlaylistModeButton_Paint(object sender, PaintEventArgs e)
|
|
|
- {
|
|
|
- // Draw the arrow glyph on the right side of the button
|
|
|
- var arrowX = PlaylistModeButton.ClientRectangle.Width - 24;
|
|
|
- var arrowY = PlaylistModeButton.ClientRectangle.Height / 2 + 12;
|
|
|
+ internal enum PlaylistMode
|
|
|
+ {
|
|
|
+ [Display(ShortName = "⇶", Name = "正常", Description = "播放完当前项自动播放下一项,直到列表最后一项播放完,停止")]
|
|
|
+ Normal = 0,
|
|
|
|
|
|
- var arrowBrush = PlaylistModeButton.Enabled ? SystemBrushes.ControlText : SystemBrushes.ButtonShadow;
|
|
|
- var arrows = new[]
|
|
|
- {
|
|
|
- new PointF(arrowX, arrowY),
|
|
|
- new PointF(arrowX + 18, arrowY),
|
|
|
- new PointF(arrowX + 9 , arrowY + 9 )
|
|
|
- };
|
|
|
- e.Graphics.FillPolygon(arrowBrush, arrows);
|
|
|
- }
|
|
|
+ [Display(ShortName = "🔁", Name = "列表循环", Description = "无限循环整个播放列表")]
|
|
|
+ LoopList = 1,
|
|
|
|
|
|
- private void PlaylistModeButton_Click(object sender, EventArgs e)
|
|
|
- {
|
|
|
+ [Display(ShortName = "🔂", Name = "单曲循环", Description = "无限循环当前项")]
|
|
|
+ LoopTrack = 2,
|
|
|
|
|
|
- }
|
|
|
+ [Display(ShortName = "🔀", Name = "随机", Description = "无限随机整个播放列表")]
|
|
|
+ Random = 3,
|
|
|
+
|
|
|
+ [Display(ShortName = "⤞", Name = "一次", Description = "播放完当前项,停止")]
|
|
|
+ Once = 4,
|
|
|
}
|
|
|
|
|
|
internal enum UIPlaybackState
|