Browse Source

feat: added experimental logic to have a station not add songs via autofill that were played recently

Kristian Vos 1 year ago
parent
commit
b0701dddd9
2 changed files with 108 additions and 4 deletions
  1. 60 0
      backend/logic/cache/index.js
  2. 48 4
      backend/logic/stations.js

+ 60 - 0
backend/logic/cache/index.js

@@ -386,6 +386,66 @@ class _CacheModule extends CoreClass {
 		});
 	}
 
+	/**
+	 * Adds a value to a list in Redis using LPUSH
+	 *
+	 * @param {object} payload - object containing payload
+	 * @param {string} payload.key -  name of the list
+	 * @param {*} payload.value - the value we want to set
+	 * @param {boolean} [payload.stringifyJson=true] - stringify 'value' if it's an Object or Array
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	LPUSH(payload) {
+		return new Promise((resolve, reject) => {
+			let { key, value } = payload;
+
+			if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
+			// automatically stringify objects and arrays into JSON
+			if (["object", "array"].includes(typeof value)) value = JSON.stringify(value);
+
+			CacheModule.client
+				.LPUSH(key, value)
+				.then(() => resolve())
+				.catch(err => reject(new Error(err)));
+		});
+	}
+
+	/**
+	 * Gets the length of a Redis list
+	 *
+	 * @param {object} payload - object containing payload
+	 * @param {string} payload.key -  name of the list
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	LLEN(payload) {
+		return new Promise((resolve, reject) => {
+			const { key } = payload;
+
+			CacheModule.client
+				.LLEN(key)
+				.then(len => resolve(len))
+				.catch(err => reject(new Error(err)));
+		});
+	}
+
+	/**
+	 * Removes an item from a list using RPOP
+	 *
+	 * @param {object} payload - object containing payload
+	 * @param {string} payload.key -  name of the list
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	RPOP(payload) {
+		return new Promise((resolve, reject) => {
+			const { key } = payload;
+
+			CacheModule.client
+				.RPOP(key)
+				.then(() => resolve())
+				.catch(err => reject(new Error(err)));
+		});
+	}
+
 	/**
 	 * Removes a value from a list in Redis
 	 *

+ 48 - 4
backend/logic/stations.js

@@ -547,13 +547,22 @@ class _StationsModule extends CoreClass {
 						const currentRequests = station.queue.filter(song => !song.requestedBy).length;
 						const songsStillNeeded = station.autofill.limit - currentRequests;
 						const currentSongs = station.queue;
-						const currentYoutubeIds = station.queue.map(song => song.youtubeId);
+						let currentYoutubeIds = station.queue.map(song => song.youtubeId);
 						const songsToAdd = [];
 						let lastSongAdded = null;
 
 						if (station.currentSong && station.currentSong.youtubeId)
 							currentYoutubeIds.push(station.currentSong.youtubeId);
 
+						// Block for experiment: queue_autofill_skip_last_x_played
+						if (config.has(`experimental.queue_autofill_skip_last_x_played.${stationId}`)) {
+							const redisList = `experimental:queue_autofill_skip_last_x_played:${stationId}`;
+							// Get list of last x youtube video's played, to make sure they can't be autofilled
+							const listOfYoutubeIds = await CacheModule.runJob("LRANGE", { key: redisList }, this);
+							currentYoutubeIds = [...currentYoutubeIds, ...listOfYoutubeIds];
+						}
+
+						// Block for experiment: weight_stations
 						if (
 							config.has("experimental.weight_stations") &&
 							config.get("experimental.weight_stations").indexOf(stationId) !== -1
@@ -978,11 +987,46 @@ class _StationsModule extends CoreClass {
 							});
 					},
 
-					(song, station, next) => {
+					async (song, station) => {
 						const $set = {};
 
 						if (song === null) $set.currentSong = null;
 						else {
+							// Block for experiment: queue_autofill_skip_last_x_played
+							if (config.has(`experimental.queue_autofill_skip_last_x_played.${payload.stationId}`)) {
+								const redisList = `experimental:queue_autofill_skip_last_x_played:${payload.stationId}`;
+								const maxListLength = Number(
+									config.get(`experimental.queue_autofill_skip_last_x_played.${payload.stationId}`)
+								);
+
+								// Add youtubeId to list for this station in Redis list
+								await CacheModule.runJob(
+									"LPUSH",
+									{
+										key: `experimental:queue_autofill_skip_last_x_played:${payload.stationId}`,
+										value: song.youtubeId
+									},
+									this
+								);
+
+								const currentListLength = await CacheModule.runJob("LLEN", { key: redisList }, this);
+
+								// Removes oldest youtubeId from list for this station in Redis list
+								if (currentListLength > maxListLength) {
+									const amount = currentListLength - maxListLength;
+									const promises = Array.from({ length: amount }).map(() =>
+										CacheModule.runJob(
+											"RPOP",
+											{
+												key: `experimental:queue_autofill_skip_last_x_played:${payload.stationId}`
+											},
+											this
+										)
+									);
+									await Promise.all(promises);
+								}
+							}
+
 							$set.currentSong = {
 								_id: song._id,
 								youtubeId: song.youtubeId,
@@ -1000,10 +1044,10 @@ class _StationsModule extends CoreClass {
 						$set.startedAt = Date.now();
 						$set.timePaused = 0;
 						if (station.paused) $set.pausedAt = Date.now();
-						next(null, $set, station);
+						return { $set, station };
 					},
 
-					($set, station, next) => {
+					({ $set, station }, next) => {
 						StationsModule.stationModel.updateOne({ _id: station._id }, { $set }, err => {
 							if (err) return next(err);