diff --git a/MusicPlayerV2-GUI/src/App.vue b/MusicPlayerV2-GUI/src/App.vue
index c1c2498..0ccb3fd 100644
--- a/MusicPlayerV2-GUI/src/App.vue
+++ b/MusicPlayerV2-GUI/src/App.vue
@@ -141,7 +141,7 @@
#themeSelector {
position: fixed;
top: 10px;
- right: 10px;
+ left: 10px;
background: none;
border: none;
color: var( --primary-color );
diff --git a/MusicPlayerV2-GUI/src/components/playerView.vue b/MusicPlayerV2-GUI/src/components/playerView.vue
index b495a1f..dde102a 100644
--- a/MusicPlayerV2-GUI/src/components/playerView.vue
+++ b/MusicPlayerV2-GUI/src/components/playerView.vue
@@ -45,7 +45,9 @@
close
{ control( action ) }" @play-song="( song ) => { playSong( song ) }"
- @add-new-songs="( songs ) => addNewSongs( songs )" @playlist-reorder="( move ) => moveSong( move )">
+ @add-new-songs="( songs ) => addNewSongs( songs )" @playlist-reorder="( move ) => moveSong( move )"
+ :is-logged-into-apple-music="player.isLoggedIn"
+ @add-new-songs-apple-music="( song ) => addNewSongFromObject( song )">
@@ -63,6 +65,8 @@
import type { ReadFile, Song, SongMove } from '@/scripts/song';
import { parseBlob } from 'music-metadata-browser';
import notificationsModule from './notificationsModule.vue';
+ import { useUserStore } from '@/stores/userStore';
+ import NotificationHandler from '@/scripts/notificationHandler';
const isPlaying = ref( false );
const repeatMode = ref( '' );
@@ -82,6 +86,7 @@
const pos = ref( 0 );
const duration = ref( 0 );
const notifications = ref( notificationsModule );
+ const notificationHandler = new NotificationHandler();
const emits = defineEmits( [ 'playerStateChange' ] );
@@ -90,6 +95,7 @@
if ( isPlaying.value ) {
player.control( 'play' );
startProgressTracker();
+
} else {
player.control( 'pause' );
stopProgressTracker();
@@ -364,7 +370,7 @@
const addNewSongs = async ( songs: ReadFile[] ) => {
let n = notifications.value.createNotification( 'Analyzing new songs', 200, 'progress', 'normal' );
- playlist.value = player.getPlaylist();
+ playlist.value = player.getQueue();
for ( let element in songs ) {
try {
playlist.value.push( await fetchSongData( songs[ element ] ) );
@@ -374,29 +380,48 @@
notifications.value.updateNotification( n, `Analyzing new songs (${element}/${songs.length})` );
}
player.setPlaylist( playlist.value );
- player.prepare( 0 );
- isPlaying.value = true;
- setTimeout( () => {
- startProgressTracker();
- getDetails();
- }, 2000 );
+ if ( !isPlaying.value ) {
+ player.prepare( 0 );
+ isPlaying.value = true;
+ setTimeout( () => {
+ startProgressTracker();
+ getDetails();
+ }, 2000 );
+ }
notifications.value.cancelNotification( n );
notifications.value.createNotification( 'New songs added', 10, 'ok', 'normal' );
}
+ const addNewSongFromObject = ( song: Song ) => {
+ playlist.value = player.getQueue();
+ playlist.value.push( song );
+ player.setPlaylist( playlist.value );
+ if ( !isPlaying.value ) {
+ player.prepare( 0 );
+ isPlaying.value = true;
+ setTimeout( () => {
+ startProgressTracker();
+ getDetails();
+ }, 2000 );
+ }
+ }
+
emits( 'playerStateChange', isShowingFullScreenPlayer.value ? 'show' : 'hide' );
+ const userStore = useUserStore();
+
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' );
+ if ( !userStore.isUsingKeyboard ) {
+ if ( e.key === ' ' ) {
+ e.preventDefault();
+ playPause();
+ } else if ( e.key === 'ArrowRight' ) {
+ e.preventDefault();
+ control( 'next' );
+ } else if ( e.key === 'ArrowLeft' ) {
+ e.preventDefault();
+ control( 'previous' );
+ }
}
} );
diff --git a/MusicPlayerV2-GUI/src/components/playlistView.vue b/MusicPlayerV2-GUI/src/components/playlistView.vue
index 0cca5fd..834f76f 100644
--- a/MusicPlayerV2-GUI/src/components/playlistView.vue
+++ b/MusicPlayerV2-GUI/src/components/playlistView.vue
@@ -1,11 +1,11 @@
Playlist
-
-
Load local songs
+
+
upload
+
search
Please select at least one song to proceed
-
{{ getTimeUntil( song ) }}
+
{ addNewSongsAppleMusic( song ) }">
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/components/playlistsView.vue b/MusicPlayerV2-GUI/src/components/playlistsView.vue
index c17635a..46fa4be 100644
--- a/MusicPlayerV2-GUI/src/components/playlistsView.vue
+++ b/MusicPlayerV2-GUI/src/components/playlistsView.vue
@@ -7,8 +7,8 @@
You are not logged into Apple Music.
-
-
Load custom playlist from disk
+
+
Load custom playlist from disk
Please select at least one song to proceed!
@@ -84,4 +84,8 @@
cursor: pointer;
user-select: none;
}
+
+ .pl-loader-button {
+ background-color: white;
+ }
\ No newline at end of file
diff --git a/MusicPlayerV2-GUI/src/components/searchView.vue b/MusicPlayerV2-GUI/src/components/searchView.vue
new file mode 100644
index 0000000..b7ff761
--- /dev/null
+++ b/MusicPlayerV2-GUI/src/components/searchView.vue
@@ -0,0 +1,265 @@
+
+
+
+
+
{ keyHandler( e ) }">
+
+ search
+
+
+
+
+
{{ result.attributes.name }} by {{ result.attributes.artistName }}
+
+
+
+
Enter at least three characters to start searching
+
+
+
+
+ close
+
+
+
+
+
+
+
+
+
+
+
\ 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 20f743b..4c0e091 100644
--- a/MusicPlayerV2-GUI/src/scripts/music-player.ts
+++ b/MusicPlayerV2-GUI/src/scripts/music-player.ts
@@ -228,6 +228,7 @@ class MusicKitJSWrapper {
switch ( action ) {
case "play":
if ( this.isPreparedToPlay ) {
+ this.control( 'pause' );
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
this.musicKit.play();
return false;
@@ -285,6 +286,7 @@ class MusicKitJSWrapper {
return false;
}
case "next":
+ this.control( 'pause' );
if ( this.queuePos < this.queue.length - 1 ) {
this.queuePos += 1;
this.prepare( this.queue[ this.queuePos ] );
@@ -300,6 +302,7 @@ class MusicKitJSWrapper {
return true;
}
case "previous":
+ this.control( 'pause' );
if ( this.queuePos > 0 ) {
this.queuePos -= 1;
this.prepare( this.queue[ this.queuePos ] );
diff --git a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
index 5b45f1c..25c5d20 100644
--- a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
+++ b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts
@@ -15,7 +15,25 @@ class NotificationHandler {
socket: Socket;
constructor () {
- this.socket = io();
+ this.socket = io( localStorage.getItem( 'url' ) ?? '', {
+ autoConnect: false,
+ } );
+ }
+
+ /**
+ * Create a room token and connect to
+ * @param {string} roomName
+ * @returns {Promise
}
+ */
+ connect ( roomName: string ): Promise {
+ fetch( localStorage.getItem( 'url' ) + '/createRoomToken', { credentials: 'include' } ).then( res => {
+ if ( res.status === 200 ) {
+ res.json().then( json => {
+
+ } );
+ }
+ } );
+
}
/**
@@ -35,10 +53,6 @@ class NotificationHandler {
disconnect (): void {
this.socket.disconnect();
}
-
- joinRoom ( roomName: string ): void {
- // this.socket.
- }
}
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 9fde4b5..1c01058 100644
--- a/MusicPlayerV2-GUI/src/scripts/song.d.ts
+++ b/MusicPlayerV2-GUI/src/scripts/song.d.ts
@@ -42,6 +42,14 @@ export interface Song {
additionalInfo?: string;
}
+export interface SongTransmitted {
+ title: string;
+ artist: string;
+ duration: number;
+ cover: string;
+ additionalInfo?: string;
+}
+
export interface ReadFile {
url: string;
diff --git a/MusicPlayerV2-GUI/src/stores/counter.ts b/MusicPlayerV2-GUI/src/stores/counter.ts
deleted file mode 100644
index b6757ba..0000000
--- a/MusicPlayerV2-GUI/src/stores/counter.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
-
-export const useCounterStore = defineStore('counter', () => {
- const count = ref(0)
- const doubleCount = computed(() => count.value * 2)
- function increment() {
- count.value++
- }
-
- return { count, doubleCount, increment }
-})
diff --git a/MusicPlayerV2-GUI/src/stores/userStore.ts b/MusicPlayerV2-GUI/src/stores/userStore.ts
new file mode 100644
index 0000000..7733039
--- /dev/null
+++ b/MusicPlayerV2-GUI/src/stores/userStore.ts
@@ -0,0 +1,32 @@
+/*
+* LanguageSchoolHossegorBookingSystem - userStore.js
+*
+* Created by Janis Hutz 10/27/2023, Licensed under a proprietary License
+* https://janishutz.com, development@janishutz.com
+*
+*
+*/
+
+import { defineStore } from 'pinia';
+
+export const useUserStore = defineStore( 'user', {
+ state: () => ( { 'isUserAuth': false, 'isAdminAuth': false, 'isUsingKeyboard': false, 'username': '' } ),
+ getters: {
+ getUserAuthenticated: ( state ) => state.isUserAuth,
+ getAdminAuthenticated: ( state ) => state.isAdminAuth,
+ },
+ actions: {
+ setUserAuth ( auth: boolean ) {
+ this.isUserAuth = auth;
+ },
+ setAdminAuth ( auth: boolean ) {
+ this.isAdminAuth = auth;
+ },
+ setUsername ( username: string ) {
+ this.username = username;
+ },
+ setKeyboardUsageStatus ( status: boolean ) {
+ this.isUsingKeyboard = status;
+ }
+ }
+} );
\ No newline at end of file
diff --git a/backend/src/app.ts b/backend/src/app.ts
index bccc914..93e7559 100644
--- a/backend/src/app.ts
+++ b/backend/src/app.ts
@@ -5,6 +5,10 @@ import jwt from 'jsonwebtoken';
import cors from 'cors';
import account from './account';
import sdk from 'oauth-janishutz-client-server';
+import { createServer } from 'node:http';
+import { Server } from 'socket.io';
+import crypto from 'node:crypto';
+import type { Room, Song } from './definitions';
declare let __dirname: string | undefined
if ( typeof( __dirname ) === 'undefined' ) {
@@ -22,6 +26,8 @@ const run = () => {
origin: true
} ) );
+ const httpServer = createServer( app );
+
// Load id.janishutz.com SDK and allow signing in
sdk.routes( app, ( uid: string ) => {
return new Promise( ( resolve, reject ) => {
@@ -42,11 +48,126 @@ const run = () => {
} );
}, sdkConfig );
+ // Websocket for events
+ interface SocketData {
+ [key: string]: Room;
+ }
+ const socketData: SocketData = {};
+ const io = new Server( httpServer, {
+ cors: {
+ origin: true,
+ credentials: true,
+ }
+ } );
- app.get( '/', ( request, response ) => {
+ io.on( 'connection', ( socket ) => {
+ socket.on( 'create-room', ( room: { name: string, token: string }, cb: ( res: { status: boolean, msg: string } ) => void ) => {
+ if ( room.token === socketData[ room.name ].roomToken ) {
+ socket.join( room.name );
+ cb( {
+ status: true,
+ msg: 'ADDED_TO_ROOM'
+ } )
+ } else {
+ cb( {
+ status: false,
+ msg: 'ERR_TOKEN_INVALID'
+ } );
+ }
+ } );
+
+ socket.on( 'join-room', ( room: string, cb: ( res: { status: boolean, msg: string, data?: { playbackStatus: boolean, playbackStart: number, playlist: Song[], playlistIndex: number } } ) => void ) => {
+ if ( socketData[ room ] ) {
+ socket.join( room );
+ cb( {
+ data: {
+ playbackStart: socketData[ room ].playbackStart,
+ playbackStatus: socketData[ room ].playbackStatus,
+ playlist: socketData[ room ].playlist,
+ playlistIndex: socketData[ room ].playlistIndex,
+ },
+ msg: 'STATUS_OK',
+ status: true,
+ } )
+ } else {
+ cb( {
+ msg: 'ERR_NO_ROOM_WITH_THIS_ID',
+ status: false,
+ } )
+ }
+ } );
+
+ socket.on( 'tampering', ( data: { msg: string, roomName: string } ) => {
+ if ( data.roomName ) {
+ socket.to( data.roomName ).emit( 'tampering-msg', data.msg );
+ }
+ } )
+
+ socket.on( 'playlist', ( data: { roomName: string, roomToken: string, data: Song[] } ) => {
+ if ( socketData[ data.roomName ] ) {
+ if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
+ socketData[ data.roomName ].playlist = data.data;
+ io.to( data.roomName ).emit( 'playlist', data.data );
+ }
+ }
+ } );
+
+ socket.on( 'playback', ( data: { roomName: string, roomToken: string, data: boolean } ) => {
+ if ( socketData[ data.roomName ] ) {
+ if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
+ socketData[ data.roomName ].playbackStatus = data.data;
+ io.to( data.roomName ).emit( 'playback', data.data );
+ }
+ }
+ } );
+
+ socket.on( 'playlist-index', ( data: { roomName: string, roomToken: string, data: number } ) => {
+ if ( socketData[ data.roomName ] ) {
+ if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
+ socketData[ data.roomName ].playlistIndex = data.data;
+ io.to( data.roomName ).emit( 'playlist-index', data.data );
+ }
+ }
+ } );
+
+ socket.on( 'playback-start', ( data: { roomName: string, roomToken: string, data: number } ) => {
+ if ( socketData[ data.roomName ] ) {
+ if ( socketData[ data.roomName ].roomToken === data.roomToken ) {
+ socketData[ data.roomName ].playbackStart = data.data;
+ io.to( data.roomName ).emit( 'playback-start', data.data );
+ }
+ }
+ } );
+ } );
+
+
+ app.get( '/', ( request: express.Request, response: express.Response ) => {
response.send( 'Please visit https://music.janishutz.com to use this service' );
} );
+
+ app.get( '/createRoomToken', ( request: express.Request, response: express.Response ) => {
+ if ( sdk.checkAuth( request ) ) {
+ const roomName = String( request.query.roomName ) ?? '';
+ if ( !socketData[ roomName ] ) {
+ const roomToken = crypto.randomUUID();
+ socketData[ roomName ] = {
+ playbackStart: 0,
+ playbackStatus: false,
+ playlist: [],
+ playlistIndex: 0,
+ roomName: roomName,
+ roomToken: roomToken,
+ };
+ response.send( roomToken );
+ } else {
+ response.status( 409 ).send( 'ERR_CONFLICT' );
+ }
+ } else {
+ response.status( 403 ).send( 'ERR_FORBIDDEN' );
+ }
+ } );
+
app.get( '/getAppleMusicDevToken', ( req, res ) => {
// sign dev token
@@ -73,7 +194,7 @@ const run = () => {
const PORT = process.env.PORT || 8081;
- app.listen( PORT );
+ httpServer.listen( PORT );
}
export default {
diff --git a/backend/src/definitions.d.ts b/backend/src/definitions.d.ts
new file mode 100644
index 0000000..4402200
--- /dev/null
+++ b/backend/src/definitions.d.ts
@@ -0,0 +1,16 @@
+export interface Room {
+ playbackStatus: boolean;
+ playbackStart: number;
+ playlist: Song[];
+ playlistIndex: number;
+ roomName: string;
+ roomToken: string;
+}
+
+export interface Song {
+ title: string;
+ artist: string;
+ duration: number;
+ cover: string;
+ additionalInfo?: string;
+}
\ No newline at end of file
diff --git a/old/frontend/src/components/notifications.vue b/old/frontend/src/components/notifications.vue
index 5b74c58..58dd47b 100644
--- a/old/frontend/src/components/notifications.vue
+++ b/old/frontend/src/components/notifications.vue
@@ -131,7 +131,7 @@ export default {