A new full-screen, touch-first edition of the player aimed at phones through tablets - no native app, just a web page you can "Add to Home Screen". Reuses the shared engine + look-ahead scheduler (same player loop as player.html); new UI is a big pulsing beat display, beat-dot row with accent grouping, huge BPM (tap to type, vertical drag to scrub), prev/play/next +/- and tap-tempo, and a bottom sheet for set lists / patch+link loading / volume. Mobile concerns handled: - iOS ring/silent switch: navigator.audioSession.type="playback" + a silent buffer warmup inside the play gesture, so audio isn't muted by the switch. - Screen Wake Lock while running (re-acquired on visibilitychange). - PWA: manifest.webmanifest + apple-touch meta + mobile-sw.js (network-first app shell, passthrough for everything else) -> installable + offline. Multi-file is fine here since it targets mobile (waives the single-file rule). - viewport-fit=cover + safe-area insets, no user zoom, touch-action:manipulation, overscroll-behavior:none; transport buttons flex-share the row so they never overflow a narrow phone; responsive portrait/landscape, phone->tablet. - Fullscreen toggle where supported (Android/desktop; iOS uses home-screen PWA). Wired into build.sh + deploy.sh (page + PWA assets) and added to the index gallery as PM_M-1 Mobile. New metronome app icons generated in assets/. Conformance suite unaffected (engine untouched): 47 pass, 1 known. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.8 KiB
JavaScript
51 lines
1.8 KiB
JavaScript
/* Service worker for the PolyMeter mobile app (mobile.html).
|
|
*
|
|
* Deliberately minimal and non-intrusive: it only manages its OWN app-shell URLs
|
|
* (the page, manifest, icons). For every other request it does NOT call
|
|
* respondWith(), so the rest of the site behaves exactly as if no SW existed.
|
|
*
|
|
* Strategy for the shell: network-first, fall back to cache. The page is a single
|
|
* self-contained file that is version-stamped on deploy, so when the device is
|
|
* online it always gets the freshest build; offline it still launches from cache.
|
|
*/
|
|
const CACHE = "polymeter-mobile-v1";
|
|
const SHELL = [
|
|
"/mobile.html",
|
|
"/manifest.webmanifest",
|
|
"/icon-192.png",
|
|
"/icon-512.png",
|
|
"/icon-180.png",
|
|
];
|
|
const SHELL_PATHS = new Set(SHELL);
|
|
|
|
self.addEventListener("install", (e) => {
|
|
self.skipWaiting();
|
|
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).catch(() => {}));
|
|
});
|
|
|
|
self.addEventListener("activate", (e) => {
|
|
e.waitUntil(
|
|
caches.keys()
|
|
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
|
.then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener("fetch", (e) => {
|
|
const req = e.request;
|
|
if (req.method !== "GET") return;
|
|
const url = new URL(req.url);
|
|
if (url.origin !== self.location.origin) return;
|
|
// Treat any navigation to /mobile.html (with or without ?standalone=1 etc.) as the shell.
|
|
const path = url.pathname;
|
|
if (!SHELL_PATHS.has(path)) return; // not ours — let the browser handle it
|
|
|
|
e.respondWith(
|
|
fetch(req)
|
|
.then((res) => {
|
|
if (res && res.ok) { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(path, copy)); }
|
|
return res;
|
|
})
|
|
.catch(() => caches.match(path).then((hit) => hit || caches.match("/mobile.html")))
|
|
);
|
|
});
|