Browse Source

Switched station to skip locally automatically instead of directly from the backend

Kristian Vos 3 years ago
parent
commit
0e03cf99c6

+ 12 - 2
backend/logic/actions/apis.js

@@ -123,7 +123,12 @@ export default {
 	 * @param {Function} cb - callback
 	 */
 	joinRoom(session, page, cb) {
-		if (page === "home" || page.startsWith("profile.") || page.startsWith("manage-station.") || page.startsWith("edit-song.")) {
+		if (
+			page === "home" ||
+			page.startsWith("profile.") ||
+			page.startsWith("manage-station.") ||
+			page.startsWith("edit-song.")
+		) {
 			WSModule.runJob("SOCKET_JOIN_ROOM", {
 				socketId: session.socketId,
 				room: page
@@ -145,7 +150,12 @@ export default {
 	 * @param {Function} cb - callback
 	 */
 	leaveRoom(session, room, cb) {
-		if (room === "home" || room.startsWith("profile.") || room.startsWith("manage-station.") || room.startsWith("edit-song.")) {
+		if (
+			room === "home" ||
+			room.startsWith("profile.") ||
+			room.startsWith("manage-station.") ||
+			room.startsWith("edit-song.")
+		) {
 			WSModule.runJob("SOCKET_LEAVE_ROOM", {
 				socketId: session.socketId,
 				room

+ 31 - 0
backend/logic/actions/songs.js

@@ -322,6 +322,37 @@ export default {
 		);
 	}),
 
+	/**
+	 * Updates all songs
+	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param cb
+	 */
+	updateAll: isAdminRequired(async function length(session, cb) {
+		async.waterfall(
+			[
+				next => {
+					SongsModule.runJob("UPDATE_ALL_SONGS", {})
+						.then(() => {
+							next();
+						})
+						.catch(err => {
+							next(err);
+						});
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "SONGS_UPDATE_ALL", `Failed to update all songs. "${err}"`);
+					return cb({ status: "error", message: err });
+				}
+				this.log("SUCCESS", "SONGS_UPDATE_ALL", `Updated all songs successfully.`);
+				return cb({ status: "success", message: "Successfully updated all songs." });
+			}
+		);
+	}),
+
 	/**
 	 * Gets a song from the YouTube song id
 	 *

+ 113 - 8
backend/logic/actions/stations.js

@@ -1013,6 +1013,86 @@ export default {
 		);
 	},
 
+	getNextSongInfo(session, stationId, songId, cb) {
+		async.waterfall(
+			[
+				next => {
+					StationsModule.runJob("GET_STATION", { stationId }, this)
+						.then(station => next(null, station))
+						.catch(err => {
+							next(err);
+						});
+				},
+
+				(station, next) => {
+					if (!station) return next("Station not found.");
+					return StationsModule.runJob("CAN_USER_VIEW_STATION", { station, userId: session.userId }, this)
+						.then(canView => {
+							if (!canView) next("Not allowed to get info about station.");
+							else next(null, station);
+						})
+						.catch(err => {
+							console.log(stationId, station, songId);
+							next(err);
+						});
+				},
+
+				(station, next) => {
+					if (
+						station.currentSong._id === songId ||
+						(station.queue.length > 0 && station.queue[0]._id === songId) ||
+						(station.queue.length > 1 && station.queue[1]._id === songId)
+					) {
+						next(null);
+					} else next("That song is not next.");
+				},
+
+				next => {
+					SongsModule.runJob("GET_SONG", { songId }, this)
+						.then(response => {
+							const { song } = response;
+							const nextSong = {
+								_id: song._id,
+								youtubeId: song.youtubeId,
+								title: song.title,
+								artists: song.artists,
+								duration: song.duration,
+								likes: song.likes,
+								dislikes: song.dislikes,
+								skipDuration: song.skipDuration,
+								thumbnail: song.thumbnail,
+								requestedAt: song.requestedAt,
+								requestedBy: song.requestedBy,
+								status: song.status
+							};
+							next(null, { nextSong });
+						})
+						.catch(err => {
+							next(err);
+						});
+				}
+			],
+			async (err, data) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"STATIONS_GET_NEXT_SONG_INFO",
+						`Getting next song info for station "${stationId}" failed. "${err}"`
+					);
+					return cb({ status: "error", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"STATIONS_GET_NEXT_SONG_INFO",
+					`Got next song info for station "${stationId}" successfully.`
+				);
+				return cb({ status: "success", data });
+			}
+		);
+	},
+
 	/**
 	 * Gets a station by id
 	 *
@@ -1346,14 +1426,14 @@ export default {
 				(station, next) => {
 					skipVotes = station.currentSong.skipVotes.length;
 					WSModule.runJob("GET_SOCKETS_FOR_ROOM", { room: `station.${stationId}` }, this)
-						.then(sockets => next(null, sockets))
+						.then(sockets => next(null, station, sockets))
 						.catch(next);
 				},
 
-				(sockets, next) => {
+				(station, sockets, next) => {
 					if (sockets.length <= skipVotes) {
 						shouldSkip = true;
-						return next();
+						return next(null, station);
 					}
 
 					const users = [];
@@ -1374,12 +1454,12 @@ export default {
 							if (err) return next(err);
 
 							if (users.length <= skipVotes) shouldSkip = true;
-							return next();
+							return next(null, station);
 						}
 					);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "STATIONS_VOTE_SKIP", `Vote skipping station "${stationId}" failed. "${err}"`);
@@ -1392,7 +1472,21 @@ export default {
 					value: stationId
 				});
 
-				if (shouldSkip) StationsModule.runJob("SKIP_STATION", { stationId });
+				if (shouldSkip) {
+					console.log(111, station);
+					WSModule.runJob("EMIT_TO_ROOM", {
+						room: `station.${station._id}`,
+						args: [
+							"event:songs.skip",
+							{
+								data: {
+									skippedSong: station.currentSong
+								}
+							}
+						]
+					});
+					StationsModule.runJob("SKIP_STATION", { stationId });
+				}
 
 				return cb({
 					status: "success",
@@ -1422,15 +1516,26 @@ export default {
 
 				(station, next) => {
 					if (!station) return next("Station not found.");
-					return next();
+					return next(null, station);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "STATIONS_FORCE_SKIP", `Force skipping station "${stationId}" failed. "${err}"`);
 					return cb({ status: "error", message: err });
 				}
+				WSModule.runJob("EMIT_TO_ROOM", {
+					room: `station.${station._id}`,
+					args: [
+						"event:songs.skip",
+						{
+							data: {
+								skippedSong: station.currentSong
+							}
+						}
+					]
+				});
 				StationsModule.runJob("SKIP_STATION", { stationId });
 				this.log("SUCCESS", "STATIONS_FORCE_SKIP", `Force skipped station "${stationId}" successfully.`);
 				return cb({

+ 45 - 0
backend/logic/songs.js

@@ -371,6 +371,51 @@ class _SongsModule extends CoreClass {
 		);
 	}
 
+	/**
+	 * Updates all songs
+	 *
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	UPDATE_ALL_SONGS() {
+		return new Promise((resolve, reject) =>
+			async.waterfall(
+				[
+					next => {
+						return next("Currently disabled since it's broken due to the backend memory leak issue.");
+						SongsModule.SongModel.find({}, next);
+					},
+
+					(songs, next) => {
+						let index = 0;
+						const { length } = songs;
+						async.eachLimit(
+							songs,
+							10,
+							(song, next) => {
+								index += 1;
+								console.log(`Updating song #${index} out of ${length}: ${song._id}`);
+								SongsModule.runJob("UPDATE_SONG", { songId: song._id }, this, 9)
+									.then(() => {
+										next();
+									})
+									.catch(err => {
+										next(err);
+									});
+							},
+							err => {
+								next(err);
+							}
+						);
+					}
+				],
+				err => {
+					if (err && err !== true) return reject(new Error(err));
+					return resolve();
+				}
+			)
+		);
+	}
+
 	/**
 	 * Deletes song from id from Mongo and cache
 	 *

+ 15 - 0
frontend/src/pages/Admin/tabs/VerifiedSongs.vue

@@ -27,6 +27,15 @@
 			>
 				Keyboard shortcuts helper
 			</button>
+			<!-- <confirm placement="bottom" @confirm="updateAllSongs()">
+				<button
+					class="button is-danger"
+					content="Update all songs"
+					v-tippy
+				>
+					Update all songs
+				</button>
+			</confirm> -->
 			<br />
 			<div>
 				<input
@@ -354,6 +363,12 @@ export default {
 				else new Toast(res.message);
 			});
 		},
+		updateAllSongs() {
+			this.socket.dispatch("songs.updateAll", res => {
+				if (res.status === "success") new Toast(res.message);
+				else new Toast(res.message);
+			});
+		},
 		getSet() {
 			if (this.isGettingSet) return;
 			if (this.position >= this.maxPosition) return;

+ 175 - 104
frontend/src/pages/Station/index.vue

@@ -760,55 +760,29 @@ export default {
 			}
 		);
 
-		this.socket.on("event:songs.next", res => {
-			const { currentSong } = res.data;
-
-			this.updateCurrentSong(currentSong || {});
-
-			let nextSong = null;
-			if (this.songsList[1]) {
-				nextSong = this.songsList[1].youtubeId
-					? this.songsList[1]
-					: null;
+		this.socket.on("event:songs.skip", res => {
+			const { skippedSong } = res.data;
+			if (this.currentSong._id === skippedSong._id) {
+				this.skipSong();
 			}
-			this.updateNextSong(nextSong);
-
-			this.startedAt = res.data.startedAt;
-			this.updateStationPaused(res.data.paused);
-			this.timePaused = res.data.timePaused;
-
-			if (currentSong) {
-				this.updateNoSong(false);
-
-				if (!this.playerReady) this.youtubeReady();
-				else this.playVideo();
-
-				this.socket.dispatch(
-					"songs.getOwnSongRatings",
-					currentSong.youtubeId,
-					res => {
-						if (
-							res.status === "success" &&
-							this.currentSong.youtubeId === res.data.youtubeId
-						) {
-							this.liked = res.data.liked;
-							this.disliked = res.data.disliked;
+		});
 
-							if (
-								this.autoSkipDisliked &&
-								res.data.disliked === true
-							) {
-								this.voteSkipStation();
-								new Toast(
-									"Automatically voted to skip disliked song."
-								);
-							}
-						}
-					}
-				);
-			} else {
-				if (this.playerReady) this.player.pauseVideo();
-				this.updateNoSong(true);
+		this.socket.on("event:songs.next", res => {
+			const { currentSong, startedAt, paused, timePaused } = res.data;
+			if (this.noSong) {
+				this.setCurrentSong({
+					currentSong,
+					startedAt,
+					paused,
+					timePaused,
+					pausedAt: 0
+				});
+			} else if (
+				this.nextSong &&
+				currentSong &&
+				this.nextSong._id === currentSong._id
+			) {
+				this.setNextSong(currentSong);
 			}
 		});
 
@@ -816,12 +790,20 @@ export default {
 			this.pausedAt = res.data.pausedAt;
 			this.updateStationPaused(true);
 			this.pauseLocalPlayer();
+
+			clearTimeout(window.stationNextSongTimeout);
 		});
 
 		this.socket.on("event:stations.resume", res => {
 			this.timePaused = res.data.timePaused;
 			this.updateStationPaused(false);
 			if (!this.localPaused) this.resumeLocalPlayer();
+
+			if (this.currentSong)
+				window.stationNextSongTimeout = setTimeout(
+					this.skipSong,
+					this.getTimeRemaining()
+				);
 		});
 
 		this.socket.on("event:stations.remove", () => {
@@ -883,7 +865,7 @@ export default {
 					? this.songsList[0]
 					: null;
 
-			this.updateNextSong(nextSong);
+			this.setNextSong(nextSong);
 
 			this.addPartyPlaylistSongToQueue();
 		});
@@ -897,7 +879,7 @@ export default {
 					? this.songsList[0]
 					: null;
 
-			this.updateNextSong(nextSong);
+			this.setNextSong(nextSong);
 		});
 
 		this.socket.on("event:song.voteSkipSong", () => {
@@ -1011,6 +993,7 @@ export default {
 		});
 
 		clearInterval(this.activityWatchVideoDataInterval);
+		clearTimeout(window.stationNextSongTimeout);
 
 		this.socket.dispatch("stations.leave", this.station._id, () => {});
 
@@ -1038,6 +1021,126 @@ export default {
 				}
 			);
 		},
+		setNextSong(nextSong) {
+			if (
+				nextSong &&
+				(!this.nextSong || this.nextSong._id !== nextSong._id) &&
+				(!this.noSong && nextSong._id !== this.currentSong._id)
+			) {
+				this.updateNextSong(nextSong);
+
+				this.socket.dispatch(
+					"stations.getNextSongInfo",
+					this.station._id,
+					nextSong._id,
+					data => {
+						if (data.status === "success") {
+							if (
+								this.nextSong &&
+								this.nextSong._id === nextSong._id
+							) {
+								this.updateNextSong(data.data.nextSong);
+							}
+						}
+					}
+				);
+			}
+			if (!nextSong) {
+				this.updateNextSong(null);
+			}
+		},
+		skipSong() {
+			if (this.nextSong) {
+				this.setCurrentSong({
+					currentSong: {
+						...this.nextSong,
+						skipVotes: 0
+					},
+					startedAt: Date.now(),
+					paused: this.stationPaused,
+					timePaused: 0,
+					pausedAt: 0
+				});
+			} else {
+				this.setCurrentSong({
+					currentSong: null,
+					startedAt: 0,
+					paused: this.stationPaused,
+					timePaused: 0,
+					pausedAt: 0
+				});
+			}
+		},
+		setCurrentSong(data) {
+			const {
+				currentSong,
+				startedAt,
+				paused,
+				timePaused,
+				pausedAt
+			} = data;
+
+			this.updateCurrentSong(currentSong || {});
+
+			if (
+				currentSong &&
+				this.nextSong &&
+				currentSong._id === this.nextSong._id
+			) {
+				this.setNextSong(null);
+			}
+
+			clearTimeout(window.stationNextSongTimeout);
+
+			this.startedAt = startedAt;
+			this.updateStationPaused(paused);
+			this.timePaused = timePaused;
+			this.pausedAt = pausedAt;
+
+			if (currentSong) {
+				this.updateNoSong(false);
+
+				if (!this.playerReady) this.youtubeReady();
+				else this.playVideo();
+
+				if (!this.stationPaused) {
+					window.stationNextSongTimeout = setTimeout(
+						this.skipSong,
+						this.getTimeRemaining()
+					);
+				}
+
+				this.socket.dispatch(
+					"songs.getOwnSongRatings",
+					currentSong.youtubeId,
+					res => {
+						if (
+							res.status === "success" &&
+							this.currentSong.youtubeId === res.data.youtubeId
+						) {
+							this.liked = res.data.liked;
+							this.disliked = res.data.disliked;
+
+							if (
+								this.autoSkipDisliked &&
+								res.data.disliked === true
+							) {
+								this.voteSkipStation();
+								new Toast(
+									"Automatically voted to skip disliked song."
+								);
+							}
+						}
+					}
+				);
+			} else {
+				if (this.playerReady) this.player.pauseVideo();
+				this.updateNoSong(true);
+			}
+
+			this.calculateTimeElapsed();
+			this.resizeSeekerbar();
+		},
 		youtubeReady() {
 			if (!this.player) {
 				this.player = new window.YT.Player("stationPlayer", {
@@ -1174,6 +1277,12 @@ export default {
 			}
 			return 0;
 		},
+		getTimeRemaining() {
+			if (this.currentSong) {
+				return this.currentSong.duration * 1000 - this.getTimeElapsed();
+			}
+			return 0;
+		},
 		playVideo() {
 			if (this.playerReady) {
 				this.videoLoading = true;
@@ -1185,18 +1294,17 @@ export default {
 				if (window.stationInterval !== 0)
 					clearInterval(window.stationInterval);
 				window.stationInterval = setInterval(() => {
-					this.resizeSeekerbar();
-					this.calculateTimeElapsed();
+					if (!this.stationPaused) {
+						this.resizeSeekerbar();
+						this.calculateTimeElapsed();
+					}
 				}, 150);
 			}
 		},
 		resizeSeekerbar() {
-			if (!this.stationPaused) {
-				this.seekerbarPercentage = parseFloat(
-					(this.getTimeElapsed() / 1000 / this.currentSong.duration) *
-						100
-				);
-			}
+			this.seekerbarPercentage = parseFloat(
+				(this.getTimeElapsed() / 1000 / this.currentSong.duration) * 100
+			);
 		},
 		calculateTimeElapsed() {
 			if (
@@ -1225,7 +1333,7 @@ export default {
 				}
 			}
 
-			if (!this.stationPaused && !this.localPaused) {
+			if (!this.stationPaused && !this.localPaused && this.playerReady) {
 				const timeElapsed = this.getTimeElapsed();
 				const currentPlayerTime =
 					Math.max(
@@ -1298,7 +1406,7 @@ export default {
 
 			const songDuration = this.currentSong.duration;
 			if (songDuration <= duration) this.player.pauseVideo();
-			if (!this.stationPaused && duration <= songDuration)
+			if (duration <= songDuration)
 				this.timeElapsed = utils.formatTime(duration);
 		},
 		toggleLock() {
@@ -1560,53 +1668,16 @@ export default {
 
 						document.body.style.cssText = `--primary-color: var(--${res.data.theme})`;
 
-						const currentSong = res.data.currentSong
-							? res.data.currentSong
-							: {};
-
-						this.updateCurrentSong(currentSong);
+						this.setCurrentSong({
+							currentSong: res.data.currentSong,
+							startedAt: res.data.startedAt,
+							paused: res.data.paused,
+							timePaused: res.data.timePaused,
+							pausedAt: res.data.pausedAt
+						});
 
-						this.startedAt = res.data.startedAt;
-						this.updateStationPaused(res.data.paused);
-						this.timePaused = res.data.timePaused;
 						this.updateUserCount(res.data.userCount);
 						this.updateUsers(res.data.users);
-						this.pausedAt = res.data.pausedAt;
-
-						if (res.data.currentSong) {
-							this.updateNoSong(false);
-							this.youtubeReady();
-							this.playVideo();
-							this.socket.dispatch(
-								"songs.getOwnSongRatings",
-								res.data.currentSong.youtubeId,
-								res => {
-									if (
-										res.status === "success" &&
-										this.currentSong.youtubeId ===
-											res.data.youtubeId
-									) {
-										this.liked = res.data.liked;
-										this.disliked = res.data.disliked;
-									}
-								}
-							);
-						} else {
-							if (this.playerReady) this.player.pauseVideo();
-							this.updateNoSong(true);
-						}
-
-						this.socket.dispatch(
-							"stations.getStationIncludedPlaylistsById",
-							this.station._id,
-							res => {
-								if (res.status === "success") {
-									this.setIncludedPlaylists(
-										res.data.playlists
-									);
-								}
-							}
-						);
 
 						this.socket.dispatch(
 							"stations.getStationExcludedPlaylistsById",
@@ -1629,7 +1700,7 @@ export default {
 										? this.songsList[0]
 										: null;
 								}
-								this.updateNextSong(nextSong);
+								this.setNextSong(nextSong);
 							}
 						});
 

+ 14 - 0
frontend/src/store/modules/station.js

@@ -79,6 +79,20 @@ const mutations = {
 	leaveStation(state) {
 		state.station = {};
 		state.partyPlaylists = [];
+		state.editing = {};
+		state.userCount = 0;
+		state.users = {
+			loggedIn: [],
+			loggedOut: []
+		};
+		state.currentSong = {};
+		state.nextSong = null;
+		state.songsList = [];
+		state.stationPaused = true;
+		state.localPaused = false;
+		state.noSong = true;
+		state.includedPlaylists = [];
+		state.excludedPlaylists = [];
 	},
 	editStation(state, station) {
 		state.editing = { ...station };