Prepare for new sdk

This commit is contained in:
2025-09-15 11:17:17 +02:00
parent 6b9d556e57
commit 6e93cfdf2c
19 changed files with 899 additions and 938 deletions

View File

@@ -1,9 +1,11 @@
import ColorThief from 'colorthief';
const colorThief = new ColorThief();
const getImageData = (): Promise<number[][]> => {
return new Promise( ( resolve ) => {
const img = ( document.getElementById( 'current-image' ) as HTMLImageElement );
return new Promise( resolve => {
const img = document.getElementById( 'current-image' ) as HTMLImageElement;
if ( img.complete ) {
resolve( colorThief.getPalette( img ) );
} else {
@@ -12,32 +14,39 @@ const getImageData = (): Promise<number[][]> => {
} );
}
} );
}
};
const createBackground = () => {
return new Promise( ( resolve ) => {
return new Promise( resolve => {
getImageData().then( palette => {
const colourDetails: number[][] = [];
const colours: string[] = [];
let differentEnough = true;
if ( palette[ 0 ] ) {
for ( const i in palette ) {
for ( const colour in colourDetails ) {
const colourDiff = ( Math.abs( colourDetails[ colour ][ 0 ] - palette[ i ][ 0 ] ) / 255
+ Math.abs( colourDetails[ colour ][ 1 ] - palette[ i ][ 1 ] ) / 255
+ Math.abs( colourDetails[ colour ][ 2 ] - palette[ i ][ 2 ] ) / 255 ) / 3 * 100;
if ( colourDiff > 15 ) {
differentEnough = true;
}
}
if ( differentEnough ) {
colourDetails.push( palette[ i ] );
colours.push( 'rgb(' + palette[ i ][ 0 ] + ',' + palette[ i ][ 1 ] + ',' + palette[ i ][ 2 ] + ')' );
}
differentEnough = false;
}
}
let outColours = 'conic-gradient(';
if ( colours.length < 3 ) {
for ( let i = 0; i < 3; i++ ) {
if ( colours[ i ] ) {
@@ -61,45 +70,56 @@ const createBackground = () => {
outColours += colours[ i ] + ',';
}
}
outColours += colours[ 0 ] ?? 'blue' + ')';
resolve( outColours );
} );
} );
}
};
let callbackFun = () => {};
let callbackFun = () => {}
const subscribeToBeatUpdate = ( cb: () => void ) => {
callbackFun = cb;
micAudioHandler();
}
};
const unsubscribeFromBeatUpdate = () => {
callbackFun = () => {}
callbackFun = () => {};
try {
clearInterval( micAnalyzer );
} catch ( e ) { /* empty */ }
}
};
const coolDown = () => {
beatDetected = false;
}
};
let micAnalyzer = 0;
let beatDetected = false;
const micAudioHandler = () => {
const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
const analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array( bufferLength );
beatDetected = false;
navigator.mediaDevices.getUserMedia( { audio: true } ).then( ( stream ) => {
navigator.mediaDevices.getUserMedia( {
'audio': true
} ).then( stream => {
const mic = audioContext.createMediaStreamSource( stream );
mic.connect( analyser );
analyser.getByteFrequencyData( dataArray );
let prevSpectrum: number[] = [];
const threshold = 10; // Adjust as needed
micAnalyzer = setInterval( () => {
analyser.getByteFrequencyData( dataArray );
// Convert the frequency data to a numeric array
@@ -115,25 +135,27 @@ const micAudioHandler = () => {
callbackFun();
}
}
prevSpectrum = currentSpectrum;
}, 60 / 180 * 250 );
} );
}
};
const calculateSpectralFlux = ( prevSpectrum: number[], currentSpectrum: number[] ) => {
let flux = 0;
for ( let i = 0; i < prevSpectrum.length; i++ ) {
const diff = currentSpectrum[ i ] - prevSpectrum[ i ];
flux += Math.max( 0, diff );
}
return flux;
}
};
export default {
createBackground,
subscribeToBeatUpdate,
unsubscribeFromBeatUpdate,
coolDown,
}
};

View File

@@ -1,30 +1,33 @@
/*
* 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";
import type { SSEMap } from "./song";
import {
io, type Socket
} from 'socket.io-client';
import type {
SSEMap
} from './song';
class SocketConnection {
socket: Socket;
roomName: string;
isConnected: boolean;
useSocket: boolean;
eventSource?: EventSource;
toBeListenedForItems: SSEMap;
reconnectRetryCount: number;
openConnectionsCount: number;
constructor () {
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
autoConnect: false,
'autoConnect': false,
} );
this.roomName = location.pathname.split( '/' )[ 2 ];
this.isConnected = false;
@@ -35,55 +38,71 @@ class SocketConnection {
}
/**
* Create a room token and connect to
* Create a room token and connect to
* @returns {Promise<string>}
*/
connect (): Promise<any> {
connect (): Promise<unknown> {
return new Promise( ( resolve, reject ) => {
if ( this.reconnectRetryCount < 5 ) {
if ( this.useSocket ) {
this.socket.connect();
this.socket.emit( 'join-room', this.roomName, ( res: { status: boolean, msg: string, data: any } ) => {
if ( res.status === true ) {
this.isConnected = true;
resolve( res.data );
} else {
console.debug( res.msg );
reject( 'ERR_ROOM_CONNECTING' );
this.socket.emit(
'join-room', this.roomName, ( res: {
'status': boolean,
'msg': string,
'data': unknown
} ) => {
if ( res.status === true ) {
this.isConnected = true;
resolve( res.data );
} else {
console.debug( res.msg );
reject( 'ERR_ROOM_CONNECTING' );
}
}
} );
);
} else {
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
this.openConnectionsCount += 1;
fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => {
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
= 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!' );
}
console.log( '[ SSE Connection ] - '
+ new Date().toISOString() + ': Connection successfully established!' );
};
this.eventSource.onmessage = ( e ) => {
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 );
}
}
};
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.log( '[ SSE Connection ] - '
+ new Date().toISOString()
+ ': Reconnecting due to connection error!' );
// console.debug( e );
this.eventSource = undefined;
this.reconnectRetryCount += 1;
setTimeout( () => {
this.connect();
@@ -91,13 +110,18 @@ class SocketConnection {
}
};
} else {
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Could not connect due to error ' + res.status );
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( () => {
console.log( '[ SSE Connection ] - '
+ new Date().toISOString()
+ ': Could not connect due to error.' );
reject( 'ERR_ROOM_CONNECTING' );
} );
} else {
console.log( '[ SSE Connection ]: Trimmed connections' );
reject( 'ERR_TOO_MANY_CONNECTIONS' );
@@ -116,16 +140,23 @@ class SocketConnection {
* @param {any} data
* @returns {void}
*/
emit ( event: string, data: any ): void {
emit ( event: string, data: unknown ): void {
if ( this.isConnected ) {
if ( this.useSocket ) {
this.socket.emit( event, { 'roomName': this.roomName, 'data': data } );
this.socket.emit( event, {
'roomName': this.roomName,
'data': data
} );
} else {
fetch( localStorage.getItem( 'url' ) + '/socket/update', {
method: 'post',
body: JSON.stringify( { 'event': event, 'roomName': this.roomName, 'data': data } ),
credentials: 'include',
headers: {
'method': 'post',
'body': JSON.stringify( {
'event': event,
'roomName': this.roomName,
'data': data
} ),
'credentials': 'include',
'headers': {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
@@ -140,11 +171,11 @@ class SocketConnection {
* @param {( data: any ) => void} cb The callback function / listener function
* @returns {void}
*/
registerListener ( event: string, cb: ( data: any ) => void ): void {
registerListener ( event: string, cb: ( data: unknown ) => void ): void {
if ( this.useSocket ) {
if ( this.isConnected ) {
this.socket.on( event, cb );
}
}
} else {
this.toBeListenedForItems[ event ] = cb;
}
@@ -171,9 +202,11 @@ class SocketConnection {
if ( this.eventSource ) {
return this.eventSource!.OPEN && this.isConnected;
}
return false;
}
}
}
export default SocketConnection;
export default SocketConnection;

View File

@@ -1,25 +1,39 @@
import type { SearchResult, Song, SongMove } from "./song";
import type {
SearchResult, Song, SongMove
} from './song';
interface Config {
devToken: string;
userToken: string;
'devToken': string;
'userToken': string;
}
type ControlAction = 'play' | 'pause' | 'next' | 'previous' | 'skip-10' | 'back-10';
type RepeatMode = 'off' | 'once' | 'all';
class MusicKitJSWrapper {
playingSongID: number;
playlist: Song[];
queue: number[];
config: Config;
musicKit: any;
isLoggedIn: boolean;
isPreparedToPlay: boolean;
repeatMode: RepeatMode;
isShuffleEnabled: boolean;
hasEncounteredAuthError: boolean;
queuePos: number;
audioPlayer: HTMLAudioElement;
constructor () {
@@ -27,8 +41,8 @@ class MusicKitJSWrapper {
this.playlist = [];
this.queue = [];
this.config = {
devToken: '',
userToken: '',
'devToken': '',
'userToken': '',
};
this.isShuffleEnabled = false;
this.repeatMode = 'off';
@@ -58,16 +72,18 @@ class MusicKitJSWrapper {
this.musicKit.authorize().then( () => {
this.isLoggedIn = true;
this.init();
} ).catch( () => {
this.hasEncounteredAuthError = true;
} );
} )
.catch( () => {
this.hasEncounteredAuthError = true;
} );
} else {
this.musicKit.authorize().then( () => {
this.isLoggedIn = true;
this.init();
} ).catch( () => {
this.hasEncounteredAuthError = true;
} );
} )
.catch( () => {
this.hasEncounteredAuthError = true;
} );
}
}
@@ -76,25 +92,29 @@ class MusicKitJSWrapper {
* @returns {void}
*/
init (): void {
fetch( localStorage.getItem( 'url' ) + '/getAppleMusicDevToken', { credentials: 'include' } ).then( res => {
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,
app: {
name: 'MusicPlayer',
build: '3'
'developerToken': token,
'app': {
'name': 'MusicPlayer',
'build': '3'
},
storefrontId: 'CH',
'storefrontId': 'CH',
} ).then( () => {
this.config.devToken = token;
this.musicKit = MusicKit.getInstance();
if ( this.musicKit.isAuthorized ) {
this.isLoggedIn = true;
this.config.userToken = this.musicKit.musicUserToken;
}
this.musicKit.shuffleMode = MusicKit.PlayerShuffleMode.off;
} );
} );
@@ -107,7 +127,10 @@ class MusicKitJSWrapper {
* @returns {boolean[]} Returns an array, where the first element indicates login status, the second one, if an error was encountered
*/
getAuth (): boolean[] {
return [ this.isLoggedIn, this.hasEncounteredAuthError ];
return [
this.isLoggedIn,
this.hasEncounteredAuthError
];
}
/**
@@ -119,8 +142,8 @@ class MusicKitJSWrapper {
apiGetRequest ( url: string, callback: ( data: object ) => void ): void {
if ( this.config.devToken != '' && this.config.userToken != '' ) {
fetch( url, {
method: 'GET',
headers: {
'method': 'GET',
'headers': {
'Authorization': `Bearer ${ this.config.devToken }`,
'Music-User-Token': this.config.userToken
}
@@ -128,13 +151,19 @@ class MusicKitJSWrapper {
if ( res.status === 200 ) {
res.json().then( json => {
try {
callback( { 'status': 'ok', 'data': json } );
} catch( err ) { /* empty */}
callback( {
'status': 'ok',
'data': json
} );
} catch ( err ) { /* empty */ }
} );
} else {
try {
callback( { 'status': 'error', 'error': res.status } );
} catch( err ) { /* empty */}
callback( {
'status': 'error',
'error': res.status
} );
} catch ( err ) { /* empty */ }
}
} );
} else return;
@@ -152,34 +181,41 @@ class MusicKitJSWrapper {
setPlaylistByID ( id: string ): Promise<void> {
return new Promise( ( resolve, reject ) => {
this.musicKit.setQueue( { playlist: id } ).then( () => {
this.musicKit.setQueue( {
'playlist': id
} ).then( () => {
const pl = this.musicKit.queue.items;
const songs: Song[] = [];
for ( const item in pl ) {
let url = pl[ item ].attributes.artwork.url;
url = url.replace( '{w}', pl[ item ].attributes.artwork.width );
url = url.replace( '{h}', pl[ item ].attributes.artwork.height );
const song: Song = {
artist: pl[ item ].attributes.artistName,
cover: url,
duration: pl[ item ].attributes.durationInMillis / 1000,
id: pl[ item ].id,
origin: 'apple-music',
title: pl[ item ].attributes.name,
genres: pl[ item ].attributes.genreNames
}
'artist': pl[ item ].attributes.artistName,
'cover': url,
'duration': pl[ item ].attributes.durationInMillis / 1000,
'id': pl[ item ].id,
'origin': 'apple-music',
'title': pl[ item ].attributes.name,
'genres': pl[ item ].attributes.genreNames
};
songs.push( song );
}
this.playlist = songs;
this.setShuffle( this.isShuffleEnabled );
this.queuePos = 0;
this.playingSongID = this.queue[ 0 ];
this.prepare( this.playingSongID );
resolve();
} ).catch( err => {
console.error( err );
reject( err );
} );
} )
.catch( err => {
console.error( err );
reject( err );
} );
} );
}
@@ -192,20 +228,25 @@ class MusicKitJSWrapper {
if ( this.playlist.length > 0 ) {
this.playingSongID = playlistID;
this.isPreparedToPlay = true;
for ( const el in this.queue ) {
if ( this.queue[ el ] === playlistID ) {
this.queuePos = parseInt( el );
break;
}
}
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
this.musicKit.setQueue( { 'song': this.playlist[ this.playingSongID ].id } ).then( () => {
this.musicKit.setQueue( {
'song': this.playlist[ this.playingSongID ].id
} ).then( () => {
setTimeout( () => {
this.control( 'play' );
}, 500 );
} ).catch( ( err ) => {
console.log( err );
} );
} )
.catch( err => {
console.log( err );
} );
} else {
this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
this.audioPlayer.src = this.playlist[ this.playingSongID ].id;
@@ -213,6 +254,7 @@ class MusicKitJSWrapper {
this.control( 'play' );
}, 500 );
}
return true;
} else {
return false;
@@ -226,50 +268,63 @@ class MusicKitJSWrapper {
*/
control ( action: ControlAction ): boolean {
switch ( action ) {
case "play":
case 'play':
if ( this.isPreparedToPlay ) {
this.control( 'pause' );
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
this.musicKit.play();
return false;
} else {
this.audioPlayer.play();
return false;
}
} else {
return false;
}
case "pause":
case 'pause':
if ( this.isPreparedToPlay ) {
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
this.musicKit.pause();
return false;
} else {
this.audioPlayer.pause();
return false;
}
} else {
return false;
}
case "back-10":
case 'back-10':
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
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;
}
case "skip-10":
case 'skip-10':
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
if ( this.musicKit.currentPlaybackTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime + 10 );
return false;
} else {
if ( this.repeatMode !== 'once' ) {
this.control( 'next' );
return true;
} else {
this.musicKit.seekToTime( 0 );
return false;
}
}
@@ -283,32 +338,42 @@ class MusicKitJSWrapper {
this.audioPlayer.currentTime = 0;
}
}
return false;
}
case "next":
case 'next':
this.control( 'pause' );
if ( this.queuePos < this.queue.length - 1 ) {
this.queuePos += 1;
this.prepare( this.queue[ this.queuePos ] );
return true;
} else {
this.queuePos = 0;
if ( this.repeatMode !== 'all' ) {
this.control( 'pause' );
} else {
this.playingSongID = this.queue[ this.queuePos ];
this.prepare( this.queue[ this.queuePos ] );
}
return true;
}
case "previous":
case 'previous':
this.control( 'pause' );
if ( this.queuePos > 0 ) {
this.queuePos -= 1;
this.prepare( this.queue[ this.queuePos ] );
return true;
} else {
this.queuePos = this.queue.length - 1;
return true;
}
}
@@ -317,15 +382,22 @@ class MusicKitJSWrapper {
setShuffle ( enabled: boolean ) {
this.isShuffleEnabled = enabled;
this.queue = [];
if ( enabled ) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const d = [];
for ( const el in this.playlist ) {
d.push( parseInt( el ) );
}
this.queue = d.map( value => ( { value, sort: Math.random() } ) )
.sort( ( a, b ) => a.sort - b.sort )
.map( ( { value } ) => value );
this.queue = d.map( value => ( {
value,
'sort': Math.random()
} ) )
.sort( ( a, b ) => a.sort - b.sort )
.map( ( {
value
} ) => value );
this.queue.splice( this.queue.indexOf( this.playingSongID ), 1 );
this.queue.push( this.playingSongID );
this.queue.reverse();
@@ -334,6 +406,7 @@ 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 ) {
@@ -359,29 +432,37 @@ class MusicKitJSWrapper {
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;
}
@@ -435,9 +516,11 @@ class MusicKitJSWrapper {
*/
getQueue (): Song[] {
const data = [];
for ( const el in this.queue ) {
data.push( this.playlist[ this.queue[ el ] ] );
}
return data;
}
@@ -449,13 +532,14 @@ class MusicKitJSWrapper {
getUserPlaylists ( cb: ( data: object ) => void ): boolean {
if ( this.isLoggedIn ) {
this.apiGetRequest( 'https://api.music.apple.com/v1/me/library/playlists', cb );
return true;
} else {
return false;
}
}
getPlaying ( ): boolean {
getPlaying ( ): boolean {
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
return this.musicKit.isPlaying;
} else {
@@ -466,18 +550,21 @@ class MusicKitJSWrapper {
findSongOnAppleMusic ( searchTerm: string ): Promise<SearchResult> {
// TODO: Make storefront adjustable
return new Promise( ( resolve, reject ) => {
const queryParameters = {
term: ( searchTerm ),
types: [ 'songs' ],
const queryParameters = {
'term': searchTerm,
'types': [ 'songs' ],
};
this.musicKit.api.music( `v1/catalog/ch/search`, queryParameters ).then( results => {
this.musicKit.api.music( 'v1/catalog/ch/search', queryParameters ).then( results => {
resolve( results );
} ).catch( e => {
console.error( e );
reject( e );
} );
} )
.catch( e => {
console.error( e );
reject( e );
} );
} );
}
}
export default MusicKitJSWrapper;
export default MusicKitJSWrapper;

View File

@@ -1,34 +1,41 @@
/*
* 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"
import type { SSEMap } from "./song";
import {
io, type Socket
} from 'socket.io-client';
import type {
SSEMap
} from './song';
class NotificationHandler {
socket: Socket;
roomName: string;
roomToken: string;
isConnected: boolean;
useSocket: boolean;
eventSource?: EventSource;
toBeListenedForItems: SSEMap;
reconnectRetryCount: number;
lastEmitTimestamp: number;
openConnectionsCount: number;
pendingRequestCount: number;
connectionWasSuccessful: boolean;
constructor () {
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
autoConnect: false,
'autoConnect': false,
} );
this.roomName = '';
this.roomToken = '';
@@ -43,35 +50,44 @@ class NotificationHandler {
}
/**
* Create a room token and connect to
* @param {string} roomName
* Create a room token and connect to
* @param {string} roomName
* @param {boolean} useAntiTamper
* @returns {Promise<string>}
*/
connect ( roomName: string, useAntiTamper: boolean ): Promise<void> {
return new Promise( ( resolve, reject ) => {
fetch( localStorage.getItem( 'url' ) + '/createRoomToken?roomName=' + roomName + '&useAntiTamper=' + useAntiTamper, { credentials: 'include' } ).then( res => {
fetch( localStorage.getItem( 'url' ) + '/createRoomToken?roomName=' + roomName + '&useAntiTamper=' + useAntiTamper, {
'credentials': 'include'
} ).then( res => {
if ( res.status === 200 ) {
res.text().then( text => {
this.roomToken = text;
this.roomName = roomName;
if ( this.useSocket ) {
this.socket.connect();
this.socket.emit( 'create-room', {
name: this.roomName,
token: this.roomToken
}, ( res: { status: boolean, msg: string } ) => {
if ( res.status === true ) {
this.isConnected = true;
resolve();
} else {
reject( 'ERR_ROOM_CONNECTING' );
this.socket.emit(
'create-room', {
'name': this.roomName,
'token': this.roomToken
}, ( res: {
'status': boolean,
'msg': string
} ) => {
if ( res.status === true ) {
this.isConnected = true;
resolve();
} else {
reject( 'ERR_ROOM_CONNECTING' );
}
}
} );
);
} else {
this.sseConnect().then( () => {
resolve();
} ).catch( );
} )
.catch( );
}
} );
} else if ( res.status === 409 ) {
@@ -90,9 +106,13 @@ class NotificationHandler {
if ( this.reconnectRetryCount < 5 ) {
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
this.openConnectionsCount += 1;
fetch( localStorage.getItem( 'url' ) + '/socket/joinRoom?room=' + this.roomName, { credentials: 'include' } ).then( res => {
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 = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, {
'withCredentials': true
} );
this.eventSource.onopen = () => {
this.isConnected = true;
@@ -100,25 +120,26 @@ class NotificationHandler {
this.reconnectRetryCount = 0;
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' );
resolve();
}
this.eventSource.onmessage = ( e ) => {
};
this.eventSource.onmessage = e => {
const d = JSON.parse( e.data );
if ( this.toBeListenedForItems[ d.type ] ) {
this.toBeListenedForItems[ d.type ]( d.data );
}
}
this.eventSource.onerror = ( e ) => {
};
this.eventSource.onerror = e => {
if ( this.isConnected ) {
this.isConnected = false;
this.eventSource?.close();
this.openConnectionsCount -= 1;
console.debug( e );
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' );
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' );
this.eventSource = undefined;
this.reconnectRetryCount += 1;
setTimeout( () => {
this.sseConnect();
@@ -131,21 +152,22 @@ class NotificationHandler {
} else {
reject( 'ERR_ROOM_CONNECTING_STATUS_CODE' );
}
} ).catch( () => {
if ( !this.connectionWasSuccessful ) {
reject( 'ERR_ROOM_CONNECTING' );
} else {
this.openConnectionsCount -= 1;
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to severe connection error!' );
this.eventSource = undefined;
this.reconnectRetryCount += 1;
setTimeout( () => {
this.sseConnect();
}, 1000 * this.reconnectRetryCount );
}
} );
} )
.catch( () => {
if ( !this.connectionWasSuccessful ) {
reject( 'ERR_ROOM_CONNECTING' );
} else {
this.openConnectionsCount -= 1;
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to severe connection error!' );
this.eventSource = undefined;
this.reconnectRetryCount += 1;
setTimeout( () => {
this.sseConnect();
}, 1000 * this.reconnectRetryCount );
}
} );
} else {
resolve();
}
@@ -169,9 +191,14 @@ class NotificationHandler {
emit ( event: string, data: any ): void {
if ( this.isConnected ) {
if ( this.useSocket ) {
this.socket.emit( event, { 'roomToken': this.roomToken, 'roomName': this.roomName, 'data': data } );
this.socket.emit( event, {
'roomToken': this.roomToken,
'roomName': this.roomName,
'data': data
} );
} else {
const now = new Date().getTime();
if ( this.lastEmitTimestamp < now - 250 ) {
this.lastEmitTimestamp = now;
this.sendEmitConventionally( event, data );
@@ -189,10 +216,15 @@ class NotificationHandler {
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: {
'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'
}
@@ -209,7 +241,7 @@ class NotificationHandler {
if ( this.useSocket ) {
if ( this.isConnected ) {
this.socket.on( event, cb );
}
}
} else {
this.toBeListenedForItems[ event ] = cb;
}
@@ -222,22 +254,32 @@ class NotificationHandler {
async disconnect (): Promise<void> {
if ( this.isConnected ) {
if ( this.useSocket ) {
this.socket.emit( 'delete-room', {
name: this.roomName,
token: this.roomToken
}, ( res: { status: boolean, msg: string } ) => {
this.socket.disconnect();
if ( !res.status ) {
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
this.socket.emit(
'delete-room', {
'name': this.roomName,
'token': this.roomToken
}, ( res: {
'status': boolean,
'msg': string
} ) => {
this.socket.disconnect();
if ( !res.status ) {
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
}
return;
}
return;
} );
);
} else {
fetch( localStorage.getItem( 'url' ) + '/socket/deleteRoom', {
method: 'post',
body: JSON.stringify( { 'roomName': this.roomName, 'roomToken': this.roomToken } ),
credentials: 'include',
headers: {
'method': 'post',
'body': JSON.stringify( {
'roomName': this.roomName,
'roomToken': this.roomToken
} ),
'credentials': 'include',
'headers': {
'Content-Type': 'application/json',
'charset': 'utf-8'
}
@@ -247,10 +289,12 @@ class NotificationHandler {
} else {
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
}
return;
} ).catch( () => {
return;
} );
} )
.catch( () => {
return;
} );
}
}
}
@@ -258,6 +302,7 @@ class NotificationHandler {
getRoomName (): string {
return this.roomName;
}
}
export default NotificationHandler;

View File

@@ -4,92 +4,92 @@ export interface Song {
/**
* The ID. Either the apple music ID, or if from local disk, an ID starting in local_
*/
id: string;
'id': string;
/**
* Origin of the song
*/
origin: Origin;
'origin': Origin;
/**
* The cover image as a URL
*/
cover: string;
'cover': string;
/**
* The artist of the song
*/
artist: string;
'artist': string;
/**
* The name of the song
*/
title: string;
'title': string;
/**
* Duration of the song in milliseconds
*/
duration: number;
'duration': number;
/**
* (OPTIONAL) The genres this song belongs to. Can be displayed on the showcase screen, but requires settings there
*/
genres?: string[];
'genres'?: string[];
/**
* (OPTIONAL) This will be displayed in brackets on the showcase screens
*/
additionalInfo?: string;
'additionalInfo'?: string;
}
export interface SongTransmitted {
title: string;
artist: string;
duration: number;
cover: string;
additionalInfo?: string;
'title': string;
'artist': string;
'duration': number;
'cover': string;
'additionalInfo'?: string;
}
export interface ReadFile {
url: string;
filename: string;
'url': string;
'filename': string;
}
export interface SearchResult {
data: {
results: {
songs: {
data: AppleMusicSongData[],
href: string;
'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
'id': string,
'type': string;
'href': string;
'attributes': {
'albumName': string;
'artistName': string;
'artwork': {
'width': number,
'height': number,
'url': string
},
name: string;
genreNames: string[];
durationInMillis: number;
'name': string;
'genreNames': string[];
'durationInMillis': number;
}
}
export interface SongMove {
songID: string;
newPos: number;
'songID': string;
'newPos': number;
}
export interface SSEMap {
[key: string]: ( data: any ) => void;
}
}