Settings and video backgrounds, bug fixes

This commit is contained in:
danbulant 2020-09-19 18:33:29 +02:00
parent 86a45a90ae
commit 98c9cff4b5
14 changed files with 2545 additions and 530 deletions

179
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "osu",
"version": "1.0.0",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -804,7 +804,6 @@
"version": "6.12.5",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
"integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@ -930,6 +929,11 @@
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true
},
"atomically": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.3.2.tgz",
"integrity": "sha512-MAiqx5ir1nOoMeG2vLXJnj4oFROJYB1hMqa2aAo6GQVIkPdkIcrq9W9SR0OaRtvEowO7Y2bsXqKFuDMTO4iOAQ=="
},
"author-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz",
@ -1509,6 +1513,23 @@
}
}
},
"conf": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/conf/-/conf-7.1.2.tgz",
"integrity": "sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==",
"requires": {
"ajv": "^6.12.2",
"atomically": "^1.3.1",
"debounce-fn": "^4.0.0",
"dot-prop": "^5.2.0",
"env-paths": "^2.2.0",
"json-schema-typed": "^7.0.3",
"make-dir": "^3.1.0",
"onetime": "^5.1.0",
"pkg-up": "^3.1.0",
"semver": "^7.3.2"
}
},
"config-chain": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
@ -1605,6 +1626,21 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
},
"debounce-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
"integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
"requires": {
"mimic-fn": "^3.0.0"
},
"dependencies": {
"mimic-fn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="
}
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -1695,6 +1731,30 @@
"dev": true,
"optional": true
},
"discord-rpc": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/discord-rpc/-/discord-rpc-3.1.4.tgz",
"integrity": "sha512-QaBu+gHica2SzgRAmTpuJ4J8DX9+fDwAqhvaie3hcbkU9WPqewEPh21pWdd/7vTI/JNuapU7PFm2ZKg3BTkbGg==",
"requires": {
"node-fetch": "^2.6.1",
"ws": "^7.3.1"
},
"dependencies": {
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
}
}
},
"dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
"requires": {
"is-obj": "^2.0.0"
}
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@ -2038,6 +2098,22 @@
"debug": "^2.2.0"
}
},
"electron-store": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-6.0.0.tgz",
"integrity": "sha512-ujb0a/6gxMxb9vOQ2BjOehK9VCyq5OKvttekd9v/tohA9oBHnAdV+Vxu4eoRh+/F9ShPFhcvDZkMdqO5i+TXUw==",
"requires": {
"conf": "^7.1.1",
"type-fest": "^0.16.0"
},
"dependencies": {
"type-fest": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
"integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="
}
}
},
"electron-winstaller": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-4.0.1.tgz",
@ -2159,8 +2235,7 @@
"env-paths": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
"dev": true
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA=="
},
"error-ex": {
"version": "1.3.2",
@ -2335,14 +2410,12 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"fd-slicer": {
"version": "1.1.0",
@ -3041,6 +3114,11 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
@ -3164,8 +3242,12 @@
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-schema-typed": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
"integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
},
"json-stringify-safe": {
"version": "5.0.1",
@ -3341,6 +3423,21 @@
"sourcemap-codec": "^1.4.4"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"requires": {
"semver": "^6.0.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@ -3505,8 +3602,7 @@
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"mimic-response": {
"version": "1.0.1",
@ -3596,8 +3692,7 @@
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-gyp": {
"version": "7.1.0",
@ -3749,7 +3844,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
@ -4040,6 +4134,54 @@
}
}
},
"pkg-up": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
"requires": {
"find-up": "^3.0.0"
},
"dependencies": {
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
}
}
},
"plist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz",
@ -4124,8 +4266,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"qs": {
"version": "6.5.2",
@ -4445,8 +4586,7 @@
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"semver-compare": {
"version": "1.0.0",
@ -5042,7 +5182,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
"integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}

View file

@ -1,7 +1,7 @@
{
"name": "osu",
"productName": "osu",
"version": "0.1.0",
"version": "0.2.0",
"description": "Osu! visualizer",
"main": "src/index.js",
"scripts": {
@ -48,9 +48,11 @@
},
"dependencies": {
"concurrently": "^5.3.0",
"discord-rpc": "^3.1.4",
"electron-is-dev": "^1.2.0",
"electron-reload": "^1.5.0",
"electron-squirrel-startup": "^1.0.0",
"electron-store": "^6.0.0",
"osu-db-parser": "^1.0.35",
"osu-parser": "^0.3.3",
"sirv-cli": "^1.0.0",

View file

@ -1,5 +1,6 @@
main.svelte-5atqf{position:relative;width:100vw;height:100vh}.background.svelte-5atqf{position:fixed;z-index:0;left:0;right:0;width:100vw;height:100vh}.menu.svelte-5atqf{position:absolute;z-index:1;left:0;right:0;width:100vw;height:100vh}
.info.svelte-1j9fr45.svelte-1j9fr45{opacity:1;position:relative;top:0;left:0;width:100vw;height:80px;transition:opacity 0.6s;z-index:1}.volume.svelte-1j9fr45.svelte-1j9fr45{opacity:1;position:fixed;z-index:2;right:0;bottom:0;border-radius:50%;color:black;font-size:30px}.hidden.svelte-1j9fr45.svelte-1j9fr45{opacity:0;transition:opacity 1s}.info.svelte-1j9fr45 .song.svelte-1j9fr45{color:white;position:absolute;padding:5px 5px 5px 25px;top:0;right:0;text-align:right;background:black;background:linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.5) 15%, rgba(0,0,0,0.5) 100%)}.info.svelte-1j9fr45 .song h2.svelte-1j9fr45{margin:0}.info.svelte-1j9fr45 .controls.svelte-1j9fr45{height:50px;display:flex}.info.svelte-1j9fr45 .controls div.svelte-1j9fr45{height:100%}.info.svelte-1j9fr45 .controls img.svelte-1j9fr45{height:100%;filter:invert(100%)}
.main.svelte-yh70k3.svelte-yh70k3{width:100%;height:100%;background-size:cover;background-repeat:no-repeat}@keyframes svelte-yh70k3-bpm{from{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}to{width:525px;height:525px;top:calc(50vh - 262.5px);left:calc(50vw - 262.5px)}}@keyframes svelte-yh70k3-bpmShadow{0%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}70%{width:510px;height:510px;top:calc(50vh - 255px);left:calc(50vw - 255px)}100%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}}.main.svelte-yh70k3 img.svelte-yh70k3{position:fixed;width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}.main.svelte-yh70k3 .logo.svelte-yh70k3{animation-name:svelte-yh70k3-bpm;animation-iteration-count:infinite;animation-direction:alternate}.main.svelte-yh70k3 .shadow.svelte-yh70k3{opacity:0.2;animation-name:svelte-yh70k3-bpmShadow;animation-iteration-count:infinite;animation-delay:50ms}
.info.svelte-1qt5obi.svelte-1qt5obi{opacity:1;position:relative;top:0;left:0;width:100vw;height:80px;transition:opacity 0.6s;z-index:2}.volume.svelte-1qt5obi.svelte-1qt5obi{opacity:1;position:fixed;z-index:5;right:0;bottom:0;border-radius:50%;font-size:30px;color:white;background-color:black;width:100px;height:100px}.volume.svelte-1qt5obi .slider.svelte-1qt5obi{position:relative;top:0;left:0;width:100%;height:100%}.percent.svelte-1qt5obi.svelte-1qt5obi{position:absolute;top:25px;left:0;width:100%;height:100%;text-align:center}.progress-ring.svelte-1qt5obi.svelte-1qt5obi{position:absolute;top:0;left:0;width:100%;height:100%}.progress-ring.svelte-1qt5obi circle.svelte-1qt5obi{transition:stroke-dashoffset 0.32s;transform:rotate(-90deg);transform-origin:50% 50%;position:absolute;top:1px;left:1px;width:100%;height:100%}.hidden.svelte-1qt5obi.svelte-1qt5obi{opacity:0;transition:opacity 1s}.info.svelte-1qt5obi .song.svelte-1qt5obi{color:white;position:absolute;padding:5px 5px 5px 25px;top:0;right:0;text-align:right;background:black;background:linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.5) 15%, rgba(0,0,0,0.5) 100%)}.info.svelte-1qt5obi .song h2.svelte-1qt5obi{margin:0}.info.svelte-1qt5obi .controls.svelte-1qt5obi{height:50px;display:flex}.info.svelte-1qt5obi .controls div.svelte-1qt5obi{height:100%}.info.svelte-1qt5obi .controls img.svelte-1qt5obi{height:100%;filter:invert(100%)}.info.svelte-1qt5obi .controls .settings img.svelte-1qt5obi{height:65%;padding-top:25%}
.main.svelte-18bmol8.svelte-18bmol8{width:100%;height:100%;background-size:cover;background-repeat:no-repeat}@keyframes svelte-18bmol8-bpm{from{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}to{width:525px;height:525px;top:calc(50vh - 262.5px);left:calc(50vw - 262.5px)}}@keyframes svelte-18bmol8-bpmShadow{0%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}70%{width:510px;height:510px;top:calc(50vh - 255px);left:calc(50vw - 255px)}100%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}}video.svelte-18bmol8.svelte-18bmol8{position:fixed;z-index:0;top:0;left:0;width:100vw;height:100vh}.main.svelte-18bmol8 img.svelte-18bmol8{position:fixed;width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px);z-index:1}.main.svelte-18bmol8 .logo.svelte-18bmol8{animation-name:svelte-18bmol8-bpm;animation-direction:alternate}.main.svelte-18bmol8 .shadow.svelte-18bmol8{opacity:0.2;animation-name:svelte-18bmol8-bpmShadow;animation-delay:50ms}.main.svelte-18bmol8 .repeat.svelte-18bmol8{animation-iteration-count:infinite}
.bg.svelte-1895ym0{position:fixed;display:none;width:100vw;height:100vh;top:0;left:0;z-index:3}.bg.visible.svelte-1895ym0{display:block}nav.svelte-1895ym0{position:fixed;height:100vh;width:400px;top:0;left:-400px;opacity:0;background:rgba(0,0,0,0.4);color:white;z-index:4;transition:opacity 0.3s, left 0.3s}nav.visible.svelte-1895ym0{left:0;opacity:1}
/*# sourceMappingURL=bundle.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="25px" height="25.001px" viewBox="0 0 25 25.001" style="enable-background:new 0 0 25 25.001;" xml:space="preserve">
<g>
<path d="M24.38,10.175l-2.231-0.268c-0.228-0.851-0.562-1.655-0.992-2.401l1.387-1.763c0.212-0.271,0.188-0.69-0.057-0.934
l-2.299-2.3c-0.242-0.243-0.662-0.269-0.934-0.057l-1.766,1.389c-0.743-0.43-1.547-0.764-2.396-0.99L14.825,0.62
C14.784,0.279,14.469,0,14.125,0h-3.252c-0.344,0-0.659,0.279-0.699,0.62L9.906,2.851c-0.85,0.227-1.655,0.562-2.398,0.991
L5.743,2.455c-0.27-0.212-0.69-0.187-0.933,0.056L2.51,4.812C2.268,5.054,2.243,5.474,2.456,5.746L3.842,7.51
c-0.43,0.744-0.764,1.549-0.991,2.4l-2.23,0.267C0.28,10.217,0,10.532,0,10.877v3.252c0,0.344,0.279,0.657,0.621,0.699l2.231,0.268
c0.228,0.848,0.561,1.652,0.991,2.396l-1.386,1.766c-0.211,0.271-0.187,0.69,0.057,0.934l2.296,2.301
c0.243,0.242,0.663,0.269,0.933,0.057l1.766-1.39c0.744,0.43,1.548,0.765,2.398,0.991l0.268,2.23
c0.041,0.342,0.355,0.62,0.699,0.62h3.252c0.345,0,0.659-0.278,0.699-0.62l0.268-2.23c0.851-0.228,1.655-0.562,2.398-0.991
l1.766,1.387c0.271,0.212,0.69,0.187,0.933-0.056l2.299-2.301c0.244-0.242,0.269-0.662,0.056-0.935l-1.388-1.764
c0.431-0.744,0.764-1.548,0.992-2.397l2.23-0.268C24.721,14.785,25,14.473,25,14.127v-3.252
C25.001,10.529,24.723,10.216,24.38,10.175z M12.501,18.75c-3.452,0-6.25-2.798-6.25-6.25s2.798-6.25,6.25-6.25
s6.25,2.798,6.25,6.25S15.954,18.75,12.501,18.75z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

515
public/lib/osu-parser.js Normal file
View file

@ -0,0 +1,515 @@
var fs = require('fs');
var slidercalc = require('osu-parser/lib/slidercalc.js');
function beatmapParser() {
var beatmap = {
nbCircles: 0,
nbSliders: 0,
nbSpinners: 0,
timingPoints: [],
breakTimes: [],
hitObjects: []
};
var osuSection;
var bpmMin;
var bpmMax;
var members;
var timingLines = [];
var objectLines = [];
var eventsLines = [];
var sectionReg = /^\[([a-zA-Z0-9]+)\]$/;
var keyValReg = /^([a-zA-Z0-9]+)[ ]*:[ ]*(.+)$/;
var curveTypes = {
C: "catmull",
B: "bezier",
L: "linear",
P: "pass-through"
};
/**
* Get the timing point affecting a specific offset
* @param {Integer} offset
* @return {Object} timingPoint
*/
var getTimingPoint = function (offset) {
for (var i = beatmap.timingPoints.length - 1; i >= 0; i--) {
if (beatmap.timingPoints[i].offset <= offset) { return beatmap.timingPoints[i]; }
}
return beatmap.timingPoints[0];
};
/**
* Parse additions member
* @param {String} str additions member (sample:add:customSampleIndex:Volume:hitsound)
* @return {Object} additions a list of additions
*/
var parseAdditions = function (str) {
if (!str) return {};
var additions = {};
var adds = str.split(':');
if (adds[0] && adds[0] !== '0') {
var sample;
switch (adds[0]) {
case '1':
sample = 'normal';
break;
case '2':
sample = 'soft';
break;
case '3':
sample = 'drum';
break;
}
additions.sample = sample;
}
if (adds[1] && adds[1] !== '0') {
var addSample;
switch (adds[1]) {
case '1':
addSample = 'normal';
break;
case '2':
addSample = 'soft';
break;
case '3':
addSample = 'drum';
break;
}
additions.additionalSample = addSample;
}
if (adds[2] && adds[2] !== '0') { additions.customSampleIndex = parseInt(adds[2]); }
if (adds[3] && adds[3] !== '0') { additions.hitsoundVolume = parseInt(adds[3]); }
if (adds[4]) { additions.hitsound = adds[4]; }
return additions;
};
/**
* Parse a timing line
* @param {String} line
*/
var parseTimingPoint = function (line) {
members = line.split(',');
var timingPoint = {
offset: parseInt(members[0]),
beatLength: parseFloat(members[1]),
velocity: 1,
timingSignature: parseInt(members[2]),
sampleSetId: parseInt(members[3]),
customSampleIndex: parseInt(members[4]),
sampleVolume: parseInt(members[5]),
timingChange: (members[6] == 1),
kiaiTimeActive: (members[7] == 1)
};
if (!isNaN(timingPoint.beatLength) && timingPoint.beatLength !== 0) {
if (timingPoint.beatLength > 0) {
// If positive, beatLength is the length of a beat in milliseconds
var bpm = Math.round(60000 / timingPoint.beatLength);
beatmap.bpmMin = beatmap.bpmMin ? Math.min(beatmap.bpmMin, bpm) : bpm;
beatmap.bpmMax = beatmap.bpmMax ? Math.max(beatmap.bpmMax, bpm) : bpm;
timingPoint.bpm = bpm;
} else {
// If negative, beatLength is a velocity factor
timingPoint.velocity = Math.abs(100 / timingPoint.beatLength);
}
}
beatmap.timingPoints.push(timingPoint);
};
/**
* Parse an object line
* @param {String} line
*/
var parseHitObject = function (line) {
members = line.split(',');
var soundType = members[4];
var objectType = members[3];
var hitObject = {
startTime: parseInt(members[2]),
newCombo: ((objectType & 4) == 4),
soundTypes: [],
position: [
parseInt(members[0]),
parseInt(members[1])
]
};
/**
* sound type is a bitwise flag enum
* 0 : normal
* 2 : whistle
* 4 : finish
* 8 : clap
*/
if ((soundType & 2) == 2) { hitObject.soundTypes.push('whistle'); }
if ((soundType & 4) == 4) { hitObject.soundTypes.push('finish'); }
if ((soundType & 8) == 8) { hitObject.soundTypes.push('clap'); }
if (hitObject.soundTypes.length === 0) { hitObject.soundTypes.push('normal'); }
/**
* object type is a bitwise flag enum
* 1: circle
* 2: slider
* 8: spinner
*/
if ((objectType & 1) == 1) {
// Circle
beatmap.nbCircles++;
hitObject.objectName = 'circle';
hitObject.additions = parseAdditions(members[5]);
} else if ((objectType & 8) == 8) {
// Spinner
beatmap.nbSpinners++;
hitObject.objectName = 'spinner';
hitObject.endTime = parseInt(members[5]);
hitObject.additions = parseAdditions(members[6]);
} else if ((objectType & 2) == 2) {
// Slider
beatmap.nbSliders++;
hitObject.objectName = 'slider';
hitObject.repeatCount = parseInt(members[6]);
hitObject.pixelLength = parseInt(members[7]);
hitObject.additions = parseAdditions(members[10]);
hitObject.edges = [];
hitObject.points = [
[hitObject.position[0], hitObject.position[1]]
];
/**
* Calculate slider duration
*/
var timing = getTimingPoint(hitObject.startTime);
if (timing) {
var pxPerBeat = beatmap.SliderMultiplier * 100 * timing.velocity;
var beatsNumber = (hitObject.pixelLength * hitObject.repeatCount) / pxPerBeat;
hitObject.duration = Math.ceil(beatsNumber * timing.beatLength);
hitObject.endTime = hitObject.startTime + hitObject.duration;
}
/**
* Parse slider points
*/
var points = (members[5] || '').split('|');
if (points.length) {
hitObject.curveType = curveTypes[points[0]] || 'unknown';
for (var i = 1, l = points.length; i < l; i++) {
var coordinates = points[i].split(':');
hitObject.points.push([
parseInt(coordinates[0]),
parseInt(coordinates[1])
]);
}
}
var edgeSounds = [];
var edgeAdditions = [];
if (members[8]) { edgeSounds = members[8].split('|'); }
if (members[9]) { edgeAdditions = members[9].split('|'); }
/**
* Get soundTypes and additions for each slider edge
*/
for (var j = 0, lgt = hitObject.repeatCount + 1; j < lgt; j++) {
var edge = {
soundTypes: [],
additions: parseAdditions(edgeAdditions[j])
};
if (edgeSounds[j]) {
var sound = edgeSounds[j];
if ((sound & 2) == 2) { edge.soundTypes.push('whistle'); }
if ((sound & 4) == 4) { edge.soundTypes.push('finish'); }
if ((sound & 8) == 8) { edge.soundTypes.push('clap'); }
if (edge.soundTypes.length === 0) { edge.soundTypes.push('normal'); }
} else {
edge.soundTypes.push('normal');
}
hitObject.edges.push(edge);
}
// get coordinates of the slider endpoint
var endPoint = slidercalc.getEndPoint(hitObject.curveType, hitObject.pixelLength, hitObject.points);
if (endPoint && endPoint[0] && endPoint[1]) {
hitObject.endPosition = [
Math.round(endPoint[0]),
Math.round(endPoint[1])
];
} else {
// If endPosition could not be calculated, approximate it by setting it to the last point
hitObject.endPosition = hitObject.points[hitObject.points.length - 1];
}
} else {
// Unknown
hitObject.objectName = 'unknown';
}
beatmap.hitObjects.push(hitObject);
};
/**
* Parse an event line
* @param {String} line
*/
var parseEvent = function (line) {
/**
* Background line : 0,0,"bg.jpg"
* TODO: confirm that the second member is always zero
*
* Breaktimes lines : 2,1000,2000
* second integer is start offset
* third integer is end offset
*/
members = line.split(',');
if (members[0] == '0' && members[1] == '0' && members[2]) {
var bgName = members[2].trim();
if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
beatmap.bgFilename = bgName.substring(1, bgName.length - 1);
} else {
beatmap.bgFilename = bgName;
}
} else if (members[0] == 'Video' && members[2]) {
var bgName = members[2].trim();
if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
beatmap.video = bgName.substring(1, bgName.length - 1);
} else {
beatmap.video = bgName;
}
} else if (members[0] == '2' && /^[0-9]+$/.test(members[1]) && /^[0-9]+$/.test(members[2])) {
beatmap.breakTimes.push({
startTime: parseInt(members[1]),
endTime: parseInt(members[2])
});
}
};
/**
* Compute the total time and the draining time of the beatmap
*/
var computeDuration = function () {
var firstObject = beatmap.hitObjects[0];
var lastObject = beatmap.hitObjects[beatmap.hitObjects.length - 1];
var totalBreakTime = 0;
beatmap.breakTimes.forEach(function (breakTime) {
totalBreakTime += (breakTime.endTime - breakTime.startTime);
});
if (firstObject && lastObject) {
beatmap.totalTime = Math.floor(lastObject.startTime / 1000);
beatmap.drainingTime = Math.floor((lastObject.startTime - firstObject.startTime - totalBreakTime) / 1000);
} else {
beatmap.totalTime = 0;
beatmap.drainingTime = 0;
}
};
/**
* Browse objects and compute max combo
*/
var computeMaxCombo = function () {
if (beatmap.timingPoints.length === 0) { return; }
var maxCombo = 0;
var sliderMultiplier = parseFloat(beatmap.SliderMultiplier);
var sliderTickRate = parseInt(beatmap.SliderTickRate, 10);
var timingPoints = beatmap.timingPoints;
var currentTiming = timingPoints[0];
var nextOffset = timingPoints[1] ? timingPoints[1].offset : Infinity;
var i = 1;
beatmap.hitObjects.forEach(function (hitObject) {
if (hitObject.startTime >= nextOffset) {
currentTiming = timingPoints[i++];
nextOffset = timingPoints[i] ? timingPoints[i].offset : Infinity;
}
var osupxPerBeat = sliderMultiplier * 100 * currentTiming.velocity;
var tickLength = osupxPerBeat / sliderTickRate;
switch (hitObject.objectName) {
case 'spinner':
case 'circle':
maxCombo++;
break;
case 'slider':
var tickPerSide = Math.ceil((Math.floor(hitObject.pixelLength / tickLength * 100) / 100) - 1);
maxCombo += (hitObject.edges.length - 1) * (tickPerSide + 1) + 1; // 1 combo for each tick and endpoint
}
});
beatmap.maxCombo = maxCombo;
};
/**
* Read a single line, parse when key/value, store when further parsing needed
* @param {String|Buffer} line
*/
var readLine = function (line) {
line = line.toString().trim();
if (!line) { return; }
var match = sectionReg.exec(line);
if (match) {
osuSection = match[1].toLowerCase();
return;
}
if(line.startsWith("//")) return;
switch (osuSection) {
case 'timingpoints':
timingLines.push(line);
break;
case 'hitobjects':
objectLines.push(line);
break;
case 'events':
eventsLines.push(line);
break;
default:
if (!osuSection) {
match = /^osu file format (v[0-9]+)$/.exec(line);
if (match) {
beatmap.fileFormat = match[1];
return;
}
}
/**
* Apart from events, timingpoints and hitobjects sections, lines are "key: value"
*/
match = keyValReg.exec(line);
if (match) { beatmap[match[1]] = match[2]; }
}
};
/**
* Compute everything that require the file to be completely parsed and return the beatmap
* @return {Object} beatmap
*/
var buildBeatmap = function () {
if (beatmap.Tags) {
beatmap.tagsArray = beatmap.Tags.split(' ');
}
eventsLines.forEach(parseEvent);
beatmap.breakTimes.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
timingLines.forEach(parseTimingPoint);
beatmap.timingPoints.sort(function (a, b) { return (a.offset > b.offset ? 1 : -1); });
var timingPoints = beatmap.timingPoints;
for (var i = 1, l = timingPoints.length; i < l; i++) {
if (!timingPoints[i].hasOwnProperty('bpm')) {
timingPoints[i].beatLength = timingPoints[i - 1].beatLength;
timingPoints[i].bpm = timingPoints[i - 1].bpm;
}
}
objectLines.forEach(parseHitObject);
beatmap.hitObjects.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
computeMaxCombo();
computeDuration();
return beatmap;
};
return {
readLine: readLine,
buildBeatmap: buildBeatmap
};
}
/**
* Parse a .osu file
* @param {String} file path to the file
* @param {Function} callback(err, beatmap)
*/
exports.parseFile = function (file, callback) {
if (!fs.existsSync(file)) {
callback(new Error('File does not exist'));
return;
}
var parser = beatmapParser();
var stream = fs.createReadStream(file);
var buffer = '';
stream.on('data', function (chunk) {
buffer += chunk;
var lines = buffer.split(/\r?\n/);
buffer = lines.pop() || '';
lines.forEach(parser.readLine);
});
stream.on('error', function (err) {
callback(err);
});
stream.on('end', function () {
buffer.split(/\r?\n/).forEach(parser.readLine);
callback(null, parser.buildBeatmap());
});
};
/**
* Parse a stream containing .osu content
* @param {Stream} stream
* @param {Function} callback(err, beatmap)
*/
exports.parseStream = function (stream, callback) {
var parser = beatmapParser();
var buffer = '';
stream.on('data', function (chunk) {
buffer += chunk.toString();
var lines = buffer.split(/\r?\n/);
buffer = lines.pop() || '';
lines.forEach(parser.readLine);
});
stream.on('error', function (err) {
callback(err);
});
stream.on('end', function () {
buffer.split(/\r?\n/).forEach(parser.readLine);
callback(null, parser.buildBeatmap());
});
};
/**
* Parse the content of a .osu
* @param {String|Buffer} content
* @return {Object} beatmap
*/
exports.parseContent = function (content) {
var parser = beatmapParser();
content.toString().split(/[\n\r]+/).forEach(function (line) {
parser.readLine(line);
});
return parser.buildBeatmap();
};

View file

@ -1,18 +1,59 @@
<script>
import Menu from "./Menu.svelte";
import Visualizer from "./Visualizer.svelte";
const Store = require('electron-store');
const store = new Store();
var songData = {};
var config = store.get("config");
var osuData = {};
(() => {
const configTemplate = {
parallax: {
enabled: true,
treshold: 10
},
rpc: true,
backgrounds: 0,
mediaSession: true,
videoBackground: true,
autohide: {
info: 2000,
volume: 2000
}
};
function checkSettings(value, template) {
if(value === undefined) return template;
if(typeof value !== "object") return value;
var out = {};
for(var key in template) {
if(value[key] === undefined || typeof value[key] === "undefined") {
out[key] = template[key];
continue;
}
if(typeof value[key] === "object") out[key] = checkSettings(value[key], template[key]);
if(typeof value[key] !== "object") out[key] = value[key];
}
return out;
}
config = checkSettings(config, configTemplate);
})();
$: store.set("config", config);
</script>
<main>
<div class="background">
<Visualizer bind:songData bind:osuData/>
<Visualizer bind:songData bind:osuData {config} />
</div>
<div class="menu">
<Menu bind:song={songData} bind:osuData/>
<Menu bind:song={songData} bind:osuData bind:config />
</div>
</main>

View file

@ -1,17 +1,20 @@
<script>
import Options from "./components/options.svelte";
const fs = require("fs");
const osuParser = require("./lib/osu-parser.js");
export var osuData;
export var song;
export var config;
var last = Date.now();
var lastVolumeUpdate = Date.now() - 5000;
var dialogActive = false;
var settingsOpen = false;
var now = Date.now();
setInterval(() => {
now = Date.now();
}, 500);
var playing = true;
}, 800);
function resetPool() {
if(!osuData.songs) return false;
@ -21,6 +24,11 @@
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
osuData.songPool.forEach(v => {
delete v.audio;
delete v.video;
v.playing = true;
})
song = osuData.songPool.shift();
}
@ -40,85 +48,143 @@
$: console.log(song);
$: {
if(song && song.folder && !song.audio) {
song.audio = new Audio(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + song.folder + "/" + song.audioFile);
song.audio.play();
(() => {
if(song && song.folder && !song.audio) {
// var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// song.context = audioCtx;
// song.analyser = audioCtx.createAnalyser();
song.audio = new Audio(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + song.folder + "/" + song.audioFile);
// song.source = audioCtx.createMediaElementSource(song.audio);
// song.source.connect(song.analyser);
// song.analyser.connect(audioCtx.destination);
song.audio.play();
song.audio.onended = () => {
playNext();
}
song.audio.onpause = () => {
playing = false;
}
song.audio.onplay = () => {
playing = true;
}
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: song.song,
artist: song.artist,
album: "Osu! visualizer",
artwork: [
{ src: process.env.USERPROFILE + "/AppData/Local/osu!/Data/bt/" + song.id + ".jpg", type: 'image/jpeg' },
]
});
song.audio.onended = () => {
playNext();
}
song.audio.onpause = () => {
song.playing = false;
if(song.video) song.video.pause();
}
song.audio.onplay = () => {
song.playing = true;
if(song.video) song.video.play();
}
if ('mediaSession' in navigator && config.mediaSession) {
navigator.mediaSession.metadata = new MediaMetadata({
title: song.song,
artist: song.artist,
album: "Osu! visualizer",
artwork: [
// { src: process.env.USERPROFILE + "/AppData/Local/osu!/Data/bt/" + song.id + ".jpg", type: 'image/jpeg' },
]
});
navigator.mediaSession.setActionHandler('play', function() { playing = true; song.audio.play(); });
navigator.mediaSession.setActionHandler('pause', function() { playing = false; song.audio.pause(); });
navigator.mediaSession.setActionHandler('nexttrack', function() { playNext()});
navigator.mediaSession.setActionHandler('play', function() { song.playing = true; song.audio.play(); });
navigator.mediaSession.setActionHandler('pause', function() { song.playing = false; song.audio.pause(); });
navigator.mediaSession.setActionHandler('nexttrack', function() { playNext()});
}
}
})();
}
$: if(song && song.audio && config.rpc) {
if(song.playing) {
window.songActivity = {
state: "Listening to osu! beatmaps",
details: `${song.artist} - ${song.song}`,
startTimestamp: Date.now(),
endTimestamp: Date.now() + song.audio.duration * 1000,
instance: false,
largeImageKey: "logo",
largeImageText: "Osu!visualizer"
}
} else {
window.songActivity = {
state: "Paused",
details: `${song.artist} - ${song.song}`,
instance: false,
largeImageKey: "logo",
largeImageText: "Osu!visualizer"
}
}
}
function togglePlay() {
playing = !playing;
if(playing) {
song.playing = !song.playing;
if(!song.audio) return;
if(song.playing) {
song.audio.play();
} else {
song.audio.pause();
}
}
var volume = 1;
function updateVolume(e) {
if(!song || !song.audio || !e.altKey) return;
lastVolumeUpdate = Date.now();
var volume = song.audio.volume;
volume += e.deltaY * -0.0005;
song.audio.volume = Math.min(1, Math.max(volume, 0));
volume = Math.min(1, Math.max(volume, 0));
}
$: if(song.audio) song.audio.volume = volume;
setTimeout(() => {
song = song;
playNext();
}, 200);
const volumeWidth = 100;
const volumeStroke = 4;
const volumeRadius = 50;
</script>
<svelte:window on:mousemove={() => last = Date.now()} on:wheel={e => updateVolume(e)} />
<div class="menu">
<div class="info" class:hidden={now - last > 2000}>
{#if song}
<div class="song">
<h2>{song.artist} - {song.song}</h2>
<div class="controls">
<div class="play" on:click={togglePlay}>
<img src="images/music_{playing ? "pause" : "play"}.svg" alt="{playing ? "Pause" : "Play"} music" title="{playing ? "Pause" : "Play"} music">
</div>
<div class="forward" on:click={playNext}>
<img src="images/music_forward.svg" alt="Skip the song" title="Skip the song">
{#if now - last < config.autohide.info + 1000}
<div class="info" class:hidden={now - last > config.autohide.info}>
{#if song}
<div class="song">
<h2>{song.artist} - {song.song}</h2>
<div class="controls">
<div class="play" on:click={togglePlay}>
<img src="images/music_{song.playing ? "pause" : "play"}.svg" alt="{song.playing ? "Pause" : "Play"} music" title="{song.playing ? "Pause" : "Play"} music">
</div>
<div class="forward" on:click={playNext}>
<img src="images/music_forward.svg" alt="Skip the song" title="Skip the song">
</div>
<div class="settings" on:click={() => settingsOpen = !settingsOpen}>
<img src="images/settings.svg" alt="Settings" title="Open settings">
</div>
</div>
</div>
</div>
{/if}
</div>
{#if now - lastVolumeUpdate < 4000 && song && song.audio}
<div class="volume" class:hidden={now - lastVolumeUpdate > 2000}>
{/if}
</div>
{/if}
{#if now - lastVolumeUpdate < config.autohide.volume + 1000 && song && song.audio}
<div class="volume" class:hidden={now - lastVolumeUpdate > config.autohide.volume}>
<div class="slider">
<div class="percent">
{Math.round(song.audio.volume * 100)}%
</div>
<svg class="progress-ring" width={volumeWidth} height={volumeWidth}>
<circle
stroke-width={volumeStroke}
fill="transparent"
stroke="blue"
stroke-dasharray={volumeRadius * 2 * Math.PI + " " + volumeRadius * 2 * Math.PI}
stroke-dashoffset={volumeRadius * 2 * Math.PI - song.audio.volume * volumeRadius * 2 * Math.PI}
r={volumeRadius - 1}
cx={volumeRadius - 1}
cy={volumeRadius + 1}
/>
</svg>
</div>
</div>
{/if}
<Options bind:config={config} bind:visible={settingsOpen} />
</div>
<style>
@ -130,18 +196,53 @@
width: 100vw;
height: 80px;
transition: opacity 0.6s;
z-index: 1;
z-index: 2;
}
.volume {
opacity: 1;
position: fixed;
z-index: 2;
z-index: 5;
right: 0;
bottom: 0;
border-radius: 50%;
color: black;
font-size: 30px;
color: white;
background-color: black;
width: 100px;
height: 100px;
}
.volume .slider {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.percent {
position: absolute;
top: 25px;
left: 0;
width: 100%;
height: 100%;
text-align: center;
}
.progress-ring {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.progress-ring circle {
transition: stroke-dashoffset 0.32s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
position: absolute;
top: 1px;
left: 1px;
width: 100%;
height: 100%;
}
.hidden {
@ -176,4 +277,8 @@
height: 100%;
filter: invert(100%);
}
.info .controls .settings img {
height: 65%;
padding-top: 25%;
}
</style>

View file

@ -1,10 +1,11 @@
<script>
import { createEventDispatcher } from 'svelte';
import { onMount } from 'svelte';
const fs = require("fs");
const OsuDBParser = require("osu-db-parser");
const osuParser = require("osu-parser");
const osuParser = require("./lib/osu-parser.js");
export var osuData;
export var songData;
export var config;
var wallpapers = [];
try {
@ -25,7 +26,8 @@
song: v.song_title,
song_u: v.song_title_unicode,
id: v.beatmapset_id,
dataFile: `${v.artist_name} - ${v.song_title} (${v.creator_name}) [${v.difficulty}].osu`
dataFile: `${v.artist_name} - ${v.song_title} (${v.creator_name}) [${v.difficulty}].osu`.replace(/\/|\*|"|:|\?/g, ""),
playing: true
})).filter((v, i, a) => a.findIndex(x => x.id === v.id) === i);
} catch(e) {
console.error("Osu DB weren't found. You must have osu installed and started at least once.", e);
@ -34,20 +36,43 @@
var wallpaper;
function shuffleWallpapers() {
wallpaper = wallpapers[Math.floor(Math.random() * wallpapers.length)];
switch(config.backgrounds) {
case 0:
wallpaper = `${process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Data/bg/${wallpapers[Math.floor(Math.random() * wallpapers.length)]}`;
break;
case 1:
if(songData.beatmap) {
wallpaper = `${process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Songs/${songData.folder}/${songData.beatmap.bgFilename}`;
} else {
wallpaper = `${process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Data/bg/${wallpapers[Math.floor(Math.random() * wallpapers.length)]}`;
}
break;
default:
wallpaper = `${process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Data/bg/${wallpapers[Math.floor(Math.random() * wallpapers.length)]}`;
}
}
shuffleWallpapers();
var lastSong = null;
var lastBackgroundOption = null;
$: {
if(songData !== lastSong) {
lastSong = songData;
shuffleWallpapers();
}
if(config.backgrounds !== lastBackgroundOption) {
lastBackgroundOption = config.backgrounds;
shuffleWallpapers();
}
}
function fetchBeatmap() {
let file = fs.readFileSync(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + songData.folder + "/" + songData.dataFile);
songData.beatmap = osuParser.parseContent(file);
if(config.backgrounds === 1) {
wallpaper = `${process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Songs/${songData.folder}/${songData.beatmap.bgFilename}`;
}
}
$: if(songData && songData.dataFile && !songData.beatmap) fetchBeatmap();
@ -56,9 +81,11 @@
y: 0.5
};
const parallaxTreshold = 10;
var parallaxTreshold;
$: parallaxTreshold = config.parallax.treshold;
function updateMouse(e) {
if(!config.parallax.enabled) return;
mouse = {
x: -(e.clientX / window.innerWidth) * parallaxTreshold - parallaxTreshold/2,
y: -(e.clientY / window.innerHeight) * parallaxTreshold - parallaxTreshold/2
@ -78,7 +105,7 @@
setInterval(() => {
if(!songData) return;
if(!songData.beatmap && songData.dataFile) fetchBeatmap();
if(!songData.beatmap) return;
if(!songData.beatmap || !songData.audio) return;
var tp = null;
for(var t of songData.beatmap.timingPoints) {
@ -94,6 +121,25 @@
animDuration = tp.beatLength/2;
kiaiTime = tp.kiaiTimeActive;
}, 50);
$: {
if(!songData || !songData.beatmap || !songData.beatmap.video || !config.videoBackground) window.backgroundVideo = null;
}
$: console.log("Wallpaper", wallpaper);
$: console.log("Beatmap", songData.beatmap);
var backgroundVideo;
$: {
if(backgroundVideo) {
songData.video = backgroundVideo;
if(songData && songData.audio && songData.video) {
songData.video.currentTime = songData.audio.currentTime;
}
}
}
</script>
<svelte:window on:mousemove={updateMouse} on:resize={resize} />
@ -101,13 +147,24 @@
<div
class="main"
style="
background-image: url('{process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Data/bg/{wallpaper}');
background-image: url('{wallpaper}');
background-size: {!isWidthSmaller ? `calc(100% + ${parallaxTreshold * 1.5}px) auto` : `auto calc(100% + ${parallaxTreshold * 1.5}px)`};
background-position: {mouse.x}px {mouse.y}px;
"
>
<img src="images/logo.svg" alt="logo" class="logo" style="animation-duration: {animDuration}ms;">
<img src="images/logo.svg" alt="" class="shadow" style="animation-duration: {animDuration * 2}ms;">
{#if songData && songData.beatmap && songData.beatmap.video && config.videoBackground}
<!-- svelte-ignore a11y-media-has-caption -->
<video bind:this={backgroundVideo} style="
width: {isWidthSmaller ? "auto" : `calc(100% + ${parallaxTreshold * 1.5}px)`};
height: {!isWidthSmaller ? "auto" : `calc(100% + ${parallaxTreshold * 1.5}px)`};
top: {mouse.y}px;
left: {mouse.x}px;
">
<source src="file:///{process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Songs/{songData.folder}/{songData.beatmap.video}">
</video>
{/if}
<img src="images/logo.svg" alt="logo" class="logo" style="animation-duration: {animDuration}ms;" class:repeat={songData.playing}>
<img src="images/logo.svg" alt="" class="shadow" style="animation-duration: {animDuration * 2}ms;" class:repeat={songData.playing}>
</div>
<style>
@ -154,24 +211,36 @@
}
}
video {
position: fixed;
z-index: 0;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.main img {
position: fixed;
width: 500px;
height: 500px;
top: calc(50vh - 250px);
left: calc(50vw - 250px);
z-index: 1;
}
.main .logo {
animation-name: bpm;
animation-iteration-count: infinite;
animation-direction: alternate;
}
.main .shadow {
opacity: 0.2;
animation-name: bpmShadow;
animation-iteration-count: infinite;
animation-delay: 50ms;
}
.main .repeat {
animation-iteration-count: infinite;
}
</style>

View file

@ -0,0 +1,89 @@
<script>
export var config;
export var visible;
$: console.log("Config", config);
</script>
<div class="options">
<div class="bg" class:visible={visible} on:click={() => visible = false}></div>
<nav class:visible={visible}>
<h2>Options</h2>
<div class="group">
<h3>Parallax</h3>
<div class="row">
<span>Enable parallax</span>
<input type="checkbox" bind:checked={config.parallax.enabled}>
</div>
<div class="row" class:enabled={config.parallax.enabled}>
<span>Parallax treshold</span>
<input type="range" min="1" max="30" bind:value={config.parallax.treshold}>
</div>
</div>
<div class="group">
<h3>Integrations</h3>
<div class="row">
<span>Discord Rich Presence</span>
<input type="checkbox" bind:checked={config.rpc}>
</div>
<div class="row">
<span>MediaSession (system-wide controls)</span>
<input type="checkbox" bind:checked={config.mediaSession}>
</div>
</div>
<div class="group">
<h3>Backgrounds</h3>
<select bind:value={config.backgrounds}>
<option value={0}>Osu!wallpapers</option>
<option value={1}>Beatmap wallpapers</option>
</select>
<div class="row">
<span>Video backgrounds</span>
<input type="checkbox" bind:checked={config.videoBackground}>
</div>
</div>
<div class="group">
<h3>UI</h3>
<div class="row">
<span>Song info hide timeout</span>
<input type="range" min="1000" max="15000" step="500" bind:value={config.autohide.info}>
</div>
<div class="row">
<span>Volume hide timeout</span>
<input type="range" min="1000" max="15000" step="500" bind:value={config.autohide.volume}>
</div>
</div>
</nav>
</div>
<style>
.bg {
position: fixed;
display: none;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 3;
}
.bg.visible {
display: block;
}
nav {
position: fixed;
height: 100vh;
width: 400px;
top: 0;
left: -400px;
opacity: 0;
background: rgba(0,0,0,0.4);
color: white;
z-index: 4;
transition: opacity 0.3s, left 0.3s;
}
nav.visible {
left: 0;
opacity: 1;
}
</style>

View file

@ -1,38 +1,43 @@
const { app, BrowserWindow } = require('electron');
const isDev = require('electron-is-dev');
const RPC = require("discord-rpc");
const rpc = new RPC.Client({ transport: "ipc" });
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
app.quit();
}
if(!isDev) require('update-electron-app')()
if (!isDev) require('update-electron-app')()
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
var mainWindow;
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
},
autoHideMenuBar: true
});
mainWindow.setMenu(null);
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
},
autoHideMenuBar: true
});
mainWindow.setMenu(null);
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, '../public/index.html'));
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, '../public/index.html'));
// Open the DevTools.
mainWindow.webContents.openDevTools();
// Open the DevTools.
if(isDev) mainWindow.webContents.openDevTools();
};
require('electron-reload')(__dirname, {
electron: path.join(__dirname, '../node_modules', '.bin', 'electron'),
awaitWriteFinish: true,
electron: path.join(__dirname, '../node_modules', '.bin', 'electron'),
awaitWriteFinish: true,
});
// This method will be called when Electron has finished
@ -44,18 +49,39 @@ app.on('ready', createWindow);
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
async function setActivity() {
if (!rpc || !mainWindow) {
return;
}
const activity = await mainWindow.webContents.executeJavaScript('window.songActivity');
rpc.setActivity(activity);
}
rpc.on('ready', () => {
setActivity();
// activity can only be set every 15 seconds
setInterval(() => {
setActivity();
}, 15e3);
});
rpc.login({ clientId: "756806736106618951" });