diff --git a/Selector.Cache/AudioFeaturePuller.cs b/Selector.Cache/AudioFeaturePuller.cs new file mode 100644 index 0000000..e55d1e4 --- /dev/null +++ b/Selector.Cache/AudioFeaturePuller.cs @@ -0,0 +1,40 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using SpotifyAPI.Web; +using StackExchange.Redis; + +namespace Selector.Cache +{ + public class AudioFeaturePuller + { + private readonly IRefreshTokenFactoryProvider SpotifyFactory; + private readonly IDatabaseAsync Cache; + + public AudioFeaturePuller( + IRefreshTokenFactoryProvider spotifyFactory, + IDatabaseAsync cache + ) + { + SpotifyFactory = spotifyFactory; + Cache = cache; + } + + public async Task Get(string userId, string trackId) + { + var track = await Cache.StringGetAsync(Key.AudioFeature(trackId)); + if (track == RedisValue.Null) + { + // TODO: finish implementing network pull + // return await SpotifyClient.GetAudioFeatures(trackId); + throw new NotImplementedException("Can't pull over network yet"); + } + else + { + var deserialised = JsonSerializer.Deserialize(track); + return deserialised; + } + } + + } +} \ No newline at end of file diff --git a/Selector.Web/CSS/now.scss b/Selector.Web/CSS/now.scss index 25e23f7..3529b17 100644 --- a/Selector.Web/CSS/now.scss +++ b/Selector.Web/CSS/now.scss @@ -1,7 +1,7 @@ - .card { background-color: grey; color: white; + margin: 5px; box-shadow: 4px 4px 2px #5e5e5e; } @@ -18,4 +18,37 @@ img { margin: 15px; } +} + +@media only screen and (min-width: 768px) { + .now-playing-card { + position: fixed; + right: 20px; + bottom: 20px; + + width: 300px; + } +} + +.spotify-logo { + width: 21px; + + img { + width: 21px; + } +} + +.info-card { + margin-top: 10px; + margin-bottom: 10px; + + img { + margin: 15px; + } +} + +@media only screen and (min-width: 768px) { + .info-card { + max-width: 300px; + } } \ No newline at end of file diff --git a/Selector.Web/Hubs/NowPlayingHub.cs b/Selector.Web/Hubs/NowPlayingHub.cs index 6c53ca4..4e1c8f3 100644 --- a/Selector.Web/Hubs/NowPlayingHub.cs +++ b/Selector.Web/Hubs/NowPlayingHub.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; +using SpotifyAPI.Web; using StackExchange.Redis; using Selector.Cache; @@ -12,11 +13,13 @@ namespace Selector.Web.Hubs public interface INowPlayingHubClient { public Task OnNewPlaying(CurrentlyPlayingDTO context); + // public Task OnNewAudioFeature(TrackAudioFeatures features); } public class NowPlayingHub: Hub { private readonly IDatabaseAsync Cache; + // private readonly AudioFeaturePuller AudioFeaturePuller; public NowPlayingHub(IDatabaseAsync cache) { @@ -34,5 +37,10 @@ namespace Selector.Web.Hubs var deserialised = JsonSerializer.Deserialize(nowPlaying); await Clients.Caller.OnNewPlaying(deserialised); } + + // public async Task SendAudioFeatures(string trackId) + // { + // await Clients.Caller.OnNewAudioFeature(await AudioFeaturePuller.Get(trackId)); + // } } } \ No newline at end of file diff --git a/Selector.Web/Pages/Now.cshtml b/Selector.Web/Pages/Now.cshtml index d0e7f86..c98ca3d 100644 --- a/Selector.Web/Pages/Now.cshtml +++ b/Selector.Web/Pages/Now.cshtml @@ -6,8 +6,12 @@

Now

-
- +
+ + + + +
diff --git a/Selector.Web/Startup.cs b/Selector.Web/Startup.cs index 7344f61..b97c40f 100644 --- a/Selector.Web/Startup.cs +++ b/Selector.Web/Startup.cs @@ -117,6 +117,8 @@ namespace Selector.Web services.AddTransient(services => services.GetService().GetSubscriber()); } + services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(); diff --git a/Selector.Web/appsettings.json b/Selector.Web/appsettings.json index f90e5ed..1e809b2 100644 --- a/Selector.Web/appsettings.json +++ b/Selector.Web/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Trace", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } diff --git a/Selector.Web/scripts/HubInterfaces.ts b/Selector.Web/scripts/HubInterfaces.ts index 69bc215..46b5992 100644 --- a/Selector.Web/scripts/HubInterfaces.ts +++ b/Selector.Web/scripts/HubInterfaces.ts @@ -9,6 +9,7 @@ export interface nowPlayingProxy { export interface NowPlayingHubClient { OnNewPlaying: (context: CurrentlyPlayingDTO) => void; + OnNewAudioFeature: (features: TrackAudioFeatures) => void; } export interface NowPlayingHub { @@ -106,6 +107,27 @@ export interface FullTrack { isLocal: boolean; } +export interface TrackAudioFeatures { + type: string; + trackHref: string; + timeSignature: number; + tempo: number; + speechiness: number; + mode: number; + loudness: number; + uri: string; + liveness: number; + instrumentalness: number; + id: string; + energy: number; + durationMs: number; + danceability: number; + analysisUrl: string; + acousticness: number; + key: number; + valence: number; +} + export interface LinkedTrack { externalUrls: { [key: string]: string; }; href: string; diff --git a/Selector.Web/scripts/Now/BaseInfoCard.ts b/Selector.Web/scripts/Now/BaseInfoCard.ts new file mode 100644 index 0000000..152c164 --- /dev/null +++ b/Selector.Web/scripts/Now/BaseInfoCard.ts @@ -0,0 +1,12 @@ +import * as Vue from "vue"; + +let BaseInfoCard: Vue.Component = { + props: ['html'], + template: + ` +
+
+ ` +} + +export default BaseInfoCard; \ No newline at end of file diff --git a/Selector.Web/scripts/Now/NowPlayingCard.ts b/Selector.Web/scripts/Now/NowPlayingCard.ts new file mode 100644 index 0000000..ce38228 --- /dev/null +++ b/Selector.Web/scripts/Now/NowPlayingCard.ts @@ -0,0 +1,48 @@ +import * as Vue from "vue"; + +let component: Vue.Component = { + props: ['track', 'episode'], + computed: { + IsTrackPlaying() { + return this.track !== null && this.track !== undefined; + }, + IsEpisodePlaying() { + return this.episode !== null && this.episode !== undefined; + } + }, + template: + ` +
+ +

{{ track.name }}

+
+ {{ track.album.name }} +
+
+ +
+ +
+ +
+ +

{{ episode.name }}

+
+ {{ episode.show.name }} +
+
+ {{ episode.show.publisher }} +
+ +
+ +
+

No Playback

+
+ ` +} + +export default component; \ No newline at end of file diff --git a/Selector.Web/scripts/Now/Spotify.ts b/Selector.Web/scripts/Now/Spotify.ts new file mode 100644 index 0000000..71ddc0a --- /dev/null +++ b/Selector.Web/scripts/Now/Spotify.ts @@ -0,0 +1,25 @@ +import * as Vue from "vue"; + +export let PopularityCard: Vue.Component = { + props: ['track'], + template: + ` +
+

Popularity

+

{{ track.popularity }}%

+ +
+ ` +} + +export let SpotifyLogoLink: Vue.Component = { + props: ['link'], + template: + ` + + + + ` +} \ No newline at end of file diff --git a/Selector.Web/scripts/NowPlayingCard.ts b/Selector.Web/scripts/NowPlayingCard.ts deleted file mode 100644 index ccea83b..0000000 --- a/Selector.Web/scripts/NowPlayingCard.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as Vue from "vue"; - -let component: Vue.Component = { - props: ['track'], - template: - ` -
- -

{{ track.name }}

-
- {{ track.album.name }} -
-
- -
- - - -
- ` -} - -export default component; \ No newline at end of file diff --git a/Selector.Web/scripts/now.ts b/Selector.Web/scripts/now.ts index bdd13b7..d272868 100644 --- a/Selector.Web/scripts/now.ts +++ b/Selector.Web/scripts/now.ts @@ -1,7 +1,9 @@ import * as signalR from "@microsoft/signalr"; import * as Vue from "vue"; -import { FullTrack, CurrentlyPlayingDTO } from "./HubInterfaces"; -import NowPlayingCard from "./NowPlayingCard"; +import { TrackAudioFeatures, CurrentlyPlayingDTO } from "./HubInterfaces"; +import NowPlayingCard from "./Now/NowPlayingCard"; +import { PopularityCard, SpotifyLogoLink } from "./Now/Spotify"; +import BaseInfoCard from "./Now/BaseInfoCard"; const connection = new signalR.HubConnectionBuilder() .withUrl("/hub") @@ -13,32 +15,22 @@ connection.start() }) .catch(err => console.error(err)); +interface InfoCard { + html: string +} + interface NowPlaying { - currentlyPlaying?: CurrentlyPlayingDTO + currentlyPlaying?: CurrentlyPlayingDTO, + trackFeatures?: TrackAudioFeatures, + cards: InfoCard[] } const app = Vue.createApp({ data() { return { - currentlyPlaying: { - track: { - name: "No Playback", - album: { - name: "", - images: [ - { - url: "" - } - ] - }, - artists: [ - { - name: "" - } - ], - externalUrls: {} - } - } + currentlyPlaying: undefined, + trackFeatures: undefined, + cards: [] } as NowPlaying }, created() { @@ -46,9 +38,24 @@ const app = Vue.createApp({ { console.log(context); this.currentlyPlaying = context; + this.cards = []; + + // if(context.track !== null && context.track !== undefined) + // { + // connection.invoke("SendAudioFeatures", context.track.id); + // } }); + + // connection.on("OnNewAudioFeature", (feature: TrackAudioFeatures) => + // { + // console.log(feature); + // this.trackFeatures = feature; + // }); } }); app.component("now-playing-card", NowPlayingCard); +app.component("info-card", BaseInfoCard); +app.component("popularity", PopularityCard); +app.component("spotify-logo", SpotifyLogoLink); const vm = app.mount('#app'); \ No newline at end of file diff --git a/Selector/Helpers/SpotifyExtensions.cs b/Selector/Helpers/SpotifyExtensions.cs index a2b6a8d..b47b271 100644 --- a/Selector/Helpers/SpotifyExtensions.cs +++ b/Selector/Helpers/SpotifyExtensions.cs @@ -27,6 +27,10 @@ namespace Selector { return $"{currentPlaying.IsPlaying}, {episode.DisplayString()}, {currentPlaying.Device?.DisplayString()}"; } + else if (currentPlaying.Item is null) + { + return $"{currentPlaying.IsPlaying}, no item, {currentPlaying.Device?.DisplayString()}"; + } else { throw new ArgumentException("Unknown playing type"); diff --git a/Selector/Watcher/PlayerWatcher.cs b/Selector/Watcher/PlayerWatcher.cs index 30fd7a1..4853d0d 100644 --- a/Selector/Watcher/PlayerWatcher.cs +++ b/Selector/Watcher/PlayerWatcher.cs @@ -75,6 +75,7 @@ namespace Selector { Logger.LogDebug($"Playback started: {Live.DisplayString()}"); OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); } // STOPPED PLAYBACK else if((previous.Item is FullTrack || previous.Item is FullEpisode) @@ -82,6 +83,7 @@ namespace Selector { Logger.LogDebug($"Playback stopped: {previous.DisplayString()}"); OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); + OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername)); } // CONTINUING PLAYBACK else {