almost finished

This commit is contained in:
2023-11-09 21:06:41 +01:00
parent 5037663fd5
commit 6bbc97f2c3
10 changed files with 167 additions and 133 deletions

View File

@@ -11,7 +11,7 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
</head> </head>
<body> <body>
<div class="info">Designed and developed by Janis Hutz https://janishutz.com</div> <div class="info">Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a></div>
<div class="content" id="app"> <div class="content" id="app">
<div v-if="hasLoaded" style="width: 100%"> <div v-if="hasLoaded" style="width: 100%">
<div class="current-song-wrapper"> <div class="current-song-wrapper">
@@ -27,17 +27,6 @@
</div> </div>
<div class="song-list-wrapper"> <div class="song-list-wrapper">
<div v-for="song in songQueue" class="song-list"> <div v-for="song in songQueue" class="song-list">
<span class="material-symbols-outlined song-image" v-if="!song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying )">music_note</span>
<img v-else-if="song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying ) && song.coverArtOrigin === 'api'" :src="song.coverArtURL" class="song-image">
<img v-else-if="song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying ) && song.coverArtOrigin !== 'api'" :src="'/getSongCover?filename=' + song.filename" class="song-image">
<div v-if="playingSong.filename === song.filename && isPlaying" class="playing-symbols">
<div class="playing-symbols-wrapper">
<div class="playing-bar" id="bar-1"></div>
<div class="playing-bar" id="bar-2"></div>
<div class="playing-bar" id="bar-3"></div>
</div>
</div>
<span class="material-symbols-outlined pause-icon" v-if="!isPlaying && playingSong.filename === song.filename">pause</span>
<div class="song-details-wrapper"> <div class="song-details-wrapper">
<h3>{{ song.title }}</h3> <h3>{{ song.title }}</h3>
<p>{{ song.artist }}</p> <p>{{ song.artist }}</p>

View File

@@ -48,52 +48,18 @@ body {
flex-direction: row; flex-direction: row;
} }
.playing-bar {
height: 60%;
background-color: white;
width: 10%;
border-radius: 50px;
margin: auto;
}
#bar-1 {
animation: music-playing 0.9s infinite ease-in-out;
}
#bar-2 {
animation: music-playing 0.9s infinite ease-in-out;
animation-delay: 0.3s;
}
#bar-3 {
animation: music-playing 0.9s infinite ease-in-out;
animation-delay: 0.6s;
}
@keyframes music-playing {
0% {
transform: scaleY( 1 );
}
50% {
transform: scaleY( 0.5 );
}
100% {
transform: scaleY( 1 );
}
}
.song-list-wrapper { .song-list-wrapper {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
margin-bottom: 5%;
} }
.song-list { .song-list {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center;
align-items: center; align-items: center;
width: 80%; width: 80%;
margin: 2px; margin: 2px;
@@ -107,6 +73,7 @@ body {
display: block; display: block;
margin-left: 10px; margin-left: 10px;
margin-right: auto; margin-right: auto;
width: 65%;
} }
.song-list .song-image { .song-list .song-image {
@@ -128,22 +95,20 @@ body {
.current-song-wrapper { .current-song-wrapper {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
height: 55vh;
width: 100%; width: 100%;
margin-bottom: 0.5%; margin-bottom: 2%;
margin-top: 0.25%; margin-top: 1%;
} }
.current-song { .current-song {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
margin-top: 1vh; margin-top: 1vh;
padding: 1vh; padding: 1vh;
max-width: 80%;
text-align: center; text-align: center;
background-color: rgba( 0, 0, 0, 0.4 ); background-color: rgba( 0, 0, 0, 0.4 );
} }
@@ -157,19 +122,27 @@ body {
font-size: 30vh !important; font-size: 30vh !important;
} }
#canvas {
display: none;
}
#app { #app {
background-color: rgba( 0, 0, 0, 0 ); background-color: rgba( 0, 0, 0, 0 );
} }
#progress { #progress, #progress::-webkit-progress-bar {
background-color: rgba(45, 28, 145); background-color: rgba(45, 28, 145);
color: rgba(45, 28, 145);
width: 30vw; width: 30vw;
border: none; border: none;
border-radius: 50px; border-radius: 0px;
accent-color: white;
-webkit-appearance: none;
appearance: none;
}
#progress::-moz-progress-bar {
background-color: white;
}
#progress::-webkit-progress-value {
background-color: white !important;
} }
.mode-selector-wrapper { .mode-selector-wrapper {
@@ -199,3 +172,8 @@ body {
padding: 0; padding: 0;
top: 50%; top: 50%;
} }
.time-until {
width: 30%;
text-align: end;
}

View File

@@ -11,8 +11,6 @@ createApp( {
pos: 0, pos: 0,
queuePos: 0, queuePos: 0,
colourPalette: [], colourPalette: [],
startTime: 0,
offsetTime: 0,
progressBar: 0, progressBar: 0,
timeTracker: null, timeTracker: null,
}; };
@@ -54,11 +52,13 @@ createApp( {
}, },
methods: { methods: {
startTimeTracker () { startTimeTracker () {
this.startTime = new Date().getTime();
this.timeTracker = setInterval( () => { this.timeTracker = setInterval( () => {
this.pos = this.playingSong.startTime - new Date().getTime() / 1000; this.pos = ( new Date().getTime() - this.playingSong.startTime ) / 1000 + this.oldPos;
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000; this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
}, 75 ); if ( isNaN( this.progressBar ) ) {
this.progressBar = 0;
}
}, 100 );
}, },
stopTimeTracker () { stopTimeTracker () {
clearInterval( this.timeTracker ); clearInterval( this.timeTracker );
@@ -79,13 +79,11 @@ createApp( {
this.songs = data.data.songQueue ?? []; this.songs = data.data.songQueue ?? [];
this.pos = data.data.pos ?? 0; this.pos = data.data.pos ?? 0;
this.oldPos = data.data.pos ?? 0; this.oldPos = data.data.pos ?? 0;
this.startTime = new Date().getTime();
this.progressBar = this.pos / this.playingSong.duration * 1000; this.progressBar = this.pos / this.playingSong.duration * 1000;
this.queuePos = data.data.queuePos ?? 0; this.queuePos = data.data.queuePos ?? 0;
} else if ( data.type === 'pos' ) { } else if ( data.type === 'pos' ) {
this.pos = data.data; this.pos = data.data;
this.oldPos = data.data; this.oldPos = data.data;
this.startTime = new Date().getTime();
this.progressBar = data.data / this.playingSong.duration * 1000; this.progressBar = data.data / this.playingSong.duration * 1000;
} else if ( data.type === 'isPlaying' ) { } else if ( data.type === 'isPlaying' ) {
this.isPlaying = data.data; this.isPlaying = data.data;
@@ -102,12 +100,18 @@ createApp( {
this.hasLoaded = true; this.hasLoaded = true;
}; };
let self = this;
source.addEventListener( 'error', function( e ) { source.addEventListener( 'error', function( e ) {
if ( e.eventPhase == EventSource.CLOSED ) source.close(); if ( e.eventPhase == EventSource.CLOSED ) source.close();
if ( e.target.readyState == EventSource.CLOSED ) { if ( e.target.readyState == EventSource.CLOSED ) {
console.log( 'disconnected' ); console.log( 'disconnected' );
} }
setTimeout( () => {
self.connect();
}, 1000 );
}, false ); }, false );
}, },
}, },

View File

@@ -19,13 +19,14 @@ app.use( session( {
resave: false, resave: false,
} ) ); } ) );
const conf = JSON.parse( fs.readFileSync( path.join( __dirname + '/config/config.json' ) ) );
// TODO: Import from config // TODO: Import from config
const remoteURL = 'http://localhost:3000'; const remoteURL = conf.connectionURL ?? 'http://localhost:3000';
let hasConnected = false; let hasConnected = false;
const connect = () => { const connect = () => {
if ( authKey !== '' ) { if ( authKey !== '' && conf.doConnect ) {
axios.post( remoteURL + '/connect', { 'authKey': authKey } ).then( res => { axios.post( remoteURL + '/connect', { 'authKey': authKey } ).then( res => {
if ( res.status === 200 ) { if ( res.status === 200 ) {
console.log( '[ BACKEND INTEGRATION ] Connection successful' ); console.log( '[ BACKEND INTEGRATION ] Connection successful' );
@@ -42,10 +43,7 @@ const connect = () => {
} }
}; };
let authKey = ''; let authKey = conf.authKey ?? '';
try {
authKey = '' + fs.readFileSync( path.join( __dirname + '/config/authKey.txt' ) );
} catch( err ) {};
connect(); connect();
@@ -69,8 +67,8 @@ app.get( '/', ( request, response ) => {
} ); } );
app.get( '/openSongs', ( req, res ) => { app.get( '/openSongs', ( req, res ) => {
res.send( '{ "data": [ "/home/janis/Music/KB2022" ] }' ); // res.send( '{ "data": [ "/home/janis/Music/KB2022" ] }' );
// res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' ); res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' );
// res.send( { 'data': dialog.showOpenDialogSync( { properties: [ 'openDirectory' ], title: 'Open music library folder' } ) } ); // res.send( { 'data': dialog.showOpenDialogSync( { properties: [ 'openDirectory' ], title: 'Open music library folder' } ) } );
} ); } );
@@ -119,6 +117,24 @@ app.get( '/mainNotifier', ( req, res ) => {
} ); } );
const sendUpdate = ( update ) => { const sendUpdate = ( update ) => {
if ( update === 'pos' ) {
currentDetails[ 'playingSong' ][ 'startTime' ] = new Date().getTime();
for ( let client in connectedClients ) {
connectedClients[ client ].write( 'data: ' + JSON.stringify( { 'type': 'playingSong', 'data': currentDetails[ 'playingSong' ] } ) + '\n\n' );
}
} else if ( update === 'playingSong' ) {
currentDetails[ update ][ 'startTime' ] = new Date().getTime();
} else if ( update === 'isPlaying' ) {
currentDetails[ 'playingSong' ][ 'startTime' ] = new Date().getTime();
for ( let client in connectedClients ) {
connectedClients[ client ].write( 'data: ' + JSON.stringify( { 'type': 'playingSong', 'data': currentDetails[ 'playingSong' ] } ) + '\n\n' );
}
for ( let client in connectedClients ) {
connectedClients[ client ].write( 'data: ' + JSON.stringify( { 'type': 'pos', 'data': currentDetails[ 'pos' ] } ) + '\n\n' );
}
}
for ( let client in connectedClients ) { for ( let client in connectedClients ) {
connectedClients[ client ].write( 'data: ' + JSON.stringify( { 'type': update, 'data': currentDetails[ update ] } ) + '\n\n' ); connectedClients[ client ].write( 'data: ' + JSON.stringify( { 'type': update, 'data': currentDetails[ update ] } ) + '\n\n' );
} }
@@ -126,16 +142,20 @@ const sendUpdate = ( update ) => {
// Check if connected and if not, try to authenticate with data from authKey file // Check if connected and if not, try to authenticate with data from authKey file
if ( hasConnected ) { if ( hasConnected ) {
if ( update === 'pos' ) { if ( update === 'isPlaying' ) {
return; axios.post( remoteURL + '/statusUpdate', { 'type': 'playingSong', 'data': currentDetails[ 'playingSong' ], 'authKey': authKey } ).catch( err => {
} else if ( update === 'playingSong' ) { console.error( err );
currentDetails[ update ][ 'startTime' ] === new Date().getTime(); } );
axios.post( remoteURL + '/statusUpdate', { 'type': 'pos', 'data': currentDetails[ 'pos' ], 'authKey': authKey } ).catch( err => {
console.error( err );
} );
} else if ( update === 'pos' ) {
axios.post( remoteURL + '/statusUpdate', { 'type': 'playingSong', 'data': currentDetails[ 'playingSong' ], 'authKey': authKey } ).catch( err => {
console.error( err );
} );
} }
axios.post( remoteURL + '/statusUpdate', { 'type': update, 'data': currentDetails[ update ], 'authKey': authKey } ).then( res => { axios.post( remoteURL + '/statusUpdate', { 'type': update, 'data': currentDetails[ update ], 'authKey': authKey } ).catch( err => {
if ( res.status !== 200 ) {
console.log( res );
}
} ).catch( err => {
console.error( err ); console.error( err );
} ); } );
} else { } else {

View File

@@ -156,19 +156,27 @@ body {
font-size: 30vh !important; font-size: 30vh !important;
} }
#canvas {
display: none;
}
#app { #app {
background-color: rgba( 0, 0, 0, 0 ); background-color: rgba( 0, 0, 0, 0 );
} }
#progress { #progress, #progress::-webkit-progress-bar {
background-color: rgba(45, 28, 145); background-color: rgba(45, 28, 145);
color: rgba(45, 28, 145);
width: 30vw; width: 30vw;
border: none; border: none;
border-radius: 50px; border-radius: 0px;
accent-color: white;
-webkit-appearance: none;
appearance: none;
}
#progress::-moz-progress-bar {
background-color: white;
}
#progress::-webkit-progress-value {
background-color: white !important;
} }
.mode-selector-wrapper { .mode-selector-wrapper {

View File

@@ -12,7 +12,7 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
</head> </head>
<body> <body>
<div class="info">Designed and developed by Janis Hutz https://janishutz.com</div> <div class="info">Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a></div>
<div class="content" id="app"> <div class="content" id="app">
<div v-if="hasLoaded" style="width: 100%"> <div v-if="hasLoaded" style="width: 100%">
<div class="current-song-wrapper"> <div class="current-song-wrapper">
@@ -64,7 +64,6 @@
<div class="beat"></div> <div class="beat"></div>
<div class="beat-manual"></div> <div class="beat-manual"></div>
</div> </div>
<canvas id="canvas"></canvas>
</div> </div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

View File

@@ -11,11 +11,8 @@ createApp( {
pos: 0, pos: 0,
queuePos: 0, queuePos: 0,
colourPalette: [], colourPalette: [],
startTime: 0,
offsetTime: 0,
progressBar: 0, progressBar: 0,
timeTracker: null, timeTracker: null,
timeCorrector: null,
visualizationSettings: 'mic', visualizationSettings: 'mic',
micAnalyzer: null, micAnalyzer: null,
beatDetected: false, beatDetected: false,
@@ -59,20 +56,16 @@ createApp( {
}, },
methods: { methods: {
startTimeTracker () { startTimeTracker () {
this.startTime = new Date().getTime();
this.timeTracker = setInterval( () => { this.timeTracker = setInterval( () => {
this.pos += 0.075; this.pos = ( new Date().getTime() - this.playingSong.startTime ) / 1000 + this.oldPos;
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000; this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
}, 75 ); if ( isNaN( this.progressBar ) ) {
this.progressBar = 0;
this.timeCorrector = setInterval( () => { }
this.pos = this.oldPos + ( new Date().getTime() - this.startTime ) / 1000; }, 100 );
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
}, 5000 );
}, },
stopTimeTracker () { stopTimeTracker () {
clearInterval( this.timeTracker ); clearInterval( this.timeTracker );
clearInterval( this.timeCorrector );
this.oldPos = this.pos; this.oldPos = this.pos;
}, },
getImageData() { getImageData() {
@@ -109,7 +102,6 @@ createApp( {
this.songs = data.data.songQueue ?? []; this.songs = data.data.songQueue ?? [];
this.pos = data.data.pos ?? 0; this.pos = data.data.pos ?? 0;
this.oldPos = data.data.pos ?? 0; this.oldPos = data.data.pos ?? 0;
this.startTime = new Date().getTime();
this.progressBar = this.pos / this.playingSong.duration * 1000; this.progressBar = this.pos / this.playingSong.duration * 1000;
this.queuePos = data.data.queuePos ?? 0; this.queuePos = data.data.queuePos ?? 0;
this.getImageData().then( palette => { this.getImageData().then( palette => {
@@ -122,7 +114,6 @@ createApp( {
} else if ( data.type === 'pos' ) { } else if ( data.type === 'pos' ) {
this.pos = data.data; this.pos = data.data;
this.oldPos = data.data; this.oldPos = data.data;
this.startTime = new Date().getTime();
this.progressBar = data.data / this.playingSong.duration * 1000; this.progressBar = data.data / this.playingSong.duration * 1000;
} else if ( data.type === 'isPlaying' ) { } else if ( data.type === 'isPlaying' ) {
this.isPlaying = data.data; this.isPlaying = data.data;
@@ -147,12 +138,19 @@ createApp( {
this.hasLoaded = true; this.hasLoaded = true;
}; };
let self = this;
source.addEventListener( 'error', function( e ) { source.addEventListener( 'error', function( e ) {
if ( e.eventPhase == EventSource.CLOSED ) source.close(); if ( e.eventPhase == EventSource.CLOSED ) source.close();
if ( e.target.readyState == EventSource.CLOSED ) { if ( e.target.readyState == EventSource.CLOSED ) {
console.log( 'disconnected' ); console.log( 'disconnected' );
} }
// TODO: Notify about disconnect
setTimeout( () => {
self.connect();
}, 1000 );
}, false ); }, false );
}, },
handleBackground() { handleBackground() {
@@ -206,6 +204,7 @@ createApp( {
this.setVisualization(); this.setVisualization();
}, },
setVisualization () { setVisualization () {
if ( Object.keys( this.playingSong ).length > 0 ) {
if ( this.visualizationSettings === 'bpm' ) { if ( this.visualizationSettings === 'bpm' ) {
if ( this.playingSong.bpm && this.isPlaying ) { if ( this.playingSong.bpm && this.isPlaying ) {
$( '.beat' ).show(); $( '.beat' ).show();
@@ -229,6 +228,9 @@ createApp( {
} catch ( err ) {} } catch ( err ) {}
this.micAudioHandler(); this.micAudioHandler();
} }
} else {
console.log( 'not playing yet' );
}
}, },
micAudioHandler () { micAudioHandler () {
const audioContext = new ( window.AudioContext || window.webkitAudioContext )(); const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
@@ -288,9 +290,9 @@ createApp( {
}, },
mounted() { mounted() {
this.connect(); this.connect();
if ( this.visualizationSettings === 'mic' ) { // if ( this.visualizationSettings === 'mic' ) {
this.micAudioHandler(); // this.micAudioHandler();
} // }
}, },
watch: { watch: {
isPlaying( value ) { isPlaying( value ) {

View File

@@ -31,7 +31,7 @@
</div> </div>
<div class="playback-pos-info"> <div class="playback-pos-info">
<div style="margin-right: auto;">{{ playbackPosBeautified }}</div> <div style="margin-right: auto;">{{ playbackPosBeautified }}</div>
<div>{{ durationBeautified }}</div> <div @click="toggleShowMode()" style="cursor: pointer;">{{ durationBeautified }}</div>
</div> </div>
<sliderView :active="audioLoaded" :position="playbackPos" :duration="playingSong.duration" @pos="( p ) => { setPos( p ) }" <sliderView :active="audioLoaded" :position="playbackPos" :duration="playingSong.duration" @pos="( p ) => { setPos( p ) }"
name="player"></sliderView> name="player"></sliderView>
@@ -142,6 +142,7 @@ export default {
hasLoadedSongs: false, hasLoadedSongs: false,
isShowingFancyView: false, isShowingFancyView: false,
notifier: null, notifier: null,
isShowingRemainingTime: false,
} }
}, },
components: { components: {
@@ -208,6 +209,22 @@ export default {
} }
}, 300 ); }, 300 );
}, },
toggleShowMode() {
this.isShowingRemainingTime = !this.isShowingRemainingTime;
if ( !this.isShowingRemainingTime ) {
const minuteCounts = Math.floor( this.playingSong.duration / 60 );
this.durationBeautified = String( minuteCounts ) + ':';
if ( ( '' + minuteCounts ).length === 1 ) {
this.durationBeautified = '0' + minuteCounts + ':';
}
const secondCounts = Math.floor( this.playingSong.duration - minuteCounts * 60 );
if ( ( '' + secondCounts ).length === 1 ) {
this.durationBeautified += '0' + secondCounts;
} else {
this.durationBeautified += secondCounts;
}
}
},
sendUpdate( update ) { sendUpdate( update ) {
let data = {}; let data = {};
if ( update === 'pos' ) { if ( update === 'pos' ) {
@@ -250,14 +267,27 @@ export default {
} else { } else {
this.playbackPosBeautified += secondCount; this.playbackPosBeautified += secondCount;
} }
}, 0.02 );
this.progressTracker = setInterval( () => { if ( this.isShowingRemainingTime ) {
const minuteCounts = Math.floor( ( this.playingSong.duration - this.playbackPos ) / 60 );
this.durationBeautified = '-' + String( minuteCounts ) + ':';
if ( ( '' + minuteCounts ).length === 1 ) {
this.durationBeautified = '-0' + minuteCounts + ':';
}
const secondCounts = Math.floor( ( this.playingSong.duration - this.playbackPos ) - minuteCounts * 60 );
if ( ( '' + secondCounts ).length === 1 ) {
this.durationBeautified += '0' + secondCounts;
} else {
this.durationBeautified += secondCounts;
}
}
}, 20 );
this.sendUpdate( 'pos' ); this.sendUpdate( 'pos' );
}, 5000 );
this.sendUpdate( 'isPlaying' ); this.sendUpdate( 'isPlaying' );
} else if ( action === 'pause' ) { } else if ( action === 'pause' ) {
this.$emit( 'update', { 'type': 'playback', 'status': false } ); this.$emit( 'update', { 'type': 'playback', 'status': false } );
musicPlayer.pause(); musicPlayer.pause();
this.sendUpdate( 'pos' );
try { try {
clearInterval( this.progressTracker ); clearInterval( this.progressTracker );
clearInterval( this.notifier ); clearInterval( this.notifier );

View File

@@ -1 +0,0 @@
gaöwovwef89voawö8p9 odövefw8öoaewpf89wec

View File

@@ -0,0 +1,5 @@
{
"connectionURL": "http://localhost:3000",
"authKey": "gaöwovwef89voawö8p9 odövefw8öoaewpf89wec",
"doConnect": true
}