2015-10-16 23:44:35 +01:00
using SpotifyAPI.Local.Models ;
using System ;
2015-07-07 17:11:11 +01:00
using System.ComponentModel ;
using System.Diagnostics ;
using System.IO ;
using System.Runtime.InteropServices ;
2016-09-09 13:56:37 +01:00
using System.Threading.Tasks ;
2015-07-07 17:11:11 +01:00
using System.Timers ;
namespace SpotifyAPI.Local
{
2016-07-22 20:38:58 +01:00
public class SpotifyLocalAPI : IDisposable
2015-07-07 17:11:11 +01:00
{
[DllImport("user32.dll")]
private static extern void keybd_event ( byte bVk , byte bScan , uint dwFlags , int dwExtraInfo ) ;
2015-10-16 23:44:35 +01:00
2015-07-07 17:11:11 +01:00
private bool _listenForEvents ;
2015-10-16 23:44:35 +01:00
2015-07-07 17:11:11 +01:00
public bool ListenForEvents
{
get
{
return _listenForEvents ;
}
set
{
_listenForEvents = value ;
_eventTimer . Enabled = value ;
}
}
private ISynchronizeInvoke _synchronizingObject ;
2015-10-16 23:44:35 +01:00
2015-07-07 17:11:11 +01:00
public ISynchronizeInvoke SynchronizingObject
{
get
{
return _synchronizingObject ;
}
set
{
_synchronizingObject = value ;
_eventTimer . SynchronizingObject = value ;
}
}
2017-06-03 23:15:56 +01:00
private const int WindowsSevenMajorVersion = 6 ;
private const int WindowsSevenMinorVersion = 1 ;
2015-10-16 23:44:35 +01:00
private const byte VkMediaNextTrack = 0xb0 ;
private const byte VkMediaPrevTrack = 0xb1 ;
private const int KeyeventfExtendedkey = 0x1 ;
private const int KeyeventfKeyup = 0x2 ;
2015-07-07 17:11:11 +01:00
2015-10-16 23:44:35 +01:00
private readonly RemoteHandler _rh ;
2016-07-22 20:38:58 +01:00
private Timer _eventTimer ;
2015-07-07 17:11:11 +01:00
private StatusResponse _eventStatusResponse ;
2016-01-07 08:23:58 +00:00
public event EventHandler < TrackChangeEventArgs > OnTrackChange ;
2015-10-16 23:44:35 +01:00
2016-01-07 08:23:58 +00:00
public event EventHandler < PlayStateEventArgs > OnPlayStateChange ;
2015-10-16 23:44:35 +01:00
2016-01-07 08:23:58 +00:00
public event EventHandler < VolumeChangeEventArgs > OnVolumeChange ;
2015-10-16 23:44:35 +01:00
2016-01-07 08:23:58 +00:00
public event EventHandler < TrackTimeChangeEventArgs > OnTrackTimeChange ;
2015-07-07 17:11:11 +01:00
2016-07-29 23:19:57 +01:00
public SpotifyLocalAPI ( int timerIntervall = 50 )
2015-07-07 17:11:11 +01:00
{
2017-09-03 13:44:11 +01:00
_rh = new RemoteHandler ( new SpotifyLocalAPIConfig ( ) ) ;
2016-07-22 20:38:58 +01:00
AttachTimer ( timerIntervall ) ;
}
2017-09-03 13:44:11 +01:00
public SpotifyLocalAPI ( SpotifyLocalAPIConfig config )
{
_rh = new RemoteHandler ( config ) ;
AttachTimer ( config . TimerInterval ) ;
}
2016-07-22 20:38:58 +01:00
private void AttachTimer ( int intervall )
{
2015-07-07 17:11:11 +01:00
_eventTimer = new Timer
{
2016-07-22 20:38:58 +01:00
Interval = intervall ,
2015-07-07 17:11:11 +01:00
AutoReset = false ,
Enabled = false
} ;
_eventTimer . Elapsed + = ElapsedTick ;
}
private void ElapsedTick ( object sender , ElapsedEventArgs e )
{
if ( _eventStatusResponse = = null )
{
_eventStatusResponse = GetStatus ( ) ;
_eventTimer . Start ( ) ;
return ;
}
StatusResponse newStatusResponse = GetStatus ( ) ;
if ( newStatusResponse = = null )
{
_eventTimer . Start ( ) ;
return ;
}
if ( ! newStatusResponse . Running & & newStatusResponse . Track = = null )
{
_eventTimer . Start ( ) ;
return ;
}
if ( newStatusResponse . Track ! = null & & _eventStatusResponse . Track ! = null )
{
2016-08-28 21:35:47 +01:00
if ( newStatusResponse . Track . TrackResource ? . Uri ! = _eventStatusResponse . Track . TrackResource ? . Uri )
2015-07-07 17:11:11 +01:00
{
2016-01-07 08:23:58 +00:00
OnTrackChange ? . Invoke ( this , new TrackChangeEventArgs ( )
2015-07-07 17:11:11 +01:00
{
OldTrack = _eventStatusResponse . Track ,
NewTrack = newStatusResponse . Track
} ) ;
}
}
2015-10-01 16:28:40 +01:00
if ( newStatusResponse . Playing ! = _eventStatusResponse . Playing )
2015-07-07 17:11:11 +01:00
{
2016-01-07 08:23:58 +00:00
OnPlayStateChange ? . Invoke ( this , new PlayStateEventArgs ( )
2015-07-07 17:11:11 +01:00
{
Playing = newStatusResponse . Playing
} ) ;
}
2015-10-01 16:28:40 +01:00
if ( newStatusResponse . Volume ! = _eventStatusResponse . Volume )
2015-07-07 17:11:11 +01:00
{
2016-01-07 08:23:58 +00:00
OnVolumeChange ? . Invoke ( this , new VolumeChangeEventArgs ( )
2015-07-07 17:11:11 +01:00
{
OldVolume = _eventStatusResponse . Volume ,
NewVolume = newStatusResponse . Volume
} ) ;
}
2015-10-01 16:28:40 +01:00
if ( newStatusResponse . PlayingPosition ! = _eventStatusResponse . PlayingPosition )
2015-07-07 17:11:11 +01:00
{
2016-01-07 08:23:58 +00:00
OnTrackTimeChange ? . Invoke ( this , new TrackTimeChangeEventArgs ( )
2015-07-07 17:11:11 +01:00
{
TrackTime = newStatusResponse . PlayingPosition
} ) ;
}
_eventStatusResponse = newStatusResponse ;
_eventTimer . Start ( ) ;
}
2017-06-03 23:15:56 +01:00
private bool IsOSCompatible ( int minMajor , int minMinor )
{
return Environment . OSVersion . Version . Major > minMajor | | ( Environment . OSVersion . Version . Major = = minMajor & & Environment . OSVersion . Version . Minor > = minMinor ) ;
}
2015-07-07 17:11:11 +01:00
/// <summary>
/// Connects with Spotify. Needs to be called before all other SpotifyAPI functions
/// </summary>
/// <returns>Returns true, if it was successful, false if not</returns>
public Boolean Connect ( )
{
return _rh . Init ( ) ;
}
2015-10-15 13:58:23 +01:00
/// <summary>
/// Update and returns the new StatusResponse from the Spotify-Player
/// </summary>
/// <returns>An up-to-date StatusResponse</returns>
2015-07-07 17:11:11 +01:00
public StatusResponse GetStatus ( )
{
return _rh . GetNewStatus ( ) ;
}
2015-10-15 13:58:23 +01:00
/// <summary>
2015-10-17 10:55:27 +01:00
/// Mutes Spotify (Requires Windows 7 or newer)
2015-10-15 13:58:23 +01:00
/// </summary>
2015-07-07 17:11:11 +01:00
public void Mute ( )
{
2017-06-03 23:15:56 +01:00
if ( ! IsOSCompatible ( WindowsSevenMajorVersion , WindowsSevenMinorVersion ) )
2015-10-23 20:25:13 +01:00
throw new NotSupportedException ( "This feature is only available on Windows 7 or newer" ) ;
2015-10-17 10:55:27 +01:00
VolumeMixerControl . MuteSpotify ( true ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
2015-10-17 10:55:27 +01:00
/// Unmutes Spotify (Requires Windows 7 or newer)
2015-07-07 17:11:11 +01:00
/// </summary>
public void UnMute ( )
{
2017-06-03 23:15:56 +01:00
if ( ! IsOSCompatible ( WindowsSevenMajorVersion , WindowsSevenMinorVersion ) )
2015-10-23 20:25:13 +01:00
throw new NotSupportedException ( "This feature is only available on Windows 7 or newer" ) ;
2015-10-17 10:55:27 +01:00
VolumeMixerControl . MuteSpotify ( false ) ;
}
/// <summary>
2015-10-17 10:58:38 +01:00
/// Checks whether Spotify is muted in the Volume Mixer control (required Windows 7 or newer)
2015-10-17 10:55:27 +01:00
/// </summary>
/// <returns>Null if an error occured, otherwise the muted state</returns>
2015-10-23 20:25:13 +01:00
public bool IsSpotifyMuted ( )
2015-10-17 10:55:27 +01:00
{
2017-06-03 23:15:56 +01:00
if ( ! IsOSCompatible ( WindowsSevenMajorVersion , WindowsSevenMinorVersion ) )
2015-10-23 20:25:13 +01:00
throw new NotSupportedException ( "This feature is only available on Windows 7 or newer" ) ;
2015-10-17 10:55:27 +01:00
return VolumeMixerControl . IsSpotifyMuted ( ) ;
}
/// <summary>
/// Sets the Volume Mixer volume (requires Windows 7 or newer)
/// </summary>
2015-10-21 12:18:57 +01:00
/// <param name="volume">A value between 0 and 100</param>
2015-10-17 10:55:27 +01:00
public void SetSpotifyVolume ( float volume = 100 )
{
2017-06-03 23:15:56 +01:00
if ( ! IsOSCompatible ( WindowsSevenMajorVersion , WindowsSevenMinorVersion ) )
2015-10-23 20:25:13 +01:00
throw new NotSupportedException ( "This feature is only available on Windows 7 or newer" ) ;
if ( volume < 0 | | volume > 100 )
2015-10-28 16:05:09 +00:00
throw new ArgumentOutOfRangeException ( nameof ( volume ) ) ;
2015-10-17 10:55:27 +01:00
VolumeMixerControl . SetSpotifyVolume ( volume ) ;
}
/// <summary>
/// Return the Volume Mixer volume of Spotify (requires Windows 7 or newer)
/// </summary>
/// <returns>Null if an error occured, otherwise a float between 0 and 100</returns>
2015-10-23 20:25:13 +01:00
public float GetSpotifyVolume ( )
2015-10-17 10:55:27 +01:00
{
2017-06-03 23:15:56 +01:00
if ( ! IsOSCompatible ( WindowsSevenMajorVersion , WindowsSevenMinorVersion ) )
2015-10-23 20:25:13 +01:00
throw new NotSupportedException ( "This feature is only available on Windows 7 or newer" ) ;
2015-10-17 10:55:27 +01:00
return VolumeMixerControl . GetSpotifyVolume ( ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Pause function
/// </summary>
2016-09-09 13:56:37 +01:00
public async Task Pause ( )
2015-07-07 17:11:11 +01:00
{
2016-09-09 13:56:37 +01:00
await _rh . SendPauseRequest ( ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Play function
/// </summary>
2016-09-09 13:56:37 +01:00
public async Task Play ( )
2015-07-07 17:11:11 +01:00
{
2016-09-09 13:56:37 +01:00
await _rh . SendPlayRequest ( ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Simulates a KeyPress
/// </summary>
/// <param name="keyCode">The keycode for the represented Key</param>
internal void PressKey ( byte keyCode )
{
keybd_event ( keyCode , 0x45 , KeyeventfExtendedkey , 0 ) ;
keybd_event ( keyCode , 0x45 , KeyeventfExtendedkey | KeyeventfKeyup , 0 ) ;
}
/// <summary>
/// Plays a Spotify URI within an optional context.
/// </summary>
/// <param name="uri">The Spotify URI</param>
/// <param name="context">The context in which to play the specified <paramref name="uri"/>. </param>
/// <remarks>
/// Contexts are basically a queue in spotify. a song can be played within a context, meaning that hitting next / previous would lead to another song. Contexts are leveraged by widgets as described in the "Multiple tracks player" section of the following documentation page: https://developer.spotify.com/technologies/widgets/spotify-play-button/
/// </remarks>
2016-09-09 13:56:37 +01:00
public async Task PlayURL ( string uri , string context = "" )
2015-07-07 17:11:11 +01:00
{
2016-09-09 13:56:37 +01:00
await _rh . SendPlayRequest ( uri , context ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Adds a Spotify URI to the Queue
/// </summary>
/// <param name="uri">The Spotify URI</param>
2015-07-15 16:41:44 +01:00
[Obsolete("This method doesn't work with the current spotify version.")]
2016-09-09 13:56:37 +01:00
public async Task AddToQueue ( string uri )
2015-07-07 17:11:11 +01:00
{
2016-09-09 13:56:37 +01:00
await _rh . SendQueueRequest ( uri ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Skips the current song (Using keypress simulation)
/// </summary>
public void Skip ( )
{
PressKey ( VkMediaNextTrack ) ;
}
/// <summary>
/// Emulates the "Previous" Key (Using keypress simulation)
/// </summary>
public void Previous ( )
{
PressKey ( VkMediaPrevTrack ) ;
}
/// <summary>
/// Checks if Spotify is running
/// </summary>
/// <returns>True, if it's running, false if not</returns>
public static Boolean IsSpotifyRunning ( )
{
return Process . GetProcessesByName ( "spotify" ) . Length > = 1 ;
}
/// <summary>
/// Checks if Spotify's WebHelper is running (Needed for API Calls)
/// </summary>
/// <returns>True, if it's running, false if not</returns>
public static Boolean IsSpotifyWebHelperRunning ( )
{
return Process . GetProcessesByName ( "spotifywebhelper" ) . Length > = 1 ;
}
2017-11-09 14:37:10 +00:00
/// <summary>
/// Determines whether [spotify is installed].
/// </summary>
/// <returns>
/// <c>true</c> if [spotify is installed]; otherwise, <c>false</c>.
/// </returns>
public static bool IsSpotifyInstalled ( )
{
bool isInstalled = false ;
// Checks if UWP Spotify is installed.
string uwpSpotifyPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , @"Packages\SpotifyAB.SpotifyMusic_zpdnekdrzrea0" ) ;
isInstalled = Directory . Exists ( uwpSpotifyPath ) ;
// If UWP Spotify is not installed, try look for desktop version
if ( ! isInstalled )
{
string desktopSpotifyPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"Spotify\Spotify.exe" ) ;
isInstalled = File . Exists ( desktopSpotifyPath ) ;
}
return isInstalled ;
}
2015-07-07 17:11:11 +01:00
/// <summary>
/// Runs Spotify
/// </summary>
public static void RunSpotify ( )
{
2015-10-16 12:54:39 +01:00
if ( ! IsSpotifyRunning ( ) & & File . Exists ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\spotify.exe" ) ) )
2015-08-02 22:02:37 +01:00
Process . Start ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\spotify.exe" ) ) ;
2015-07-07 17:11:11 +01:00
}
/// <summary>
/// Runs Spotify's WebHelper
/// </summary>
public static void RunSpotifyWebHelper ( )
{
2015-10-16 12:54:39 +01:00
if ( ! IsSpotifyWebHelperRunning ( ) & & File . Exists ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\data\spotifywebhelper.exe" ) ) )
{
2015-08-02 22:02:37 +01:00
Process . Start ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\data\spotifywebhelper.exe" ) ) ;
2015-10-16 12:54:39 +01:00
}
else if ( ! IsSpotifyWebHelperRunning ( ) & & File . Exists ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\spotifywebhelper.exe" ) ) )
{
Process . Start ( Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"spotify\spotifywebhelper.exe" ) ) ;
}
2015-07-07 17:11:11 +01:00
}
2016-07-22 20:38:58 +01:00
public void Dispose ( )
{
if ( _eventTimer = = null )
return ;
_eventTimer . Enabled = false ;
_eventTimer . Elapsed - = ElapsedTick ;
}
2015-07-07 17:11:11 +01:00
}
2015-10-16 23:44:35 +01:00
}