adding dummies for faking a spotify connection
This commit is contained in:
parent
7037dce2da
commit
6ae7994610
@ -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());
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ namespace Selector.Cache
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
||||||
|
{
|
||||||
|
if (!Magic.Dummy)
|
||||||
{
|
{
|
||||||
var config = await spotifyFactory.GetConfig();
|
var config = await spotifyFactory.GetConfig();
|
||||||
var client = new SpotifyClient(config);
|
var client = new SpotifyClient(config);
|
||||||
@ -34,5 +36,13 @@ namespace Selector.Cache
|
|||||||
LoggerFactory.CreateLogger<CachingAudioFeatureInjector>()
|
LoggerFactory.CreateLogger<CachingAudioFeatureInjector>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new DummyAudioFeatureInjector(
|
||||||
|
watcher,
|
||||||
|
LoggerFactory.CreateLogger<DummyAudioFeatureInjector>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
101
Selector/Consumers/DummyAudioFeatureInjector.cs
Normal file
101
Selector/Consumers/DummyAudioFeatureInjector.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,8 @@ namespace Selector
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
public async Task<IPlayerConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
||||||
|
{
|
||||||
|
if (!Magic.Dummy)
|
||||||
{
|
{
|
||||||
var config = await spotifyFactory.GetConfig();
|
var config = await spotifyFactory.GetConfig();
|
||||||
var client = new SpotifyClient(config);
|
var client = new SpotifyClient(config);
|
||||||
@ -33,5 +35,13 @@ namespace Selector
|
|||||||
LoggerFactory.CreateLogger<AudioFeatureInjector>()
|
LoggerFactory.CreateLogger<AudioFeatureInjector>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new DummyAudioFeatureInjector(
|
||||||
|
watcher,
|
||||||
|
LoggerFactory.CreateLogger<DummyAudioFeatureInjector>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
Selector/Magic.cs
Normal file
8
Selector/Magic.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
namespace Selector;
|
||||||
|
|
||||||
|
public static class Magic
|
||||||
|
{
|
||||||
|
public const bool Dummy = true;
|
||||||
|
}
|
||||||
|
|
163
Selector/Watcher/DummyPlayerWatcher.cs
Normal file
163
Selector/Watcher/DummyPlayerWatcher.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ namespace Selector
|
|||||||
where T : class, IWatcher
|
where T : class, IWatcher
|
||||||
{
|
{
|
||||||
if(typeof(T).IsAssignableFrom(typeof(PlayerWatcher)))
|
if(typeof(T).IsAssignableFrom(typeof(PlayerWatcher)))
|
||||||
|
{
|
||||||
|
if(!Magic.Dummy)
|
||||||
{
|
{
|
||||||
var config = await spotifyFactory.GetConfig();
|
var config = await spotifyFactory.GetConfig();
|
||||||
var client = new SpotifyClient(config);
|
var client = new SpotifyClient(config);
|
||||||
@ -35,11 +37,25 @@ namespace Selector
|
|||||||
Equal,
|
Equal,
|
||||||
LoggerFactory?.CreateLogger<PlayerWatcher>() ?? NullLogger<PlayerWatcher>.Instance,
|
LoggerFactory?.CreateLogger<PlayerWatcher>() ?? NullLogger<PlayerWatcher>.Instance,
|
||||||
pollPeriod: pollPeriod
|
pollPeriod: pollPeriod
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
SpotifyUsername = user.DisplayName,
|
SpotifyUsername = user.DisplayName,
|
||||||
Id = id
|
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)))
|
||||||
{
|
{
|
||||||
var config = await spotifyFactory.GetConfig();
|
var config = await spotifyFactory.GetConfig();
|
||||||
|
Loading…
Reference in New Issue
Block a user