adding dummies for faking a spotify connection

This commit is contained in:
Andy Pack 2023-01-25 20:52:59 +00:00
parent 7037dce2da
commit 6ae7994610
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
10 changed files with 348 additions and 40 deletions

View File

@ -138,7 +138,7 @@ namespace Selector.CLI
if (CacheWriterFactory is not null) consumers.Add(await CacheWriterFactory.Get()); if (CacheWriterFactory is not null) consumers.Add(await CacheWriterFactory.Get());
if (PublisherFactory is not null) consumers.Add(await PublisherFactory.Get()); if (PublisherFactory is not null) consumers.Add(await PublisherFactory.Get());
if (MappingPersisterFactory is not null) consumers.Add(await MappingPersisterFactory.Get()); if (MappingPersisterFactory is not null && !Magic.Dummy) consumers.Add(await MappingPersisterFactory.Get());
if (UserEventFirerFactory is not null) consumers.Add(await UserEventFirerFactory.Get()); if (UserEventFirerFactory is not null) consumers.Add(await UserEventFirerFactory.Get());

View File

@ -24,15 +24,25 @@ namespace Selector.Cache
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null) public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
{ {
var config = await spotifyFactory.GetConfig(); if (!Magic.Dummy)
var client = new SpotifyClient(config); {
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);
return new CachingAudioFeatureInjector( return new CachingAudioFeatureInjector(
watcher, watcher,
Db, Db,
client.Tracks, client.Tracks,
LoggerFactory.CreateLogger<CachingAudioFeatureInjector>() LoggerFactory.CreateLogger<CachingAudioFeatureInjector>()
); );
}
else
{
return new DummyAudioFeatureInjector(
watcher,
LoggerFactory.CreateLogger<DummyAudioFeatureInjector>()
);
}
} }
} }
} }

View File

@ -27,7 +27,7 @@ namespace Selector.Cache
var track = await Cache?.StringGetAsync(Key.AudioFeature(trackId)); var track = await Cache?.StringGetAsync(Key.AudioFeature(trackId));
if (Cache is null || track == RedisValue.Null) if (Cache is null || track == RedisValue.Null)
{ {
if(!string.IsNullOrWhiteSpace(refreshToken)) if(!string.IsNullOrWhiteSpace(refreshToken) && !Magic.Dummy)
{ {
var factory = await SpotifyFactory.GetFactory(refreshToken); var factory = await SpotifyFactory.GetFactory(refreshToken);
var spotifyClient = new SpotifyClient(await factory.GetConfig()); var spotifyClient = new SpotifyClient(await factory.GetConfig());

View File

@ -49,7 +49,7 @@ namespace Selector
}, CancelToken); }, CancelToken);
} }
public async Task AsyncCallback(ListeningChangeEventArgs e) public virtual async Task AsyncCallback(ListeningChangeEventArgs e)
{ {
using var scope = Logger.GetListeningEventArgsScope(e); using var scope = Logger.GetListeningEventArgsScope(e);

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using SpotifyAPI.Web;
namespace Selector
{
public class DummyAudioFeatureInjector : AudioFeatureInjector
{
private TrackAudioFeatures[] _features = new[]
{
new TrackAudioFeatures
{
Acousticness = 0.5f,
Danceability = 0.5f,
DurationMs = 100,
Energy = 0.5f,
Instrumentalness = 0.5f,
Key = 5,
Liveness = 0.5f,
Loudness = 0.5f,
Mode = 1,
Speechiness = 0.5f,
Tempo = 120f,
TimeSignature = 4,
Valence = 0.5f,
}
};
private int _contextIdx = 0;
private DateTime _lastNext = DateTime.UtcNow;
private TimeSpan _contextLifespan = TimeSpan.FromSeconds(30);
public DummyAudioFeatureInjector(
IPlayerWatcher watcher,
ILogger<DummyAudioFeatureInjector> logger = null,
CancellationToken token = default
): base (watcher, null, logger, token)
{
}
private bool ShouldCycle() => DateTime.UtcNow - _lastNext > _contextLifespan;
private void BumpContextIndex()
{
Logger.LogDebug("Next feature");
_contextIdx++;
if (_contextIdx >= _features.Length)
{
_contextIdx = 0;
}
}
private TrackAudioFeatures GetFeature()
{
if (ShouldCycle()) BumpContextIndex();
return _features[_contextIdx];
}
public override Task AsyncCallback(ListeningChangeEventArgs e)
{
using var scope = Logger.GetListeningEventArgsScope(e);
if (e.Current.Item is FullTrack track)
{
if (string.IsNullOrWhiteSpace(track.Id)) return Task.CompletedTask;
var audioFeatures = GetFeature();
var analysedTrack = AnalysedTrack.From(track, audioFeatures);
Timeline.Add(analysedTrack, DateHelper.FromUnixMilli(e.Current.Timestamp));
OnNewFeature(analysedTrack);
}
else if (e.Current.Item is FullEpisode episode)
{
if (string.IsNullOrWhiteSpace(episode.Id)) return Task.CompletedTask;
Logger.LogDebug("Ignoring podcast episdoe [{episode}]", episode.DisplayString());
}
else if (e.Current.Item is null)
{
Logger.LogDebug("Skipping audio feature pulling for null item [{context}]", e.Current.DisplayString());
}
else
{
Logger.LogError("Unknown item pulled from API [{item}]", e.Current.Item);
}
return Task.CompletedTask;
}
}
}

View File

@ -24,14 +24,24 @@ namespace Selector
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null) public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
{ {
var config = await spotifyFactory.GetConfig(); if (!Magic.Dummy)
var client = new SpotifyClient(config); {
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);
return new AudioFeatureInjector( return new AudioFeatureInjector(
watcher, watcher,
client.Tracks, client.Tracks,
LoggerFactory.CreateLogger<AudioFeatureInjector>() LoggerFactory.CreateLogger<AudioFeatureInjector>()
); );
}
else
{
return new DummyAudioFeatureInjector(
watcher,
LoggerFactory.CreateLogger<DummyAudioFeatureInjector>()
);
}
} }
} }
} }

8
Selector/Magic.cs Normal file
View File

@ -0,0 +1,8 @@
using System;
namespace Selector;
public static class Magic
{
public const bool Dummy = true;
}

View File

@ -0,0 +1,163 @@
using System;
using Microsoft.Extensions.Logging;
using SpotifyAPI.Web;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Selector
{
public class DummyPlayerWatcher: PlayerWatcher
{
private CurrentlyPlayingContext[] _contexts = new[]
{
new CurrentlyPlayingContext
{
Device = new Device
{
Id = "Dev 1",
IsActive = true,
IsPrivateSession = false,
IsRestricted = false,
Name = "Dev 1",
Type = "Phone",
VolumePercent = 50
},
ShuffleState = false,
Context = new Context
{
},
IsPlaying = true,
Item = new FullTrack
{
Name = "Track 1",
Id = "track-1",
Href = "https://sarsoo.xyz",
Popularity = 50,
Uri = "spotify:track:1",
Album = new SimpleAlbum
{
Name = "Album 1",
Artists = new List<SimpleArtist>
{
new SimpleArtist
{
Name = "Artist 1"
}
}
},
Artists = new List<SimpleArtist>
{
new SimpleArtist
{
Name = "Artist 1"
}
}
}
},
new CurrentlyPlayingContext
{
Device = new Device
{
Id = "Dev 1",
IsActive = true,
IsPrivateSession = false,
IsRestricted = false,
Name = "Dev 1",
Type = "Phone",
VolumePercent = 50
},
ShuffleState = false,
Context = new Context
{
},
IsPlaying = true,
Item = new FullTrack
{
Name = "Track 2",
Id = "track-2",
Href = "https://sarsoo.xyz",
Popularity = 50,
Uri = "spotify:track:2",
Album = new SimpleAlbum
{
Name = "Album 1",
Artists = new List<SimpleArtist>
{
new SimpleArtist
{
Name = "Artist 2"
}
}
},
Artists = new List<SimpleArtist>
{
new SimpleArtist
{
Name = "Artist 2"
}
}
}
}
};
private int _contextIdx = 0;
private DateTime _lastNext = DateTime.UtcNow;
private TimeSpan _contextLifespan = TimeSpan.FromSeconds(30);
public DummyPlayerWatcher(IEqual equalityChecker,
ILogger<DummyPlayerWatcher> logger = null,
int pollPeriod = 3000) : base(null, equalityChecker, logger, pollPeriod)
{
}
private bool ShouldCycle() => DateTime.UtcNow - _lastNext > _contextLifespan;
private void BumpContextIndex()
{
Logger.LogDebug("Next song");
_contextIdx++;
if(_contextIdx >= _contexts.Length)
{
_contextIdx = 0;
}
}
private CurrentlyPlayingContext GetContext()
{
if (ShouldCycle()) BumpContextIndex();
return _contexts[_contextIdx];
}
public override Task WatchOne(CancellationToken token = default)
{
token.ThrowIfCancellationRequested();
var polledCurrent = GetContext();
using var polledLogScope = Logger.BeginScope(new Dictionary<string, object>() { { "context", polledCurrent?.DisplayString() } });
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
// swap new item into live and bump existing down to previous
Previous = Live;
Live = polledCurrent;
OnNetworkPoll(GetEvent());
CheckPlaying();
CheckContext();
CheckItem();
CheckDevice();
return Task.CompletedTask;
}
}
}

View File

@ -11,7 +11,7 @@ namespace Selector
{ {
public class PlayerWatcher: BaseWatcher, IPlayerWatcher public class PlayerWatcher: BaseWatcher, IPlayerWatcher
{ {
new private readonly ILogger<PlayerWatcher> Logger; new protected readonly ILogger<PlayerWatcher> Logger;
private readonly IPlayerClient spotifyClient; private readonly IPlayerClient spotifyClient;
private readonly IEqual eq; private readonly IEqual eq;
@ -26,8 +26,8 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> DeviceChange; public event EventHandler<ListeningChangeEventArgs> DeviceChange;
public event EventHandler<ListeningChangeEventArgs> PlayingChange; public event EventHandler<ListeningChangeEventArgs> PlayingChange;
public CurrentlyPlayingContext Live { get; private set; } public CurrentlyPlayingContext Live { get; protected set; }
private CurrentlyPlayingContext Previous { get; set; } protected CurrentlyPlayingContext Previous { get; set; }
public PlayerTimeline Past { get; set; } = new(); public PlayerTimeline Past { get; set; } = new();
public PlayerWatcher(IPlayerClient spotifyClient, public PlayerWatcher(IPlayerClient spotifyClient,
@ -95,7 +95,7 @@ namespace Selector
} }
} }
private void CheckItem() protected void CheckItem()
{ {
switch (Previous, Live) switch (Previous, Live)
@ -163,7 +163,7 @@ namespace Selector
} }
} }
private void CheckContext() protected void CheckContext()
{ {
if ((Previous, Live) if ((Previous, Live)
is (null or { Context: null }, { Context: not null })) is (null or { Context: null }, { Context: not null }))
@ -178,7 +178,7 @@ namespace Selector
} }
} }
private void CheckPlaying() protected void CheckPlaying()
{ {
switch (Previous, Live) switch (Previous, Live)
{ {
@ -201,7 +201,7 @@ namespace Selector
} }
} }
private void CheckDevice() protected void CheckDevice()
{ {
// DEVICE // DEVICE
if (!eq.IsEqual(Previous?.Device, Live?.Device)) if (!eq.IsEqual(Previous?.Device, Live?.Device))
@ -218,13 +218,13 @@ namespace Selector
} }
} }
private ListeningChangeEventArgs GetEvent() => ListeningChangeEventArgs.From(Previous, Live, Past, id: Id, username: SpotifyUsername); protected ListeningChangeEventArgs GetEvent() => ListeningChangeEventArgs.From(Previous, Live, Past, id: Id, username: SpotifyUsername);
/// <summary> /// <summary>
/// Store currently playing in last plays. Determine whether new list or appending required /// Store currently playing in last plays. Determine whether new list or appending required
/// </summary> /// </summary>
/// <param name="current">New currently playing to store</param> /// <param name="current">New currently playing to store</param>
private void StoreCurrentPlaying(CurrentlyPlayingContext current) protected void StoreCurrentPlaying(CurrentlyPlayingContext current)
{ {
Past?.Add(current); Past?.Add(current);
} }

View File

@ -24,21 +24,37 @@ namespace Selector
{ {
if(typeof(T).IsAssignableFrom(typeof(PlayerWatcher))) if(typeof(T).IsAssignableFrom(typeof(PlayerWatcher)))
{ {
var config = await spotifyFactory.GetConfig(); if(!Magic.Dummy)
var client = new SpotifyClient(config); {
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);
// TODO: catch spotify exceptions // TODO: catch spotify exceptions
var user = await client.UserProfile.Current(); var user = await client.UserProfile.Current();
return new PlayerWatcher( return new PlayerWatcher(
client.Player, client.Player,
Equal, Equal,
LoggerFactory?.CreateLogger<PlayerWatcher>() ?? NullLogger<PlayerWatcher>.Instance, LoggerFactory?.CreateLogger<PlayerWatcher>() ?? NullLogger<PlayerWatcher>.Instance,
pollPeriod: pollPeriod pollPeriod: pollPeriod
) { )
SpotifyUsername = user.DisplayName, {
Id = id SpotifyUsername = user.DisplayName,
}; Id = id
};
}
else
{
return new DummyPlayerWatcher(
Equal,
LoggerFactory?.CreateLogger<DummyPlayerWatcher>() ?? NullLogger<DummyPlayerWatcher>.Instance,
pollPeriod: pollPeriod
)
{
SpotifyUsername = "dummy",
Id = id
};
}
} }
else if (typeof(T).IsAssignableFrom(typeof(PlaylistWatcher))) else if (typeof(T).IsAssignableFrom(typeof(PlaylistWatcher)))
{ {