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 {
|
.card {
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
color: white;
|
color: white;
|
||||||
|
margin: 5px;
|
||||||
|
|
||||||
box-shadow: 4px 4px 2px #5e5e5e;
|
box-shadow: 4px 4px 2px #5e5e5e;
|
||||||
}
|
}
|
||||||
@ -18,4 +18,37 @@
|
|||||||
img {
|
img {
|
||||||
margin: 15px;
|
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 System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
using SpotifyAPI.Web;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
|
|
||||||
using Selector.Cache;
|
using Selector.Cache;
|
||||||
@ -12,11 +13,13 @@ namespace Selector.Web.Hubs
|
|||||||
public interface INowPlayingHubClient
|
public interface INowPlayingHubClient
|
||||||
{
|
{
|
||||||
public Task OnNewPlaying(CurrentlyPlayingDTO context);
|
public Task OnNewPlaying(CurrentlyPlayingDTO context);
|
||||||
|
// public Task OnNewAudioFeature(TrackAudioFeatures features);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NowPlayingHub: Hub<INowPlayingHubClient>
|
public class NowPlayingHub: Hub<INowPlayingHubClient>
|
||||||
{
|
{
|
||||||
private readonly IDatabaseAsync Cache;
|
private readonly IDatabaseAsync Cache;
|
||||||
|
// private readonly AudioFeaturePuller AudioFeaturePuller;
|
||||||
|
|
||||||
public NowPlayingHub(IDatabaseAsync cache)
|
public NowPlayingHub(IDatabaseAsync cache)
|
||||||
{
|
{
|
||||||
@ -34,5 +37,10 @@ namespace Selector.Web.Hubs
|
|||||||
var deserialised = JsonSerializer.Deserialize<CurrentlyPlayingDTO>(nowPlaying);
|
var deserialised = JsonSerializer.Deserialize<CurrentlyPlayingDTO>(nowPlaying);
|
||||||
await Clients.Caller.OnNewPlaying(deserialised);
|
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">
|
<div class="text-center">
|
||||||
<h1 class="display-4">Now</h1>
|
<h1 class="display-4">Now</h1>
|
||||||
<div id="app" class="col-12">
|
<div id="app">
|
||||||
<now-playing-card :track="currentlyPlaying.track" class="col-md-3"></now-playing-card>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -117,6 +117,8 @@ namespace Selector.Web
|
|||||||
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
services.AddTransient<ISubscriber>(services => services.GetService<ConnectionMultiplexer>().GetSubscriber());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
services.AddSingleton<IRefreshTokenFactoryProvider, CachingRefreshTokenFactoryProvider>();
|
||||||
|
|
||||||
services.AddSingleton<CacheHubProxy>();
|
services.AddSingleton<CacheHubProxy>();
|
||||||
services.AddHostedService<CacheHubProxyService>();
|
services.AddHostedService<CacheHubProxyService>();
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Trace",
|
||||||
"Microsoft": "Warning",
|
"Microsoft": "Warning",
|
||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ export interface nowPlayingProxy {
|
|||||||
|
|
||||||
export interface NowPlayingHubClient {
|
export interface NowPlayingHubClient {
|
||||||
OnNewPlaying: (context: CurrentlyPlayingDTO) => void;
|
OnNewPlaying: (context: CurrentlyPlayingDTO) => void;
|
||||||
|
OnNewAudioFeature: (features: TrackAudioFeatures) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NowPlayingHub {
|
export interface NowPlayingHub {
|
||||||
@ -106,6 +107,27 @@ export interface FullTrack {
|
|||||||
isLocal: boolean;
|
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 {
|
export interface LinkedTrack {
|
||||||
externalUrls: { [key: string]: string; };
|
externalUrls: { [key: string]: string; };
|
||||||
href: 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 signalR from "@microsoft/signalr";
|
||||||
import * as Vue from "vue";
|
import * as Vue from "vue";
|
||||||
import { FullTrack, CurrentlyPlayingDTO } from "./HubInterfaces";
|
import { TrackAudioFeatures, CurrentlyPlayingDTO } from "./HubInterfaces";
|
||||||
import NowPlayingCard from "./NowPlayingCard";
|
import NowPlayingCard from "./Now/NowPlayingCard";
|
||||||
|
import { PopularityCard, SpotifyLogoLink } from "./Now/Spotify";
|
||||||
|
import BaseInfoCard from "./Now/BaseInfoCard";
|
||||||
|
|
||||||
const connection = new signalR.HubConnectionBuilder()
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl("/hub")
|
.withUrl("/hub")
|
||||||
@ -13,32 +15,22 @@ connection.start()
|
|||||||
})
|
})
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
|
|
||||||
|
interface InfoCard {
|
||||||
|
html: string
|
||||||
|
}
|
||||||
|
|
||||||
interface NowPlaying {
|
interface NowPlaying {
|
||||||
currentlyPlaying?: CurrentlyPlayingDTO
|
currentlyPlaying?: CurrentlyPlayingDTO,
|
||||||
|
trackFeatures?: TrackAudioFeatures,
|
||||||
|
cards: InfoCard[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentlyPlaying: {
|
currentlyPlaying: undefined,
|
||||||
track: {
|
trackFeatures: undefined,
|
||||||
name: "No Playback",
|
cards: []
|
||||||
album: {
|
|
||||||
name: "",
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
artists: [
|
|
||||||
{
|
|
||||||
name: ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
externalUrls: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as NowPlaying
|
} as NowPlaying
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -46,9 +38,24 @@ const app = Vue.createApp({
|
|||||||
{
|
{
|
||||||
console.log(context);
|
console.log(context);
|
||||||
this.currentlyPlaying = 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("now-playing-card", NowPlayingCard);
|
||||||
|
app.component("info-card", BaseInfoCard);
|
||||||
|
app.component("popularity", PopularityCard);
|
||||||
|
app.component("spotify-logo", SpotifyLogoLink);
|
||||||
const vm = app.mount('#app');
|
const vm = app.mount('#app');
|
@ -27,6 +27,10 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
return $"{currentPlaying.IsPlaying}, {episode.DisplayString()}, {currentPlaying.Device?.DisplayString()}";
|
return $"{currentPlaying.IsPlaying}, {episode.DisplayString()}, {currentPlaying.Device?.DisplayString()}";
|
||||||
}
|
}
|
||||||
|
else if (currentPlaying.Item is null)
|
||||||
|
{
|
||||||
|
return $"{currentPlaying.IsPlaying}, no item, {currentPlaying.Device?.DisplayString()}";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Unknown playing type");
|
throw new ArgumentException("Unknown playing type");
|
||||||
|
@ -75,6 +75,7 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
Logger.LogDebug($"Playback started: {Live.DisplayString()}");
|
Logger.LogDebug($"Playback started: {Live.DisplayString()}");
|
||||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
||||||
|
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
||||||
}
|
}
|
||||||
// STOPPED PLAYBACK
|
// STOPPED PLAYBACK
|
||||||
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
|
else if((previous.Item is FullTrack || previous.Item is FullEpisode)
|
||||||
@ -82,6 +83,7 @@ namespace Selector
|
|||||||
{
|
{
|
||||||
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}");
|
Logger.LogDebug($"Playback stopped: {previous.DisplayString()}");
|
||||||
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
OnPlayingChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
||||||
|
OnItemChange(ListeningChangeEventArgs.From(previous, Live, Past, id: Id, username: SpotifyUsername));
|
||||||
}
|
}
|
||||||
// CONTINUING PLAYBACK
|
// CONTINUING PLAYBACK
|
||||||
else {
|
else {
|
||||||
|
Loading…
Reference in New Issue
Block a user