adding consumer, audio features injector

This commit is contained in:
andy 2021-10-13 23:19:47 +01:00
parent b5956ef4a0
commit 9dfad73397
20 changed files with 438 additions and 111 deletions

View File

@ -27,6 +27,7 @@ namespace Selector.CLI
{
public const string Key = "Watcher";
public bool Enabled { get; set; } = true;
public List<WatcherInstanceOptions> Instances { get; set; } = new();
}
@ -39,6 +40,7 @@ namespace Selector.CLI
public string RefreshKey { get; set; }
public int PollPeriod { get; set; } = 5000;
public WatcherType Type { get; set; } = WatcherType.Player;
public List<Consumers> Consumers { get; set; } = default;
#nullable enable
public string? PlaylistUri { get; set; }
public string? WatcherCollection { get; set; }
@ -49,4 +51,9 @@ namespace Selector.CLI
{
Player, Playlist
}
enum Consumers
{
AudioFeatures
}
}

View File

@ -20,14 +20,20 @@ namespace Selector.CLI
=> Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) => {
Console.WriteLine("~~~ Selector CLI ~~~");
Console.WriteLine("");
Console.WriteLine("Configuring...");
// CONFIG
services.Configure<RootOptions>(options => {
context.Configuration.GetSection(RootOptions.Key).Bind(options);
context.Configuration.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}").Bind(options.WatcherOptions);
});
Console.WriteLine("Adding Services...");
// SERVICES
services.AddSingleton<IWatcherFactory, WatcherFactory>();
services.AddSingleton<IConsumerFactory, AudioFeatureInjectorFactory>();
services.AddSingleton<IWatcherCollectionFactory, WatcherCollectionFactory>();
// For generating spotify clients
services.AddSingleton<IRefreshTokenFactoryProvider, RefreshTokenFactoryProvider>();
@ -35,15 +41,25 @@ namespace Selector.CLI
switch(context.Configuration.GetValue<EqualityChecker>("selector:equality"))
{
case EqualityChecker.Uri:
Console.WriteLine("Using Uri Equality");
services.AddTransient<IEqual, UriEqual>();
break;
case EqualityChecker.String:
Console.WriteLine("Using String Equality");
services.AddTransient<IEqual, StringEqual>();
break;
}
// HOSTED SERVICES
services.AddHostedService<WatcherService>();
if(context.Configuration
.GetSection($"{RootOptions.Key}:{WatcherOptions.Key}")
.Get<WatcherOptions>()
.Enabled)
{
Console.WriteLine("Adding Watcher Service");
services.AddHostedService<WatcherService>();
}
})
.ConfigureLogging((context, builder) => {
builder.ClearProviders();

View File

@ -4,7 +4,8 @@
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
},
"nativeDebugging": true
}
}
}

View File

@ -17,6 +17,7 @@ namespace Selector.CLI
private const string ConfigInstanceKey = "localconfig";
private readonly ILogger<WatcherService> Logger;
private readonly ILoggerFactory LoggerFactory;
private readonly RootOptions Config;
private readonly IWatcherFactory WatcherFactory;
private readonly IWatcherCollectionFactory WatcherCollectionFactory;
@ -28,10 +29,11 @@ namespace Selector.CLI
IWatcherFactory watcherFactory,
IWatcherCollectionFactory watcherCollectionFactory,
IRefreshTokenFactoryProvider spotifyFactory,
ILogger<WatcherService> logger,
ILoggerFactory loggerFactory,
IOptions<RootOptions> config
) {
Logger = logger;
Logger = loggerFactory.CreateLogger<WatcherService>();
LoggerFactory = loggerFactory;
Config = config.Value;
WatcherFactory = watcherFactory;
WatcherCollectionFactory = watcherCollectionFactory;
@ -42,7 +44,7 @@ namespace Selector.CLI
public async Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Starting up");
Logger.LogInformation("Starting watcher service...");
Logger.LogInformation("Loading config instances...");
var watcherIndices = await InitialiseConfigInstances();
@ -60,11 +62,11 @@ namespace Selector.CLI
var logMsg = new StringBuilder();
if (!string.IsNullOrWhiteSpace(watcherOption.Name))
{
logMsg.Append($"Creating {watcherOption.Name} watcher [{watcherOption.Type}]");
logMsg.Append($"Creating [{watcherOption.Name}] watcher [{watcherOption.Type}]");
}
else
{
logMsg.Append($"Creating new {watcherOption.Type} watcher");
logMsg.Append($"Creating new [{watcherOption.Type}] watcher");
}
if (!string.IsNullOrWhiteSpace(watcherOption.PlaylistUri)) logMsg.Append($" [{ watcherOption.PlaylistUri}]");
@ -92,7 +94,19 @@ namespace Selector.CLI
break;
}
watcherCollection.Add(watcher);
List<IConsumer> consumers = new();
foreach(var consumer in watcherOption.Consumers)
{
switch(consumer)
{
case Consumers.AudioFeatures:
var factory = new AudioFeatureInjectorFactory(LoggerFactory);
consumers.Add(await factory.Get(spotifyFactory));
break;
}
}
watcherCollection.Add(watcher, consumers);
}
return indices;
@ -104,6 +118,7 @@ namespace Selector.CLI
{
try
{
Logger.LogInformation($"Starting watcher collection [{index}]");
Watchers[index].Start();
}
catch (KeyNotFoundException)
@ -119,7 +134,7 @@ namespace Selector.CLI
foreach((var key, var watcher) in Watchers)
{
Logger.LogInformation($"Stopping watcher collection: {key}");
Logger.LogInformation($"Stopping watcher collection [{key}]");
watcher.Stop();
}

View File

@ -6,16 +6,17 @@
"Watcher": {
"Instances": [
{
"name": "player watcher",
"name": "Player Watcher",
"type": "player",
"pollperiod": 1000
"pollperiod": 2000,
"consumers": [ "audiofeatures" ]
}
]
}
},
"Logging": {
"LogLevel": {
"Default": "Information"
"Default": "Trace"
}
}
}

View File

@ -16,6 +16,10 @@
name="logfile"
fileName=".\selector.log"
layout="${format}" />
<target xsi:type="File"
name="tracefile"
fileName=".\selector.trace.log"
layout="${format}" />
<target xsi:type="Console"
name="logconsole"
layout="${format}" />
@ -23,6 +27,9 @@
<!-- rules to map from logger name to target -->
<rules>
<logger name="Selector.*" minlevel="Trace" writeTo="logfile,logconsole" />
<logger name="*" minlevel="Debug" writeTo="logfile" />
<logger name="*" minlevel="Trace" writeTo="tracefile" />
<logger name="Selector.*" minlevel="Debug" writeTo="logconsole" />
</rules>
</nlog>

View File

@ -0,0 +1,103 @@
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 AudioFeatureInjector : IConsumer
{
private readonly IPlayerWatcher Watcher;
private readonly ITracksClient TrackClient;
private readonly ILogger<AudioFeatureInjector> Logger;
public CancellationToken CancelToken { get; set; }
public AnalysedTrackTimeline Timeline { get; set; } = new();
public AudioFeatureInjector(
IPlayerWatcher watcher,
ITracksClient trackClient,
ILogger<AudioFeatureInjector> logger = null,
CancellationToken token = default
){
Watcher = watcher;
TrackClient = trackClient;
Logger = logger ?? NullLogger<AudioFeatureInjector>.Instance;
CancelToken = token;
}
public void Callback(object sender, ListeningChangeEventArgs e)
{
if (e.Current is null) return;
Task.Run(() => { return AsyncCallback(e); }, CancelToken);
}
private async Task AsyncCallback(ListeningChangeEventArgs e)
{
if (e.Current.Item is FullTrack track)
{
Logger.LogTrace("Making Spotify call");
var audioFeatures = await TrackClient.GetAudioFeatures(track.Id);
Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]");
Timeline.Add(AnalysedTrack.From(track, audioFeatures));
}
else if (e.Current.Item is FullEpisode episode)
{
Logger.LogDebug($"Ignoring podcast episdoe [{episode.DisplayString()}]");
}
else
{
Logger.LogError($"Unknown item pulled from API [{e.Current.Item}]");
}
}
public void Subscribe(IWatcher watch = null)
{
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
if (watcher is IPlayerWatcher watcherCast)
{
watcherCast.ItemChange += Callback;
}
else
{
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
}
}
public void Unsubscribe(IWatcher watch = null)
{
var watcher = watch ?? Watcher ?? throw new ArgumentNullException("No watcher provided");
if (watcher is IPlayerWatcher watcherCast)
{
watcherCast.ItemChange -= Callback;
}
else
{
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
}
}
}
public class AnalysedTrack {
public FullTrack Track { get; set; }
public TrackAudioFeatures Features { get; set; }
public static AnalysedTrack From(FullTrack track, TrackAudioFeatures features)
{
return new AnalysedTrack()
{
Track = track,
Features = features
};
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SpotifyAPI.Web;
namespace Selector
{
public class AudioFeatureInjectorFactory: IConsumerFactory {
private readonly ILoggerFactory LoggerFactory;
public AudioFeatureInjectorFactory(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory;
}
public async Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
{
var config = await spotifyFactory.GetConfig();
var client = new SpotifyClient(config);
return new AudioFeatureInjector(
watcher,
client.Tracks,
LoggerFactory.CreateLogger<AudioFeatureInjector>()
);
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace Selector
{
public interface IConsumer
{
public void Callback(object sender, ListeningChangeEventArgs e);
public void Subscribe(IWatcher watch = null);
public void Unsubscribe(IWatcher watch = null);
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Threading.Tasks;
namespace Selector
{
public interface IConsumerFactory
{
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher);
}
}

View File

@ -5,20 +5,23 @@ using System.Linq;
using SpotifyAPI.Web;
namespace Selector.Helpers
namespace Selector
{
public static class SpotifyExtensions
{
public static string ToString(this FullTrack track) => $"{track.Name} - {track.Album.Name} - {track.Artists}";
public static string ToString(this SimpleAlbum album) => $"{album.Name} - {album.Artists}";
public static string ToString(this SimpleArtist artist) => $"{artist.Name}";
public static string DisplayString(this FullTrack track) => $"{track.Name} / {track.Album.Name} / {track.Artists.DisplayString()}";
public static string DisplayString(this SimpleAlbum album) => $"{album.Name} / {album.Artists.DisplayString()}";
public static string DisplayString(this SimpleArtist artist) => artist.Name;
public static string ToString(this FullEpisode ep) => $"{ep.Name} - {ep.Show}";
public static string ToString(this SimpleShow show) => $"{show.Name} - {show.Publisher}";
public static string DisplayString(this FullEpisode ep) => $"{ep.Name} / {ep.Show.DisplayString()}";
public static string DisplayString(this SimpleShow show) => $"{show.Name} / {show.Publisher}";
public static string ToString(this CurrentlyPlayingContext context) => $"{context.IsPlaying}, {context.Item}, {context.Device}";
public static string ToString(this Device device) => $"{device.Id}: {device.Name} {device.VolumePercent}%";
public static string ToString(this IEnumerable<SimpleArtist> artists) => string.Join("/", artists.Select(a => a.Name));
public static string DisplayString(this CurrentlyPlayingContext currentPlaying) => $"{currentPlaying.IsPlaying}, {currentPlaying.Item}, {currentPlaying.Device.DisplayString()}";
public static string DisplayString(this Context context) => $"{context.Type}, {context.Uri}";
public static string DisplayString(this Device device) => $"{device.Name} ({device.Id}) {device.VolumePercent}%";
public static string DisplayString(this TrackAudioFeatures feature) => $"Acou. {feature.Acousticness}, Dance {feature.Danceability}, Energy {feature.Energy}, Instru. {feature.Instrumentalness}, Key {feature.Key}, Live {feature.Liveness}, Loud {feature.Loudness}, Mode {feature.Mode}, Speech {feature.Speechiness}, Tempo {feature.Tempo}, Valence {feature.Valence}";
public static string DisplayString(this IEnumerable<SimpleArtist> artists) => string.Join(", ", artists.Select(a => a.DisplayString()));
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using SpotifyAPI.Web;
namespace Selector
{
public class AnalysedTrackTimeline
: BaseTimeline<AnalysedTrack>, ITrackTimeline<AnalysedTrack>
{
public IEqual EqualityChecker { get; set; }
public AnalysedTrack Get(FullTrack track)
=> GetAll(track)
.LastOrDefault();
public IEnumerable<AnalysedTrack> GetAll(FullTrack track)
=> GetAllTimelineItems(track)
.Select(t => t.Item);
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(FullTrack track)
=> Recent
.Where(i => EqualityChecker.IsEqual(i.Item.Track, track));
public AnalysedTrack Get(SimpleAlbum album)
=> GetAll(album)
.LastOrDefault();
public IEnumerable<AnalysedTrack> GetAll(SimpleAlbum album)
=> GetAllTimelineItems(album)
.Select(t => t.Item);
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(SimpleAlbum album)
=> Recent
.Where(i => EqualityChecker.IsEqual(i.Item.Track.Album, album));
public AnalysedTrack Get(SimpleArtist artist)
=> GetAll(artist)
.LastOrDefault();
public IEnumerable<AnalysedTrack> GetAll(SimpleArtist artist)
=> GetAllTimelineItems(artist)
.Select(t => t.Item);
public IEnumerable<TimelineItem<AnalysedTrack>> GetAllTimelineItems(SimpleArtist artist)
=> Recent
.Where(i => EqualityChecker.IsEqual(i.Item.Track.Artists[0], artist));
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Selector
{
public abstract class BaseTimeline<T> : ITimeline<T>, IEnumerable<TimelineItem<T>> where T : class
{
protected List<TimelineItem<T>> Recent = new();
public int Count { get => Recent.Count; }
public void Clear() => Recent.Clear();
public bool SortOnBackDate { get; set; } = true;
private int? max = 1000;
public int? MaxSize
{
get => max;
set => max = value is null ? value : Math.Max(1, (int) value);
}
public virtual void Add(T item) => Add(item, DateTime.UtcNow);
public virtual void Add(T item, DateTime timestamp)
{
Recent.Add(TimelineItem<T>.From(item, timestamp));
if (timestamp < Recent.Last().Time && SortOnBackDate)
{
Sort();
}
CheckSize();
}
public void Sort()
{
Recent = Recent.OrderBy(i => i.Time).ToList();
}
protected void CheckSize()
{
if (MaxSize is int maxSize && Count > maxSize)
{
Recent.RemoveRange(0, Count - maxSize);
}
}
public T Get()
=> Recent.Last().Item;
public T Get(DateTime at)
=> GetTimelineItem(at).Item;
public TimelineItem<T> GetTimelineItem(DateTime at)
=> Recent
.Where(i => i.Time <= at).LastOrDefault();
public IEnumerator<TimelineItem<T>> GetEnumerator() => Recent.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using SpotifyAPI.Web;
namespace Selector
{
public interface ITrackTimeline<T>: ITimeline<T>
{
public T Get(FullTrack track);
public IEnumerable<T> GetAll(FullTrack track);
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(FullTrack track);
public T Get(SimpleAlbum album);
public IEnumerable<T> GetAll(SimpleAlbum album);
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(SimpleAlbum album);
public T Get(SimpleArtist artist);
public IEnumerable<T> GetAll(SimpleArtist artist);
public IEnumerable<TimelineItem<T>> GetAllTimelineItems(SimpleArtist artist);
}
}

View File

@ -7,67 +7,11 @@ using SpotifyAPI.Web;
namespace Selector
{
public class PlayerTimeline
: ITimeline<CurrentlyPlayingContext>,
IEnumerable<TimelineItem<CurrentlyPlayingContext>>
: BaseTimeline<CurrentlyPlayingContext>, ITrackTimeline<CurrentlyPlayingContext>
{
private List<TimelineItem<CurrentlyPlayingContext>> recentlyPlayed = new();
public IEqual EqualityChecker { get; set; }
public bool SortOnBackDate { get; set; } = true;
public int Count { get => recentlyPlayed.Count; }
private int? max = 1000;
public int? MaxSize {
get => max;
set {
if(value is null)
{
max = value;
}
else
{
max = Math.Max(1, (int) value);
}
}
}
public void Add(CurrentlyPlayingContext item) => Add(item, DateHelper.FromUnixMilli(item.Timestamp));
public void Add(CurrentlyPlayingContext item, DateTime timestamp)
{
recentlyPlayed.Add(TimelineItem<CurrentlyPlayingContext>.From(item, timestamp));
if (timestamp < recentlyPlayed.Last().Time && SortOnBackDate)
{
Sort();
}
CheckSize();
}
public void Sort()
{
recentlyPlayed = recentlyPlayed
.OrderBy(i => i.Time)
.ToList();
}
private void CheckSize()
{
if (MaxSize is int maxSize && Count > maxSize) {
recentlyPlayed.RemoveRange(0, Count - maxSize);
}
}
public void Clear() => recentlyPlayed.Clear();
public CurrentlyPlayingContext Get()
=> recentlyPlayed.Last().Item;
public CurrentlyPlayingContext Get(DateTime at)
=> GetTimelineItem(at)?.Item;
public TimelineItem<CurrentlyPlayingContext> GetTimelineItem(DateTime at)
=> recentlyPlayed
.Where(i => i.Time <= at).LastOrDefault();
public override void Add(CurrentlyPlayingContext item) => Add(item, DateHelper.FromUnixMilli(item.Timestamp));
public CurrentlyPlayingContext Get(FullTrack track)
=> GetAll(track)
@ -77,8 +21,8 @@ namespace Selector
=> GetAllTimelineItems(track)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullTrack track)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullTrack track)
=> Recent
.Where(i => i.Item.Item is FullTrack iterTrack
&& EqualityChecker.IsEqual(iterTrack, track));
@ -90,8 +34,8 @@ namespace Selector
=> GetAllTimelineItems(ep)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullEpisode ep)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(FullEpisode ep)
=> Recent
.Where(i => i.Item.Item is FullEpisode iterEp
&& EqualityChecker.IsEqual(iterEp, ep));
@ -103,8 +47,8 @@ namespace Selector
=> GetAllTimelineItems(album)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleAlbum album)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleAlbum album)
=> Recent
.Where(i => i.Item.Item is FullTrack iterTrack
&& EqualityChecker.IsEqual(iterTrack.Album, album));
@ -116,8 +60,8 @@ namespace Selector
=> GetAllTimelineItems(artist)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleArtist artist)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(SimpleArtist artist)
=> Recent
.Where(i => i.Item.Item is FullTrack iterTrack
&& EqualityChecker.IsEqual(iterTrack.Artists[0], artist));
@ -129,8 +73,8 @@ namespace Selector
=> GetAllTimelineItems(device)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Device device)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Device device)
=> Recent
.Where(i => EqualityChecker.IsEqual(i.Item.Device, device));
public CurrentlyPlayingContext Get(Context context)
@ -141,11 +85,8 @@ namespace Selector
=> GetAllTimelineItems(context)
.Select(t => t.Item);
private IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Context context)
=> recentlyPlayed
public IEnumerable<TimelineItem<CurrentlyPlayingContext>> GetAllTimelineItems(Context context)
=> Recent
.Where(i => EqualityChecker.IsEqual(i.Item.Context, context));
public IEnumerator<TimelineItem<CurrentlyPlayingContext>> GetEnumerator() => recentlyPlayed.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -5,17 +5,29 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Selector
{
public abstract class BaseWatcher: IWatcher
{
protected readonly ILogger<BaseWatcher> Logger;
public BaseWatcher(ILogger<BaseWatcher> logger = null)
{
Logger = logger ?? NullLogger<BaseWatcher>.Instance;
}
public abstract Task WatchOne(CancellationToken token);
public async Task Watch(CancellationToken cancelToken)
{
Logger.LogDebug("Starting watcher");
while (true) {
cancelToken.ThrowIfCancellationRequested();
await WatchOne(cancelToken);
Logger.LogTrace($"Finished watch one, delaying {PollPeriod}ms...");
await Task.Delay(PollPeriod, cancelToken);
}
}

View File

@ -11,7 +11,7 @@ namespace Selector
{
public class PlayerWatcher: BaseWatcher, IPlayerWatcher
{
private readonly ILogger<PlayerWatcher> Logger;
new private readonly ILogger<PlayerWatcher> Logger;
private readonly IPlayerClient spotifyClient;
private readonly IEqual eq;
@ -26,12 +26,13 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> PlayingChange;
public CurrentlyPlayingContext Live { get; private set; }
public PlayerTimeline Past { get; private set; }
public PlayerTimeline Past { get; set; }
public PlayerWatcher(IPlayerClient spotifyClient,
IEqual equalityChecker,
ILogger<PlayerWatcher> logger = null,
int pollPeriod = 3000) {
int pollPeriod = 3000
) : base(logger) {
this.spotifyClient = spotifyClient;
eq = equalityChecker;
@ -44,7 +45,9 @@ namespace Selector
token.ThrowIfCancellationRequested();
try{
Logger.LogTrace("Making Spotify call");
var polledCurrent = await spotifyClient.GetCurrentPlayback();
Logger.LogTrace($"Received Spotify call [{polledCurrent?.DisplayString()}]");
if (polledCurrent != null) StoreCurrentPlaying(polledCurrent);
@ -70,14 +73,14 @@ namespace Selector
if(previous is null
&& (Live.Item is FullTrack || Live.Item is FullEpisode))
{
Logger.LogDebug($"Playback started: {Live}");
Logger.LogDebug($"Playback started: {Live.DisplayString()}");
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
}
// STOPPED PLAYBACK
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
&& Live is null)
{
Logger.LogDebug($"Playback stopped: {previous}");
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}");
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live));
}
// CONTINUING PLAYBACK
@ -88,17 +91,17 @@ namespace Selector
&& Live.Item is FullTrack currentTrack)
{
if(!eq.IsEqual(previousTrack, currentTrack)) {
Logger.LogDebug($"Track changed: {previousTrack} -> {currentTrack}");
Logger.LogDebug($"Track changed: {previousTrack.DisplayString()} -> {currentTrack.DisplayString()}");
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
}
if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) {
Logger.LogDebug($"Album changed: {previousTrack.Album} -> {currentTrack.Album}");
Logger.LogDebug($"Album changed: {previousTrack.Album.DisplayString()} -> {currentTrack.Album.DisplayString()}");
OnAlbumChange(ListeningChangeEventArgs.From(previous, Live));
}
if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) {
Logger.LogDebug($"Artist changed: {previousTrack.Artists[0]} -> {currentTrack.Artists[0]}");
Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}");
OnArtistChange(ListeningChangeEventArgs.From(previous, Live));
}
}
@ -115,7 +118,7 @@ namespace Selector
&& Live.Item is FullEpisode currentEp)
{
if(!eq.IsEqual(previousEp, currentEp)) {
Logger.LogDebug($"Podcast changed: {previousEp} -> {currentEp}");
Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}");
OnItemChange(ListeningChangeEventArgs.From(previous, Live));
}
}
@ -125,13 +128,13 @@ namespace Selector
// CONTEXT
if(!eq.IsEqual(previous.Context, Live.Context)) {
Logger.LogDebug($"Context changed: {previous.Context} -> {Live.Context}");
Logger.LogDebug($"Context changed: {previous.Context.DisplayString()} -> {Live.Context.DisplayString()}");
OnContextChange(ListeningChangeEventArgs.From(previous, Live));
}
// DEVICE
if(!eq.IsEqual(previous?.Device, Live?.Device)) {
Logger.LogDebug($"Device changed: {previous?.Device} -> {Live?.Device}");
Logger.LogDebug($"Device changed: {previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}");
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live));
}

View File

@ -8,7 +8,8 @@ namespace Selector
{
public bool IsRunning { get; }
public void Add(IWatcher watcher);
public void Add(IWatcher watcher, List<IConsumer> consumers);
public void Start();
public void Stop();
}

View File

@ -34,9 +34,14 @@ namespace Selector
public void Add(IWatcher watcher)
{
var context = WatcherContext.From(watcher);
Add(watcher, default);
}
public void Add(IWatcher watcher, List<IConsumer> consumers)
{
var context = WatcherContext.From(watcher, consumers);
if (IsRunning) context.Start();
Watchers.Add(context);
}
@ -45,7 +50,7 @@ namespace Selector
public void Start()
{
Logger.LogDebug($"Starting {Count} watchers");
Logger.LogDebug($"Starting {Count} watcher(s)");
foreach(var watcher in Watchers)
{
watcher.Start();
@ -55,7 +60,7 @@ namespace Selector
public void Stop()
{
Logger.LogDebug($"Stopping {Count} watchers");
Logger.LogDebug($"Stopping {Count} watcher(s)");
foreach (var watcher in Watchers)
{
watcher.Stop();

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -9,6 +10,7 @@ namespace Selector
public class WatcherContext: IDisposable
{
public IWatcher Watcher { get; set; }
private List<IConsumer> Consumers { get; set; } = new();
public bool IsRunning { get; private set; }
public Task Task { get; set; }
public CancellationTokenSource TokenSource { get; set; }
@ -18,11 +20,30 @@ namespace Selector
Watcher = watcher;
}
public WatcherContext(IWatcher watcher, List<IConsumer> consumers)
{
Watcher = watcher;
Consumers = consumers ?? new();
}
public static WatcherContext From(IWatcher watcher)
{
return new(watcher);
}
public static WatcherContext From(IWatcher watcher, List<IConsumer> consumers)
{
return new(watcher, consumers);
}
public void AddConsumer(IConsumer consumer)
{
if (IsRunning)
consumer.Subscribe(Watcher);
Consumers.Add(consumer);
}
public void Start()
{
if (IsRunning)
@ -30,6 +51,9 @@ namespace Selector
IsRunning = true;
TokenSource = new();
Consumers.ForEach(c => c.Subscribe(Watcher));
Task = Watcher.Watch(TokenSource.Token);
Task.ContinueWith(t =>
{
@ -39,6 +63,8 @@ namespace Selector
public void Stop()
{
Consumers.ForEach(c => c.Unsubscribe(Watcher));
TokenSource.Cancel();
IsRunning = false;
}