diff --git a/index.html b/index.html
index c61c31b..684ab57 100644
--- a/index.html
+++ b/index.html
@@ -661,6 +661,16 @@
Romaji anzeigen
diff --git a/src/player/player.js b/src/player/player.js
index d7de7d3..4b9c93c 100644
--- a/src/player/player.js
+++ b/src/player/player.js
@@ -20,20 +20,33 @@ let _bridgeReady = false; // true once onReady has fired; onQueueEnded is ign
// ─── SCROBBLE ─────────────────────────────────────────────────────────────
let _scrobbleTimer = null;
let _scrobbleId = null;
-function _scheduleScrobble(id) {
- if (_scrobbleId === String(id)) return; // already scheduled for this track
+function _scheduleScrobble(id, durSec) {
+ if (_scrobbleId === String(id)) return;
_cancelScrobble();
_scrobbleId = String(id);
+ const mode = S.cfg.scrobbleMode || '15s';
+ if (mode === 'end') return; // fired explicitly in onEnded / onQueueEnded
+ let delay;
+ const dur = (durSec || 0) * 1000;
+ switch (mode) {
+ case '30s': delay = 30_000; break;
+ case '1/3': delay = dur > 0 ? Math.floor(dur / 3) : 30_000; break;
+ case '2/3': delay = dur > 0 ? Math.floor(dur * 2 / 3) : 60_000; break;
+ default: delay = 15_000; break;
+ }
_scrobbleTimer = setTimeout(() => {
if (_scrobbleId) api('scrobble', `&id=${_scrobbleId}&time=${Date.now()}`).catch(() => {});
_scrobbleId = null;
- }, 15_000);
+ }, delay);
}
function _cancelScrobble() {
clearTimeout(_scrobbleTimer);
_scrobbleTimer = null;
_scrobbleId = null;
}
+function _scrobbleNow(s) {
+ if (s) api('scrobble', `&id=${s.id}&time=${Date.now()}`).catch(() => {});
+}
// Unified duration / position / seek helpers — use native state when available,
// fall back to HTMLAudioElement when not.
@@ -192,7 +205,7 @@ window.NSPlayer = {
try {
const s = S.queue[S.qIdx];
if (!s) return;
- _scheduleScrobble(s.id);
+ _scheduleScrobble(s.id, s.duration);
if (S.radioMode && S.queue.length - S.qIdx < 5) fillRadioQueue();
} catch (e) {}
},
@@ -230,6 +243,7 @@ window.NSPlayer = {
if (!_bridgeReady || Date.now() - _readyTs < 1500) return;
_nativePlaying = false;
setPlayIcon(false);
+ if (S.cfg.scrobbleMode === 'end') _scrobbleNow(S.queue[S.qIdx]);
onEnded(); // handles repeat, radio top-up, and queue advance
},
@@ -376,7 +390,7 @@ export function playCurrent() {
updatePlayerUI(s, true);
onNpSongChange(s.id);
renderQueue();
- _scheduleScrobble(s.id); // 12s timer — cancelled on pause/skip
+ _scheduleScrobble(s.id, s.duration);
const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id);
_clearPreload();
@@ -691,7 +705,8 @@ export function nextSong() {
export function onEnded() {
if(S.repeat === 'one') { S.audio.currentTime = 0; S.audio.play(); return; }
- _cancelScrobble(); // timer irrelevant now — song already finished
+ if (!HAS_BRIDGE && S.cfg.scrobbleMode === 'end') _scrobbleNow(S.queue[S.qIdx]);
+ _cancelScrobble();
nextSong();
}
diff --git a/src/ui/settings.js b/src/ui/settings.js
index 8d417e6..23aa9e8 100644
--- a/src/ui/settings.js
+++ b/src/ui/settings.js
@@ -50,6 +50,7 @@ export function openSettings() {
document.getElementById('s-show-playcount').classList.toggle('on', !!S.cfg.showPlayCount);
document.getElementById('s-show-romaji').classList.toggle('on', !!S.cfg.showRomaji);
document.getElementById('s-cover-color-bg').classList.toggle('on', !!S.cfg.coverColorBg);
+ document.getElementById('s-scrobble-mode').value = S.cfg.scrobbleMode || '15s';
_renderThemePicker();
document.getElementById('settings-modal').classList.add('open');
checkBackend();
@@ -131,6 +132,7 @@ export function saveSettings() {
const romajiChanged = S.cfg.showRomaji !== document.getElementById('s-show-romaji').classList.contains('on');
S.cfg.showRomaji = document.getElementById('s-show-romaji').classList.contains('on');
S.cfg.coverColorBg = document.getElementById('s-cover-color-bg').classList.contains('on');
+ S.cfg.scrobbleMode = document.getElementById('s-scrobble-mode').value || '15s';
updateNpColorMode();
localStorage.setItem('ns_cfg', JSON.stringify(S.cfg));
closeSettings(); toast('✅ Gespeichert');