Added Recorder class. Removed poll.js file. Recorder class is now how this keeps track of what to scrobble.
This commit is contained in:
parent
888e954fd7
commit
e5e0853d03
14
app.js
14
app.js
@ -3,11 +3,18 @@ const express = require('express');
|
|||||||
const helmet = require("helmet");
|
const helmet = require("helmet");
|
||||||
const logger = require("./services/logging");
|
const logger = require("./services/logging");
|
||||||
const rateLimit = require("express-rate-limit");
|
const rateLimit = require("express-rate-limit");
|
||||||
|
const sessions = require("./services/session-manager");
|
||||||
|
const PlexTracker = require("./services/trackers/PlexTracker");
|
||||||
|
const SpotifyTracker = require("./services/trackers/SpotifyTracker");
|
||||||
|
const Recorder = require("./services/recorder");
|
||||||
|
|
||||||
const poll = require("./services/poll");
|
|
||||||
setInterval(poll, 5000);
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || config.web.port || 9111;
|
const spotify = new SpotifyTracker(config.spotify);
|
||||||
|
(async () => await spotify.loadCredentials())();
|
||||||
|
const plex = new PlexTracker(config.plex);
|
||||||
|
const recorder = new Recorder(sessions, [plex, spotify], config.scrobble, logger);
|
||||||
|
|
||||||
|
setInterval(() => recorder.record(), 5000);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@ -27,6 +34,7 @@ const limiter = rateLimit({
|
|||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
app.use(limiter);
|
app.use(limiter);
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || config.web.port || 9111;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
logger.info("Listening to port " + PORT + ".");
|
logger.info("Listening to port " + PORT + ".");
|
||||||
});
|
});
|
@ -19,7 +19,6 @@ class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set playing(value) {
|
set playing(value) {
|
||||||
console.log('playing =', value);
|
|
||||||
this.#current = value;
|
this.#current = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
126
services/poll.js
126
services/poll.js
@ -1,126 +0,0 @@
|
|||||||
const plex = require("./plex");
|
|
||||||
const logger = require("./logging")
|
|
||||||
const config = require("../config/configuration");
|
|
||||||
const Session = require("../models/session");
|
|
||||||
const sessions = require("../services/session-manager");
|
|
||||||
const spotify = require("./spotify");
|
|
||||||
|
|
||||||
let lastTick = Date.now();
|
|
||||||
|
|
||||||
async function poll() {
|
|
||||||
const now = Date.now();
|
|
||||||
const timeDiff = now - lastTick;
|
|
||||||
const playing = [];
|
|
||||||
|
|
||||||
await spotify.loadCredentials();
|
|
||||||
await spotify.refreshTokenIfNeeded();
|
|
||||||
const spotifyTrack = await spotify.getCurrentlyPlaying();
|
|
||||||
if (spotifyTrack != null)
|
|
||||||
playing.push(spotifyTrack);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await plex.getCurrentlyPlaying();
|
|
||||||
playing.push.apply(playing, data);
|
|
||||||
} catch (ex) {
|
|
||||||
logger.error(ex, "Could not fetch currently playing data from Plex.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let current of playing) {
|
|
||||||
let session = sessions.get(current.sessionKey);
|
|
||||||
if (session == null) {
|
|
||||||
session = new Session(current.sessionKey);
|
|
||||||
sessions.add(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
const previous = session.playing;
|
|
||||||
session.playing = current;
|
|
||||||
if (previous == null) {
|
|
||||||
logger.info(current, "A new session has started.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.playing.state == "paused" || previous.state == "paused") {
|
|
||||||
session.pauseDuration += timeDiff - (session.playing.playtime - previous.playtime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkIfCanScrobble(session, previous, now, timeDiff)) {
|
|
||||||
logger.info(previous, "Scrobble");
|
|
||||||
session.pauseDuration = 0;
|
|
||||||
session.lastScrobbleTimestamp = now - (timeDiff - (previous.duration - previous.playtime));
|
|
||||||
} else if (session.playing.playtime < previous.playtime && session.playing.mediaKey != previous.mediaKey) {
|
|
||||||
session.pauseDuration = 0;
|
|
||||||
if (session.playing.playtime < timeDiff)
|
|
||||||
session.lastScrobbleTimestamp = now - session.playing.playtime;
|
|
||||||
else
|
|
||||||
session.lastScrobbleTimestamp = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids = sessions.getSessionIds();
|
|
||||||
for (let sessionId of ids) {
|
|
||||||
if (playing.some(p => p.sessionKey == sessionId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
session.playing = null;
|
|
||||||
if (checkIfCanScrobble(session, session.playing, now, timeDiff)) {
|
|
||||||
logger.info(session.playing, "Scrobble");
|
|
||||||
}
|
|
||||||
sessions.remove(sessionId);
|
|
||||||
logger.debug("Deleted old session (" + sessionId + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
lastTick = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyFilter(track, filters) {
|
|
||||||
if (!filters || filters.length == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (let filter of filters) {
|
|
||||||
if (filter.library && !filter.library.some(l => l == track.library))
|
|
||||||
continue;
|
|
||||||
if (filter.ip && !filter.ip.some(l => l == track.ip))
|
|
||||||
continue;
|
|
||||||
if (filter.deviceId && !filter.deviceId.some(l => l == track.deviceId))
|
|
||||||
continue;
|
|
||||||
if (filter.platform && !filter.platform.some(l => l == track.platform))
|
|
||||||
continue;
|
|
||||||
if (filter.product && !filter.product.some(l => l == track.product))
|
|
||||||
continue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkIfCanScrobble(session, previous, now, timeDiff) {
|
|
||||||
if (!previous)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let filters = [];
|
|
||||||
if (previous.source == 'plex')
|
|
||||||
filters = config.plex.filters;
|
|
||||||
|
|
||||||
if (!applyFilter(previous, filters)) {
|
|
||||||
logger.debug(previous, 'No filters got triggered. Ignoring.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrobbleDuration = config.scrobble.minimum.duration || 240;
|
|
||||||
const scrobblePercent = config.scrobble.minimum.percent || 50;
|
|
||||||
|
|
||||||
const current = session.playing;
|
|
||||||
const durationPlayed = now - (session.lastScrobbleTimestamp ?? session.started) - session.pauseDuration;
|
|
||||||
const newPlayback = current == null || current.playtime < previous.playtime && current.playtime < timeDiff;
|
|
||||||
const canBeScrobbled = durationPlayed > scrobbleDuration * 1000 || durationPlayed / previous.duration > scrobblePercent / 100.0;
|
|
||||||
|
|
||||||
return newPlayback && canBeScrobbled;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInt(value) {
|
|
||||||
return !isNaN(value) &&
|
|
||||||
parseInt(Number(value)) == value &&
|
|
||||||
!isNaN(parseInt(value, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = poll;
|
|
99
services/recorder.js
Normal file
99
services/recorder.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
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;
|
Loading…
Reference in New Issue
Block a user