-
-
{{ currentlyPlayingSongName }} by {{ currentlyPlayingSongArtist }}
-
- player.goToPos( pos )"
- v-if="isShowingFullScreenPlayer">
-
- => {
+ return new Promise( ( resolve, reject ) => {
+ fetch( songDetails.url ).then( res => {
+ if ( res.status === 200 ) {
+ res.blob().then( blob => {
+ parseBlob( blob ).then( data => {
+ player.findSongOnAppleMusic( data.common.title ?? songDetails.filename.split( '.' )[ 0 ] ).then( d => {
+ let url = d.data.results.songs.data[ 0 ].attributes.artwork.url;
+ url = url.replace( '{w}', String( d.data.results.songs.data[ 0 ].attributes.artwork.width ) );
+ url = url.replace( '{h}', String( d.data.results.songs.data[ 0 ].attributes.artwork.height ) );
+ const song: Song = {
+ artist: data.common.artist ?? d.data.results.songs.data[ 0 ].attributes.artistName,
+ title: data.common.title ?? d.data.results.songs.data[ 0 ].attributes.name,
+ duration: data.format.duration ?? ( d.data.results.songs.data[ 0 ].attributes.durationInMillis / 1000 ),
+ id: songDetails.url,
+ origin: 'disk',
+ cover: url
+ }
+ resolve( song );
+ } ).catch( e => {
+ reject( e );
+ } );
+ } ).catch( e => {
+ reject( e );
+ } );
+ } ).catch( e => {
+ reject( e );
+ } );
+ }
+ } ).catch( e => {
+ reject( e );
+ } );
+ } );
+ }
+
const getDetails = () => {
const details = player.getPlayingSong();
currentlyPlayingSongName.value = details.title;
coverArt.value = details.cover;
- currentlyPlayingSongIndex.value = player.getPlayingSongID();
+ currentlyPlayingSongIndex.value = player.getQueueID();
playlist.value = player.getQueue();
- console.log( playlist.value );
currentlyPlayingSongArtist.value = details.artist;
}
@@ -207,7 +288,10 @@
let progressTracker = 0;
+ let hasReachedEnd = false;
+ let hasStarted = false;
const startProgressTracker = () => {
+ hasReachedEnd = false;
isPlaying.value = true;
const playingSong = player.getPlayingSong();
duration.value = playingSong.duration;
@@ -224,9 +308,18 @@
}
progressTracker = setInterval( () => {
pos.value = player.getPlaybackPos();
- if ( pos.value > playingSong.duration - 1 ) {
- // TODO: repeat
- control( 'next' );
+ if ( pos.value > playingSong.duration - 1 && !hasReachedEnd ) {
+ stopProgressTracker();
+ hasReachedEnd = true;
+ if ( repeatMode.value === '_one_on' ) {
+ player.goToPos( 0 );
+ } else {
+ control( 'next' );
+ }
+ }
+
+ if ( pos.value > 0 && !hasStarted ) {
+ hasStarted = true;
}
const minuteCount = Math.floor( pos.value / 60 );
@@ -264,6 +357,49 @@
isPlaying.value = false;
}
+ const moveSong = ( move: SongMove ) => {
+ player.moveSong( move );
+ getDetails();
+ }
+
+ const addNewSongs = async ( songs: ReadFile[] ) => {
+ let n = notifications.value.createNotification( 'Analyzing new songs', 200, 'progress', 'normal' );
+ playlist.value = player.getPlaylist();
+ for ( let element in songs ) {
+ try {
+ playlist.value.push( await fetchSongData( songs[ element ] ) );
+ } catch ( e ) {
+ console.error( e );
+ }
+ notifications.value.updateNotification( n, `Analyzing new songs (${element}/${songs.length})` );
+ }
+ player.setPlaylist( playlist.value );
+ player.prepare( 0 );
+ isPlaying.value = true;
+ setTimeout( () => {
+ startProgressTracker();
+ getDetails();
+ }, 2000 );
+ notifications.value.cancelNotification( n );
+ notifications.value.createNotification( 'New songs added', 10, 'ok', 'normal' );
+ }
+
+ emits( 'playerStateChange', isShowingFullScreenPlayer.value ? 'show' : 'hide' );
+
+ document.addEventListener( 'keydown', ( e ) => {
+ if ( e.key === ' ' ) {
+ // TODO: fix
+ e.preventDefault();
+ playPause();
+ } else if ( e.key === 'ArrowRight' ) {
+ e.preventDefault();
+ control( 'next' );
+ } else if ( e.key === 'ArrowLeft' ) {
+ e.preventDefault();
+ control( 'previous' );
+ }
+ } );
+
defineExpose( {
logIntoAppleMusic,
getPlaylists,
@@ -271,12 +407,12 @@
getAuth,
skipLogin,
selectPlaylist,
+ selectCustomPlaylist,
} );
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/components/playlistView.vue b/MusicPlayerV2-GUI/src/components/playlistView.vue
index c67ec06..0cca5fd 100644
--- a/MusicPlayerV2-GUI/src/components/playlistView.vue
+++ b/MusicPlayerV2-GUI/src/components/playlistView.vue
@@ -1,7 +1,12 @@
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/components/playlistsView.vue b/MusicPlayerV2-GUI/src/components/playlistsView.vue
index 4d1da10..c17635a 100644
--- a/MusicPlayerV2-GUI/src/components/playlistsView.vue
+++ b/MusicPlayerV2-GUI/src/components/playlistsView.vue
@@ -1,10 +1,16 @@
{{ nicePlaybackPos }}
-/
-{{ niceDuration }}
+
+
+
@@ -46,7 +60,9 @@
import playlistView from '@/components/playlistView.vue';
import MusicKitJSWrapper from '@/scripts/music-player';
import sliderView from './sliderView.vue';
- import type { Song } from '@/scripts/song';
+ import type { ReadFile, Song, SongMove } from '@/scripts/song';
+ import { parseBlob } from 'music-metadata-browser';
+ import notificationsModule from './notificationsModule.vue';
const isPlaying = ref( false );
const repeatMode = ref( '' );
@@ -65,6 +81,7 @@
const currentlyPlayingSongArtist = ref( '' );
const pos = ref( 0 );
const duration = ref( 0 );
+ const notifications = ref( notificationsModule );
const emits = defineEmits( [ 'playerStateChange' ] );
@@ -79,6 +96,10 @@
}
}
+ const toggleRemaining = () => {
+ isShowingRemainingTime.value = !isShowingRemainingTime.value;
+ }
+
const control = ( action: string ) => {
if ( action === 'pause' ) {
isPlaying.value = false;
@@ -103,9 +124,11 @@
if ( shuffleMode.value === '' ) {
shuffleMode.value = '_on';
player.setShuffle( true );
+ getDetails();
} else {
shuffleMode.value = '';
player.setShuffle( false );
+ getDetails();
}
getDetails();
} else if ( action === 'forward' ) {
@@ -177,13 +200,71 @@
} );
}
+ const selectCustomPlaylist = async ( pl: ReadFile[] ) => {
+ let n = notifications.value.createNotification( 'Analyzing playlist', 200, 'progress', 'normal' );
+ playlist.value = [];
+ let plLoad: Song[] = [];
+ for ( let element in pl ) {
+ try {
+ plLoad.push( await fetchSongData( pl[ element ] ) );
+ } catch ( e ) {
+ console.error( e );
+ }
+ notifications.value.updateNotification( n, `Analyzing playlist (${element}/${pl.length})` );
+ }
+ playlist.value = plLoad;
+ player.setPlaylist( playlist.value );
+ player.prepare( 0 );
+ isPlaying.value = true;
+ setTimeout( () => {
+ startProgressTracker();
+ getDetails();
+ }, 2000 );
+ notifications.value.cancelNotification( n );
+ notifications.value.createNotification( 'Playlist loaded', 10, 'ok', 'normal' );
+ }
+
+ const fetchSongData = ( songDetails: ReadFile ): Promise
+
+
+
+
-
+
+
{{ currentlyPlayingSongName }} by {{ currentlyPlayingSongArtist }}
+
+
+
{{ nicePlaybackPos }}
+/
+{{ niceDuration }}
+
-
- replay_10
- pause
- play_arrow
- forward_10
-
+
+
+
+ replay_10
+ pause
+ play_arrow
+ forward_10
+
+
- repeat{{ repeatMode }}
- shuffle{{ shuffleMode }}
+
+
+
+ repeat{{ repeatMode }}
+ shuffle{{ shuffleMode }}
+
close
- { control( action ) }" @play-song="( song ) => { playSong( song ) }">
+ { control( action ) }" @play-song="( song ) => { playSong( song ) }"
+ @add-new-songs="( songs ) => addNewSongs( songs )" @playlist-reorder="( move ) => moveSong( move )">
+ Playlist
+ + +Please select at least one song to proceed
+
+
@@ -16,16 +21,18 @@
+
+
{{ song.title }}
- +Your playlists
-
- loading...
+
+ Loading...
+
+
+ +
You are not logged into Apple Music.
++ +
Please select at least one song to proceed!
+
{{ pl.attributes.name }}
@@ -14,15 +20,38 @@
+
+
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/scripts/music-player.ts b/MusicPlayerV2-GUI/src/scripts/music-player.ts
index 0ad2dd7..20f743b 100644
--- a/MusicPlayerV2-GUI/src/scripts/music-player.ts
+++ b/MusicPlayerV2-GUI/src/scripts/music-player.ts
@@ -1,4 +1,4 @@
-import type { Song } from "./song";
+import type { SearchResult, Song, SongMove } from "./song";
interface Config {
devToken: string;
@@ -20,6 +20,7 @@ class MusicKitJSWrapper {
isShuffleEnabled: boolean;
hasEncounteredAuthError: boolean;
queuePos: number;
+ audioPlayer: HTMLAudioElement;
constructor () {
this.playingSongID = 0;
@@ -35,6 +36,7 @@ class MusicKitJSWrapper {
this.isLoggedIn = false;
this.hasEncounteredAuthError = false;
this.queuePos = 0;
+ this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
const self = this;
@@ -77,6 +79,7 @@ class MusicKitJSWrapper {
fetch( localStorage.getItem( 'url' ) + '/getAppleMusicDevToken', { credentials: 'include' } ).then( res => {
if ( res.status === 200 ) {
res.text().then( token => {
+ this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
// MusicKit global is now defined
MusicKit.configure( {
developerToken: token,
@@ -204,7 +207,11 @@ class MusicKitJSWrapper {
console.log( err );
} );
} else {
- // TODO: Implement
+ this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
+ this.audioPlayer.src = this.playlist[ this.playingSongID ].id;
+ setTimeout( () => {
+ this.control( 'play' );
+ }, 500 );
}
return true;
} else {
@@ -225,8 +232,8 @@ class MusicKitJSWrapper {
this.musicKit.play();
return false;
} else {
+ this.audioPlayer.play();
return false;
- // TODO: Implement
}
} else {
return false;
@@ -237,8 +244,8 @@ class MusicKitJSWrapper {
this.musicKit.pause();
return false;
} else {
+ this.audioPlayer.pause();
return false;
- // TODO: Implement
}
} else {
return false;
@@ -248,8 +255,8 @@ class MusicKitJSWrapper {
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? this.musicKit.currentPlaybackTime - 10 : 0 );
return false;
} else {
+ this.audioPlayer.currentTime = this.audioPlayer.currentTime > 10 ? this.audioPlayer.currentTime - 10 : 0;
return false;
- // TODO: Implement
}
case "skip-10":
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
@@ -266,30 +273,30 @@ class MusicKitJSWrapper {
}
}
} else {
- // TODO: Finish
- // if ( this.audioPlayer.currentTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
- // this.audioPlayer.currentTime = this.audioPlayer.currentTime + 10;
- // this.pos = this.audioPlayer.currentTime;
- // this.sendUpdate( 'pos' );
- // } else {
- // if ( this.repeatMode !== 'one' ) {
- // this.control( 'next' );
- // } else {
- // this.audioPlayer.currentTime = 0;
- // this.pos = this.audioPlayer.currentTime;
- // this.sendUpdate( 'pos' );
- // }
- // }
+ if ( this.audioPlayer.currentTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
+ this.audioPlayer.currentTime = this.audioPlayer.currentTime + 10;
+ } else {
+ if ( this.repeatMode !== 'once' ) {
+ this.control( 'next' );
+ } else {
+ this.audioPlayer.currentTime = 0;
+ }
+ }
return false;
}
case "next":
- if ( this.queuePos < this.queue.length ) {
+ if ( this.queuePos < this.queue.length - 1 ) {
this.queuePos += 1;
this.prepare( this.queue[ this.queuePos ] );
return true;
} else {
this.queuePos = 0;
- this.control( 'pause' );
+ if ( this.repeatMode !== 'all' ) {
+ this.control( 'pause' );
+ } else {
+ this.playingSongID = this.queue[ this.queuePos ];
+ this.prepare( this.queue[ this.queuePos ] );
+ }
return true;
}
case "previous":
@@ -324,6 +331,13 @@ class MusicKitJSWrapper {
this.queue.push( parseInt( song ) );
}
}
+ // Find current song ID in queue
+ for ( const el in this.queue ) {
+ if ( this.queue[ el ] === this.playingSongID ) {
+ this.queuePos = parseInt( el );
+ break;
+ }
+ }
}
setRepeatMode ( mode: RepeatMode ) {
@@ -334,10 +348,40 @@ class MusicKitJSWrapper {
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
this.musicKit.seekToTime( pos );
} else {
- // TODO: Implement
+ this.audioPlayer.currentTime = pos;
}
}
+
+ moveSong ( move: SongMove ) {
+ const newQueue = [];
+ const finishedQueue = [];
+ let songID = 0;
+ for ( const song in this.playlist ) {
+ if ( this.playlist[ song ].id === move.songID ) {
+ songID = parseInt( song );
+ break;
+ }
+ }
+ for ( const el in this.queue ) {
+ if ( this.queue[ el ] !== songID ) {
+ newQueue.push( this.queue[ el ] );
+ }
+ }
+ let hasBeenAdded = false;
+ for ( const el in newQueue ) {
+ if ( parseInt( el ) === move.newPos ) {
+ finishedQueue.push( songID );
+ hasBeenAdded = true;
+ }
+ finishedQueue.push( newQueue[ el ] );
+ }
+ if ( !hasBeenAdded ) {
+ finishedQueue.push( songID );
+ }
+ this.queue = finishedQueue;
+ }
+
/**
* Get the current position of the play heed. Will return in ms since start of the song
* @returns {number}
@@ -346,8 +390,7 @@ class MusicKitJSWrapper {
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
return this.musicKit.currentPlaybackTime;
} else {
- return 0;
- // TODO: Implement
+ return this.audioPlayer.currentTime;
}
}
@@ -367,6 +410,14 @@ class MusicKitJSWrapper {
return this.playingSongID;
}
+ /**
+ * Get the queue index of the currently playing song
+ * @returns {number}
+ */
+ getQueueID (): number {
+ return this.queuePos;
+ }
+
/**
* Get the full playlist, as it is set currently, not ordered by queue settings, but as passed in originally
* @returns {Song[]}
@@ -405,14 +456,25 @@ class MusicKitJSWrapper {
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
return this.musicKit.isPlaying;
} else {
- // TODO: Implement
- return false;
+ return !this.audioPlayer.paused;
}
}
- // findSongOnAppleMusic ( searchTerm: string ): Song => {
- // TODO: Implement
- // }
+ findSongOnAppleMusic ( searchTerm: string ): Promise {
+ // TODO: Make storefront adjustable
+ return new Promise( ( resolve, reject ) => {
+ const queryParameters = {
+ term: ( searchTerm ),
+ types: [ 'songs' ],
+ };
+ this.musicKit.api.music( `v1/catalog/ch/search`, queryParameters ).then( results => {
+ resolve( results );
+ } ).catch( e => {
+ console.error( e );
+ reject( e );
+ } );
+ } );
+ }
}
export default MusicKitJSWrapper;
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
index fbf2a57..5b45f1c 100644
--- a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
+++ b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
@@ -1,7 +1,44 @@
-const subscribe = ( handler: ( data: any ) => {} ): string => {
- return '';
+/*
+* MusicPlayerV2 - notificationHandler.ts
+*
+* Created by Janis Hutz 06/26/2024, Licensed under the GPL V3 License
+* https://janishutz.com, development@janishutz.com
+*
+*
+*/
+
+// These functions handle connections to the backend with socket.io
+
+import { io, type Socket } from "socket.io-client"
+
+class NotificationHandler {
+ socket: Socket;
+
+ constructor () {
+ this.socket = io();
+ }
+
+ /**
+ * Description
+ * @param {string} event The event to emit
+ * @param {any} data
+ * @returns {void}
+ */
+ emit ( event: string, data: any ): void {
+ this.socket.emit( event, data );
+ }
+
+ registerListener ( event: string, cb: ( data: any ) => void ): void {
+ this.socket.on( event, cb );
+ }
+
+ disconnect (): void {
+ this.socket.disconnect();
+ }
+
+ joinRoom ( roomName: string ): void {
+ // this.socket.
+ }
}
-const unsubscribe = ( id: string ) => {
-
-}
\ No newline at end of file
+export default NotificationHandler;
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/scripts/song.d.ts b/MusicPlayerV2-GUI/src/scripts/song.d.ts
index 4ddca1a..9fde4b5 100644
--- a/MusicPlayerV2-GUI/src/scripts/song.d.ts
+++ b/MusicPlayerV2-GUI/src/scripts/song.d.ts
@@ -40,4 +40,44 @@ export interface Song {
* (OPTIONAL) This will be displayed in brackets on the showcase screens
*/
additionalInfo?: string;
+}
+
+
+export interface ReadFile {
+ url: string;
+ filename: string;
+}
+
+export interface SearchResult {
+ data: {
+ results: {
+ songs: {
+ data: AppleMusicSongData[],
+ href: string;
+ }
+ };
+ }
+}
+
+export interface AppleMusicSongData {
+ id: string,
+ type: string;
+ href: string;
+ attributes: {
+ albumName: string;
+ artistName: string;
+ artwork: {
+ width: number,
+ height: number,
+ url: string
+ },
+ name: string;
+ genreNames: string[];
+ durationInMillis: number;
+ }
+}
+
+export interface SongMove {
+ songID: string;
+ newPos: number;
}
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/views/AppView.vue b/MusicPlayerV2-GUI/src/views/AppView.vue
index 84493d9..a1d6a15 100644
--- a/MusicPlayerV2-GUI/src/views/AppView.vue
+++ b/MusicPlayerV2-GUI/src/views/AppView.vue
@@ -1,14 +1,15 @@
-
- { selectPlaylist( id ) }">
+
- { handlePlayerStateChange( state ) }"
+ { handlePlayerStateChange( state ) }"
ref="player">
@@ -17,8 +18,10 @@
import playerView from '@/components/playerView.vue';
import libraryView from '@/components/libraryView.vue';
import { ref } from 'vue';
+import type { ReadFile } from '@/scripts/song';
const isLoggedIntoAppleMusic = ref( false );
+ const isReady = ref( false );
const isShowingFullScreenPlayer = ref( false );
const player = ref( playerView );
const playlists = ref( [] );
@@ -37,6 +40,7 @@
loginChecker = setInterval( () => {
if ( player.value.getAuth()[ 0 ] ) {
isLoggedIntoAppleMusic.value = true;
+ isReady.value = true;
player.value.getPlaylists( ( data ) => {
playlists.value = data.data.data;
} );
@@ -49,13 +53,18 @@
}
const skipLogin = () => {
- isLoggedIntoAppleMusic.value = true;
+ isReady.value = true;
+ isLoggedIntoAppleMusic.value = false;
player.value.skipLogin();
}
const selectPlaylist = ( id: string ) => {
player.value.selectPlaylist( id );
}
+
+ const selectCustomPlaylist = ( playlist: ReadFile[] ) => {
+ player.value.selectCustomPlaylist( playlist );
+ }
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/vite.config.ts b/MusicPlayerV2-GUI/vite.config.ts
index 83badb7..28acfe6 100644
--- a/MusicPlayerV2-GUI/vite.config.ts
+++ b/MusicPlayerV2-GUI/vite.config.ts
@@ -2,6 +2,7 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
+import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
// https://vitejs.dev/config/
export default defineConfig({
@@ -13,6 +14,18 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
+ optimizeDeps: {
+ esbuildOptions: {
+ define: {
+ global: 'globalThis',
+ },
+ plugins: [
+ NodeGlobalsPolyfillPlugin({
+ buffer: true,
+ }),
+ ],
+ },
+ },
server: {
port: 8080
}
diff --git a/backend/config/config b/backend/config/config
deleted file mode 120000
index 4088526..0000000
--- a/backend/config/config
+++ /dev/null
@@ -1 +0,0 @@
-../config/
\ No newline at end of file
diff --git a/backend/package-lock.json b/backend/package-lock.json
index eddb572..47e417a 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -18,7 +18,8 @@
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"node-mysql": "^0.4.2",
- "oauth-janishutz-client-server": "file:../../oauth/client/server/dist"
+ "oauth-janishutz-client-server": "file:../../oauth/client/server/dist",
+ "socket.io": "^4.7.5"
},
"devDependencies": {
"typescript": "^5.4.5"
@@ -40,6 +41,12 @@
"typescript": "^5.3.3"
}
},
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -59,6 +66,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
+ "license": "MIT"
+ },
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
@@ -174,6 +187,15 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
"node_modules/better-js-class": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/better-js-class/-/better-js-class-0.1.3.tgz",
@@ -375,6 +397,68 @@
"node": ">= 0.8"
}
},
+ "node_modules/engine.io": {
+ "version": "6.5.5",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
+ "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookie": "^0.4.1",
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.4.1",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
+ "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/engine.io/node_modules/cookie": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+ "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "license": "MIT"
+ },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@@ -1073,6 +1157,116 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/socket.io": {
+ "version": "4.7.5",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
+ "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "engine.io": "~6.5.2",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+ "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.17.1"
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "license": "MIT"
+ },
"node_modules/sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
@@ -1186,6 +1380,27 @@
"engines": {
"node": ">= 0.8"
}
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/backend/package.json b/backend/package.json
index 8849a82..27e8d85 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -29,6 +29,7 @@
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"node-mysql": "^0.4.2",
- "oauth-janishutz-client-server": "file:../../oauth/client/server/dist"
+ "oauth-janishutz-client-server": "file:../../oauth/client/server/dist",
+ "socket.io": "^4.7.5"
}
}
diff --git a/backend/src/storage/db.ts b/backend/src/storage/db.ts
index d43d824..aecfa05 100644
--- a/backend/src/storage/db.ts
+++ b/backend/src/storage/db.ts
@@ -19,8 +19,8 @@ if ( typeof( __dirname ) === 'undefined' ) {
}
const dbRef = {
- 'user': 'jh_store_users',
- 'users': 'jh_store_users',
+ 'user': 'music_users',
+ 'users': 'music_users',
};
diff --git a/backend/src/storage/mysqldb.ts b/backend/src/storage/mysqldb.ts
index 32ae4e7..ba46fe0 100644
--- a/backend/src/storage/mysqldb.ts
+++ b/backend/src/storage/mysqldb.ts
@@ -60,7 +60,6 @@ class SQLDB {
console.log( '[ SQL ] Connected to database successfully' );
self.sqlConnection.on( 'error', ( err ) => {
if ( err.code === 'ECONNRESET' ) {
- console.error( '[ SQL ] Reconnecting to database, because connection was reset!' );
self.isRecovering = true;
setTimeout( () => {
self.disconnect();
@@ -84,7 +83,7 @@ class SQLDB {
if ( error ) throw error;
if ( results[ 0 ][ '@@default_storage_engine' ] !== 'InnoDB' ) throw 'DB HAS TO USE InnoDB!';
} );
- this.sqlConnection.query( 'CREATE TABLE jh_store_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, data VARCHAR( 55000 ), uid TINYTEXT, lang TINYTEXT, username TINYTEXT, stripe_user_id TINYTEXT, settings VARCHAR( 5000 ), PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
+ this.sqlConnection.query( 'CREATE TABLE music_users ( account_id INT ( 10 ) NOT NULL AUTO_INCREMENT, email TINYTEXT NOT NULL, uid TINYTEXT, lang TINYTEXT, username TINYTEXT, settings VARCHAR( 5000 ), PRIMARY KEY ( account_id ) ) ENGINE=INNODB;', ( error ) => {
if ( error ) if ( error.code !== 'ER_TABLE_EXISTS_ERROR' ) throw error;
return 'DONE';
} );
+ { selectPlaylist( id ) }"
+ :is-logged-in="isLoggedIntoAppleMusic" @custom-playlist="( pl ) => selectCustomPlaylist( pl )">