From 89e169b70a3ed6314d0f586979cb49bcfb8ac32c Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 3 May 2026 20:23:51 +0200 Subject: [PATCH] fix: prevent notification close on skip and background audio stall - playCurrent: set _trackChanging flag and update media session metadata before changing src, so the pause event fired by src-swap does not set playbackState='paused' and dismiss the Samsung cover screen notification - Remove stalled->load() handler that silently paused audio with no recovery - Error handler now retries regardless of paused state (load() causes paused) - Add .catch() to audio.play() in playCurrent Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 578 ++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- public/icon-192.png | Bin 0 -> 6776 bytes public/icon-512.png | Bin 0 -> 21447 bytes scripts/gen-icons.mjs | 19 ++ src/main.js | 11 +- src/player.js | 28 +- src/state.js | 1 + 8 files changed, 632 insertions(+), 9 deletions(-) create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 scripts/gen-icons.mjs diff --git a/package-lock.json b/package-lock.json index 90d8d2f..e9bbff1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,21 @@ "": { "name": "novasonic", "devDependencies": { + "sharp": "^0.34.5", "vite": "^5.4.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -400,6 +412,496 @@ "node": ">=12" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", @@ -757,6 +1259,16 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -911,6 +1423,64 @@ "fsevents": "~2.3.2" } }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -921,6 +1491,14 @@ "node": ">=0.10.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", diff --git a/package.json b/package.json index 2a604e2..1525efe 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,11 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "icons": "node scripts/gen-icons.mjs" }, "devDependencies": { + "sharp": "^0.34.5", "vite": "^5.4.0" } } diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..ed27106fab67d9adda3a041f6eafce0fb7d5cdf9 GIT binary patch literal 6776 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%0DQwfNQwpF; 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 { S.audio.addEventListener('timeupdate', updateProgress); S.audio.addEventListener('ended', onEnded); S.audio.addEventListener('play', () => { + S._trackChanging = false; absInterrupt(); setPlayIcon(true); document.getElementById('player-art').classList.add('spinning'); @@ -134,16 +135,16 @@ window.addEventListener('load', () => { S.audio.addEventListener('pause', () => { setPlayIcon(false); document.getElementById('player-art').classList.remove('spinning'); - if(navigator.mediaSession) navigator.mediaSession.playbackState = 'paused'; + // Suppress during track change — src swap fires pause before the new track starts + if(!S._trackChanging && navigator.mediaSession) navigator.mediaSession.playbackState = 'paused'; }); S.previewAudio.addEventListener('timeupdate', updatePreviewProgress); S.previewAudio.addEventListener('ended', stopPreview); - // Recovery: try to resume if audio stalls (network hiccup, background throttle) - S.audio.addEventListener('stalled', () => { if(!S.audio.paused) S.audio.load(); }); - S.audio.addEventListener('waiting', () => { /* let browser handle buffering */ }); + // stalled = browser buffer ran out briefly; let the browser retry naturally. + // The watchdog below catches genuine long freezes. S.audio.addEventListener('error', () => { - if(!S.audio.paused && S.queue[S.qIdx]) { + if(S.queue[S.qIdx]) { const t = setTimeout(() => { S.audio.load(); S.audio.play().catch(()=>{}); }, 2000); S.audio.addEventListener('play', () => clearTimeout(t), { once: true }); } diff --git a/src/player.js b/src/player.js index 70fc830..4121130 100644 --- a/src/player.js +++ b/src/player.js @@ -17,11 +17,12 @@ export function playQueue(songs, startIdx) { export function playCurrent() { if(S.qIdx < 0 || S.qIdx >= S.queue.length) return; const s = S.queue[S.qIdx]; - S.audio.src = streamUrl(s.id); - S.audio.play(); - updatePlayerUI(s); + S._trackChanging = true; + updatePlayerUI(s); // update metadata/notification BEFORE src change onNpSongChange(s.id); renderQueue(); + S.audio.src = streamUrl(s.id); + S.audio.play().catch(() => {}); } export function updatePlayerUI(s) { @@ -122,6 +123,8 @@ export function setPlayIcon(playing) { const cover = document.getElementById('np-cover'); if(cover) cover.classList.toggle('playing', playing); } + // Android native MediaSession bridge + if (window.Android) { try { window.Android.setPlaybackState(playing); } catch(_) {} } } export function prevSong() { @@ -193,9 +196,16 @@ export async function fillRadioQueue() { } // ─── PROGRESS & SCRUBBING ─── +let _androidPosTick = 0; export function updateProgress() { if(!S.audio.duration || S._scrubbing) return; syncMediaSessionPosition(); + // Update Android native position every ~5 seconds to keep lock screen scrubber in sync + const now = Date.now(); + if(now - _androidPosTick > 5000) { + _androidPosTick = now; + if (window.Android) { try { window.Android.updatePosition(S.audio.currentTime, S.audio.duration); } catch(_) {} } + } const p = (S.audio.currentTime / S.audio.duration) * 100; document.getElementById('progress-fill').style.width = p + '%'; document.getElementById('time-cur').textContent = formatDur(S.audio.currentTime); @@ -279,6 +289,18 @@ export function setVolume(v) { // ─── MEDIA SESSION ─── export function updateMediaSession(s) { + // Android native MediaSession bridge — use 512px art for high-DPI screens + if (window.Android) { + try { + window.Android.updateTrack( + s.title || '', + s.artist || '', + s.coverArt ? coverUrl(s.coverArt, 512) : '', + s.duration || 0 + ); + } catch(_) {} + } + if(!navigator.mediaSession) return; const artwork = s.coverArt ? [ { src: coverUrl(s.coverArt,96), sizes:'96x96', type:'image/jpeg' }, diff --git a/src/state.js b/src/state.js index 51edff9..100293e 100644 --- a/src/state.js +++ b/src/state.js @@ -13,6 +13,7 @@ export const S = { previewCurrent: null, _scrubbing: false, _msPosLast: 0, + _trackChanging: false, queueOpen: false, // Now Playing overlay