99 lines
3.2 KiB
JavaScript
99 lines
3.2 KiB
JavaScript
const AggregateTracker = require("./trackers/AggregateTracker");
|
|
const Session = require("../models/session");
|
|
|
|
class Recorder {
|
|
#sessions = null;
|
|
#trackers = null;
|
|
#config = null;
|
|
#logger = null;
|
|
#lastTick = null;
|
|
|
|
constructor(sessions, trackers, config, logger) {
|
|
this.#sessions = sessions;
|
|
this.#trackers = new AggregateTracker(trackers);
|
|
this.#config = config;
|
|
this.#logger = logger;
|
|
this.#lastTick = Date.now();
|
|
}
|
|
|
|
async record() {
|
|
const now = Date.now();
|
|
const timeDiff = now - this.#lastTick;
|
|
const media = await this.#trackers.poll();
|
|
const data = media.map(m => this.#fetchSession(m));
|
|
|
|
// Find sessions that ended and that are deemable of a scrobble.
|
|
const sessionIds = this.#sessions.getSessionIds();
|
|
const stopped = sessionIds.filter(sessionId => !data.some(d => sessionId == d.session.id)).map(s => this.#sessions.get(s));
|
|
const sessionEnded = stopped.filter(s => this.#canScrobble(s, null, s.playing, now));
|
|
|
|
// Find ongoing sessions that have moved on to the next song.
|
|
const finishedPlaying = data.filter(d => this.#listen(d.session, d.media, d.session.playing, now, timeDiff));
|
|
|
|
const scrobbling = finishedPlaying.concat(sessionEnded);
|
|
for (let track of scrobbling)
|
|
this.#scrobble(track);
|
|
|
|
// Remove dead sessions.
|
|
for (let sessionId of stopped)
|
|
this.#sessions.remove(sessionId);
|
|
|
|
this.#lastTick = now;
|
|
}
|
|
|
|
#fetchSession(media) {
|
|
let session = this.#sessions.get(media.session);
|
|
if (session == null) {
|
|
session = new Session(media.session);
|
|
this.#sessions.add(session);
|
|
}
|
|
|
|
return { session: session, media: media }
|
|
}
|
|
|
|
#listen(session, current, previous, timestamp, timeDiff) {
|
|
session.playing = current;
|
|
|
|
if (previous == null) {
|
|
this.#logger.info(current, "A new session has started.");
|
|
return false;
|
|
}
|
|
|
|
if (session.playing.state == "paused" || previous.state == "paused") {
|
|
session.pauseDuration += timeDiff - (session.playing.progress - previous.progress);
|
|
}
|
|
|
|
if (this.#canScrobble(session, current, previous, timestamp)) {
|
|
session.pauseDuration = 0;
|
|
session.lastScrobbleTimestamp = timestamp - (timeDiff - (previous.duration - previous.progress));
|
|
return true;
|
|
} else if (current.progress < previous.progress && session.playing.id != previous.id) {
|
|
session.pauseDuration = 0;
|
|
if (current.progress < timeDiff)
|
|
session.lastScrobbleTimestamp = timestamp - session.playing.progress;
|
|
else
|
|
session.lastScrobbleTimestamp = timestamp;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#canScrobble(session, current, previous, timestamp) {
|
|
if (previous == null)
|
|
return false;
|
|
|
|
const scrobbleDuration = this.#config.minimum.duration || 240;
|
|
const scrobblePercent = this.#config.minimum.percent || 50;
|
|
|
|
const durationPlayed = timestamp - (session.lastScrobbleTimestamp || session.started) - session.pauseDuration;
|
|
const newPlayback = current == null || current.progress < previous.progress;
|
|
const canBeScrobbled = durationPlayed > scrobbleDuration * 1000 || durationPlayed / previous.duration > scrobblePercent / 100.0;
|
|
|
|
return newPlayback && canBeScrobbled;
|
|
}
|
|
|
|
#scrobble(media) {
|
|
this.#logger.info(media, "Scrobble");
|
|
}
|
|
}
|
|
|
|
module.exports = Recorder; |