feat: server-side romaji overrides (shared list); preload ExoPlayer from SharedPreferences on cold start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-05 10:47:25 +02:00
1 parent b924f01b2a
commit 36a0b38eca
9 files changed
+116 -14

No files matched your search

+31 -10
View File
@@ -99,19 +99,40 @@ export async function fetchRomajiIndex() {
export function invalidateRomajiIndex() { _romajiIndex = null; _romajiMap = null; _artistRomajiMap = null; }
// ─── ROMAJI OVERRIDES ───
// Manual kanji → romaji corrections stored in localStorage. Always take priority over auto-conversion.
// Stored server-side (shared across all clients). Cached in memory per session.
let _overridesCache = null; // null = not loaded yet
export function getRomajiOverrides() {
try { return JSON.parse(localStorage.getItem('ns_romaji_overrides') || '{}'); } catch { return {}; }
return _overridesCache || {};
}
export function setRomajiOverride(kanji, romaji) {
const o = getRomajiOverrides();
if (romaji?.trim()) o[kanji.trim()] = romaji.trim(); else delete o[kanji.trim()];
localStorage.setItem('ns_romaji_overrides', JSON.stringify(o));
export async function loadRomajiOverrides() {
if (!S.cfg?.backendUrl) return;
try {
const r = await fetch(`${S.cfg.backendUrl}/api/romaji-overrides`);
_overridesCache = r.ok ? await r.json() : {};
} catch { _overridesCache = {}; }
}
export function removeRomajiOverride(kanji) {
const o = getRomajiOverrides();
delete o[kanji];
localStorage.setItem('ns_romaji_overrides', JSON.stringify(o));
export async function setRomajiOverride(kanji, romaji) {
if (!S.cfg?.backendUrl || !kanji?.trim()) return;
try {
await fetch(`${S.cfg.backendUrl}/api/romaji-overrides`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kanji: kanji.trim(), romaji: romaji?.trim() || '' }),
});
if (!_overridesCache) _overridesCache = {};
if (romaji?.trim()) _overridesCache[kanji.trim()] = romaji.trim();
else delete _overridesCache[kanji.trim()];
} catch (e) { console.warn('setRomajiOverride failed:', e); }
}
export async function removeRomajiOverride(kanji) {
if (!S.cfg?.backendUrl || !kanji) return;
try {
await fetch(`${S.cfg.backendUrl}/api/romaji-overrides/${encodeURIComponent(kanji)}`, { method: 'DELETE' });
if (_overridesCache) delete _overridesCache[kanji];
} catch (e) { console.warn('removeRomajiOverride failed:', e); }
}
export function dispArtistById(id, name) {
+2 -1
View File
@@ -1,6 +1,6 @@
import { S } from './state.js';
import { toast } from './utils.js';
import { api, fetchRomajiIndex } from './api.js';
import { api, fetchRomajiIndex, loadRomajiOverrides } from './api.js';
import { injectPWA } from './pwa.js';
import { loadSavedTheme } from './themes.js';
import {
@@ -96,6 +96,7 @@ async function startApp() {
if (bnav) bnav.style.display = '';
absLoadSettingsLibraries();
}
loadRomajiOverrides().catch(() => {}); // fire-and-forget — cache for dispArtist/dispAlbum/dispTitle
if (S.cfg.showRomaji) fetchRomajiIndex().catch(() => {}); // fire-and-forget — don't block navigation
if(_navLoad() && S._navCursor >= 0 && S._navStack[S._navCursor]) {
const last = S._navStack[S._navCursor];
+8
View File
@@ -65,6 +65,13 @@ const buildTrackList = () => S.queue.map(_trackToNative);
// Single-track JSON for setQueue — ExoPlayer always holds exactly one item.
const _nativeSingle = s => JSON.stringify([_trackToNative(s)]);
// Persist the current track to Android SharedPreferences so MusicService can
// pre-warm ExoPlayer on the next cold start before the WebView finishes loading.
function _persistLastPlayed(s) {
if (!HAS_BRIDGE || !s) return;
try { window.NSBridge.saveLastPlayed(JSON.stringify(_trackToNative(s))); } catch (_) {}
}
// Reload native player with just the current song. The single-track model means
// ExoPlayer is never responsible for queue order — JS owns that entirely.
function _pushQueueIfBridge() {
@@ -453,6 +460,7 @@ export function updatePlayerUI(s, _skipMediaSession = false) {
document.title = `${s.title} — NovaSonic`;
if (!_skipMediaSession) updateMediaSession(s);
if(document.getElementById('page-recommendations').classList.contains('active')) loadRecommendations();
_persistLastPlayed(s);
try {
localStorage.setItem('ns_last_played', JSON.stringify({
song: { id:s.id, title:s.title, artist:s.artist, album:s.album, albumId:s.albumId,
+3 -3
View File
@@ -92,7 +92,7 @@ export async function addRomajiOverrideUI() {
const k = (document.getElementById('ro-kanji')?.value || '').trim();
const v = (document.getElementById('ro-romaji')?.value || '').trim();
if (!k || !v) { toast('⚠ Kanji und Romaji eingeben'); return; }
setRomajiOverride(k, v);
await setRomajiOverride(k, v);
document.getElementById('ro-kanji').value = '';
document.getElementById('ro-romaji').value = '';
_renderRomajiOverrides();
@@ -111,8 +111,8 @@ export async function addRomajiOverrideUI() {
toast('⚠ Fix-Artist-Sort: ' + e.message);
}
}
export function removeRomajiOverrideUI(kanji) {
removeRomajiOverride(kanji);
export async function removeRomajiOverrideUI(kanji) {
await removeRomajiOverride(kanji);
_renderRomajiOverrides();
}
export function openRomajiOverridesModal() {