floating player, popularity card, item change on from or to null context
ready to inject audio features
This commit is contained in:
parent
83bb76a5eb
commit
cb8553af60
40
Selector.Cache/AudioFeaturePuller.cs
Normal file
40
Selector.Cache/AudioFeaturePuller.cs
Normal file
@ -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<TrackAudioFeatures> 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<TrackAudioFeatures>(track);
|
||||
return deserialised;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
|
||||
.card {
|
||||
background-color: grey;
|
||||
color: white;
|
||||
margin: 5px;
|
||||
|
||||
box-shadow: 4px 4px 2px #5e5e5e;
|
||||
}
|
||||
@ -19,3 +19,36 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<INowPlayingHubClient>
|
||||
{
|
||||
private readonly IDatabaseAsync Cache;
|
||||
// private readonly AudioFeaturePuller AudioFeaturePuller;
|
||||
|
||||
public NowPlayingHub(IDatabaseAsync cache)
|
||||
{
|
||||
@ -34,5 +37,10 @@ namespace Selector.Web.Hubs
|
||||
var deserialised = JsonSerializer.Deserialize<CurrentlyPlayingDTO>(nowPlaying);
|
||||
await Clients.Caller.OnNewPlaying(deserialised);
|
||||
}
|
||||
|
||||
// public async Task SendAudioFeatures(string trackId)
|
||||
// {
|
||||
// await Clients.Caller.OnNewAudioFeature(await AudioFeaturePuller.Get(trackId));
|
||||
// }
|
||||
}
|
||||
}
|
@ -6,8 +6,12 @@
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Now</h1>
|
||||
<div id="app" class="col-12">
|
||||
<now-playing-card :track="currentlyPlaying.track" class="col-md-3"></now-playing-card>
|
||||
<div id="app">
|
||||
<now-playing-card :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined"></now-playing-card>
|
||||
<now-playing-card v-else></now-playing-card>
|
||||
|
||||
<popularity :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined && currentlyPlaying.track != null && currentlyPlaying.track != undefined" />
|
||||
<info-card v-for="card in cards" :html="card.html" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -117,6 +117,8 @@ namespace Selector.Web
|
||||
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
||||
}
|
||||
|
||||
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
|
||||
|
||||
services.AddSingleton<CacheHubProxy>();
|
||||
services.AddHostedService<CacheHubProxyService>();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Default": "Trace",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
|
@ -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;
|
||||
|
12
Selector.Web/scripts/Now/BaseInfoCard.ts
Normal file
12
Selector.Web/scripts/Now/BaseInfoCard.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as Vue from "vue";
|
||||
|
||||
let BaseInfoCard: Vue.Component = {
|
||||
props: ['html'],
|
||||
template:
|
||||
`
|
||||
<div class="card info-card" v-html="html">
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export default BaseInfoCard;
|
48
Selector.Web/scripts/Now/NowPlayingCard.ts
Normal file
48
Selector.Web/scripts/Now/NowPlayingCard.ts
Normal file
@ -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:
|
||||
`
|
||||
<div class="card now-playing-card" v-if="IsTrackPlaying">
|
||||
<img :src="track.album.images[0].url" class="cover-art">
|
||||
<h4>{{ track.name }}</h4>
|
||||
<h6>
|
||||
{{ track.album.name }}
|
||||
</h6>
|
||||
<h6>
|
||||
<template v-for="(artist, index) in track.artists">
|
||||
<template v-if="index > 0">, </template>
|
||||
<span>{{ artist.name }}</span>
|
||||
</template>
|
||||
</h6>
|
||||
<spotify-logo :link="track.externalUrls.spotify" />
|
||||
</div>
|
||||
|
||||
<div class="card now-playing-card" v-else-if="IsEpisodePlaying">
|
||||
<img :src="episode.show.images[0].url" class="cover-art">
|
||||
<h4>{{ episode.name }}</h4>
|
||||
<h6>
|
||||
{{ episode.show.name }}
|
||||
</h6>
|
||||
<h6>
|
||||
{{ episode.show.publisher }}
|
||||
</h6>
|
||||
<spotify-logo :link="episode.externalUrls.spotify" />
|
||||
</div>
|
||||
|
||||
<div class="card now-playing-card" v-else>
|
||||
<h4>No Playback</h4>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export default component;
|
25
Selector.Web/scripts/Now/Spotify.ts
Normal file
25
Selector.Web/scripts/Now/Spotify.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as Vue from "vue";
|
||||
|
||||
export let PopularityCard: Vue.Component = {
|
||||
props: ['track'],
|
||||
template:
|
||||
`
|
||||
<div class="card info-card">
|
||||
<h3>Popularity</h3>
|
||||
<h1>{{ track.popularity }}%</h1>
|
||||
<spotify-logo :link="track.externalUrls.spotify" />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export let SpotifyLogoLink: Vue.Component = {
|
||||
props: ['link'],
|
||||
template:
|
||||
`
|
||||
<a :href="link" class="spotify-logo" v-if="link != null && link != undefined">
|
||||
<img src="/Spotify_Icon_RGB_White.png">
|
||||
</a>
|
||||
|
||||
<img src="/Spotify_Icon_RGB_White.png" v-else>
|
||||
`
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import * as Vue from "vue";
|
||||
|
||||
let component: Vue.Component = {
|
||||
props: ['track'],
|
||||
template:
|
||||
`
|
||||
<div class="card now-playing-card" >
|
||||
<img :src="track.album.images[0].url" class="cover-art">
|
||||
<h4>{{ track.name }}</h4>
|
||||
<h6>
|
||||
{{ track.album.name }}
|
||||
</h6>
|
||||
<h6>
|
||||
<template v-for="(artist, index) in track.artists">
|
||||
<template v-if="index > 0">, </template>
|
||||
<span>{{ artist.name }}</span>
|
||||
</template>
|
||||
</h6>
|
||||
<a :href="track.externalUrls.spotify" style="width: 21px">
|
||||
<img src="/Spotify_Icon_RGB_White.png" style="width: 21px">
|
||||
</a>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export default component;
|
@ -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');
|
@ -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");
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user