Compare commits
2 Commits
1b91e330c1
...
539b9a5055
Author | SHA1 | Date | |
---|---|---|---|
539b9a5055 | |||
478bd76aeb |
44
models/session.js
Normal file
44
models/session.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
class Session {
|
||||||
|
constructor(id) {
|
||||||
|
this._id = id;
|
||||||
|
this._started = Date.now();
|
||||||
|
this._current = null;
|
||||||
|
this._previous = null;
|
||||||
|
this._lastScrobble = null;
|
||||||
|
this._pauseDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get playing() {
|
||||||
|
return this._current;
|
||||||
|
}
|
||||||
|
|
||||||
|
set playing(value) {
|
||||||
|
this._current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get started() {
|
||||||
|
return this._started;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastScrobbleTimestamp() {
|
||||||
|
return this._lastScrobble;
|
||||||
|
}
|
||||||
|
|
||||||
|
set lastScrobbleTimestamp(value) {
|
||||||
|
this._lastScrobble = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get pauseDuration() {
|
||||||
|
return this._pauseDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
set pauseDuration(value) {
|
||||||
|
this._pauseDuration = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Session;
|
@ -1,13 +1,15 @@
|
|||||||
const plex = require("./plex");
|
const plex = require("./plex");
|
||||||
const logger = require("./logging")
|
const logger = require("./logging")
|
||||||
const config = require("../config/configuration");
|
const config = require("../config/configuration");
|
||||||
|
const Session = require("../models/session");
|
||||||
|
const sessions = require("../services/session-manager");
|
||||||
const spotify = require("./spotify");
|
const spotify = require("./spotify");
|
||||||
|
|
||||||
const lastPlaying = {};
|
let lastTick = Date.now();
|
||||||
const lastScrobbleTimes = {};
|
|
||||||
|
|
||||||
async function poll() {
|
async function poll() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
const timeDiff = now - lastTick;
|
||||||
const playing = [];
|
const playing = [];
|
||||||
|
|
||||||
await spotify.loadCredentials();
|
await spotify.loadCredentials();
|
||||||
@ -25,31 +27,50 @@ async function poll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let current of playing) {
|
for (let current of playing) {
|
||||||
const previous = lastPlaying[current.sessionKey];
|
let session = sessions.get(current.sessionKey);
|
||||||
lastPlaying[current.sessionKey] = current;
|
if (session == null) {
|
||||||
|
session = new Session(current.sessionKey);
|
||||||
|
sessions.add(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = session.playing;
|
||||||
|
session.playing = current;
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
logger.info(current, "A new session has started.");
|
logger.info(current, "A new session has started.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkIfCanScrobble(current, previous, now)) {
|
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");
|
logger.info(previous, "Scrobble");
|
||||||
lastScrobbleTimes[previous.mediaKey] = now;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrobble then remove lingering sessions
|
const ids = sessions.getSessionIds();
|
||||||
for (let key in lastPlaying) {
|
for (let sessionId of ids) {
|
||||||
if (!playing.some(p => p.sessionKey == key)) {
|
if (playing.some(p => p.sessionKey == sessionId))
|
||||||
const track = lastPlaying[key];
|
continue;
|
||||||
if (checkIfCanScrobble(null, track, now)) {
|
|
||||||
logger.info(track, "Scrobble");
|
session.playing = null;
|
||||||
lastScrobbleTimes[track.mediaKey] = now;
|
if (checkIfCanScrobble(session, session.playing, now, timeDiff)) {
|
||||||
}
|
logger.info(session.playing, "Scrobble");
|
||||||
delete lastPlaying[key];
|
|
||||||
logger.debug("Deleted old session.", key);
|
|
||||||
}
|
}
|
||||||
|
sessions.remove(sessionId);
|
||||||
|
logger.debug("Deleted old session (" + sessionId + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTick = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyFilter(track, filters) {
|
function applyFilter(track, filters) {
|
||||||
@ -72,7 +93,7 @@ function applyFilter(track, filters) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkIfCanScrobble(current, previous, now) {
|
function checkIfCanScrobble(session, previous, now, timeDiff) {
|
||||||
if (!previous)
|
if (!previous)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -85,20 +106,15 @@ function checkIfCanScrobble(current, previous, now) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrobbleDuration = isInt(config.scrobble.minimum.duration) ? Number(config.scrobble.minimum.duration) : 30;
|
const scrobbleDuration = config.scrobble.minimum.duration || 240;
|
||||||
const scrobblePercent = isInt(config.scrobble.minimum.percent) ? Number(config.scrobble.minimum.percent) : 30;
|
const scrobblePercent = config.scrobble.minimum.percent || 50;
|
||||||
|
|
||||||
if (previous) {
|
const current = session.playing;
|
||||||
const newPlayback = current == null || current.playtime < previous.playtime;
|
const durationPlayed = now - (session.lastScrobbleTimestamp ?? session.started) - session.pauseDuration;
|
||||||
const canBeScrobbled = previous.playtime > scrobbleDuration * 1000 || previous.playtime / previous.duration > scrobblePercent;
|
const newPlayback = current == null || current.playtime < previous.playtime && current.playtime < timeDiff;
|
||||||
|
const canBeScrobbled = durationPlayed > scrobbleDuration * 1000 || durationPlayed / previous.duration > scrobblePercent / 100.0;
|
||||||
|
|
||||||
if (newPlayback && canBeScrobbled) {
|
return newPlayback && canBeScrobbled;
|
||||||
const sameSong = current != null && current.mediaKey == previous.mediaKey;
|
|
||||||
const lastTime = lastScrobbleTimes[previous.mediaKey];
|
|
||||||
return !sameSong || !lastTime || now - lastTime > scrobbleDuration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInt(value) {
|
function isInt(value) {
|
||||||
|
33
services/session-manager.js
Normal file
33
services/session-manager.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
class SessionManager {
|
||||||
|
constructor() {
|
||||||
|
this._sessions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
add(session) {
|
||||||
|
if (!session || !session.id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._sessions[session.id] = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
contains(sessionId) {
|
||||||
|
return this._sessions[sessionId] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(sessionId) {
|
||||||
|
return this._sessions[sessionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSessionIds() {
|
||||||
|
return Object.keys(this._sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(sessionId) {
|
||||||
|
if (!sessionId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
delete this._sessions[sessionId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SessionManager();
|
@ -34,7 +34,7 @@ async function refreshTokenIfNeeded() {
|
|||||||
}
|
}
|
||||||
await fs.writeFile("credentials.spotify.json", JSON.stringify(data));
|
await fs.writeFile("credentials.spotify.json", JSON.stringify(data));
|
||||||
token = data;
|
token = data;
|
||||||
logger.info("Updated access token for Spotify.");
|
logger.debug("Updated access token for Spotify.");
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
logger.error(ex, "Failed to get Spotify oauth.");
|
logger.error(ex, "Failed to get Spotify oauth.");
|
||||||
|
Loading…
Reference in New Issue
Block a user