diff --git a/backend/ui/index.html b/backend/ui/index.html index 1a6aa64..291e35d 100644 --- a/backend/ui/index.html +++ b/backend/ui/index.html @@ -11,7 +11,7 @@ -
Designed and developed by Janis Hutz https://janishutz.com
+
Designed and developed by Janis Hutz https://janishutz.com
@@ -27,17 +27,6 @@
- music_note - - -
-
-
-
-
-
-
- pause

{{ song.title }}

{{ song.artist }}

diff --git a/backend/ui/showcase.css b/backend/ui/showcase.css index 8d5bb75..adb209a 100644 --- a/backend/ui/showcase.css +++ b/backend/ui/showcase.css @@ -48,52 +48,18 @@ body { 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 { width: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column; + margin-bottom: 5%; } .song-list { display: flex; flex-direction: row; - justify-content: center; align-items: center; width: 80%; margin: 2px; @@ -107,6 +73,7 @@ body { display: block; margin-left: 10px; margin-right: auto; + width: 65%; } .song-list .song-image { @@ -128,22 +95,20 @@ body { .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.25%; + margin-bottom: 2%; + margin-top: 1%; } .current-song { display: flex; - justify-content: center; align-items: center; flex-direction: column; margin-top: 1vh; padding: 1vh; + max-width: 80%; text-align: center; background-color: rgba( 0, 0, 0, 0.4 ); } @@ -157,19 +122,27 @@ body { font-size: 30vh !important; } -#canvas { - display: none; -} - #app { background-color: rgba( 0, 0, 0, 0 ); } -#progress { +#progress, #progress::-webkit-progress-bar { background-color: rgba(45, 28, 145); + color: rgba(45, 28, 145); width: 30vw; 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 { @@ -198,4 +171,9 @@ body { margin: 0; padding: 0; top: 50%; +} + +.time-until { + width: 30%; + text-align: end; } \ No newline at end of file diff --git a/backend/ui/showcase.js b/backend/ui/showcase.js index 2609216..84325a8 100644 --- a/backend/ui/showcase.js +++ b/backend/ui/showcase.js @@ -11,8 +11,6 @@ createApp( { pos: 0, queuePos: 0, colourPalette: [], - startTime: 0, - offsetTime: 0, progressBar: 0, timeTracker: null, }; @@ -54,11 +52,13 @@ createApp( { }, methods: { startTimeTracker () { - this.startTime = new Date().getTime(); 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; - }, 75 ); + if ( isNaN( this.progressBar ) ) { + this.progressBar = 0; + } + }, 100 ); }, stopTimeTracker () { clearInterval( this.timeTracker ); @@ -79,13 +79,11 @@ 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; } else if ( data.type === 'pos' ) { this.pos = data.data; this.oldPos = data.data; - this.startTime = new Date().getTime(); this.progressBar = data.data / this.playingSong.duration * 1000; } else if ( data.type === 'isPlaying' ) { this.isPlaying = data.data; @@ -102,12 +100,18 @@ createApp( { this.hasLoaded = true; }; + let self = this; + source.addEventListener( 'error', function( e ) { if ( e.eventPhase == EventSource.CLOSED ) source.close(); if ( e.target.readyState == EventSource.CLOSED ) { console.log( 'disconnected' ); } + + setTimeout( () => { + self.connect(); + }, 1000 ); }, false ); }, }, diff --git a/frontend/src/app.js b/frontend/src/app.js index 65c9461..7d7337c 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -19,13 +19,14 @@ app.use( session( { resave: false, } ) ); +const conf = JSON.parse( fs.readFileSync( path.join( __dirname + '/config/config.json' ) ) ); // TODO: Import from config -const remoteURL = 'http://localhost:3000'; +const remoteURL = conf.connectionURL ?? 'http://localhost:3000'; let hasConnected = false; const connect = () => { - if ( authKey !== '' ) { + if ( authKey !== '' && conf.doConnect ) { axios.post( remoteURL + '/connect', { 'authKey': authKey } ).then( res => { if ( res.status === 200 ) { console.log( '[ BACKEND INTEGRATION ] Connection successful' ); @@ -42,10 +43,7 @@ const connect = () => { } }; -let authKey = ''; -try { - authKey = '' + fs.readFileSync( path.join( __dirname + '/config/authKey.txt' ) ); -} catch( err ) {}; +let authKey = conf.authKey ?? ''; connect(); @@ -69,8 +67,8 @@ app.get( '/', ( request, response ) => { } ); app.get( '/openSongs', ( req, res ) => { - res.send( '{ "data": [ "/home/janis/Music/KB2022" ] }' ); - // res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' ); + // res.send( '{ "data": [ "/home/janis/Music/KB2022" ] }' ); + res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' ); // res.send( { 'data': dialog.showOpenDialogSync( { properties: [ 'openDirectory' ], title: 'Open music library folder' } ) } ); } ); @@ -119,6 +117,24 @@ app.get( '/mainNotifier', ( req, res ) => { } ); 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 ) { 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 if ( hasConnected ) { - if ( update === 'pos' ) { - return; - } else if ( update === 'playingSong' ) { - currentDetails[ update ][ 'startTime' ] === new Date().getTime(); + if ( update === 'isPlaying' ) { + axios.post( remoteURL + '/statusUpdate', { 'type': 'playingSong', 'data': currentDetails[ 'playingSong' ], 'authKey': authKey } ).catch( err => { + console.error( err ); + } ); + + 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 => { - if ( res.status !== 200 ) { - console.log( res ); - } - } ).catch( err => { + axios.post( remoteURL + '/statusUpdate', { 'type': update, 'data': currentDetails[ update ], 'authKey': authKey } ).catch( err => { console.error( err ); } ); } else { diff --git a/frontend/src/client/showcase.css b/frontend/src/client/showcase.css index d2a0090..bc3c4b3 100644 --- a/frontend/src/client/showcase.css +++ b/frontend/src/client/showcase.css @@ -156,19 +156,27 @@ body { font-size: 30vh !important; } -#canvas { - display: none; -} - #app { background-color: rgba( 0, 0, 0, 0 ); } -#progress { +#progress, #progress::-webkit-progress-bar { background-color: rgba(45, 28, 145); + color: rgba(45, 28, 145); width: 30vw; 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 { diff --git a/frontend/src/client/showcase.html b/frontend/src/client/showcase.html index 4fb3b99..8cc5596 100644 --- a/frontend/src/client/showcase.html +++ b/frontend/src/client/showcase.html @@ -12,7 +12,7 @@ -
Designed and developed by Janis Hutz https://janishutz.com
+
Designed and developed by Janis Hutz https://janishutz.com
@@ -64,7 +64,6 @@
-
diff --git a/frontend/src/client/showcase.js b/frontend/src/client/showcase.js index 0fc1c40..21c7055 100644 --- a/frontend/src/client/showcase.js +++ b/frontend/src/client/showcase.js @@ -11,11 +11,8 @@ createApp( { pos: 0, queuePos: 0, colourPalette: [], - startTime: 0, - offsetTime: 0, progressBar: 0, timeTracker: null, - timeCorrector: null, visualizationSettings: 'mic', micAnalyzer: null, beatDetected: false, @@ -59,20 +56,16 @@ createApp( { }, methods: { startTimeTracker () { - this.startTime = new Date().getTime(); 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; - }, 75 ); - - this.timeCorrector = setInterval( () => { - this.pos = this.oldPos + ( new Date().getTime() - this.startTime ) / 1000; - this.progressBar = ( this.pos / this.playingSong.duration ) * 1000; - }, 5000 ); + if ( isNaN( this.progressBar ) ) { + this.progressBar = 0; + } + }, 100 ); }, stopTimeTracker () { clearInterval( this.timeTracker ); - clearInterval( this.timeCorrector ); this.oldPos = this.pos; }, getImageData() { @@ -109,7 +102,6 @@ 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; this.getImageData().then( palette => { @@ -122,7 +114,6 @@ createApp( { } else if ( data.type === 'pos' ) { this.pos = data.data; this.oldPos = data.data; - this.startTime = new Date().getTime(); this.progressBar = data.data / this.playingSong.duration * 1000; } else if ( data.type === 'isPlaying' ) { this.isPlaying = data.data; @@ -146,6 +137,8 @@ createApp( { source.onopen = () => { this.hasLoaded = true; }; + + let self = this; source.addEventListener( 'error', function( e ) { if ( e.eventPhase == EventSource.CLOSED ) source.close(); @@ -153,6 +146,11 @@ createApp( { if ( e.target.readyState == EventSource.CLOSED ) { console.log( 'disconnected' ); } + + // TODO: Notify about disconnect + setTimeout( () => { + self.connect(); + }, 1000 ); }, false ); }, handleBackground() { @@ -206,28 +204,32 @@ createApp( { this.setVisualization(); }, setVisualization () { - if ( this.visualizationSettings === 'bpm' ) { - 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 { + if ( Object.keys( this.playingSong ).length > 0 ) { + if ( this.visualizationSettings === 'bpm' ) { + 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(); + } + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + } else if ( this.visualizationSettings === 'off' ) { $( '.beat' ).hide(); + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + } else if ( this.visualizationSettings === 'mic' ) { + $( '.beat-manual' ).hide(); + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + this.micAudioHandler(); } - try { - clearInterval( this.micAnalyzer ); - } catch ( err ) {} - } else if ( this.visualizationSettings === 'off' ) { - $( '.beat' ).hide(); - try { - clearInterval( this.micAnalyzer ); - } catch ( err ) {} - } else if ( this.visualizationSettings === 'mic' ) { - $( '.beat-manual' ).hide(); - try { - clearInterval( this.micAnalyzer ); - } catch ( err ) {} - this.micAudioHandler(); + } else { + console.log( 'not playing yet' ); } }, micAudioHandler () { @@ -288,9 +290,9 @@ createApp( { }, mounted() { this.connect(); - if ( this.visualizationSettings === 'mic' ) { - this.micAudioHandler(); - } + // if ( this.visualizationSettings === 'mic' ) { + // this.micAudioHandler(); + // } }, watch: { isPlaying( value ) { diff --git a/frontend/src/components/player.vue b/frontend/src/components/player.vue index 347acd6..70a8431 100644 --- a/frontend/src/components/player.vue +++ b/frontend/src/components/player.vue @@ -31,7 +31,7 @@
{{ playbackPosBeautified }}
-
{{ durationBeautified }}
+
{{ durationBeautified }}
@@ -142,6 +142,7 @@ export default { hasLoadedSongs: false, isShowingFancyView: false, notifier: null, + isShowingRemainingTime: false, } }, components: { @@ -208,6 +209,22 @@ export default { } }, 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 ) { let data = {}; if ( update === 'pos' ) { @@ -250,14 +267,27 @@ export default { } else { this.playbackPosBeautified += secondCount; } - }, 0.02 ); - this.progressTracker = setInterval( () => { - this.sendUpdate( 'pos' ); - }, 5000 ); + + 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( 'isPlaying' ); } else if ( action === 'pause' ) { this.$emit( 'update', { 'type': 'playback', 'status': false } ); musicPlayer.pause(); + this.sendUpdate( 'pos' ); try { clearInterval( this.progressTracker ); clearInterval( this.notifier ); diff --git a/frontend/src/config/authKey.txt b/frontend/src/config/authKey.txt deleted file mode 100644 index 0169bdf..0000000 --- a/frontend/src/config/authKey.txt +++ /dev/null @@ -1 +0,0 @@ -gaöwovwef89voawö8p9 odövefw8öoaewpf89wec \ No newline at end of file diff --git a/frontend/src/config/config.json b/frontend/src/config/config.json new file mode 100644 index 0000000..6d4cf6a --- /dev/null +++ b/frontend/src/config/config.json @@ -0,0 +1,5 @@ +{ + "connectionURL": "http://localhost:3000", + "authKey": "gaöwovwef89voawö8p9 odövefw8öoaewpf89wec", + "doConnect": true +} \ No newline at end of file