First commit for basic naive implementation
This commit is contained in:
parent
0cda436dfe
commit
d85449e3aa
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ obj/
|
|||||||
bin/
|
bin/
|
||||||
publish
|
publish
|
||||||
.vs
|
.vs
|
||||||
*.csproj.user
|
*.csproj.user
|
||||||
|
*.user
|
||||||
|
9
App.xaml
Normal file
9
App.xaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="unison.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:unison"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
17
App.xaml.cs
Normal file
17
App.xaml.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
10
AssemblyInfo.cs
Normal file
10
AssemblyInfo.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
1415
MPDCtrl/BinaryDownloader.cs
Normal file
1415
MPDCtrl/BinaryDownloader.cs
Normal file
File diff suppressed because it is too large
Load Diff
29
MPDCtrl/Classes/AlbumImage.cs
Normal file
29
MPDCtrl/Classes/AlbumImage.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace MPDCtrl.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// AlbumCover class.
|
||||||
|
/// </summary>
|
||||||
|
public class AlbumImage
|
||||||
|
{
|
||||||
|
public bool IsDownloading { get; set; }
|
||||||
|
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
|
||||||
|
public string SongFilePath { get; set; }
|
||||||
|
|
||||||
|
public byte[] BinaryData { get; set; } = Array.Empty<byte>();
|
||||||
|
|
||||||
|
public int BinarySize { get; set; }
|
||||||
|
|
||||||
|
public ImageSource AlbumImageSource { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
53
MPDCtrl/Classes/Playlist.cs
Normal file
53
MPDCtrl/Classes/Playlist.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MPDCtrl.Models
|
||||||
|
{
|
||||||
|
public class Playlist
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
private string _lastModified;
|
||||||
|
public string LastModified
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _lastModified;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_lastModified == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastModified = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastModifiedFormated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime _lastModifiedDateTime = default; //new DateTime(1998,04,30)
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_lastModified))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lastModifiedDateTime = DateTime.Parse(_lastModified, null, System.Globalization.DateTimeStyles.RoundtripKind);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Wrong LastModified timestamp format. " + _lastModified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||||
|
return _lastModifiedDateTime.ToString(culture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
52
MPDCtrl/Classes/Result.cs
Normal file
52
MPDCtrl/Classes/Result.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MPDCtrl.Models
|
||||||
|
{
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
public bool IsSuccess;
|
||||||
|
public string ErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConnectionResult: Result
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// generic
|
||||||
|
public class CommandResult : Result
|
||||||
|
{
|
||||||
|
public string ResultText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CommandBinaryResult : Result
|
||||||
|
{
|
||||||
|
public int WholeSize;
|
||||||
|
public int ChunkSize;
|
||||||
|
public string Type;
|
||||||
|
public byte[] BinaryData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for commands that return playlist songs.
|
||||||
|
public class CommandPlaylistResult : CommandResult
|
||||||
|
{
|
||||||
|
public ObservableCollection<SongInfo> PlaylistSongs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for commands that return search result.
|
||||||
|
public class CommandSearchResult : CommandResult
|
||||||
|
{
|
||||||
|
public ObservableCollection<SongInfo> SearchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Not used?
|
||||||
|
public class IdleResult : CommandResult
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
231
MPDCtrl/Classes/Song.cs
Normal file
231
MPDCtrl/Classes/Song.cs
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
//using MPDCtrl.Common;
|
||||||
|
|
||||||
|
namespace MPDCtrl.Models
|
||||||
|
{
|
||||||
|
// SongFile > SongInfo > SongInfoEx
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic song file class. (for listall)
|
||||||
|
/// </summary>
|
||||||
|
public class SongFile// : ViewModelBase
|
||||||
|
{
|
||||||
|
public string File { get; set; } = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SongInfo class. (for playlist or search result)
|
||||||
|
/// </summary>
|
||||||
|
public class SongInfo : SongFile
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
public string Track { get; set; } = "";
|
||||||
|
public string Disc { get; set; } = "";
|
||||||
|
public string Time { get; set; } = "";
|
||||||
|
public string TimeFormated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string _timeFormatted = "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Time))
|
||||||
|
{
|
||||||
|
int sec, min, hour, s;
|
||||||
|
|
||||||
|
double dtime = double.Parse(Time);
|
||||||
|
sec = Convert.ToInt32(dtime);
|
||||||
|
|
||||||
|
//sec = Int32.Parse(_time);
|
||||||
|
min = sec / 60;
|
||||||
|
s = sec % 60;
|
||||||
|
hour = min / 60;
|
||||||
|
min %= 60;
|
||||||
|
|
||||||
|
if ((hour == 0) && min == 0)
|
||||||
|
{
|
||||||
|
_timeFormatted = String.Format("{0}", s);
|
||||||
|
}
|
||||||
|
else if ((hour == 0) && (min != 0))
|
||||||
|
{
|
||||||
|
_timeFormatted = String.Format("{0}:{1:00}", min, s);
|
||||||
|
}
|
||||||
|
else if ((hour != 0) && (min != 0))
|
||||||
|
{
|
||||||
|
_timeFormatted = String.Format("{0}:{1:00}:{2:00}", hour, min, s);
|
||||||
|
}
|
||||||
|
else if (hour != 0)
|
||||||
|
{
|
||||||
|
_timeFormatted = String.Format("{0}:{1:00}:{2:00}", hour, min, s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Oops@TimeFormated: " + Time + " : " + hour.ToString() + " " + min.ToString() + " " + s.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FormatException e)
|
||||||
|
{
|
||||||
|
// Ignore.
|
||||||
|
// System.Diagnostics.Debug.WriteLine(e.Message);
|
||||||
|
System.Diagnostics.Debug.WriteLine("Wrong Time format. " + Time + " " + e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _timeFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public double TimeSort
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
double dtime = double.NaN;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dtime = double.Parse(Time);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return dtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Duration { get; set; } = "";
|
||||||
|
public string Artist { get; set; } = "";
|
||||||
|
public string Album { get; set; } = "";
|
||||||
|
public string AlbumArtist { get; set; } = "";
|
||||||
|
public string Composer { get; set; } = "";
|
||||||
|
public string Date { get; set; } = "";
|
||||||
|
public string Genre { get; set; } = "";
|
||||||
|
|
||||||
|
private string _lastModified;
|
||||||
|
public string LastModified
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _lastModified;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_lastModified == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastModified = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastModifiedFormated
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
DateTime _lastModifiedDateTime = default; //new DateTime(1998,04,30)
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_lastModified))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lastModifiedDateTime = DateTime.Parse(_lastModified, null, System.Globalization.DateTimeStyles.RoundtripKind);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Wrong LastModified timestamp format. " + _lastModified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||||
|
return _lastModifiedDateTime.ToString(culture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for sorting and (playlist pos)
|
||||||
|
private int _index;
|
||||||
|
public int Index
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_index == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_index = value;
|
||||||
|
//this.NotifyPropertyChanged(nameof(Index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isSelected;
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isSelected;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isSelected == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isSelected = value;
|
||||||
|
|
||||||
|
//NotifyPropertyChanged("IsSelected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexPlusOne
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _index+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// song class with some extra info. (for queue)
|
||||||
|
/// </summary>
|
||||||
|
public class SongInfoEx : SongInfo
|
||||||
|
{
|
||||||
|
// Queue specific
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
private string _pos;
|
||||||
|
public string Pos
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _pos;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_pos == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_pos = value;
|
||||||
|
//this.NotifyPropertyChanged(nameof(Pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isPlaying;
|
||||||
|
public bool IsPlaying
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isPlaying;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isPlaying == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isPlaying = value;
|
||||||
|
//this.NotifyPropertyChanged(nameof(IsPlaying));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
MPDCtrl/Classes/Status.cs
Normal file
149
MPDCtrl/Classes/Status.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MPDCtrl.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// MPD "status" class. (for "status" command result)
|
||||||
|
/// </summary>
|
||||||
|
public class Status
|
||||||
|
{
|
||||||
|
public enum MpdPlayState
|
||||||
|
{
|
||||||
|
Play, Pause, Stop
|
||||||
|
};
|
||||||
|
|
||||||
|
private MpdPlayState _ps;
|
||||||
|
private string _bitrate;
|
||||||
|
private int _volume = 50;
|
||||||
|
private bool _volumeIsSet;
|
||||||
|
private bool _repeat;
|
||||||
|
private bool _random;
|
||||||
|
private bool _consume;
|
||||||
|
private bool _single;
|
||||||
|
private string _songID = "";
|
||||||
|
private double _songTime = 0;
|
||||||
|
private double _songElapsed = 0;
|
||||||
|
private string _error = "";
|
||||||
|
|
||||||
|
public MpdPlayState MpdState
|
||||||
|
{
|
||||||
|
get { return _ps; }
|
||||||
|
set { _ps = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MpdBitrate
|
||||||
|
{
|
||||||
|
get { return _bitrate; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_bitrate = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MpdVolume
|
||||||
|
{
|
||||||
|
get { return _volume; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_volume = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MpdVolumeIsSet
|
||||||
|
{
|
||||||
|
get { return _volumeIsSet; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_volumeIsSet = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MpdRepeat
|
||||||
|
{
|
||||||
|
get { return _repeat; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_repeat = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MpdRandom
|
||||||
|
{
|
||||||
|
get { return _random; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_random = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool MpdConsume
|
||||||
|
{
|
||||||
|
get { return _consume; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_consume = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MpdSingle
|
||||||
|
{
|
||||||
|
get { return _single; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_single = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MpdSongID
|
||||||
|
{
|
||||||
|
get { return _songID; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_songID = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MpdSongTime
|
||||||
|
{
|
||||||
|
get { return _songTime; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_songTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MpdSongElapsed
|
||||||
|
{
|
||||||
|
get { return _songElapsed; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_songElapsed = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string MpdError
|
||||||
|
{
|
||||||
|
get { return _error; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_error = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_volume = 50;
|
||||||
|
_volumeIsSet = false;
|
||||||
|
_repeat = false;
|
||||||
|
_random = false;
|
||||||
|
_consume = false;
|
||||||
|
_songID = "";
|
||||||
|
_songTime = 0;
|
||||||
|
_songElapsed = 0;
|
||||||
|
_error = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
3722
MPDCtrl/MPC.cs
Normal file
3722
MPDCtrl/MPC.cs
Normal file
File diff suppressed because it is too large
Load Diff
69
MainWindow.xaml
Normal file
69
MainWindow.xaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<Window x:Class="unison.MainWindow"
|
||||||
|
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:local="clr-namespace:unison"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="unison"
|
||||||
|
Closing="Window_Closing" Icon="/images/nocover.png" ResizeMode="CanMinimize" WindowStyle="SingleBorderWindow" SizeToContent="WidthAndHeight">
|
||||||
|
<Grid Background="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}" MinHeight="280">
|
||||||
|
<Grid x:Name="TopLayout" Margin="10,10,10,0" VerticalAlignment="Top" Width="Auto" MinHeight="220">
|
||||||
|
<Grid x:Name="Display" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="220,0,0,0" MinHeight="220" MinWidth="400">
|
||||||
|
<Grid x:Name="CurrentSong" Margin="10,0,10,0" VerticalAlignment="Top" MinHeight="140">
|
||||||
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||||
|
<TextBlock x:Name="SongTitle" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="20"/>
|
||||||
|
<TextBlock x:Name="SongArtist" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="18"/>
|
||||||
|
<TextBlock x:Name="SongAlbum" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal" FontSize="16"/>
|
||||||
|
<TextBlock x:Name="Bitrate" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Normal"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Grid x:Name="Controls" VerticalAlignment="Bottom">
|
||||||
|
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" VerticalAlignment="Center">
|
||||||
|
<Grid HorizontalAlignment="Center" Margin="0,0,0,5">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock x:Name="CurrentTime" Text="0:00" TextWrapping="Wrap" HorizontalAlignment="Left"/>
|
||||||
|
<Slider x:Name="TimeSlider" Height="18" MinWidth="320" Margin="5,0,5,0" HorizontalAlignment="Center" Maximum="100"/>
|
||||||
|
<TextBlock x:Name="EndTime" Text="0:00" TextWrapping="Wrap" Height="18" HorizontalAlignment="Right"/>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top">
|
||||||
|
<Button x:Name="PreviousTrack" Content="⏪︎" Click="Previous_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Left"/>
|
||||||
|
<Button x:Name="PauseButton" Content="⏯️" Click="Pause_Clicked" FontSize="18" FontWeight="Bold" Background="{x:Null}" BorderBrush="{x:Null}" Margin="10,0,10,0"/>
|
||||||
|
<Button x:Name="NextTrack" Content="⏩︎" Click="Next_Clicked" FontSize="18" Background="{x:Null}" BorderBrush="{x:Null}" FontWeight="Bold" HorizontalAlignment="Right"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Grid VerticalAlignment="Stretch">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" HorizontalAlignment="Center" Margin="0,6,0,0">
|
||||||
|
<TextBlock Text="🔈" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||||
|
<Slider x:Name="VolumeSlider" Maximum="100" Value="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="180" FlowDirection="LeftToRight" Margin="5,0,5,0"/>
|
||||||
|
<TextBlock Text="🔊" TextWrapping="Wrap" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<Button x:Name="Random" Content="🔀" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="{x:Null}" BorderBrush="{x:Null}" FontSize="15" Click="Random_Clicked" Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}"/>
|
||||||
|
<Button x:Name="Repeat" Content="🔁" Background="{x:Null}" FontSize="15" BorderBrush="{x:Null}" Click="Repeat_Clicked"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom">
|
||||||
|
<Button x:Name="Single" Content="🔂" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="15" Background="{x:Null}" BorderBrush="{x:Null}" Click="Single_Clicked"/>
|
||||||
|
<Button x:Name="Consume" Content="❎" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="15" BorderBrush="{x:Null}" Background="{x:Null}" Click="Consume_Clicked"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Border x:Name="Cover_Border" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Left" VerticalAlignment="Center" MaxWidth="215" MaxHeight="215" >
|
||||||
|
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Source="images/nocover.png" MinWidth="215" MinHeight="215" MaxWidth="215" MaxHeight="215"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid x:Name="BottomLayout" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="{DynamicResource {x:Static SystemColors.AppWorkspaceBrushKey}}" Width="Auto" Margin="0,-2,0,0" MinHeight="40">
|
||||||
|
<Button x:Name="Snapcast" Content="Start Snapcast" HorizontalAlignment="Left" VerticalAlignment="Center" Click="Snapcast_Clicked" Margin="10,0,0,0" Padding="5, 2"/>
|
||||||
|
<TextBlock x:Name="DebugText" HorizontalAlignment="Center" Text="debug" TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" MinWidth="350"/>
|
||||||
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,10,0">
|
||||||
|
<!--<Button x:Name="Settings" Content="Shuffle" Padding="5, 2" HorizontalAlignment="Right" Margin="0,0,10,0"/>
|
||||||
|
<Button x:Name="Shuffle" Content="Settings" Padding="5, 2"/>-->
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
216
MainWindow.xaml.cs
Normal file
216
MainWindow.xaml.cs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
using MPDCtrl.Models;
|
||||||
|
|
||||||
|
namespace unison
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly MPC _mpd = new();
|
||||||
|
private bool _connected = false;
|
||||||
|
private int _currentVolume;
|
||||||
|
private bool _currentRandom;
|
||||||
|
private bool _currentRepeat;
|
||||||
|
private bool _currentSingle;
|
||||||
|
private bool _currentConsume;
|
||||||
|
private double _currentElapsed;
|
||||||
|
|
||||||
|
private readonly Process _snapcast = new Process();
|
||||||
|
private bool _snapcastStarted = false;
|
||||||
|
|
||||||
|
private string _snapcastVersion = "snapclient_0.25.0-1_win64";
|
||||||
|
private string _mpdHost = "192.168.1.13";
|
||||||
|
private int _mpdPort = 6600;
|
||||||
|
private string _mpdPassword = null;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ConnectToMPD();
|
||||||
|
DispatcherTimer timer = new DispatcherTimer();
|
||||||
|
timer.Interval = TimeSpan.FromSeconds(0.2);
|
||||||
|
timer.Tick += timer_Tick;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ConnectToMPD()
|
||||||
|
{
|
||||||
|
Trace.WriteLine("Trying to connect...");
|
||||||
|
_connected = await _mpd.MpdCommandConnectionStart(_mpdHost, _mpdPort, _mpdPassword);
|
||||||
|
if (_connected)
|
||||||
|
{
|
||||||
|
await _mpd.MpdQueryStatus();
|
||||||
|
await Task.Delay(5);
|
||||||
|
_currentVolume = _mpd.MpdStatus.MpdVolume;
|
||||||
|
_currentRandom = _mpd.MpdStatus.MpdRandom;
|
||||||
|
_currentRepeat = _mpd.MpdStatus.MpdRepeat;
|
||||||
|
_currentSingle = _mpd.MpdStatus.MpdSingle;
|
||||||
|
_currentConsume = _mpd.MpdStatus.MpdConsume;
|
||||||
|
_currentElapsed = _mpd.MpdStatus.MpdSongElapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_Tick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
LoopMPD();
|
||||||
|
UpdateInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CheckStatus<T>(ref T a, T b)
|
||||||
|
{
|
||||||
|
if (Comparer<T>.Default.Compare(a, b) != 0)
|
||||||
|
a = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void LoopMPD()
|
||||||
|
{
|
||||||
|
if (!_connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var status = await _mpd.MpdQueryStatus();
|
||||||
|
//Trace.WriteLine(status.ResultText);
|
||||||
|
await Task.Delay(5);
|
||||||
|
|
||||||
|
if (status != null)
|
||||||
|
{
|
||||||
|
CheckStatus(ref _currentVolume, _mpd.MpdStatus.MpdVolume);
|
||||||
|
CheckStatus(ref _currentRandom, _mpd.MpdStatus.MpdRandom);
|
||||||
|
CheckStatus(ref _currentRepeat, _mpd.MpdStatus.MpdRepeat);
|
||||||
|
CheckStatus(ref _currentSingle, _mpd.MpdStatus.MpdSingle);
|
||||||
|
CheckStatus(ref _currentConsume, _mpd.MpdStatus.MpdConsume);
|
||||||
|
CheckStatus(ref _currentElapsed, _mpd.MpdStatus.MpdSongElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _mpd.MpdQueryCurrentSong();
|
||||||
|
await Task.Delay(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateButton(ref Button button, bool b)
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
button.Foreground = System.Windows.SystemColors.GradientActiveCaptionBrush;
|
||||||
|
else
|
||||||
|
button.Foreground = System.Windows.SystemColors.DesktopBrush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatSeconds(double time)
|
||||||
|
{
|
||||||
|
var timespan = TimeSpan.FromSeconds(time);
|
||||||
|
return timespan.ToString(@"mm\:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateInterface()
|
||||||
|
{
|
||||||
|
if (_mpd.MpdCurrentSong != null)
|
||||||
|
{
|
||||||
|
SongTitle.Text = _mpd.MpdCurrentSong.Title;
|
||||||
|
SongTitle.ToolTip = _mpd.MpdCurrentSong.File;
|
||||||
|
SongArtist.Text = _mpd.MpdCurrentSong.Artist;
|
||||||
|
SongAlbum.Text = _mpd.MpdCurrentSong.Album + " (" + _mpd.MpdCurrentSong.Date + ")";
|
||||||
|
Bitrate.Text = _mpd.MpdCurrentSong.File.Substring(_mpd.MpdCurrentSong.File.LastIndexOf(".") + 1);
|
||||||
|
Bitrate.Text += " – ";
|
||||||
|
Bitrate.Text += _mpd.MpdStatus.MpdBitrate + "kbps";
|
||||||
|
|
||||||
|
CurrentTime.Text = FormatSeconds(_currentElapsed);
|
||||||
|
EndTime.Text = FormatSeconds(_mpd.MpdStatus.MpdSongTime);
|
||||||
|
|
||||||
|
TimeSlider.Value = _currentElapsed / _mpd.MpdCurrentSong.TimeSort * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VolumeSlider.Value != _currentVolume)
|
||||||
|
{
|
||||||
|
VolumeSlider.Value = _currentVolume;
|
||||||
|
VolumeSlider.ToolTip = _currentVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||||
|
PauseButton.Content = "⏸";
|
||||||
|
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
||||||
|
PauseButton.Content = "▶️";
|
||||||
|
|
||||||
|
if (_snapcastStarted)
|
||||||
|
Snapcast.Content = "Stop Snapcast";
|
||||||
|
else
|
||||||
|
Snapcast.Content = "Start Snapcast";
|
||||||
|
|
||||||
|
DebugText.Text = _mpd.MpdHost + ":" + _mpd.MpdPort;
|
||||||
|
|
||||||
|
UpdateButton(ref Random, _currentRandom);
|
||||||
|
UpdateButton(ref Repeat, _currentRepeat);
|
||||||
|
UpdateButton(ref Single, _currentSingle);
|
||||||
|
UpdateButton(ref Consume, _currentConsume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Pause_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Play)
|
||||||
|
await _mpd.MpdPlaybackPause();
|
||||||
|
else if (_mpd.MpdStatus.MpdState == Status.MpdPlayState.Pause)
|
||||||
|
await _mpd.MpdPlaybackPlay(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Previous_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdPlaybackPrev(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Next_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdPlaybackNext(_currentVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Random_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdSetRandom(!_currentRandom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Repeat_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdSetRepeat(!_currentRepeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Single_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdSetSingle(!_currentSingle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Consume_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await _mpd.MpdSetConsume(!_currentConsume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Snapcast_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_snapcastStarted)
|
||||||
|
{
|
||||||
|
_snapcast.StartInfo.FileName = _snapcastVersion + @"\snapclient.exe";
|
||||||
|
_snapcast.StartInfo.Arguments = "--host " + _mpd.MpdHost;
|
||||||
|
_snapcast.StartInfo.CreateNoWindow = true;
|
||||||
|
_snapcast.Start();
|
||||||
|
_snapcastStarted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_snapcast.Kill();
|
||||||
|
_snapcastStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (_snapcastStarted)
|
||||||
|
_snapcast.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
Properties/PublishProfiles/FolderProfile.pubxml
Normal file
14
Properties/PublishProfiles/FolderProfile.pubxml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<TargetFramework>net5.0-windows</TargetFramework>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
BIN
images/nocover.png
Normal file
BIN
images/nocover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
snapclient_0.25.0-1_win64/FLAC.dll
Normal file
BIN
snapclient_0.25.0-1_win64/FLAC.dll
Normal file
Binary file not shown.
BIN
snapclient_0.25.0-1_win64/ogg.dll
Normal file
BIN
snapclient_0.25.0-1_win64/ogg.dll
Normal file
Binary file not shown.
BIN
snapclient_0.25.0-1_win64/opus.dll
Normal file
BIN
snapclient_0.25.0-1_win64/opus.dll
Normal file
Binary file not shown.
BIN
snapclient_0.25.0-1_win64/snapclient.exe
Normal file
BIN
snapclient_0.25.0-1_win64/snapclient.exe
Normal file
Binary file not shown.
BIN
snapclient_0.25.0-1_win64/soxr.dll
Normal file
BIN
snapclient_0.25.0-1_win64/soxr.dll
Normal file
Binary file not shown.
BIN
snapclient_0.25.0-1_win64/vorbis.dll
Normal file
BIN
snapclient_0.25.0-1_win64/vorbis.dll
Normal file
Binary file not shown.
47
unison.csproj
Normal file
47
unison.csproj
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net5.0-windows</TargetFramework>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ApplicationIcon>unison.ico</ApplicationIcon>
|
||||||
|
<Win32Resource></Win32Resource>
|
||||||
|
<StartupObject>unison.App</StartupObject>
|
||||||
|
<Version>0.0.1</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="images\nocover.png" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\FLAC.dll" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\ogg.dll" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\opus.dll" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\snapclient.exe" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\soxr.dll" />
|
||||||
|
<None Remove="snapclient_0.25.0-1_win64\vorbis.dll" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="images\nocover.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\FLAC.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\ogg.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\opus.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\snapclient.exe">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\soxr.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="snapclient_0.25.0-1_win64\vorbis.dll">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
BIN
unison.ico
Normal file
BIN
unison.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
25
unison.sln
Normal file
25
unison.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.31515.178
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "unison", "unison.csproj", "{489048C4-3FCA-4573-B34C-943D03F94D04}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{489048C4-3FCA-4573-B34C-943D03F94D04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {12DA9EE9-38C6-4BBE-8133-6F7F30AF3A67}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
Loading…
Reference in New Issue
Block a user