caching working, caching audio feature injector, added timeline to event
This commit is contained in:
parent
b0467c3df9
commit
a3510c05ed
@ -71,7 +71,7 @@ namespace Selector.CLI
|
||||
|
||||
enum Consumers
|
||||
{
|
||||
AudioFeatures, CacheWriter, Publisher
|
||||
AudioFeatures, AudioFeaturesCache, CacheWriter, Publisher
|
||||
}
|
||||
|
||||
class DatabaseOptions {
|
||||
|
@ -114,6 +114,11 @@ namespace Selector.CLI
|
||||
consumers.Add(await featureInjector.Get(spotifyFactory));
|
||||
break;
|
||||
|
||||
case Consumers.AudioFeaturesCache:
|
||||
var featureInjectorCache = new CachingAudioFeatureInjectorFactory(LoggerFactory, Cache);
|
||||
consumers.Add(await featureInjectorCache.Get(spotifyFactory));
|
||||
break;
|
||||
|
||||
case Consumers.CacheWriter:
|
||||
var cacheWriter = new CacheWriterFactory(Cache, LoggerFactory);
|
||||
consumers.Add(await cacheWriter.Get());
|
||||
|
@ -9,7 +9,7 @@
|
||||
"name": "Player Watcher",
|
||||
"type": "player",
|
||||
"pollperiod": 2000,
|
||||
"consumers": [ "audiofeatures", "cachewriter" ]
|
||||
"consumers": [ "audiofeaturescache", "cachewriter", "publisher" ]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
48
Selector.Cache/Consumer/AudioInjectorCaching.cs
Normal file
48
Selector.Cache/Consumer/AudioInjectorCaching.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
using SpotifyAPI.Web;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Selector.Cache
|
||||
{
|
||||
public class CachingAudioFeatureInjector : AudioFeatureInjector
|
||||
{
|
||||
private readonly IDatabaseAsync Db;
|
||||
public TimeSpan CacheExpiry { get; set; } = TimeSpan.FromDays(1);
|
||||
|
||||
public CachingAudioFeatureInjector(
|
||||
IPlayerWatcher watcher,
|
||||
IDatabaseAsync db,
|
||||
ITracksClient trackClient,
|
||||
ILogger<CachingAudioFeatureInjector> logger = null,
|
||||
CancellationToken token = default
|
||||
) : base(watcher, trackClient, logger, token) {
|
||||
|
||||
Db = db;
|
||||
|
||||
NewFeature += CacheCallback;
|
||||
}
|
||||
|
||||
public void CacheCallback(object sender, AnalysedTrack e)
|
||||
{
|
||||
Task.Run(() => { return AsyncCacheCallback(e); }, CancelToken);
|
||||
}
|
||||
|
||||
public async Task AsyncCacheCallback(AnalysedTrack e)
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(e);
|
||||
|
||||
Logger.LogTrace($"Caching current for [{e.Track.DisplayString()}]");
|
||||
|
||||
var resp = await Db.StringSetAsync(Key.AudioFeature(e.Track.Id), payload, expiry: CacheExpiry);
|
||||
|
||||
Logger.LogDebug($"Cached audio feature for [{e.Track.DisplayString()}], {(resp ? "value set" : "value NOT set")}");
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SpotifyAPI.Web;
|
||||
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Selector.Cache
|
||||
@ -33,14 +33,20 @@ namespace Selector.Cache
|
||||
public void Callback(object sender, ListeningChangeEventArgs e)
|
||||
{
|
||||
if (e.Current is null) return;
|
||||
|
||||
|
||||
Task.Run(() => { return AsyncCallback(e); }, CancelToken);
|
||||
}
|
||||
|
||||
public async Task AsyncCallback(ListeningChangeEventArgs e)
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(e);
|
||||
await Db.StringSetAsync(Key.CurrentlyPlaying(e.Username), payload);
|
||||
var payload = JsonSerializer.Serialize((CurrentlyPlayingDTO) e);
|
||||
|
||||
Logger.LogTrace($"Caching current for [{e.Username}]");
|
||||
|
||||
var resp = await Db.StringSetAsync(Key.CurrentlyPlaying(e.Username), payload);
|
||||
|
||||
Logger.LogDebug($"Cached current for [{e.Username}], {(resp ? "value set" : "value NOT set")}");
|
||||
|
||||
}
|
||||
|
||||
public void Subscribe(IWatcher watch = null)
|
43
Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs
Normal file
43
Selector.Cache/Consumer/Factory/AudioInjectorCaching.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using SpotifyAPI.Web;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Selector.Cache
|
||||
{
|
||||
public interface ICachingAudioFeatureInjectorFactory
|
||||
{
|
||||
public Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher);
|
||||
}
|
||||
|
||||
public class CachingAudioFeatureInjectorFactory: ICachingAudioFeatureInjectorFactory {
|
||||
|
||||
private readonly ILoggerFactory LoggerFactory;
|
||||
private readonly IDatabaseAsync Db;
|
||||
|
||||
public CachingAudioFeatureInjectorFactory(
|
||||
ILoggerFactory loggerFactory,
|
||||
IDatabaseAsync db
|
||||
) {
|
||||
LoggerFactory = loggerFactory;
|
||||
Db = db;
|
||||
}
|
||||
|
||||
public async Task<IConsumer> Get(ISpotifyConfigFactory spotifyFactory, IPlayerWatcher watcher = null)
|
||||
{
|
||||
var config = await spotifyFactory.GetConfig();
|
||||
var client = new SpotifyClient(config);
|
||||
|
||||
return new CachingAudioFeatureInjector(
|
||||
watcher,
|
||||
Db,
|
||||
client.Tracks,
|
||||
LoggerFactory.CreateLogger<CachingAudioFeatureInjector>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SpotifyAPI.Web;
|
||||
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace Selector.Cache
|
||||
@ -39,8 +39,13 @@ namespace Selector.Cache
|
||||
|
||||
public async Task AsyncCallback(ListeningChangeEventArgs e)
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(e);
|
||||
await Subscriber.PublishAsync(Key.CurrentlyPlaying(e.Username), payload);
|
||||
var payload = JsonSerializer.Serialize((CurrentlyPlayingDTO) e);
|
||||
|
||||
Logger.LogTrace($"Publishing current for [{e.Username}]");
|
||||
|
||||
var receivers = await Subscriber.PublishAsync(Key.CurrentlyPlaying(e.Username), payload);
|
||||
|
||||
Logger.LogDebug($"Published current for [{e.Username}], {receivers} receivers");
|
||||
}
|
||||
|
||||
public void Subscribe(IWatcher watch = null)
|
40
Selector.Cache/DTO.cs
Normal file
40
Selector.Cache/DTO.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
using SpotifyAPI.Web;
|
||||
|
||||
namespace Selector.Cache {
|
||||
|
||||
public class CurrentlyPlayingDTO {
|
||||
public CurrentlyPlayingContext Context { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
public FullTrack Track { get; set; }
|
||||
public FullEpisode Episode { get; set; }
|
||||
|
||||
public static explicit operator CurrentlyPlayingDTO(ListeningChangeEventArgs e)
|
||||
{
|
||||
if(e.Current.Item is FullTrack track)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Context = e.Current,
|
||||
Username = e.Username,
|
||||
Track = track
|
||||
};
|
||||
}
|
||||
else if (e.Current.Item is FullEpisode episode)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Context = e.Current,
|
||||
Username = e.Username,
|
||||
Episode = episode
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unknown item item");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,11 @@ namespace Selector.Cache
|
||||
public class Key
|
||||
{
|
||||
public const string CurrentlyPlayingName = "CurrentlyPlaying";
|
||||
public const string TrackName = "Track";
|
||||
public const string AudioFeatureName = "AudioFeature";
|
||||
|
||||
public static string CurrentlyPlaying(string user) => Namespace(new[] { user, CurrentlyPlayingName });
|
||||
public static string AudioFeature(string trackId) => Namespace(new[] { TrackName, trackId, AudioFeatureName });
|
||||
|
||||
public static string Namespace(string[] args) => string.Join(":", args);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ namespace Selector
|
||||
{
|
||||
public class AudioFeatureInjector : IConsumer
|
||||
{
|
||||
private readonly IPlayerWatcher Watcher;
|
||||
private readonly ITracksClient TrackClient;
|
||||
private readonly ILogger<AudioFeatureInjector> Logger;
|
||||
protected readonly IPlayerWatcher Watcher;
|
||||
protected readonly ITracksClient TrackClient;
|
||||
protected readonly ILogger<AudioFeatureInjector> Logger;
|
||||
|
||||
protected event EventHandler<AnalysedTrack> NewFeature;
|
||||
|
||||
public CancellationToken CancelToken { get; set; }
|
||||
|
||||
@ -47,7 +49,10 @@ namespace Selector
|
||||
var audioFeatures = await TrackClient.GetAudioFeatures(track.Id);
|
||||
Logger.LogDebug($"Adding audio features [{track.DisplayString()}]: [{audioFeatures.DisplayString()}]");
|
||||
|
||||
Timeline.Add(AnalysedTrack.From(track, audioFeatures), DateHelper.FromUnixMilli(e.Current.Timestamp));
|
||||
var analysedTrack = AnalysedTrack.From(track, audioFeatures);
|
||||
|
||||
Timeline.Add(analysedTrack, DateHelper.FromUnixMilli(e.Current.Timestamp));
|
||||
OnNewFeature(analysedTrack);
|
||||
}
|
||||
catch (APIUnauthorizedException ex)
|
||||
{
|
||||
@ -102,6 +107,11 @@ namespace Selector
|
||||
throw new ArgumentException("Provided watcher is not a PlayerWatcher");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnNewFeature(AnalysedTrack args)
|
||||
{
|
||||
NewFeature?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public class AnalysedTrack {
|
||||
|
@ -4,16 +4,18 @@ using SpotifyAPI.Web;
|
||||
namespace Selector
|
||||
{
|
||||
public class ListeningChangeEventArgs: EventArgs {
|
||||
public CurrentlyPlayingContext Previous;
|
||||
public CurrentlyPlayingContext Current;
|
||||
public string Username;
|
||||
public CurrentlyPlayingContext Previous { get; set; }
|
||||
public CurrentlyPlayingContext Current { get; set; }
|
||||
public string Username { get; set; }
|
||||
PlayerTimeline Timeline { get; set; }
|
||||
|
||||
public static ListeningChangeEventArgs From(CurrentlyPlayingContext previous, CurrentlyPlayingContext current, string username = null)
|
||||
public static ListeningChangeEventArgs From(CurrentlyPlayingContext previous, CurrentlyPlayingContext current, PlayerTimeline timeline, string username = null)
|
||||
{
|
||||
return new ListeningChangeEventArgs()
|
||||
{
|
||||
Previous = previous,
|
||||
Current = current,
|
||||
Timeline = timeline,
|
||||
Username = username
|
||||
};
|
||||
}
|
||||
|
@ -74,14 +74,14 @@ namespace Selector
|
||||
&& (Live.Item is FullTrack || Live.Item is FullEpisode))
|
||||
{
|
||||
Logger.LogDebug($"Playback started: {Live.DisplayString()}");
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
// STOPPED PLAYBACK
|
||||
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
|
||||
&& Live is null)
|
||||
{
|
||||
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}");
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
// CONTINUING PLAYBACK
|
||||
else {
|
||||
@ -92,17 +92,17 @@ namespace Selector
|
||||
{
|
||||
if(!eq.IsEqual(previousTrack, currentTrack)) {
|
||||
Logger.LogDebug($"Track changed: {previousTrack.DisplayString()} -> {currentTrack.DisplayString()}");
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
|
||||
if(!eq.IsEqual(previousTrack.Album, currentTrack.Album)) {
|
||||
Logger.LogDebug($"Album changed: {previousTrack.Album.DisplayString()} -> {currentTrack.Album.DisplayString()}");
|
||||
OnAlbumChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnAlbumChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
|
||||
if(!eq.IsEqual(previousTrack.Artists[0], currentTrack.Artists[0])) {
|
||||
Logger.LogDebug($"Artist changed: {previousTrack.Artists.DisplayString()} -> {currentTrack.Artists.DisplayString()}");
|
||||
OnArtistChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnArtistChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
}
|
||||
// CHANGED CONTENT
|
||||
@ -110,8 +110,8 @@ namespace Selector
|
||||
|| (previous.Item is FullEpisode && Live.Item is FullTrack))
|
||||
{
|
||||
Logger.LogDebug($"Media type changed: {previous.Item}, {previous.Item}");
|
||||
OnContentChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnContentChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
// PODCASTS
|
||||
else if(previous.Item is FullEpisode previousEp
|
||||
@ -119,7 +119,7 @@ namespace Selector
|
||||
{
|
||||
if(!eq.IsEqual(previousEp, currentEp)) {
|
||||
Logger.LogDebug($"Podcast changed: {previousEp.DisplayString()} -> {currentEp.DisplayString()}");
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -129,25 +129,25 @@ namespace Selector
|
||||
// CONTEXT
|
||||
if(!eq.IsEqual(previous.Context, Live.Context)) {
|
||||
Logger.LogDebug($"Context changed: {previous.Context.DisplayString()} -> {Live.Context.DisplayString()}");
|
||||
OnContextChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnContextChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
|
||||
// DEVICE
|
||||
if(!eq.IsEqual(previous?.Device, Live?.Device)) {
|
||||
Logger.LogDebug($"Device changed: {previous?.Device.DisplayString()} -> {Live?.Device.DisplayString()}");
|
||||
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnDeviceChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
|
||||
// IS PLAYING
|
||||
if(previous.IsPlaying != Live.IsPlaying) {
|
||||
Logger.LogDebug($"Playing state changed: {previous.IsPlaying} -> {Live.IsPlaying}");
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
|
||||
// VOLUME
|
||||
if(previous.Device.VolumePercent != Live.Device.VolumePercent) {
|
||||
Logger.LogDebug($"Volume changed: {previous.Device.VolumePercent}% -> {Live.Device.VolumePercent}%");
|
||||
OnVolumeChange(ListeningChangeEventArgs.From(previous, Live, Username));
|
||||
OnVolumeChange(ListeningChangeEventArgs.From(previous, Live, Past, Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user