mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 04:54:23 +00:00
Prepare for new sdk
This commit is contained in:
@@ -75,7 +75,7 @@ const style = {
|
|||||||
],
|
],
|
||||||
'@stylistic/eol-last': [
|
'@stylistic/eol-last': [
|
||||||
'error',
|
'error',
|
||||||
'always'
|
'never'
|
||||||
],
|
],
|
||||||
'@stylistic/function-call-spacing': [
|
'@stylistic/function-call-spacing': [
|
||||||
'error',
|
'error',
|
||||||
@@ -83,7 +83,9 @@ const style = {
|
|||||||
],
|
],
|
||||||
'@stylistic/function-paren-newline': [
|
'@stylistic/function-paren-newline': [
|
||||||
'error',
|
'error',
|
||||||
'multiline'
|
{
|
||||||
|
'minItems': 3
|
||||||
|
}
|
||||||
],
|
],
|
||||||
'@stylistic/function-call-argument-newline': [
|
'@stylistic/function-call-argument-newline': [
|
||||||
'error',
|
'error',
|
||||||
|
|||||||
184
MusicPlayerV2-GUI/package-lock.json
generated
184
MusicPlayerV2-GUI/package-lock.json
generated
@@ -13,7 +13,7 @@
|
|||||||
"@melloware/coloris": "^0.24.0",
|
"@melloware/coloris": "^0.24.0",
|
||||||
"@rollup/plugin-inject": "^5.0.5",
|
"@rollup/plugin-inject": "^5.0.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"colorthief": "^2.2.0",
|
"colorthief": "^2.6.0",
|
||||||
"music-metadata-browser": "^2.5.10",
|
"music-metadata-browser": "^2.5.10",
|
||||||
"musickit-typescript": "^1.2.4",
|
"musickit-typescript": "^1.2.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
@@ -23,9 +23,10 @@
|
|||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.34.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"@stylistic/eslint-plugin": "^5.3.1",
|
"@stylistic/eslint-plugin": "^5.3.1",
|
||||||
"@tsconfig/node20": "^20.1.2",
|
"@tsconfig/node20": "^20.1.2",
|
||||||
|
"@types/jquery": "^3.5.33",
|
||||||
"@types/node": "^20.11.10",
|
"@types/node": "^20.11.10",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
"npm-run-all2": "^6.1.1",
|
"npm-run-all2": "^6.1.1",
|
||||||
"sass-embedded": "^1.92.0",
|
"sass-embedded": "^1.92.0",
|
||||||
"typescript": "~5.3.0",
|
"typescript": "~5.3.0",
|
||||||
"typescript-eslint": "^8.42.0",
|
"typescript-eslint": "^8.43.0",
|
||||||
"vite": "^7.1.4",
|
"vite": "^7.1.4",
|
||||||
"vue-tsc": "^2.0.29"
|
"vue-tsc": "^2.0.29"
|
||||||
}
|
}
|
||||||
@@ -639,9 +640,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.34.0",
|
"version": "9.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz",
|
||||||
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
|
"integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1816,6 +1817,16 @@
|
|||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jquery": {
|
||||||
|
"version": "3.5.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz",
|
||||||
|
"integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/sizzle": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1840,18 +1851,25 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/sizzle": {
|
||||||
|
"version": "2.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz",
|
||||||
|
"integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz",
|
||||||
"integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==",
|
"integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.42.0",
|
"@typescript-eslint/scope-manager": "8.43.0",
|
||||||
"@typescript-eslint/type-utils": "8.42.0",
|
"@typescript-eslint/type-utils": "8.43.0",
|
||||||
"@typescript-eslint/utils": "8.42.0",
|
"@typescript-eslint/utils": "8.43.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.42.0",
|
"@typescript-eslint/visitor-keys": "8.43.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -1865,7 +1883,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.42.0",
|
"@typescript-eslint/parser": "^8.43.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
@@ -1881,16 +1899,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz",
|
||||||
"integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==",
|
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.42.0",
|
"@typescript-eslint/scope-manager": "8.43.0",
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.42.0",
|
"@typescript-eslint/typescript-estree": "8.43.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.42.0",
|
"@typescript-eslint/visitor-keys": "8.43.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1906,14 +1924,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz",
|
||||||
"integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==",
|
"integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.42.0",
|
"@typescript-eslint/tsconfig-utils": "^8.43.0",
|
||||||
"@typescript-eslint/types": "^8.42.0",
|
"@typescript-eslint/types": "^8.43.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1928,14 +1946,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz",
|
||||||
"integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==",
|
"integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.42.0"
|
"@typescript-eslint/visitor-keys": "8.43.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1946,9 +1964,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz",
|
||||||
"integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==",
|
"integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1963,15 +1981,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz",
|
||||||
"integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==",
|
"integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.42.0",
|
"@typescript-eslint/typescript-estree": "8.43.0",
|
||||||
"@typescript-eslint/utils": "8.42.0",
|
"@typescript-eslint/utils": "8.43.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -1988,9 +2006,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz",
|
||||||
"integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==",
|
"integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2002,16 +2020,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz",
|
||||||
"integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==",
|
"integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.42.0",
|
"@typescript-eslint/project-service": "8.43.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.42.0",
|
"@typescript-eslint/tsconfig-utils": "8.43.0",
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.42.0",
|
"@typescript-eslint/visitor-keys": "8.43.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -2057,16 +2075,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz",
|
||||||
"integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==",
|
"integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.42.0",
|
"@typescript-eslint/scope-manager": "8.43.0",
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.42.0"
|
"@typescript-eslint/typescript-estree": "8.43.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -2081,13 +2099,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz",
|
||||||
"integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==",
|
"integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.42.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3420,6 +3438,20 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/@eslint/js": {
|
||||||
|
"version": "9.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
|
||||||
|
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://eslint.org/donate"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
"version": "10.4.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||||
@@ -4014,9 +4046,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-arrayish": {
|
"node_modules/is-arrayish": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-buffer": {
|
"node_modules/is-buffer": {
|
||||||
@@ -5923,9 +5955,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sharp/node_modules/detect-libc": {
|
"node_modules/sharp/node_modules/detect-libc": {
|
||||||
"version": "2.0.4",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
|
||||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
"integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -6040,9 +6072,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/simple-swizzle": {
|
"node_modules/simple-swizzle": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-arrayish": "^0.3.1"
|
"is-arrayish": "^0.3.1"
|
||||||
@@ -6406,16 +6438,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.42.0",
|
"version": "8.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.43.0.tgz",
|
||||||
"integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==",
|
"integrity": "sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.42.0",
|
"@typescript-eslint/eslint-plugin": "8.43.0",
|
||||||
"@typescript-eslint/parser": "8.42.0",
|
"@typescript-eslint/parser": "8.43.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.42.0",
|
"@typescript-eslint/typescript-estree": "8.43.0",
|
||||||
"@typescript-eslint/utils": "8.42.0"
|
"@typescript-eslint/utils": "8.43.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"@melloware/coloris": "^0.24.0",
|
"@melloware/coloris": "^0.24.0",
|
||||||
"@rollup/plugin-inject": "^5.0.5",
|
"@rollup/plugin-inject": "^5.0.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"colorthief": "^2.2.0",
|
"colorthief": "^2.6.0",
|
||||||
"music-metadata-browser": "^2.5.10",
|
"music-metadata-browser": "^2.5.10",
|
||||||
"musickit-typescript": "^1.2.4",
|
"musickit-typescript": "^1.2.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
@@ -27,9 +27,10 @@
|
|||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.34.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"@stylistic/eslint-plugin": "^5.3.1",
|
"@stylistic/eslint-plugin": "^5.3.1",
|
||||||
"@tsconfig/node20": "^20.1.2",
|
"@tsconfig/node20": "^20.1.2",
|
||||||
|
"@types/jquery": "^3.5.33",
|
||||||
"@types/node": "^20.11.10",
|
"@types/node": "^20.11.10",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
"npm-run-all2": "^6.1.1",
|
"npm-run-all2": "^6.1.1",
|
||||||
"sass-embedded": "^1.92.0",
|
"sass-embedded": "^1.92.0",
|
||||||
"typescript": "~5.3.0",
|
"typescript": "~5.3.0",
|
||||||
"typescript-eslint": "^8.42.0",
|
"typescript-eslint": "^8.43.0",
|
||||||
"vite": "^7.1.4",
|
"vite": "^7.1.4",
|
||||||
"vue-tsc": "^2.0.29"
|
"vue-tsc": "^2.0.29"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,52 @@
|
|||||||
<!--
|
|
||||||
* libreevent - App.vue
|
|
||||||
*
|
|
||||||
* Created by Janis Hutz 05/14/2023, Licensed under the GPL V3 License
|
|
||||||
* https://janishutz.com, development@janishutz.com
|
|
||||||
*
|
|
||||||
*
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<button @click="changeTheme();" id="themeSelector" title="Toggle between light and dark mode"><span class="material-symbols-outlined" v-html="theme"></span></button>
|
<button id="themeSelector" title="Toggle between light and dark mode" @click="changeTheme();">
|
||||||
<router-view v-slot="{ Component, route }" id="main-view">
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<span class="material-symbols-outlined" v-html="theme"></span>
|
||||||
|
</button>
|
||||||
|
<router-view id="main-view" v-slot="{ Component, route }">
|
||||||
<transition :name="route.meta.transition ? String( route.meta.transition ) : 'fade'" mode="out-in">
|
<transition :name="route.meta.transition ? String( route.meta.transition ) : 'fade'" mode="out-in">
|
||||||
<component :is="Component"></component>
|
<component :is="Component" />
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
RouterView
|
||||||
|
} from 'vue-router';
|
||||||
|
|
||||||
|
const theme = ref( 'light_mode' );
|
||||||
|
|
||||||
|
const changeTheme = () => {
|
||||||
|
if ( theme.value === 'dark_mode' ) {
|
||||||
|
document.documentElement.classList.remove( 'dark' );
|
||||||
|
document.documentElement.classList.add( 'light' );
|
||||||
|
localStorage.setItem( 'theme', 'light_mode' );
|
||||||
|
theme.value = 'light_mode';
|
||||||
|
} else if ( theme.value === 'light_mode' ) {
|
||||||
|
document.documentElement.classList.remove( 'light' );
|
||||||
|
document.documentElement.classList.add( 'dark' );
|
||||||
|
localStorage.setItem( 'theme', 'dark_mode' );
|
||||||
|
theme.value = 'dark_mode';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
theme.value = localStorage.getItem( 'theme' ) ?? '';
|
||||||
|
|
||||||
|
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || theme.value === 'dark_mode' ) {
|
||||||
|
document.documentElement.classList.add( 'dark' );
|
||||||
|
theme.value = 'dark_mode';
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add( 'light' );
|
||||||
|
theme.value = 'light_mode';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: var( --background-color );
|
background-color: var( --background-color );
|
||||||
@@ -178,33 +207,3 @@
|
|||||||
background-position: 0px;
|
background-position: 0px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { RouterView } from 'vue-router';
|
|
||||||
|
|
||||||
const theme = ref( 'light_mode' );
|
|
||||||
|
|
||||||
const changeTheme = () => {
|
|
||||||
if ( theme.value === 'dark_mode' ) {
|
|
||||||
document.documentElement.classList.remove( 'dark' );
|
|
||||||
document.documentElement.classList.add( 'light' );
|
|
||||||
localStorage.setItem( 'theme', 'light_mode' );
|
|
||||||
theme.value = 'light_mode';
|
|
||||||
} else if ( theme.value === 'light_mode' ) {
|
|
||||||
document.documentElement.classList.remove( 'light' );
|
|
||||||
document.documentElement.classList.add( 'dark' );
|
|
||||||
localStorage.setItem( 'theme', 'dark_mode' );
|
|
||||||
theme.value = 'dark_mode';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
theme.value = localStorage.getItem( 'theme' ) ?? '';
|
|
||||||
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || theme.value === 'dark_mode' ) {
|
|
||||||
document.documentElement.classList.add( 'dark' );
|
|
||||||
theme.value = 'dark_mode';
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add( 'light' );
|
|
||||||
theme.value = 'light_mode';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import { createApp } from 'vue'
|
import {
|
||||||
import { createPinia } from 'pinia'
|
createApp
|
||||||
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
createPinia
|
||||||
|
} from 'pinia';
|
||||||
|
import App from './App.vue';
|
||||||
|
import router from './router';
|
||||||
|
|
||||||
import App from './App.vue'
|
const app = createApp( App );
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
app.use( createPinia() );
|
||||||
|
app.use( router );
|
||||||
app.use(createPinia())
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
// localStorage.setItem( 'url', 'http://localhost:8082' );
|
// localStorage.setItem( 'url', 'http://localhost:8082' );
|
||||||
localStorage.setItem( 'url', 'https://music-api.janishutz.com' );
|
localStorage.setItem( 'url', 'https://music-api.janishutz.com' );
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount( '#app' );
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import ColorThief from 'colorthief';
|
import ColorThief from 'colorthief';
|
||||||
|
|
||||||
const colorThief = new ColorThief();
|
const colorThief = new ColorThief();
|
||||||
|
|
||||||
const getImageData = (): Promise<number[][]> => {
|
const getImageData = (): Promise<number[][]> => {
|
||||||
return new Promise( ( resolve ) => {
|
return new Promise( resolve => {
|
||||||
const img = ( document.getElementById( 'current-image' ) as HTMLImageElement );
|
const img = document.getElementById( 'current-image' ) as HTMLImageElement;
|
||||||
|
|
||||||
if ( img.complete ) {
|
if ( img.complete ) {
|
||||||
resolve( colorThief.getPalette( img ) );
|
resolve( colorThief.getPalette( img ) );
|
||||||
} else {
|
} else {
|
||||||
@@ -12,32 +14,39 @@ const getImageData = (): Promise<number[][]> => {
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
};
|
||||||
|
|
||||||
const createBackground = () => {
|
const createBackground = () => {
|
||||||
return new Promise( ( resolve ) => {
|
return new Promise( resolve => {
|
||||||
getImageData().then( palette => {
|
getImageData().then( palette => {
|
||||||
const colourDetails: number[][] = [];
|
const colourDetails: number[][] = [];
|
||||||
const colours: string[] = [];
|
const colours: string[] = [];
|
||||||
|
|
||||||
let differentEnough = true;
|
let differentEnough = true;
|
||||||
|
|
||||||
if ( palette[ 0 ] ) {
|
if ( palette[ 0 ] ) {
|
||||||
for ( const i in palette ) {
|
for ( const i in palette ) {
|
||||||
for ( const colour in colourDetails ) {
|
for ( const colour in colourDetails ) {
|
||||||
const colourDiff = ( Math.abs( colourDetails[ colour ][ 0 ] - palette[ i ][ 0 ] ) / 255
|
const colourDiff = ( Math.abs( colourDetails[ colour ][ 0 ] - palette[ i ][ 0 ] ) / 255
|
||||||
+ Math.abs( colourDetails[ colour ][ 1 ] - palette[ i ][ 1 ] ) / 255
|
+ Math.abs( colourDetails[ colour ][ 1 ] - palette[ i ][ 1 ] ) / 255
|
||||||
+ Math.abs( colourDetails[ colour ][ 2 ] - palette[ i ][ 2 ] ) / 255 ) / 3 * 100;
|
+ Math.abs( colourDetails[ colour ][ 2 ] - palette[ i ][ 2 ] ) / 255 ) / 3 * 100;
|
||||||
|
|
||||||
if ( colourDiff > 15 ) {
|
if ( colourDiff > 15 ) {
|
||||||
differentEnough = true;
|
differentEnough = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( differentEnough ) {
|
if ( differentEnough ) {
|
||||||
colourDetails.push( palette[ i ] );
|
colourDetails.push( palette[ i ] );
|
||||||
colours.push( 'rgb(' + palette[ i ][ 0 ] + ',' + palette[ i ][ 1 ] + ',' + palette[ i ][ 2 ] + ')' );
|
colours.push( 'rgb(' + palette[ i ][ 0 ] + ',' + palette[ i ][ 1 ] + ',' + palette[ i ][ 2 ] + ')' );
|
||||||
}
|
}
|
||||||
|
|
||||||
differentEnough = false;
|
differentEnough = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let outColours = 'conic-gradient(';
|
let outColours = 'conic-gradient(';
|
||||||
|
|
||||||
if ( colours.length < 3 ) {
|
if ( colours.length < 3 ) {
|
||||||
for ( let i = 0; i < 3; i++ ) {
|
for ( let i = 0; i < 3; i++ ) {
|
||||||
if ( colours[ i ] ) {
|
if ( colours[ i ] ) {
|
||||||
@@ -61,45 +70,56 @@ const createBackground = () => {
|
|||||||
outColours += colours[ i ] + ',';
|
outColours += colours[ i ] + ',';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outColours += colours[ 0 ] ?? 'blue' + ')';
|
outColours += colours[ 0 ] ?? 'blue' + ')';
|
||||||
resolve( outColours );
|
resolve( outColours );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let callbackFun = () => {};
|
||||||
|
|
||||||
let callbackFun = () => {}
|
|
||||||
const subscribeToBeatUpdate = ( cb: () => void ) => {
|
const subscribeToBeatUpdate = ( cb: () => void ) => {
|
||||||
callbackFun = cb;
|
callbackFun = cb;
|
||||||
micAudioHandler();
|
micAudioHandler();
|
||||||
}
|
};
|
||||||
|
|
||||||
const unsubscribeFromBeatUpdate = () => {
|
const unsubscribeFromBeatUpdate = () => {
|
||||||
callbackFun = () => {}
|
callbackFun = () => {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clearInterval( micAnalyzer );
|
clearInterval( micAnalyzer );
|
||||||
} catch ( e ) { /* empty */ }
|
} catch ( e ) { /* empty */ }
|
||||||
}
|
};
|
||||||
|
|
||||||
const coolDown = () => {
|
const coolDown = () => {
|
||||||
beatDetected = false;
|
beatDetected = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
let micAnalyzer = 0;
|
let micAnalyzer = 0;
|
||||||
let beatDetected = false;
|
let beatDetected = false;
|
||||||
|
|
||||||
const micAudioHandler = () => {
|
const micAudioHandler = () => {
|
||||||
const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
|
const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
|
||||||
const analyser = audioContext.createAnalyser();
|
const analyser = audioContext.createAnalyser();
|
||||||
|
|
||||||
analyser.fftSize = 256;
|
analyser.fftSize = 256;
|
||||||
const bufferLength = analyser.frequencyBinCount;
|
const bufferLength = analyser.frequencyBinCount;
|
||||||
const dataArray = new Uint8Array( bufferLength );
|
const dataArray = new Uint8Array( bufferLength );
|
||||||
|
|
||||||
beatDetected = false;
|
beatDetected = false;
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia( { audio: true } ).then( ( stream ) => {
|
navigator.mediaDevices.getUserMedia( {
|
||||||
|
'audio': true
|
||||||
|
} ).then( stream => {
|
||||||
const mic = audioContext.createMediaStreamSource( stream );
|
const mic = audioContext.createMediaStreamSource( stream );
|
||||||
|
|
||||||
mic.connect( analyser );
|
mic.connect( analyser );
|
||||||
analyser.getByteFrequencyData( dataArray );
|
analyser.getByteFrequencyData( dataArray );
|
||||||
let prevSpectrum: number[] = [];
|
let prevSpectrum: number[] = [];
|
||||||
|
|
||||||
const threshold = 10; // Adjust as needed
|
const threshold = 10; // Adjust as needed
|
||||||
|
|
||||||
micAnalyzer = setInterval( () => {
|
micAnalyzer = setInterval( () => {
|
||||||
analyser.getByteFrequencyData( dataArray );
|
analyser.getByteFrequencyData( dataArray );
|
||||||
// Convert the frequency data to a numeric array
|
// Convert the frequency data to a numeric array
|
||||||
@@ -115,25 +135,27 @@ const micAudioHandler = () => {
|
|||||||
callbackFun();
|
callbackFun();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prevSpectrum = currentSpectrum;
|
prevSpectrum = currentSpectrum;
|
||||||
}, 60 / 180 * 250 );
|
}, 60 / 180 * 250 );
|
||||||
} );
|
} );
|
||||||
}
|
};
|
||||||
|
|
||||||
const calculateSpectralFlux = ( prevSpectrum: number[], currentSpectrum: number[] ) => {
|
const calculateSpectralFlux = ( prevSpectrum: number[], currentSpectrum: number[] ) => {
|
||||||
let flux = 0;
|
let flux = 0;
|
||||||
|
|
||||||
for ( let i = 0; i < prevSpectrum.length; i++ ) {
|
for ( let i = 0; i < prevSpectrum.length; i++ ) {
|
||||||
const diff = currentSpectrum[ i ] - prevSpectrum[ i ];
|
const diff = currentSpectrum[ i ] - prevSpectrum[ i ];
|
||||||
|
|
||||||
flux += Math.max( 0, diff );
|
flux += Math.max( 0, diff );
|
||||||
}
|
}
|
||||||
|
|
||||||
return flux;
|
return flux;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createBackground,
|
createBackground,
|
||||||
subscribeToBeatUpdate,
|
subscribeToBeatUpdate,
|
||||||
unsubscribeFromBeatUpdate,
|
unsubscribeFromBeatUpdate,
|
||||||
coolDown,
|
coolDown,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -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
|
// These functions handle connections to the backend with socket.io
|
||||||
|
|
||||||
import { io, type Socket } from "socket.io-client";
|
import {
|
||||||
import type { SSEMap } from "./song";
|
io, type Socket
|
||||||
|
} from 'socket.io-client';
|
||||||
|
import type {
|
||||||
|
SSEMap
|
||||||
|
} from './song';
|
||||||
|
|
||||||
class SocketConnection {
|
class SocketConnection {
|
||||||
|
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
|
|
||||||
roomName: string;
|
roomName: string;
|
||||||
|
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
|
||||||
useSocket: boolean;
|
useSocket: boolean;
|
||||||
|
|
||||||
eventSource?: EventSource;
|
eventSource?: EventSource;
|
||||||
|
|
||||||
toBeListenedForItems: SSEMap;
|
toBeListenedForItems: SSEMap;
|
||||||
|
|
||||||
reconnectRetryCount: number;
|
reconnectRetryCount: number;
|
||||||
|
|
||||||
openConnectionsCount: number;
|
openConnectionsCount: number;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
|
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
|
||||||
autoConnect: false,
|
'autoConnect': false,
|
||||||
} );
|
} );
|
||||||
this.roomName = location.pathname.split( '/' )[ 2 ];
|
this.roomName = location.pathname.split( '/' )[ 2 ];
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
@@ -38,48 +41,64 @@ class SocketConnection {
|
|||||||
* Create a room token and connect to
|
* Create a room token and connect to
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
connect (): Promise<any> {
|
connect (): Promise<unknown> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
if ( this.reconnectRetryCount < 5 ) {
|
if ( this.reconnectRetryCount < 5 ) {
|
||||||
if ( this.useSocket ) {
|
if ( this.useSocket ) {
|
||||||
this.socket.connect();
|
this.socket.connect();
|
||||||
this.socket.emit( 'join-room', this.roomName, ( res: { status: boolean, msg: string, data: any } ) => {
|
this.socket.emit(
|
||||||
if ( res.status === true ) {
|
'join-room', this.roomName, ( res: {
|
||||||
this.isConnected = true;
|
'status': boolean,
|
||||||
resolve( res.data );
|
'msg': string,
|
||||||
} else {
|
'data': unknown
|
||||||
console.debug( res.msg );
|
} ) => {
|
||||||
reject( 'ERR_ROOM_CONNECTING' );
|
if ( res.status === true ) {
|
||||||
|
this.isConnected = true;
|
||||||
|
resolve( res.data );
|
||||||
|
} else {
|
||||||
|
console.debug( res.msg );
|
||||||
|
reject( 'ERR_ROOM_CONNECTING' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} );
|
);
|
||||||
} else {
|
} else {
|
||||||
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
|
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
|
||||||
this.openConnectionsCount += 1;
|
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 ) {
|
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.eventSource.onopen = () => {
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
this.reconnectRetryCount = 0;
|
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 );
|
const d = JSON.parse( e.data );
|
||||||
|
|
||||||
if ( this.toBeListenedForItems[ d.type ] ) {
|
if ( this.toBeListenedForItems[ d.type ] ) {
|
||||||
this.toBeListenedForItems[ d.type ]( d.data );
|
this.toBeListenedForItems[ d.type ]( d.data );
|
||||||
} else if ( d.type === 'basics' ) {
|
} else if ( d.type === 'basics' ) {
|
||||||
resolve( d.data );
|
resolve( d.data );
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
this.eventSource.onerror = () => {
|
this.eventSource.onerror = () => {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.openConnectionsCount -= 1;
|
this.openConnectionsCount -= 1;
|
||||||
this.eventSource?.close();
|
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 );
|
// console.debug( e );
|
||||||
|
|
||||||
this.eventSource = undefined;
|
this.eventSource = undefined;
|
||||||
@@ -91,13 +110,18 @@ class SocketConnection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} 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' );
|
reject( 'ERR_ROOM_CONNECTING' );
|
||||||
}
|
}
|
||||||
} ).catch( () => {
|
} )
|
||||||
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Could not connect due to error.' );
|
.catch( () => {
|
||||||
reject( 'ERR_ROOM_CONNECTING' );
|
console.log( '[ SSE Connection ] - '
|
||||||
} );
|
+ new Date().toISOString()
|
||||||
|
+ ': Could not connect due to error.' );
|
||||||
|
reject( 'ERR_ROOM_CONNECTING' );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
console.log( '[ SSE Connection ]: Trimmed connections' );
|
console.log( '[ SSE Connection ]: Trimmed connections' );
|
||||||
reject( 'ERR_TOO_MANY_CONNECTIONS' );
|
reject( 'ERR_TOO_MANY_CONNECTIONS' );
|
||||||
@@ -116,16 +140,23 @@ class SocketConnection {
|
|||||||
* @param {any} data
|
* @param {any} data
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
emit ( event: string, data: any ): void {
|
emit ( event: string, data: unknown ): void {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
if ( this.useSocket ) {
|
if ( this.useSocket ) {
|
||||||
this.socket.emit( event, { 'roomName': this.roomName, 'data': data } );
|
this.socket.emit( event, {
|
||||||
|
'roomName': this.roomName,
|
||||||
|
'data': data
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
fetch( localStorage.getItem( 'url' ) + '/socket/update', {
|
fetch( localStorage.getItem( 'url' ) + '/socket/update', {
|
||||||
method: 'post',
|
'method': 'post',
|
||||||
body: JSON.stringify( { 'event': event, 'roomName': this.roomName, 'data': data } ),
|
'body': JSON.stringify( {
|
||||||
credentials: 'include',
|
'event': event,
|
||||||
headers: {
|
'roomName': this.roomName,
|
||||||
|
'data': data
|
||||||
|
} ),
|
||||||
|
'credentials': 'include',
|
||||||
|
'headers': {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'charset': 'utf-8'
|
'charset': 'utf-8'
|
||||||
}
|
}
|
||||||
@@ -140,7 +171,7 @@ class SocketConnection {
|
|||||||
* @param {( data: any ) => void} cb The callback function / listener function
|
* @param {( data: any ) => void} cb The callback function / listener function
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
registerListener ( event: string, cb: ( data: any ) => void ): void {
|
registerListener ( event: string, cb: ( data: unknown ) => void ): void {
|
||||||
if ( this.useSocket ) {
|
if ( this.useSocket ) {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
this.socket.on( event, cb );
|
this.socket.on( event, cb );
|
||||||
@@ -171,9 +202,11 @@ class SocketConnection {
|
|||||||
if ( this.eventSource ) {
|
if ( this.eventSource ) {
|
||||||
return this.eventSource!.OPEN && this.isConnected;
|
return this.eventSource!.OPEN && this.isConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SocketConnection;
|
export default SocketConnection;
|
||||||
@@ -1,25 +1,39 @@
|
|||||||
import type { SearchResult, Song, SongMove } from "./song";
|
import type {
|
||||||
|
SearchResult, Song, SongMove
|
||||||
|
} from './song';
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
devToken: string;
|
'devToken': string;
|
||||||
userToken: string;
|
'userToken': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControlAction = 'play' | 'pause' | 'next' | 'previous' | 'skip-10' | 'back-10';
|
type ControlAction = 'play' | 'pause' | 'next' | 'previous' | 'skip-10' | 'back-10';
|
||||||
type RepeatMode = 'off' | 'once' | 'all';
|
type RepeatMode = 'off' | 'once' | 'all';
|
||||||
|
|
||||||
class MusicKitJSWrapper {
|
class MusicKitJSWrapper {
|
||||||
|
|
||||||
playingSongID: number;
|
playingSongID: number;
|
||||||
|
|
||||||
playlist: Song[];
|
playlist: Song[];
|
||||||
|
|
||||||
queue: number[];
|
queue: number[];
|
||||||
|
|
||||||
config: Config;
|
config: Config;
|
||||||
|
|
||||||
musicKit: any;
|
musicKit: any;
|
||||||
|
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
|
|
||||||
isPreparedToPlay: boolean;
|
isPreparedToPlay: boolean;
|
||||||
|
|
||||||
repeatMode: RepeatMode;
|
repeatMode: RepeatMode;
|
||||||
|
|
||||||
isShuffleEnabled: boolean;
|
isShuffleEnabled: boolean;
|
||||||
|
|
||||||
hasEncounteredAuthError: boolean;
|
hasEncounteredAuthError: boolean;
|
||||||
|
|
||||||
queuePos: number;
|
queuePos: number;
|
||||||
|
|
||||||
audioPlayer: HTMLAudioElement;
|
audioPlayer: HTMLAudioElement;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -27,8 +41,8 @@ class MusicKitJSWrapper {
|
|||||||
this.playlist = [];
|
this.playlist = [];
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
this.config = {
|
this.config = {
|
||||||
devToken: '',
|
'devToken': '',
|
||||||
userToken: '',
|
'userToken': '',
|
||||||
};
|
};
|
||||||
this.isShuffleEnabled = false;
|
this.isShuffleEnabled = false;
|
||||||
this.repeatMode = 'off';
|
this.repeatMode = 'off';
|
||||||
@@ -58,16 +72,18 @@ class MusicKitJSWrapper {
|
|||||||
this.musicKit.authorize().then( () => {
|
this.musicKit.authorize().then( () => {
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
this.init();
|
this.init();
|
||||||
} ).catch( () => {
|
} )
|
||||||
this.hasEncounteredAuthError = true;
|
.catch( () => {
|
||||||
} );
|
this.hasEncounteredAuthError = true;
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
this.musicKit.authorize().then( () => {
|
this.musicKit.authorize().then( () => {
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
this.init();
|
this.init();
|
||||||
} ).catch( () => {
|
} )
|
||||||
this.hasEncounteredAuthError = true;
|
.catch( () => {
|
||||||
} );
|
this.hasEncounteredAuthError = true;
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,25 +92,29 @@ class MusicKitJSWrapper {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
init (): 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 ) {
|
if ( res.status === 200 ) {
|
||||||
res.text().then( token => {
|
res.text().then( token => {
|
||||||
this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
|
this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
|
||||||
// MusicKit global is now defined
|
// MusicKit global is now defined
|
||||||
MusicKit.configure( {
|
MusicKit.configure( {
|
||||||
developerToken: token,
|
'developerToken': token,
|
||||||
app: {
|
'app': {
|
||||||
name: 'MusicPlayer',
|
'name': 'MusicPlayer',
|
||||||
build: '3'
|
'build': '3'
|
||||||
},
|
},
|
||||||
storefrontId: 'CH',
|
'storefrontId': 'CH',
|
||||||
} ).then( () => {
|
} ).then( () => {
|
||||||
this.config.devToken = token;
|
this.config.devToken = token;
|
||||||
this.musicKit = MusicKit.getInstance();
|
this.musicKit = MusicKit.getInstance();
|
||||||
|
|
||||||
if ( this.musicKit.isAuthorized ) {
|
if ( this.musicKit.isAuthorized ) {
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
this.config.userToken = this.musicKit.musicUserToken;
|
this.config.userToken = this.musicKit.musicUserToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.musicKit.shuffleMode = MusicKit.PlayerShuffleMode.off;
|
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
|
* @returns {boolean[]} Returns an array, where the first element indicates login status, the second one, if an error was encountered
|
||||||
*/
|
*/
|
||||||
getAuth (): boolean[] {
|
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 {
|
apiGetRequest ( url: string, callback: ( data: object ) => void ): void {
|
||||||
if ( this.config.devToken != '' && this.config.userToken != '' ) {
|
if ( this.config.devToken != '' && this.config.userToken != '' ) {
|
||||||
fetch( url, {
|
fetch( url, {
|
||||||
method: 'GET',
|
'method': 'GET',
|
||||||
headers: {
|
'headers': {
|
||||||
'Authorization': `Bearer ${ this.config.devToken }`,
|
'Authorization': `Bearer ${ this.config.devToken }`,
|
||||||
'Music-User-Token': this.config.userToken
|
'Music-User-Token': this.config.userToken
|
||||||
}
|
}
|
||||||
@@ -128,13 +151,19 @@ class MusicKitJSWrapper {
|
|||||||
if ( res.status === 200 ) {
|
if ( res.status === 200 ) {
|
||||||
res.json().then( json => {
|
res.json().then( json => {
|
||||||
try {
|
try {
|
||||||
callback( { 'status': 'ok', 'data': json } );
|
callback( {
|
||||||
} catch( err ) { /* empty */}
|
'status': 'ok',
|
||||||
|
'data': json
|
||||||
|
} );
|
||||||
|
} catch ( err ) { /* empty */ }
|
||||||
} );
|
} );
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
callback( { 'status': 'error', 'error': res.status } );
|
callback( {
|
||||||
} catch( err ) { /* empty */}
|
'status': 'error',
|
||||||
|
'error': res.status
|
||||||
|
} );
|
||||||
|
} catch ( err ) { /* empty */ }
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
} else return;
|
} else return;
|
||||||
@@ -152,34 +181,41 @@ class MusicKitJSWrapper {
|
|||||||
|
|
||||||
setPlaylistByID ( id: string ): Promise<void> {
|
setPlaylistByID ( id: string ): Promise<void> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
this.musicKit.setQueue( { playlist: id } ).then( () => {
|
this.musicKit.setQueue( {
|
||||||
|
'playlist': id
|
||||||
|
} ).then( () => {
|
||||||
const pl = this.musicKit.queue.items;
|
const pl = this.musicKit.queue.items;
|
||||||
const songs: Song[] = [];
|
const songs: Song[] = [];
|
||||||
|
|
||||||
for ( const item in pl ) {
|
for ( const item in pl ) {
|
||||||
let url = pl[ item ].attributes.artwork.url;
|
let url = pl[ item ].attributes.artwork.url;
|
||||||
|
|
||||||
url = url.replace( '{w}', pl[ item ].attributes.artwork.width );
|
url = url.replace( '{w}', pl[ item ].attributes.artwork.width );
|
||||||
url = url.replace( '{h}', pl[ item ].attributes.artwork.height );
|
url = url.replace( '{h}', pl[ item ].attributes.artwork.height );
|
||||||
const song: Song = {
|
const song: Song = {
|
||||||
artist: pl[ item ].attributes.artistName,
|
'artist': pl[ item ].attributes.artistName,
|
||||||
cover: url,
|
'cover': url,
|
||||||
duration: pl[ item ].attributes.durationInMillis / 1000,
|
'duration': pl[ item ].attributes.durationInMillis / 1000,
|
||||||
id: pl[ item ].id,
|
'id': pl[ item ].id,
|
||||||
origin: 'apple-music',
|
'origin': 'apple-music',
|
||||||
title: pl[ item ].attributes.name,
|
'title': pl[ item ].attributes.name,
|
||||||
genres: pl[ item ].attributes.genreNames
|
'genres': pl[ item ].attributes.genreNames
|
||||||
}
|
};
|
||||||
|
|
||||||
songs.push( song );
|
songs.push( song );
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playlist = songs;
|
this.playlist = songs;
|
||||||
this.setShuffle( this.isShuffleEnabled );
|
this.setShuffle( this.isShuffleEnabled );
|
||||||
this.queuePos = 0;
|
this.queuePos = 0;
|
||||||
this.playingSongID = this.queue[ 0 ];
|
this.playingSongID = this.queue[ 0 ];
|
||||||
this.prepare( this.playingSongID );
|
this.prepare( this.playingSongID );
|
||||||
resolve();
|
resolve();
|
||||||
} ).catch( err => {
|
} )
|
||||||
console.error( err );
|
.catch( err => {
|
||||||
reject( err );
|
console.error( err );
|
||||||
} );
|
reject( err );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,20 +228,25 @@ class MusicKitJSWrapper {
|
|||||||
if ( this.playlist.length > 0 ) {
|
if ( this.playlist.length > 0 ) {
|
||||||
this.playingSongID = playlistID;
|
this.playingSongID = playlistID;
|
||||||
this.isPreparedToPlay = true;
|
this.isPreparedToPlay = true;
|
||||||
|
|
||||||
for ( const el in this.queue ) {
|
for ( const el in this.queue ) {
|
||||||
if ( this.queue[ el ] === playlistID ) {
|
if ( this.queue[ el ] === playlistID ) {
|
||||||
this.queuePos = parseInt( el );
|
this.queuePos = parseInt( el );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
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( () => {
|
setTimeout( () => {
|
||||||
this.control( 'play' );
|
this.control( 'play' );
|
||||||
}, 500 );
|
}, 500 );
|
||||||
} ).catch( ( err ) => {
|
} )
|
||||||
console.log( err );
|
.catch( err => {
|
||||||
} );
|
console.log( err );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
|
this.audioPlayer = document.getElementById( 'local-audio' ) as HTMLAudioElement;
|
||||||
this.audioPlayer.src = this.playlist[ this.playingSongID ].id;
|
this.audioPlayer.src = this.playlist[ this.playingSongID ].id;
|
||||||
@@ -213,6 +254,7 @@ class MusicKitJSWrapper {
|
|||||||
this.control( 'play' );
|
this.control( 'play' );
|
||||||
}, 500 );
|
}, 500 );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -226,50 +268,63 @@ class MusicKitJSWrapper {
|
|||||||
*/
|
*/
|
||||||
control ( action: ControlAction ): boolean {
|
control ( action: ControlAction ): boolean {
|
||||||
switch ( action ) {
|
switch ( action ) {
|
||||||
case "play":
|
case 'play':
|
||||||
if ( this.isPreparedToPlay ) {
|
if ( this.isPreparedToPlay ) {
|
||||||
this.control( 'pause' );
|
this.control( 'pause' );
|
||||||
|
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.play();
|
this.musicKit.play();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
this.audioPlayer.play();
|
this.audioPlayer.play();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case "pause":
|
|
||||||
|
case 'pause':
|
||||||
if ( this.isPreparedToPlay ) {
|
if ( this.isPreparedToPlay ) {
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.pause();
|
this.musicKit.pause();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
this.audioPlayer.pause();
|
this.audioPlayer.pause();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case "back-10":
|
|
||||||
|
case 'back-10':
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? this.musicKit.currentPlaybackTime - 10 : 0 );
|
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? this.musicKit.currentPlaybackTime - 10 : 0 );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
this.audioPlayer.currentTime = this.audioPlayer.currentTime > 10 ? this.audioPlayer.currentTime - 10 : 0;
|
this.audioPlayer.currentTime = this.audioPlayer.currentTime > 10 ? this.audioPlayer.currentTime - 10 : 0;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case "skip-10":
|
|
||||||
|
case 'skip-10':
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
if ( this.musicKit.currentPlaybackTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
|
if ( this.musicKit.currentPlaybackTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
|
||||||
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime + 10 );
|
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime + 10 );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if ( this.repeatMode !== 'once' ) {
|
if ( this.repeatMode !== 'once' ) {
|
||||||
this.control( 'next' );
|
this.control( 'next' );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.musicKit.seekToTime( 0 );
|
this.musicKit.seekToTime( 0 );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,32 +338,42 @@ class MusicKitJSWrapper {
|
|||||||
this.audioPlayer.currentTime = 0;
|
this.audioPlayer.currentTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case "next":
|
|
||||||
|
case 'next':
|
||||||
this.control( 'pause' );
|
this.control( 'pause' );
|
||||||
|
|
||||||
if ( this.queuePos < this.queue.length - 1 ) {
|
if ( this.queuePos < this.queue.length - 1 ) {
|
||||||
this.queuePos += 1;
|
this.queuePos += 1;
|
||||||
this.prepare( this.queue[ this.queuePos ] );
|
this.prepare( this.queue[ this.queuePos ] );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.queuePos = 0;
|
this.queuePos = 0;
|
||||||
|
|
||||||
if ( this.repeatMode !== 'all' ) {
|
if ( this.repeatMode !== 'all' ) {
|
||||||
this.control( 'pause' );
|
this.control( 'pause' );
|
||||||
} else {
|
} else {
|
||||||
this.playingSongID = this.queue[ this.queuePos ];
|
this.playingSongID = this.queue[ this.queuePos ];
|
||||||
this.prepare( this.queue[ this.queuePos ] );
|
this.prepare( this.queue[ this.queuePos ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case "previous":
|
|
||||||
|
case 'previous':
|
||||||
this.control( 'pause' );
|
this.control( 'pause' );
|
||||||
|
|
||||||
if ( this.queuePos > 0 ) {
|
if ( this.queuePos > 0 ) {
|
||||||
this.queuePos -= 1;
|
this.queuePos -= 1;
|
||||||
this.prepare( this.queue[ this.queuePos ] );
|
this.prepare( this.queue[ this.queuePos ] );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.queuePos = this.queue.length - 1;
|
this.queuePos = this.queue.length - 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,15 +382,22 @@ class MusicKitJSWrapper {
|
|||||||
setShuffle ( enabled: boolean ) {
|
setShuffle ( enabled: boolean ) {
|
||||||
this.isShuffleEnabled = enabled;
|
this.isShuffleEnabled = enabled;
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
|
|
||||||
if ( enabled ) {
|
if ( enabled ) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const d = [];
|
const d = [];
|
||||||
|
|
||||||
for ( const el in this.playlist ) {
|
for ( const el in this.playlist ) {
|
||||||
d.push( parseInt( el ) );
|
d.push( parseInt( el ) );
|
||||||
}
|
}
|
||||||
this.queue = d.map( value => ( { value, sort: Math.random() } ) )
|
|
||||||
.sort( ( a, b ) => a.sort - b.sort )
|
this.queue = d.map( value => ( {
|
||||||
.map( ( { value } ) => 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.splice( this.queue.indexOf( this.playingSongID ), 1 );
|
||||||
this.queue.push( this.playingSongID );
|
this.queue.push( this.playingSongID );
|
||||||
this.queue.reverse();
|
this.queue.reverse();
|
||||||
@@ -334,6 +406,7 @@ class MusicKitJSWrapper {
|
|||||||
this.queue.push( parseInt( song ) );
|
this.queue.push( parseInt( song ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find current song ID in queue
|
// Find current song ID in queue
|
||||||
for ( const el in this.queue ) {
|
for ( const el in this.queue ) {
|
||||||
if ( this.queue[ el ] === this.playingSongID ) {
|
if ( this.queue[ el ] === this.playingSongID ) {
|
||||||
@@ -359,29 +432,37 @@ class MusicKitJSWrapper {
|
|||||||
moveSong ( move: SongMove ) {
|
moveSong ( move: SongMove ) {
|
||||||
const newQueue = [];
|
const newQueue = [];
|
||||||
const finishedQueue = [];
|
const finishedQueue = [];
|
||||||
|
|
||||||
let songID = 0;
|
let songID = 0;
|
||||||
|
|
||||||
for ( const song in this.playlist ) {
|
for ( const song in this.playlist ) {
|
||||||
if ( this.playlist[ song ].id === move.songID ) {
|
if ( this.playlist[ song ].id === move.songID ) {
|
||||||
songID = parseInt( song );
|
songID = parseInt( song );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const el in this.queue ) {
|
for ( const el in this.queue ) {
|
||||||
if ( this.queue[ el ] !== songID ) {
|
if ( this.queue[ el ] !== songID ) {
|
||||||
newQueue.push( this.queue[ el ] );
|
newQueue.push( this.queue[ el ] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasBeenAdded = false;
|
let hasBeenAdded = false;
|
||||||
|
|
||||||
for ( const el in newQueue ) {
|
for ( const el in newQueue ) {
|
||||||
if ( parseInt( el ) === move.newPos ) {
|
if ( parseInt( el ) === move.newPos ) {
|
||||||
finishedQueue.push( songID );
|
finishedQueue.push( songID );
|
||||||
hasBeenAdded = true;
|
hasBeenAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedQueue.push( newQueue[ el ] );
|
finishedQueue.push( newQueue[ el ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !hasBeenAdded ) {
|
if ( !hasBeenAdded ) {
|
||||||
finishedQueue.push( songID );
|
finishedQueue.push( songID );
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue = finishedQueue;
|
this.queue = finishedQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,9 +516,11 @@ class MusicKitJSWrapper {
|
|||||||
*/
|
*/
|
||||||
getQueue (): Song[] {
|
getQueue (): Song[] {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
for ( const el in this.queue ) {
|
for ( const el in this.queue ) {
|
||||||
data.push( this.playlist[ this.queue[ el ] ] );
|
data.push( this.playlist[ this.queue[ el ] ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,13 +532,14 @@ class MusicKitJSWrapper {
|
|||||||
getUserPlaylists ( cb: ( data: object ) => void ): boolean {
|
getUserPlaylists ( cb: ( data: object ) => void ): boolean {
|
||||||
if ( this.isLoggedIn ) {
|
if ( this.isLoggedIn ) {
|
||||||
this.apiGetRequest( 'https://api.music.apple.com/v1/me/library/playlists', cb );
|
this.apiGetRequest( 'https://api.music.apple.com/v1/me/library/playlists', cb );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaying ( ): boolean {
|
getPlaying ( ): boolean {
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
return this.musicKit.isPlaying;
|
return this.musicKit.isPlaying;
|
||||||
} else {
|
} else {
|
||||||
@@ -467,17 +551,20 @@ class MusicKitJSWrapper {
|
|||||||
// TODO: Make storefront adjustable
|
// TODO: Make storefront adjustable
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
const queryParameters = {
|
const queryParameters = {
|
||||||
term: ( searchTerm ),
|
'term': searchTerm,
|
||||||
types: [ 'songs' ],
|
'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 );
|
resolve( results );
|
||||||
} ).catch( e => {
|
} )
|
||||||
console.error( e );
|
.catch( e => {
|
||||||
reject( e );
|
console.error( e );
|
||||||
} );
|
reject( e );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MusicKitJSWrapper;
|
export default MusicKitJSWrapper;
|
||||||
@@ -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
|
// These functions handle connections to the backend with socket.io
|
||||||
|
|
||||||
import { io, type Socket } from "socket.io-client"
|
import {
|
||||||
import type { SSEMap } from "./song";
|
io, type Socket
|
||||||
|
} from 'socket.io-client';
|
||||||
|
import type {
|
||||||
|
SSEMap
|
||||||
|
} from './song';
|
||||||
|
|
||||||
class NotificationHandler {
|
class NotificationHandler {
|
||||||
|
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
|
|
||||||
roomName: string;
|
roomName: string;
|
||||||
|
|
||||||
roomToken: string;
|
roomToken: string;
|
||||||
|
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
|
||||||
useSocket: boolean;
|
useSocket: boolean;
|
||||||
|
|
||||||
eventSource?: EventSource;
|
eventSource?: EventSource;
|
||||||
|
|
||||||
toBeListenedForItems: SSEMap;
|
toBeListenedForItems: SSEMap;
|
||||||
|
|
||||||
reconnectRetryCount: number;
|
reconnectRetryCount: number;
|
||||||
|
|
||||||
lastEmitTimestamp: number;
|
lastEmitTimestamp: number;
|
||||||
|
|
||||||
openConnectionsCount: number;
|
openConnectionsCount: number;
|
||||||
|
|
||||||
pendingRequestCount: number;
|
pendingRequestCount: number;
|
||||||
|
|
||||||
connectionWasSuccessful: boolean;
|
connectionWasSuccessful: boolean;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
|
this.socket = io( localStorage.getItem( 'url' ) ?? '', {
|
||||||
autoConnect: false,
|
'autoConnect': false,
|
||||||
} );
|
} );
|
||||||
this.roomName = '';
|
this.roomName = '';
|
||||||
this.roomToken = '';
|
this.roomToken = '';
|
||||||
@@ -50,28 +57,37 @@ class NotificationHandler {
|
|||||||
*/
|
*/
|
||||||
connect ( roomName: string, useAntiTamper: boolean ): Promise<void> {
|
connect ( roomName: string, useAntiTamper: boolean ): Promise<void> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
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 ) {
|
if ( res.status === 200 ) {
|
||||||
res.text().then( text => {
|
res.text().then( text => {
|
||||||
this.roomToken = text;
|
this.roomToken = text;
|
||||||
this.roomName = roomName;
|
this.roomName = roomName;
|
||||||
|
|
||||||
if ( this.useSocket ) {
|
if ( this.useSocket ) {
|
||||||
this.socket.connect();
|
this.socket.connect();
|
||||||
this.socket.emit( 'create-room', {
|
this.socket.emit(
|
||||||
name: this.roomName,
|
'create-room', {
|
||||||
token: this.roomToken
|
'name': this.roomName,
|
||||||
}, ( res: { status: boolean, msg: string } ) => {
|
'token': this.roomToken
|
||||||
if ( res.status === true ) {
|
}, ( res: {
|
||||||
this.isConnected = true;
|
'status': boolean,
|
||||||
resolve();
|
'msg': string
|
||||||
} else {
|
} ) => {
|
||||||
reject( 'ERR_ROOM_CONNECTING' );
|
if ( res.status === true ) {
|
||||||
|
this.isConnected = true;
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject( 'ERR_ROOM_CONNECTING' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} );
|
);
|
||||||
} else {
|
} else {
|
||||||
this.sseConnect().then( () => {
|
this.sseConnect().then( () => {
|
||||||
resolve();
|
resolve();
|
||||||
} ).catch( );
|
} )
|
||||||
|
.catch( );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
} else if ( res.status === 409 ) {
|
} else if ( res.status === 409 ) {
|
||||||
@@ -90,9 +106,13 @@ class NotificationHandler {
|
|||||||
if ( this.reconnectRetryCount < 5 ) {
|
if ( this.reconnectRetryCount < 5 ) {
|
||||||
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
|
if ( this.openConnectionsCount < 1 && !this.isConnected ) {
|
||||||
this.openConnectionsCount += 1;
|
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 ) {
|
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.eventSource.onopen = () => {
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
@@ -100,22 +120,23 @@ class NotificationHandler {
|
|||||||
this.reconnectRetryCount = 0;
|
this.reconnectRetryCount = 0;
|
||||||
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' );
|
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' );
|
||||||
resolve();
|
resolve();
|
||||||
}
|
};
|
||||||
|
|
||||||
this.eventSource.onmessage = ( e ) => {
|
this.eventSource.onmessage = e => {
|
||||||
const d = JSON.parse( e.data );
|
const d = JSON.parse( e.data );
|
||||||
|
|
||||||
if ( this.toBeListenedForItems[ d.type ] ) {
|
if ( this.toBeListenedForItems[ d.type ] ) {
|
||||||
this.toBeListenedForItems[ d.type ]( d.data );
|
this.toBeListenedForItems[ d.type ]( d.data );
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
this.eventSource.onerror = ( e ) => {
|
this.eventSource.onerror = e => {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.eventSource?.close();
|
this.eventSource?.close();
|
||||||
this.openConnectionsCount -= 1;
|
this.openConnectionsCount -= 1;
|
||||||
console.debug( e );
|
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.eventSource = undefined;
|
||||||
|
|
||||||
@@ -131,21 +152,22 @@ class NotificationHandler {
|
|||||||
} else {
|
} else {
|
||||||
reject( 'ERR_ROOM_CONNECTING_STATUS_CODE' );
|
reject( 'ERR_ROOM_CONNECTING_STATUS_CODE' );
|
||||||
}
|
}
|
||||||
} ).catch( () => {
|
} )
|
||||||
if ( !this.connectionWasSuccessful ) {
|
.catch( () => {
|
||||||
reject( 'ERR_ROOM_CONNECTING' );
|
if ( !this.connectionWasSuccessful ) {
|
||||||
} else {
|
reject( 'ERR_ROOM_CONNECTING' );
|
||||||
this.openConnectionsCount -= 1;
|
} else {
|
||||||
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to severe connection error!' );
|
this.openConnectionsCount -= 1;
|
||||||
|
console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to severe connection error!' );
|
||||||
|
|
||||||
this.eventSource = undefined;
|
this.eventSource = undefined;
|
||||||
|
|
||||||
this.reconnectRetryCount += 1;
|
this.reconnectRetryCount += 1;
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
this.sseConnect();
|
this.sseConnect();
|
||||||
}, 1000 * this.reconnectRetryCount );
|
}, 1000 * this.reconnectRetryCount );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -169,9 +191,14 @@ class NotificationHandler {
|
|||||||
emit ( event: string, data: any ): void {
|
emit ( event: string, data: any ): void {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
if ( this.useSocket ) {
|
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 {
|
} else {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
if ( this.lastEmitTimestamp < now - 250 ) {
|
if ( this.lastEmitTimestamp < now - 250 ) {
|
||||||
this.lastEmitTimestamp = now;
|
this.lastEmitTimestamp = now;
|
||||||
this.sendEmitConventionally( event, data );
|
this.sendEmitConventionally( event, data );
|
||||||
@@ -189,10 +216,15 @@ class NotificationHandler {
|
|||||||
|
|
||||||
sendEmitConventionally ( event: string, data: any ): void {
|
sendEmitConventionally ( event: string, data: any ): void {
|
||||||
fetch( localStorage.getItem( 'url' ) + '/socket/update', {
|
fetch( localStorage.getItem( 'url' ) + '/socket/update', {
|
||||||
method: 'post',
|
'method': 'post',
|
||||||
body: JSON.stringify( { 'event': event, 'roomName': this.roomName, 'roomToken': this.roomToken, 'data': data } ),
|
'body': JSON.stringify( {
|
||||||
credentials: 'include',
|
'event': event,
|
||||||
headers: {
|
'roomName': this.roomName,
|
||||||
|
'roomToken': this.roomToken,
|
||||||
|
'data': data
|
||||||
|
} ),
|
||||||
|
'credentials': 'include',
|
||||||
|
'headers': {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'charset': 'utf-8'
|
'charset': 'utf-8'
|
||||||
}
|
}
|
||||||
@@ -222,22 +254,32 @@ class NotificationHandler {
|
|||||||
async disconnect (): Promise<void> {
|
async disconnect (): Promise<void> {
|
||||||
if ( this.isConnected ) {
|
if ( this.isConnected ) {
|
||||||
if ( this.useSocket ) {
|
if ( this.useSocket ) {
|
||||||
this.socket.emit( 'delete-room', {
|
this.socket.emit(
|
||||||
name: this.roomName,
|
'delete-room', {
|
||||||
token: this.roomToken
|
'name': this.roomName,
|
||||||
}, ( res: { status: boolean, msg: string } ) => {
|
'token': this.roomToken
|
||||||
this.socket.disconnect();
|
}, ( res: {
|
||||||
if ( !res.status ) {
|
'status': boolean,
|
||||||
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
|
'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 {
|
} else {
|
||||||
fetch( localStorage.getItem( 'url' ) + '/socket/deleteRoom', {
|
fetch( localStorage.getItem( 'url' ) + '/socket/deleteRoom', {
|
||||||
method: 'post',
|
'method': 'post',
|
||||||
body: JSON.stringify( { 'roomName': this.roomName, 'roomToken': this.roomToken } ),
|
'body': JSON.stringify( {
|
||||||
credentials: 'include',
|
'roomName': this.roomName,
|
||||||
headers: {
|
'roomToken': this.roomToken
|
||||||
|
} ),
|
||||||
|
'credentials': 'include',
|
||||||
|
'headers': {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'charset': 'utf-8'
|
'charset': 'utf-8'
|
||||||
}
|
}
|
||||||
@@ -247,10 +289,12 @@ class NotificationHandler {
|
|||||||
} else {
|
} else {
|
||||||
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
|
alert( 'Unable to delete the room you were just in. The name will be blocked until the next server restart!' );
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} ).catch( () => {
|
} )
|
||||||
return;
|
.catch( () => {
|
||||||
} );
|
return;
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +302,7 @@ class NotificationHandler {
|
|||||||
getRoomName (): string {
|
getRoomName (): string {
|
||||||
return this.roomName;
|
return this.roomName;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotificationHandler;
|
export default NotificationHandler;
|
||||||
|
|||||||
70
MusicPlayerV2-GUI/src/scripts/song.d.ts
vendored
70
MusicPlayerV2-GUI/src/scripts/song.d.ts
vendored
@@ -4,90 +4,90 @@ export interface Song {
|
|||||||
/**
|
/**
|
||||||
* The ID. Either the apple music ID, or if from local disk, an ID starting in local_
|
* 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 of the song
|
||||||
*/
|
*/
|
||||||
origin: Origin;
|
'origin': Origin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cover image as a URL
|
* The cover image as a URL
|
||||||
*/
|
*/
|
||||||
cover: string;
|
'cover': string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The artist of the song
|
* The artist of the song
|
||||||
*/
|
*/
|
||||||
artist: string;
|
'artist': string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the song
|
* The name of the song
|
||||||
*/
|
*/
|
||||||
title: string;
|
'title': string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of the song in milliseconds
|
* 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
|
* (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
|
* (OPTIONAL) This will be displayed in brackets on the showcase screens
|
||||||
*/
|
*/
|
||||||
additionalInfo?: string;
|
'additionalInfo'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SongTransmitted {
|
export interface SongTransmitted {
|
||||||
title: string;
|
'title': string;
|
||||||
artist: string;
|
'artist': string;
|
||||||
duration: number;
|
'duration': number;
|
||||||
cover: string;
|
'cover': string;
|
||||||
additionalInfo?: string;
|
'additionalInfo'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ReadFile {
|
export interface ReadFile {
|
||||||
url: string;
|
'url': string;
|
||||||
filename: string;
|
'filename': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
data: {
|
'data': {
|
||||||
results: {
|
'results': {
|
||||||
songs: {
|
'songs': {
|
||||||
data: AppleMusicSongData[],
|
'data': AppleMusicSongData[],
|
||||||
href: string;
|
'href': string;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppleMusicSongData {
|
export interface AppleMusicSongData {
|
||||||
id: string,
|
'id': string,
|
||||||
type: string;
|
'type': string;
|
||||||
href: string;
|
'href': string;
|
||||||
attributes: {
|
'attributes': {
|
||||||
albumName: string;
|
'albumName': string;
|
||||||
artistName: string;
|
'artistName': string;
|
||||||
artwork: {
|
'artwork': {
|
||||||
width: number,
|
'width': number,
|
||||||
height: number,
|
'height': number,
|
||||||
url: string
|
'url': string
|
||||||
},
|
},
|
||||||
name: string;
|
'name': string;
|
||||||
genreNames: string[];
|
'genreNames': string[];
|
||||||
durationInMillis: number;
|
'durationInMillis': number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SongMove {
|
export interface SongMove {
|
||||||
songID: string;
|
'songID': string;
|
||||||
newPos: number;
|
'newPos': number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SSEMap {
|
export interface SSEMap {
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
/*
|
import {
|
||||||
* LanguageSchoolHossegorBookingSystem - userStore.js
|
defineStore
|
||||||
*
|
} from 'pinia';
|
||||||
* Created by Janis Hutz 10/27/2023, Licensed under a proprietary License
|
|
||||||
* https://janishutz.com, development@janishutz.com
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
|
|
||||||
|
|
||||||
// FOSS-VERSION: To enable the UI to be used with the FOSS version, change "isUserAuth" to true, you will be "logged in"
|
// FOSS-VERSION: To enable the UI to be used with the FOSS version, change "isUserAuth" to true, you will be "logged in"
|
||||||
export const useUserStore = defineStore( 'user', {
|
export const useUserStore = defineStore( 'user', {
|
||||||
state: () => ( { 'isUserAuth': true, 'hasSubscribed': false, 'isUsingKeyboard': false, 'username': '', 'isFOSSVersion': false } ),
|
'state': () => ( {
|
||||||
getters: {
|
'isUserAuth': true,
|
||||||
getUserAuthenticated: ( state ) => state.isUserAuth,
|
'hasSubscribed': false,
|
||||||
getSubscriptionStatus: ( state ) => state.hasSubscribed,
|
'isUsingKeyboard': false,
|
||||||
|
'username': '',
|
||||||
|
'isFOSSVersion': false
|
||||||
|
} ),
|
||||||
|
'getters': {
|
||||||
|
'getUserAuthenticated': state => state.isUserAuth,
|
||||||
|
'getSubscriptionStatus': state => state.hasSubscribed,
|
||||||
},
|
},
|
||||||
actions: {
|
'actions': {
|
||||||
setUserAuth ( auth: boolean ) {
|
setUserAuth ( auth: boolean ) {
|
||||||
this.isUserAuth = auth;
|
this.isUserAuth = auth;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-view">
|
<div class="app-view">
|
||||||
<button id="logout" @click="logout()"><span class="material-symbols-outlined">logout</span></button>
|
<button id="logout" @click="logout()">
|
||||||
<div class="loading-view" v-if="!hasFinishedLoading">
|
<span class="material-symbols-outlined">logout</span>
|
||||||
|
</button>
|
||||||
|
<div v-if="!hasFinishedLoading" class="loading-view">
|
||||||
<h1>Loading...</h1>
|
<h1>Loading...</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="home-view" v-else-if="hasFinishedLoading && isReady">
|
<div v-else-if="hasFinishedLoading && isReady" class="home-view">
|
||||||
<libraryView class="library-view" :playlists="playlists" @selected-playlist="( id ) => { selectPlaylist( id ) }"
|
<libraryView
|
||||||
:is-logged-in="isLoggedIntoAppleMusic" @custom-playlist="( pl ) => selectCustomPlaylist( pl )"></libraryView>
|
class="library-view"
|
||||||
|
:playlists="playlists"
|
||||||
|
:is-logged-in="isLoggedIntoAppleMusic"
|
||||||
|
@selected-playlist="( id ) => { selectPlaylist( id ) }"
|
||||||
|
@custom-playlist="( pl ) => selectCustomPlaylist( pl )"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="login-view">
|
<div v-else class="login-view">
|
||||||
<img src="@/assets/appleMusicIcon.svg" alt="Apple Music Icon">
|
<img src="@/assets/appleMusicIcon.svg" alt="Apple Music Icon">
|
||||||
<button class="fancy-button" style="margin-top: 20px;" @click="logIntoAppleMusic()">Log into Apple Music</button>
|
<button class="fancy-button" style="margin-top: 20px;" @click="logIntoAppleMusic()">
|
||||||
<button class="fancy-button" title="This allows you to use local playlists only. Cover images for your songs will be fetched from the apple music api as good as possible" @click="skipLogin()">Continue without logging in</button>
|
Log into Apple Music
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="fancy-button"
|
||||||
|
title="This allows you to use local playlists only.
|
||||||
|
Cover images for your songs will be fetched from the apple music api as good as possible"
|
||||||
|
@click="skipLogin()"
|
||||||
|
>
|
||||||
|
Continue without logging in
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<playerView :class="'player-view' + ( isReady ? ( isShowingFullScreenPlayer ? ' full-screen-player' : '' ) : ' player-hidden' )" @player-state-change="( state ) => { handlePlayerStateChange( state ) }"
|
<playerView
|
||||||
ref="player"></playerView>
|
ref="player"
|
||||||
|
:class="'player-view'
|
||||||
|
+ ( isReady ? ( isShowingFullScreenPlayer ? ' full-screen-player' : '' ) : ' player-hidden' )"
|
||||||
|
@player-state-change="( state ) => { handlePlayerStateChange( state ) }"
|
||||||
|
/>
|
||||||
<!-- TODO: Call to backend to check if user has access -->
|
<!-- TODO: Call to backend to check if user has access -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,10 +42,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import playerView from '@/components/playerView.vue';
|
import playerView from '@/components/playerView.vue';
|
||||||
import libraryView from '@/components/libraryView.vue';
|
import libraryView from '@/components/libraryView.vue';
|
||||||
import { ref } from 'vue';
|
import {
|
||||||
import type { ReadFile } from '@/scripts/song';
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
import type {
|
||||||
|
ReadFile
|
||||||
|
} from '@/scripts/song';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { useUserStore } from '@/stores/userStore';
|
import {
|
||||||
|
useUserStore
|
||||||
|
} from '@/stores/userStore';
|
||||||
|
|
||||||
const isLoggedIntoAppleMusic = ref( false );
|
const isLoggedIntoAppleMusic = ref( false );
|
||||||
const isReady = ref( false );
|
const isReady = ref( false );
|
||||||
@@ -41,7 +67,7 @@
|
|||||||
} else {
|
} else {
|
||||||
isShowingFullScreenPlayer.value = true;
|
isShowingFullScreenPlayer.value = true;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let loginChecker = 0;
|
let loginChecker = 0;
|
||||||
|
|
||||||
@@ -51,7 +77,7 @@
|
|||||||
if ( player.value.getAuth()[ 0 ] ) {
|
if ( player.value.getAuth()[ 0 ] ) {
|
||||||
isLoggedIntoAppleMusic.value = true;
|
isLoggedIntoAppleMusic.value = true;
|
||||||
isReady.value = true;
|
isReady.value = true;
|
||||||
player.value.getPlaylists( ( data ) => {
|
player.value.getPlaylists( data => {
|
||||||
playlists.value = data.data.data;
|
playlists.value = data.data.data;
|
||||||
} );
|
} );
|
||||||
clearInterval( loginChecker );
|
clearInterval( loginChecker );
|
||||||
@@ -60,25 +86,27 @@
|
|||||||
alert( 'An error occurred when logging you in. Please try again!' );
|
alert( 'An error occurred when logging you in. Please try again!' );
|
||||||
}
|
}
|
||||||
}, 500 );
|
}, 500 );
|
||||||
}
|
};
|
||||||
|
|
||||||
const skipLogin = () => {
|
const skipLogin = () => {
|
||||||
isReady.value = true;
|
isReady.value = true;
|
||||||
isLoggedIntoAppleMusic.value = false;
|
isLoggedIntoAppleMusic.value = false;
|
||||||
player.value.skipLogin();
|
player.value.skipLogin();
|
||||||
}
|
};
|
||||||
|
|
||||||
const selectPlaylist = ( id: string ) => {
|
const selectPlaylist = ( id: string ) => {
|
||||||
player.value.selectPlaylist( id );
|
player.value.selectPlaylist( id );
|
||||||
player.value.controlUI( 'show' );
|
player.value.controlUI( 'show' );
|
||||||
}
|
};
|
||||||
|
|
||||||
const selectCustomPlaylist = ( playlist: ReadFile[] ) => {
|
const selectCustomPlaylist = ( playlist: ReadFile[] ) => {
|
||||||
player.value.selectCustomPlaylist( playlist );
|
player.value.selectCustomPlaylist( playlist );
|
||||||
player.value.controlUI( 'show' );
|
player.value.controlUI( 'show' );
|
||||||
}
|
};
|
||||||
|
|
||||||
fetch( localStorage.getItem( 'url' ) + '/checkUserStatus', { credentials: 'include' } ).then( res => {
|
fetch( localStorage.getItem( 'url' ) + '/checkUserStatus', {
|
||||||
|
'credentials': 'include'
|
||||||
|
} ).then( res => {
|
||||||
if ( res.status === 200 ) {
|
if ( res.status === 200 ) {
|
||||||
res.text().then( text => {
|
res.text().then( text => {
|
||||||
if ( text === 'ok' ) {
|
if ( text === 'ok' ) {
|
||||||
@@ -102,7 +130,7 @@
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
// location.href = 'http://localhost:8080/logout?return=' + location.href;
|
// location.href = 'http://localhost:8080/logout?return=' + location.href;
|
||||||
location.href = 'https://id.janishutz.com/logout?return=' + location.href;
|
location.href = 'https://id.janishutz.com/logout?return=' + location.href;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -3,13 +3,35 @@
|
|||||||
<div class="top-view">
|
<div class="top-view">
|
||||||
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo">
|
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo">
|
||||||
<h1>MusicPlayer</h1>
|
<h1>MusicPlayer</h1>
|
||||||
<p v-if="reasonForRedirectHere" style="color: red;">{{ reasons[ reasonForRedirectHere ] }}</p>
|
<p v-if="reasonForRedirectHere" style="color: red;">
|
||||||
<p v-if="!reasonForRedirectHere"><i>An Open Source, browser-based MusicPlayer with beautiful graphics</i></p>
|
{{ reasons[ reasonForRedirectHere ] }}
|
||||||
|
</p>
|
||||||
|
<p v-if="!reasonForRedirectHere">
|
||||||
|
<i>An Open Source, browser-based MusicPlayer with beautiful graphics</i>
|
||||||
|
</p>
|
||||||
<div style="margin-top: 20px;">
|
<div style="margin-top: 20px;">
|
||||||
<a href="https://store.janishutz.com/product/com.janishutz.MusicPlayer" class="fancy-button" target="_blank">Subscribe</a>
|
<a href="https://store.janishutz.com/product/com.janishutz.MusicPlayer" class="fancy-button" target="_blank">Subscribe</a>
|
||||||
<a href="/" class="fancy-button" style="margin-left: 10px;" v-if="!reasonForRedirectHere">Log in</a>
|
<a
|
||||||
<button href="/" class="fancy-button" style="margin-left: 10px;" v-if="reasonForRedirectHere" @click="logout()">Log out</button>
|
v-if="!reasonForRedirectHere"
|
||||||
<a href="https://github.com/simplePCBuilding/MusicPlayerV2" class="fancy-button" style="margin-left: 10px;" target="_blank">GitHub</a>
|
href="/"
|
||||||
|
class="fancy-button"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
>Log in</a>
|
||||||
|
<button
|
||||||
|
v-if="reasonForRedirectHere"
|
||||||
|
href="/"
|
||||||
|
class="fancy-button"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
@click="logout()"
|
||||||
|
>
|
||||||
|
Log out
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href="https://github.com/simplePCBuilding/MusicPlayerV2"
|
||||||
|
class="fancy-button"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
target="_blank"
|
||||||
|
>GitHub</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -20,7 +42,10 @@
|
|||||||
<p>Use MusicPlayer in conjunction with Apple Music</p>
|
<p>Use MusicPlayer in conjunction with Apple Music</p>
|
||||||
|
|
||||||
<h2>Share your playlist</h2>
|
<h2>Share your playlist</h2>
|
||||||
<p>You can share your playlist on a beautifully animated public page, so that other people can join in and view your playlist</p>
|
<p>
|
||||||
|
You can share your playlist on a beautifully animated public page,
|
||||||
|
so that other people can join in and view your playlist
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Fully browser based</h2>
|
<h2>Fully browser based</h2>
|
||||||
<p>No installation required when using MusicPlayer on <a href="https://music.janishutz.com">music.janishutz.com</a></p>
|
<p>No installation required when using MusicPlayer on <a href="https://music.janishutz.com">music.janishutz.com</a></p>
|
||||||
@@ -29,7 +54,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, type Ref } from 'vue';
|
import {
|
||||||
|
ref, type Ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
interface Reasons {
|
interface Reasons {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@@ -39,12 +66,13 @@
|
|||||||
'notOwned': 'Please subscribe to use MusicPlayer here, or download and install it manually from GitHub!',
|
'notOwned': 'Please subscribe to use MusicPlayer here, or download and install it manually from GitHub!',
|
||||||
} );
|
} );
|
||||||
const reasonForRedirectHere = ref( sessionStorage.getItem( 'getRedirectionReason' ) );
|
const reasonForRedirectHere = ref( sessionStorage.getItem( 'getRedirectionReason' ) );
|
||||||
|
|
||||||
sessionStorage.removeItem( 'getRedirectionReason' );
|
sessionStorage.removeItem( 'getRedirectionReason' );
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
// location.href = 'http://localhost:8080/logout?return=' + location.href;
|
// location.href = 'http://localhost:8080/logout?return=' + location.href;
|
||||||
location.href = 'https://id.janishutz.com/logout?return=' + location.href;
|
location.href = 'https://id.janishutz.com/logout?return=' + location.href;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,54 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home-view">
|
<div class="home-view">
|
||||||
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo">
|
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo">
|
||||||
<button :class="'fancy-button' + ( isTryingToSignIn ? ' fancy-button-inactive' : '' )" @click="login()"
|
<button
|
||||||
style="margin-top: 5vh;" title="Sign in or sign up with janishutz.com ID" v-if="status"
|
:class="'fancy-button' + ( isTryingToSignIn ? ' fancy-button-inactive' : '' )"
|
||||||
>{{ isTryingToSignIn ? 'Signing you in...' : 'Login / Sign up' }}</button>
|
style="margin-top: 5vh;"
|
||||||
<p v-else>We are sorry, but we were unable to initialize the login services. Please reload the page if you wish to retry!</p>
|
title="Sign in or sign up with janishutz.com ID"
|
||||||
<p style="width: 80%;">MusicPlayer is a browser based Music Player, that allows you to connect other devices, simply with another web-browser, where you can see the current playlist with sleek animations. You can log in using your Apple Music account or load a playlist from your local disk, simply by selecting the songs using a file picker.</p>
|
@click="login()"
|
||||||
<router-link to="/get" class="fancy-button">More information</router-link>
|
>
|
||||||
<notificationsModule ref="notifications" location="bottomleft" size="bigger"></notificationsModule>
|
{{ isTryingToSignIn ? 'Signing you in...' : 'Login / Sign up' }}
|
||||||
|
</button>
|
||||||
|
<p style="width: 80%;">
|
||||||
|
MusicPlayer is a browser based Music Player, that allows you to connect other devices,
|
||||||
|
simply with another web-browser, where you can see the current playlist with sleek animations.
|
||||||
|
You can log in using your Apple Music account or load a playlist from your local disk,
|
||||||
|
simply by selecting the songs using a file picker.
|
||||||
|
</p>
|
||||||
|
<router-link to="/get" class="fancy-button">
|
||||||
|
More information
|
||||||
|
</router-link>
|
||||||
|
<notificationsModule ref="notifications" location="bottomleft" size="bigger" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO: Make possible to install and use without account, if using FOSS version
|
// TODO: Make possible to install and use without account, if using FOSS version
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { RouterLink } from 'vue-router';
|
import {
|
||||||
import { useUserStore } from '@/stores/userStore';
|
RouterLink
|
||||||
|
} from 'vue-router';
|
||||||
|
import {
|
||||||
|
useUserStore
|
||||||
|
} from '@/stores/userStore';
|
||||||
import notificationsModule from '@/components/notificationsModule.vue';
|
import notificationsModule from '@/components/notificationsModule.vue';
|
||||||
import { ref } from 'vue';
|
import {
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
const notifications = ref( notificationsModule );
|
const notifications = ref( notificationsModule );
|
||||||
const isTryingToSignIn = ref( true );
|
const isTryingToSignIn = ref( true );
|
||||||
|
|
||||||
interface JanishutzIDSDK {
|
interface JanishutzIDSDK {
|
||||||
setLoginSDKURL: ( url: string ) => undefined;
|
'setLoginSDKURL': ( url: string ) => undefined;
|
||||||
createSession: () => undefined;
|
'createSession': () => undefined;
|
||||||
verifySession: () => Promise<JHIDSessionStatus>
|
'verifySession': () => Promise<JHIDSessionStatus>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JHIDSessionStatus {
|
interface JHIDSessionStatus {
|
||||||
status: boolean;
|
'status': boolean;
|
||||||
username: string;
|
'username': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sdk: JanishutzIDSDK;
|
let sdk: JanishutzIDSDK;
|
||||||
const status = ref( true );
|
|
||||||
|
|
||||||
if ( typeof( JanishutzID ) !== 'undefined' ) {
|
|
||||||
sdk = JanishutzID();
|
|
||||||
sdk.setLoginSDKURL( localStorage.getItem( 'url' ) ?? '' );
|
|
||||||
} else {
|
|
||||||
setTimeout( () => {
|
|
||||||
notifications.value.createNotification( 'Unable to initialize account services!', 5, 'error' );
|
|
||||||
}, 1000 );
|
|
||||||
status.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
sdk.createSession();
|
sdk.createSession();
|
||||||
}
|
};
|
||||||
|
|
||||||
const store = useUserStore();
|
const store = useUserStore();
|
||||||
|
|
||||||
@@ -56,15 +62,17 @@
|
|||||||
router.push( localStorage.getItem( 'redirect' ) ?? '/app' );
|
router.push( localStorage.getItem( 'redirect' ) ?? '/app' );
|
||||||
localStorage.removeItem( 'redirect' );
|
localStorage.removeItem( 'redirect' );
|
||||||
} else {
|
} else {
|
||||||
if ( typeof( sdk ) !== 'undefined' ) {
|
if ( typeof sdk !== 'undefined' ) {
|
||||||
sdk.verifySession().then( res => {
|
sdk.verifySession().then( res => {
|
||||||
if ( res.status ) {
|
if ( res.status ) {
|
||||||
store.isUserAuth = true;
|
store.isUserAuth = true;
|
||||||
store.username = res.username;
|
store.username = res.username;
|
||||||
|
|
||||||
if ( localStorage.getItem( 'close-tab' ) ) {
|
if ( localStorage.getItem( 'close-tab' ) ) {
|
||||||
localStorage.removeItem( 'close-tab' );
|
localStorage.removeItem( 'close-tab' );
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem( 'login-ok', 'true' );
|
localStorage.setItem( 'login-ok', 'true' );
|
||||||
router.push( localStorage.getItem( 'redirect' ) ?? '/app' );
|
router.push( localStorage.getItem( 'redirect' ) ?? '/app' );
|
||||||
localStorage.removeItem( 'redirect' );
|
localStorage.removeItem( 'redirect' );
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="info">Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a></div>
|
<div class="info">
|
||||||
|
Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a>
|
||||||
|
</div>
|
||||||
<div class="remote-view">
|
<div class="remote-view">
|
||||||
<div v-if="hasLoaded && !showCouldNotFindRoom" style="width: 100%">
|
<div v-if="hasLoaded && !showCouldNotFindRoom" style="width: 100%">
|
||||||
<div class="current-song-wrapper">
|
<div class="current-song-wrapper">
|
||||||
<img v-if="playlist[ playingSong ]" :src="playlist[ playingSong ].cover" class="fancy-view-song-art" id="current-image" crossorigin="anonymous">
|
<img
|
||||||
|
v-if="playlist[ playingSong ]"
|
||||||
|
id="current-image"
|
||||||
|
:src="playlist[ playingSong ].cover"
|
||||||
|
class="fancy-view-song-art"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
>
|
||||||
<span v-else class="material-symbols-outlined fancy-view-song-art">music_note</span>
|
<span v-else class="material-symbols-outlined fancy-view-song-art">music_note</span>
|
||||||
<div class="current-song">
|
<div class="current-song">
|
||||||
<h1 style="margin-bottom: 5px;">{{ playlist[ playingSong ] ? playlist[ playingSong ].title : 'Not playing' }}</h1>
|
<h1 style="margin-bottom: 5px;">
|
||||||
|
{{ playlist[ playingSong ] ? playlist[ playingSong ].title : 'Not playing' }}
|
||||||
|
</h1>
|
||||||
<p>{{ playlist[ playingSong ] ? playlist[ playingSong ].artist : '' }}</p>
|
<p>{{ playlist[ playingSong ] ? playlist[ playingSong ].artist : '' }}</p>
|
||||||
<p class="additional-info" v-if="playlist[ playingSong ] ? ( playlist[ playingSong ].additionalInfo !== '' ) : false">{{ playlist[ playingSong ] ? playlist[ playingSong ].additionalInfo : '' }}</p>
|
<p
|
||||||
<progress max="1000" id="progress" :value="progressBar"></progress>
|
v-if="playlist[ playingSong ] ? ( playlist[ playingSong ].additionalInfo !== '' ) : false"
|
||||||
|
class="additional-info"
|
||||||
|
>
|
||||||
|
{{ playlist[ playingSong ] ? playlist[ playingSong ].additionalInfo : '' }}
|
||||||
|
</p>
|
||||||
|
<progress id="progress" max="1000" :value="progressBar"></progress>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-list-wrapper">
|
<div class="song-list-wrapper">
|
||||||
<div v-for="song in songQueue" v-bind:key="song.id" class="song-list">
|
<div v-for="song in songQueue" :key="song.id" class="song-list">
|
||||||
<div class="song-details-wrapper">
|
<div class="song-details-wrapper">
|
||||||
<h3>{{ song.title }}</h3>
|
<h3>{{ song.title }}</h3>
|
||||||
<p>{{ song.artist }}</p>
|
<p>{{ song.artist }}</p>
|
||||||
@@ -32,7 +47,9 @@
|
|||||||
<div v-else style="max-width: 80%;">
|
<div v-else style="max-width: 80%;">
|
||||||
<span class="material-symbols-outlined" style="font-size: 4rem;">wifi_off</span>
|
<span class="material-symbols-outlined" style="font-size: 4rem;">wifi_off</span>
|
||||||
<h1>Couldn't connect!</h1>
|
<h1>Couldn't connect!</h1>
|
||||||
<p>There does not appear to be a share with the specified name, or an error occurred when connecting.</p>
|
<p>
|
||||||
|
There does not appear to be a share with the specified name, or an error occurred when connecting.
|
||||||
|
</p>
|
||||||
<p>You may <a href="">reload</a> the page to try again!</p>
|
<p>You may <a href="">reload</a> the page to try again!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,8 +58,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SocketConnection from '@/scripts/connection';
|
import SocketConnection from '@/scripts/connection';
|
||||||
import type { Song } from '@/scripts/song';
|
import type {
|
||||||
import { computed, ref, type Ref } from 'vue';
|
Song
|
||||||
|
} from '@/scripts/song';
|
||||||
|
import {
|
||||||
|
computed, ref, type Ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
const isPlaying = ref( false );
|
const isPlaying = ref( false );
|
||||||
const playlist: Ref<Song[]> = ref( [] );
|
const playlist: Ref<Song[]> = ref( [] );
|
||||||
@@ -52,6 +73,7 @@
|
|||||||
const hasLoaded = ref( false );
|
const hasLoaded = ref( false );
|
||||||
const showCouldNotFindRoom = ref( false );
|
const showCouldNotFindRoom = ref( false );
|
||||||
const playbackStart = ref( 0 );
|
const playbackStart = ref( 0 );
|
||||||
|
|
||||||
let timeTracker = 0;
|
let timeTracker = 0;
|
||||||
|
|
||||||
const conn = new SocketConnection();
|
const conn = new SocketConnection();
|
||||||
@@ -61,18 +83,22 @@
|
|||||||
isPlaying.value = d.playbackStatus;
|
isPlaying.value = d.playbackStatus;
|
||||||
playingSong.value = d.playlistIndex;
|
playingSong.value = d.playlistIndex;
|
||||||
playbackStart.value = d.playbackStart;
|
playbackStart.value = d.playbackStart;
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
startTimeTracker();
|
startTimeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
pos.value = ( new Date().getTime() - parseInt( d.playbackStart ) ) / 1000;
|
pos.value = ( new Date().getTime() - parseInt( d.playbackStart ) ) / 1000;
|
||||||
progressBar.value = ( pos.value / ( playlist.value[ playingSong.value ] ? playlist.value[ playingSong.value ].duration : 1 ) ) * 1000;
|
progressBar.value = ( pos.value / ( playlist.value[ playingSong.value ]
|
||||||
|
? playlist.value[ playingSong.value ].duration : 1 ) ) * 1000;
|
||||||
hasLoaded.value = true;
|
hasLoaded.value = true;
|
||||||
conn.registerListener( 'playlist', ( data ) => {
|
conn.registerListener( 'playlist', data => {
|
||||||
playlist.value = data;
|
playlist.value = data;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playback', ( data ) => {
|
conn.registerListener( 'playback', data => {
|
||||||
isPlaying.value = data;
|
isPlaying.value = data;
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
startTimeTracker();
|
startTimeTracker();
|
||||||
} else {
|
} else {
|
||||||
@@ -80,72 +106,81 @@
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playback-start', ( data ) => {
|
conn.registerListener( 'playback-start', data => {
|
||||||
playbackStart.value = data;
|
playbackStart.value = data;
|
||||||
pos.value = ( new Date().getTime() - parseInt( data ) ) / 1000;
|
pos.value = ( new Date().getTime() - parseInt( data ) ) / 1000;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playlist-index', ( data ) => {
|
conn.registerListener( 'playlist-index', data => {
|
||||||
playingSong.value = parseInt( data );
|
playingSong.value = parseInt( data );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
conn.registerListener( 'delete-share', ( _ ) => {
|
conn.registerListener( 'delete-share', _ => {
|
||||||
alert( 'This share was just deleted. It is no longer available. The page will reload automatically to try and re-establish connection!' );
|
alert( `This share was just deleted. It is no longer available.
|
||||||
|
The page will reload automatically to try and re-establish connection!` );
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
location.reload();
|
location.reload();
|
||||||
} );
|
} );
|
||||||
} ).catch( e => {
|
} )
|
||||||
console.error( e );
|
.catch( e => {
|
||||||
showCouldNotFindRoom.value = true;
|
console.error( e );
|
||||||
} );
|
showCouldNotFindRoom.value = true;
|
||||||
|
} );
|
||||||
|
|
||||||
const songQueue = computed( () => {
|
const songQueue = computed( () => {
|
||||||
let ret: Song[] = [];
|
let ret: Song[] = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
|
|
||||||
for ( let song in playlist.value ) {
|
for ( let song in playlist.value ) {
|
||||||
if ( pos >= playingSong.value ) {
|
if ( pos >= playingSong.value ) {
|
||||||
ret.push( playlist.value[ song ] );
|
ret.push( playlist.value[ song ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// TODO: Handle disconnect from updater (=> have it disconnect)
|
|
||||||
|
|
||||||
const getTimeUntil = computed( () => {
|
const getTimeUntil = computed( () => {
|
||||||
return ( song: string ) => {
|
return ( song: string ) => {
|
||||||
let timeRemaining = 0;
|
let timeRemaining = 0;
|
||||||
|
|
||||||
for ( let i = playingSong.value; i < Object.keys( playlist.value ).length - 1; i++ ) {
|
for ( let i = playingSong.value; i < Object.keys( playlist.value ).length - 1; i++ ) {
|
||||||
if ( playlist.value[ i ].id == song ) {
|
if ( playlist.value[ i ].id == song ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeRemaining += playlist.value[ i ].duration;
|
timeRemaining += playlist.value[ i ].duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
if ( timeRemaining === 0 ) {
|
if ( timeRemaining === 0 ) {
|
||||||
return 'Currently playing';
|
return 'Currently playing';
|
||||||
} else {
|
} else {
|
||||||
return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - pos.value / 60 ) + 'min';
|
return 'Playing in less than ' + Math.ceil( ( timeRemaining / 60 ) - ( pos.value / 60 ) ) + 'min';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ( timeRemaining === 0 ) {
|
if ( timeRemaining === 0 ) {
|
||||||
return 'Plays next';
|
return 'Plays next';
|
||||||
} else {
|
} else {
|
||||||
return 'Playing less than ' + Math.ceil( timeRemaining / 60 - pos.value / 60 ) + 'min after starting to play';
|
return 'Playing less than '
|
||||||
|
+ Math.ceil( ( timeRemaining / 60 ) - ( pos.value / 60 ) ) + 'min after starting to play';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const startTimeTracker = () => {
|
const startTimeTracker = () => {
|
||||||
try {
|
try {
|
||||||
clearInterval( timeTracker );
|
clearInterval( timeTracker );
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch ( err ) { /* empty */ }
|
} catch ( err ) { /* empty */ }
|
||||||
|
|
||||||
timeTracker = setInterval( () => {
|
timeTracker = setInterval( () => {
|
||||||
pos.value = ( new Date().getTime() - playbackStart.value ) / 1000;
|
pos.value = ( new Date().getTime() - playbackStart.value ) / 1000;
|
||||||
progressBar.value = ( pos.value / playlist.value[ playingSong.value ].duration ) * 1000;
|
progressBar.value = ( pos.value / playlist.value[ playingSong.value ].duration ) * 1000;
|
||||||
|
|
||||||
if ( isNaN( progressBar.value ) ) {
|
if ( isNaN( progressBar.value ) ) {
|
||||||
progressBar.value = 0;
|
progressBar.value = 0;
|
||||||
}
|
}
|
||||||
@@ -156,11 +191,11 @@
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}, 100 );
|
}, 100 );
|
||||||
}
|
};
|
||||||
|
|
||||||
const stopTimeTracker = () => {
|
const stopTimeTracker = () => {
|
||||||
clearInterval( timeTracker );
|
clearInterval( timeTracker );
|
||||||
}
|
};
|
||||||
|
|
||||||
document.addEventListener( 'visibilitychange', () => {
|
document.addEventListener( 'visibilitychange', () => {
|
||||||
if ( !document.hidden ) {
|
if ( !document.hidden ) {
|
||||||
|
|||||||
@@ -1,34 +1,66 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<span class="anti-tamper material-symbols-outlined" v-if="isAntiTamperEnabled" @click="secureModeInfo( 'toggle' )">lock</span>
|
<span
|
||||||
<div class="anti-tamper-info" v-if="isShowingSecureModeInfo && isAntiTamperEnabled" @click="secureModeInfo( 'hide' )">Anti-Tamper is enabled. Leaving this window will cause a notification to be dispatched to the player!</div>
|
v-if="isAntiTamperEnabled"
|
||||||
<div class="info">Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a></div>
|
class="anti-tamper material-symbols-outlined"
|
||||||
|
@click="secureModeInfo( 'toggle' )"
|
||||||
|
>lock</span>
|
||||||
|
<div
|
||||||
|
v-if="isShowingSecureModeInfo && isAntiTamperEnabled"
|
||||||
|
class="anti-tamper-info"
|
||||||
|
@click="secureModeInfo( 'hide' )"
|
||||||
|
>
|
||||||
|
Anti-Tamper is enabled. Leaving this window will cause a notification to be dispatched to the player!
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a>
|
||||||
|
</div>
|
||||||
<div class="remote-view">
|
<div class="remote-view">
|
||||||
<div v-if="hasLoaded && !showCouldNotFindRoom" class="showcase-wrapper">
|
<div v-if="hasLoaded && !showCouldNotFindRoom" class="showcase-wrapper">
|
||||||
<div class="current-song-wrapper">
|
<div class="current-song-wrapper">
|
||||||
<img v-if="playlist[ playingSong ]" :src="playlist[ playingSong ].cover" class="fancy-view-song-art" id="current-image" crossorigin="anonymous">
|
<img
|
||||||
|
v-if="playlist[ playingSong ]"
|
||||||
|
id="current-image"
|
||||||
|
:src="playlist[ playingSong ].cover"
|
||||||
|
class="fancy-view-song-art"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
>
|
||||||
<span v-else class="material-symbols-outlined fancy-view-song-art">music_note</span>
|
<span v-else class="material-symbols-outlined fancy-view-song-art">music_note</span>
|
||||||
<div class="current-song">
|
<div class="current-song">
|
||||||
<h1 style="margin-bottom: 5px;">{{ playlist[ playingSong ] ? playlist[ playingSong ].title : 'Not playing' }}</h1>
|
<h1 style="margin-bottom: 5px;">
|
||||||
|
{{ playlist[ playingSong ] ? playlist[ playingSong ].title : 'Not playing' }}
|
||||||
|
</h1>
|
||||||
<p>{{ playlist[ playingSong ] ? playlist[ playingSong ].artist : '' }}</p>
|
<p>{{ playlist[ playingSong ] ? playlist[ playingSong ].artist : '' }}</p>
|
||||||
<p class="additional-info" v-if="playlist[ playingSong ] ? ( playlist[ playingSong ].additionalInfo !== '' ) : false">{{ playlist[ playingSong ] ? playlist[ playingSong ].additionalInfo : '' }}</p>
|
<p
|
||||||
<progress max="1000" id="progress" :value="progressBar"></progress>
|
v-if="playlist[ playingSong ] ? ( playlist[ playingSong ].additionalInfo !== '' ) : false"
|
||||||
|
class="additional-info"
|
||||||
|
>
|
||||||
|
{{ playlist[ playingSong ] ? playlist[ playingSong ].additionalInfo : '' }}
|
||||||
|
</p>
|
||||||
|
<progress id="progress" max="1000" :value="progressBar"></progress>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mode-selector-wrapper">
|
<div class="mode-selector-wrapper">
|
||||||
<select v-model="visualizationSettings" @change="handleAnimationChange()">
|
<select v-model="visualizationSettings" @change="handleAnimationChange()">
|
||||||
<option value="mic">Microphone (Mic access required)</option>
|
<option value="mic">
|
||||||
<option value="off">No visualization except background</option>
|
Microphone (Mic access required)
|
||||||
|
</option>
|
||||||
|
<option value="off">
|
||||||
|
No visualization except background
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-list-wrapper">
|
<div class="song-list-wrapper">
|
||||||
<div v-for="song in songQueue" v-bind:key="song.id" class="song-list">
|
<div v-for="song in songQueue" :key="song.id" class="song-list">
|
||||||
<img :src="song.cover" class="song-image">
|
<img :src="song.cover" class="song-image">
|
||||||
<div v-if="( playlist[ playingSong ] ? playlist[ playingSong ].id : '' ) === song.id && isPlaying" class="playing-symbols">
|
<div
|
||||||
|
v-if="( playlist[ playingSong ] ? playlist[ playingSong ].id : '' ) === song.id && isPlaying"
|
||||||
|
class="playing-symbols"
|
||||||
|
>
|
||||||
<div class="playing-symbols-wrapper">
|
<div class="playing-symbols-wrapper">
|
||||||
<div class="playing-bar" id="bar-1"></div>
|
<div id="bar-1" class="playing-bar"></div>
|
||||||
<div class="playing-bar" id="bar-2"></div>
|
<div id="bar-2" class="playing-bar"></div>
|
||||||
<div class="playing-bar" id="bar-3"></div>
|
<div id="bar-3" class="playing-bar"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-details-wrapper">
|
<div class="song-details-wrapper">
|
||||||
@@ -47,10 +79,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="showcase-wrapper">
|
<div v-else class="showcase-wrapper">
|
||||||
<h1>Couldn't connect!</h1>
|
<h1>Couldn't connect!</h1>
|
||||||
<p>There does not appear to be a share with the specified name, or an error occurred when connecting.</p>
|
<p>
|
||||||
|
There does not appear to be a share with the specified name, or an error occurred when connecting.
|
||||||
|
</p>
|
||||||
<p>You may reload the page to try again!</p>
|
<p>You may reload the page to try again!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="background" id="background">
|
<div id="background" class="background">
|
||||||
<div class="beat-manual"></div>
|
<div class="beat-manual"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,8 +93,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SocketConnection from '@/scripts/connection';
|
import SocketConnection from '@/scripts/connection';
|
||||||
import type { Song } from '@/scripts/song';
|
import type {
|
||||||
import { computed, ref, type Ref } from 'vue';
|
Song
|
||||||
|
} from '@/scripts/song';
|
||||||
|
import {
|
||||||
|
computed, ref, type Ref
|
||||||
|
} from 'vue';
|
||||||
import bizualizer from '@/scripts/bizualizer';
|
import bizualizer from '@/scripts/bizualizer';
|
||||||
|
|
||||||
const isPlaying = ref( false );
|
const isPlaying = ref( false );
|
||||||
@@ -71,10 +109,11 @@
|
|||||||
const hasLoaded = ref( false );
|
const hasLoaded = ref( false );
|
||||||
const showCouldNotFindRoom = ref( false );
|
const showCouldNotFindRoom = ref( false );
|
||||||
const playbackStart = ref( 0 );
|
const playbackStart = ref( 0 );
|
||||||
|
|
||||||
let timeTracker = 0;
|
let timeTracker = 0;
|
||||||
|
|
||||||
const visualizationSettings = ref( 'mic' );
|
const visualizationSettings = ref( 'mic' );
|
||||||
const isAntiTamperEnabled = ref( false );
|
const isAntiTamperEnabled = ref( false );
|
||||||
|
|
||||||
const conn = new SocketConnection();
|
const conn = new SocketConnection();
|
||||||
|
|
||||||
conn.connect().then( d => {
|
conn.connect().then( d => {
|
||||||
@@ -82,22 +121,29 @@
|
|||||||
isPlaying.value = d.playbackStatus;
|
isPlaying.value = d.playbackStatus;
|
||||||
playingSong.value = d.playlistIndex;
|
playingSong.value = d.playlistIndex;
|
||||||
playbackStart.value = d.playbackStart;
|
playbackStart.value = d.playbackStart;
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
startTimeTracker();
|
startTimeTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
pos.value = ( new Date().getTime() - parseInt( d.playbackStart ) ) / 1000;
|
pos.value = ( new Date().getTime() - parseInt( d.playbackStart ) ) / 1000;
|
||||||
progressBar.value = ( pos.value / ( playlist.value[ playingSong.value ] ? playlist.value[ playingSong.value ].duration : 1 ) ) * 1000;
|
progressBar.value
|
||||||
|
= ( pos.value / ( playlist.value[ playingSong.value ]
|
||||||
|
? playlist.value[ playingSong.value ].duration : 1 ) ) * 1000;
|
||||||
hasLoaded.value = true;
|
hasLoaded.value = true;
|
||||||
|
|
||||||
if ( d.useAntiTamper ) {
|
if ( d.useAntiTamper ) {
|
||||||
isAntiTamperEnabled.value = true;
|
isAntiTamperEnabled.value = true;
|
||||||
notifier();
|
notifier();
|
||||||
}
|
}
|
||||||
conn.registerListener( 'playlist', ( data ) => {
|
|
||||||
|
conn.registerListener( 'playlist', data => {
|
||||||
playlist.value = data;
|
playlist.value = data;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playback', ( data ) => {
|
conn.registerListener( 'playback', data => {
|
||||||
isPlaying.value = data;
|
isPlaying.value = data;
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
startTimeTracker();
|
startTimeTracker();
|
||||||
} else {
|
} else {
|
||||||
@@ -105,12 +151,12 @@
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playback-start', ( data ) => {
|
conn.registerListener( 'playback-start', data => {
|
||||||
playbackStart.value = data;
|
playbackStart.value = data;
|
||||||
pos.value = ( new Date().getTime() - parseInt( data ) ) / 1000;
|
pos.value = ( new Date().getTime() - parseInt( data ) ) / 1000;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.registerListener( 'playlist-index', ( data ) => {
|
conn.registerListener( 'playlist-index', data => {
|
||||||
playingSong.value = parseInt( data );
|
playingSong.value = parseInt( data );
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
setBackground();
|
setBackground();
|
||||||
@@ -118,58 +164,64 @@
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
conn.registerListener( 'delete-share', ( _ ) => {
|
conn.registerListener( 'delete-share', _ => {
|
||||||
alert( 'This share was just deleted. It is no longer available. This page will reload automatically!' );
|
alert( 'This share was just deleted. It is no longer available. This page will reload automatically!' );
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
location.reload();
|
location.reload();
|
||||||
} );
|
} );
|
||||||
} ).catch( e => {
|
} )
|
||||||
console.error( e );
|
.catch( e => {
|
||||||
showCouldNotFindRoom.value = true;
|
console.error( e );
|
||||||
} );
|
showCouldNotFindRoom.value = true;
|
||||||
|
} );
|
||||||
|
|
||||||
const songQueue = computed( () => {
|
const songQueue = computed( () => {
|
||||||
let ret: Song[] = [];
|
let ret: Song[] = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
|
|
||||||
for ( let song in playlist.value ) {
|
for ( let song in playlist.value ) {
|
||||||
if ( pos >= playingSong.value ) {
|
if ( pos >= playingSong.value ) {
|
||||||
ret.push( playlist.value[ song ] );
|
ret.push( playlist.value[ song ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// TODO: Handle disconnect from updater (=> have it disconnect)
|
|
||||||
|
|
||||||
const getTimeUntil = computed( () => {
|
const getTimeUntil = computed( () => {
|
||||||
return ( song: string ) => {
|
return ( song: string ) => {
|
||||||
let timeRemaining = 0;
|
let timeRemaining = 0;
|
||||||
|
|
||||||
for ( let i = playingSong.value; i < Object.keys( playlist.value ).length - 1; i++ ) {
|
for ( let i = playingSong.value; i < Object.keys( playlist.value ).length - 1; i++ ) {
|
||||||
if ( playlist.value[ i ].id == song ) {
|
if ( playlist.value[ i ].id == song ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeRemaining += playlist.value[ i ].duration;
|
timeRemaining += playlist.value[ i ].duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
if ( timeRemaining === 0 ) {
|
if ( timeRemaining === 0 ) {
|
||||||
return 'Currently playing';
|
return 'Currently playing';
|
||||||
} else {
|
} else {
|
||||||
return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - pos.value / 60 ) + 'min';
|
return 'Playing in less than ' + Math.ceil( ( timeRemaining / 60 ) - ( pos.value / 60 ) ) + 'min';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ( timeRemaining === 0 ) {
|
if ( timeRemaining === 0 ) {
|
||||||
return 'Plays next';
|
return 'Plays next';
|
||||||
} else {
|
} else {
|
||||||
return 'Playing less than ' + Math.ceil( timeRemaining / 60 - pos.value / 60 ) + 'min after starting to play';
|
return 'Playing less than '
|
||||||
|
+ Math.ceil( ( timeRemaining / 60 ) - ( pos.value / 60 ) ) + 'min after starting to play';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const startTimeTracker = () => {
|
const startTimeTracker = () => {
|
||||||
try {
|
try {
|
||||||
clearInterval( timeTracker );
|
clearInterval( timeTracker );
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch ( err ) { /* empty */ }
|
} catch ( err ) { /* empty */ }
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
@@ -179,21 +231,23 @@
|
|||||||
timeTracker = setInterval( () => {
|
timeTracker = setInterval( () => {
|
||||||
pos.value = ( new Date().getTime() - playbackStart.value ) / 1000;
|
pos.value = ( new Date().getTime() - playbackStart.value ) / 1000;
|
||||||
progressBar.value = ( pos.value / playlist.value[ playingSong.value ].duration ) * 1000;
|
progressBar.value = ( pos.value / playlist.value[ playingSong.value ].duration ) * 1000;
|
||||||
|
|
||||||
if ( isNaN( progressBar.value ) ) {
|
if ( isNaN( progressBar.value ) ) {
|
||||||
progressBar.value = 0;
|
progressBar.value = 0;
|
||||||
}
|
}
|
||||||
}, 100 );
|
}, 100 );
|
||||||
}
|
};
|
||||||
|
|
||||||
const stopTimeTracker = () => {
|
const stopTimeTracker = () => {
|
||||||
clearInterval( timeTracker );
|
clearInterval( timeTracker );
|
||||||
|
|
||||||
handleAnimationChange();
|
handleAnimationChange();
|
||||||
}
|
};
|
||||||
|
|
||||||
const animateBeat = () => {
|
const animateBeat = () => {
|
||||||
$( '.beat-manual' ).stop();
|
$( '.beat-manual' ).stop();
|
||||||
const duration = Math.ceil( 60 / 180 * 500 ) - 50;
|
const duration = Math.ceil( 60 / 180 * 500 ) - 50;
|
||||||
|
|
||||||
$( '.beat-manual' ).fadeIn( 50 );
|
$( '.beat-manual' ).fadeIn( 50 );
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
$( '.beat-manual' ).fadeOut( duration );
|
$( '.beat-manual' ).fadeOut( duration );
|
||||||
@@ -202,30 +256,31 @@
|
|||||||
$( '.beat-manual' ).stop();
|
$( '.beat-manual' ).stop();
|
||||||
}, duration );
|
}, duration );
|
||||||
}, 50 );
|
}, 50 );
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleAnimationChange = () => {
|
const handleAnimationChange = () => {
|
||||||
if ( visualizationSettings.value === 'mic' && isPlaying.value ) {
|
if ( visualizationSettings.value === 'mic' && isPlaying.value ) {
|
||||||
bizualizer.subscribeToBeatUpdate( animateBeat );
|
bizualizer.subscribeToBeatUpdate( animateBeat );
|
||||||
} else {
|
} else {
|
||||||
bizualizer.unsubscribeFromBeatUpdate()
|
bizualizer.unsubscribeFromBeatUpdate();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const setBackground = () => {
|
const setBackground = () => {
|
||||||
bizualizer.createBackground().then( bg => {
|
bizualizer.createBackground().then( bg => {
|
||||||
$( '#background' ).css( 'background', bg );
|
$( '#background' ).css( 'background', bg );
|
||||||
} );
|
} );
|
||||||
}
|
};
|
||||||
|
|
||||||
const notifier = () => {
|
const notifier = () => {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
|
|
||||||
console.warn( '[ notifier ]: Status is now enabled \n\n-> Any leaving or tampering with the website will send a notification to the host' );
|
console.warn( '[ notifier ]: Status is now enabled \n\n-> Any leaving or tampering with the website will send a notification to the host' );
|
||||||
|
|
||||||
// Detect if window is currently in focus
|
// Detect if window is currently in focus
|
||||||
window.onblur = () => {
|
window.onblur = () => {
|
||||||
sendNotification();
|
sendNotification();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Detect if browser window becomes hidden (also with blur event)
|
// Detect if browser window becomes hidden (also with blur event)
|
||||||
document.onvisibilitychange = () => {
|
document.onvisibilitychange = () => {
|
||||||
@@ -233,18 +288,19 @@
|
|||||||
sendNotification();
|
sendNotification();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const sendNotification = () => {
|
const sendNotification = () => {
|
||||||
new Notification( 'YOU ARE UNDER SURVEILLANCE', {
|
new Notification( 'YOU ARE UNDER SURVEILLANCE', {
|
||||||
body: 'Please return to the original webpage immediately!',
|
'body': 'Please return to the original webpage immediately!',
|
||||||
requireInteraction: true,
|
'requireInteraction': true,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
conn.emit( 'tampering', '' );
|
conn.emit( 'tampering', '' );
|
||||||
}
|
};
|
||||||
|
|
||||||
const isShowingSecureModeInfo = ref( false );
|
const isShowingSecureModeInfo = ref( false );
|
||||||
|
|
||||||
const secureModeInfo = ( action: string ) => {
|
const secureModeInfo = ( action: string ) => {
|
||||||
if ( action === 'toggle' ) {
|
if ( action === 'toggle' ) {
|
||||||
isShowingSecureModeInfo.value = !isShowingSecureModeInfo.value;
|
isShowingSecureModeInfo.value = !isShowingSecureModeInfo.value;
|
||||||
@@ -253,7 +309,7 @@
|
|||||||
} else {
|
} else {
|
||||||
isShowingSecureModeInfo.value = false;
|
isShowingSecureModeInfo.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,416 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="info">Designed and developed by Janis Hutz <a href="https://janishutz.com" target="_blank" style="text-decoration: none; color: white;">https://janishutz.com</a></div>
|
|
||||||
<div class="content" id="app">
|
|
||||||
<div v-if="hasLoaded" style="width: 100%">
|
|
||||||
<div class="current-song-wrapper">
|
|
||||||
<span class="material-symbols-outlined fancy-view-song-art" v-if="!playingSong.hasCoverArt">music_note</span>
|
|
||||||
<img v-else-if="playingSong.hasCoverArt && playingSong.coverArtOrigin === 'api'" :src="playingSong.coverArtURL" class="fancy-view-song-art" id="current-image" crossorigin="anonymous">
|
|
||||||
<img v-else :src="'/getSongCover?filename=' + playingSong.filename" class="fancy-view-song-art" id="current-image">
|
|
||||||
<div class="current-song">
|
|
||||||
<progress max="1000" id="progress" :value="progressBar"></progress>
|
|
||||||
<h1>{{ playingSong.title }}</h1>
|
|
||||||
<p class="dancing-style" v-if="playingSong.dancingStyle">{{ playingSong.dancingStyle }}</p>
|
|
||||||
<p>{{ playingSong.artist }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mode-selector-wrapper">
|
|
||||||
<select v-model="visualizationSettings" @change="setVisualization()">
|
|
||||||
<option value="mic">Microphone (Mic access required)</option>
|
|
||||||
<option value="bpm">BPM (might not be 100% accurate)</option>
|
|
||||||
<option value="off">No visualization except background</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="song-list-wrapper">
|
|
||||||
<div v-for="song in songQueue" class="song-list">
|
|
||||||
<span class="material-symbols-outlined song-image" v-if="!song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying )">music_note</span>
|
|
||||||
<img v-else-if="song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying ) && song.coverArtOrigin === 'api'" :src="song.coverArtURL" class="song-image">
|
|
||||||
<img v-else-if="song.hasCoverArt && ( playingSong.filename !== song.filename || isPlaying ) && song.coverArtOrigin !== 'api'" :src="'/getSongCover?filename=' + song.filename" class="song-image">
|
|
||||||
<div v-if="playingSong.filename === song.filename && isPlaying" class="playing-symbols">
|
|
||||||
<div class="playing-symbols-wrapper">
|
|
||||||
<div class="playing-bar" id="bar-1"></div>
|
|
||||||
<div class="playing-bar" id="bar-2"></div>
|
|
||||||
<div class="playing-bar" id="bar-3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="material-symbols-outlined pause-icon" v-if="!isPlaying && playingSong.filename === song.filename">pause</span>
|
|
||||||
<div class="song-details-wrapper">
|
|
||||||
<h3>{{ song.title }}</h3>
|
|
||||||
<p>{{ song.artist }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="time-until">
|
|
||||||
{{ getTimeUntil( song ) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <img :src="" alt=""> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<h1>Loading...</h1>
|
|
||||||
</div>
|
|
||||||
<div class="background" id="background">
|
|
||||||
<div class="beat"></div>
|
|
||||||
<div class="beat-manual"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- TODO: Get ColorThief either from CDN or preferably as NPM module -->
|
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Song } from '@/scripts/song';
|
|
||||||
import { computed, ref, type Ref } from 'vue';
|
|
||||||
import { ColorThief } from 'colorthief';
|
|
||||||
|
|
||||||
const hasLoaded = ref( false );
|
|
||||||
const songs: Ref<Song[]> = ref( [] );
|
|
||||||
const playingSong = ref( 0 );
|
|
||||||
const isPlaying = ref( false );
|
|
||||||
const pos = ref( 0 );
|
|
||||||
const colourPalette: string[] = [];
|
|
||||||
const progressBar = ref( 0 );
|
|
||||||
const timeTracker = ref( 0 );
|
|
||||||
const visualizationSettings = ref( 'mic' );
|
|
||||||
const micAnalyzer = ref( 0 );
|
|
||||||
const beatDetected = ref( false );
|
|
||||||
const colorThief = new ColorThief();
|
|
||||||
const songQueue = computed( () => {
|
|
||||||
let ret = [];
|
|
||||||
let pos = 0;
|
|
||||||
for ( let song in songs.value ) {
|
|
||||||
if ( pos >= playingSong.value ) {
|
|
||||||
ret.push( songs.value[ song ] );
|
|
||||||
}
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
} );
|
|
||||||
const getTimeUntil = computed( () => {
|
|
||||||
return ( song ) => {
|
|
||||||
let timeRemaining = 0;
|
|
||||||
for ( let i = this.queuePos; i < Object.keys( this.songs ).length - 1; i++ ) {
|
|
||||||
if ( this.songs[ i ] == song ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
timeRemaining += parseInt( this.songs[ i ].duration );
|
|
||||||
}
|
|
||||||
if ( isPlaying.value ) {
|
|
||||||
if ( timeRemaining === 0 ) {
|
|
||||||
return 'Currently playing';
|
|
||||||
} else {
|
|
||||||
return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ( timeRemaining === 0 ) {
|
|
||||||
return 'Plays next';
|
|
||||||
} else {
|
|
||||||
return 'Playing less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min after starting to play';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
methods: {
|
|
||||||
startTimeTracker () {
|
|
||||||
this.timeTracker = setInterval( () => {
|
|
||||||
this.pos = ( new Date().getTime() - this.playingSong.startTime ) / 1000 + this.oldPos;
|
|
||||||
this.progressBar = ( this.pos / this.playingSong.duration ) * 1000;
|
|
||||||
if ( isNaN( this.progressBar ) ) {
|
|
||||||
this.progressBar = 0;
|
|
||||||
}
|
|
||||||
}, 100 );
|
|
||||||
},
|
|
||||||
stopTimeTracker () {
|
|
||||||
clearInterval( this.timeTracker );
|
|
||||||
this.oldPos = this.pos;
|
|
||||||
},
|
|
||||||
getImageData() {
|
|
||||||
return new Promise( ( resolve, reject ) => {
|
|
||||||
if ( this.playingSong.hasCoverArt ) {
|
|
||||||
setTimeout( () => {
|
|
||||||
const img = document.getElementById( 'current-image' );
|
|
||||||
if ( img.complete ) {
|
|
||||||
resolve( this.colorThief.getPalette( img ) );
|
|
||||||
} else {
|
|
||||||
img.addEventListener( 'load', () => {
|
|
||||||
resolve( this.colorThief.getPalette( img ) );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}, 500 );
|
|
||||||
} else {
|
|
||||||
reject( 'no image' );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
},
|
|
||||||
connect() {
|
|
||||||
this.colorThief = new ColorThief();
|
|
||||||
let source = new EventSource( '/clientDisplayNotifier', { withCredentials: true } );
|
|
||||||
source.onmessage = ( e ) => {
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse( e.data );
|
|
||||||
} catch ( err ) {
|
|
||||||
data = { 'type': e.data };
|
|
||||||
}
|
|
||||||
if ( data.type === 'basics' ) {
|
|
||||||
this.isPlaying = data.data.isPlaying ?? false;
|
|
||||||
this.playingSong = data.data.playingSong ?? {};
|
|
||||||
this.songs = data.data.songQueue ?? [];
|
|
||||||
this.pos = data.data.pos ?? 0;
|
|
||||||
this.oldPos = data.data.pos ?? 0;
|
|
||||||
this.progressBar = this.pos / this.playingSong.duration * 1000;
|
|
||||||
this.queuePos = data.data.queuePos ?? 0;
|
|
||||||
this.getImageData().then( palette => {
|
|
||||||
this.colourPalette = palette;
|
|
||||||
this.handleBackground();
|
|
||||||
} ).catch( () => {
|
|
||||||
this.colourPalette = [ { 'r': 255, 'g': 0, 'b': 0 }, { 'r': 0, 'g': 255, 'b': 0 }, { 'r': 0, 'g': 0, 'b': 255 } ];
|
|
||||||
this.handleBackground();
|
|
||||||
} );
|
|
||||||
} else if ( data.type === 'pos' ) {
|
|
||||||
this.pos = data.data;
|
|
||||||
this.oldPos = data.data;
|
|
||||||
this.progressBar = data.data / this.playingSong.duration * 1000;
|
|
||||||
} else if ( data.type === 'isPlaying' ) {
|
|
||||||
this.isPlaying = data.data;
|
|
||||||
this.handleBackground();
|
|
||||||
} else if ( data.type === 'songQueue' ) {
|
|
||||||
this.songs = data.data;
|
|
||||||
} else if ( data.type === 'playingSong' ) {
|
|
||||||
this.playingSong = data.data;
|
|
||||||
this.getImageData().then( palette => {
|
|
||||||
this.colourPalette = palette;
|
|
||||||
this.handleBackground();
|
|
||||||
} ).catch( () => {
|
|
||||||
this.colourPalette = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ];
|
|
||||||
this.handleBackground();
|
|
||||||
} );
|
|
||||||
} else if ( data.type === 'queuePos' ) {
|
|
||||||
this.queuePos = data.data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
source.onopen = () => {
|
|
||||||
this.isReconnecting = false;
|
|
||||||
this.hasLoaded = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
source.addEventListener( 'error', function( e ) {
|
|
||||||
if ( e.eventPhase == EventSource.CLOSED ) source.close();
|
|
||||||
|
|
||||||
if ( e.target.readyState == EventSource.CLOSED ) {
|
|
||||||
console.log( 'disconnected' );
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Notify about disconnect
|
|
||||||
setTimeout( () => {
|
|
||||||
if ( !self.isReconnecting ) {
|
|
||||||
self.isReconnecting = true;
|
|
||||||
self.tryReconnect();
|
|
||||||
}
|
|
||||||
}, 1000 );
|
|
||||||
}, false );
|
|
||||||
},
|
|
||||||
tryReconnect() {
|
|
||||||
const int = setInterval( () => {
|
|
||||||
if ( !this.isReconnecting ) {
|
|
||||||
clearInterval( int );
|
|
||||||
} else {
|
|
||||||
connectToSSESource();
|
|
||||||
}
|
|
||||||
}, 1000 );
|
|
||||||
},
|
|
||||||
handleBackground() {
|
|
||||||
let colourDetails = [];
|
|
||||||
let colours = [];
|
|
||||||
let differentEnough = true;
|
|
||||||
if ( this.colourPalette[ 0 ] ) {
|
|
||||||
for ( let i in this.colourPalette ) {
|
|
||||||
for ( let colour in colourDetails ) {
|
|
||||||
const colourDiff = ( Math.abs( colourDetails[ colour ][ 0 ] - this.colourPalette[ i ][ 0 ] ) / 255
|
|
||||||
+ Math.abs( colourDetails[ colour ][ 1 ] - this.colourPalette[ i ][ 1 ] ) / 255
|
|
||||||
+ Math.abs( colourDetails[ colour ][ 2 ] - this.colourPalette[ i ][ 2 ] ) / 255 ) / 3 * 100;
|
|
||||||
if ( colourDiff > 15 ) {
|
|
||||||
differentEnough = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( differentEnough ) {
|
|
||||||
colourDetails.push( this.colourPalette[ i ] );
|
|
||||||
colours.push( 'rgb(' + this.colourPalette[ i ][ 0 ] + ',' + this.colourPalette[ i ][ 1 ] + ',' + this.colourPalette[ i ][ 2 ] + ')' );
|
|
||||||
}
|
|
||||||
differentEnough = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let outColours = 'conic-gradient(';
|
|
||||||
if ( colours.length < 3 ) {
|
|
||||||
for ( let i = 0; i < 3; i++ ) {
|
|
||||||
if ( colours[ i ] ) {
|
|
||||||
outColours += colours[ i ] + ',';
|
|
||||||
} else {
|
|
||||||
if ( i === 0 ) {
|
|
||||||
outColours += 'blue,';
|
|
||||||
} else if ( i === 1 ) {
|
|
||||||
outColours += 'green,';
|
|
||||||
} else if ( i === 2 ) {
|
|
||||||
outColours += 'red,';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ( colours.length < 11 ) {
|
|
||||||
for ( let i in colours ) {
|
|
||||||
outColours += colours[ i ] + ',';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for ( let i = 0; i < 10; i++ ) {
|
|
||||||
outColours += colours[ i ] + ',';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outColours += colours[ 0 ] ?? 'blue' + ')';
|
|
||||||
|
|
||||||
$( '#background' ).css( 'background', outColours );
|
|
||||||
this.setVisualization();
|
|
||||||
},
|
|
||||||
setVisualization () {
|
|
||||||
if ( Object.keys( this.playingSong ).length > 0 ) {
|
|
||||||
if ( this.visualizationSettings === 'bpm' ) {
|
|
||||||
if ( this.playingSong.bpm && this.isPlaying ) {
|
|
||||||
$( '.beat' ).show();
|
|
||||||
$( '.beat' ).css( 'animation-duration', 60 / this.playingSong.bpm );
|
|
||||||
$( '.beat' ).css( 'animation-delay', this.pos % ( 60 / this.playingSong.bpm * this.pos ) + this.playingSong.bpmOffset - ( 60 / this.playingSong.bpm * this.pos / 2 ) );
|
|
||||||
} else {
|
|
||||||
$( '.beat' ).hide();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
clearInterval( this.micAnalyzer );
|
|
||||||
} catch ( err ) {}
|
|
||||||
} else if ( this.visualizationSettings === 'off' ) {
|
|
||||||
$( '.beat' ).hide();
|
|
||||||
try {
|
|
||||||
clearInterval( this.micAnalyzer );
|
|
||||||
} catch ( err ) {}
|
|
||||||
} else if ( this.visualizationSettings === 'mic' ) {
|
|
||||||
$( '.beat-manual' ).hide();
|
|
||||||
try {
|
|
||||||
clearInterval( this.micAnalyzer );
|
|
||||||
} catch ( err ) {}
|
|
||||||
this.micAudioHandler();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log( 'not playing yet' );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
micAudioHandler () {
|
|
||||||
const audioContext = new ( window.AudioContext || window.webkitAudioContext )();
|
|
||||||
const analyser = audioContext.createAnalyser();
|
|
||||||
analyser.fftSize = 256;
|
|
||||||
const bufferLength = analyser.frequencyBinCount;
|
|
||||||
const dataArray = new Uint8Array( bufferLength );
|
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia( { audio: true } ).then( ( stream ) => {
|
|
||||||
const mic = audioContext.createMediaStreamSource( stream );
|
|
||||||
mic.connect( analyser );
|
|
||||||
analyser.getByteFrequencyData( dataArray );
|
|
||||||
let prevSpectrum = null;
|
|
||||||
let threshold = 10; // Adjust as needed
|
|
||||||
this.beatDetected = false;
|
|
||||||
this.micAnalyzer = setInterval( () => {
|
|
||||||
analyser.getByteFrequencyData( dataArray );
|
|
||||||
// Convert the frequency data to a numeric array
|
|
||||||
const currentSpectrum = Array.from( dataArray );
|
|
||||||
|
|
||||||
if ( prevSpectrum ) {
|
|
||||||
// Calculate the spectral flux
|
|
||||||
const flux = this.calculateSpectralFlux( prevSpectrum, currentSpectrum );
|
|
||||||
|
|
||||||
if ( flux > threshold && !this.beatDetected ) {
|
|
||||||
// Beat detected
|
|
||||||
this.beatDetected = true;
|
|
||||||
this.animateBeat();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevSpectrum = currentSpectrum;
|
|
||||||
}, 20 );
|
|
||||||
} );
|
|
||||||
},
|
|
||||||
animateBeat () {
|
|
||||||
$( '.beat-manual' ).stop();
|
|
||||||
const duration = Math.ceil( 60 / ( this.playingSong.bpm ?? 180 ) * 500 ) - 50;
|
|
||||||
$( '.beat-manual' ).fadeIn( 50 );
|
|
||||||
setTimeout( () => {
|
|
||||||
$( '.beat-manual' ).fadeOut( duration );
|
|
||||||
setTimeout( () => {
|
|
||||||
$( '.beat-manual' ).stop();
|
|
||||||
this.beatDetected = false;
|
|
||||||
}, duration );
|
|
||||||
}, 50 );
|
|
||||||
},
|
|
||||||
calculateSpectralFlux( prevSpectrum, currentSpectrum ) {
|
|
||||||
let flux = 0;
|
|
||||||
|
|
||||||
for ( let i = 0; i < prevSpectrum.length; i++ ) {
|
|
||||||
const diff = currentSpectrum[ i ] - prevSpectrum[ i ];
|
|
||||||
flux += Math.max( 0, diff );
|
|
||||||
}
|
|
||||||
|
|
||||||
return flux;
|
|
||||||
},
|
|
||||||
notifier() {
|
|
||||||
if ( parseInt( this.lastDispatch ) + 5000 < new Date().getTime() ) {
|
|
||||||
|
|
||||||
}
|
|
||||||
Notification.requestPermission();
|
|
||||||
|
|
||||||
console.warn( '[ notifier ]: Status is now enabled \n\n-> Any leaving or tampering with the website will send a notification to the host' );
|
|
||||||
// Detect if window is currently in focus
|
|
||||||
window.onblur = () => {
|
|
||||||
this.sendNotification( 'blur' );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect if browser window becomes hidden (also with blur event)
|
|
||||||
document.onvisibilitychange = () => {
|
|
||||||
if ( document.visibilityState === 'hidden' ) {
|
|
||||||
this.sendNotification( 'visibility' );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
sendNotification( notification ) {
|
|
||||||
let fetchOptions = {
|
|
||||||
method: 'post',
|
|
||||||
body: JSON.stringify( { 'type': notification } ),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'charset': 'utf-8'
|
|
||||||
},
|
|
||||||
};
|
|
||||||
fetch( '/clientStatusUpdate', fetchOptions ).catch( err => {
|
|
||||||
console.error( err );
|
|
||||||
} );
|
|
||||||
|
|
||||||
new Notification( 'YOU ARE UNDER SURVEILLANCE', {
|
|
||||||
body: 'Please return to the original webpage immediately!',
|
|
||||||
requireInteraction: true,
|
|
||||||
} )
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.connect();
|
|
||||||
this.notifier();
|
|
||||||
// if ( this.visualizationSettings === 'mic' ) {
|
|
||||||
// this.micAudioHandler();
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
isPlaying( value ) {
|
|
||||||
if ( value ) {
|
|
||||||
this.startTimeTracker();
|
|
||||||
} else {
|
|
||||||
this.stopTimeTracker();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ).mount( '#app' );
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"vite.config.*",
|
"vite.config.*",
|
||||||
"vitest.config.*",
|
"vitest.config.*",
|
||||||
"cypress.config.*",
|
|
||||||
"nightwatch.conf.*",
|
"nightwatch.conf.*",
|
||||||
"playwright.config.*"
|
"playwright.config.*"
|
||||||
],
|
],
|
||||||
@@ -14,6 +13,6 @@
|
|||||||
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": ["node"]
|
"types": ["node", "jquery"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user