9 Commits

15 changed files with 189 additions and 615 deletions

View File

@ -1,5 +1,27 @@
# Changelog # Changelog
## v1.3.1
*Released: 03/11/2022*
* Update .NET version from 5.0 to 6.0
* Fix: simple patch to avoid a crash concerning GetAlbumCover
* Fix: connection change now working
## v1.3
*Released: 18/04/2022*
* New feature: add support for readpicture, aka embedded cover art
* New feature: add support for MPD password
* Spanish translation
* Trim album release date
* Cover icon when a radio is playing
* Update MpcNET package from 1.3 to 1.4
* Fix: Snapcast not working with hostname
* Fix: some radios crash
* Fix: disable/enable radios and Snapcast with connection
## v1.2 ## v1.2
*Released: 07/04/2022* *Released: 07/04/2022*

View File

@ -64,17 +64,19 @@ namespace unison
private MpcConnection _connection; private MpcConnection _connection;
private MpcConnection _commandConnection; private MpcConnection _commandConnection;
private IPEndPoint _mpdEndpoint; private IPEndPoint _mpdEndpoint;
private CancellationTokenSource _cancelToken;
private CancellationTokenSource _cancelCommand;
private CancellationTokenSource _cancelConnect;
public MPDHandler() public MPDHandler()
{ {
Initialize(null, null); Startup(null, null);
_stats = new Statistics(); _stats = new Statistics();
_retryTimer = new DispatcherTimer(); _retryTimer = new DispatcherTimer();
_retryTimer.Interval = TimeSpan.FromSeconds(5); _retryTimer.Interval = TimeSpan.FromSeconds(5);
_retryTimer.Tick += Initialize; _retryTimer.Tick += Startup;
_elapsedTimer = new System.Timers.Timer(500); _elapsedTimer = new System.Timers.Timer(500);
_elapsedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ElapsedTimer); _elapsedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ElapsedTimer);
@ -147,7 +149,7 @@ namespace unison
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command) public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
{ {
if (_commandConnection == null) if (_commandConnection == null || !IsConnected())
{ {
Trace.WriteLine("[SafelySendCommandAsync] no command connection"); Trace.WriteLine("[SafelySendCommandAsync] no command connection");
return default(T); return default(T);
@ -175,21 +177,52 @@ namespace unison
return default(T); return default(T);
} }
private void Initialize(object sender, EventArgs e) public async void Startup(object sender, EventArgs e)
{ {
if (!_connected) await Initialize();
Connect();
} }
public async void Connect() public async Task Initialize()
{ {
_cancelToken = new CancellationTokenSource(); Trace.WriteLine("Initializing");
CancellationToken token = _cancelToken.Token;
Disconnected();
if (!_connected)
await Connect();
}
public void Disconnected()
{
_connected = false;
ConnectionChanged?.Invoke(this, EventArgs.Empty);
_commandConnection?.DisconnectAsync();
_connection?.DisconnectAsync();
_cancelConnect?.Cancel();
_cancelConnect = new CancellationTokenSource();
_cancelCommand?.Cancel();
_cancelCommand = new CancellationTokenSource();
_connection = null;
_commandConnection = null;
Trace.WriteLine("Disconnected");
}
public async Task Connect()
{
Trace.WriteLine("Connecting");
if (_cancelCommand.IsCancellationRequested || _cancelConnect.IsCancellationRequested)
return;
try try
{ {
_connection = await ConnectInternal(token); _connection = await ConnectInternal(_cancelConnect.Token);
_commandConnection = await ConnectInternal(token); _commandConnection = await ConnectInternal(_cancelCommand.Token);
} }
catch (MpcNET.Exceptions.MpcConnectException e) catch (MpcNET.Exceptions.MpcConnectException e)
{ {
@ -217,11 +250,14 @@ namespace unison
await UpdateStatusAsync(); await UpdateStatusAsync();
await UpdateSongAsync(); await UpdateSongAsync();
Loop(token); Loop(_cancelCommand.Token);
} }
private async Task<MpcConnection> ConnectInternal(CancellationToken token) private async Task<MpcConnection> ConnectInternal(CancellationToken token)
{ {
if (token.IsCancellationRequested)
return null;
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress); IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress);
if (_ipAddress == null) if (_ipAddress == null)
@ -261,18 +297,6 @@ namespace unison
return connection; return connection;
} }
public void Disconnected()
{
_connected = false;
ConnectionChanged?.Invoke(this, EventArgs.Empty);
if (_connection != null)
_connection = null;
if (_commandConnection != null)
_commandConnection = null;
}
private void Loop(CancellationToken token) private void Loop(CancellationToken token)
{ {
Task.Run(async () => Task.Run(async () =>
@ -281,8 +305,7 @@ namespace unison
{ {
try try
{ {
token.ThrowIfCancellationRequested(); if (token.IsCancellationRequested || _connection == null || !IsConnected())
if (token.IsCancellationRequested || _connection == null)
break; break;
IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update")); IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update"));
@ -291,14 +314,17 @@ namespace unison
await HandleIdleResponseAsync(idleChanges.Response.Content); await HandleIdleResponseAsync(idleChanges.Response.Content);
else else
{ {
Trace.WriteLine($"Error in Idle connection thread: {idleChanges.Response?.Content}"); Trace.WriteLine($"Error in Idle connection thread (1): {idleChanges.Response?.Content}");
throw new Exception(idleChanges.Response?.Content); throw new Exception(idleChanges.Response?.Content);
} }
} }
catch (Exception e) catch (Exception e)
{ {
if (token.IsCancellationRequested)
Trace.WriteLine($"Idle connection cancelled.");
else
Trace.WriteLine($"Error in Idle connection thread: {e.Message}"); Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
Disconnected(); await Initialize();
break; break;
} }
} }
@ -321,6 +347,7 @@ namespace unison
catch (Exception e) catch (Exception e)
{ {
Trace.WriteLine($"Error in Idle connection thread: {e.Message}"); Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
await Initialize();
} }
} }
@ -345,6 +372,7 @@ namespace unison
catch (Exception e) catch (Exception e)
{ {
Trace.WriteLine($"Error in Idle connection thread: {e.Message}"); Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
await Initialize();
} }
_isUpdatingStatus = false; _isUpdatingStatus = false;
@ -352,6 +380,8 @@ namespace unison
private async Task UpdateSongAsync() private async Task UpdateSongAsync()
{ {
Trace.WriteLine("Updating song");
if (_connection == null || _isUpdatingSong) if (_connection == null || _isUpdatingSong)
return; return;
@ -371,13 +401,17 @@ namespace unison
catch (Exception e) catch (Exception e)
{ {
Trace.WriteLine($"Error in Idle connection thread: {e.Message}"); Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
await Initialize();
} }
_isUpdatingSong = false; _isUpdatingSong = false;
} }
private async void GetAlbumCover(string path, CancellationToken token = default) private async void GetAlbumCover(string path, CancellationToken token)
{ {
if (token.IsCancellationRequested)
return;
List<byte> data = new List<byte>(); List<byte> data = new List<byte>();
try try
{ {
@ -390,7 +424,7 @@ namespace unison
if (_connection == null) if (_connection == null)
return; return;
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize)); IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
if (!albumReq.IsResponseValid) if (!albumReq.IsResponseValid)
break; break;
@ -412,7 +446,7 @@ namespace unison
if (_connection == null) if (_connection == null)
return; return;
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize)); IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize));
if (!albumReq.IsResponseValid) if (!albumReq.IsResponseValid)
break; break;
@ -428,6 +462,9 @@ namespace unison
} }
catch (Exception e) catch (Exception e)
{ {
if (token.IsCancellationRequested)
return;
Trace.WriteLine("Exception caught while getting albumart: " + e); Trace.WriteLine("Exception caught while getting albumart: " + e);
return; return;
} }
@ -441,7 +478,7 @@ namespace unison
{ {
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); _cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
} }
catch (System.NotSupportedException) catch
{ {
_cover = null; _cover = null;
} }
@ -476,7 +513,7 @@ namespace unison
SongChanged?.Invoke(this, EventArgs.Empty); SongChanged?.Invoke(this, EventArgs.Empty);
string uri = Regex.Escape(_currentSong.Path); string uri = Regex.Escape(_currentSong.Path);
GetAlbumCover(uri); GetAlbumCover(uri, _cancelCommand.Token);
} }
public void UpdateCover() public void UpdateCover()
@ -553,24 +590,25 @@ namespace unison
public async void QueryStats() public async void QueryStats()
{ {
Dictionary<string, string> Response = await SafelySendCommandAsync(new StatsCommand()); Dictionary<string, string> response = await SafelySendCommandAsync(new StatsCommand());
if (Response == null)
return;
_stats.Songs = int.Parse(Response["songs"]); if (response != null)
_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; TimeSpan time;
time = TimeSpan.FromSeconds(int.Parse(Response["uptime"])); time = TimeSpan.FromSeconds(int.Parse(response["uptime"]));
_stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss"); _stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(Response["db_playtime"])); time = TimeSpan.FromSeconds(int.Parse(response["db_playtime"]));
_stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss"); _stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss");
time = TimeSpan.FromSeconds(int.Parse(Response["playtime"])); time = TimeSpan.FromSeconds(int.Parse(response["playtime"]));
_stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss"); _stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss");
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(Response["db_update"])).ToLocalTime(); DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(response["db_update"])).ToLocalTime();
_stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm"); _stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm");
} }
} }
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
Resources/nothing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

BIN
Resources/radio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -104,21 +104,21 @@
<VisualBrush Visual="{Binding ElementName=mask}"/> <VisualBrush Visual="{Binding ElementName=mask}"/>
</StackPanel.OpacityMask> </StackPanel.OpacityMask>
<Image x:Name="Cover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" Visibility="Collapsed" /> <Image x:Name="Cover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" Visibility="Collapsed" />
<Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" /> <Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" Visibility="Collapsed" />
<Image x:Name="RadioCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/radio.png" Visibility="Collapsed" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40"> <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="10,0,0,0"> <StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,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"> <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 Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/> <emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/> <TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
</StackPanel> </StackPanel>
</Button> </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"> <StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="📻" Padding="0,0,0,2"/> <emoji:TextBlock Text="📻" Padding="0,0,0,2"/>
<TextBlock Text="{x:Static properties:Resources.Radios}" Margin="5, 0, 0, 0"/> <TextBlock Text="{x:Static properties:Resources.Radios}" Margin="5, 0, 0, 0"/>
@ -136,13 +136,12 @@
<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" /> <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>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0"> <StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
<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"> <!--<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/> <emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/> <TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
</StackPanel> </StackPanel>
</Button> </Button>-->
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}"> <Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/> <emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/>

View File

@ -6,8 +6,6 @@ using System.Windows.Threading;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using MpcNET.Commands.Queue;
using System.Diagnostics;
namespace unison namespace unison
{ {
@ -15,7 +13,6 @@ namespace unison
{ {
private readonly Settings _settingsWin; private readonly Settings _settingsWin;
private readonly Radios _radiosWin; private readonly Radios _radiosWin;
private readonly Shuffle _shuffleWin;
private readonly DispatcherTimer _timer; private readonly DispatcherTimer _timer;
private readonly MPDHandler _mpd; private readonly MPDHandler _mpd;
@ -28,7 +25,6 @@ namespace unison
_settingsWin = new Settings(); _settingsWin = new Settings();
_radiosWin = new Radios(); _radiosWin = new Radios();
_shuffleWin = new Shuffle();
_timer = new DispatcherTimer(); _timer = new DispatcherTimer();
_mpd = (MPDHandler)Application.Current.Properties["mpd"]; _mpd = (MPDHandler)Application.Current.Properties["mpd"];
@ -50,12 +46,12 @@ namespace unison
{ {
if (_mpd.IsConnected()) if (_mpd.IsConnected())
{ {
Snapcast.IsEnabled = true;
ConnectionOkIcon.Visibility = Visibility.Visible; ConnectionOkIcon.Visibility = Visibility.Visible;
ConnectionFailIcon.Visibility = Visibility.Collapsed; ConnectionFailIcon.Visibility = Visibility.Collapsed;
_shuffleWin.ListGenre(); Snapcast.IsEnabled = true;
_shuffleWin.ListFolder(); if (_radiosWin.IsConnected())
Radio.IsEnabled = true;
} }
else else
{ {
@ -63,12 +59,15 @@ namespace unison
DefaultState(true); DefaultState(true);
ConnectionOkIcon.Visibility = Visibility.Collapsed; ConnectionOkIcon.Visibility = Visibility.Collapsed;
ConnectionFailIcon.Visibility = Visibility.Visible; ConnectionFailIcon.Visibility = Visibility.Visible;
Snapcast.IsEnabled = false;
Radio.IsEnabled = false;
} }
_settingsWin.UpdateConnectionStatus(); _settingsWin.UpdateConnectionStatus();
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}"; Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
} }
public async void OnSongChanged(object sender, EventArgs e) public void OnSongChanged(object sender, EventArgs e)
{ {
if (_mpd.GetCurrentSong() == null) if (_mpd.GetCurrentSong() == null)
return; return;
@ -114,32 +113,6 @@ namespace unison
_timer.Start(); _timer.Start();
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time); EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
} }
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 (queueSize < 5)
{
_shuffleWin.AddContinuousSongs();
}
// query queue
// if (queue.SongRemaining < 5)
//{
// // query shuffle songs
//}
}
} }
public void OnStatusChanged(object sender, EventArgs e) public void OnStatusChanged(object sender, EventArgs e)
@ -186,8 +159,9 @@ namespace unison
PlayPause.Text = (string)Application.Current.FindResource("pauseButton"); PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
TimeSlider.Value = 50; TimeSlider.Value = 50;
TimeSlider.IsEnabled = false; TimeSlider.IsEnabled = false;
NoCover.Visibility = Visibility.Visible; NoCover.Visibility = Visibility.Collapsed;
Cover.Visibility = Visibility.Collapsed; Cover.Visibility = Visibility.Collapsed;
RadioCover.Visibility = Visibility.Collapsed;
if (LostConnection) if (LostConnection)
{ {
@ -199,16 +173,18 @@ namespace unison
public void OnCoverChanged(object sender, EventArgs e) public void OnCoverChanged(object sender, EventArgs e)
{ {
if (_mpd.GetCover() == null) NoCover.Visibility = Visibility.Collapsed;
{
NoCover.Visibility = Visibility.Visible;
Cover.Visibility = Visibility.Collapsed; Cover.Visibility = Visibility.Collapsed;
} RadioCover.Visibility = Visibility.Collapsed;
if (_mpd.GetCurrentSong().Time == -1)
RadioCover.Visibility = Visibility.Visible;
else if (_mpd.GetCover() == null)
NoCover.Visibility = Visibility.Visible;
else if (Cover.Source != _mpd.GetCover()) else if (Cover.Source != _mpd.GetCover())
{ {
Cover.Source = _mpd.GetCover(); Cover.Source = _mpd.GetCover();
Cover.Visibility = Visibility.Visible; Cover.Visibility = Visibility.Visible;
NoCover.Visibility = Visibility.Collapsed;
} }
} }
@ -221,11 +197,6 @@ namespace unison
SnapcastText.Text = unison.Resources.Resources.StartSnapcast; SnapcastText.Text = unison.Resources.Resources.StartSnapcast;
} }
public void OnRadioBrowserConnected()
{
Radio.IsEnabled = true;
}
public void UpdateButton(ref Border border, bool b) public void UpdateButton(ref Border border, bool b)
{ {
border.Style = b ? (Style)Resources["SelectedButton"] : (Style)Resources["UnselectedButton"]; border.Style = b ? (Style)Resources["SelectedButton"] : (Style)Resources["UnselectedButton"];
@ -267,15 +238,6 @@ namespace unison
_radiosWin.WindowState = WindowState.Normal; _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) public void Settings_Clicked(object sender, RoutedEventArgs e)
{ {
_settingsWin.Show(); _settingsWin.Show();

View File

@ -54,6 +54,9 @@ namespace unison
{ {
private RadioBrowserClient _radioBrowser; private RadioBrowserClient _radioBrowser;
private MPDHandler _mpd; private MPDHandler _mpd;
private bool _connected = true;
public bool IsConnected() => _connected;
public Radios() public Radios()
{ {
@ -69,11 +72,8 @@ namespace unison
Debug.WriteLine("Exception while connecting to RadioBrowser: " + e.Message); Debug.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
return; return;
} }
Application.Current.Dispatcher.Invoke(() =>
{ _connected = true;
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
MainWin.OnRadioBrowserConnected();
});
} }
public async void Initialize() public async void Initialize()

View File

@ -43,7 +43,7 @@
<StackPanel Margin="0,5,0,0"> <StackPanel Margin="0,5,0,0">
<TextBlock Text="{x:Static properties:Resources.Settings_Password}" TextWrapping="Wrap" Margin="5,0,0,0"/> <TextBlock Text="{x:Static properties:Resources.Settings_Password}" TextWrapping="Wrap" Margin="5,0,0,0"/>
<PasswordBox x:Name="MpdPassword" Width="250" Margin="10,2,0,0"/> <PasswordBox x:Name="MpdPassword" KeyDown="ConnectHandler" Width="250" Margin="10,2,0,0"/>
<TextBlock Text="{x:Static properties:Resources.Settings_ConnectionPasswordInfo}" TextWrapping="Wrap" Margin="10,5,0,0" MaxWidth="250" HorizontalAlignment="Left"/> <TextBlock Text="{x:Static properties:Resources.Settings_ConnectionPasswordInfo}" TextWrapping="Wrap" Margin="10,5,0,0" MaxWidth="250" HorizontalAlignment="Left"/>
</StackPanel> </StackPanel>
@ -52,7 +52,6 @@
<Run x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}"/> <Run x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}"/>
</TextBlock> </TextBlock>
<!--<TextBlock x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}" TextWrapping="Wrap" Margin="5,10,0,0"/>-->
<Button x:Name="ConnectButton" Content="{x:Static properties:Resources.Settings_ConnectButton}" Margin="0,10,0,0" Width="120" Click="MPDConnect_Clicked"/> <Button x:Name="ConnectButton" Content="{x:Static properties:Resources.Settings_ConnectButton}" Margin="0,10,0,0" Width="120" Click="MPDConnect_Clicked"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -60,6 +59,36 @@
</DockPanel> </DockPanel>
</TabItem> </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}"> <TabItem Header="{x:Static properties:Resources.Settings_Shortcuts}">
<DockPanel Margin="8"> <DockPanel Margin="8">
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0"> <GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
@ -161,67 +190,7 @@
</StackPanel> </StackPanel>
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="ShortcutsReset_Clicked"/> <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>
<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">
<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>
</TextBlock>
</StackPanel> </StackPanel>
</Grid> </Grid>
</GroupBox> </GroupBox>

View File

@ -111,14 +111,11 @@ namespace unison
SaveSettings(); SaveSettings();
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
if (mpd.IsConnected())
mpd = new MPDHandler();
ConnectButton.IsEnabled = false; ConnectButton.IsEnabled = false;
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting; ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
System.Threading.Tasks.Task.Run(() => { mpd.Connect(); }); MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
System.Threading.Tasks.Task.Run(async () => { await mpd.Initialize(); });
} }
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e) private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)

View File

@ -1,102 +0,0 @@
<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"
mc:Ignorable="d"
Title="Shuffle" Closing="Window_Closing" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
<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 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>
<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">
<TextBlock>
<Run Text="Number of songs in filter: "/><Run x:Name="SongFilterNumber" FontWeight="Bold"/>
</TextBlock>
</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>
<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>
</StackPanel>
</StackPanel>
</GroupBox>
</StackPanel>
</StackPanel>
</Grid>
</Window>

View File

@ -1,307 +0,0 @@
using MpcNET;
using MpcNET.Commands.Database;
using MpcNET.Commands.Reflection;
using MpcNET.Tags;
using MpcNET.Types;
using MpcNET.Types.Filters;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
namespace unison
{
public partial class Shuffle : Window
{
private MPDHandler _mpd;
bool _continuous = false;
List<string> _songList { get; }
public Shuffle()
{
InitializeComponent();
_songList = new();
}
private bool IsFilterEmpty()
{
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;
}
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 (Response.Count > 0)
{
Genre.Items.Add("");
foreach (var genre in Response)
Genre.Items.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 (Response != null)
{
Directory.Items.Add("");
foreach (var directory in Response)
Directory.Items.Add(directory.Name);
}
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
WindowState = WindowState.Minimized;
Hide();
}
public void InitHwnd()
{
WindowInteropHelper helper = new(this);
helper.EnsureHandle();
}
private void Reset_Clicked(object sender, RoutedEventArgs e)
{
Song.Text = "";
Artist.Text = "";
Album.Text = "";
Year.Text = "";
Genre.SelectedIndex = 0;
Directory.SelectedIndex = 0;
}
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
{
if (ContinuousShuffle.IsChecked == true)
{
AddToQueueGroup.IsEnabled = false;
_continuous = true;
_songList.Clear();
if (!IsFilterEmpty())
{
/*await*/
GetSongsFromFilter();
}
}
else
{
AddToQueueGroup.IsEnabled = true;
_continuous = false;
}
}
private async void ContinuousShuffle_AddToQueueRandom()
{
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;
}
private async void AddToQueueRandom()
{
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
for (int i = 0; i < int.Parse(SongNumber.Text); 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);
AddedSongs++;
NumberAddedSongs.Text = AddedSongs.ToString();
}
}
SearchStatus.Visibility = Visibility.Collapsed;
}
private async Task GetSongsFromFilter()
{
_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(Directory.Text);
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(filtersA, 0, song + 1) });
string Response = await _mpd.SafelySendCommandAsync(commandList);
Debug.WriteLine(Response);
// create a list of the file url
string[] value = Response.Split(", [file, ");
// there are no song in this filter
if (value[0] == "")
{
SongFilterNumber.Text = _songList.Count.ToString();
return;
}
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();
}
// remove characters from first file
_songList[0] = _songList[0].Substring(7, _songList[0].Length - 7);
SongFilterPanel.Visibility = Visibility.Visible;
SongFilterNumber.Text = _songList.Count.ToString();
}
private async void AddToQueueFilter()
{
await GetSongsFromFilter();
int AddedSongs = 0;
NumberAddedSongs.Text = AddedSongs.ToString();
SearchStatus.Visibility = Visibility.Visible;
// more requested songs than available => add everything
if (int.Parse(SongNumber.Text) > _songList.Count)
{
foreach (string path in _songList)
{
await Task.Delay(1);
_mpd.AddSong(path);
AddedSongs++;
NumberAddedSongs.Text = AddedSongs.ToString();
}
}
// more available songs than requested =>
// we add unique indexes until we reach the requested amount
else
{
HashSet<int> SongIndex = new();
while (SongIndex.Count < int.Parse(SongNumber.Text))
{
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;
}
private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
{
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
if (_mpd.GetStats() == null)
return;
if (IsFilterEmpty())
AddToQueueRandom();
else
AddToQueueFilter();
}
}
}

View File

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

View File

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

View File

@ -2,17 +2,17 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon> <ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
<Win32Resource></Win32Resource> <Win32Resource></Win32Resource>
<StartupObject>unison.App</StartupObject> <StartupObject>unison.App</StartupObject>
<Version>1.3</Version> <Version>1.3.1</Version>
<Company /> <Company />
<Authors>Théo Marchal</Authors> <Authors>Théo Marchal</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://git.n700.ovh/keb/unison</PackageProjectUrl> <PackageProjectUrl>https://github.com/ZetaKebab/unison</PackageProjectUrl>
<RepositoryUrl>https://git.n700.ovh/keb/unison</RepositoryUrl> <RepositoryUrl>https://github.com/ZetaKebab/unison</RepositoryUrl>
<Copyright>Théo Marchal</Copyright> <Copyright>Théo Marchal</Copyright>
<PackageIconUrl /> <PackageIconUrl />
</PropertyGroup> </PropertyGroup>
@ -22,6 +22,8 @@
<None Remove="Resources\icon-mini.ico" /> <None Remove="Resources\icon-mini.ico" />
<None Remove="Resources\nocover.png" /> <None Remove="Resources\nocover.png" />
<None Remove="LICENSE" /> <None Remove="LICENSE" />
<None Remove="Resources\nothing.png" />
<None Remove="Resources\radio.png" />
<None Include="LICENSE"> <None Include="LICENSE">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath></PackagePath> <PackagePath></PackagePath>
@ -38,6 +40,12 @@
<Resource Include="Resources\nocover.png"> <Resource Include="Resources\nocover.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource> </Resource>
<Resource Include="Resources\nothing.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\radio.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<Content Include="LICENSE"> <Content Include="LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>