diff --git a/MusicPlayerV2-GUI/src/components/playerView.vue b/MusicPlayerV2-GUI/src/components/playerView.vue index dff082f..8899fa9 100644 --- a/MusicPlayerV2-GUI/src/components/playerView.vue +++ b/MusicPlayerV2-GUI/src/components/playerView.vue @@ -451,7 +451,7 @@ niceDuration.value += secondCounts; } } - }, 50 ); + }, 100 ); } const prepNiceDurationTime = ( playingSong: Song ) => { @@ -596,8 +596,8 @@ } } - window.addEventListener( 'beforeunload', () => { - notificationHandler.disconnect(); + window.addEventListener( 'beforeunload', async () => { + await notificationHandler.disconnect(); } ); defineExpose( { diff --git a/MusicPlayerV2-GUI/src/components/playlistView.vue b/MusicPlayerV2-GUI/src/components/playlistView.vue index dad2d27..018fd9c 100644 --- a/MusicPlayerV2-GUI/src/components/playlistView.vue +++ b/MusicPlayerV2-GUI/src/components/playlistView.vue @@ -28,7 +28,7 @@ arrow_downward

{{ song.title }}

- +

{{ getTimeUntil( song ) }}

diff --git a/MusicPlayerV2-GUI/src/scripts/connection.ts b/MusicPlayerV2-GUI/src/scripts/connection.ts index 2075224..bec578f 100644 --- a/MusicPlayerV2-GUI/src/scripts/connection.ts +++ b/MusicPlayerV2-GUI/src/scripts/connection.ts @@ -20,6 +20,7 @@ class SocketConnection { eventSource?: EventSource; toBeListenedForItems: SSEMap; reconnectRetryCount: number; + openConnectionsCount: number; constructor () { this.socket = io( localStorage.getItem( 'url' ) ?? '', { @@ -30,6 +31,7 @@ class SocketConnection { this.useSocket = localStorage.getItem( 'music-player-config' ) === 'ws'; this.toBeListenedForItems = {}; this.reconnectRetryCount = 0; + this.openConnectionsCount = 0; } /** @@ -51,40 +53,55 @@ class SocketConnection { } } ); } else { - fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => { - if ( res.status === 200 ) { - this.eventSource = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, { withCredentials: true } ); + if ( this.openConnectionsCount < 1 && !this.isConnected ) { + this.openConnectionsCount += 1; + fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => { + if ( res.status === 200 ) { + this.eventSource = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, { withCredentials: true } ); - this.eventSource.onmessage = ( e ) => { - const d = JSON.parse( e.data ); - if ( this.toBeListenedForItems[ d.type ] ) { - this.toBeListenedForItems[ d.type ]( d.data ); - } else if ( d.type === 'basics' ) { + this.eventSource.onopen = () => { this.isConnected = true; - resolve( d.data ); + this.reconnectRetryCount = 0; + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' ); } - } - this.eventSource.onerror = ( e ) => { - if ( this.isConnected ) { - this.isConnected = false; - console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' ); - console.debug( e ); - - this.eventSource = undefined; - - this.reconnectRetryCount += 1; - setTimeout( () => { - this.connect(); - }, 500 * this.reconnectRetryCount ); + this.eventSource.onmessage = ( e ) => { + const d = JSON.parse( e.data ); + if ( this.toBeListenedForItems[ d.type ] ) { + this.toBeListenedForItems[ d.type ]( d.data ); + } else if ( d.type === 'basics' ) { + resolve( d.data ); + } } - }; - } else { + + this.eventSource.onerror = () => { + if ( this.isConnected ) { + this.isConnected = false; + this.openConnectionsCount -= 1; + this.eventSource?.close(); + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' ); + // console.debug( e ); + + this.eventSource = undefined; + + this.reconnectRetryCount += 1; + setTimeout( () => { + this.connect(); + }, 500 * this.reconnectRetryCount ); + } + }; + } else { + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Could not connect due to error ' + res.status ); + reject( 'ERR_ROOM_CONNECTING' ); + } + } ).catch( () => { + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Could not connect due to error.' ); reject( 'ERR_ROOM_CONNECTING' ); - } - } ).catch( () => { - reject( 'ERR_ROOM_CONNECTING' ); - } ); + } ); + } else { + console.log( '[ SSE Connection ]: Trimmed connections' ); + reject( 'ERR_TOO_MANY_CONNECTIONS' ); + } } } else { alert( 'Could not reconnect to the share. Please reload the page to retry!' ); @@ -146,6 +163,17 @@ class SocketConnection { } } } + + getStatus (): boolean { + if ( this.useSocket ) { + return true; + } else { + if ( this.eventSource ) { + return this.eventSource!.OPEN && this.isConnected; + } + return false; + } + } } export default SocketConnection; \ No newline at end of file diff --git a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts index 6ccd166..2b80bf9 100644 --- a/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts +++ b/MusicPlayerV2-GUI/src/scripts/notificationHandler.ts @@ -21,6 +21,9 @@ class NotificationHandler { eventSource?: EventSource; toBeListenedForItems: SSEMap; reconnectRetryCount: number; + lastEmitTimestamp: number; + openConnectionsCount: number; + pendingRequestCount: number; constructor () { this.socket = io( localStorage.getItem( 'url' ) ?? '', { @@ -32,6 +35,9 @@ class NotificationHandler { this.useSocket = localStorage.getItem( 'music-player-config' ) === 'ws'; this.toBeListenedForItems = {}; this.reconnectRetryCount = 0; + this.lastEmitTimestamp = 0; + this.pendingRequestCount = 0; + this.openConnectionsCount = 0; } /** @@ -78,42 +84,50 @@ class NotificationHandler { sseConnect (): Promise { return new Promise( ( resolve, reject ) => { if ( this.reconnectRetryCount < 5 ) { - fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => { - if ( res.status === 200 ) { - this.eventSource = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, { withCredentials: true } ); - this.eventSource.onopen = () => { - this.isConnected = true; - this.reconnectRetryCount = 0; - resolve(); - } - - this.eventSource.onmessage = ( e ) => { - const d = JSON.parse( e.data ); - if ( this.toBeListenedForItems[ d.type ] ) { - this.toBeListenedForItems[ d.type ]( d.data ); + if ( this.openConnectionsCount < 1 && !this.isConnected ) { + this.openConnectionsCount += 1; + fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => { + if ( res.status === 200 ) { + this.eventSource = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, { withCredentials: true } ); + this.eventSource.onopen = () => { + this.isConnected = true; + this.reconnectRetryCount = 0; + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' ); + resolve(); } - } - - this.eventSource.onerror = ( e ) => { - if ( this.isConnected ) { - this.isConnected = false; - console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' ); - console.debug( e ); - - this.eventSource = undefined; - - this.reconnectRetryCount += 1; - setTimeout( () => { - this.sseConnect(); - }, 500 * this.reconnectRetryCount ); + + this.eventSource.onmessage = ( e ) => { + const d = JSON.parse( e.data ); + if ( this.toBeListenedForItems[ d.type ] ) { + this.toBeListenedForItems[ d.type ]( d.data ); + } } - }; - } else { + + this.eventSource.onerror = ( e ) => { + if ( this.isConnected ) { + this.isConnected = false; + this.eventSource?.close(); + this.openConnectionsCount -= 1; + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' ); + console.debug( e ); + + this.eventSource = undefined; + + this.reconnectRetryCount += 1; + setTimeout( () => { + this.sseConnect(); + }, 500 * this.reconnectRetryCount ); + } + }; + } else { + reject( 'ERR_ROOM_CONNECTING' ); + } + } ).catch( () => { reject( 'ERR_ROOM_CONNECTING' ); - } - } ).catch( () => { - reject( 'ERR_ROOM_CONNECTING' ); - } ); + } ); + } else { + resolve(); + } } else { if ( confirm( 'Connection lost and it could not be reestablished. Please click ok to retry or press cancel to stop retrying. Your share will be deleted as a result thereof.' ) ) { this.reconnectRetryCount = 0; @@ -136,19 +150,34 @@ class NotificationHandler { if ( this.useSocket ) { this.socket.emit( event, { 'roomToken': this.roomToken, 'roomName': this.roomName, 'data': data } ); } else { - fetch( localStorage.getItem( 'url' ) + '/socket/update', { - method: 'post', - body: JSON.stringify( { 'event': event, 'roomName': this.roomName, 'roomToken': this.roomToken, 'data': data } ), - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - 'charset': 'utf-8' - } - } ).catch( () => {} ); + const now = new Date().getTime(); + if ( this.lastEmitTimestamp < now - 250 ) { + this.lastEmitTimestamp = now; + this.sendEmitConventionally( event, data ); + } else { + this.pendingRequestCount += 1; + setTimeout( () => { + this.pendingRequestCount = 0; + this.lastEmitTimestamp = now; + this.sendEmitConventionally( event, data ); + }, 250 * this.pendingRequestCount ); + } } } } + sendEmitConventionally ( event: string, data: any ): void { + fetch( localStorage.getItem( 'url' ) + '/socket/update', { + method: 'post', + body: JSON.stringify( { 'event': event, 'roomName': this.roomName, 'roomToken': this.roomToken, 'data': data } ), + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'charset': 'utf-8' + } + } ).catch( () => {} ); + } + /** * Register a listener function for an event * @param {string} event The event to listen for @@ -169,7 +198,7 @@ class NotificationHandler { * Disconnect from the server * @returns {any} */ - disconnect (): void { + async disconnect (): Promise { if ( this.isConnected ) { if ( this.useSocket ) { this.socket.emit( 'delete-room', { @@ -180,6 +209,7 @@ class NotificationHandler { if ( !res.status ) { alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' ); } + return; } ); } else { fetch( localStorage.getItem( 'url' ) + '/socket/deleteRoom', { @@ -196,7 +226,10 @@ class NotificationHandler { } else { alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' ); } - } ).catch( () => {} ); + return; + } ).catch( () => { + return; + } ); } } } diff --git a/MusicPlayerV2-GUI/src/views/RemoteView.vue b/MusicPlayerV2-GUI/src/views/RemoteView.vue index 90c2c2e..cc84fd1 100644 --- a/MusicPlayerV2-GUI/src/views/RemoteView.vue +++ b/MusicPlayerV2-GUI/src/views/RemoteView.vue @@ -149,12 +149,28 @@ if ( isNaN( progressBar.value ) ) { progressBar.value = 0; } + + if ( playlist.value[ playingSong.value ].duration + 10 - pos.value < 0 ) { + stopTimeTracker(); + alert( 'It looks like if you have been disconnected! We are trying to reconnect you now!' ); + location.reload(); + } }, 100 ); } const stopTimeTracker = () => { clearInterval( timeTracker ); } + + document.addEventListener( 'visibilitychange', () => { + if ( !document.hidden ) { + if ( !conn.getStatus ) { + stopTimeTracker(); + alert( 'It looks like if you have been disconnected! We are trying to reconnect you now!' ); + location.reload(); + } + } + } );