v2.1.0: Fix audio playback (CORS/workbox), add console logger

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 <head> (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.
This commit is contained in:
fabiodalez-dev committed 2026-04-18 13:23:22 +02:00
1 parent 3b5503bb45
commit 3aab74e451
4 files changed
+187 -7

No files matched your search

+20
View File
@@ -3,6 +3,26 @@
// Injected into the Monochrome web app at build time.
// ═══════════════════════════════════════════════════════════════════
// (fm-logger.js is loaded separately in <head> 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);
+145
View File
@@ -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 + ')']);
})();
+20 -6
View File
@@ -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 <head> — captures ALL console output from start
sed -i '' 's|</head>|<script src="./js/fm-logger.js"></script></head>|' index.html
# Service JS LAST (module) in <body> — loads after DOM
sed -i '' 's|</body>|<script type="module" src="./js/android-service.js"></script></body>|' 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|<span>Monochrome</span>|<span>Fabiodalez</span>|' 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
+2 -1
View File
@@ -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"