Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
b5cb6e4c3a | |||
d7a0079c86 | |||
e76cf3cd2a | |||
c785965041 | |||
b95edde7df | |||
5a8a5baa33 | |||
a3e09c2407 | |||
38a7856a0f | |||
e80f06d8bf | |||
18143d0311 | |||
049b861689 | |||
7d71d90538 | |||
8c9e1cd91c | |||
cef9f8d68c | |||
60f3442d38 | |||
b559ea01ab | |||
2f80af70ba | |||
aeb1b1d573 | |||
8451e684db | |||
9c8027428c | |||
8e04161a60 | |||
ccbb4525f3 | |||
529754934d | |||
0471ba88c1 | |||
b33ae3b9be | |||
67667c74da | |||
8033aebb23 | |||
0898d5cf11 | |||
cb741349fa | |||
cabfb165da | |||
b074a4e975 | |||
53d7668fd6 | |||
124b263499 | |||
3f74b1a4e7 | |||
ded5908ca2 | |||
519fe4968e | |||
f95b884d16 | |||
4136c13d5b | |||
0ab1afc2f8 | |||
3b59e51368 | |||
c7a93c2d82 | |||
43350aed36 |
84
App.config
Normal file
84
App.config
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="unison.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<userSettings>
|
||||
<unison.Properties.Settings>
|
||||
<setting name="mpd_host" serializeAs="String">
|
||||
<value>192.168.0.1</value>
|
||||
</setting>
|
||||
<setting name="mpd_port" serializeAs="String">
|
||||
<value>6600</value>
|
||||
</setting>
|
||||
<setting name="mpd_password" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="snapcast_startup" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="snapcast_window" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="snapcast_path" serializeAs="String">
|
||||
<value>snapcast_0.27</value>
|
||||
</setting>
|
||||
<setting name="snapcast_port" serializeAs="String">
|
||||
<value>1704</value>
|
||||
</setting>
|
||||
<setting name="volume_offset" serializeAs="String">
|
||||
<value>5</value>
|
||||
</setting>
|
||||
<setting name="nextTrack_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="nextTrack_vk" serializeAs="String">
|
||||
<value>176</value>
|
||||
</setting>
|
||||
<setting name="previousTrack_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="previousTrack_vk" serializeAs="String">
|
||||
<value>177</value>
|
||||
</setting>
|
||||
<setting name="playPause_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="playPause_vk" serializeAs="String">
|
||||
<value>179</value>
|
||||
</setting>
|
||||
<setting name="volumeUp_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="volumeUp_vk" serializeAs="String">
|
||||
<value>175</value>
|
||||
</setting>
|
||||
<setting name="volumeDown_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="volumeDown_vk" serializeAs="String">
|
||||
<value>174</value>
|
||||
</setting>
|
||||
<setting name="volumeMute_mod" serializeAs="String">
|
||||
<value>2</value>
|
||||
</setting>
|
||||
<setting name="volumeMute_vk" serializeAs="String">
|
||||
<value>173</value>
|
||||
</setting>
|
||||
<setting name="showWindow_mod" serializeAs="String">
|
||||
<value>3</value>
|
||||
</setting>
|
||||
<setting name="showWindow_vk" serializeAs="String">
|
||||
<value>13</value>
|
||||
</setting>
|
||||
<setting name="MainWindowTop" serializeAs="String">
|
||||
<value>100</value>
|
||||
</setting>
|
||||
<setting name="MainWindowLeft" serializeAs="String">
|
||||
<value>100</value>
|
||||
</setting>
|
||||
</unison.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
17
App.xaml.cs
17
App.xaml.cs
@ -1,6 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using unison.Handlers;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
@ -9,12 +9,17 @@ namespace unison
|
||||
private TaskbarIcon _systray;
|
||||
private HotkeyHandler _hotkeys;
|
||||
private SnapcastHandler _snapcast;
|
||||
private ShuffleHandler _shuffle;
|
||||
private MPDHandler _mpd;
|
||||
private UpdateHandler _updater;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
unison.Resources.Resources.Culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||
|
||||
//debug language
|
||||
//unison.Resources.Resources.Culture = CultureInfo.GetCultureInfo("fr-FR");
|
||||
//unison.Resources.Resources.Culture = System.Globalization.CultureInfo.GetCultureInfo("fr-FR");
|
||||
//unison.Resources.Resources.Culture = System.Globalization.CultureInfo.GetCultureInfo("es-ES");
|
||||
|
||||
base.OnStartup(e);
|
||||
|
||||
@ -27,6 +32,12 @@ namespace unison
|
||||
_snapcast = new SnapcastHandler();
|
||||
Current.Properties["snapcast"] = _snapcast;
|
||||
|
||||
_shuffle = new ShuffleHandler();
|
||||
Current.Properties["shuffle"] = _shuffle;
|
||||
|
||||
_updater = new UpdateHandler();
|
||||
Current.Properties["updater"] = _updater;
|
||||
|
||||
Current.MainWindow = new MainWindow();
|
||||
|
||||
_systray = (TaskbarIcon)FindResource("SystrayTaskbar");
|
||||
|
65
CHANGELOG.md
Normal file
65
CHANGELOG.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4
|
||||
|
||||
*Released: 09/12/2022*
|
||||
|
||||
* New feature: shuffle system
|
||||
* New feature: (un)installer script
|
||||
* New feature: update system
|
||||
* New feature: update MPD database button
|
||||
* Restore window position when relaunching the app
|
||||
* New settings organisation
|
||||
* Update Emoji.WPF from 0.3.3 to 0.3.4
|
||||
* Fix: cover images are displaying in all cases
|
||||
* Fix: querying a large list of radios could hang the app
|
||||
|
||||
## 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
|
||||
|
||||
*Released: 07/04/2022*
|
||||
|
||||
* New feature: support for custom shortcuts
|
||||
* New feature: MPD stats
|
||||
* Add GitHub repository link in settings
|
||||
* MpcNET NuGet package integration
|
||||
* Update Snapcast from v0.25 to v0.26
|
||||
* Fix: hostname supported for connection
|
||||
* Fix: crash when using invalid IP or Hostname
|
||||
* Fix: crash when no internet connection when using RadioBrowser
|
||||
|
||||
## v1.1
|
||||
|
||||
*Released: 04/10/2021*
|
||||
|
||||
* Radio browser
|
||||
* Mute shortcut
|
||||
* Enter key works in settings textboxes
|
||||
* Share current song by double-clicking
|
||||
|
||||
## v1.0
|
||||
|
||||
*Released: 03/09/2021*
|
||||
|
||||
* First release of unison
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -34,7 +35,7 @@ namespace unison
|
||||
public class MPDHandler
|
||||
{
|
||||
private bool _connected;
|
||||
public string _version;
|
||||
private string _version;
|
||||
private int _currentVolume;
|
||||
private int _previousVolume;
|
||||
private bool _currentRandom;
|
||||
@ -43,18 +44,19 @@ namespace unison
|
||||
private bool _currentConsume;
|
||||
private double _currentTime;
|
||||
private double _totalTime;
|
||||
private IEnumerable<IMpdFile> _Playlist;
|
||||
|
||||
private MpdStatus _currentStatus;
|
||||
private IMpdFile _currentSong;
|
||||
private BitmapFrame _cover;
|
||||
public Statistics _stats;
|
||||
private BitmapImage _cover;
|
||||
private readonly Statistics _stats;
|
||||
private readonly System.Timers.Timer _elapsedTimer;
|
||||
private DispatcherTimer _retryTimer;
|
||||
private readonly DispatcherTimer _retryTimer;
|
||||
|
||||
bool _isUpdatingStatus = false;
|
||||
bool _isUpdatingSong = false;
|
||||
private bool _isUpdatingStatus = false;
|
||||
private bool _isUpdatingSong = false;
|
||||
|
||||
bool _invalidIp = false;
|
||||
public IPAddress _ipAddress;
|
||||
|
||||
private event EventHandler ConnectionChanged;
|
||||
private event EventHandler StatusChanged;
|
||||
@ -64,19 +66,21 @@ namespace unison
|
||||
private MpcConnection _connection;
|
||||
private MpcConnection _commandConnection;
|
||||
private IPEndPoint _mpdEndpoint;
|
||||
private CancellationTokenSource cancelToken;
|
||||
|
||||
public CancellationTokenSource _cancelCommand;
|
||||
private CancellationTokenSource _cancelConnect;
|
||||
|
||||
private bool UpdateStarted = false;
|
||||
|
||||
public MPDHandler()
|
||||
{
|
||||
cancelToken = new CancellationTokenSource();
|
||||
|
||||
Initialize(null, null);
|
||||
Startup(null, null);
|
||||
|
||||
_stats = new Statistics();
|
||||
|
||||
_retryTimer = new DispatcherTimer();
|
||||
_retryTimer.Interval = TimeSpan.FromSeconds(5);
|
||||
_retryTimer.Tick += Initialize;
|
||||
_retryTimer.Tick += Startup;
|
||||
|
||||
_elapsedTimer = new System.Timers.Timer(500);
|
||||
_elapsedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ElapsedTimer);
|
||||
@ -97,7 +101,7 @@ namespace unison
|
||||
|
||||
void OnConnectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_connected && !_invalidIp)
|
||||
if (!_connected)
|
||||
_retryTimer.Start();
|
||||
else
|
||||
_retryTimer.Stop();
|
||||
@ -149,10 +153,10 @@ namespace unison
|
||||
|
||||
public async Task<T> SafelySendCommandAsync<T>(IMpcCommand<T> command)
|
||||
{
|
||||
if (_commandConnection == null)
|
||||
if (_commandConnection == null || !IsConnected())
|
||||
{
|
||||
Trace.WriteLine("[SafelySendCommandAsync] no command connection");
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
@ -160,7 +164,7 @@ namespace unison
|
||||
IMpdMessage<T> response = await _commandConnection.SendAsync(command);
|
||||
if (!response.IsResponseValid)
|
||||
{
|
||||
var mpdError = response.Response?.Result?.MpdError;
|
||||
string mpdError = response.Response?.Result?.MpdError;
|
||||
if (mpdError != null && mpdError != "")
|
||||
throw new Exception(mpdError);
|
||||
else
|
||||
@ -174,30 +178,65 @@ namespace unison
|
||||
Trace.WriteLine($"Sending {command.GetType().Name} failed: {e.Message}");
|
||||
}
|
||||
|
||||
return default(T);
|
||||
return default;
|
||||
}
|
||||
|
||||
private void Initialize(object sender, EventArgs e)
|
||||
public async void Startup(object sender, EventArgs e)
|
||||
{
|
||||
await Initialize();
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
Trace.WriteLine("Initializing");
|
||||
|
||||
Disconnected();
|
||||
|
||||
if (!_connected)
|
||||
Connect();
|
||||
await Connect();
|
||||
}
|
||||
|
||||
public async void Connect()
|
||||
public void Disconnected()
|
||||
{
|
||||
CancellationToken token = cancelToken.Token;
|
||||
_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
|
||||
{
|
||||
_connection = await ConnectInternal(token);
|
||||
_commandConnection = await ConnectInternal(token);
|
||||
_connection = await ConnectInternal(_cancelConnect.Token);
|
||||
_commandConnection = await ConnectInternal(_cancelCommand.Token);
|
||||
}
|
||||
catch(MpcNET.Exceptions.MpcConnectException)
|
||||
catch (MpcNET.Exceptions.MpcConnectException e)
|
||||
{
|
||||
_invalidIp = true;
|
||||
_connected = false;
|
||||
Trace.WriteLine($"Error in connect: {e.Message}");
|
||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
if (_connection != null && _commandConnection != null)
|
||||
{
|
||||
_invalidIp = false;
|
||||
if (_connection.IsConnected && _commandConnection.IsConnected)
|
||||
{
|
||||
_connected = true;
|
||||
@ -207,6 +246,7 @@ namespace unison
|
||||
}
|
||||
else
|
||||
{
|
||||
_connected = false;
|
||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
@ -214,21 +254,29 @@ namespace unison
|
||||
await UpdateStatusAsync();
|
||||
await UpdateSongAsync();
|
||||
|
||||
Loop(token);
|
||||
Loop(_cancelCommand.Token);
|
||||
}
|
||||
|
||||
private async Task<MpcConnection> ConnectInternal(CancellationToken token)
|
||||
{
|
||||
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out IPAddress ipAddress);
|
||||
if (token.IsCancellationRequested)
|
||||
return null;
|
||||
|
||||
if (ipAddress == null)
|
||||
IPAddress.TryParse(Properties.Settings.Default.mpd_host, out _ipAddress);
|
||||
|
||||
if (_ipAddress == null)
|
||||
{
|
||||
IPAddress[] addrList;
|
||||
try
|
||||
{
|
||||
addrList = Dns.GetHostAddresses(Properties.Settings.Default.mpd_host);
|
||||
IPAddress[] addrList = Dns.GetHostAddresses(Properties.Settings.Default.mpd_host);
|
||||
if (addrList.Length > 0)
|
||||
ipAddress = addrList[0];
|
||||
{
|
||||
foreach (IPAddress addr in addrList)
|
||||
{
|
||||
if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||
_ipAddress = addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@ -236,11 +284,11 @@ namespace unison
|
||||
}
|
||||
}
|
||||
|
||||
_mpdEndpoint = new IPEndPoint(ipAddress, Properties.Settings.Default.mpd_port);
|
||||
_mpdEndpoint = new IPEndPoint(_ipAddress, Properties.Settings.Default.mpd_port);
|
||||
MpcConnection connection = new MpcConnection(_mpdEndpoint);
|
||||
await connection.ConnectAsync(token);
|
||||
|
||||
/*if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.mpd_password))
|
||||
{
|
||||
IMpdMessage<string> result = await connection.SendAsync(new PasswordCommand(Properties.Settings.Default.mpd_password));
|
||||
if (!result.IsResponseValid)
|
||||
@ -248,21 +296,11 @@ namespace unison
|
||||
string mpdError = result.Response?.Result?.MpdError;
|
||||
Trace.WriteLine(mpdError);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void Disconnected()
|
||||
{
|
||||
_connected = false;
|
||||
|
||||
_connection = null;
|
||||
_commandConnection = null;
|
||||
|
||||
ConnectionChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Loop(CancellationToken token)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
@ -271,41 +309,76 @@ namespace unison
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested || _connection == null)
|
||||
if (token.IsCancellationRequested || _connection == null || !IsConnected())
|
||||
break;
|
||||
|
||||
var idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options"));
|
||||
IMpdMessage<string> idleChanges = await _connection.SendAsync(new IdleCommand("stored_playlist playlist player mixer output options update"));
|
||||
|
||||
if (idleChanges.IsResponseValid)
|
||||
await HandleIdleResponseAsync(idleChanges.Response.Content);
|
||||
else
|
||||
{
|
||||
Trace.WriteLine($"Error in Idle connection thread (1): {idleChanges.Response?.Content}");
|
||||
throw new Exception(idleChanges.Response?.Content);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||
Disconnected();
|
||||
if (token.IsCancellationRequested)
|
||||
Trace.WriteLine($"Idle connection cancelled.");
|
||||
else
|
||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||
await Initialize();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleIdleResponseAsync(string subsystems)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (subsystems.Contains("player") || subsystems.Contains("mixer") || subsystems.Contains("output") || subsystems.Contains("options"))
|
||||
if (subsystems.Contains("player") || subsystems.Contains("mixer") || subsystems.Contains("output") || subsystems.Contains("options") || subsystems.Contains("update"))
|
||||
{
|
||||
await UpdateStatusAsync();
|
||||
|
||||
if (subsystems.Contains("player"))
|
||||
await UpdateSongAsync();
|
||||
|
||||
if (subsystems.Contains("update"))
|
||||
UpdateDatabaseSync();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||
await Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDatabaseSync()
|
||||
{
|
||||
if (!UpdateStarted)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||
MainWin.GetSettings().MPDDatabaseUpdate_Start();
|
||||
});
|
||||
|
||||
UpdateStarted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||
MainWin.GetSettings().MPDDatabaseUpdate_Stop();
|
||||
MainWin.UpdateStats();
|
||||
});
|
||||
UpdateStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,6 +403,7 @@ namespace unison
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||
await Initialize();
|
||||
}
|
||||
|
||||
_isUpdatingStatus = false;
|
||||
@ -337,6 +411,8 @@ namespace unison
|
||||
|
||||
private async Task UpdateSongAsync()
|
||||
{
|
||||
Trace.WriteLine("Updating song");
|
||||
|
||||
if (_connection == null || _isUpdatingSong)
|
||||
return;
|
||||
|
||||
@ -356,16 +432,23 @@ namespace unison
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine($"Error in Idle connection thread: {e.Message}");
|
||||
await Initialize();
|
||||
}
|
||||
|
||||
_isUpdatingSong = false;
|
||||
|
||||
Trace.WriteLine("Updated song");
|
||||
}
|
||||
|
||||
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>();
|
||||
try
|
||||
{
|
||||
bool ReadPictureFailed = true;
|
||||
long totalBinarySize = 9999;
|
||||
long currentSize = 0;
|
||||
|
||||
@ -374,21 +457,47 @@ namespace unison
|
||||
if (_connection == null)
|
||||
return;
|
||||
|
||||
var albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
||||
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new AlbumArtCommand(path, currentSize));
|
||||
if (!albumReq.IsResponseValid)
|
||||
break;
|
||||
|
||||
var response = albumReq.Response.Content;
|
||||
if (response.Binary == 0)
|
||||
MpdBinaryData response = albumReq.Response.Content;
|
||||
if (response == null || response.Binary == 0)
|
||||
break;
|
||||
|
||||
ReadPictureFailed = false;
|
||||
totalBinarySize = response.Size;
|
||||
currentSize += response.Binary;
|
||||
data.AddRange(response.Data);
|
||||
}
|
||||
while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||
|
||||
do
|
||||
{
|
||||
if (!ReadPictureFailed)
|
||||
break;
|
||||
if (_connection == null)
|
||||
return;
|
||||
|
||||
IMpdMessage<MpdBinaryData> albumReq = await _connection.SendAsync(new ReadPictureCommand(path, currentSize));
|
||||
if (!albumReq.IsResponseValid)
|
||||
break;
|
||||
|
||||
MpdBinaryData response = albumReq.Response.Content;
|
||||
if (response == null || response.Binary == 0)
|
||||
break;
|
||||
|
||||
totalBinarySize = response.Size;
|
||||
currentSize += response.Binary;
|
||||
data.AddRange(response.Data);
|
||||
} while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||
}
|
||||
while (currentSize < totalBinarySize && !token.IsCancellationRequested);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
Trace.WriteLine("Exception caught while getting albumart: " + e);
|
||||
return;
|
||||
}
|
||||
@ -398,14 +507,12 @@ namespace unison
|
||||
else
|
||||
{
|
||||
using MemoryStream stream = new MemoryStream(data.ToArray());
|
||||
try
|
||||
{
|
||||
_cover = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
}
|
||||
catch (System.NotSupportedException)
|
||||
{
|
||||
_cover = null;
|
||||
}
|
||||
_cover = new BitmapImage();
|
||||
_cover.BeginInit();
|
||||
_cover.CacheOption = BitmapCacheOption.OnLoad;
|
||||
_cover.StreamSource = stream;
|
||||
_cover.EndInit();
|
||||
_cover.Freeze();
|
||||
}
|
||||
UpdateCover();
|
||||
}
|
||||
@ -437,7 +544,7 @@ namespace unison
|
||||
SongChanged?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
string uri = Regex.Escape(_currentSong.Path);
|
||||
GetAlbumCover(uri);
|
||||
GetAlbumCover(uri, _cancelCommand.Token);
|
||||
}
|
||||
|
||||
public void UpdateCover()
|
||||
@ -447,17 +554,31 @@ namespace unison
|
||||
|
||||
public IMpdFile GetCurrentSong() => _currentSong;
|
||||
public MpdStatus GetStatus() => _currentStatus;
|
||||
public BitmapFrame GetCover() => _cover;
|
||||
public BitmapImage GetCover() => _cover;
|
||||
public string GetVersion() => _version;
|
||||
public Statistics GetStats() => _stats;
|
||||
public double GetCurrentTime() => _currentTime;
|
||||
public IEnumerable<IMpdFile> GetPlaylist() => _Playlist;
|
||||
|
||||
public bool IsConnected() => _connected;
|
||||
public bool IsPlaying() => _currentStatus?.State == MpdState.Play;
|
||||
|
||||
public void Prev() => SendCommand(new PreviousCommand());
|
||||
public void Next() => SendCommand(new NextCommand());
|
||||
public bool CanPrevNext = true;
|
||||
|
||||
public void Prev()
|
||||
{
|
||||
if (CanPrevNext && !_isUpdatingSong)
|
||||
SendCommand(new PreviousCommand());
|
||||
}
|
||||
|
||||
public void Next()
|
||||
{
|
||||
if (CanPrevNext && !_isUpdatingSong)
|
||||
SendCommand(new NextCommand());
|
||||
}
|
||||
|
||||
public void PlayPause() => SendCommand(new PauseResumeCommand());
|
||||
public void Play(int pos) => SendCommand(new PlayCommand(pos));
|
||||
|
||||
public void Random() => SendCommand(new RandomCommand(!_currentRandom));
|
||||
public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
|
||||
@ -503,7 +624,6 @@ namespace unison
|
||||
|
||||
public void AddSong(string Uri)
|
||||
{
|
||||
Debug.WriteLine("AddCommand path: " + Uri);
|
||||
SendCommand(new AddCommand(Uri));
|
||||
}
|
||||
|
||||
@ -513,24 +633,61 @@ namespace unison
|
||||
SendCommand(commandList);
|
||||
}
|
||||
|
||||
public async Task QueryPlaylist() => _Playlist = await SafelySendCommandAsync(new PlaylistCommand());
|
||||
|
||||
public int GetPlaylistCount()
|
||||
{
|
||||
if (_Playlist == null)
|
||||
return 0;
|
||||
return _Playlist.ToArray().Length;
|
||||
}
|
||||
|
||||
public void UpdateDB() => SendCommand(new UpdateCommand());
|
||||
|
||||
private static string FormatTime(TimeSpan time)
|
||||
{
|
||||
string FormattedTime = "";
|
||||
|
||||
if (time.Days == 1)
|
||||
FormattedTime += $"{time.Days} {Resources.Resources.Day}, ";
|
||||
else if (time.Days > 1)
|
||||
FormattedTime += $"{time.Days} {Resources.Resources.Days}, ";
|
||||
|
||||
if (time.Hours == 1)
|
||||
FormattedTime += $"{time.Hours} {Resources.Resources.Hour}, ";
|
||||
else
|
||||
FormattedTime += $"{time.Hours} {Resources.Resources.Hours}, ";
|
||||
|
||||
if (time.Minutes == 1)
|
||||
FormattedTime += $"{time.Minutes} {Resources.Resources.Minute}, ";
|
||||
else
|
||||
FormattedTime += $"{time.Minutes} {Resources.Resources.Minutes}, ";
|
||||
|
||||
if (time.Seconds == 1)
|
||||
FormattedTime += $"{time.Seconds} {Resources.Resources.Second}";
|
||||
else
|
||||
FormattedTime += $"{time.Seconds} {Resources.Resources.Seconds}";
|
||||
|
||||
return FormattedTime;
|
||||
}
|
||||
|
||||
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"]);
|
||||
_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"]);
|
||||
|
||||
_stats.Uptime = FormatTime(TimeSpan.FromSeconds(int.Parse(Response["uptime"])));
|
||||
_stats.TotalPlaytime = FormatTime(TimeSpan.FromSeconds(int.Parse(Response["db_playtime"])));
|
||||
_stats.TotalTimePlayed = FormatTime(TimeSpan.FromSeconds(int.Parse(Response["playtime"])));
|
||||
|
||||
TimeSpan time;
|
||||
time = TimeSpan.FromSeconds(int.Parse(response["uptime"]));
|
||||
_stats.Uptime = time.ToString(@"dd\:hh\:mm\:ss");
|
||||
time = TimeSpan.FromSeconds(int.Parse(response["db_playtime"]));
|
||||
_stats.TotalPlaytime = time.ToString(@"dd\:hh\:mm\:ss");
|
||||
time = TimeSpan.FromSeconds(int.Parse(response["playtime"]));
|
||||
_stats.TotalTimePlayed = time.ToString(@"dd\:hh\:mm\:ss");
|
||||
|
||||
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(response["db_update"])).ToLocalTime();
|
||||
_stats.DatabaseUpdate = date.ToString("dd/MM/yyyy @ HH:mm");
|
||||
DateTime date = new DateTime(1970, 1, 1).AddSeconds(int.Parse(Response["db_update"])).ToLocalTime();
|
||||
string dayOfWeek = Resources.Resources.Culture.DateTimeFormat.GetDayName(date.DayOfWeek);
|
||||
_stats.DatabaseUpdate = dayOfWeek + " " + date.ToString("dd/MM/yyyy @ HH:mm");
|
||||
}
|
||||
}
|
||||
}
|
81
Handlers/RadioHandler.cs
Normal file
81
Handlers/RadioHandler.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RadioBrowser;
|
||||
using RadioBrowser.Models;
|
||||
|
||||
namespace unison.Handlers
|
||||
{
|
||||
public class CountryListItem
|
||||
{
|
||||
public uint Count { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Name == "")
|
||||
return "None";
|
||||
return $"{Name} ({Count})";
|
||||
}
|
||||
}
|
||||
|
||||
public class StationListItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Codec { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
public Uri Url { get; set; }
|
||||
|
||||
private string _country;
|
||||
public string Country
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_country.Length == 0)
|
||||
return "🏴☠️";
|
||||
return string.Concat(_country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5))); // return emoji
|
||||
}
|
||||
set
|
||||
{
|
||||
_country = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class RadioHandler
|
||||
{
|
||||
private readonly RadioBrowserClient _radioBrowser;
|
||||
private readonly bool _connected = true;
|
||||
|
||||
public bool IsConnected() => _connected;
|
||||
|
||||
|
||||
public RadioHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
_radioBrowser = new RadioBrowserClient();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
|
||||
public async Task<List<NameAndCount>> GetCountries()
|
||||
{
|
||||
return await _radioBrowser.Lists.GetCountriesAsync();
|
||||
}
|
||||
|
||||
public async Task<List<StationInfo>> AdvancedSearch(AdvancedSearchOptions options)
|
||||
{
|
||||
return await _radioBrowser.Search.AdvancedAsync(options);
|
||||
}
|
||||
}
|
||||
}
|
110
Handlers/ShuffleHandler.cs
Normal file
110
Handlers/ShuffleHandler.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using MpcNET;
|
||||
using MpcNET.Commands.Database;
|
||||
using MpcNET.Commands.Playback;
|
||||
using MpcNET.Commands.Queue;
|
||||
using MpcNET.Commands.Reflection;
|
||||
using MpcNET.Tags;
|
||||
using MpcNET.Types;
|
||||
using MpcNET.Types.Filters;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
class ShuffleHandler
|
||||
{
|
||||
private readonly MPDHandler _mpd;
|
||||
|
||||
public int AddedSongs = 0;
|
||||
public List<string> SongList { get; }
|
||||
|
||||
public ShuffleHandler()
|
||||
{
|
||||
SongList = new();
|
||||
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
}
|
||||
|
||||
public async Task GetSongsFromFilter(List<IFilter> filter, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
SongList.Clear();
|
||||
|
||||
int song = _mpd.GetStats().Songs;
|
||||
IEnumerable<IMpdFile> response = await _mpd.SafelySendCommandAsync(new SearchCommand(filter, 0, song + 1));
|
||||
foreach (IMpdFile file in response)
|
||||
SongList.Add(file.Path);
|
||||
}
|
||||
|
||||
public async Task AddToQueueRandom(int SongNumber, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
int AddedSongs = 0;
|
||||
|
||||
var commandList = new CommandList();
|
||||
int songTotal = _mpd.GetStats().Songs;
|
||||
|
||||
for (int i = 0; i < SongNumber; i++)
|
||||
{
|
||||
int song = new Random().Next(0, songTotal - 1);
|
||||
commandList.Add(new SearchAddCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1));
|
||||
AddedSongs++;
|
||||
|
||||
// play if stopped or unknown state (no queue managing at the moment, so mandatory)
|
||||
if (i == 0 && (_mpd.GetStatus().State != MpdState.Play && _mpd.GetStatus().State != MpdState.Pause))
|
||||
commandList.Add(new PlayCommand(0));
|
||||
}
|
||||
|
||||
await _mpd.SafelySendCommandAsync(commandList);
|
||||
}
|
||||
|
||||
public async Task AddToQueueFilter(int SongNumber, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
int AddedSongs = 0;
|
||||
|
||||
// more (or equal) requested songs than available => add everything
|
||||
if (SongNumber >= SongList.Count)
|
||||
{
|
||||
var commandList = new CommandList();
|
||||
foreach (string path in SongList)
|
||||
{
|
||||
commandList.Add(new AddCommand(path));
|
||||
AddedSongs++;
|
||||
}
|
||||
|
||||
commandList.Add(new PlayCommand(0));
|
||||
await _mpd.SafelySendCommandAsync(commandList);
|
||||
}
|
||||
// more available songs than requested =>
|
||||
// we add unique indexes until we reach the requested amount
|
||||
else
|
||||
{
|
||||
HashSet<int> SongIndex = new();
|
||||
while (SongIndex.Count < SongNumber)
|
||||
{
|
||||
int MaxIndex = new Random().Next(0, SongList.Count - 1);
|
||||
SongIndex.Add(MaxIndex);
|
||||
}
|
||||
|
||||
var commandList = new CommandList();
|
||||
foreach (int index in SongIndex)
|
||||
{
|
||||
commandList.Add(new AddCommand(SongList[index]));
|
||||
AddedSongs++;
|
||||
}
|
||||
|
||||
commandList.Add(new PlayCommand(0));
|
||||
await _mpd.SafelySendCommandAsync(commandList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,19 +8,30 @@ namespace unison
|
||||
public class SnapcastHandler
|
||||
{
|
||||
private readonly Process _snapcast = new();
|
||||
|
||||
public bool HasStarted { get; private set; }
|
||||
|
||||
public void OnConnectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (Properties.Settings.Default.snapcast_startup)
|
||||
{
|
||||
var mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
if (mpd.IsConnected())
|
||||
LaunchOrExit();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateInterface()
|
||||
private void HandleExit(object sender, EventArgs e)
|
||||
{
|
||||
_snapcast.Kill();
|
||||
HasStarted = false;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateInterface();
|
||||
});
|
||||
}
|
||||
|
||||
public static void UpdateInterface()
|
||||
{
|
||||
TaskbarIcon Systray = (TaskbarIcon)Application.Current.Properties["systray"];
|
||||
SystrayViewModel DataContext = Systray.DataContext as SystrayViewModel;
|
||||
@ -37,19 +48,24 @@ namespace unison
|
||||
{
|
||||
if (!HasStarted && !ForceExit)
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
|
||||
_snapcast.StartInfo.FileName = Properties.Settings.Default.snapcast_path + @"\snapclient.exe";
|
||||
_snapcast.StartInfo.Arguments = $"--host {Properties.Settings.Default.mpd_host}";
|
||||
_snapcast.StartInfo.Arguments = $"--host {mpd._ipAddress}";
|
||||
_snapcast.StartInfo.CreateNoWindow = !Properties.Settings.Default.snapcast_window;
|
||||
_snapcast.EnableRaisingEvents = true;
|
||||
_snapcast.Exited += new EventHandler(HandleExit);
|
||||
|
||||
try
|
||||
{
|
||||
_snapcast.Start();
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
MessageBox.Show($"[{unison.Resources.Resources.Snapcast_Popup1}]\n" +
|
||||
$"{unison.Resources.Resources.Snapcast_Popup2} {err.Message}\n\n" +
|
||||
$"{unison.Resources.Resources.Snapcast_Popup3} {Properties.Settings.Default.snapcast_path}\n" +
|
||||
$"{unison.Resources.Resources.Snapcast_Popup4}",
|
||||
MessageBox.Show($"[{Resources.Resources.Snapcast_Popup1}]\n" +
|
||||
$"{Resources.Resources.Snapcast_Popup2} {err.Message}\n\n" +
|
||||
$"{Resources.Resources.Snapcast_Popup3} {Properties.Settings.Default.snapcast_path}\n" +
|
||||
$"{Resources.Resources.Snapcast_Popup4}",
|
||||
"unison", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
57
Handlers/UpdateHandler.cs
Normal file
57
Handlers/UpdateHandler.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.Windows;
|
||||
using AutoUpdaterDotNET;
|
||||
|
||||
namespace unison.Handlers
|
||||
{
|
||||
internal class UpdateHandler
|
||||
{
|
||||
readonly string xmlFile = "https://raw.githubusercontent.com/ZetaKebab/unison/main/Installer/unison.xml";
|
||||
|
||||
private bool _UpdateAvailable = false;
|
||||
private bool _RequestedCheck = false;
|
||||
|
||||
public bool UpdateAvailable() => _UpdateAvailable;
|
||||
|
||||
public UpdateHandler()
|
||||
{
|
||||
AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
|
||||
Start();
|
||||
}
|
||||
|
||||
public void Start(bool RequestCheck = false)
|
||||
{
|
||||
_RequestedCheck = RequestCheck;
|
||||
AutoUpdater.Start(xmlFile);
|
||||
}
|
||||
|
||||
private static string CutVersionNumber(string number)
|
||||
{
|
||||
return number.Substring(0, number.LastIndexOf("."));
|
||||
}
|
||||
|
||||
private void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
|
||||
{
|
||||
if (args.Error == null)
|
||||
{
|
||||
if (args.IsUpdateAvailable)
|
||||
{
|
||||
_UpdateAvailable = true;
|
||||
string number = CutVersionNumber(args.CurrentVersion);
|
||||
|
||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||
MainWin.UpdateUpdateStatus(number);
|
||||
|
||||
MessageBoxResult Result = MessageBox.Show($"{Resources.Resources.Update_Message1} {number}.\n{Resources.Resources.Update_Message2}",
|
||||
"unison", MessageBoxButton.YesNo, MessageBoxImage.Information);
|
||||
if (Result == MessageBoxResult.Yes)
|
||||
AutoUpdater.DownloadUpdate(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_RequestedCheck)
|
||||
MessageBox.Show($"{Resources.Resources.Update_NoUpdate}", "unison", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
Installer/unison.iss
Normal file
43
Installer/unison.iss
Normal file
@ -0,0 +1,43 @@
|
||||
#define Name "unison"
|
||||
#define Version "1.4"
|
||||
#define Snapcast "snapcast_0.27"
|
||||
#define Publisher "Théo Marchal"
|
||||
#define URL "https://github.com/ZetaKebab/unison"
|
||||
#define ExeName "unison.exe"
|
||||
|
||||
[Setup]
|
||||
AppName={#Name}
|
||||
AppVersion={#Version}
|
||||
AppVerName={#Name} v{#Version}
|
||||
AppPublisher={#Publisher}
|
||||
AppPublisherURL={#URL}
|
||||
AppSupportURL={#URL}
|
||||
AppUpdatesURL={#URL}
|
||||
DefaultDirName={autopf}\{#Name}
|
||||
DisableProgramGroupPage=yes
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
OutputBaseFilename="{#Name}-v{#Version}-setup"
|
||||
OutputDir=..\publish\installer
|
||||
SetupIconFile=..\Resources\icon-full.ico
|
||||
UninstallDisplayIcon = "{app}\{#Name}.exe"
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\publish\{#ExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\publish\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\publish\unison.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\publish\{#Snapcast}\*"; DestDir: "{app}\{#Snapcast}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#Name}"; Filename: "{app}\{#ExeName}"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#Name}.exe"; Parameters: "-frominstaller"; Flags: nowait postinstall skipifsilent
|
||||
|
7
Installer/unison.xml
Normal file
7
Installer/unison.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.3.1.0</version>
|
||||
<url>https://github.com/ZetaKebab/unison/releases/download/v1.3.1/unison-v1.3.1.zip</url>
|
||||
<changelog>https://raw.githubusercontent.com/ZetaKebab/unison/main/CHANGELOG.md</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
</item>
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
MIT License Copyright (c) 2022 Théo Marchal
|
||||
MIT License Copyright (c) 2023 Théo Marchal
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
@ -4,14 +4,14 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Configuration>Release-Stable</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<SelfContained>false</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishSingleFile>True</PublishSingleFile>
|
||||
<PublishReadyToRun>False</PublishReadyToRun>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
28
Properties/Settings.Designer.cs
generated
28
Properties/Settings.Designer.cs
generated
@ -12,7 +12,7 @@ namespace unison.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
@ -85,7 +85,7 @@ namespace unison.Properties {
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("snapclient_0.26.0-1_win64")]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("snapcast_0.27")]
|
||||
public string snapcast_path {
|
||||
get {
|
||||
return ((string)(this["snapcast_path"]));
|
||||
@ -286,5 +286,29 @@ namespace unison.Properties {
|
||||
this["showWindow_vk"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("100")]
|
||||
public double MainWindowTop {
|
||||
get {
|
||||
return ((double)(this["MainWindowTop"]));
|
||||
}
|
||||
set {
|
||||
this["MainWindowTop"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("100")]
|
||||
public double MainWindowLeft {
|
||||
get {
|
||||
return ((double)(this["MainWindowLeft"]));
|
||||
}
|
||||
set {
|
||||
this["MainWindowLeft"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="snapcast_path" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">snapclient_0.26.0-1_win64</Value>
|
||||
<Value Profile="(Default)">snapcast_0.27</Value>
|
||||
</Setting>
|
||||
<Setting Name="snapcast_port" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">1704</Value>
|
||||
@ -68,5 +68,11 @@
|
||||
<Setting Name="showWindow_vk" Type="System.UInt32" Scope="User">
|
||||
<Value Profile="(Default)">13</Value>
|
||||
</Setting>
|
||||
<Setting Name="MainWindowTop" Type="System.Double" Scope="User">
|
||||
<Value Profile="(Default)">100</Value>
|
||||
</Setting>
|
||||
<Setting Name="MainWindowLeft" Type="System.Double" Scope="User">
|
||||
<Value Profile="(Default)">100</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
12
README.md
12
README.md
@ -6,7 +6,7 @@
|
||||
|
||||
* lightweight window that can be toggled with shortcuts
|
||||
* music control through rebindable shortcuts
|
||||
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
|
||||
* [Snapcast](https://github.com/badaix/snapcast) integration
|
||||
* Radio stations
|
||||
|
||||
## Features
|
||||
@ -33,17 +33,11 @@ Through [Radio-Browser](https://www.radio-browser.info), a community database, y
|
||||
|
||||
![Radio stations](Screenshots/screen4.png)
|
||||
|
||||
## Caveats
|
||||
|
||||
### Missing features
|
||||
|
||||
* MPD passwords: I don't really see the point, but if asked, I will integrate them.
|
||||
|
||||
### Planned features
|
||||
## Planned features
|
||||
|
||||
* A complete shuffle system based on set criteria, aka a smart playlist.
|
||||
* Playlist, queue and library management. I use other software to do it, but I will implement them at some point.
|
||||
|
||||
## Translations
|
||||
|
||||
unison is translated in English and French. You can contribute if you want!
|
||||
unison is translated in English, French and Spanish. You can contribute if you want!
|
467
Resources/Resources.Designer.cs
generated
467
Resources/Resources.Designer.cs
generated
@ -19,7 +19,7 @@ namespace unison.Resources {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@ -60,6 +60,33 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Database.
|
||||
/// </summary>
|
||||
public static string Database {
|
||||
get {
|
||||
return ResourceManager.GetString("Database", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to day.
|
||||
/// </summary>
|
||||
public static string Day {
|
||||
get {
|
||||
return ResourceManager.GetString("Day", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to days.
|
||||
/// </summary>
|
||||
public static string Days {
|
||||
get {
|
||||
return ResourceManager.GetString("Days", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exit.
|
||||
/// </summary>
|
||||
@ -69,6 +96,123 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Album.
|
||||
/// </summary>
|
||||
public static string FilterType_Album {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Album", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Artist.
|
||||
/// </summary>
|
||||
public static string FilterType_Artist {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Artist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Directory.
|
||||
/// </summary>
|
||||
public static string FilterType_Directory {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Directory", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Genre.
|
||||
/// </summary>
|
||||
public static string FilterType_Genre {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Genre", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Song.
|
||||
/// </summary>
|
||||
public static string FilterType_Song {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Song", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Year.
|
||||
/// </summary>
|
||||
public static string FilterType_Year {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterType_Year", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to hour.
|
||||
/// </summary>
|
||||
public static string Hour {
|
||||
get {
|
||||
return ResourceManager.GetString("Hour", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to hours.
|
||||
/// </summary>
|
||||
public static string Hours {
|
||||
get {
|
||||
return ResourceManager.GetString("Hours", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to minute.
|
||||
/// </summary>
|
||||
public static string Minute {
|
||||
get {
|
||||
return ResourceManager.GetString("Minute", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to minutes.
|
||||
/// </summary>
|
||||
public static string Minutes {
|
||||
get {
|
||||
return ResourceManager.GetString("Minutes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to contains.
|
||||
/// </summary>
|
||||
public static string Operator_Contains {
|
||||
get {
|
||||
return ResourceManager.GetString("Operator_Contains", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to is.
|
||||
/// </summary>
|
||||
public static string Operator_Is {
|
||||
get {
|
||||
return ResourceManager.GetString("Operator_Is", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to is not.
|
||||
/// </summary>
|
||||
public static string Operator_IsNot {
|
||||
get {
|
||||
return ResourceManager.GetString("Operator_IsNot", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Country.
|
||||
/// </summary>
|
||||
@ -150,6 +294,24 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to second.
|
||||
/// </summary>
|
||||
public static string Second {
|
||||
get {
|
||||
return ResourceManager.GetString("Second", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to seconds.
|
||||
/// </summary>
|
||||
public static string Seconds {
|
||||
get {
|
||||
return ResourceManager.GetString("Seconds", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
@ -196,7 +358,25 @@ namespace unison.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connected to MPD.
|
||||
/// Looks up a localized string similar to Please note that since MPD passwords are not secure (they are sent in plain text to the server), they are saved as is in the setting file..
|
||||
/// </summary>
|
||||
public static string Settings_ConnectionPasswordInfo {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_ConnectionPasswordInfo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Status:.
|
||||
/// </summary>
|
||||
public static string Settings_ConnectionStatus {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_ConnectionStatus", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to connected to MPD.
|
||||
/// </summary>
|
||||
public static string Settings_ConnectionStatusConnected {
|
||||
get {
|
||||
@ -205,7 +385,7 @@ namespace unison.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connecting....
|
||||
/// Looks up a localized string similar to connecting....
|
||||
/// </summary>
|
||||
public static string Settings_ConnectionStatusConnecting {
|
||||
get {
|
||||
@ -214,7 +394,7 @@ namespace unison.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Not connected..
|
||||
/// Looks up a localized string similar to not connected..
|
||||
/// </summary>
|
||||
public static string Settings_ConnectionStatusOffline {
|
||||
get {
|
||||
@ -258,6 +438,15 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Password.
|
||||
/// </summary>
|
||||
public static string Settings_Password {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Password", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Play / pause.
|
||||
/// </summary>
|
||||
@ -321,6 +510,51 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The shuffle window allows to add random songs to your queue. Both options take into account the filter..
|
||||
/// </summary>
|
||||
public static string Settings_Shuffle1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Shuffle1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to If the filter is empty, the entire music library is taken into account..
|
||||
/// </summary>
|
||||
public static string Settings_Shuffle2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Shuffle2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The filter is queried each time you use the Add to queue or Continuous shuffle options..
|
||||
/// </summary>
|
||||
public static string Settings_Shuffle3 {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Shuffle3", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add a fixed number of songs to the queue. For technical reasons, it is limited to 100 random songs without a filter, and to 1000 songs with a filter..
|
||||
/// </summary>
|
||||
public static string Settings_Shuffle4 {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Shuffle4", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to..
|
||||
/// </summary>
|
||||
public static string Settings_Shuffle5 {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_Shuffle5", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You can change to your own locally installed version of the Snapcast client with an.
|
||||
/// </summary>
|
||||
@ -411,6 +645,15 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Update database.
|
||||
/// </summary>
|
||||
public static string Settings_UpdateDatabase {
|
||||
get {
|
||||
return ResourceManager.GetString("Settings_UpdateDatabase", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Version:.
|
||||
/// </summary>
|
||||
@ -465,6 +708,132 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Shuffle.
|
||||
/// </summary>
|
||||
public static string Shuffle {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Adding.
|
||||
/// </summary>
|
||||
public static string Shuffle_ButtonMessage1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_ButtonMessage1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to songs....
|
||||
/// </summary>
|
||||
public static string Shuffle_ButtonMessage2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_ButtonMessage2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to done!.
|
||||
/// </summary>
|
||||
public static string Shuffle_ButtonMessage3 {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_ButtonMessage3", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Continuous shuffle.
|
||||
/// </summary>
|
||||
public static string Shuffle_Continuous {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_Continuous", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enable continuous shuffle.
|
||||
/// </summary>
|
||||
public static string Shuffle_ContinuousEnable {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_ContinuousEnable", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Filter.
|
||||
/// </summary>
|
||||
public static string Shuffle_Filter {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_Filter", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Query filter.
|
||||
/// </summary>
|
||||
public static string Shuffle_FilterQuery {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_FilterQuery", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reset.
|
||||
/// </summary>
|
||||
public static string Shuffle_FilterReset {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_FilterReset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Number of songs in filter:.
|
||||
/// </summary>
|
||||
public static string Shuffle_FilterSongNumber {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_FilterSongNumber", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Querying filter....
|
||||
/// </summary>
|
||||
public static string Shuffle_Querying1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_Querying1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to done!.
|
||||
/// </summary>
|
||||
public static string Shuffle_Querying2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_Querying2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add to queue.
|
||||
/// </summary>
|
||||
public static string Shuffle_Queue {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_Queue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Songs to add.
|
||||
/// </summary>
|
||||
public static string Shuffle_QueueSongs {
|
||||
get {
|
||||
return ResourceManager.GetString("Shuffle_QueueSongs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Snapcast error.
|
||||
/// </summary>
|
||||
@ -510,15 +879,6 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stats.
|
||||
/// </summary>
|
||||
public static string Stats {
|
||||
get {
|
||||
return ResourceManager.GetString("Stats", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Albums:.
|
||||
/// </summary>
|
||||
@ -573,6 +933,24 @@ namespace unison.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Updating database....
|
||||
/// </summary>
|
||||
public static string Stats_UpdateDBMessage1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Stats_UpdateDBMessage1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to done!.
|
||||
/// </summary>
|
||||
public static string Stats_UpdateDBMessage2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Stats_UpdateDBMessage2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to MPD uptime:.
|
||||
/// </summary>
|
||||
@ -590,5 +968,68 @@ namespace unison.Resources {
|
||||
return ResourceManager.GetString("StopSnapcast", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Check for updates.
|
||||
/// </summary>
|
||||
public static string Update_ButtonCheck {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_ButtonCheck", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Start update.
|
||||
/// </summary>
|
||||
public static string Update_ButtonStart {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_ButtonStart", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Update available! New version is.
|
||||
/// </summary>
|
||||
public static string Update_Message1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_Message1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install now?.
|
||||
/// </summary>
|
||||
public static string Update_Message2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_Message2", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No update available..
|
||||
/// </summary>
|
||||
public static string Update_NoUpdate {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_NoUpdate", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New version.
|
||||
/// </summary>
|
||||
public static string Update_String1 {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_String1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to available!.
|
||||
/// </summary>
|
||||
public static string Update_String2 {
|
||||
get {
|
||||
return ResourceManager.GetString("Update_String2", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
444
Resources/Resources.es-ES.resx
Normal file
444
Resources/Resources.es-ES.resx
Normal file
@ -0,0 +1,444 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Database" xml:space="preserve">
|
||||
<value>Base de datos</value>
|
||||
</data>
|
||||
<data name="Day" xml:space="preserve">
|
||||
<value>día</value>
|
||||
</data>
|
||||
<data name="Days" xml:space="preserve">
|
||||
<value>días</value>
|
||||
</data>
|
||||
<data name="Exit" xml:space="preserve">
|
||||
<value>Salir</value>
|
||||
</data>
|
||||
<data name="FilterType_Album" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="FilterType_Artist" xml:space="preserve">
|
||||
<value>Artista</value>
|
||||
</data>
|
||||
<data name="FilterType_Directory" xml:space="preserve">
|
||||
<value>Directorio</value>
|
||||
</data>
|
||||
<data name="FilterType_Genre" xml:space="preserve">
|
||||
<value>Género</value>
|
||||
</data>
|
||||
<data name="FilterType_Song" xml:space="preserve">
|
||||
<value>Canción</value>
|
||||
</data>
|
||||
<data name="FilterType_Year" xml:space="preserve">
|
||||
<value>Año</value>
|
||||
</data>
|
||||
<data name="Hour" xml:space="preserve">
|
||||
<value>hora</value>
|
||||
</data>
|
||||
<data name="Hours" xml:space="preserve">
|
||||
<value>horas</value>
|
||||
</data>
|
||||
<data name="Minute" xml:space="preserve">
|
||||
<value>minuto</value>
|
||||
</data>
|
||||
<data name="Minutes" xml:space="preserve">
|
||||
<value>minutos</value>
|
||||
</data>
|
||||
<data name="Operator_Contains" xml:space="preserve">
|
||||
<value>contiene</value>
|
||||
</data>
|
||||
<data name="Operator_Is" xml:space="preserve">
|
||||
<value>es</value>
|
||||
</data>
|
||||
<data name="Operator_IsNot" xml:space="preserve">
|
||||
<value>no es</value>
|
||||
</data>
|
||||
<data name="Radios" xml:space="preserve">
|
||||
<value>Radios</value>
|
||||
</data>
|
||||
<data name="Radio_Country" xml:space="preserve">
|
||||
<value>Países</value>
|
||||
</data>
|
||||
<data name="Radio_Loading" xml:space="preserve">
|
||||
<value>Cargando estaciones...</value>
|
||||
</data>
|
||||
<data name="Radio_Name" xml:space="preserve">
|
||||
<value>Nombre</value>
|
||||
</data>
|
||||
<data name="Radio_NotFound" xml:space="preserve">
|
||||
<value>¡No estaciones encontradas!</value>
|
||||
</data>
|
||||
<data name="Radio_Reset" xml:space="preserve">
|
||||
<value>Reinicializar</value>
|
||||
</data>
|
||||
<data name="Radio_Search" xml:space="preserve">
|
||||
<value>Búsqueda</value>
|
||||
</data>
|
||||
<data name="Radio_SearchStation" xml:space="preserve">
|
||||
<value>Buscar estación</value>
|
||||
</data>
|
||||
<data name="Radio_Tags" xml:space="preserve">
|
||||
<value>Tags</value>
|
||||
</data>
|
||||
<data name="Second" xml:space="preserve">
|
||||
<value>segundo</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>segundos</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Ajustes</value>
|
||||
</data>
|
||||
<data name="Settings_About" xml:space="preserve">
|
||||
<value>Acerca de</value>
|
||||
</data>
|
||||
<data name="Settings_AboutInfo" xml:space="preserve">
|
||||
<value>unison es un software libre. Es construido con las siguientes tecnologías:</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectButton" xml:space="preserve">
|
||||
<value>Conexión</value>
|
||||
</data>
|
||||
<data name="Settings_Connection" xml:space="preserve">
|
||||
<value>Conexión</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||
<value>Tenga en cuenta que, dado que las contraseñas de MPD no son seguras (se envían en texto sin formato al servidor), se guardan tal cual en el archivo de configuración.</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||
<value>Estado:</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||
<value>conectado a MPD</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||
<value>conectando...</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||
<value>no conectado.</value>
|
||||
</data>
|
||||
<data name="Settings_Host" xml:space="preserve">
|
||||
<value>Host</value>
|
||||
</data>
|
||||
<data name="Settings_License" xml:space="preserve">
|
||||
<value>Licencia</value>
|
||||
</data>
|
||||
<data name="Settings_MadeBy" xml:space="preserve">
|
||||
<value>Creado por</value>
|
||||
</data>
|
||||
<data name="Settings_NextTrack" xml:space="preserve">
|
||||
<value>Pista siguiente</value>
|
||||
</data>
|
||||
<data name="Settings_Password" xml:space="preserve">
|
||||
<value>Contraseña</value>
|
||||
</data>
|
||||
<data name="Settings_PlayPause" xml:space="preserve">
|
||||
<value>Jugar / pausa</value>
|
||||
</data>
|
||||
<data name="Settings_Port" xml:space="preserve">
|
||||
<value>Puerto</value>
|
||||
</data>
|
||||
<data name="Settings_PreviousTrack" xml:space="preserve">
|
||||
<value>Pista precedente</value>
|
||||
</data>
|
||||
<data name="Settings_Shortcuts" xml:space="preserve">
|
||||
<value>Atajos</value>
|
||||
</data>
|
||||
<data name="Settings_ShortcutsInfo" xml:space="preserve">
|
||||
<value>Tenga en cuenta que si no está reconocida la tecla, esto se debe a una limitación en el funcionamiento de las teclas virtuales.</value>
|
||||
</data>
|
||||
<data name="Settings_ShortcutsKey" xml:space="preserve">
|
||||
<value>Entre una tecla...</value>
|
||||
</data>
|
||||
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||
<value>Mostrar ventana</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle1" xml:space="preserve">
|
||||
<value>La ventana Aleatorio permite agregar canciones aleatorias a la fila. La dos opciones tienen en cuenta el filtro.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle2" xml:space="preserve">
|
||||
<value>Si el filtro es vacío, la integralidad de la biblioteca musical se tiene en cuenta.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle3" xml:space="preserve">
|
||||
<value>El filtro es buscado cada vez que las opciones Agregar a la fila o Aleatorio continuo son usadas.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle4" xml:space="preserve">
|
||||
<value>Agrega un número dado de canciones a la fila. For razones tecnicas, es opción es limitada a 100 canciones aleatorias sin filtro, y a 1000 canciones con filtro.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle5" xml:space="preserve">
|
||||
<value>Activando esa opción, unison va a agregar automaticamente canciones a la fila para nunca llegar al cabo de canciones a escuchar.</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||
<value>Puede cambiar a su propia versión instalada localmente del cliente Snapcast con una ruta</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastInfo2" xml:space="preserve">
|
||||
<value> absoluta</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastInfo3" xml:space="preserve">
|
||||
<value>.</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastLauch" xml:space="preserve">
|
||||
<value>Lanzar al inicio</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastPath" xml:space="preserve">
|
||||
<value>Ruta del ejecutable</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastPort" xml:space="preserve">
|
||||
<value>Puerto</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastResetButton" xml:space="preserve">
|
||||
<value>Reinicializar</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastWindow" xml:space="preserve">
|
||||
<value>Mostrar ventana de Snapcast</value>
|
||||
</data>
|
||||
<data name="Settings_SourceCode1" xml:space="preserve">
|
||||
<value>Código fuente disponible gratuitamente</value>
|
||||
</data>
|
||||
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||
<value>aquí</value>
|
||||
</data>
|
||||
<data name="Settings_UpdateDatabase" xml:space="preserve">
|
||||
<value>Actualizar base de datos</value>
|
||||
</data>
|
||||
<data name="Settings_Version" xml:space="preserve">
|
||||
<value>Versión:</value>
|
||||
</data>
|
||||
<data name="Settings_VolumeDown" xml:space="preserve">
|
||||
<value>Bajar volumen</value>
|
||||
</data>
|
||||
<data name="Settings_VolumeMute" xml:space="preserve">
|
||||
<value>Volumen mudo</value>
|
||||
</data>
|
||||
<data name="Settings_VolumeOffset" xml:space="preserve">
|
||||
<value>Intervalo de volumen</value>
|
||||
</data>
|
||||
<data name="Settings_VolumeUp" xml:space="preserve">
|
||||
<value>Subir volumen</value>
|
||||
</data>
|
||||
<data name="ShowWindow" xml:space="preserve">
|
||||
<value>Mostrar ventana</value>
|
||||
</data>
|
||||
<data name="Shuffle" xml:space="preserve">
|
||||
<value>Aleatorio</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage1" xml:space="preserve">
|
||||
<value>Agregando</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage2" xml:space="preserve">
|
||||
<value>canciones...</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage3" xml:space="preserve">
|
||||
<value>¡terminado!</value>
|
||||
</data>
|
||||
<data name="Shuffle_Continuous" xml:space="preserve">
|
||||
<value>Aleatorio continuo</value>
|
||||
</data>
|
||||
<data name="Shuffle_ContinuousEnable" xml:space="preserve">
|
||||
<value>Empezar aleatorio continuo</value>
|
||||
</data>
|
||||
<data name="Shuffle_Filter" xml:space="preserve">
|
||||
<value>Filtro</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterQuery" xml:space="preserve">
|
||||
<value>Búsqueda de filtro</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterReset" xml:space="preserve">
|
||||
<value>Reinicializar</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterSongNumber" xml:space="preserve">
|
||||
<value>Canciones en el filtro:</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying1" xml:space="preserve">
|
||||
<value>Búsqueda de filtro...</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying2" xml:space="preserve">
|
||||
<value>¡terminado!</value>
|
||||
</data>
|
||||
<data name="Shuffle_Queue" xml:space="preserve">
|
||||
<value>Agregar a la fila</value>
|
||||
</data>
|
||||
<data name="Shuffle_QueueSongs" xml:space="preserve">
|
||||
<value>Canciones para agregar</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||
<value>Error Snapcast</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup2" xml:space="preserve">
|
||||
<value>Ruta no válida:</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup3" xml:space="preserve">
|
||||
<value>Ruta actual:</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup4" xml:space="preserve">
|
||||
<value>Puede restablecerlo en la configuración si es necesario.</value>
|
||||
</data>
|
||||
<data name="StartSnapcast" xml:space="preserve">
|
||||
<value>Iniciar Snapcast</value>
|
||||
</data>
|
||||
<data name="Stats_Albums" xml:space="preserve">
|
||||
<value>Álbumes:</value>
|
||||
</data>
|
||||
<data name="Stats_Artists" xml:space="preserve">
|
||||
<value>Artistas:</value>
|
||||
</data>
|
||||
<data name="Stats_LastDatabaseUpdate" xml:space="preserve">
|
||||
<value>Última actualización de la base de datos:</value>
|
||||
</data>
|
||||
<data name="Stats_Songs" xml:space="preserve">
|
||||
<value>Canciones:</value>
|
||||
</data>
|
||||
<data name="Stats_TotalPlaytime" xml:space="preserve">
|
||||
<value>Tiempo de juego total:</value>
|
||||
</data>
|
||||
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||
<value>Tiempo total jugado:</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage1" xml:space="preserve">
|
||||
<value>Actualizando base de datos...</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage2" xml:space="preserve">
|
||||
<value> ¡terminado!</value>
|
||||
</data>
|
||||
<data name="Stats_Uptime" xml:space="preserve">
|
||||
<value>Tiempo de actividad de MPD:</value>
|
||||
</data>
|
||||
<data name="StopSnapcast" xml:space="preserve">
|
||||
<value>Parar Snapcast</value>
|
||||
</data>
|
||||
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||
<value>Buscar actualización</value>
|
||||
</data>
|
||||
<data name="Update_ButtonStart" xml:space="preserve">
|
||||
<value>Actualizar</value>
|
||||
</data>
|
||||
<data name="Update_Message1" xml:space="preserve">
|
||||
<value>¡Actualización disponible! La nueva versión es la</value>
|
||||
</data>
|
||||
<data name="Update_Message2" xml:space="preserve">
|
||||
<value>¿Instalar ahora?</value>
|
||||
</data>
|
||||
<data name="Update_NoUpdate" xml:space="preserve">
|
||||
<value>No actualización disponible.</value>
|
||||
</data>
|
||||
<data name="Update_String1" xml:space="preserve">
|
||||
<value>¡Nueva versión</value>
|
||||
</data>
|
||||
<data name="Update_String2" xml:space="preserve">
|
||||
<value>disponible!</value>
|
||||
</data>
|
||||
</root>
|
@ -112,14 +112,62 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Database" xml:space="preserve">
|
||||
<value>Base de donnée</value>
|
||||
</data>
|
||||
<data name="Day" xml:space="preserve">
|
||||
<value>jour</value>
|
||||
</data>
|
||||
<data name="Days" xml:space="preserve">
|
||||
<value>jours</value>
|
||||
</data>
|
||||
<data name="Exit" xml:space="preserve">
|
||||
<value>Quitter</value>
|
||||
</data>
|
||||
<data name="FilterType_Album" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="FilterType_Artist" xml:space="preserve">
|
||||
<value>Artiste</value>
|
||||
</data>
|
||||
<data name="FilterType_Directory" xml:space="preserve">
|
||||
<value>Dossier</value>
|
||||
</data>
|
||||
<data name="FilterType_Genre" xml:space="preserve">
|
||||
<value>Genre</value>
|
||||
</data>
|
||||
<data name="FilterType_Song" xml:space="preserve">
|
||||
<value>Chanson</value>
|
||||
</data>
|
||||
<data name="FilterType_Year" xml:space="preserve">
|
||||
<value>Année</value>
|
||||
</data>
|
||||
<data name="Hour" xml:space="preserve">
|
||||
<value>heure</value>
|
||||
</data>
|
||||
<data name="Hours" xml:space="preserve">
|
||||
<value>heures</value>
|
||||
</data>
|
||||
<data name="Minute" xml:space="preserve">
|
||||
<value>minute</value>
|
||||
</data>
|
||||
<data name="Minutes" xml:space="preserve">
|
||||
<value>minutes</value>
|
||||
</data>
|
||||
<data name="Operator_Contains" xml:space="preserve">
|
||||
<value>contient</value>
|
||||
</data>
|
||||
<data name="Operator_Is" xml:space="preserve">
|
||||
<value>est</value>
|
||||
</data>
|
||||
<data name="Operator_IsNot" xml:space="preserve">
|
||||
<value>n'est pas</value>
|
||||
</data>
|
||||
<data name="Radios" xml:space="preserve">
|
||||
<value>Radios</value>
|
||||
</data>
|
||||
@ -133,7 +181,7 @@
|
||||
<value>Nom</value>
|
||||
</data>
|
||||
<data name="Radio_NotFound" xml:space="preserve">
|
||||
<value>Aucun station trouvée !</value>
|
||||
<value>Aucune station trouvée !</value>
|
||||
</data>
|
||||
<data name="Radio_Reset" xml:space="preserve">
|
||||
<value>Réinitialiser</value>
|
||||
@ -147,6 +195,12 @@
|
||||
<data name="Radio_Tags" xml:space="preserve">
|
||||
<value>Tags</value>
|
||||
</data>
|
||||
<data name="Second" xml:space="preserve">
|
||||
<value>seconde</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>secondes</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Configuration</value>
|
||||
</data>
|
||||
@ -162,14 +216,20 @@
|
||||
<data name="Settings_Connection" xml:space="preserve">
|
||||
<value>Connexion</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||
<value>Veuillez noter que comme les mots de passe de MPD ne sont pas sécurisés (ils sont envoyés en clair au serveur), ils sont sauvegardés tels quels.</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||
<value>Statut :</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||
<value>Connecté à MPD</value>
|
||||
<value>connecté à MPD</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||
<value>Connexion en cours...</value>
|
||||
<value>connexion en cours...</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||
<value>Non connecté.</value>
|
||||
<value>non connecté.</value>
|
||||
</data>
|
||||
<data name="Settings_Host" xml:space="preserve">
|
||||
<value>Hôte</value>
|
||||
@ -183,6 +243,9 @@
|
||||
<data name="Settings_NextTrack" xml:space="preserve">
|
||||
<value>Piste suivante</value>
|
||||
</data>
|
||||
<data name="Settings_Password" xml:space="preserve">
|
||||
<value>Mot de passe</value>
|
||||
</data>
|
||||
<data name="Settings_PlayPause" xml:space="preserve">
|
||||
<value>Jouer / pause</value>
|
||||
</data>
|
||||
@ -204,6 +267,21 @@
|
||||
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||
<value>Afficher la fenêtre</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle1" xml:space="preserve">
|
||||
<value>La fenêtre aléatoire permet d'ajouter des chansons aléatoires à la file. Les deux options prennent en compte le filtre.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle2" xml:space="preserve">
|
||||
<value>Si le filtre est vide, l'intégralité de la bibliothèque est prise en compte.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle3" xml:space="preserve">
|
||||
<value>Le filtre est recherché à chaque fois que les options Ajouter à la file ou Aléatoire continu sont utilisées.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle4" xml:space="preserve">
|
||||
<value>Ajoute un nombre fixe de chansons à la file. Pour des raisons techniques, cette option est limitée à 100 chansons aléatoires sans filtre, et à 1000 chansons avec filtre.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle5" xml:space="preserve">
|
||||
<value>En activant cette option, unison va ajouter automatiquement des chansons à la file pour ne jamais arriver à cours de chansons à écouter.</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||
<value>Il est possible de mettre votre version localement installé de Snapcast avec un </value>
|
||||
</data>
|
||||
@ -234,6 +312,9 @@
|
||||
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||
<value>ici</value>
|
||||
</data>
|
||||
<data name="Settings_UpdateDatabase" xml:space="preserve">
|
||||
<value>Mettre à jour la base de donnée</value>
|
||||
</data>
|
||||
<data name="Settings_Version" xml:space="preserve">
|
||||
<value>Version :</value>
|
||||
</data>
|
||||
@ -252,6 +333,48 @@
|
||||
<data name="ShowWindow" xml:space="preserve">
|
||||
<value>Montrer la fenêtre</value>
|
||||
</data>
|
||||
<data name="Shuffle" xml:space="preserve">
|
||||
<value>Aléatoire</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage1" xml:space="preserve">
|
||||
<value>Ajout de</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage2" xml:space="preserve">
|
||||
<value>chansons...</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage3" xml:space="preserve">
|
||||
<value>fini !</value>
|
||||
</data>
|
||||
<data name="Shuffle_Continuous" xml:space="preserve">
|
||||
<value>Aléatoire continu</value>
|
||||
</data>
|
||||
<data name="Shuffle_ContinuousEnable" xml:space="preserve">
|
||||
<value>Activer le mode aléatoire continu</value>
|
||||
</data>
|
||||
<data name="Shuffle_Filter" xml:space="preserve">
|
||||
<value>Filtre</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterQuery" xml:space="preserve">
|
||||
<value>Recherche du filtre</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterReset" xml:space="preserve">
|
||||
<value>Réinitialiser</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterSongNumber" xml:space="preserve">
|
||||
<value>Nombre de chansons dans le filtre :</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying1" xml:space="preserve">
|
||||
<value>Recherche du filtre...</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying2" xml:space="preserve">
|
||||
<value>fini !</value>
|
||||
</data>
|
||||
<data name="Shuffle_Queue" xml:space="preserve">
|
||||
<value>Ajouter à la file</value>
|
||||
</data>
|
||||
<data name="Shuffle_QueueSongs" xml:space="preserve">
|
||||
<value>Chansons à ajouter</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||
<value>Erreur Snapcast</value>
|
||||
</data>
|
||||
@ -267,9 +390,6 @@
|
||||
<data name="StartSnapcast" xml:space="preserve">
|
||||
<value>Démarrer Snapcast</value>
|
||||
</data>
|
||||
<data name="Stats" xml:space="preserve">
|
||||
<value>Stats</value>
|
||||
</data>
|
||||
<data name="Stats_Albums" xml:space="preserve">
|
||||
<value>Albums :</value>
|
||||
</data>
|
||||
@ -288,10 +408,37 @@
|
||||
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||
<value>Temps d'écoute écoulé :</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage1" xml:space="preserve">
|
||||
<value>Mise à jour de la base de donnée...</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage2" xml:space="preserve">
|
||||
<value> fini !</value>
|
||||
</data>
|
||||
<data name="Stats_Uptime" xml:space="preserve">
|
||||
<value>MPD lancé depuis : </value>
|
||||
<value>MPD lancé depuis :</value>
|
||||
</data>
|
||||
<data name="StopSnapcast" xml:space="preserve">
|
||||
<value>Stopper Snapcast</value>
|
||||
</data>
|
||||
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||
<value>Vérifier les mises à jour</value>
|
||||
</data>
|
||||
<data name="Update_ButtonStart" xml:space="preserve">
|
||||
<value>Mettre à jour</value>
|
||||
</data>
|
||||
<data name="Update_Message1" xml:space="preserve">
|
||||
<value>Mise à jour disponible ! La nouvelle version est la</value>
|
||||
</data>
|
||||
<data name="Update_Message2" xml:space="preserve">
|
||||
<value>Installer maintenant ?</value>
|
||||
</data>
|
||||
<data name="Update_NoUpdate" xml:space="preserve">
|
||||
<value>Pas de mise à jour disponible.</value>
|
||||
</data>
|
||||
<data name="Update_String1" xml:space="preserve">
|
||||
<value>Nouvelle version</value>
|
||||
</data>
|
||||
<data name="Update_String2" xml:space="preserve">
|
||||
<value>disponible !</value>
|
||||
</data>
|
||||
</root>
|
@ -112,14 +112,62 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Database" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
<data name="Day" xml:space="preserve">
|
||||
<value>day</value>
|
||||
</data>
|
||||
<data name="Days" xml:space="preserve">
|
||||
<value>days</value>
|
||||
</data>
|
||||
<data name="Exit" xml:space="preserve">
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="FilterType_Album" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="FilterType_Artist" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="FilterType_Directory" xml:space="preserve">
|
||||
<value>Directory</value>
|
||||
</data>
|
||||
<data name="FilterType_Genre" xml:space="preserve">
|
||||
<value>Genre</value>
|
||||
</data>
|
||||
<data name="FilterType_Song" xml:space="preserve">
|
||||
<value>Song</value>
|
||||
</data>
|
||||
<data name="FilterType_Year" xml:space="preserve">
|
||||
<value>Year</value>
|
||||
</data>
|
||||
<data name="Hour" xml:space="preserve">
|
||||
<value>hour</value>
|
||||
</data>
|
||||
<data name="Hours" xml:space="preserve">
|
||||
<value>hours</value>
|
||||
</data>
|
||||
<data name="Minute" xml:space="preserve">
|
||||
<value>minute</value>
|
||||
</data>
|
||||
<data name="Minutes" xml:space="preserve">
|
||||
<value>minutes</value>
|
||||
</data>
|
||||
<data name="Operator_Contains" xml:space="preserve">
|
||||
<value>contains</value>
|
||||
</data>
|
||||
<data name="Operator_Is" xml:space="preserve">
|
||||
<value>is</value>
|
||||
</data>
|
||||
<data name="Operator_IsNot" xml:space="preserve">
|
||||
<value>is not</value>
|
||||
</data>
|
||||
<data name="Radios" xml:space="preserve">
|
||||
<value>Radios</value>
|
||||
</data>
|
||||
@ -147,6 +195,12 @@
|
||||
<data name="Radio_Tags" xml:space="preserve">
|
||||
<value>Tags</value>
|
||||
</data>
|
||||
<data name="Second" xml:space="preserve">
|
||||
<value>second</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>seconds</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
@ -162,14 +216,20 @@
|
||||
<data name="Settings_Connection" xml:space="preserve">
|
||||
<value>Connection</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionPasswordInfo" xml:space="preserve">
|
||||
<value>Please note that since MPD passwords are not secure (they are sent in plain text to the server), they are saved as is in the setting file.</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatus" xml:space="preserve">
|
||||
<value>Status:</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnected" xml:space="preserve">
|
||||
<value>Connected to MPD</value>
|
||||
<value>connected to MPD</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusConnecting" xml:space="preserve">
|
||||
<value>Connecting...</value>
|
||||
<value>connecting...</value>
|
||||
</data>
|
||||
<data name="Settings_ConnectionStatusOffline" xml:space="preserve">
|
||||
<value>Not connected.</value>
|
||||
<value>not connected.</value>
|
||||
</data>
|
||||
<data name="Settings_Host" xml:space="preserve">
|
||||
<value>Host</value>
|
||||
@ -183,6 +243,9 @@
|
||||
<data name="Settings_NextTrack" xml:space="preserve">
|
||||
<value>Next track</value>
|
||||
</data>
|
||||
<data name="Settings_Password" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="Settings_PlayPause" xml:space="preserve">
|
||||
<value>Play / pause</value>
|
||||
</data>
|
||||
@ -204,6 +267,21 @@
|
||||
<data name="Settings_ShowWindow" xml:space="preserve">
|
||||
<value>Show window</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle1" xml:space="preserve">
|
||||
<value>The shuffle window allows to add random songs to your queue. Both options take into account the filter.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle2" xml:space="preserve">
|
||||
<value>If the filter is empty, the entire music library is taken into account.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle3" xml:space="preserve">
|
||||
<value>The filter is queried each time you use the Add to queue or Continuous shuffle options.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle4" xml:space="preserve">
|
||||
<value>Add a fixed number of songs to the queue. For technical reasons, it is limited to 100 random songs without a filter, and to 1000 songs with a filter.</value>
|
||||
</data>
|
||||
<data name="Settings_Shuffle5" xml:space="preserve">
|
||||
<value>By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to.</value>
|
||||
</data>
|
||||
<data name="Settings_SnapcastInfo1" xml:space="preserve">
|
||||
<value>You can change to your own locally installed version of the Snapcast client with an</value>
|
||||
</data>
|
||||
@ -234,6 +312,9 @@
|
||||
<data name="Settings_SourceCode2" xml:space="preserve">
|
||||
<value>here</value>
|
||||
</data>
|
||||
<data name="Settings_UpdateDatabase" xml:space="preserve">
|
||||
<value>Update database</value>
|
||||
</data>
|
||||
<data name="Settings_Version" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
@ -252,6 +333,48 @@
|
||||
<data name="ShowWindow" xml:space="preserve">
|
||||
<value>Show window</value>
|
||||
</data>
|
||||
<data name="Shuffle" xml:space="preserve">
|
||||
<value>Shuffle</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage1" xml:space="preserve">
|
||||
<value>Adding</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage2" xml:space="preserve">
|
||||
<value>songs...</value>
|
||||
</data>
|
||||
<data name="Shuffle_ButtonMessage3" xml:space="preserve">
|
||||
<value>done!</value>
|
||||
</data>
|
||||
<data name="Shuffle_Continuous" xml:space="preserve">
|
||||
<value>Continuous shuffle</value>
|
||||
</data>
|
||||
<data name="Shuffle_ContinuousEnable" xml:space="preserve">
|
||||
<value>Enable continuous shuffle</value>
|
||||
</data>
|
||||
<data name="Shuffle_Filter" xml:space="preserve">
|
||||
<value>Filter</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterQuery" xml:space="preserve">
|
||||
<value>Query filter</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterReset" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="Shuffle_FilterSongNumber" xml:space="preserve">
|
||||
<value>Number of songs in filter:</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying1" xml:space="preserve">
|
||||
<value>Querying filter...</value>
|
||||
</data>
|
||||
<data name="Shuffle_Querying2" xml:space="preserve">
|
||||
<value>done!</value>
|
||||
</data>
|
||||
<data name="Shuffle_Queue" xml:space="preserve">
|
||||
<value>Add to queue</value>
|
||||
</data>
|
||||
<data name="Shuffle_QueueSongs" xml:space="preserve">
|
||||
<value>Songs to add</value>
|
||||
</data>
|
||||
<data name="Snapcast_Popup1" xml:space="preserve">
|
||||
<value>Snapcast error</value>
|
||||
</data>
|
||||
@ -267,9 +390,6 @@
|
||||
<data name="StartSnapcast" xml:space="preserve">
|
||||
<value>Start Snapcast</value>
|
||||
</data>
|
||||
<data name="Stats" xml:space="preserve">
|
||||
<value>Stats</value>
|
||||
</data>
|
||||
<data name="Stats_Albums" xml:space="preserve">
|
||||
<value>Albums:</value>
|
||||
</data>
|
||||
@ -288,10 +408,37 @@
|
||||
<data name="Stats_TotalTimePlayed" xml:space="preserve">
|
||||
<value>Total time played:</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage1" xml:space="preserve">
|
||||
<value>Updating database...</value>
|
||||
</data>
|
||||
<data name="Stats_UpdateDBMessage2" xml:space="preserve">
|
||||
<value>done!</value>
|
||||
</data>
|
||||
<data name="Stats_Uptime" xml:space="preserve">
|
||||
<value>MPD uptime:</value>
|
||||
</data>
|
||||
<data name="StopSnapcast" xml:space="preserve">
|
||||
<value>Stop Snapcast</value>
|
||||
</data>
|
||||
<data name="Update_ButtonCheck" xml:space="preserve">
|
||||
<value>Check for updates</value>
|
||||
</data>
|
||||
<data name="Update_ButtonStart" xml:space="preserve">
|
||||
<value>Start update</value>
|
||||
</data>
|
||||
<data name="Update_Message1" xml:space="preserve">
|
||||
<value>Update available! New version is</value>
|
||||
</data>
|
||||
<data name="Update_Message2" xml:space="preserve">
|
||||
<value>Install now?</value>
|
||||
</data>
|
||||
<data name="Update_NoUpdate" xml:space="preserve">
|
||||
<value>No update available.</value>
|
||||
</data>
|
||||
<data name="Update_String1" xml:space="preserve">
|
||||
<value>New version</value>
|
||||
</data>
|
||||
<data name="Update_String2" xml:space="preserve">
|
||||
<value>available!</value>
|
||||
</data>
|
||||
</root>
|
@ -2,7 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:sys="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<system:String x:Key="snapcastPath">snapclient_0.26.0-1_win64</system:String>
|
||||
<system:String x:Key="snapcastPath">snapcast_0.27</system:String>
|
||||
<system:String x:Key="snapcastPort">1704</system:String>
|
||||
|
||||
<system:String x:Key="playButton"></system:String>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
BIN
Resources/nothing.png
Normal file
BIN
Resources/nothing.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 369 B |
BIN
Resources/radio.png
Normal file
BIN
Resources/radio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -6,7 +6,7 @@
|
||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
xmlns:properties="clr-namespace:unison.Resources"
|
||||
mc:Ignorable="d"
|
||||
Title="unison" Closing="Window_Closing" Icon="/Resources/icon-full.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
|
||||
Title="unison" Closing="Window_Closing" LocationChanged="Window_LocationChanged" Icon="/Resources/icon-full.ico" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
|
||||
|
||||
<Window.Resources>
|
||||
<Style TargetType="Border" x:Key="UnselectedButton">
|
||||
@ -17,7 +17,7 @@
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270" MinWidth="650">
|
||||
<Grid Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}" MinHeight="270" MinWidth="700">
|
||||
<Grid x:Name="TopLayout" Margin="10,0,10,0" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
|
||||
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="225,0,0,0" Height="Auto" Width="Auto">
|
||||
<GroupBox Height="220" VerticalAlignment="Center">
|
||||
@ -104,20 +104,22 @@
|
||||
<VisualBrush Visual="{Binding ElementName=mask}"/>
|
||||
</StackPanel.OpacityMask>
|
||||
<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>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" Width="Auto" MinHeight="40">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0">
|
||||
<Button x:Name="Shuffle" Padding="5, 2" Click="Shuffle_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" Margin="0,0,10,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
|
||||
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
|
||||
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
|
||||
<TextBlock Text="{x:Static properties:Resources.Shuffle}" Margin="5, 0, 0, 0"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" Margin="5,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||
|
||||
<Button x:Name="Radio" Padding="5, 2" HorizontalAlignment="Left" Click="Radios_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="📻" Padding="0,0,0,2"/>
|
||||
<TextBlock Text="{x:Static properties:Resources.Radios}" Margin="5, 0, 0, 0"/>
|
||||
@ -135,12 +137,13 @@
|
||||
<TextBlock x:Name="Connection" HorizontalAlignment="Center" Text="Not connected" TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Center" Foreground="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" Margin="5,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||
<!--<Button x:Name="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<Button x:Name="Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="0,0,10,0" Padding="5, 2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}" IsEnabled="False">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🔁" Padding="0,0,0,2"/>
|
||||
<TextBlock Text="Shuffle" Margin="5, 0, 0, 0"/>
|
||||
<emoji:TextBlock Text="🔊" Padding="0,0,0,2"/>
|
||||
<TextBlock x:Name="SnapcastText" Text="{x:Static properties:Resources.StartSnapcast}" Margin="5, 0, 0, 0"/>
|
||||
</StackPanel>
|
||||
</Button>-->
|
||||
</Button>
|
||||
|
||||
<Button x:Name="Settings" Padding="5, 2" Click="Settings_Clicked" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" FocusVisualStyle="{x:Null}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<emoji:TextBlock Text="🛠️" Padding="0,0,0,2"/>
|
||||
|
@ -6,6 +6,9 @@ using System.Windows.Threading;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Diagnostics;
|
||||
using System.Data;
|
||||
using MpcNET.Commands.Playback;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
@ -13,18 +16,24 @@ namespace unison
|
||||
{
|
||||
private readonly Settings _settingsWin;
|
||||
private readonly Radios _radiosWin;
|
||||
private readonly Shuffle _shuffleWin;
|
||||
private readonly DispatcherTimer _timer;
|
||||
private readonly MPDHandler _mpd;
|
||||
|
||||
public Settings GetSettings() => _settingsWin;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitHwnd();
|
||||
InitializeComponent();
|
||||
DefaultState(true);
|
||||
WindowState = WindowState.Minimized;
|
||||
Top = Properties.Settings.Default.MainWindowTop;
|
||||
Left = Properties.Settings.Default.MainWindowLeft;
|
||||
|
||||
_settingsWin = new Settings();
|
||||
_radiosWin = new Radios();
|
||||
_shuffleWin = new Shuffle();
|
||||
_timer = new DispatcherTimer();
|
||||
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
|
||||
@ -42,13 +51,26 @@ namespace unison
|
||||
TimeSlider.Value = _mpd.GetCurrentTime() / _mpd.GetCurrentSong().Time * 100;
|
||||
}
|
||||
|
||||
public void UpdateStats()
|
||||
{
|
||||
_mpd.QueryStats();
|
||||
_settingsWin.UpdateStats();
|
||||
}
|
||||
|
||||
public void OnConnectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_mpd.IsConnected())
|
||||
{
|
||||
Snapcast.IsEnabled = true;
|
||||
UpdateStats();
|
||||
|
||||
ConnectionOkIcon.Visibility = Visibility.Visible;
|
||||
ConnectionFailIcon.Visibility = Visibility.Collapsed;
|
||||
|
||||
Snapcast.IsEnabled = true;
|
||||
if (_radiosWin.IsConnected())
|
||||
Radio.IsEnabled = true;
|
||||
|
||||
_shuffleWin.Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -56,12 +78,15 @@ namespace unison
|
||||
DefaultState(true);
|
||||
ConnectionOkIcon.Visibility = Visibility.Collapsed;
|
||||
ConnectionFailIcon.Visibility = Visibility.Visible;
|
||||
|
||||
Snapcast.IsEnabled = false;
|
||||
Radio.IsEnabled = false;
|
||||
}
|
||||
_settingsWin.UpdateConnectionStatus();
|
||||
Connection.Text = $"{Properties.Settings.Default.mpd_host}:{Properties.Settings.Default.mpd_port}";
|
||||
}
|
||||
|
||||
public void OnSongChanged(object sender, EventArgs e)
|
||||
public async void OnSongChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_mpd.GetCurrentSong() == null)
|
||||
return;
|
||||
@ -83,7 +108,7 @@ namespace unison
|
||||
SongAlbum.Text = _mpd.GetCurrentSong().Album;
|
||||
|
||||
if (_mpd.GetCurrentSong().Date != null)
|
||||
SongAlbum.Text += $" ({ _mpd.GetCurrentSong().Date})";
|
||||
SongAlbum.Text += $" ({_mpd.GetCurrentSong().Date.Split("-")[0]})";
|
||||
|
||||
SongGenre.Text = _mpd.GetCurrentSong().Genre;
|
||||
SongFormat.Text = _mpd.GetCurrentSong().Path.Substring(_mpd.GetCurrentSong().Path.LastIndexOf(".") + 1);
|
||||
@ -107,6 +132,19 @@ namespace unison
|
||||
_timer.Start();
|
||||
EndTime.Text = FormatSeconds(_mpd.GetCurrentSong().Time);
|
||||
}
|
||||
|
||||
Trace.WriteLine("Song changed called!");
|
||||
|
||||
if (_shuffleWin.GetContinuous())
|
||||
{
|
||||
// force consume: there's no other way to be sure
|
||||
// that we don't get to the end of the queue with nothing to play
|
||||
_mpd.SendCommand(new ConsumeCommand(true));
|
||||
|
||||
_mpd.CanPrevNext = false;
|
||||
await _shuffleWin.HandleContinuous();
|
||||
_mpd.CanPrevNext = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStatusChanged(object sender, EventArgs e)
|
||||
@ -135,9 +173,6 @@ namespace unison
|
||||
DefaultState();
|
||||
}
|
||||
}
|
||||
|
||||
_mpd.QueryStats();
|
||||
_settingsWin.UpdateStats();
|
||||
}
|
||||
|
||||
private void DefaultState(bool LostConnection = false)
|
||||
@ -153,8 +188,9 @@ namespace unison
|
||||
PlayPause.Text = (string)Application.Current.FindResource("pauseButton");
|
||||
TimeSlider.Value = 50;
|
||||
TimeSlider.IsEnabled = false;
|
||||
NoCover.Visibility = Visibility.Visible;
|
||||
NoCover.Visibility = Visibility.Collapsed;
|
||||
Cover.Visibility = Visibility.Collapsed;
|
||||
RadioCover.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (LostConnection)
|
||||
{
|
||||
@ -166,16 +202,18 @@ namespace unison
|
||||
|
||||
public void OnCoverChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_mpd.GetCover() == null)
|
||||
{
|
||||
NoCover.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;
|
||||
Cover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else if (Cover.Source != _mpd.GetCover())
|
||||
{
|
||||
Cover.Source = _mpd.GetCover();
|
||||
Cover.Visibility = Visibility.Visible;
|
||||
NoCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,22 +226,17 @@ namespace unison
|
||||
SnapcastText.Text = unison.Resources.Resources.StartSnapcast;
|
||||
}
|
||||
|
||||
public void OnRadioBrowserConnected()
|
||||
{
|
||||
Radio.IsEnabled = true;
|
||||
}
|
||||
|
||||
public void UpdateButton(ref Border border, bool b)
|
||||
{
|
||||
border.Style = b ? (Style)Resources["SelectedButton"] : (Style)Resources["UnselectedButton"];
|
||||
}
|
||||
|
||||
public string FormatSeconds(int time)
|
||||
public static string FormatSeconds(int time)
|
||||
{
|
||||
TimeSpan timespan = TimeSpan.FromSeconds(time);
|
||||
return timespan.ToString(@"mm\:ss");
|
||||
}
|
||||
public string FormatSeconds(double time)
|
||||
public static string FormatSeconds(double time)
|
||||
{
|
||||
TimeSpan timespan = TimeSpan.FromSeconds(time);
|
||||
return timespan.ToString(@"mm\:ss");
|
||||
@ -212,7 +245,6 @@ namespace unison
|
||||
public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
|
||||
public void Previous_Clicked(object sender, RoutedEventArgs e) => _mpd.Prev();
|
||||
public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
|
||||
|
||||
public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
|
||||
public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
|
||||
public void Single_Clicked(object sender, RoutedEventArgs e) => _mpd.Single();
|
||||
@ -234,6 +266,15 @@ namespace unison
|
||||
_radiosWin.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
public void Shuffle_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_shuffleWin.Show();
|
||||
_shuffleWin.Activate();
|
||||
|
||||
if (_shuffleWin.WindowState == WindowState.Minimized)
|
||||
_shuffleWin.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
public void Settings_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_settingsWin.Show();
|
||||
@ -267,6 +308,11 @@ namespace unison
|
||||
slider.ToolTip = (int)slider.Value;
|
||||
}
|
||||
|
||||
public void UpdateUpdateStatus(string version)
|
||||
{
|
||||
_settingsWin.UpdateUpdateStatus(version);
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
@ -297,5 +343,12 @@ namespace unison
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void Window_LocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
Properties.Settings.Default.MainWindowTop = Top;
|
||||
Properties.Settings.Default.MainWindowLeft = Left;
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<Button Content="{x:Static properties:Resources.Radio_Search}" Click="Search_Clicked" Padding="5, 2"/>
|
||||
<Button x:Name="SearchButton" Content="{x:Static properties:Resources.Radio_Search}" Click="Search_Clicked" Padding="5, 2"/>
|
||||
<Button Content="{x:Static properties:Resources.Radio_Reset}" Click="Reset_Clicked" Margin="10,0,0,0" Padding="5, 2"/>
|
||||
<TextBlock x:Name="SearchStatus" Margin="15,1,0,0" FontStyle="Italic" />
|
||||
</StackPanel>
|
||||
|
@ -1,112 +1,96 @@
|
||||
using RadioBrowser;
|
||||
using RadioBrowser.Models;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Collections.Generic;
|
||||
using unison.Handlers;
|
||||
using RadioBrowser.Models;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
public class CountryListItem
|
||||
{
|
||||
public uint Count { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Name == "")
|
||||
return "None";
|
||||
return $"{Name} ({Count})";
|
||||
}
|
||||
}
|
||||
|
||||
public class StationListItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Codec { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public int Bitrate { get; set; }
|
||||
public Uri Url { get; set; }
|
||||
|
||||
private string _country;
|
||||
public string Country
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_country.Length == 0)
|
||||
return "🏴☠️";
|
||||
return string.Concat(_country.ToUpper().Select(x => char.ConvertFromUtf32(x + 0x1F1A5))); // return emoji
|
||||
}
|
||||
set
|
||||
{
|
||||
_country = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Radios : Window
|
||||
{
|
||||
private RadioBrowserClient _radioBrowser;
|
||||
private MPDHandler _mpd;
|
||||
private RadioHandler _radio;
|
||||
|
||||
public bool IsConnected() => _radio.IsConnected();
|
||||
|
||||
public Radios()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
try
|
||||
{
|
||||
_radioBrowser = new RadioBrowserClient();
|
||||
Initialize();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("Exception while connecting to RadioBrowser: " + e.Message);
|
||||
return;
|
||||
}
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainWindow MainWin = (MainWindow)Application.Current.MainWindow;
|
||||
MainWin.OnRadioBrowserConnected();
|
||||
});
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
List<NameAndCount> Countries = await _radioBrowser.Lists.GetCountriesAsync();
|
||||
CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 });
|
||||
|
||||
foreach (NameAndCount Country in Countries)
|
||||
_radio = new RadioHandler();
|
||||
|
||||
if (!_radio.IsConnected())
|
||||
SearchButton.IsEnabled = false;
|
||||
|
||||
try
|
||||
{
|
||||
CountryList.Items.Add(new CountryListItem
|
||||
CountryList.Items.Add(new CountryListItem { Name = "", Count = 0 });
|
||||
|
||||
List<NameAndCount> Countries = await _radio.GetCountries();
|
||||
foreach (NameAndCount Country in Countries)
|
||||
{
|
||||
Name = Country.Name,
|
||||
Count = Country.Stationcount
|
||||
});
|
||||
CountryList.Items.Add(new CountryListItem
|
||||
{
|
||||
Name = Country.Name,
|
||||
Count = Country.Stationcount
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine("Exception while getting countries in RadioBrowser: " + e.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private string CleanString(string str)
|
||||
private static string CleanString(string str)
|
||||
{
|
||||
return str.Replace("\r\n", "").Replace("\n", "").Replace("\r", "");
|
||||
}
|
||||
|
||||
private void SearchHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
Search_Clicked(null, null);
|
||||
}
|
||||
|
||||
private async void Search_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
CountryListItem a = (CountryListItem)CountryList.SelectedItem;
|
||||
await SearchAdvanced(NameSearch.Text, a?.Name, TagSearch.Text);
|
||||
}
|
||||
catch (Exception except)
|
||||
{
|
||||
Trace.WriteLine("Error on RadioBrowser search: " + except.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SearchAdvanced(string name, string country, string tags)
|
||||
{
|
||||
try
|
||||
{
|
||||
SearchStatus.Text = unison.Resources.Resources.Radio_Loading;
|
||||
|
||||
List<StationInfo> advancedSearch = await _radioBrowser.Search.AdvancedAsync(new AdvancedSearchOptions
|
||||
List<StationInfo> advancedSearch = await Task.Run(async () =>
|
||||
{
|
||||
Name = name,
|
||||
Country = country,
|
||||
TagList = tags
|
||||
return await _radio.AdvancedSearch(new AdvancedSearchOptions
|
||||
{
|
||||
Name = name,
|
||||
Country = country,
|
||||
TagList = tags
|
||||
});
|
||||
});
|
||||
|
||||
RadioListGrid.Items.Clear();
|
||||
@ -132,7 +116,7 @@ namespace unison
|
||||
}
|
||||
catch (Exception except)
|
||||
{
|
||||
Debug.WriteLine("Error on RadioBrowser search advanced: " + except.Message);
|
||||
Trace.WriteLine("Error on RadioBrowser search advanced: " + except.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,13 +136,13 @@ namespace unison
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Debug.WriteLine("Error: Invalid index.");
|
||||
Trace.WriteLine("Error: Invalid index.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (station.Url == null)
|
||||
{
|
||||
Debug.WriteLine("Error: Invalid station.");
|
||||
Trace.WriteLine("Error: Invalid station.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -166,19 +150,6 @@ namespace unison
|
||||
_mpd.ClearAddAndPlay(station.Url.AbsoluteUri);
|
||||
}
|
||||
|
||||
private async void Search_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
CountryListItem a = (CountryListItem)CountryList.SelectedItem;
|
||||
await SearchAdvanced(NameSearch.Text, a?.Name, TagSearch.Text);
|
||||
}
|
||||
catch (Exception except)
|
||||
{
|
||||
Debug.WriteLine("Error on RadioBrowser search: " + except.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NameSearch.Text = "";
|
||||
@ -186,12 +157,6 @@ namespace unison
|
||||
CountryList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void SearchHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
Search_Clicked(null, null);
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
@ -17,7 +17,7 @@
|
||||
</x:Array>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid MinWidth="390">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TabControl Margin="10">
|
||||
<TabItem Header="MPD">
|
||||
@ -33,75 +33,26 @@
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_Host}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox x:Name="MpdHost" KeyDown="ConnectHandler" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
<TextBox x:Name="MpdHost" KeyDown="ConnectHandler" TextChanged="MpdConnectTextBox" TextWrapping="Wrap" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5,0,0">
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_Port}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox x:Name="MpdPort" KeyDown="ConnectHandler" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
<TextBox x:Name="MpdPort" KeyDown="ConnectHandler" PreviewTextInput="NumberValidationTextBox" MaxLength="5" TextWrapping="Wrap" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!--<StackPanel Margin="0,5,0,0">
|
||||
<TextBlock Text="Password" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<TextBox x:Name="MpdPassword" TextWrapping="Wrap" Width="250" Margin="10,2,0,0"/>
|
||||
</StackPanel>-->
|
||||
|
||||
<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"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{x:Static properties:Resources.Stats}">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="📊"/>
|
||||
<Run Text="{x:Static properties:Resources.Stats}"/>
|
||||
</TextBlock>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<TextBlock>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Songs}"/><Run Text=" "/><Run x:Name="StatSong"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Albums}"/><Run Text=" "/><Run x:Name="StatAlbum"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Artists}"/><Run Text=" "/><Run x:Name="StatArtist"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_TotalPlaytime}"/><Run Text=" "/><Run x:Name="StatTotalPlaytime"/><LineBreak/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Uptime}"/><Run Text=" "/><Run x:Name="StatUptime"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_TotalTimePlayed}"/><Run Text=" "/><Run x:Name="StatTotalTimePlayed"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_LastDatabaseUpdate}"/><Run Text=" "/><Run x:Name="StatDatabaseUpdate"/>
|
||||
</TextBlock>
|
||||
</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 Margin="0,5,0,0">
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_Password}" TextWrapping="Wrap" Margin="5,0,0,0"/>
|
||||
<PasswordBox x:Name="MpdPassword" KeyDown="ConnectHandler" Margin="10,2,0,0"/>
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_ConnectionPasswordInfo}" TextWrapping="Wrap" Margin="10,5,0,0" MaxWidth="390"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock TextWrapping="Wrap" Margin="5,10,0,0">
|
||||
<Run Text="{x:Static properties:Resources.Settings_ConnectionStatus}" FontWeight="Bold" />
|
||||
<Run x:Name="ConnectionStatus" Text="{x:Static properties:Resources.Settings_ConnectionStatusOffline}"/>
|
||||
</TextBlock>
|
||||
|
||||
<Button x:Name="ConnectButton" Content="{x:Static properties:Resources.Settings_ConnectButton}" Margin="0,10,0,0" Width="120" Click="MPDConnect_Clicked" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
@ -151,7 +102,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -159,7 +110,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -167,7 +118,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -175,7 +126,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -183,7 +134,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -191,7 +142,7 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
@ -199,23 +150,114 @@
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD1" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<ComboBox ItemsSource="{StaticResource ShortcutItems}" Margin="0,0,5,0" MinWidth="70" SelectionChanged="MOD_SelectionChanged" Tag="MOD2" FontWeight="Light" SelectedIndex="0" BorderBrush="{x:Null}" FocusVisualStyle="{x:Null}"></ComboBox>
|
||||
<Button Click="RemapKey_Clicked" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FocusVisualStyle="{x:Null}">
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
<TextBlock Text="None" TextAlignment="Center" TextWrapping="Wrap" MinWidth="150" Margin="5,1,5,1" HorizontalAlignment="Stretch" FontWeight="Light" VerticalAlignment="Stretch"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_ShortcutsInfo}" TextWrapping="Wrap" Margin="0,2,0,0" MaxWidth="420" />
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_ShortcutsInfo}" TextWrapping="Wrap" MaxWidth="440" TextAlignment="Justify" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Content="{x:Static properties:Resources.Settings_SnapcastResetButton}" Margin="0,10,0,0" Width="120" Click="ShortcutsReset_Clicked"/>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Snapcast">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<emoji:TextBlock Text="🔊 Snapcast"/>
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox x:Name="SnapcastStartup" Margin="5, 5, 0, 0">
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastLauch}" TextWrapping="Wrap"/>
|
||||
</CheckBox>
|
||||
<CheckBox x:Name="SnapcastWindow" Margin="5,2.5,0,0">
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastWindow}" TextWrapping="Wrap"/>
|
||||
</CheckBox>
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPort}" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
||||
<TextBox x:Name="SnapcastPort" MaxLength="5" PreviewTextInput="NumberValidationTextBox" TextWrapping="Wrap" Margin="10,2,5,0"/>
|
||||
<TextBlock Text="{x:Static properties:Resources.Settings_SnapcastPath}" TextWrapping="Wrap" Margin="5,5,0,0"/>
|
||||
<TextBox x:Name="SnapcastPath" TextWrapping="Wrap" Margin="10,2,5,0"/>
|
||||
<TextBlock TextWrapping="Wrap" Margin="5,5,0,0" TextAlignment="Left" Width="390">
|
||||
<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}" Width="120" Click="SnapcastReset_Clicked" Margin="0,5,0,0" BorderThickness="1,1,1,1"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{x:Static properties:Resources.Shuffle}">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="🔁 "/>
|
||||
<Run Text="{x:Static properties:Resources.Shuffle}"></Run>
|
||||
</TextBlock>
|
||||
</GroupBox.Header>
|
||||
<Grid MaxWidth="500">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run Text="{x:Static properties:Resources.Settings_Shuffle1}"></Run>
|
||||
<Run Text="{x:Static properties:Resources.Settings_Shuffle2}"></Run><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Settings_Shuffle3}"></Run><LineBreak/><LineBreak/>
|
||||
|
||||
<Run FontWeight="Bold" Text="{x:Static properties:Resources.Shuffle_Queue}"></Run><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Settings_Shuffle4}"></Run>
|
||||
<LineBreak/><LineBreak/>
|
||||
|
||||
<Run FontWeight="Bold" Text="{x:Static properties:Resources.Shuffle_Continuous}"></Run><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Settings_Shuffle5}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{x:Static properties:Resources.Database}" Height="20" VerticalAlignment="Bottom">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="📊"/>
|
||||
<Run Text="{x:Static properties:Resources.Database}"/>
|
||||
</TextBlock>
|
||||
</GroupBox.Header>
|
||||
<StackPanel>
|
||||
|
||||
<Grid VerticalAlignment="Top">
|
||||
<TextBlock>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Songs}"/><Run Text=" "/><Run x:Name="StatSong"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Albums}"/><Run Text=" "/><Run x:Name="StatAlbum"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Artists}"/><Run Text=" "/><Run x:Name="StatArtist"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_TotalPlaytime}"/><Run Text=" "/><Run x:Name="StatTotalPlaytime"/><LineBreak/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_Uptime}"/><Run Text=" "/><Run x:Name="StatUptime"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_TotalTimePlayed}"/><Run Text=" "/><Run x:Name="StatTotalTimePlayed"/><LineBreak/>
|
||||
<Run Text="{x:Static properties:Resources.Stats_LastDatabaseUpdate}"/><Run Text=" "/><Run x:Name="StatDatabaseUpdate"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<Button x:Name="UpdateDatabaseButton" Content="{x:Static properties:Resources.Settings_UpdateDatabase}" Click="MPDDatabaseUpdate_Clicked" MinWidth="120" Padding="3,1,3,1" FocusVisualStyle="{x:Null}"/>
|
||||
<TextBlock x:Name="UpdateDBMessage" Text="{x:Static properties:Resources.Stats_UpdateDBMessage1}" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
|
||||
<TextBlock x:Name="UpdateDBMessage2" Text="{x:Static properties:Resources.Stats_UpdateDBMessage2}" Margin="3,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{x:Static properties:Resources.Settings_About}" Height="20" VerticalAlignment="Bottom">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
@ -224,16 +266,24 @@
|
||||
</GroupBox.Header>
|
||||
<Grid VerticalAlignment="Top">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" VerticalAlignment="Top">
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
<Run Text="{x:Static properties:Resources.Settings_Version}"/>
|
||||
<Run Text="{Binding GetVersion, Mode = OneWay}"/>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="UpdateText" TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
<Run x:Name="UpdateText2" Text="New version X.X.X available!" FontWeight="Bold"/>
|
||||
</TextBlock>
|
||||
|
||||
<Button x:Name="UpdateButton" Content="{x:Static properties:Resources.Update_ButtonCheck}" Margin="0,3,0,10" Width="150" Click="CheckUpdates" HorizontalAlignment="Left"/>
|
||||
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
<Run Text="{x:Static properties:Resources.Settings_AboutInfo}" /><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/Difegue/MpcNET" RequestNavigate="Hyperlink_RequestNavigate">MpcNET</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/hardcodet/wpf-notifyicon" RequestNavigate="Hyperlink_RequestNavigate">wpf-notifyicon</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/samhocevar/emoji.wpf" RequestNavigate="Hyperlink_RequestNavigate">Emoji.WPF</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/tof4/RadioBrowser" RequestNavigate="Hyperlink_RequestNavigate">RadioBrowser</Hyperlink>
|
||||
※ <Hyperlink NavigateUri="https://github.com/tof4/RadioBrowser" RequestNavigate="Hyperlink_RequestNavigate">RadioBrowser</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/ravibpatel/AutoUpdater.NET" RequestNavigate="Hyperlink_RequestNavigate">AutoUpdater.NET</Hyperlink><LineBreak/>
|
||||
※ <Hyperlink NavigateUri="https://github.com/badaix/snapcast" RequestNavigate="Hyperlink_RequestNavigate">Snapcast</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Margin="0,10,0,0">
|
||||
<Run Text="{x:Static properties:Resources.Settings_SourceCode1}" />
|
||||
@ -250,7 +300,11 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{x:Static properties:Resources.Settings_License}" Height="20" VerticalAlignment="Bottom">
|
||||
<DockPanel Margin="8">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0" Margin="0,10,0,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
|
@ -1,15 +1,19 @@
|
||||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Threading;
|
||||
using unison.Handlers;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
@ -35,7 +39,7 @@ namespace unison
|
||||
}
|
||||
}
|
||||
|
||||
HotkeyHandler _hotkeys = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||
readonly HotkeyHandler _hotkeys = (HotkeyHandler)Application.Current.Properties["hotkeys"];
|
||||
|
||||
public Settings()
|
||||
{
|
||||
@ -52,16 +56,189 @@ namespace unison
|
||||
{
|
||||
MpdHost.Text = Properties.Settings.Default.mpd_host;
|
||||
MpdPort.Text = Properties.Settings.Default.mpd_port.ToString();
|
||||
//MpdPassword.Text = Properties.Settings.Default.mpd_password;
|
||||
MpdPassword.Password = Properties.Settings.Default.mpd_password;
|
||||
SnapcastStartup.IsChecked = Properties.Settings.Default.snapcast_startup;
|
||||
SnapcastWindow.IsChecked = Properties.Settings.Default.snapcast_window;
|
||||
SnapcastPath.Text = Properties.Settings.Default.snapcast_path;
|
||||
SnapcastPort.Text = Properties.Settings.Default.snapcast_port.ToString();
|
||||
VolumeOffset.Text = Properties.Settings.Default.volume_offset.ToString();
|
||||
|
||||
UpdateText.Visibility = Visibility.Collapsed;
|
||||
|
||||
InitializeShortcuts();
|
||||
}
|
||||
|
||||
public void UpdateConnectionStatus()
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
if (mpd.IsConnected())
|
||||
{
|
||||
ConnectionStatus.Text = $"{unison.Resources.Resources.Settings_ConnectionStatusConnected} {mpd.GetVersion()}.";
|
||||
ConnectButton.IsEnabled = false;
|
||||
UpdateDatabaseButton.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusOffline;
|
||||
ConnectButton.IsEnabled = true;
|
||||
UpdateDatabaseButton.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
Regex regex = new Regex("[^0-9]+");
|
||||
e.Handled = regex.IsMatch(e.Text);
|
||||
}
|
||||
|
||||
private void MpdConnectTextBox(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
TextBox textBox = (TextBox)sender;
|
||||
|
||||
if (textBox.Text == Properties.Settings.Default.mpd_host)
|
||||
ConnectButton.IsEnabled = false;
|
||||
else
|
||||
ConnectButton.IsEnabled = true;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
ProcessStartInfo psi = new ProcessStartInfo(e.Uri.AbsoluteUri);
|
||||
psi.UseShellExecute = true;
|
||||
Process.Start(psi);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ConnectButton.IsEnabled)
|
||||
return;
|
||||
|
||||
SaveSettings();
|
||||
|
||||
ConnectButton.IsEnabled = false;
|
||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
|
||||
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
Task.Run(async () => { await mpd.Initialize(); });
|
||||
}
|
||||
|
||||
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SnapcastPath.Text = (string)Application.Current.FindResource("snapcastPath");
|
||||
SnapcastPort.Text = (string)Application.Current.FindResource("snapcastPort");
|
||||
}
|
||||
|
||||
public void UpdateStats()
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
StatSong.Text = mpd.GetStats().Songs.ToString();
|
||||
StatAlbum.Text = mpd.GetStats().Albums.ToString();
|
||||
StatArtist.Text = mpd.GetStats().Artists.ToString();
|
||||
StatTotalPlaytime.Text = mpd.GetStats().TotalPlaytime.ToString();
|
||||
StatUptime.Text = mpd.GetStats().Uptime.ToString();
|
||||
StatTotalTimePlayed.Text = mpd.GetStats().TotalTimePlayed.ToString();
|
||||
StatDatabaseUpdate.Text = mpd.GetStats().DatabaseUpdate.ToString();
|
||||
}
|
||||
|
||||
private void ConnectHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
MPDConnect_Clicked(null, null);
|
||||
}
|
||||
|
||||
private void MPDDatabaseUpdate_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
if (mpd.IsConnected())
|
||||
mpd.UpdateDB();
|
||||
}
|
||||
|
||||
private static void TimedText(TextBlock textBlock, int time)
|
||||
{
|
||||
DispatcherTimer Timer = new DispatcherTimer();
|
||||
Timer.Interval = TimeSpan.FromSeconds(time);
|
||||
Timer.Tick += (sender, args) =>
|
||||
{
|
||||
Timer.Stop();
|
||||
textBlock.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
Timer.Start();
|
||||
}
|
||||
|
||||
public void MPDDatabaseUpdate_Start()
|
||||
{
|
||||
UpdateDBMessage.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public void MPDDatabaseUpdate_Stop()
|
||||
{
|
||||
UpdateDBMessage2.Visibility = Visibility.Visible;
|
||||
TimedText(UpdateDBMessage, 2);
|
||||
TimedText(UpdateDBMessage2, 2);
|
||||
}
|
||||
|
||||
private void CheckUpdates(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateHandler updater = (UpdateHandler)Application.Current.Properties["updater"];
|
||||
updater.Start(true);
|
||||
}
|
||||
|
||||
public void UpdateUpdateStatus(string version)
|
||||
{
|
||||
UpdateText.Visibility = Visibility.Visible;
|
||||
UpdateText2.Text = unison.Resources.Resources.Update_String1 + " " + version + " " + unison.Resources.Resources.Update_String2;
|
||||
UpdateButton.Content = unison.Resources.Resources.Update_ButtonStart;
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
SaveSettings();
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
public void InitHwnd()
|
||||
{
|
||||
WindowInteropHelper helper = new(this);
|
||||
helper.EnsureHandle();
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
||||
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
||||
Properties.Settings.Default.mpd_password = MpdPassword.Password;
|
||||
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
||||
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
||||
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
||||
Properties.Settings.Default.snapcast_port = int.Parse(SnapcastPort.Text, CultureInfo.InvariantCulture);
|
||||
Properties.Settings.Default.volume_offset = int.Parse(VolumeOffset.Text, CultureInfo.InvariantCulture);
|
||||
|
||||
Properties.Settings.Default.nextTrack_mod = GetMod(Shortcut_NextTrack);
|
||||
Properties.Settings.Default.nextTrack_vk = GetVk(Shortcut_NextTrack);
|
||||
Properties.Settings.Default.previousTrack_mod = GetMod(Shortcut_PreviousTrack);
|
||||
Properties.Settings.Default.previousTrack_vk = GetVk(Shortcut_PreviousTrack);
|
||||
Properties.Settings.Default.playPause_mod = GetMod(Shortcut_PlayPause);
|
||||
Properties.Settings.Default.playPause_vk = GetVk(Shortcut_PlayPause);
|
||||
Properties.Settings.Default.volumeUp_mod = GetMod(Shortcut_VolumeUp);
|
||||
Properties.Settings.Default.volumeUp_vk = GetVk(Shortcut_VolumeUp);
|
||||
Properties.Settings.Default.volumeDown_mod = GetMod(Shortcut_VolumeDown);
|
||||
Properties.Settings.Default.volumeDown_vk = GetVk(Shortcut_VolumeDown);
|
||||
Properties.Settings.Default.volumeMute_mod = GetMod(Shortcut_VolumeMute);
|
||||
Properties.Settings.Default.volumeMute_vk = GetVk(Shortcut_VolumeMute);
|
||||
Properties.Settings.Default.showWindow_mod = GetMod(Shortcut_ShowWindow);
|
||||
Properties.Settings.Default.showWindow_vk = GetVk(Shortcut_ShowWindow);
|
||||
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
|
||||
|
||||
// Hotkeys
|
||||
|
||||
void InitializeShortcuts()
|
||||
{
|
||||
System.Collections.Generic.IEnumerable<StackPanel> stackPanelCollection = RebindKeyWrapper.Children.OfType<StackPanel>();
|
||||
@ -108,55 +285,6 @@ namespace unison
|
||||
}
|
||||
}
|
||||
|
||||
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
Regex regex = new Regex("[^0-9]+");
|
||||
e.Handled = regex.IsMatch(e.Text);
|
||||
}
|
||||
|
||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
ProcessStartInfo psi = new(e.Uri.AbsoluteUri);
|
||||
psi.UseShellExecute = true;
|
||||
Process.Start(psi);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public void UpdateConnectionStatus()
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
if (mpd.IsConnected())
|
||||
ConnectionStatus.Text = $"{unison.Resources.Resources.Settings_ConnectionStatusConnected} {mpd.GetVersion()}.";
|
||||
else
|
||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusOffline;
|
||||
}
|
||||
|
||||
private void MPDConnect_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
ConnectionStatus.Text = unison.Resources.Resources.Settings_ConnectionStatusConnecting;
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
mpd.Connect();
|
||||
}
|
||||
|
||||
private void SnapcastReset_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SnapcastPath.Text = (string)Application.Current.FindResource("snapcastPath");
|
||||
SnapcastPort.Text = (string)Application.Current.FindResource("snapcastPort");
|
||||
}
|
||||
|
||||
public void UpdateStats()
|
||||
{
|
||||
MPDHandler mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
StatSong.Text = mpd.GetStats().Songs.ToString();
|
||||
StatAlbum.Text = mpd.GetStats().Albums.ToString();
|
||||
StatArtist.Text = mpd.GetStats().Artists.ToString();
|
||||
StatTotalPlaytime.Text = mpd.GetStats().TotalPlaytime.ToString();
|
||||
StatUptime.Text = mpd.GetStats().Uptime.ToString();
|
||||
StatTotalTimePlayed.Text = mpd.GetStats().TotalTimePlayed.ToString();
|
||||
StatDatabaseUpdate.Text = mpd.GetStats().DatabaseUpdate.ToString();
|
||||
}
|
||||
|
||||
private void HotkeyChanged()
|
||||
{
|
||||
_hotkeys.RemoveHotkeys();
|
||||
@ -254,7 +382,7 @@ namespace unison
|
||||
HotkeyChanged();
|
||||
}
|
||||
|
||||
private HotkeyHandler.VK GetVirtualKey(Key key)
|
||||
private static HotkeyHandler.VK GetVirtualKey(Key key)
|
||||
{
|
||||
foreach (object value in System.Enum.GetValues(typeof(HotkeyHandler.VK)))
|
||||
{
|
||||
@ -264,7 +392,7 @@ namespace unison
|
||||
return HotkeyHandler.VK.None;
|
||||
}
|
||||
|
||||
private HotkeyHandler.MOD GetMOD(string str)
|
||||
private static HotkeyHandler.MOD GetMOD(string str)
|
||||
{
|
||||
foreach (object value in System.Enum.GetValues(typeof(HotkeyHandler.MOD)))
|
||||
{
|
||||
@ -296,65 +424,16 @@ namespace unison
|
||||
InitializeShortcuts();
|
||||
}
|
||||
|
||||
public uint GetMod(StackPanel stackPanel)
|
||||
private static uint GetMod(StackPanel stackPanel)
|
||||
{
|
||||
return (uint)(GetMOD(stackPanel.Children.OfType<ComboBox>().First().SelectedItem.ToString()) | GetMOD(stackPanel.Children.OfType<ComboBox>().Last().SelectedItem.ToString()));
|
||||
}
|
||||
|
||||
public uint GetVk(StackPanel stackPanel)
|
||||
private static uint GetVk(StackPanel stackPanel)
|
||||
{
|
||||
Button button = stackPanel.Children.OfType<Button>().First();
|
||||
TextBlock textBlock = (TextBlock)button.Content;
|
||||
return (uint)(HotkeyHandler.VK)System.Enum.Parse(typeof(HotkeyHandler.VK), textBlock.Text, true);
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
Properties.Settings.Default.mpd_host = MpdHost.Text;
|
||||
Properties.Settings.Default.mpd_port = int.Parse(MpdPort.Text, CultureInfo.InvariantCulture);
|
||||
//Properties.Settings.Default.mpd_password = MpdPassword.Text;
|
||||
Properties.Settings.Default.snapcast_startup = (bool)SnapcastStartup.IsChecked;
|
||||
Properties.Settings.Default.snapcast_window = (bool)SnapcastWindow.IsChecked;
|
||||
Properties.Settings.Default.snapcast_path = SnapcastPath.Text;
|
||||
Properties.Settings.Default.snapcast_port = int.Parse(SnapcastPort.Text, CultureInfo.InvariantCulture);
|
||||
Properties.Settings.Default.volume_offset = int.Parse(VolumeOffset.Text, CultureInfo.InvariantCulture);
|
||||
|
||||
Properties.Settings.Default.nextTrack_mod = GetMod(Shortcut_NextTrack);
|
||||
Properties.Settings.Default.nextTrack_vk = GetVk(Shortcut_NextTrack);
|
||||
Properties.Settings.Default.previousTrack_mod = GetMod(Shortcut_PreviousTrack);
|
||||
Properties.Settings.Default.previousTrack_vk = GetVk(Shortcut_PreviousTrack);
|
||||
Properties.Settings.Default.playPause_mod = GetMod(Shortcut_PlayPause);
|
||||
Properties.Settings.Default.playPause_vk = GetVk(Shortcut_PlayPause);
|
||||
Properties.Settings.Default.volumeUp_mod = GetMod(Shortcut_VolumeUp);
|
||||
Properties.Settings.Default.volumeUp_vk = GetVk(Shortcut_VolumeUp);
|
||||
Properties.Settings.Default.volumeDown_mod = GetMod(Shortcut_VolumeDown);
|
||||
Properties.Settings.Default.volumeDown_vk = GetVk(Shortcut_VolumeDown);
|
||||
Properties.Settings.Default.volumeMute_mod = GetMod(Shortcut_VolumeMute);
|
||||
Properties.Settings.Default.volumeMute_vk = GetVk(Shortcut_VolumeMute);
|
||||
Properties.Settings.Default.showWindow_mod = GetMod(Shortcut_ShowWindow);
|
||||
Properties.Settings.Default.showWindow_vk = GetVk(Shortcut_ShowWindow);
|
||||
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
|
||||
private void ConnectHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
MPDConnect_Clicked(null, null);
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
SaveSettings();
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
public void InitHwnd()
|
||||
{
|
||||
WindowInteropHelper helper = new(this);
|
||||
helper.EnsureHandle();
|
||||
}
|
||||
}
|
||||
}
|
119
Views/Shuffle.xaml
Normal file
119
Views/Shuffle.xaml
Normal file
@ -0,0 +1,119 @@
|
||||
<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:properties="clr-namespace:unison.Resources" 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">
|
||||
<x:Static Member="properties:Resources.FilterType_Song"/>
|
||||
<x:Static Member="properties:Resources.FilterType_Artist"/>
|
||||
<x:Static Member="properties:Resources.FilterType_Album"/>
|
||||
<x:Static Member="properties:Resources.FilterType_Year"/>
|
||||
<x:Static Member="properties:Resources.FilterType_Genre"/>
|
||||
<x:Static Member="properties:Resources.FilterType_Directory"/>
|
||||
</x:Array>
|
||||
<x:Array x:Key="OperatorTypeA" Type="sys:String">
|
||||
<x:Static Member="properties:Resources.Operator_Contains"/>
|
||||
<x:Static Member="properties:Resources.Operator_Is"/>
|
||||
<x:Static Member="properties:Resources.Operator_IsNot"/>
|
||||
</x:Array>
|
||||
<x:Array x:Key="OperatorTypeB" Type="sys:String">
|
||||
<x:Static Member="properties:Resources.Operator_Is"/>
|
||||
<x:Static Member="properties:Resources.Operator_IsNot"/>
|
||||
</x:Array>
|
||||
<x:Array x:Key="OperatorTypeC" Type="sys:String">
|
||||
<x:Static Member="properties:Resources.Operator_Is"/>
|
||||
</x:Array>
|
||||
|
||||
<DataTemplate x:Key="FilterPanel">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<ComboBox x:Name="FilterType" SelectionChanged="FilterType_SelectionChanged" ItemsSource="{StaticResource FilterType}" SelectedIndex="0" Width="100" ScrollViewer.CanContentScroll="False" FocusVisualStyle="{x:Null}"/>
|
||||
<ComboBox x:Name="FilterOperator" SelectionChanged="OperatorType_SelectionChanged" ItemsSource="{StaticResource OperatorTypeA}" SelectedIndex="0" Width="80" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
|
||||
<ComboBox x:Name="FilterList" SelectedIndex="0" Width="240" Visibility="Collapsed" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
|
||||
<TextBox x:Name="FilterValue" KeyUp="QueryFilterHandler" Width="240" Margin="5,0,0,0"/>
|
||||
<Button Content="-" Padding="5, 2" Click="RemoveFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
|
||||
<Button Content="+" Padding="5, 2" Click="AddFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<StackPanel>
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="5,0,5,5">
|
||||
<GroupBox DockPanel.Dock="Top" Padding="0,4,0,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="🔡"/>
|
||||
<Run Text="{x:Static properties:Resources.Shuffle_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="{x:Static properties:Resources.Shuffle_FilterSongNumber}"/><Run Text=" "/><Run x:Name="SongFilterNumber" FontWeight="Bold"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
|
||||
<Button Content="{x:Static properties:Resources.Shuffle_FilterQuery}" Click="UpdateFilter_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="0,0,10,0"/>
|
||||
<Button Content="{x:Static properties:Resources.Shuffle_FilterReset}" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
|
||||
<TextBlock x:Name="QueryFilterText" Text="{x:Static properties:Resources.Shuffle_Querying1}" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
|
||||
<TextBlock x:Name="QueryFilterText2" Text="{x:Static properties:Resources.Shuffle_Querying2}" Margin="3,3,0,0" FontStyle="Italic" Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<GroupBox DockPanel.Dock="Right" Padding="0,4,0,0" Width="300">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="➕"/>
|
||||
<Run Text="{x:Static properties:Resources.Shuffle_Queue}"/>
|
||||
</TextBlock>
|
||||
</GroupBox.Header>
|
||||
<StackPanel Orientation="Vertical" Margin="5,5,5,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{x:Static properties:Resources.Shuffle_QueueSongs}" Margin="0,0,5,5"/>
|
||||
<TextBox x:Name="SongNumber" KeyUp="AddToQueueHandler" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="15" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<Button Content="{x:Static properties:Resources.Shuffle_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="{x:Static properties:Resources.Shuffle_ButtonMessage1}"/><Run Text=" "/><Run x:Name="NumberAddedSongs"/><Run Text=" "/><Run Text="{x:Static properties:Resources.Shuffle_ButtonMessage2}"/>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="SearchStatus2" Text="{x:Static properties:Resources.Shuffle_ButtonMessage3}" Margin="3,3,0,0" FontStyle="Italic" Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox DockPanel.Dock="Left" Padding="0,4,0,0" Width="260" Margin="0,0,5,0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock>
|
||||
<emoji:EmojiInline Text="♾️"/>
|
||||
<Run Text="{x:Static properties:Resources.Shuffle_Continuous}"/>
|
||||
</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="{x:Static properties:Resources.Shuffle_ContinuousEnable}" TextWrapping="Wrap"/>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
422
Views/Shuffle.xaml.cs
Normal file
422
Views/Shuffle.xaml.cs
Normal file
@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MpcNET.Commands.Database;
|
||||
using MpcNET.Tags;
|
||||
using MpcNET.Types;
|
||||
using MpcNET.Types.Filters;
|
||||
|
||||
namespace unison
|
||||
{
|
||||
public partial class Shuffle : Window
|
||||
{
|
||||
private readonly MPDHandler _mpd;
|
||||
private readonly ShuffleHandler _shuffle;
|
||||
|
||||
List<string> GenreList { get; }
|
||||
List<string> FolderList { get; }
|
||||
List<IFilter> Filters { get; }
|
||||
|
||||
bool _continuous = false;
|
||||
|
||||
public Shuffle()
|
||||
{
|
||||
InitializeComponent();
|
||||
GenreList = new();
|
||||
FolderList = new();
|
||||
Filters = new();
|
||||
SongFilterNumber.Text = "0";
|
||||
|
||||
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
||||
_shuffle = (ShuffleHandler)Application.Current.Properties["shuffle"];
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
ListGenre(_mpd._cancelCommand.Token);
|
||||
ListFolder(_mpd._cancelCommand.Token);
|
||||
}
|
||||
|
||||
private async void ListGenre(CancellationToken token)
|
||||
{
|
||||
if (GenreList.Count != 0)
|
||||
return;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
|
||||
|
||||
if (Response == null)
|
||||
return;
|
||||
|
||||
foreach (string genre in Response)
|
||||
GenreList.Add(genre);
|
||||
}
|
||||
|
||||
private async void ListFolder(CancellationToken token)
|
||||
{
|
||||
if (FolderList.Count != 0)
|
||||
return;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
|
||||
|
||||
if (Response == null)
|
||||
return;
|
||||
|
||||
foreach (IMpdFilePath folder in Response)
|
||||
FolderList.Add(folder.Name);
|
||||
}
|
||||
|
||||
private bool IsFilterEmpty()
|
||||
{
|
||||
if (Filters.Count == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private T FindParent<T>(DependencyObject child) where T : DependencyObject
|
||||
{
|
||||
var parent = VisualTreeHelper.GetParent(child);
|
||||
|
||||
if (parent == null)
|
||||
return null;
|
||||
|
||||
if (parent is T)
|
||||
return parent as T;
|
||||
else
|
||||
return FindParent<T>(parent);
|
||||
}
|
||||
|
||||
private void AddFilter_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
||||
SongFilterNumber.Text = "0";
|
||||
}
|
||||
|
||||
private void RemoveFilter_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (FilterPanel.Children.Count > 1)
|
||||
FilterPanel.Children.Remove(FindParent<ContentPresenter>(sender as Button));
|
||||
else
|
||||
Reset_Clicked(null, null);
|
||||
SongFilterNumber.Text = "0";
|
||||
}
|
||||
|
||||
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
|
||||
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
||||
SongFilterNumber.Text = "0";
|
||||
_shuffle.SongList.Clear();
|
||||
}
|
||||
|
||||
private static ITag FilterEquivalence_Type(string value)
|
||||
{
|
||||
if (value == unison.Resources.Resources.FilterType_Song)
|
||||
return MpdTags.Title;
|
||||
else if (value == unison.Resources.Resources.FilterType_Artist)
|
||||
return MpdTags.Artist;
|
||||
else if (value == unison.Resources.Resources.FilterType_Album)
|
||||
return MpdTags.Album;
|
||||
else if (value == unison.Resources.Resources.FilterType_Year)
|
||||
return MpdTags.Date;
|
||||
else if (value == unison.Resources.Resources.FilterType_Genre)
|
||||
return MpdTags.Genre;
|
||||
return MpdTags.Title;
|
||||
}
|
||||
|
||||
private static FilterOperator FilterEquivalence_Operator(string value)
|
||||
{
|
||||
if (value == unison.Resources.Resources.Operator_Contains)
|
||||
return FilterOperator.Contains;
|
||||
else if (value == unison.Resources.Resources.Operator_Is)
|
||||
return FilterOperator.Equal;
|
||||
else if (value == unison.Resources.Resources.Operator_IsNot)
|
||||
return FilterOperator.Different;
|
||||
return FilterOperator.Equal;
|
||||
}
|
||||
|
||||
private void FilterType_Change(object sender, string Operator, List<string> Listing)
|
||||
{
|
||||
ComboBox comboBox = sender as ComboBox;
|
||||
StackPanel stackPanel = comboBox.Parent as StackPanel;
|
||||
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
||||
{
|
||||
if (child.Name == "FilterOperator")
|
||||
{
|
||||
child.ItemsSource = (Array)FindResource(Operator);
|
||||
child.SelectedItem = child.Items[0];
|
||||
}
|
||||
|
||||
if (child.Name == "FilterList")
|
||||
{
|
||||
child.Visibility = Visibility.Visible;
|
||||
child.ItemsSource = Listing;
|
||||
child.SelectedItem = child.Items[0];
|
||||
}
|
||||
}
|
||||
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
||||
{
|
||||
if (child.Name == "FilterValue")
|
||||
child.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
SongFilterNumber.Text = "0";
|
||||
}
|
||||
|
||||
private void FilterType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
string item = e.AddedItems[0].ToString();
|
||||
if (item == unison.Resources.Resources.FilterType_Genre)
|
||||
FilterType_Change(sender, "OperatorTypeB", GenreList);
|
||||
else if (item == unison.Resources.Resources.FilterType_Directory)
|
||||
FilterType_Change(sender, "OperatorTypeC", FolderList);
|
||||
else
|
||||
{
|
||||
ComboBox combobox = sender as ComboBox;
|
||||
StackPanel stackpanel = combobox.Parent as StackPanel;
|
||||
foreach (ComboBox child in stackpanel.Children.OfType<ComboBox>())
|
||||
{
|
||||
if (child.Name == "FilterOperator")
|
||||
{
|
||||
child.ItemsSource = (Array)FindResource("OperatorTypeA");
|
||||
child.SelectedItem = child.Items[0];
|
||||
}
|
||||
|
||||
if (child.Name == "FilterList")
|
||||
child.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
foreach (TextBox child in stackpanel.Children.OfType<TextBox>())
|
||||
{
|
||||
if (child.Name == "FilterValue")
|
||||
child.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
SongFilterNumber.Text = "0";
|
||||
}
|
||||
|
||||
private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SongFilterNumber.Text = "0";
|
||||
}
|
||||
|
||||
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
QueryFilterText.Visibility = Visibility.Visible;
|
||||
await UpdateFilter();
|
||||
|
||||
QueryFilterText2.Visibility = Visibility.Visible;
|
||||
|
||||
TimedText(QueryFilterText, 1);
|
||||
TimedText(QueryFilterText2, 1);
|
||||
}
|
||||
|
||||
private static void TimedText(TextBlock textBlock, int time)
|
||||
{
|
||||
DispatcherTimer Timer = new DispatcherTimer();
|
||||
Timer.Interval = TimeSpan.FromSeconds(time);
|
||||
Timer.Tick += (sender, args) =>
|
||||
{
|
||||
Timer.Stop();
|
||||
textBlock.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
Timer.Start();
|
||||
}
|
||||
|
||||
private async Task UpdateFilter()
|
||||
{
|
||||
Filters.Clear();
|
||||
|
||||
foreach (ContentPresenter superChild in FilterPanel.Children)
|
||||
{
|
||||
ITag tag = MpdTags.Title;
|
||||
FilterOperator op = FilterOperator.None;
|
||||
string value = "";
|
||||
bool isDir = false;
|
||||
|
||||
StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
|
||||
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
||||
{
|
||||
if (child.Name == "FilterValue")
|
||||
value = child.Text;
|
||||
}
|
||||
|
||||
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
||||
{
|
||||
if (child.Name == "FilterType")
|
||||
{
|
||||
if (child.SelectedItem.ToString() == unison.Resources.Resources.FilterType_Directory)
|
||||
isDir = true;
|
||||
else
|
||||
tag = FilterEquivalence_Type(child.SelectedItem.ToString());
|
||||
}
|
||||
|
||||
if (child.Name == "FilterOperator")
|
||||
op = FilterEquivalence_Operator(child.SelectedItem.ToString());
|
||||
|
||||
if (child.Name == "FilterList" && child.Visibility == Visibility.Visible)
|
||||
value = child.SelectedItem.ToString();
|
||||
}
|
||||
|
||||
if (value != "")
|
||||
{
|
||||
if (!isDir)
|
||||
Filters.Add(new FilterTag(tag, value, op));
|
||||
else
|
||||
Filters.Add(new FilterBase(value, FilterOperator.None));
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await _shuffle.GetSongsFromFilter(Filters, _mpd._cancelCommand.Token);
|
||||
});
|
||||
SongFilterPanel.Visibility = Visibility.Visible;
|
||||
SongFilterNumber.Text = _shuffle.SongList.Count.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryFilterHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
UpdateFilter_Clicked(null, null);
|
||||
}
|
||||
|
||||
private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
Regex regex = new Regex("[^0-9]+");
|
||||
e.Handled = regex.IsMatch(e.Text);
|
||||
}
|
||||
|
||||
private void QueueValidationNumber()
|
||||
{
|
||||
int Number;
|
||||
try
|
||||
{
|
||||
Number = int.Parse(SongNumber.Text);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number < 1)
|
||||
SongNumber.Text = "1";
|
||||
if (IsFilterEmpty())
|
||||
{
|
||||
if (Number > 100)
|
||||
SongNumber.Text = "100";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Number > 1000)
|
||||
SongNumber.Text = "1000";
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddToQueue()
|
||||
{
|
||||
if (_mpd.GetStats() == null)
|
||||
return;
|
||||
|
||||
await UpdateFilter();
|
||||
QueueValidationNumber();
|
||||
|
||||
NumberAddedSongs.Text = SongNumber.Text;
|
||||
SearchStatus.Visibility = Visibility.Visible;
|
||||
|
||||
int Num = int.Parse(SongNumber.Text);
|
||||
await AddToQueue_Internal(Num);
|
||||
|
||||
SearchStatus2.Visibility = Visibility.Visible;
|
||||
|
||||
TimedText(SearchStatus, 2);
|
||||
TimedText(SearchStatus2, 2);
|
||||
}
|
||||
|
||||
private async Task AddToQueue_Internal(int Num)
|
||||
{
|
||||
if (IsFilterEmpty())
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await _shuffle.AddToQueueRandom(Num, _mpd._cancelCommand.Token);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
await _shuffle.AddToQueueFilter(Num, _mpd._cancelCommand.Token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToQueueHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Return)
|
||||
AddToQueue();
|
||||
}
|
||||
|
||||
private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AddToQueue();
|
||||
}
|
||||
|
||||
public bool GetContinuous()
|
||||
{
|
||||
return _continuous;
|
||||
}
|
||||
|
||||
public async Task HandleContinuous()
|
||||
{
|
||||
if (!_continuous)
|
||||
return;
|
||||
|
||||
int PlaylistLength = _mpd.GetStatus().PlaylistLength;
|
||||
int Num = 50 - PlaylistLength;
|
||||
|
||||
if (PlaylistLength > 25)
|
||||
return;
|
||||
|
||||
await UpdateFilter();
|
||||
await AddToQueue_Internal(Num);
|
||||
}
|
||||
|
||||
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ContinuousShuffle.IsChecked == true)
|
||||
_continuous = true;
|
||||
else
|
||||
_continuous = false;
|
||||
|
||||
if (_mpd.GetStatus().PlaylistLength < 10)
|
||||
await HandleContinuous();
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = WindowState.Minimized;
|
||||
Hide();
|
||||
}
|
||||
|
||||
public void InitHwnd()
|
||||
{
|
||||
WindowInteropHelper helper = new(this);
|
||||
helper.EnsureHandle();
|
||||
}
|
||||
}
|
||||
}
|
@ -30,11 +30,11 @@
|
||||
<Image Width="16" Height="16" emoji:Image.Source="📻" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<!--<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
||||
<MenuItem Header="Shuffle" Command="{Binding Shuffle}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="🔀" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>-->
|
||||
</MenuItem>
|
||||
<MenuItem Header="{x:Static properties:Resources.Settings}" Command="{Binding Settings}">
|
||||
<MenuItem.Icon>
|
||||
<Image Width="16" Height="16" emoji:Image.Source="🛠️" />
|
||||
|
@ -32,7 +32,7 @@ namespace unison
|
||||
CanExecuteFunc = () => true
|
||||
};
|
||||
|
||||
public string SnapcastText
|
||||
public static string SnapcastText
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -59,7 +59,7 @@ namespace unison
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand Radios
|
||||
public static ICommand Radios
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -71,7 +71,19 @@ namespace unison
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand Settings
|
||||
public static ICommand Shuffle
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DelegateCommand
|
||||
{
|
||||
CommandAction = () => ((MainWindow)Application.Current.MainWindow).Shuffle_Clicked(null, null),
|
||||
CanExecuteFunc = () => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static ICommand Settings
|
||||
{
|
||||
get
|
||||
{
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
snapcast_0.27/snapclient.exe
Normal file
BIN
snapcast_0.27/snapclient.exe
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2,17 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>Resources\icon-full.ico</ApplicationIcon>
|
||||
<Win32Resource></Win32Resource>
|
||||
<StartupObject>unison.App</StartupObject>
|
||||
<Version>1.2</Version>
|
||||
<Version>1.4</Version>
|
||||
<Company />
|
||||
<Authors>Théo Marchal</Authors>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageProjectUrl>https://git.n700.ovh/keb/unison</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.n700.ovh/keb/unison</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/ZetaKebab/unison</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/ZetaKebab/unison</RepositoryUrl>
|
||||
<Copyright>Théo Marchal</Copyright>
|
||||
<PackageIconUrl />
|
||||
</PropertyGroup>
|
||||
@ -22,6 +22,8 @@
|
||||
<None Remove="Resources\icon-mini.ico" />
|
||||
<None Remove="Resources\nocover.png" />
|
||||
<None Remove="LICENSE" />
|
||||
<None Remove="Resources\nothing.png" />
|
||||
<None Remove="Resources\radio.png" />
|
||||
<None Include="LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
@ -38,16 +40,23 @@
|
||||
<Resource Include="Resources\nocover.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\nothing.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\radio.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Content Include="LICENSE">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Emoji.Wpf" Version="0.3.3" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.0" />
|
||||
<PackageReference Include="Emoji.Wpf" Version="0.3.4" />
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||
<PackageReference Include="RadioBrowser" Version="0.6.1" />
|
||||
<PackageReference Include="MpcNET" Version="1.3.0" />
|
||||
<PackageReference Include="RadioBrowser" Version="0.7.0" />
|
||||
<PackageReference Include="MpcNET" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -64,6 +73,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Resources.es-ES.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Resources.fr-FR.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
@ -78,25 +90,25 @@
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\FLAC.dll">
|
||||
<None Update="snapcast_0.27\FLAC.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\ogg.dll">
|
||||
<None Update="snapcast_0.27\ogg.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\opus.dll">
|
||||
<None Update="snapcast_0.27\opus.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\README.txt">
|
||||
<None Update="snapcast_0.27\README.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\snapclient.exe">
|
||||
<None Update="snapcast_0.27\snapclient.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\soxr.dll">
|
||||
<None Update="snapcast_0.27\soxr.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="snapclient_0.26.0-1_win64\vorbis.dll">
|
||||
<None Update="snapcast_0.27\vorbis.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user