19 Commits
1.5 ... shuffle

Author SHA1 Message Date
f6eb00759a Cleanup 2022-11-13 15:52:27 +01:00
468ec8be07 Cleaning 2022-11-13 15:28:10 +01:00
52b0a6fc85 Bugfix 2022-11-13 14:01:07 +01:00
5a43a1284e Looks like most of shuffle issues are resolved 2022-11-11 17:57:50 +01:00
06207d9791 Merge branch 'main' into shuffle 2022-11-11 01:31:39 +01:00
3b55c79d30 Merge branch 'main' into shuffle 2022-11-03 22:20:37 +01:00
5142477b5e GetPlaylist more cleverly 2022-04-19 01:17:48 +02:00
a6b7ad9c1e Merge branch 'main' into shuffle 2022-04-19 00:23:59 +02:00
1d3515a39d Cleaned things for shuffle, but still has some deadlocks and crashes 2022-04-18 01:02:26 +02:00
792437b839 Update readme and screenshots 2022-04-17 16:04:28 +02:00
5bfa7d3b5b Basic shuffle features, must fix deadlocks 2022-04-17 16:02:09 +02:00
c5e8534af7 Merge branch 'main' into shuffle 2022-04-14 01:08:49 +02:00
5d6e3b6d1e Update MpcNET from 1.3 to 1.4 to use filters 2022-04-14 01:06:33 +02:00
6ad4d9c813 Merge branch 'main' into shuffle 2022-04-07 00:59:00 +02:00
c93a9a326e Better working continuous shuffle 2021-10-07 22:14:09 +02:00
c055c59de7 Handle no response when querying stats 2021-10-07 22:13:18 +02:00
4c71d6a6e0 Rough working shuffle system 2021-10-06 22:54:55 +02:00
3685c369b4 Merge branch 'main' into shuffle 2021-10-05 19:20:00 +02:00
e0d640532c Shuffle window (only visual) 2021-10-05 14:23:23 +02:00
22 changed files with 830 additions and 84 deletions

View File

@ -9,6 +9,7 @@ namespace unison
private TaskbarIcon _systray;
private HotkeyHandler _hotkeys;
private SnapcastHandler _snapcast;
private ShuffleHandler _shuffle;
private MPDHandler _mpd;
private UpdateHandler _updater;
@ -30,6 +31,9 @@ namespace unison
_snapcast = new SnapcastHandler();
Current.Properties["snapcast"] = _snapcast;
_shuffle = new ShuffleHandler();
Current.Properties["shuffle"] = _shuffle;
_updater = new UpdateHandler();
Current.Properties["updater"] = _updater;

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
@ -34,7 +35,7 @@ namespace unison
public class MPDHandler
{
private bool _connected;
public string _version;
private string _version;
private int _currentVolume;
private int _previousVolume;
private bool _currentRandom;
@ -43,6 +44,7 @@ namespace unison
private bool _currentConsume;
private double _currentTime;
private double _totalTime;
private IEnumerable<IMpdFile> _Playlist;
private MpdStatus _currentStatus;
private IMpdFile _currentSong;
@ -65,7 +67,7 @@ namespace unison
private MpcConnection _commandConnection;
private IPEndPoint _mpdEndpoint;
private CancellationTokenSource _cancelCommand;
public CancellationTokenSource _cancelCommand;
private CancellationTokenSource _cancelConnect;
public MPDHandler()
@ -525,13 +527,27 @@ namespace unison
public string GetVersion() => _version;
public Statistics GetStats() => _stats;
public double GetCurrentTime() => _currentTime;
public IEnumerable<IMpdFile> GetPlaylist() => _Playlist;
public bool IsConnected() => _connected;
public bool IsPlaying() => _currentStatus?.State == MpdState.Play;
public void Prev() => SendCommand(new PreviousCommand());
public void Next() => SendCommand(new NextCommand());
public bool CanPrevNext = true;
public void Prev()
{
if (CanPrevNext)
SendCommand(new PreviousCommand());
}
public void Next()
{
if (CanPrevNext)
SendCommand(new NextCommand());
}
public void PlayPause() => SendCommand(new PauseResumeCommand());
public void Play(int pos) => SendCommand(new PlayCommand(pos));
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
@ -586,27 +602,35 @@ namespace unison
SendCommand(commandList);
}
public async Task QueryPlaylist() => _Playlist = await SafelySendCommandAsync(new PlaylistCommand());
public int GetPlaylistCount()
{
if (_Playlist == null)
return 0;
return _Playlist.ToArray().Count();
}
public async void QueryStats()
{
Dictionary<string, string> response = await SafelySendCommandAsync(new StatsCommand());
Dictionary<string, string> Response = await SafelySendCommandAsync(new StatsCommand());
if (Response == null)
return;
if (response != null)
{
_stats.Songs = int.Parse(response["songs"]);
_stats.Albums = int.Parse(response["albums"]);
_stats.Artists = int.Parse(response["artists"]);
_stats.Songs = int.Parse(Response["songs"]);
_stats.Albums = int.Parse(Response["albums"]);
_stats.Artists = int.Parse(Response["artists"]);
TimeSpan time;
time = TimeSpan.FromSeconds(int.Parse(response["uptime"]));
_stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(response["db_playtime"]));
_stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(response["playtime"]));
_stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss");
TimeSpan time;
time = TimeSpan.FromSeconds(int.Parse(Response["uptime"]));
_stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(Response["db_playtime"]));
_stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(Response["playtime"]));
_stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss");
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(response["db_update"])).ToLocalTime();
_stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm");
}
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(Response["db_update"])).ToLocalTime();
_stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm");
}
}
}

View File

@ -75,7 +75,6 @@ namespace unison.Handlers
public async Task<List<StationInfo>> AdvancedSearch(AdvancedSearchOptions options)
{
return await _radioBrowser.Search.AdvancedAsync(options);
}
}

108
Handlers/ShuffleHandler.cs Normal file
View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using MpcNET;
using MpcNET.Commands.Database;
using MpcNET.Commands.Playback;
using MpcNET.Commands.Queue;
using MpcNET.Commands.Reflection;
using MpcNET.Tags;
using MpcNET.Types;
using MpcNET.Types.Filters;
namespace unison
{
class ShuffleHandler
{
private readonly MPDHandler _mpd;
public int AddedSongs = 0;
public List<string> SongList { get; }
public ShuffleHandler()
{
SongList = new();
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
}
public async Task GetSongsFromFilter(List<IFilter> filter, CancellationToken token)
{
if (token.IsCancellationRequested)
return;
SongList.Clear();
int song = _mpd.GetStats().Songs;
IEnumerable<IMpdFile> response = await _mpd.SafelySendCommandAsync(new SearchCommand(filter, 0, song + 1));
foreach (IMpdFile file in response)
SongList.Add(file.Path);
}
public async Task AddToQueueRandom(int SongNumber, CancellationToken token)
{
if (token.IsCancellationRequested)
return;
int AddedSongs = 0;
var commandList = new CommandList();
int songTotal = _mpd.GetStats().Songs;
for (int i = 0; i < SongNumber; i++)
{
int song = new Random().Next(0, songTotal - 1);
commandList.Add(new SearchAddCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1));
AddedSongs++;
// play if stopped or unknown state (no queue managing at the moment, so mandatory)
if (i == 0 && (_mpd.GetStatus().State != MpdState.Play && _mpd.GetStatus().State != MpdState.Pause))
commandList.Add(new PlayCommand(0));
}
await _mpd.SafelySendCommandAsync(commandList);
}
public async Task AddToQueueFilter(int SongNumber, CancellationToken token)
{
if (token.IsCancellationRequested)
return;
int AddedSongs = 0;
// more (or equal) requested songs than available => add everything
if (SongNumber >= SongList.Count)
{
var commandList = new CommandList();
foreach (string path in SongList)
{
commandList.Add(new AddCommand(path));
AddedSongs++;
}
await _mpd.SafelySendCommandAsync(commandList);
}
// more available songs than requested =>
// we add unique indexes until we reach the requested amount
else
{
HashSet<int> SongIndex = new();
while (SongIndex.Count < SongNumber)
{
int MaxIndex = new Random().Next(0, SongList.Count - 1);
SongIndex.Add(MaxIndex);
}
var commandList = new CommandList();
foreach (int index in SongIndex)
{
commandList.Add(new AddCommand(SongList[index]));
AddedSongs++;
}
await _mpd.SafelySendCommandAsync(commandList);
}
}
}
}

View File

@ -1,6 +1,6 @@
# unison
![Main window](Screenshots/screen1.png)
![Main window](Screenshots/screen-mainwindow.png)
**unison** is a very simple [Music Player Daemon (MPD)](https://www.musicpd.org/) daemon client with the following goals:
@ -15,13 +15,25 @@
By default, unison works as a daemon in the taskbar system tray. You can display the main window when needed at any time with a shortcut.
![Systray](Screenshots/screen2.png)
![Systray](Screenshots/screen-systray.png)
### Shortcuts
You can control your music at anytime with the shortcuts. They are usable system-wide, even if the window is not visible. They are of course fully rebindable.
![Settings => shortcuts](Screenshots/screen3.png)
![Settings => shortcuts](Screenshots/screen-shortcuts.png)
### Shuffle panel
One of unison's main feature is a complete shuffle system based on criterias, aka a smart playlist.
You have two options:
* **Add to queue** allows you to add a defined number of songs to the queue.
* **Continuous shuffle** allows you, as long as the program is running, to automatically add songs to the queue.
Each of these options work with filters, but if none are selected, it is based on the entire library.
![Shuffle panel](Screenshots/screen-shuffle.png)
### Snapcast
@ -31,12 +43,13 @@ Embedding a Snapcast client allows to listen to music on multiple devices. For e
Through [Radio-Browser](https://www.radio-browser.info), a community database, you can play radio streams directly from unison. There are more than 28,000 stations recorded on this service, so it is a nice way to discover new music and cultures.
![Radio stations](Screenshots/screen4.png)
![Radio stations](Screenshots/screen-radio.png)
## Planned features
* A complete shuffle system based on set criteria, aka a smart playlist.
* Playlist, queue and library management. I use other software to do it, but I will implement them at some point.
* Playlist, queue and library management
* More options for the shuffle panel
* Dark mode
## Translations

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -111,14 +111,15 @@
</Border>
</Grid>
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0">
<Button x:Name="Shuffle" Padding="5, 2" Click="Shuffle_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
</StackPanel>
</Button>
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" Margin="5,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="📻" Padding="0,0,0,2"/>
<TextBlock Text="{x:Static properties:Resources.Radios}" Margin="5, 0, 0, 0"/>
@ -136,12 +137,13 @@
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Center" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" Margin="5,0,0,0" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
<!--<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="0,0,10,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
</StackPanel>
</Button>-->
</Button>
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}">
<StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/>

View File

@ -6,7 +6,7 @@ using System.Windows.Threading;
using System.Windows.Interop;
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using unison.Handlers;
using System.Diagnostics;
namespace unison
{
@ -14,6 +14,7 @@ namespace unison
{
private readonly Settings _settingsWin;
private readonly Radios _radiosWin;
private readonly Shuffle _shuffleWin;
private readonly DispatcherTimer _timer;
private readonly MPDHandler _mpd;
@ -26,6 +27,7 @@ namespace unison
_settingsWin = new Settings();
_radiosWin = new Radios();
_shuffleWin = new Shuffle();
_timer = new DispatcherTimer();
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
@ -47,12 +49,17 @@ namespace unison
{
if (_mpd.IsConnected())
{
_mpd.QueryStats();
_settingsWin.UpdateStats();
ConnectionOkIcon.Visibility = Visibility.Visible;
ConnectionFailIcon.Visibility = Visibility.Collapsed;
Snapcast.IsEnabled = true;
if (_radiosWin.IsConnected())
Radio.IsEnabled = true;
_shuffleWin.Initialize();
}
else
{
@ -68,7 +75,7 @@ namespace unison
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
}
public void OnSongChanged(object sender, EventArgs e)
public async void OnSongChanged(object sender, EventArgs e)
{
if (_mpd.GetCurrentSong() == null)
return;
@ -114,6 +121,15 @@ namespace unison
_timer.Start();
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
}
Trace.WriteLine("Song changed called!");
if (_shuffleWin.GetContinuous())
{
_mpd.CanPrevNext = false;
await _shuffleWin.HandleContinuous();
_mpd.CanPrevNext = true;
}
}
public void OnStatusChanged(object sender, EventArgs e)
@ -142,9 +158,6 @@ namespace unison
DefaultState();
}
}
_mpd.QueryStats();
_settingsWin.UpdateStats();
}
private void DefaultState(bool LostConnection = false)
@ -217,7 +230,6 @@ namespace unison
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
public void Previous_Clicked(object sender, RoutedEventArgs e) => _mpd.Prev();
public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
public void Single_Clicked(object sender, RoutedEventArgs e) => _mpd.Single();
@ -239,6 +251,15 @@ namespace unison
_radiosWin.WindowState = WindowState.Normal;
}
public void Shuffle_Clicked(object sender, RoutedEventArgs e)
{
_shuffleWin.Show();
_shuffleWin.Activate();
if (_shuffleWin.WindowState == WindowState.Minimized)
_shuffleWin.WindowState = WindowState.Normal;
}
public void Settings_Clicked(object sender, RoutedEventArgs e)
{
_settingsWin.Show();

View File

@ -1,7 +1,7 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System;
using System.ComponentModel;
using System.Windows.Interop;
using System.Windows.Controls;

View File

@ -59,36 +59,6 @@
</DockPanel>
</TabItem>
<TabItem Header="Snapcast">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="🔊 Snapcast"/>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<StackPanel>
<StackPanel>
<CheckBox x:Name="SnapcastStartup" Margin="5, 5, 0, 0">
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastLauch}" TextWrapping="Wrap"/>
</CheckBox>
<CheckBox x:Name="SnapcastWindow" Margin="5,2.5,0,0">
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastWindow}" TextWrapping="Wrap"/>
</CheckBox>
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPort}" TextWrapping="Wrap" Margin="5,5,0,0"/>
<TextBox x:Name="SnapcastPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPath}" TextWrapping="Wrap" Margin="5,5,0,0"/>
<TextBox x:Name="SnapcastPath" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
<Run Text="{x:Static properties:Resources.Settings_SnapcastInfo1}" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo2}" FontStyle="Italic" FontWeight="DemiBold" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo3}" />
</TextBlock>
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="SnapcastReset_Clicked"/>
</StackPanel>
</StackPanel>
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="{x:Static properties:Resources.Settings_Shortcuts}">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
@ -132,7 +102,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -140,7 +110,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -148,7 +118,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -156,7 +126,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -164,7 +134,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -172,7 +142,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
@ -180,7 +150,7 @@
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
</Button>
</StackPanel>
</Grid>
@ -190,7 +160,64 @@
</StackPanel>
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="ShortcutsReset_Clicked"/>
</StackPanel>
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="Snapcast">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<emoji:TextBlock Text="🔊 Snapcast"/>
</GroupBox.Header>
<Grid VerticalAlignment="Top">
<StackPanel>
<StackPanel>
<CheckBox x:Name="SnapcastStartup" Margin="5, 5, 0, 0">
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastLauch}" TextWrapping="Wrap"/>
</CheckBox>
<CheckBox x:Name="SnapcastWindow" Margin="5,2.5,0,0">
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastWindow}" TextWrapping="Wrap"/>
</CheckBox>
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPort}" TextWrapping="Wrap" Margin="5,5,0,0"/>
<TextBox x:Name="SnapcastPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPath}" TextWrapping="Wrap" Margin="5,5,0,0"/>
<TextBox x:Name="SnapcastPath" TextWrapping="Wrap" Width="250" Margin="10,2,5,0" HorizontalAlignment="Left"/>
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="250">
<Run Text="{x:Static properties:Resources.Settings_SnapcastInfo1}" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo2}" FontStyle="Italic" FontWeight="DemiBold" /><Run Text="{x:Static properties:Resources.Settings_SnapcastInfo3}" />
</TextBlock>
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="SnapcastReset_Clicked"/>
</StackPanel>
</StackPanel>
</Grid>
</GroupBox>
</DockPanel>
</TabItem>
<TabItem Header="Shuffle">
<DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<TextBlock>
<emoji:EmojiInline Text="🔁 "/>
<Run Text="Shuffle"></Run>
</TextBlock>
</GroupBox.Header>
<Grid MaxWidth="500">
<StackPanel>
<TextBlock TextWrapping="Wrap">
<Run>The shuffle window allows to add random songs to your queue. Both options take into account the filter.</Run>
<Run>If the filter is empty, the entire music library is taken into account.</Run><LineBreak/><LineBreak/>
<Run FontWeight="Bold">Add to queue</Run><LineBreak/>
<Run>Add a fixed number of songs to the queue. It can take a long time to add more than 100 songs, so the option is limited to 1000 songs.</Run>
<LineBreak/><LineBreak/>
<Run FontWeight="Bold">Continuous shuffle</Run><LineBreak/>
<Run>By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to.</Run>
</TextBlock>
</StackPanel>
</Grid>
</GroupBox>

117
Views/Shuffle.xaml Normal file
View File

@ -0,0 +1,117 @@
<Window x:Class="unison.Shuffle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
xmlns:local="clr-namespace:unison" xmlns:sys="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
Title="Shuffle" Closing="Window_Closing" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
<Window.Resources>
<x:Array x:Key="FilterType" Type="sys:String">
<sys:String>Song</sys:String>
<sys:String>Artist</sys:String>
<sys:String>Album</sys:String>
<sys:String>Year</sys:String>
<sys:String>Genre</sys:String>
<sys:String>Directory</sys:String>
</x:Array>
<x:Array x:Key="OperatorTypeA" Type="sys:String">
<sys:String>contains</sys:String>
<sys:String>is</sys:String>
<sys:String>is not</sys:String>
</x:Array>
<x:Array x:Key="OperatorTypeB" Type="sys:String">
<sys:String>is</sys:String>
<sys:String>is not</sys:String>
</x:Array>
<x:Array x:Key="OperatorTypeC" Type="sys:String">
<sys:String>is</sys:String>
</x:Array>
<DataTemplate x:Key="FilterPanel">
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<ComboBox x:Name="FilterType" SelectionChanged="FilterType_SelectionChanged" ItemsSource="{StaticResource FilterType}" SelectedIndex="0" Width="100" ScrollViewer.CanContentScroll="False" FocusVisualStyle="{x:Null}"/>
<ComboBox x:Name="FilterOperator" SelectionChanged="OperatorType_SelectionChanged" ItemsSource="{StaticResource OperatorTypeA}" SelectedIndex="0" Width="80" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
<ComboBox x:Name="FilterList" SelectedIndex="0" Width="240" Visibility="Collapsed" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
<TextBox x:Name="FilterValue" KeyUp="QueryFilterHandler" Width="240" Margin="5,0,0,0"/>
<Button Content="-" Padding="5, 2" Click="RemoveFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
<Button Content="+" Padding="5, 2" Click="AddFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="5,0,5,5">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
<GroupBox.Header>
<TextBlock>
<emoji:EmojiInline Text="🔡"/>
<Run Text="Filter"/>
</TextBlock>
</GroupBox.Header>
<StackPanel Orientation="Vertical" Margin="5,0,5,0">
<StackPanel x:Name="FilterPanel">
<ContentPresenter ContentTemplate="{StaticResource FilterPanel}"/>
</StackPanel>
<StackPanel x:Name="SongFilterPanel" Margin="0,10,0,0">
<TextBlock>
<Run Text="Number of songs in filter: "/><Run x:Name="SongFilterNumber" FontWeight="Bold"/>
</TextBlock>
</StackPanel>
<StackPanel Margin="0,5,0,0">
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<Button Content="Query filter" Click="UpdateFilter_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="0,0,10,0"/>
<Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
<TextBlock x:Name="QueryFilterText" Text="Querying filter..." Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
</StackPanel>
</StackPanel>
</StackPanel>
</GroupBox>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<GroupBox DockPanel.Dock="Right" Padding="0,4,0,0" Width="248">
<GroupBox.Header>
<TextBlock>
<emoji:EmojiInline Text=""/>
<Run Text="Add to queue"/>
</TextBlock>
</GroupBox.Header>
<StackPanel Orientation="Vertical" Margin="5,5,5,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="Songs to add" Margin="0,0,5,5"/>
<TextBox x:Name="SongNumber" KeyUp="AddToQueueHandler" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="15" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Button Content="Add to queue" Click="AddToQueue_Clicked" Padding="5, 2" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
<TextBlock x:Name="SearchStatus" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed">
<Run Text="Adding "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs..."/>
</TextBlock>
</StackPanel>
</StackPanel>
</GroupBox>
<GroupBox DockPanel.Dock="Left" Padding="0,4,0,0" Width="248" Margin="0,0,5,0">
<GroupBox.Header>
<TextBlock>
<emoji:EmojiInline Text="♾️"/>
<Run Text="Continuous shuffle"/>
</TextBlock>
</GroupBox.Header>
<StackPanel Orientation="Horizontal" Margin="5,7,5,0">
<CheckBox x:Name="ContinuousShuffle" Checked="ContinuousShuffle_Checked" Unchecked="ContinuousShuffle_Checked" FocusVisualStyle="{x:Null}" VerticalAlignment="Top">
<TextBlock Text="Enable continuous shuffle" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</GroupBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Window>

419
Views/Shuffle.xaml.cs Normal file
View File

@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Threading;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MpcNET.Commands.Database;
using MpcNET.Tags;
using MpcNET.Types;
using MpcNET.Types.Filters;
namespace unison
{
public partial class Shuffle : Window
{
private readonly MPDHandler _mpd;
private readonly ShuffleHandler _shuffle;
List<string> GenreList { get; }
List<string> FolderList { get; }
List<IFilter> Filters { get; }
bool _continuous = false;
public Shuffle()
{
InitializeComponent();
GenreList = new();
FolderList = new();
Filters = new();
SongFilterNumber.Text = "0";
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
_shuffle = (ShuffleHandler)Application.Current.Properties["shuffle"];
}
public void Initialize()
{
ListGenre(_mpd._cancelCommand.Token);
ListFolder(_mpd._cancelCommand.Token);
}
public async void ListGenre(CancellationToken token)
{
if (GenreList.Count != 0)
return;
if (token.IsCancellationRequested)
return;
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
if (Response == null)
return;
foreach (string genre in Response)
GenreList.Add(genre);
}
public async void ListFolder(CancellationToken token)
{
if (FolderList.Count != 0)
return;
if (token.IsCancellationRequested)
return;
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
if (Response == null)
return;
foreach (IMpdFilePath folder in Response)
FolderList.Add(folder.Name);
}
private bool IsFilterEmpty()
{
if (Filters.Count() == 0)
return true;
return false;
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(child);
if (parent == null)
return null;
if (parent is T)
return parent as T;
else
return FindParent<T>(parent);
}
private void AddFilter_Clicked(object sender, RoutedEventArgs e)
{
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
SongFilterNumber.Text = "0";
}
private void RemoveFilter_Clicked(object sender, RoutedEventArgs e)
{
if (FilterPanel.Children.Count > 1)
FilterPanel.Children.Remove(FindParent<ContentPresenter>(sender as Button));
else
Reset_Clicked(null, null);
SongFilterNumber.Text = "0";
}
private void Reset_Clicked(object sender, RoutedEventArgs e)
{
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
SongFilterNumber.Text = "0";
_shuffle.SongList.Clear();
}
private ITag FilterEquivalence_Type(string value)
{
if (value == "Song")
return MpdTags.Title;
else if (value == "Artist")
return MpdTags.Artist;
else if (value == "Album")
return MpdTags.Album;
else if (value == "Year")
return MpdTags.Date;
else if (value == "Genre")
return MpdTags.Genre;
return MpdTags.Title;
}
private FilterOperator FilterEquivalence_Operator(string value)
{
if (value == "contains")
return FilterOperator.Contains;
else if (value == "is")
return FilterOperator.Equal;
else if (value == "is not")
return FilterOperator.Different;
return FilterOperator.Equal;
}
private void FilterType_Change(object sender, string Operator, List<string> Listing)
{
ComboBox comboBox = sender as ComboBox;
StackPanel stackPanel = comboBox.Parent as StackPanel;
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
{
if (child.Name == "FilterOperator")
{
child.ItemsSource = (Array)FindResource(Operator);
child.SelectedItem = child.Items[0];
}
if (child.Name == "FilterList")
{
child.Visibility = Visibility.Visible;
child.ItemsSource = Listing;
child.SelectedItem = child.Items[0];
}
}
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
{
if (child.Name == "FilterValue")
child.Visibility = Visibility.Collapsed;
}
SongFilterNumber.Text = "0";
}
private void FilterType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string item = e.AddedItems[0].ToString();
if (item == "Genre")
FilterType_Change(sender, "OperatorTypeB", GenreList);
else if (item == "Directory")
FilterType_Change(sender, "OperatorTypeC", FolderList);
else
{
ComboBox combobox = sender as ComboBox;
StackPanel stackpanel = combobox.Parent as StackPanel;
foreach (ComboBox child in stackpanel.Children.OfType<ComboBox>())
{
if (child.Name == "FilterOperator")
{
child.ItemsSource = (Array)FindResource("OperatorTypeA");
child.SelectedItem = child.Items[0];
}
if (child.Name == "FilterList")
child.Visibility = Visibility.Collapsed;
}
foreach (TextBox child in stackpanel.Children.OfType<TextBox>())
{
if (child.Name == "FilterValue")
child.Visibility = Visibility.Visible;
}
}
SongFilterNumber.Text = "0";
}
private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SongFilterNumber.Text = "0";
}
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
{
QueryFilterText.Visibility = Visibility.Visible;
await UpdateFilter();
TimedText(QueryFilterText, 1);
}
private void TimedText(TextBlock textBlock, int time)
{
DispatcherTimer Timer = new DispatcherTimer();
Timer.Interval = TimeSpan.FromSeconds(time);
Timer.Tick += (sender, args) =>
{
Timer.Stop();
textBlock.Visibility = Visibility.Collapsed;
};
Timer.Start();
}
private async Task UpdateFilter()
{
Filters.Clear();
foreach (ContentPresenter superChild in FilterPanel.Children)
{
ITag tag = MpdTags.Title;
FilterOperator op = FilterOperator.None;
string value = "";
bool isDir = false;
StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
{
if (child.Name == "FilterValue")
value = child.Text;
}
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
{
if (child.Name == "FilterType")
{
if (child.SelectedItem.ToString() == "Directory")
isDir = true;
else
tag = FilterEquivalence_Type(child.SelectedItem.ToString());
}
if (child.Name == "FilterOperator")
op = FilterEquivalence_Operator(child.SelectedItem.ToString());
if (child.Name == "FilterList" && child.Visibility == Visibility.Visible)
value = child.SelectedItem.ToString();
}
if (value != "")
{
if (!isDir)
Filters.Add(new FilterTag(tag, value, op));
else
Filters.Add(new FilterBase(value, FilterOperator.None));
await Task.Run(async () =>
{
await _shuffle.GetSongsFromFilter(Filters, _mpd._cancelCommand.Token);
});
SongFilterPanel.Visibility = Visibility.Visible;
SongFilterNumber.Text = _shuffle.SongList.Count.ToString();
}
}
}
private void QueryFilterHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
UpdateFilter_Clicked(null, null);
}
private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^0-9]+");
e.Handled = regex.IsMatch(e.Text);
}
private void QueueValidationNumber()
{
int Number;
try
{
Number = int.Parse(SongNumber.Text);
}
catch (Exception)
{
return;
}
if (Number < 1)
SongNumber.Text = "1";
if (IsFilterEmpty())
{
if (Number > 100)
SongNumber.Text = "100";
}
else
{
if (Number > 1000)
SongNumber.Text = "1000";
}
}
private async void AddToQueue()
{
if (_mpd.GetStats() == null)
return;
await UpdateFilter();
QueueValidationNumber();
// TODO
// Added => Adding songs...
// to
// Added X songs! (display for 5 seconds)
NumberAddedSongs.Text = SongNumber.Text;
SearchStatus.Visibility = Visibility.Visible;
int Num = int.Parse(SongNumber.Text);
await AddToQueue_Internal(Num);
TimedText(SearchStatus, 2);
}
private async Task AddToQueue_Internal(int Num)
{
if (IsFilterEmpty())
{
await Task.Run(async () =>
{
await _shuffle.AddToQueueRandom(Num, _mpd._cancelCommand.Token);
});
}
else
{
await Task.Run(async () =>
{
await _shuffle.AddToQueueFilter(Num, _mpd._cancelCommand.Token);
});
}
}
private void AddToQueueHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
AddToQueue();
}
private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
{
AddToQueue();
}
public bool GetContinuous()
{
return _continuous;
}
public async Task HandleContinuous()
{
if (!_continuous)
return;
int PlaylistLength = _mpd.GetStatus().PlaylistLength;
int Num = 10 - PlaylistLength;
if (Num < 1)
return;
await UpdateFilter();
await AddToQueue_Internal(Num);
}
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
{
if (ContinuousShuffle.IsChecked == true)
_continuous = true;
else
_continuous = false;
if (_mpd.GetStatus().PlaylistLength < 10)
await HandleContinuous();
}
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
WindowState = WindowState.Minimized;
Hide();
}
public void InitHwnd()
{
WindowInteropHelper helper = new(this);
helper.EnsureHandle();
}
}
}

View File

@ -30,11 +30,11 @@
<Image Width="16" Height="16" emoji:Image.Source="📻" />
</MenuItem.Icon>
</MenuItem>
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
</MenuItem.Icon>
</MenuItem>-->
</MenuItem>
<MenuItem Header="{x:Static properties:Resources.Settings}" Command="{Binding Settings}">
<MenuItem.Icon>
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />

View File

@ -71,6 +71,18 @@ namespace unison
}
}
public ICommand Shuffle
{
get
{
return new DelegateCommand
{
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Shuffle_Clicked(null, null),
CanExecuteFunc = () => true
};
}
}
public ICommand Settings
{
get

View File

@ -7,7 +7,7 @@
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
<Win32Resource></Win32Resource>
<StartupObject>unison.App</StartupObject>
<Version>1.3.1</Version>
<Version>1.4</Version>
<Company />
<Authors>Théo Marchal</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>