From 3aab74e451d00b336fad263d190abbe2f197a04f Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Sat, 18 Apr 2026 13:23:22 +0200 Subject: [PATCH] v2.1.0: Fix audio playback (CORS/workbox), add console logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio Fix: - Unregister service worker on Android — workbox CacheFirst on audio/video caused CORS failures on Tidal CDN streams - Patch workbox config: CacheFirst → NetworkOnly for audio/video - Shim @capgo/capacitor-media-session (our AudioForegroundService handles MediaSession natively) Console Logger: - fm-logger.js loaded synchronously in (captures ALL output from the very first script execution) - Intercepts console.log/warn/error/info/debug + window.onerror + unhandledrejection + fetch errors (4xx/5xx/network) - Saves to Downloads/FabiodalezMusic/fm-console-log.txt every 30s and on app background - Max 3000 entries, timestamped, with error flag Search Simplification: - Removed wrapper-side search hooks (dropdown preview, highlight, monkey-patch, cache cleanup) — upstream search flow handles it - Kept upstream patches: debounce 700ms, limit=100, min 3 chars, cache normalization Upstream Sync: - Pulled ~60 new commits including Tidal API v2 migration for artists, artist banners, animated covers, mobile CSS, audio slider fix, international fonts Closes #4: haptic feedback removed. --- android/android-service.js | 20 +++++ android/fm-logger.js | 145 +++++++++++++++++++++++++++++++++++++ build-android.sh | 26 +++++-- install.sh | 3 +- 4 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 android/fm-logger.js diff --git a/android/android-service.js b/android/android-service.js index fa70d10..796f1eb 100644 --- a/android/android-service.js +++ b/android/android-service.js @@ -3,6 +3,26 @@ // Injected into the Monochrome web app at build time. // ═══════════════════════════════════════════════════════════════════ +// (fm-logger.js is loaded separately in as a synchronous script +// so it captures ALL console output from the very start, before any module.) + +// ── UNREGISTER SERVICE WORKER ── +// The upstream workbox SW uses CacheFirst for audio/video, but Tidal CDN +// streams don't serve CORS headers → workbox can't read the response → +// "no-response" error → audio won't play. The Android WebView already has +// its own HTTP cache (configured in MainActivity), so we don't need SW at all. +if ('serviceWorker' in navigator) { + navigator.serviceWorker.getRegistrations().then((regs) => { + regs.forEach((r) => r.unregister()); + }).catch(() => {}); + // Also clear the SW cache storage so stale entries don't linger + if (typeof caches !== 'undefined') { + caches.keys().then((names) => { + names.forEach((name) => caches.delete(name)); + }).catch(() => {}); + } +} + // ── CLIPBOARD FALLBACK ── if (window.AndroidBridge) { const origClipboard = navigator.clipboard?.writeText?.bind(navigator.clipboard); diff --git a/android/fm-logger.js b/android/fm-logger.js new file mode 100644 index 0000000..7b36327 --- /dev/null +++ b/android/fm-logger.js @@ -0,0 +1,145 @@ +// fm-logger.js — Console logger to file (loaded FIRST, before any other scripts) +// Saves all console output to Downloads/FabiodalezMusic/fm-console-log.txt +(function () { + var _logBuffer = []; + var _MAX = 3000; + var _origLog = console.log; + var _origWarn = console.warn; + var _origError = console.error; + var _origInfo = console.info; + var _origDebug = console.debug; + var _hasErrors = false; + + var _ts = function () { + return new Date().toISOString().slice(11, 23); + }; + + var _capture = function (level, args) { + try { + var parts = []; + for (var i = 0; i < args.length; i++) { + try { + if (typeof args[i] === 'object' && args[i] !== null) { + if (args[i] instanceof Error) { + parts.push(args[i].message + (args[i].stack ? '\n' + args[i].stack : '')); + } else { + parts.push(JSON.stringify(args[i], null, 0)); + } + } else { + parts.push(String(args[i])); + } + } catch (e) { + parts.push('[unserializable]'); + } + } + var msg = _ts() + ' [' + level + '] ' + parts.join(' '); + if (_logBuffer.length >= _MAX) _logBuffer.shift(); + _logBuffer.push(msg); + if (level === 'ERR' || level === 'UNCAUGHT' || level === 'UNHANDLED') { + _hasErrors = true; + } + } catch (e) { + // logger itself must never throw + } + }; + + console.log = function () { + _capture('LOG', arguments); + return _origLog.apply(console, arguments); + }; + console.warn = function () { + _capture('WARN', arguments); + return _origWarn.apply(console, arguments); + }; + console.error = function () { + _capture('ERR', arguments); + return _origError.apply(console, arguments); + }; + console.info = function () { + _capture('INFO', arguments); + return _origInfo.apply(console, arguments); + }; + console.debug = function () { + _capture('DBG', arguments); + return _origDebug.apply(console, arguments); + }; + + window.addEventListener('error', function (e) { + _capture('UNCAUGHT', [ + e.message || 'unknown error', + 'at ' + (e.filename || '?') + ':' + (e.lineno || '?') + ':' + (e.colno || '?'), + ]); + }); + + window.addEventListener('unhandledrejection', function (e) { + var reason = e.reason; + if (reason instanceof Error) { + _capture('UNHANDLED', [reason.message, reason.stack || '']); + } else { + _capture('UNHANDLED', [reason]); + } + }); + + // Intercept fetch errors globally + var _origFetch = window.fetch; + if (_origFetch) { + window.fetch = function () { + var url = arguments[0]; + if (typeof url === 'object' && url.url) url = url.url; + return _origFetch.apply(this, arguments).then( + function (resp) { + if (!resp.ok && resp.status >= 400) { + _capture('FETCH', [resp.status + ' ' + resp.statusText + ' ' + url]); + } + return resp; + }, + function (err) { + _capture('FETCH_ERR', [String(url).substring(0, 200) + ' → ' + (err.message || err)]); + throw err; + } + ); + }; + } + + // Save function + var _saveLog = function () { + if (!_logBuffer.length) return; + // Wait for AndroidDownload bridge to be available + if (!window.AndroidDownload) { + // Retry in 2s + setTimeout(_saveLog, 2000); + return; + } + try { + var text = + '=== Fabiodalez Music Console Log ===\n' + + 'Saved: ' + new Date().toISOString() + '\n' + + 'URL: ' + location.href + '\n' + + 'UA: ' + navigator.userAgent + '\n' + + 'Entries: ' + _logBuffer.length + '\n' + + 'Errors: ' + (_hasErrors ? 'YES' : 'none') + '\n' + + '===================================\n\n' + + _logBuffer.join('\n'); + var base64 = btoa(unescape(encodeURIComponent(text))); + window.AndroidDownload.saveBase64(base64, 'fm-console-log.txt', 'text/plain'); + } catch (err) { + _origError.call(console, '[fm-logger] save failed:', err); + } + }; + + // Save on visibility change (app minimized) + document.addEventListener('visibilitychange', function () { + if (document.visibilityState === 'hidden') _saveLog(); + }); + + // Save every 30 seconds + setInterval(_saveLog, 30000); + + // Expose manual save + window._fmSaveLog = _saveLog; + window._fmGetLog = function () { + return _logBuffer.join('\n'); + }; + + _capture('INFO', ['fm-logger v2 initialized (early-load, fetch interceptor, max ' + _MAX + ')']); +})(); diff --git a/build-android.sh b/build-android.sh index 658170e..4217275 100755 --- a/build-android.sh +++ b/build-android.sh @@ -36,6 +36,7 @@ cleanup() { for f in \ index.html \ package.json \ + vite.config.ts \ js/app.js \ js/api.js \ js/cache.js \ @@ -46,7 +47,7 @@ cleanup() { fi done # Untracked files we generated during the build - rm -f js/android-service.js package-lock.json + rm -f js/android-service.js js/fm-logger.js package-lock.json rm -rf node_modules/@capgo/capacitor-media-session 2>/dev/null || true echo " ✓ Source restored to upstream." } @@ -118,11 +119,13 @@ echo " ✓ @capgo/capacitor-media-session shimmed (no-op → our AudioForegroun echo "" echo "▶ Patching for Android build..." -# 3a. Add script tag +# 3a. Add script tags +# Logger FIRST (synchronous, non-module) in — captures ALL console output from start +sed -i '' 's|||' index.html +# Service JS LAST (module) in — loads after DOM sed -i '' 's|||' index.html -# 3b. (upstream already has viewport-fit=cover — previous pinch-zoom-disable -# sed was a noop and removed; rely on meta viewport as upstream ships it.) +# 3b. (workbox audio/video CacheFirst → NetworkOnly is patched via Python below) # 3c. Brand: "Monochrome" → "Fabiodalez" in sidebar logo sed -i '' 's|Monochrome|Fabiodalez|' index.html @@ -350,14 +353,25 @@ try: except Exception as e: print(" ! api.js: searchArtists patch failed: " + str(e)) +# ── Workbox: CacheFirst → NetworkOnly for audio/video ── +# Tidal CDN streams don't serve CORS headers → CacheFirst fails with +# "no-response" in the service worker → audio won't play. +patch( + "vite.config.ts", + "handler: 'CacheFirst',\n options: {\n cacheName: 'media',", + "handler: 'NetworkOnly',\n options: {\n cacheName: 'media',", + "vite.config.ts: workbox audio/video CacheFirst -> NetworkOnly", +) + print(" ✓ Upstream JS optimizations applied.") PYEOF echo " ✓ JS upstream optimizations applied (debounce, cache, limits)." -# ── 4. Copy android-service.js from android/ storage ── +# ── 4. Copy wrapper JS files from android/ storage ── cp "$PROJECT_DIR/android/android-service.js" js/android-service.js -echo " ✓ android-service.js copied." +cp "$PROJECT_DIR/android/fm-logger.js" js/fm-logger.js +echo " ✓ android-service.js + fm-logger.js copied." # ── 5. Init Capacitor Android if needed ── if [ ! -d "$PROJECT_DIR/android" ]; then diff --git a/install.sh b/install.sh index 8823730..dec3d10 100755 --- a/install.sh +++ b/install.sh @@ -31,9 +31,10 @@ cp "$SCRIPT_DIR/build-android.sh" "$TARGET/" cp "$SCRIPT_DIR/capacitor.config.ts" "$TARGET/" chmod +x "$TARGET/build-android.sh" -# Copy android-service.js +# Copy wrapper JS files mkdir -p "$TARGET/android" cp "$SCRIPT_DIR/android/android-service.js" "$TARGET/android/" +cp "$SCRIPT_DIR/android/fm-logger.js" "$TARGET/android/" # Copy Java sources JAVA_DEST="$TARGET/android/app/src/main/java/com/monochrome/app"