animation of remote display

This commit is contained in:
2023-11-03 18:56:16 +01:00
parent 5d13e6a9e2
commit 5266a6e996
8 changed files with 246 additions and 108 deletions

View File

@@ -9,16 +9,25 @@
background: conic-gradient( blue, green, red, blue );
animation: gradientAnim 10s infinite linear;
background-position: center;
transition: all 0.1s;
}
.beat {
height: 100%;
width: 100%;
background-color: rgba( 0, 0, 0, 0.2 );
animation: beatAnim 0.6s infinite linear;
display: none;
}
@keyframes beatAnim {
0% {
background-color: rgba( 0, 0, 0, 0.2 );
}
50% {
background-color: rgba( 0, 0, 0, 0 );
}
}
@keyframes gradientAnim {
from {
transform: rotate( 0deg );

View File

@@ -1,52 +1,3 @@
:root, :root.light {
--primary-color: #2c3e50;
--accent-background: rgb(30, 30, 82);
--secondary-color: white;
--background-color: white;
--popup-color: rgb(224, 224, 224);
--accent-color: #42b983;
--hover-color: rgb(165, 165, 165);
--accent-background-hover: rgb(124, 140, 236);
--overlay-color: rgba(0, 0, 0, 0.7);
--border-color: rgb(100, 100, 100);
--highlight-backdrop: rgb(143, 134, 192);
--hint-color: rgb(174, 210, 221);
--PI: 3.14159265358979;
}
:root.dark {
--primary-color: white;
--accent-background: rgb(56, 56, 112);
--secondary-color: white;
--background-color: rgb(32, 32, 32);
--popup-color: rgb(58, 58, 58);
--accent-color: #42b983;
--hover-color: rgb(83, 83, 83);
--accent-background-hover: #4380a8;
--overlay-color: rgba(104, 104, 104, 0.575);
--border-color: rgb(190, 190, 190);
--highlight-backdrop: rgb(85, 63, 207);
--hint-color: rgb(88, 91, 110);
}
@media ( prefers-color-scheme: dark ) {
:root {
--primary-color: white;
--accent-background: rgb(56, 56, 112);
--secondary-color: white;
--background-color: rgb(32, 32, 32);
--popup-color: rgb(58, 58, 58);
--accent-color: #42b983;
--hover-color: rgb(83, 83, 83);
--accent-background-hover: #4380a8;
--overlay-color: rgba(104, 104, 104, 0.575);
--border-color: rgb(190, 190, 190);
--highlight-backdrop: rgb(85, 63, 207);
--hint-color: rgb(88, 91, 110);
}
}
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
@@ -60,6 +11,7 @@ body, html {
height: 100%;
margin: 0;
padding: 0;
color: white;
}
body {
@@ -145,7 +97,8 @@ body {
width: 80%;
margin: 2px;
padding: 1vh;
border: 1px var( --border-color ) solid;
border: 1px white solid;
background-color: rgba( 0, 0, 0, 0.4 );
}
.song-details-wrapper {
@@ -172,13 +125,26 @@ body {
user-select: none;
}
.current-song {
.current-song-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 55vh;
width: 100%;
margin-bottom: 0.5%;
margin-top: 0.5%;
}
.current-song {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 2vh;
padding: 1vh;
text-align: center;
background-color: rgba( 0, 0, 0, 0.4 );
}
.fancy-view-song-art {
@@ -196,4 +162,11 @@ body {
#app {
background-color: rgba( 0, 0, 0, 0 );
}
#progress {
background-color: rgba(45, 28, 145);
width: 30vw;
border: none;
border-radius: 50px;
}

View File

@@ -14,12 +14,14 @@
<body>
<div class="content" id="app">
<div v-if="hasLoaded" style="width: 100%">
<div class="current-song">
<div class="current-song-wrapper">
<span class="material-symbols-outlined fancy-view-song-art" v-if="!playingSong.hasCoverArt">music_note</span>
<img v-else :src="'/getSongCover?filename=' + playingSong.filename" class="fancy-view-song-art">
<progress max="1000" id="progress" :value="progressBar"></progress>
<h1>{{ playingSong.title }}</h1>
<p>{{ playingSong.artist }}</p>
<div class="current-song">
<progress max="1000" id="progress" :value="progressBar"></progress>
<h1>{{ playingSong.title }}</h1>
<p>{{ playingSong.artist }}</p>
</div>
</div>
<div class="song-list-wrapper">
<div v-for="song in songQueue" class="song-list">
@@ -37,6 +39,9 @@
<h3>{{ song.title }}</h3>
<p>{{ song.artist }}</p>
</div>
<div class="time-until">
{{ getTimeUntil( song ) }}
</div>
</div>
<!-- <img :src="" alt=""> -->
</div>

View File

@@ -27,6 +27,22 @@ createApp( {
}
}
return ret;
},
getTimeUntil( ) {
return ( song ) => {
let timeRemaining = 0;
for ( let i = this.queuePos; i < this.songs.length; i++ ) {
if ( this.songs[ i ] == song ) {
break;
}
timeRemaining += parseInt( this.songs[ i ].duration );
}
if ( timeRemaining === 0 ) {
return 'Currently playing';
} else {
return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min';
}
}
}
},
methods: {
@@ -34,12 +50,12 @@ createApp( {
this.startTime = new Date().getTime();
this.timeTracker = setInterval( () => {
this.pos += 0.075;
this.progressBar = this.pos / this.playingSong.duration * 1000;
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
}, 75 );
this.timeCorrector = setInterval( () => {
this.pos = this.oldPos + ( new Date().getTime() - this.startTime ) / 1000;
this.progressBar = this.pos / this.playingSong.duration * 1000;
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
}, 5000 );
},
stopTimeTracker () {
@@ -47,6 +63,20 @@ createApp( {
clearInterval( this.timeCorrector );
this.oldPos = this.pos;
},
// getTimeUntil( song ) {
// let timeRemaining = 0;
// for ( let i = this.queuePos; i < this.songs.length; i++ ) {
// if ( this.songs[ i ] == song ) {
// break;
// }
// timeRemaining += parseInt( this.songs[ i ].duration );
// }
// if ( timeRemaining === 0 ) {
// return 'Currently playing';
// } else {
// return 'Playing in about ' + Math.ceil( timeRemaining / 60 ) + 'min';
// }
// },
connect() {
let source = new EventSource( '/clientDisplayNotifier', { withCredentials: true } );
source.onmessage = ( e ) => {
@@ -62,9 +92,9 @@ createApp( {
this.songs = data.data.songQueue ?? [];
this.pos = data.data.pos ?? 0;
this.oldPos = data.data.pos ?? 0;
this.startTime = new Date().getTime();
this.progressBar = this.pos / this.playingSong.duration * 1000;
this.queuePos = data.data.queuePos ?? 0;
if ( this.isPlaying ) this.startTimeTracker();
getColourPalette( '/getSongCover?filename=' + data.data.playingSong.filename ).then( palette => {
this.colourPalette = palette;
this.handleBackground();
@@ -109,7 +139,8 @@ createApp( {
}, false );
},
handleBackground() {
// TODO: Consider using mic and realtime-bpm-analyzer
// TODO: Add hotkeys
// TODO: Check that colours are not too similar
let colours = {};
if ( this.colourPalette[ 0 ] ) {
for ( let i = 0; i < 3; i++ ) {
@@ -117,23 +148,17 @@ createApp( {
}
}
$( '#background' ).css( 'background', `conic-gradient( ${ colours[ 0 ] }, ${ colours[ 1 ] }, ${ colours[ 2 ] }, ${ colours[ 0 ] } )` );
// if ( this.playingSong.bpm && this.isPlaying ) {
// $( '.beat' ).show();
// $( '.beat' ).css( 'animation-duration', 60 / this.playingSong.bpm );
// $( '.beat' ).css( 'animation-delay', this.pos % ( 60 / this.playingSong.bpm * this.pos ) );
// } else {
// $( '.beat' ).hide();
// }
if ( this.playingSong.bpm && this.isPlaying ) {
$( '.beat' ).show();
$( '.beat' ).css( 'animation-duration', 60 / this.playingSong.bpm );
$( '.beat' ).css( 'animation-delay', this.pos % ( 60 / this.playingSong.bpm * this.pos ) + this.playingSong.bpmOffset - ( 60 / this.playingSong.bpm * this.pos / 2 ) );
} else {
$( '.beat' ).hide();
}
}
},
mounted() {
this.connect();
// Initialize Web Audio API components
const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
// Start audio analysis
navigator.mediaDevices.getUserMedia( { audio: true } ).then( ( stream ) => {
} );
},
watch: {
isPlaying( value ) {

View File

@@ -124,7 +124,7 @@
import FancyView from './fancyView.vue';
import Notifications from './notifications.vue';
import SliderView from './sliderView.vue';
import * as realtimeBPM from 'realtime-bpm-analyzer';
import { guess } from 'web-audio-beat-detector';
export default {
data() {
@@ -140,6 +140,7 @@ export default {
durationBeautified: '--:--',
hasLoadedSongs: false,
isShowingFancyView: false,
notifier: null,
}
},
components: {
@@ -193,15 +194,14 @@ export default {
const audioContext = new AudioContext();
fetch( 'http://localhost:8081/getSongFile?filename=' + filename ).then( res => {
res.arrayBuffer().then( buf => {
// The file is uploaded, now we decode it
audioContext.decodeAudioData( buf, audioBuffer => {
// The result is passed to the analyzer
realtimeBPM.analyzeFullBuffer( audioBuffer ).then( topCandidates => {
// Do something with the BPM
this.playingSong.bpm = topCandidates[ 0 ].tempo;
guess( audioBuffer ).then( ( data ) => {
this.playingSong.bpm = data.bpm;
this.playingSong.accurateTempo = data.tempo;
this.playingSong.bpmOffset = data.offset;
this.sendUpdate( 'playingSong' );
} );
});
} );
} );
} );
}
@@ -250,12 +250,16 @@ export default {
this.playbackPosBeautified += secondCount;
}
}, 0.02 );
this.progressTracker = setInterval( () => {
this.sendUpdate( 'pos' );
}, 5000 );
this.sendUpdate( 'isPlaying' );
} else if ( action === 'pause' ) {
this.$emit( 'update', { 'type': 'playback', 'status': false } );
musicPlayer.pause();
try {
clearInterval( this.progressTracker );
clearInterval( this.notifier );
} catch ( err ) {};
this.isPlaying = false;
this.sendUpdate( 'isPlaying' );