Skip to content

Commit

Permalink
Update deps, add missed TinYard (#37)
Browse files Browse the repository at this point in the history
Updated the dependency, added the missing TinYard implementation.. whoops!
  • Loading branch information
KieranBond authored Sep 20, 2021
1 parent ebe66aa commit 01bdd92
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 2 deletions.
2 changes: 1 addition & 1 deletion MiniSpotify/MiniSpotify/MiniSpotify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
<Version>13.0.1</Version>
</PackageReference>
<PackageReference Include="SpotifyAPI.Web.Auth">
<Version>6.1.0</Version>
<Version>6.2.1</Version>
</PackageReference>
<PackageReference Include="TinYard">
<Version>1.3.1</Version>
Expand Down
44 changes: 44 additions & 0 deletions MiniSpotify/MiniSpotify/MiniSpotifyConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using MiniSpotify.Source.Impl;
using MiniSpotify.Source.Interfaces;
using System;
using TinYard.API.Interfaces;
using TinYard.Framework.Impl.Attributes;

namespace MiniSpotify
{
public class MiniSpotifyConfig : IConfig
{
[Inject]
public IContext context;

public object Environment => null;

private SpotifyService _spotifyService;

private string _clientID = "93f2598a9eaf4056b34f7b5ca254ff17";

private event Action _onServiceConnected;

public void Configure()
{
_spotifyService = new SpotifyService(_clientID);
context.Mapper.Map<ISpotifyService>().ToValue(_spotifyService);

context.PostInitialize += OnContextInitialized;
}

private async void OnContextInitialized()
{
await _spotifyService.Connect();
_onServiceConnected.Invoke();
_spotifyService.SetupUpdate();
}

public MiniSpotifyConfig OnServiceConnected(Action callback)
{
_onServiceConnected += callback;

return this;
}
}
}
315 changes: 315 additions & 0 deletions MiniSpotify/MiniSpotify/Source/Impl/SpotifyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
using MiniSpotify.HelperScripts;
using MiniSpotify.Source.Interfaces;
using MiniSpotify.Source.VO;
using SpotifyAPI.Web;
using SpotifyAPI.Web.Auth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TinYard.Extensions.CallbackTimer.API.Services;
using TinYard.Framework.Impl.Attributes;

namespace MiniSpotify.Source.Impl
{
public class SpotifyService : ISpotifyService
{
[Inject]
public ICallbackTimer CallbackTimer { get; private set; }

public event Action<ContextualUpdateVO> OnPlaybackUpdated;

private SpotifyClient _spotifyClient;
private EmbedIOAuthServer _server;
private string _authToken;
private string _clientID;

//https://developer.spotify.com/documentation/general/guides/scopes/
private string[] _accessScopes = new string[]
{
Scopes.UserModifyPlaybackState,
Scopes.Streaming,
Scopes.UserReadRecentlyPlayed,
Scopes.UserReadCurrentlyPlaying,
Scopes.UserReadPlaybackState,
Scopes.UserLibraryModify,
Scopes.UserLibraryRead
};

private double _updateInterval = 0.5d;

public SpotifyService(string clientID)
{
_clientID = clientID;
}

public async Task<bool> Connect()
{
if (!string.IsNullOrWhiteSpace(_authToken))
return false;

// Information:
// https://github.com/JohnnyCrazy/SpotifyAPI-NET
// https://johnnycrazy.github.io/SpotifyAPI-NET/auth/implicit_grant.html

int redirectPort = 4002;
string redirectURI = $"http://localhost:{redirectPort}";

_server = new EmbedIOAuthServer(new Uri(redirectURI), redirectPort);
await _server.Start();

var auth = new LoginRequest(
new Uri(redirectURI),
_clientID,
LoginRequest.ResponseType.Token)
{
Scope = _accessScopes,
};

BrowserUtil.Open(auth.ToUri());

await AwaitImplicitGrantReceived();

return true;
}

public void SetupUpdate()
{
CallbackTimer.AddRecurringTimer(_updateInterval, Update);
}

private async Task AwaitImplicitGrantReceived()
{
bool grantReceived = false;

_server.ImplictGrantReceived += async (sender, response) =>
{
await _server.Stop();
_authToken = response.AccessToken;
_spotifyClient = new SpotifyClient(_authToken);
grantReceived = true;
// Trigger a UI catchup
UpdatePlayback();
};

while (!grantReceived)
{
await Task.Delay(300);
}
}

private void Update()
{
if (_spotifyClient == null)
return;

UpdatePlayback();
}

public void Disconnect()
{
_server?.Dispose();
}

private async void UpdatePlayback()
{
var currentPlayback = await GetCurrentPlayback();

float songProgress = currentPlayback != null ? currentPlayback.ProgressMs : 0f;
FullTrack latestSong;
if (currentPlayback == null || currentPlayback.Item == null)
latestSong = await GetLastPlayedSong();
else
latestSong = currentPlayback.Item as FullTrack;

if (latestSong == null)
return;

bool songLiked = await IsSongLiked(latestSong);
bool isSongPlaying = await IsSongPlaying(latestSong);
string artworkURL = GetSongArtworkUrl(latestSong);
string playingContext = await GetPlayingContext();
songProgress = LerpEaser.GetLerpT(LerpEaser.EaseType.Linear, songProgress, latestSong.DurationMs);// Normalize
ContextualUpdateVO updateVO = new ContextualUpdateVO(latestSong, artworkURL, playingContext, songLiked, isSongPlaying, songProgress);

OnPlaybackUpdated.Invoke(updateVO);
}

private async Task<string> GetPlayingContext()
{
var playbackContext = await GetCurrentPlayback();
if (playbackContext == null || playbackContext.Context == null)
return string.Empty;

string id = playbackContext.Context?.Uri.Split(':').Last();
switch (playbackContext.Context.Type)
{
case "album":
return (await _spotifyClient.Albums.Get(id)).Name;
case "artist":
return (await _spotifyClient.Artists.Get(id)).Name;
case "playlist":
return (await _spotifyClient.Playlists.Get(id)).Name;
default:
return string.Empty;
}
}

private async Task<FullTrack> GetLastPlayedSong()
{
var currentlyPlaying = await GetCurrentPlayback();
if(currentlyPlaying != null && currentlyPlaying.IsPlaying)
{
return currentlyPlaying.Item as FullTrack;
}

var recentlyPlayed = await _spotifyClient.Player.GetRecentlyPlayed();
var historyItem = recentlyPlayed.Items?[0];

if (historyItem == null)
return null;

return await _spotifyClient.Tracks.Get(historyItem.Track.Id);
}

public async Task<CurrentlyPlayingContext> GetCurrentPlayback()
{
CurrentlyPlayingContext currentPlayback;
try
{
currentPlayback = await _spotifyClient.Player.GetCurrentPlayback();
}
catch
{
// TODO : Add error handling / logging
return null;
}

if (currentPlayback == null || currentPlayback.Item == null)
return null;

return currentPlayback;
}

public async Task<bool> IsSongLiked(FullTrack song)
{
try
{
var likedTracks = await _spotifyClient.Library.GetTracks();
var fullPlaylist = await _spotifyClient.PaginateAll(likedTracks);

return fullPlaylist.Any(track => track.Track.Id == song.Id);
}
catch
{
return false;
}
}

public async Task<bool> IsSongPlaying(FullTrack song)
{
var playback = await GetCurrentPlayback();
if (playback == null || !playback.IsPlaying)
return false;

if (!(playback.Item is FullTrack))
return false;

return (playback.Item as FullTrack).Id == song.Id;
}

public string GetSongArtworkUrl(FullTrack song)
{
return song.Album.Images[0].Url;
}

public async Task<float> GetCurrentSongProgress()
{
var playback = await GetCurrentPlayback();
if (playback == null)
return 0f;

return playback.ProgressMs;
}

public async Task<bool> ToggleLikeCurrentSong()
{
var currentSong = await GetLastPlayedSong();
try
{
var tracksToModify = new List<string>() { currentSong.Id };
if(await IsSongLiked(currentSong))
{
await _spotifyClient.Library.RemoveTracks(new LibraryRemoveTracksRequest(tracksToModify));
}
else
{
await _spotifyClient.Library.SaveTracks(new LibrarySaveTracksRequest(tracksToModify));
}

return await IsSongLiked(currentSong);
}
catch
{
return false;
}
}

public async Task<bool> TogglePlayingStatus()
{
var playbackContext = await GetCurrentPlayback();
try
{
if (playbackContext.IsPlaying)
await _spotifyClient.Player.PausePlayback();
else
await _spotifyClient.Player.ResumePlayback();
return (await GetCurrentPlayback()).IsPlaying;
}
catch
{
return false;
}
}

public async void PlayNextSong()
{
try
{
await _spotifyClient.Player.SkipNext();
}
catch
{
// TODO : Log something
}
}

public async void PlayPreviousSong()
{
try
{
await _spotifyClient.Player.SkipPrevious();
}
catch
{
// TODO : Log something
}
}

public async void RestartCurrentSong()
{
try
{
await _spotifyClient.Player.SeekTo(new PlayerSeekToRequest(0));
}
catch
{

}
}
}
}
27 changes: 27 additions & 0 deletions MiniSpotify/MiniSpotify/Source/Interfaces/ISpotifyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using MiniSpotify.Source.VO;
using SpotifyAPI.Web;
using System;
using System.Threading.Tasks;

namespace MiniSpotify.Source.Interfaces
{
public interface ISpotifyService
{
event Action<ContextualUpdateVO> OnPlaybackUpdated;

Task<bool> Connect();
void Disconnect();

Task<CurrentlyPlayingContext> GetCurrentPlayback();
string GetSongArtworkUrl(FullTrack song);
Task<float> GetCurrentSongProgress();
Task<bool> IsSongLiked(FullTrack song);
Task<bool> IsSongPlaying(FullTrack song);

Task<bool> ToggleLikeCurrentSong();
Task<bool> TogglePlayingStatus();
void PlayNextSong();
void PlayPreviousSong();
void RestartCurrentSong();
}
}
Loading

0 comments on commit 01bdd92

Please sign in to comment.