Compare commits
10 Commits
v1.3
...
792437b839
Author | SHA1 | Date | |
---|---|---|---|
792437b839 | |||
5bfa7d3b5b | |||
c5e8534af7 | |||
5d6e3b6d1e | |||
6ad4d9c813 | |||
c93a9a326e | |||
c055c59de7 | |||
4c71d6a6e0 | |||
3685c369b4 | |||
e0d640532c |
14
CHANGELOG.md
@ -1,19 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 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*
|
||||||
|
@ -155,7 +155,9 @@ namespace unison
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Debug.WriteLine("SafelySendCommandAsync => before command");
|
||||||
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
||||||
|
Debug.WriteLine("SafelySendCommandAsync => after command");
|
||||||
if (!response.IsResponseValid)
|
if (!response.IsResponseValid)
|
||||||
{
|
{
|
||||||
string mpdError = response.Response?.Result?.MpdError;
|
string mpdError = response.Response?.Result?.MpdError;
|
||||||
@ -498,6 +500,8 @@ namespace unison
|
|||||||
public void Next() => SendCommand(new NextCommand());
|
public void Next() => SendCommand(new NextCommand());
|
||||||
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
||||||
|
|
||||||
|
public void Play(int pos) => SendCommand(new PlayCommand(pos));
|
||||||
|
|
||||||
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
||||||
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
||||||
public void Single() => SendCommand(new SingleCommand(!_currentSingle));
|
public void Single() => SendCommand(new SingleCommand(!_currentSingle));
|
||||||
@ -553,25 +557,24 @@ 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;
|
||||||
|
|
||||||
if (response != null)
|
_stats.Songs = int.Parse(Response["songs"]);
|
||||||
{
|
_stats.Albums = int.Parse(Response["albums"]);
|
||||||
_stats.Songs = int.Parse(response["songs"]);
|
_stats.Artists = int.Parse(Response["artists"]);
|
||||||
_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");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
28
README.md
@ -1,13 +1,14 @@
|
|||||||
# unison
|
# unison
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**unison** is a very simple [Music Player Daemon (MPD)](https://www.musicpd.org/) daemon client with the following goals:
|
**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
|
* lightweight window that can be toggled with shortcuts
|
||||||
* music control through rebindable shortcuts
|
* music control through rebindable shortcuts
|
||||||
|
* shuffle panel
|
||||||
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
|
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
|
||||||
* Radio stations
|
* radio stations
|
||||||
|
|
||||||
## Features
|
## 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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Shortcuts
|
### 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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Snapcast
|
### 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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Planned features
|
## Planned features
|
||||||
|
|
||||||
* A complete shuffle system based on set criteria, aka a smart playlist.
|
* Playlist, queue and library management
|
||||||
* Playlist, queue and library management. I use other software to do it, but I will implement them at some point.
|
* More options for the shuffle panel
|
||||||
|
* Dark mode
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 369 B |
Before Width: | Height: | Size: 11 KiB |
BIN
Screenshots/screen-mainwindow.png
Normal file
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
Screenshots/screen-shortcuts.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Screenshots/screen-shuffle.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
Screenshots/screen-systray.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -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" Visibility="Collapsed" />
|
<Image x:Name="NoCover" HorizontalAlignment="Center" VerticalAlignment="Center" Source="/Resources/nocover.png" />
|
||||||
<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="0,0,10,0">
|
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,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">
|
<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">
|
<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="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,12 +136,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" />
|
<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="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">
|
<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="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"/>
|
||||||
|
@ -6,6 +6,8 @@ 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
|
||||||
{
|
{
|
||||||
@ -13,6 +15,7 @@ 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;
|
||||||
|
|
||||||
@ -25,6 +28,7 @@ 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"];
|
||||||
|
|
||||||
@ -46,12 +50,13 @@ namespace unison
|
|||||||
{
|
{
|
||||||
if (_mpd.IsConnected())
|
if (_mpd.IsConnected())
|
||||||
{
|
{
|
||||||
ConnectionOkIcon.Visibility = Visibility.Visible;
|
_mpd.QueryStats();
|
||||||
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
_settingsWin.UpdateStats();
|
||||||
|
|
||||||
Snapcast.IsEnabled = true;
|
Snapcast.IsEnabled = true;
|
||||||
if (_radiosWin.IsConnected())
|
ConnectionOkIcon.Visibility = Visibility.Visible;
|
||||||
Radio.IsEnabled = true;
|
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
||||||
|
_shuffleWin.Initialize();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -59,15 +64,12 @@ 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 void OnSongChanged(object sender, EventArgs e)
|
public async void OnSongChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_mpd.GetCurrentSong() == null)
|
if (_mpd.GetCurrentSong() == null)
|
||||||
return;
|
return;
|
||||||
@ -113,6 +115,18 @@ namespace unison
|
|||||||
_timer.Start();
|
_timer.Start();
|
||||||
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine("Song changed called!");
|
||||||
|
|
||||||
|
if (!_shuffleWin.GetContinuous())
|
||||||
|
return;
|
||||||
|
|
||||||
|
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)
|
public void OnStatusChanged(object sender, EventArgs e)
|
||||||
@ -141,9 +155,6 @@ namespace unison
|
|||||||
DefaultState();
|
DefaultState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_mpd.QueryStats();
|
|
||||||
_settingsWin.UpdateStats();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DefaultState(bool LostConnection = false)
|
private void DefaultState(bool LostConnection = false)
|
||||||
@ -159,9 +170,8 @@ 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.Collapsed;
|
NoCover.Visibility = Visibility.Visible;
|
||||||
Cover.Visibility = Visibility.Collapsed;
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
RadioCover.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
if (LostConnection)
|
if (LostConnection)
|
||||||
{
|
{
|
||||||
@ -173,18 +183,16 @@ namespace unison
|
|||||||
|
|
||||||
public void OnCoverChanged(object sender, EventArgs e)
|
public void OnCoverChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
NoCover.Visibility = Visibility.Collapsed;
|
if (_mpd.GetCover() == null)
|
||||||
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;
|
NoCover.Visibility = Visibility.Visible;
|
||||||
|
Cover.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +205,11 @@ 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"];
|
||||||
@ -214,8 +227,18 @@ namespace unison
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
|
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 Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
|
||||||
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
||||||
@ -238,6 +261,15 @@ 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();
|
||||||
|
@ -54,9 +54,6 @@ 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()
|
||||||
{
|
{
|
||||||
@ -72,8 +69,11 @@ 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()
|
||||||
|
@ -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" KeyDown="ConnectHandler" Width="250" Margin="10,2,0,0"/>
|
<PasswordBox x:Name="MpdPassword" 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,6 +52,7 @@
|
|||||||
<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>
|
||||||
@ -59,36 +60,6 @@
|
|||||||
</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">
|
||||||
@ -190,7 +161,64 @@
|
|||||||
</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>
|
||||||
|
<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>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
116
Views/Shuffle.xaml
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<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" 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}"/>
|
||||||
|
</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" 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>
|
||||||
|
</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>
|
493
Views/Shuffle.xaml.cs
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
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.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
|
||||||
|
{
|
||||||
|
public partial class Shuffle : Window
|
||||||
|
{
|
||||||
|
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"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
ListGenre();
|
||||||
|
ListFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ListGenre()
|
||||||
|
{
|
||||||
|
if (_genreList.Count != 0)
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
if (_folderList.Count != 0)
|
||||||
|
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 void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitHwnd()
|
||||||
|
{
|
||||||
|
WindowInteropHelper helper = new(this);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
|
||||||
|
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
||||||
|
SongFilterNumber.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_filters.Clear();
|
||||||
|
|
||||||
|
Debug.WriteLine("is on main thread => " + IsOnMainThread());
|
||||||
|
|
||||||
|
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 GetSongsFromFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Debug.WriteLine("get songs from filter => before command");
|
||||||
|
|
||||||
|
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(_filters, 0, song + 1) });
|
||||||
|
string Response = await _mpd.SafelySendCommandAsync(commandList);
|
||||||
|
|
||||||
|
Debug.WriteLine("get songs from filter => after command");
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
_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 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)
|
||||||
|
{
|
||||||
|
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 (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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int index in SongIndex)
|
||||||
|
_mpd.AddSong(_songList[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchStatus.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool GetContinuous()
|
||||||
|
{
|
||||||
|
return _continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleContinuous()
|
||||||
|
{
|
||||||
|
if (!GetContinuous())
|
||||||
|
return;
|
||||||
|
|
||||||
|
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
|
||||||
|
_continuous = false;
|
||||||
|
|
||||||
|
await HandleContinuous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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="🛠️" />
|
||||||
|
@ -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
|
public ICommand Settings
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
<Company />
|
<Company />
|
||||||
<Authors>Théo Marchal</Authors>
|
<Authors>Théo Marchal</Authors>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageProjectUrl>https://github.com/ZetaKebab/unison</PackageProjectUrl>
|
<PackageProjectUrl>https://git.n700.ovh/keb/unison</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/ZetaKebab/unison</RepositoryUrl>
|
<RepositoryUrl>https://git.n700.ovh/keb/unison</RepositoryUrl>
|
||||||
<Copyright>Théo Marchal</Copyright>
|
<Copyright>Théo Marchal</Copyright>
|
||||||
<PackageIconUrl />
|
<PackageIconUrl />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -22,8 +22,6 @@
|
|||||||
<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>
|
||||||
@ -40,12 +38,6 @@
|
|||||||
<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>
|
||||||
|