diff --git a/backend/data/server-stubs.json b/backend/data/server-stubs.json
index 8d384c9..a099786 100644
--- a/backend/data/server-stubs.json
+++ b/backend/data/server-stubs.json
@@ -1 +1 @@
-{"playlists":[],"userid":"server-stubs","version":"1"}
+{"playlists":[{"name":"Hello World","songs":[{"duration":236,"name":"CAN'T STOP THE FEELING! (Original Song From DreamWorks Animation's \"TROLLS\")","additional-info":"Test 3","artist":"Justin Timberlake","artwork":"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/52/d0/02/52d002d7-1f7f-8d91-4791-ed04c460ec93/886445894653.jpg/2000x2000bb.jpg","identifier":"blob:http://localhost:8081/fdb6fccd-30cb-458c-a9f6-c424d2f53c43","source":"local","additional-identifier":"16_Can't_Stop_The_Feeling-Justin_Timberlake.mp3__4407330"},{"duration":258,"name":"Muévelo","additional-info":"Test","artist":"Rey Ruiz","artwork":"https://is1-ssl.mzstatic.com/image/thumb/Music122/v4/58/15/0d/58150db1-0e74-dd19-f7eb-3f15e8a04b56/197187182459.jpg/2000x2000bb.jpg","identifier":"blob:http://localhost:8081/18dcea36-839d-4714-b7fc-6800f26e07b9","source":"local","additional-identifier":"18_Muevlo-Rey_Ruiz.mp3__4831574"}]}],"userid":"server-stubs","version":"1"}
\ No newline at end of file
diff --git a/web/src/components/main/PlayerComponent.vue b/web/src/components/main/PlayerComponent.vue
index 2ffc248..0bcdd34 100644
--- a/web/src/components/main/PlayerComponent.vue
+++ b/web/src/components/main/PlayerComponent.vue
@@ -13,7 +13,7 @@
-
+
@@ -44,6 +44,15 @@
flex-direction: column;
align-items: center;
justify-content: center;
+ position: relative;
+
+ >.current-song-wrapper {
+ margin-bottom: 1.5rem;
+ width: 100%;
+ height: 100%;
+ max-height: calc(100% - 15rem);
+ overflow: hidden;
+ }
}
}
diff --git a/web/src/components/main/PlayerWrapper.vue b/web/src/components/main/PlayerWrapper.vue
index 2ba74fd..a82ce29 100644
--- a/web/src/components/main/PlayerWrapper.vue
+++ b/web/src/components/main/PlayerWrapper.vue
@@ -32,6 +32,7 @@
background-color: var(--secondary-color);
transition: bottom 1s ease;
transition-delay: 0.25s;
+ overflow: hidden;
&.hidden {
transition-delay: 0s;
diff --git a/web/src/components/main/SmallPlayerComponent.vue b/web/src/components/main/SmallPlayerComponent.vue
index d7137e4..1d58526 100644
--- a/web/src/components/main/SmallPlayerComponent.vue
+++ b/web/src/components/main/SmallPlayerComponent.vue
@@ -1,4 +1,24 @@
-
- Player
+
+
+
![Song cover]()
+
+
+
{{ song.name }}
+
{{ song.artist }}
+
+
+ {{ beautifyTime( playbackPercentage * song.duration ) }} / {{ beautifyTime( song.duration ) }}
+
+
+
+
+
diff --git a/web/src/components/player/CurrentSong.vue b/web/src/components/player/CurrentSong.vue
index 09a091f..e367324 100644
--- a/web/src/components/player/CurrentSong.vue
+++ b/web/src/components/player/CurrentSong.vue
@@ -6,6 +6,9 @@
const song = defineModel
( {
'required': false
} );
+ const props = defineProps<{
+ 'showAdditionalInfo'?: boolean
+ }>();
@@ -24,6 +27,9 @@
{{ song?.name ?? 'Not playing' }}
{{ song?.artist ?? 'No artist' }}
+
+ {{ song?.['additional-info'] }}
+
@@ -31,14 +37,22 @@
diff --git a/web/src/scss/shared/main.scss b/web/src/scss/shared/main.scss
new file mode 100644
index 0000000..1a7b4a2
--- /dev/null
+++ b/web/src/scss/shared/main.scss
@@ -0,0 +1,44 @@
+.shared-view {
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+
+ .panel {
+ width: 45%;
+ margin-left: 2.5%;
+ margin-right: 2.5%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+
+ >.current-song-wrapper {
+ margin-bottom: 1.5rem;
+ width: 100%;
+ height: 100%;
+ max-height: calc(100% - 10rem);
+ overflow: hidden;
+ }
+
+ >.time {
+ display: flex;
+ width: 85%;
+ margin-left: auto;
+ margin-right: auto;
+
+ >p {
+ margin-top: 0px;
+ margin-bottom: 5px;
+ }
+
+ >.duration {
+ margin-left: auto;
+ }
+ }
+ }
+}
diff --git a/web/src/ts/dtype/global.d.ts b/web/src/ts/dtype/global.d.ts
index 9cab2e4..f3dd2cd 100644
--- a/web/src/ts/dtype/global.d.ts
+++ b/web/src/ts/dtype/global.d.ts
@@ -8,5 +8,8 @@ declare global {
'musicplayer:playpause': CustomEvent
;
'musicplayer:playindex': CustomEvent;
'musicplayer:seek': CustomEvent;
+ 'musicplayer:update': CustomEvent;
+ 'musicplayer:reauth': CustomEvent;
+ 'musicplayer:autherror': CustomEvent;
}
}
diff --git a/web/src/ts/messages/anti-tamper.ts b/web/src/ts/messages/anti-tamper.ts
new file mode 100644
index 0000000..e69de29
diff --git a/web/src/ts/messages/index.ts b/web/src/ts/messages/index.ts
index 3349758..deb196c 100644
--- a/web/src/ts/messages/index.ts
+++ b/web/src/ts/messages/index.ts
@@ -12,6 +12,9 @@ import {
import {
playbackPercentage
} from '../player/status-tracking';
+import {
+ reauth
+} from '../util/reauth';
import request from '../request';
const RETRY_CAP = 10;
@@ -43,6 +46,8 @@ const createRoom = async ( name: string, antiTamper: boolean ): Promise
useAntiTamper.value = antiTamper;
room.value = name;
+ document.addEventListener( 'musicplayer:autherror', reauth );
+
return await connect();
};
@@ -50,6 +55,11 @@ const closeRoom = async () => {
// TODO: Trigger close on backend and disconnect.
isConnected.value = false;
localStorage.removeItem( 'room' );
+ connection?.close();
+
+ try {
+ document.removeEventListener( 'musicplayer:autherror', reauth );
+ } catch { /* empty */ }
};
const connect = (): Promise => {
@@ -102,9 +112,14 @@ let stateLock = false;
const sendPlaylistData = () => {
if ( isConnected.value && !playlistLock ) {
playlistLock = true;
- request.post( `/room/${ room.value }/update/playlist`, JSON.stringify( {
- 'playlist': queue.value
- } ) );
+
+ try {
+ request.post( `/room/${ room.value }/update/playlist`, JSON.stringify( {
+ 'playlist': queue.value
+ } ) );
+ } catch ( e ) {
+ console.error( e );
+ }
setTimeout( () => {
playlistLock = false;
@@ -112,15 +127,20 @@ const sendPlaylistData = () => {
}
};
-const sendStateData = () => {
+const sendStateData = async () => {
if ( isConnected.value && !stateLock ) {
stateLock = true;
- request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
- 'playing': isPlaying.value,
- 'index': queueIdx.value,
- 'start': new Date().getTime() - 100,
- 'offset': playbackPercentage.value * ( queue.value[ queueIdx.value ]?.duration ?? 0 )
- } ) );
+
+ try {
+ request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
+ 'playing': isPlaying.value,
+ 'index': queueIdx.value,
+ 'start': new Date().getTime() - 100,
+ 'offset': playbackPercentage.value * ( queue.value[ queueIdx.value ]?.duration ?? 0 )
+ } ) );
+ } catch ( e ) {
+ console.error( e );
+ }
setTimeout( () => {
stateLock = false;
@@ -135,6 +155,7 @@ const useRoomWatchers = () => {
document.addEventListener( 'musicplayer:playindex', sendStateData );
document.addEventListener( 'musicplayer:seek', sendStateData );
document.addEventListener( 'musicplayer:playpause', sendStateData );
+ document.addEventListener( 'musicplayer:update', sendPlaylistData );
} );
onUnmounted( () => {
@@ -149,6 +170,10 @@ const useRoomWatchers = () => {
try {
document.addEventListener( 'musicplayer:playpause', sendStateData );
} catch { /* empty */ }
+
+ try {
+ document.addEventListener( 'musicplayer:update', sendPlaylistData );
+ } catch { /* empty */ }
} );
if ( room.value ) connect();
diff --git a/web/src/ts/request.ts b/web/src/ts/request.ts
index 8ff1f6c..3237155 100644
--- a/web/src/ts/request.ts
+++ b/web/src/ts/request.ts
@@ -28,7 +28,8 @@ const wrapper = async ( url: string, opts: RequestInit ): Promise => {
if ( res.ok ) {
return res;
} else if ( res.status === 403 || res.status === 401 ) {
- // TODO: Handle these errors better (probably do something like in the old version, but better)
+ document.dispatchEvent( new CustomEvent( 'musicplayer:autherror' ) );
+
throw new AuthError( 'ERR_USER_UNAUTHORIZED' );
} else if ( res.status === 402 ) {
throw new UnownedError( 'ERR_USER_UNOWNED' );
diff --git a/web/src/ts/shared/index.ts b/web/src/ts/shared/index.ts
index ec29d08..8daa20c 100644
--- a/web/src/ts/shared/index.ts
+++ b/web/src/ts/shared/index.ts
@@ -7,6 +7,7 @@ import {
currentQueueIdx,
isPlaying,
playbackOffset,
+ playbackProgress,
playbackTime,
startTime
} from './state';
@@ -44,7 +45,7 @@ const connect = (): Promise => {
startTime.value = data.state.start;
playbackTime.value = data.state.offset;
playbackOffset.value = data.state.offset;
-
+ playbackProgress.value = data.state.offset / ( data.playlist[ data.state.index ]?.duration ?? -1 );
resolve();
};
diff --git a/web/src/ts/shared/state.ts b/web/src/ts/shared/state.ts
index 3cf4f6a..4b75483 100644
--- a/web/src/ts/shared/state.ts
+++ b/web/src/ts/shared/state.ts
@@ -19,3 +19,5 @@ export const showArtworks = ref( false );
export const playbackTime = ref( 0 );
export const playbackOffset = ref( 0 );
+
+export const playbackProgress = ref( 0 );
diff --git a/web/src/ts/util/reauth.ts b/web/src/ts/util/reauth.ts
new file mode 100644
index 0000000..38d4baf
--- /dev/null
+++ b/web/src/ts/util/reauth.ts
@@ -0,0 +1,17 @@
+export const reauth = () => {
+ localStorage.setItem( 'close-tab', 'true' );
+
+ const listener = () => {
+ if ( localStorage.getItem( 'reauth-ok' ) === 'true' ) {
+ try {
+ window.removeEventListener( 'storage', listener );
+ } catch { /* empty */ }
+
+ localStorage.removeItem( 'reauth-ok' );
+
+ document.dispatchEvent( new CustomEvent( 'musicplayer:reauth' ) );
+ }
+ };
+
+ window.addEventListener( 'storage', listener );
+};
diff --git a/web/src/views/SharedView.vue b/web/src/views/SharedView.vue
index a9fbe83..7e4c32e 100644
--- a/web/src/views/SharedView.vue
+++ b/web/src/views/SharedView.vue
@@ -2,14 +2,14 @@
import {
type ComputedRef,
computed,
- onMounted,
- ref
+ onMounted
} from 'vue';
import {
currentQueue,
currentQueueIdx,
isPlaying,
playbackOffset,
+ playbackProgress,
playbackTime,
startTime
} from '@/ts/shared/state';
@@ -25,7 +25,6 @@
import shared from '@/ts/shared';
shared.connect();
- const playbackProgress = ref( 0 );
onMounted( () => {
setInterval( () => {
@@ -62,22 +61,26 @@
-
-
-
-
-
- {{ beautifyTime( playbackTime ) }}
-
-
- {{ beautifyTime( song.duration ) }}
-
-
-
+
+
+
-
-
+
+
+
+ {{ beautifyTime( playbackTime ) }}
+
+
+ {{ beautifyTime( song.duration ) }}
+
+
+
+
+
+
diff --git a/web/src/views/StartPage.vue b/web/src/views/StartPage.vue
index 84bf638..fbbfca0 100644
--- a/web/src/views/StartPage.vue
+++ b/web/src/views/StartPage.vue
@@ -18,13 +18,24 @@
store.isAuth = await sdk.verify();
if ( store.isAuth )
- router.push( '/app' );
+ isAuthorizedHandler();
isLoggingIn.value = false;
// TODO: Logout button
} );
+ const isAuthorizedHandler = () => {
+ if ( localStorage.getItem( 'close-tab' ) === 'true' ) {
+ localStorage.setItem( 'login-ok', 'true' );
+ localStorage.removeItem( 'close-tab' );
+
+ return window.close();
+ }
+
+ router.push( '/app' );
+ };
+
const login = () => {
if ( isLoggingIn.value ) return;