PlazaSharp/WinPlaza/MainWindow.xaml.cs

290 lines
10 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Media.Imaging;
using libplaza;
using ManagedBass;
using Meziantou.Framework.Win32;
namespace WinPlaza {
public partial class MainWindow : Window {
#region StateVars
static readonly object BufferLock=new object();
private int BuffCh;
private int BufferReqs;
private int MuteVol=-1;
private int GfxWidthStep=-1;
private string LastArtwork="";
private Nightwave plaza=new Nightwave();
private Timer tRefresh;
private Timer tAudio;
private Timer tGfx;
private Timer tLogon;
private bool AudioInit=false;
private bool LikedCTrack=false;
#endregion
#region Delegates
private delegate void dgtNoParam();
private delegate void dgtBool(bool b);
private delegate void dgtStr(string s);
#endregion
#region CrossThreadCalls
private void Leveller() {
if(GfxWidthStep<0)
GfxWidthStep = 32768 / (int)chLeftCvs.ActualWidth;
chLeft.Width = Bass.ChannelGetLevelLeft(BuffCh) / GfxWidthStep;
chRight.Width = Bass.ChannelGetLevelRight(BuffCh) / GfxWidthStep;
}
private void SetStatus(string msg) =>
Dispatcher.BeginInvoke(new dgtStr(_Status), msg);
private void SetTitle(string msg) =>
Dispatcher.BeginInvoke(new dgtStr(_Title), msg);
private async void DgtStartupLogon() {
Credential c=CredentialManager.ReadCredential("Nightwave.NET");
if(c is null)
return;
if(await plaza.Login(c.UserName, c.Password)!=true)
return;
btLogin.IsEnabled = false;
btLogin.Visibility = Visibility.Hidden;
imLogin.IsEnabled = false;
imLogin.Visibility = Visibility.Hidden;
SetStatus($"Logged in as {await plaza.GetUser()}");
}
private async void DgtRefresh(bool Force = false) {
Status s=await plaza.Broadcast(Force);
if(s.FaultOccurred||s.InMaintenance) {
if(s.InMaintenance)
SetStatus("Nightwave Plaza is currently in maintenance mode.");
else
SetStatus("Ran into a problem while fetching now-playing.");
return;
}
if(s.ArtworkUri == null || s.Duration == 0) {
Console.WriteLine("why is async so hard");
throw new Exception("Something went real weird, woah. Time to Crash!");
}
slDuration.Value = s.CalculatedElapsed;
sbListeners.Text = $"{s.Listeners} listeners";
lbLikeCt.Content = s.Likes;
lbDislikeCt.Content = s.Dislikes;
lbElapsed.Content = $"{(s.CalculatedElapsed / 60).ToString("D")}:{(s.CalculatedElapsed % 60).ToString("D2")}";
if(LastArtwork != s.ArtworkUri || Force) {
lbArtist.Content = s.Artist;
lbTitle.Content = s.Title;
lbAlbum.Content = s.Album;
if(btPlayPause.IsChecked == true)
SetTitle($"{s.Title} - {s.Artist}");
slDuration.Maximum = s.Duration;
lbTime.Content = $"{(s.Duration / 60).ToString("D")}:{(s.Duration % 60).ToString("D2")}";
if(LastArtwork != s.ArtworkUri) {
if(btPlayPause.IsChecked == true)
SetStatus("Playing");
LikedCTrack = false;
art.Source = new BitmapImage(new Uri(s.ArtworkUri));
LastArtwork = s.ArtworkUri;
}
}
}
private void DgtAudio() {
long progress = Bass.StreamGetFilePosition(BuffCh, FileStreamPosition.Buffer)
* 100 / Bass.StreamGetFilePosition(BuffCh, FileStreamPosition.End);
if(progress > 75 || Bass.StreamGetFilePosition(BuffCh, FileStreamPosition.Connected) == 0) {
tAudio.Stop();
SetStatus("Playing");
SetTitle($"{lbTitle.Content} - {lbArtist.Content}");
tGfx.Start();
_ = Bass.ChannelSetSync(BuffCh, SyncFlags.MetadataReceived, 0, MetaSync);
_ = Bass.ChannelSetSync(BuffCh, SyncFlags.OggChange, 0, MetaSync);
_ = Bass.ChannelSetSync(BuffCh, SyncFlags.End, 0, EndSync);
_ = Bass.ChannelPlay(BuffCh);
} else
SetStatus($"Buffering... {progress}%");
}
#endregion
#region PlaybackControllers
private void LoadUri(string uri) => Task.Factory.StartNew(() => {
int r;
lock(BufferLock)
r = ++BufferReqs;
tAudio.Stop();
_ = Bass.StreamFree(BuffCh);
SetStatus("Connecting stream..");
int stream = Bass.CreateStream(uri, 0, BassFlags.StreamDownloadBlocks | BassFlags.StreamStatus | BassFlags.AutoFree, StatusProc, new IntPtr(r));
lock(BufferLock) {
if(r != BufferReqs) {
if(stream != 0)
_ = Bass.StreamFree(stream);
}
BuffCh = stream;
}
if(BuffCh == 0)
SetStatus("Unable to connect to stream");
else
tAudio.Start();
});
private void PlazaLoad() => LoadUri("https://radio.plaza.one/ogg");
private void PlazaStop() {
tGfx.Stop();
lock(BufferLock) {
_ = Bass.StreamFree(BufferReqs);
_ = Bass.StreamFree(BuffCh);
_ = Bass.ChannelStop(BufferReqs);
_ = Bass.ChannelStop(BuffCh);
tAudio.Stop();
SetStatus("Idle..");
SetTitle("Nightwave.Net");
}
}
#endregion
#region EventHandlers
private void EndSync(int hdl, int ch, int dat, IntPtr usr) {
SetStatus("Idle..");
SetTitle("Nightwave.Net");
}
private void MetaSync(int hdl, int ch, int dat, IntPtr usr) =>
Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), true);
private void StatusProc(IntPtr buf, int len, IntPtr usr) {
if(buf != IntPtr.Zero && len == 0 && usr.ToInt32() == BufferReqs)
SetStatus(Marshal.PtrToStringAnsi(buf));
}
private async void EvtRefresh(object sender, ElapsedEventArgs e) =>
await Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), false);
private async void EvtLogon(object sender, ElapsedEventArgs e) =>
await Dispatcher.BeginInvoke(new dgtNoParam(DgtStartupLogon));
private void EvtAudio(object sender, ElapsedEventArgs e) =>
_ = Dispatcher.BeginInvoke(new dgtNoParam(DgtAudio));
private async void EvtRender(object sender, ElapsedEventArgs e) =>
await Dispatcher.BeginInvoke(new dgtNoParam(Leveller));
private void BtPlayPause_Click(object sender, RoutedEventArgs e) {
if(!AudioInit)
return;
if(btPlayPause.IsChecked == true)
_ = Dispatcher.BeginInvoke(new dgtNoParam(PlazaLoad));
else
_ = Dispatcher.BeginInvoke(new dgtNoParam(PlazaStop));
}
private void SbMute_Click(object sender, RoutedEventArgs e) {
if(MuteVol<0) {
MuteVol = Bass.GlobalStreamVolume;
Bass.GlobalStreamVolume = 0;
sbMute.Content = "🔇";
tbMute.ImageSource = (BitmapImage)FindResource("tbMuted");
} else {
Bass.GlobalStreamVolume = MuteVol;
MuteVol = -1;
sbMute.Content = "🔊";
tbMute.ImageSource = (BitmapImage)FindResource("tbAudible");
}
}
private void SbVol_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) {
sbVol.Value += e.Delta / 25;
e.Handled = true;
}
private void SbVol_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
if(MuteVol >= 0)
sbMute.IsChecked = false;
Bass.GlobalStreamVolume = (int)sbVol.Value * 100;
}
private async void BtLogin_Click(object sender, RoutedEventArgs e) {
CredentialResult _c=CredentialManager.PromptForCredentials(messageText:"Please log into your Nightwave Plaza account", captionText:"Log into plaza.one");
if(_c is null)
return;
CredentialManager.WriteCredential("Nightwave.NET", _c.UserName, _c.Password, CredentialPersistence.Enterprise);
Credential c=CredentialManager.ReadCredential("Nightwave.NET");
if(c is null)
throw new Exception("what the fuck");
Console.WriteLine($"did login work? {await plaza.Login(c.UserName, c.Password)}");
Console.WriteLine($"can validate? {await plaza.CheckSession()}");
#if DEBUG
Console.WriteLine($"token is {plaza.GetAuthorizationToken()}");
#endif
btLogin.IsEnabled = false;
btLogin.Visibility = Visibility.Hidden;
imLogin.IsEnabled = false;
imLogin.Visibility = Visibility.Hidden;
}
private async void BtLike_Click(object sender, RoutedEventArgs e) {
Nightwave.Vote v = Nightwave.Vote.Like;
if(LikedCTrack)
v = Nightwave.Vote.Favourite;
if(await plaza.CastVote(v)) {
SetStatus($"Submitted {v}!");
LikedCTrack = !LikedCTrack; //This should allow "un-favouriting"
} else
SetStatus($"Something went weird while casting {v}");
_ = Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), true);
}
private async void BtDislike_Click(object sender, RoutedEventArgs e) {
if(await plaza.CastVote(Nightwave.Vote.Dislike))
SetStatus("Song disliked!");
else
SetStatus("Something went weird while disliking");
LikedCTrack = false;
_ = Dispatcher.BeginInvoke(new dgtBool(DgtRefresh), true);
}
private void SbOnTop_Click(object sender, RoutedEventArgs e) {
Topmost = sbOnTop.IsChecked == true;
if(tGfx.Enabled)
tGfx.Interval = 1000.0 / 60.0;
}
private void TbPlayPause_Click(object sender, EventArgs e) =>
btPlayPause.IsChecked = !btPlayPause.IsChecked;
private void TbMute_Click(object sender, EventArgs e) =>
sbMute.IsChecked = !sbMute.IsChecked;
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
_ = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
tRefresh.Stop();
if(AudioInit) {
tAudio.Stop();
tGfx.Stop();
PlazaStop();
}
}
private void Window_Deactivated(object sender, EventArgs e) {
if(tGfx.Enabled && !Topmost)
tGfx.Interval = 1000.0 / 25.0;
}
private void Window_Activated(object sender, EventArgs e) {
if(tGfx.Enabled)
tGfx.Interval = 1000.0 / 60.0;
}
#endregion
#region GUIMethods
public MainWindow() {
InitializeComponent();
tRefresh = new Timer(300) { AutoReset = true };
tRefresh.Elapsed += EvtRefresh;
tRefresh.Start();
tLogon = new Timer(1000) { AutoReset = false };
tLogon.Elapsed += EvtLogon;
tLogon.Start();
if(!Bass.Init())
sbStatus.Text = "Unable to initialise audio subsystem";
else
AudioInit = true;
if(!AudioInit)
return;
tAudio = new Timer(50);
tAudio.Elapsed += EvtAudio;
tGfx = new Timer(16) { AutoReset = true };
tGfx.Elapsed += EvtRender;
Bass.NetPlaylist = 1;
Bass.NetPreBuffer = 0;
}
~MainWindow() {
if(AudioInit)
_ = Bass.Free();
}
private void _Status(string s) => sbStatus.Text = s;
private void _Title(string s) => Title = s;
#endregion
}
}