Compare commits

..

2 Commits

Author SHA1 Message Date
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
14 changed files with 472 additions and 263 deletions

View File

@ -155,7 +155,9 @@ namespace unison
try
{
Debug.WriteLine("SafelySendCommandAsync => before command");
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
Debug.WriteLine("SafelySendCommandAsync => after command");
if (!response.IsResponseValid)
{
string mpdError = response.Response?.Result?.MpdError;
@ -498,6 +500,8 @@ namespace unison
public void Next() => 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));
public void Single() => SendCommand(new SingleCommand(!_currentSingle));

View File

@ -1,13 +1,14 @@
# 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:
* lightweight window that can be toggled with shortcuts
* music control through rebindable shortcuts
* shuffle panel
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
* Radio stations
* radio stations
## Features
@ -15,13 +16,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 +44,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

@ -50,12 +50,13 @@ namespace unison
{
if (_mpd.IsConnected())
{
_mpd.QueryStats();
_settingsWin.UpdateStats();
Snapcast.IsEnabled = true;
ConnectionOkIcon.Visibility = Visibility.Visible;
ConnectionFailIcon.Visibility = Visibility.Collapsed;
_shuffleWin.ListGenre();
_shuffleWin.ListFolder();
_shuffleWin.Initialize();
}
else
{
@ -117,29 +118,15 @@ namespace unison
Debug.WriteLine("Song changed called!");
// handle continuous shuffle
if (_shuffleWin.GetContinuous())
{
System.Collections.Generic.IEnumerable<MpcNET.Types.IMpdFile> a = await _mpd.SafelySendCommandAsync(new PlaylistCommand());
int queueSize = 0;
foreach (var i in a)
{
Debug.WriteLine(i.Path);
queueSize++;
}
Debug.WriteLine("queue size is: " + queueSize);
if (!_shuffleWin.GetContinuous())
return;
if (queueSize < 5)
{
_shuffleWin.AddContinuousSongs();
}
// query queue
// if (queue.SongRemaining < 5)
//{
// // query shuffle songs
//}
}
NextTrack.IsEnabled = false;
PreviousTrack.IsEnabled = false;
await _shuffleWin.HandleContinuous();
NextTrack.IsEnabled = true;
PreviousTrack.IsEnabled = true;
Debug.WriteLine("finished continuous");
}
public void OnStatusChanged(object sender, EventArgs e)
@ -168,9 +155,6 @@ namespace unison
DefaultState();
}
}
_mpd.QueryStats();
_settingsWin.UpdateStats();
}
private void DefaultState(bool LostConnection = false)
@ -243,8 +227,18 @@ 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 Previous_Clicked(object sender, RoutedEventArgs e)
{
if (PreviousTrack.IsEnabled)
_mpd.Prev();
}
public void Next_Clicked(object sender, RoutedEventArgs e)
{
if (NextTrack.IsEnabled)
_mpd.Next();
}
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();

View File

@ -208,19 +208,16 @@
</GroupBox.Header>
<Grid MaxWidth="500">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox TextWrapping="Wrap" Width="25" PreviewTextInput="NumberValidationTextBox" Margin="0,2,0,0"/>
<TextBlock Text="Prevent repetition rate (0-100%)" TextWrapping="Wrap" Margin="5,2,0,0"/>
</StackPanel>
<TextBlock TextWrapping="Wrap" Margin="0,10,0,0">
<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">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>
<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>

View File

@ -4,10 +4,44 @@
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: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" 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">
@ -19,83 +53,63 @@
</TextBlock>
</GroupBox.Header>
<StackPanel Orientation="Vertical" Margin="5,0,5,0">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<TextBlock Text="Song" Margin="0,0,0,2"/>
<TextBox x:Name="Song" Width="240" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="20,0,0,0">
<TextBlock Text="Artist" Margin="0,0,0,2"/>
<TextBox x:Name="Artist" Width="240" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel x:Name="FilterPanel">
<ContentPresenter ContentTemplate="{StaticResource FilterPanel}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<StackPanel Orientation="Vertical">
<TextBlock Text="Album" Margin="0,0,0,2"/>
<TextBox x:Name="Album" Width="240" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="20,0,0,0">
<TextBlock Text="Year" Margin="0,0,0,2"/>
<TextBox x:Name="Year" Width="240" Margin="5,0,0,0"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<StackPanel Orientation="Vertical">
<TextBlock Text="Genre" Margin="0,0,0,2"/>
<ComboBox x:Name="Genre" SelectedIndex="0" Width="240" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="20,0,0,0">
<TextBlock Text="Directory" Margin="0,0,0,2" TextDecorations="{x:Null}"/>
<ComboBox x:Name="Directory" SelectedIndex="0" Width="240" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" IsEnabled="True"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
</StackPanel>
<StackPanel x:Name="SongFilterPanel" Margin="0,5,0,0" Visibility="Collapsed">
<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}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</GroupBox>
<GroupBox Margin="0,5,0,0" HorizontalAlignment="Stretch">
<GroupBox.Header>
<TextBlock>
<emoji:EmojiInline Text="♾️"/>
<Run Text="Continuous shuffle"/>
</TextBlock>
</GroupBox.Header>
<StackPanel Orientation="Horizontal" Margin="5,8,0,0">
<CheckBox x:Name="ContinuousShuffle" Checked="ContinuousShuffle_Checked" Unchecked="ContinuousShuffle_Checked" FocusVisualStyle="{x:Null}">
<TextBlock Text="Enable continuous shuffle" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</GroupBox>
<GroupBox x:Name="AddToQueueGroup" Margin="0,5,0,0" HorizontalAlignment="Stretch">
<GroupBox.Header>
<TextBlock>
<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 Margin="5,0,0,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
<TextBox x:Name="SongNumber" Text="100" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBlock Text="Number of songs to add" Margin="5,0,0,5"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2,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="Added "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs to the 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" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="100" 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="Added "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs"/>
</TextBlock>
</StackPanel>
</StackPanel>
</StackPanel>
</GroupBox>
</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>

View File

@ -10,7 +10,13 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Linq;
using MpcNET.Commands.Queue;
using System.Windows.Input;
using System.Text.RegularExpressions;
namespace unison
{
@ -19,81 +25,61 @@ namespace unison
private MPDHandler _mpd;
bool _continuous = false;
List<string> _songList { get; }
List<string> _genreList { get; }
List<string> _folderList { get; }
List<IFilter> _filters { get; }
public Shuffle()
{
InitializeComponent();
_songList = new();
_genreList = new();
_folderList = new();
_filters = new();
SongFilterNumber.Text = "0";
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
}
private bool IsFilterEmpty()
public void Initialize()
{
if (Song.Text.Length == 0 && Artist.Text.Length == 0 && Album.Text.Length == 0 && Year.Text.Length == 0 && Genre.SelectedIndex == 0 && Directory.Text.Length == 0)
return true;
return false;
}
public bool GetContinuous()
{
return _continuous;
}
public void AddContinuousSongs()
{
if (IsFilterEmpty())
{
// add a completely random song
ContinuousShuffle_AddToQueueRandom();
return;
}
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
HashSet<int> SongIndex = new();
while (SongIndex.Count < 2)
{
int MaxIndex = new Random().Next(0, _songList.Count - 1);
SongIndex.Add(MaxIndex);
}
foreach (int index in SongIndex)
_mpd.AddSong(_songList[index]);
SearchStatus.Visibility = Visibility.Collapsed;
ListGenre();
ListFolder();
}
public async void ListGenre()
{
if (Genre.Items.Count == 0)
{
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
if (_genreList.Count != 0)
return;
if (Response.Count > 0)
{
Genre.Items.Add("");
foreach (var genre in Response)
Genre.Items.Add(genre);
}
}
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()
{
if (Directory.Items.Count == 0)
{
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
if (_folderList.Count != 0)
return;
if (Response != null)
{
Directory.Items.Add("");
foreach (var directory in Response)
Directory.Items.Add(directory.Name);
}
}
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 void Window_Closing(object sender, CancelEventArgs e)
@ -109,120 +95,139 @@ namespace unison
helper.EnsureHandle();
}
private bool IsOnMainThread()
{
return App.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread;
}
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)
{
Song.Text = "";
Artist.Text = "";
Album.Text = "";
Year.Text = "";
Genre.SelectedIndex = 0;
Directory.SelectedIndex = 0;
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
SongFilterNumber.Text = "0";
}
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
private ITag FilterEquivalence_Type(string value)
{
if (ContinuousShuffle.IsChecked == true)
{
AddToQueueGroup.IsEnabled = false;
_continuous = true;
_songList.Clear();
if (!IsFilterEmpty())
{
/*await*/
GetSongsFromFilter();
}
}
else
{
AddToQueueGroup.IsEnabled = true;
_continuous = false;
}
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 async void ContinuousShuffle_AddToQueueRandom()
private FilterOperator FilterEquivalence_Operator(string value)
{
for (int i = 0; i < 2; i++)
{
// generate random number
int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
// query random song
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(MpdTags.Title, "", song, song + 1) });
string Response = await _mpd.SafelySendCommandAsync(commandList);
await Task.Delay(1);
if (Response.Length > 0)
{
// parse song and add it to queue
int start = Response.IndexOf("[file, ");
int end = Response.IndexOf("],");
string filePath = Response.Substring(start + 7, end - (start + 7));
_mpd.AddSong(filePath);
}
}
SearchStatus.Visibility = Visibility.Collapsed;
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 async void AddToQueueRandom()
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
{
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
_filters.Clear();
for (int i = 0; i < int.Parse(SongNumber.Text); i++)
Debug.WriteLine("is on main thread => " + IsOnMainThread());
foreach (ContentPresenter superChild in FilterPanel.Children)
{
// generate random number
int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
ITag tag = MpdTags.Title;
FilterOperator op = FilterOperator.None;
string value = "";
bool isDir = false;
// query random song
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(MpdTags.Title, "", song, song + 1) });
string Response = await _mpd.SafelySendCommandAsync(commandList);
await Task.Delay(1);
if (Response.Length > 0)
StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
{
// parse song and add it to queue
int start = Response.IndexOf("[file, ");
int end = Response.IndexOf("],");
string filePath = Response.Substring(start + 7, end - (start + 7));
_mpd.AddSong(filePath);
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());
}
AddedSongs++;
NumberAddedSongs.Text = AddedSongs.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 GetSongsFromFilter();
}
}
SearchStatus.Visibility = Visibility.Collapsed;
}
private async Task GetSongsFromFilter()
{
Debug.WriteLine("get songs from filter => start");
Debug.WriteLine("is on main thread => " + IsOnMainThread());
_songList.Clear();
SongFilterPanel.Visibility = Visibility.Visible;
int song = _mpd.GetStats().Songs;
List<IFilter> filtersA = new();
if (Song.Text != "")
filtersA.Add(new FilterTag(MpdTags.Title, Song.Text, FilterOperator.Contains));
if (Artist.Text != "")
filtersA.Add(new FilterTag(MpdTags.Artist, Artist.Text, FilterOperator.Contains));
if (Album.Text != "")
filtersA.Add(new FilterTag(MpdTags.Album, Album.Text, FilterOperator.Contains));
if (Year.Text != "")
filtersA.Add(new FilterTag(MpdTags.Date, Year.Text, FilterOperator.Contains));
if (Genre.Text != "")
filtersA.Add(new FilterTag(MpdTags.Genre, Genre.Text, FilterOperator.Contains));
if (Directory.Text != "")
filtersA.Add(new FilterBase(Directory.Text, FilterOperator.None));
Debug.WriteLine("get songs from filter => before command");
Debug.WriteLine(Directory.Text);
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(filtersA, 0, song + 1) });
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(_filters, 0, song + 1) });
string Response = await _mpd.SafelySendCommandAsync(commandList);
Debug.WriteLine(Response);
Debug.WriteLine("get songs from filter => after command");
// create a list of the file url
string[] value = Response.Split(", [file, ");
@ -234,40 +239,185 @@ namespace unison
return;
}
Debug.WriteLine("get songs from filter => before adding to list");
foreach (string file in value)
{
int start = 0;
int end = file.IndexOf("],");
string filePath = file.Substring(start, end - start);
Debug.WriteLine(filePath);
_songList.Add(filePath);
SongFilterNumber.Text = _songList.Count.ToString();
}
Debug.WriteLine("get songs from filter => after adding to list");
// remove characters from first file
_songList[0] = _songList[0].Substring(7, _songList[0].Length - 7);
SongFilterPanel.Visibility = Visibility.Visible;
SongFilterNumber.Text = _songList.Count.ToString();
Debug.WriteLine("get songs from filter => finish");
}
private async void AddToQueueFilter()
private void FilterType_Change(object sender, string Operator, List<string> Listing)
{
await GetSongsFromFilter();
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)
{
Debug.WriteLine("selection changed => operator type");
SongFilterNumber.Text = "0";
}
private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^0-9]+");
e.Handled = regex.IsMatch(e.Text);
}
private void QueueValidationNumber()
{
if (int.Parse(SongNumber.Text) < 1)
SongNumber.Text = "1";
if (int.Parse(SongNumber.Text) > 1000)
SongNumber.Text = "1000";
}
private async void AddToQueue_Clicked(object sender, RoutedEventArgs e)
{
QueueValidationNumber();
if (_mpd.GetStats() == null)
return;
if (IsFilterEmpty())
await AddToQueueRandom(int.Parse(SongNumber.Text)); // @TODO await or not???
else
{
UpdateFilter_Clicked(null, null);
await AddToQueueFilter(int.Parse(SongNumber.Text)); // @TODO await or not???
}
}
private async /*void*/ Task AddToQueueRandom(int SongNumber)
{
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
Debug.WriteLine("song to add => " + SongNumber);
for (int i = 0; i < SongNumber; i++)
{
// generate random number
int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
// query random song
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1) });
string Response = await _mpd.SafelySendCommandAsync(commandList);
await Task.Delay(1);
if (Response.Length > 0)
{
// parse song and add it to queue
int start = Response.IndexOf("[file, ");
int end = Response.IndexOf("],");
string filePath = Response.Substring(start + 7, end - (start + 7));
_mpd.AddSong(filePath);
Debug.WriteLine("song path => " + filePath);
AddedSongs++;
NumberAddedSongs.Text = AddedSongs.ToString();
}
}
// TODO make play at first position added, as soon as possible
if (!_mpd.IsPlaying())
_mpd.Play(0);
SearchStatus.Visibility = Visibility.Collapsed;
}
private async /*void*/ Task AddToQueueFilter(int SongNumber)
{
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
Debug.WriteLine("song to add => " + SongNumber);
// more requested songs than available => add everything
if (int.Parse(SongNumber.Text) > _songList.Count)
if (SongNumber > _songList.Count)
{
foreach (string path in _songList)
{
await Task.Delay(1);
_mpd.AddSong(path);
Debug.WriteLine("song path => " + path);
AddedSongs++;
NumberAddedSongs.Text = AddedSongs.ToString();
@ -278,7 +428,7 @@ namespace unison
else
{
HashSet<int> SongIndex = new();
while (SongIndex.Count < int.Parse(SongNumber.Text))
while (SongIndex.Count < SongNumber)
{
int MaxIndex = new Random().Next(0, _songList.Count - 1);
SongIndex.Add(MaxIndex);
@ -289,19 +439,55 @@ namespace unison
}
SearchStatus.Visibility = Visibility.Collapsed;
}
private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
public bool GetContinuous()
{
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
if (_mpd.GetStats() == null)
return _continuous;
}
public async Task HandleContinuous()
{
if (!GetContinuous())
return;
if (IsFilterEmpty())
AddToQueueRandom();
Debug.WriteLine("is on main thread => " + IsOnMainThread());
IEnumerable<IMpdFile> Playlist = await _mpd.SafelySendCommandAsync(new PlaylistCommand());
int queueSize = 0;
foreach (IMpdFile file in Playlist)
{
Debug.WriteLine(file.Path);
queueSize++;
}
Debug.WriteLine("queue size is: " + queueSize);
if (queueSize < 5)
{
if (_mpd.GetStats() == null)
return;
if (IsFilterEmpty())
await AddToQueueRandom(5); // @TODO await or not?
else
await AddToQueueFilter(5); // @TODO await or not?
}
}
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
{
if (ContinuousShuffle.IsChecked == true)
{
_continuous = true;
_songList.Clear();
if (!IsFilterEmpty())
UpdateFilter_Clicked(null, null);
}
else
AddToQueueFilter();
_continuous = false;
await HandleContinuous();
}
}
}