Explorar el Código

Got stations semi-functional.

KrisVos130 hace 8 años
padre
commit
31bb3234c4

+ 3 - 1
backend/logic/actions/stations.js

@@ -93,7 +93,7 @@ module.exports = {
 
 				cache.client.hincrby('station.userCounts', stationId, 1, (err, userCount) => {
 					if (err) return cb({ status: 'error', message: 'An error occurred while joining the station' });
-					utils.socketJoinRoom(sessionId);
+					utils.socketJoinRoom(sessionId, `station.${stationId}`);
 					//TODO Emit to cache, listen on cache
 					cb({ status: 'success', currentSong: station.currentSong, startedAt: station.startedAt, paused: station.paused, timePaused: station.timePaused });
 				});
@@ -221,6 +221,7 @@ module.exports = {
 					description: data.description,
 					type: "official",
 					playlist: [defaultSong._id],
+					genres: ["edm"],
 					locked: true,
 					currentSong: defaultSong
 				}, next);
@@ -229,6 +230,7 @@ module.exports = {
 		], (err, station) => {
 			console.log(err, 123986);
 			if (err) return cb(err);
+			stations.calculateSongForStation(station);
 			cache.pub('station.create', data.name);
 			return cb(null, { 'status': 'success', 'message': 'Successfully created station.' });
 		});

+ 1 - 1
backend/logic/cache/index.js

@@ -103,7 +103,7 @@ const lib = {
 	hgetall: (table, cb, parseJson = true) => {
 		lib.client.hgetall(table, (err, obj) => {
 			if (err) return typeof cb === 'function' ? cb(err) : null;
-			if (parseJson) Object.keys(obj).forEach((key) => { try { obj[key] = JSON.parse(obj[key]); } catch (e) {} });
+			if (parseJson && obj) Object.keys(obj).forEach((key) => { try { obj[key] = JSON.parse(obj[key]); } catch (e) {} });
 			cb(null, obj);
 		});
 	},

+ 12 - 4
backend/logic/db/schemas/song.js

@@ -1,8 +1,16 @@
 module.exports = {
-	id: { type: String, unique: true, required: true },
+	_id: { type: String, unique: true, required: true },
 	title: { type: String, required: true },
 	artists: [{ type: String }],
-	duration: { type: String, required: true },
+	genres: [{ type: String }],
+	duration: { type: Number, required: true },
 	skipDuration: { type: Number, required: true },
-	thumbnail: { type: String, required: true }
-};
+	thumbnail: { type: String, required: true },
+	likes: { type: Number, required: true },
+	dislikes: { type: Number, required: true },
+	explicit: { type: Boolean, required: true },
+	requestedBy: { type: String, required: true },
+	requestedAt: { type: Date, required: true },
+	acceptedBy: { type: String, required: true },
+	acceptedAt: { type: Date, required: true }
+};

+ 2 - 0
backend/logic/db/schemas/station.js

@@ -10,6 +10,8 @@ module.exports = {
 		artists: [{ type: String }],
 		duration: { type: Number, required: true },
 		skipDuration: { type: Number, required: true },
+		likes: { type: Number, required: true },
+		dislikes: { type: Number, required: true },
 		thumbnail: { type: String, required: true }
 	},
 	currentSongIndex: { type: Number, default: 0, required: true },

+ 1 - 1
backend/logic/db/schemas/user.js

@@ -1,6 +1,6 @@
 module.exports = {
 	username: { type: String, required: true },
-	admin: { type: Boolean, default: false,required: true },
+	role: { type: String, default: 'default', required: true },
 	email: {
 		verified: { type: Boolean, default: false, required: true },
 		verificationToken: String,

+ 119 - 32
backend/logic/stations.js

@@ -4,9 +4,11 @@
 
 const cache = require('./cache');
 const db = require('./db');
+const io = require('./io');
 const utils = require('./utils');
 const notifications = require('./notifications');
 const async = require('async');
+let skipTimeout = null;
 
 module.exports = {
 
@@ -18,7 +20,6 @@ module.exports = {
 				stations.forEach((station) => {
 					console.log("Initing " + station._id);
 					_this.initializeAndReturnStation(station._id, (err, station) => {
-						console.log(err, station, 123456789);
 						//TODO Emit to homepage and admin station list
 					});
 				});
@@ -27,7 +28,47 @@ module.exports = {
 		});
 	},
 
-	initializeAndReturnStation: (stationId, cb) => {
+	calculateSongForStation: (station, cb) => {
+		let songList = [];
+		async.waterfall([
+
+			(next) => {
+				let genresDone = [];
+				station.genres.forEach((genre) => {
+					db.models.song.find({genres: genre}, (err, songs) => {
+						if (!err) {
+							songs.forEach((song) => {
+								if (songList.indexOf(song._id) === -1) songList.push(song._id);
+							});
+						}
+						genresDone.push(genre);
+						if (genresDone.length === station.genres.length) {
+							next();
+						}
+					});
+				});
+			},
+
+			(next) => {
+				let playlist = [];
+				songList.forEach(function(songId) {
+					if(station.playlist.indexOf(songId) === -1) playlist.push(songId);
+				});
+				station.playlist.filter((songId) => {
+					if (songList.indexOf(songId) !== -1) playlist.push(songId);
+				});
+				db.models.station.update({_id: station._id}, {$set: {playlist: playlist}}, (err, result) => {
+					next(err, playlist);
+				});
+			}
+
+		], (err, newPlaylist) => {
+			cb(err, newPlaylist);
+		});
+	},
+
+	initializeAndReturnStation: function(stationId, cb) {
+		let _this = this;
 		async.waterfall([
 
 			// first check the cache for the station
@@ -50,53 +91,99 @@ module.exports = {
 			if (err && err !== true) return cb(err);
 
 			// get notified when the next song for this station should play, so that we can notify our sockets
-			let notification = notifications.subscribe(`stations.nextSong?id=${station._id}`, () => {
+			/*let notification = notifications.subscribe(`stations.nextSong?id=${station._id}`, () => {*/
+			function skipSongTemp() {
 				// get the station from the cache
 				console.log('NOTIFICATION');
-				cache.hget('stations', station.name, (err, station) => {
+				//TODO Recalculate songs if the last song of the station playlist is getting played
+				cache.hget('stations', station._id, (err, station) => {
 					if (station) {
-						console.log(777);
 						// notify all the sockets on this station to go to the next song
-						io.to(`station.${stationId}`).emit("event:songs.next", {
-							currentSong: station.currentSong,
-							startedAt: station.startedAt,
-							paused: station.paused,
-							timePaused: 0
+						async.waterfall([
+
+							(next) => {
+								if (station.currentSongIndex < station.playlist.length - 1) {
+									station.currentSongIndex++;
+									db.models.song.findOne({_id: station.playlist[station.currentSongIndex]}, (err, song) => {
+										if (!err) {
+											station.currentSong = {
+												_id: song._id,
+												title: song.title,
+												artists: song.artists,
+												duration: song.duration,
+												likes: song.likes,
+												dislikes: song.dislikes,
+												skipDuration: song.skipDuration,
+												thumbnail: song.thumbnail
+											};
+											station.startedAt = Date.now();
+											next(null, station);
+										}
+									});
+								} else {
+									station.currentSongIndex = 0;
+									_this.calculateSongForStation(station, (err, newPlaylist) => {
+										if (!err) {
+											db.models.song.findOne({_id: newPlaylist[0]}, (err, song) => {
+												if (!err) {
+													station.currentSong = {
+														_id: song._id,
+														title: song.title,
+														artists: song.artists,
+														duration: song.duration,
+														likes: song.likes,
+														dislikes: song.dislikes,
+														skipDuration: song.skipDuration,
+														thumbnail: song.thumbnail
+													};
+													station.startedAt = Date.now();
+													station.playlist = newPlaylist;
+													next(null, station);
+												}
+											});
+										}
+									})
+								}
+							},
+
+							(station, next) => {
+								cache.hset('stations', station._id, station, (err) => next(err, station));
+								//TODO Also save to DB
+							},
+
+
+						], (err, station) => {
+							io.io.to(`station.${stationId}`).emit("event:songs.next", {
+								currentSong: station.currentSong,
+								startedAt: station.startedAt,
+								paused: station.paused,
+								timePaused: 0
+							});
+							// schedule a notification to be dispatched when the next song ends
+							notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
+							skipTimeout = setTimeout(skipSongTemp, station.currentSong.duration * 1000);
 						});
-						// schedule a notification to be dispatched when the next song ends
-						notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
 					}
 					// the station doesn't exist anymore, unsubscribe from it
 					else {
-						console.log(888);
 						notifications.remove(notification);
 					}
 				});
-			}, true);
+			}//, true);
 
 			if (!station.paused) {
-				console.log(station);
+				if (!station.startedAt) {
+					station.startedAt = Date.now();
+					cache.hset('stations', stationId, station);
+				}
+				//setTimeout(skipSongTemp, station.currentSong.duration * 1000);
+				if (skipTimeout === null) {
+					skipTimeout = setTimeout(skipSongTemp, 1000);
+				}
 				notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
 			}
 
 			return cb(null, station);
-
-			// will need to be added once station namespace thing is decided
-			// function generatePlaylist(arr) {
-			// 	station.playlist = [];
-			// 	return arr.reduce((promise, id) => {
-			// 		return promise.then(() => {
-			// 			return globals.db.models.song.findOne({ id }, (err, song) => {
-			// 				if (err) throw err;
-			// 				station.playlist.push(song);
-			// 			});
-			// 		});
-			// 	}, Promise.resolve());
-			// }
-
-			// generatePlaylist(station.playlist).then(() => {
-			// 	cb(null, station);
-			// });
 		});
 	}
 

+ 1 - 1
frontend/components/Admin/QueueSongs.vue

@@ -15,7 +15,7 @@
 					<tr v-for="(index, song) in songs" track-by="$index">
 						<td>
 							<p class="control">
-								<input class="input" type="text" :value="song.id" v-model="song.id">
+								<input class="input" type="text" :value="song._id" v-model="song._id">
 							</p>
 						</td>
 						<td>

+ 1 - 1
frontend/components/MainHeader.vue

@@ -55,7 +55,7 @@
 				if (!!_this.$parent.socket) {
 					_this.socket = _this.$parent.socket;
 					_this.socket.emit('users.findBySession', res => {
-						if (res.status == 'success') _this.isAdmin = res.data.admin;
+						if (res.status == 'success') _this.isAdmin = (res.data.role === 'admin');
 					});
 					clearInterval(socketInterval);
 				}

+ 22 - 16
frontend/components/Station/Station.vue

@@ -17,7 +17,7 @@
 				<!--<button v-if="!paused" @click="pauseStation()">Pause</button>-->
 				<div class="columns is-mobile">
 					<div class="column is-8-desktop is-12-mobile">
-						<h4 id="time-display">{{timeElapsed}} / {{currentSong.duration}}</h4>
+						<h4 id="time-display">{{timeElapsed}} / {{formatTime(currentSong.duration)}}</h4>
 						<h3>{{currentSong.title}}</h3>
 						<h4 class="thin" style="margin-left: 0">{{currentSong.artists}}</h4>
 						<div class="columns is-mobile">
@@ -92,7 +92,7 @@
 				player: undefined,
 				timePaused: 0,
 				paused: false,
-				timeElapsed: "00:00:00",
+				timeElapsed: "0:00",
 				interval: 0,
 				querySearch: "",
 				queryResults: [],
@@ -105,13 +105,15 @@
 			},
 			youtubeReady: function() {
 				let local = this;
+				console.log(123457)
 				local.player = new YT.Player("player", {
 					height: 270,
 					width: 480,
-					videoId: local.currentSong.id,
+					videoId: local.currentSong._id,
 					playerVars: {controls: 1, iv_load_policy: 3, rel: 0, showinfo: 0},
 					events: {
 						'onReady': function(event) {
+							console.log(4540590459);
 							local.playerReady = true;
 							let volume = parseInt(localStorage.getItem("volume"));
 							volume = (typeof volume === "number") ? volume : 20;
@@ -122,9 +124,11 @@
 							local.playVideo();
 						},
 						'onStateChange': function(event) {
+							console.log(event);
 							if (event.data === 1 && local.videoLoading === true) {
 								local.videoLoading = false;
 								local.player.seekTo(local.getTimeElapsed() / 1000, true);
+								console.log(local.paused);
 								if (local.paused) {
 									local.player.pauseVideo();
 								}
@@ -145,7 +149,7 @@
 				let local = this;
 				if (local.playerReady) {
 					local.videoLoading = true;
-					local.player.loadVideoById(local.currentSong.id);
+					local.player.loadVideoById(local.currentSong._id);
 
 					if (local.currentSong.artists) local.currentSong.artists = local.currentSong.artists.join(", ");
 
@@ -162,9 +166,13 @@
 			resizeSeekerbar: function() {
 				let local = this;
 				if (!local.paused) {
-					$(".seeker-bar").width(parseInt(((local.getTimeElapsed() / 1000) / parseInt(moment.duration(local.currentSong.duration, "hh:mm:ss").asSeconds()) * 100)) + "%");
+					$(".seeker-bar").width(parseInt(((local.getTimeElapsed() / 1000) / local.currentSong.duration * 100)) + "%");
 				}
 			},
+			formatTime: function(duration) {
+				let d = moment.duration(duration, 'seconds');
+				return ((d.hours() > 0) ? (d.hours() < 10 ? ("0" + d.hours() + ":") : (d.hours() + ":")) : "") + (d.minutes() + ":") + (d.seconds() < 10 ? ("0" + d.seconds()) : d.seconds());
+			},
 			calculateTimeElapsed: function() {
 				let local = this;
 				let currentTime = Date.now();
@@ -175,14 +183,15 @@
 				}
 
 				let duration = (Date.now() - local.startedAt - local.timePaused) / 1000;
-				let songDuration = moment.duration(local.currentSong.duration, "hh:mm:ss").asSeconds();
+				let songDuration = local.currentSong.duration;
 				if (songDuration <= duration) {
+					console.log("PAUSE!");
+					console.log(songDuration, duration);
 					local.player.pauseVideo();
 				}
 
-				let d = moment.duration(duration, 'seconds');
-				if ((!local.paused || local.timeElapsed === "00:00:00") && duration <= songDuration) {
-					local.timeElapsed = (d.hours() < 10 ? ("0" + d.hours() + ":") : (d.hours() + ":")) + (d.minutes() < 10 ? ("0" + d.minutes() + ":") : (d.minutes() + ":")) + (d.seconds() < 10 ? ("0" + d.seconds()) : d.seconds());
+				if ((!local.paused) && duration <= songDuration) {
+					local.timeElapsed = local.formatTime(duration);
 				}
 			},
 			changeVolume: function() {
@@ -217,7 +226,7 @@
 				local.socket.emit('queueSongs.add', songId, function(data) {
 					if (data) console.log(data);
 				});
-			},
+			}/*,
 			submitQuery: function() {
 				let local = this;
 				local.socket.emit('apis.searchYoutube', local.querySearch, function(results) {
@@ -232,7 +241,7 @@
 						});
 					}
 				});
-			}
+			}*/
 		},
 		ready: function() {
 			let _this = this;
@@ -256,11 +265,8 @@
 				}
 			});
 
-			_this.socket.on("SomeRoomMessage", function() {
-				console.log("SOME ROOM MESSAGE!!");
-			});
-
 			_this.socket.on('event:songs.next', data => {
+				console.log("NEXT SONG");
 				_this.currentSong = data.currentSong;
 				_this.startedAt = data.startedAt;
 				_this.paused = data.paused;
@@ -398,7 +404,7 @@
 				left: 0;
 				width: 100%;
 				height: 100%;
-				pointer-events: none;
+				/*pointer-events: none;*/
 			}
 		}
 		.video-col {

+ 2 - 2
frontend/components/pages/Home.vue

@@ -4,7 +4,7 @@
 		<div class="group" v-if="stations.official.length">
 			<div class="group-title">Official Stations</div>
 			<div class="group-stations">
-				<div class="stations-station" v-for="station in stations.official" v-link="{ path: '/official/' + station.name }" @click="this.$dispatch('joinStation', station.id)">
+				<div class="stations-station" v-for="station in stations.official" v-link="{ path: '/official/' + station._id }" @click="this.$dispatch('joinStation', station._id)">
 					<img class="station-image" :src="station.playlist[station.currentSongIndex].thumbnail" />
 					<div class="station-info">
 						<div class="station-grid-left">
@@ -21,7 +21,7 @@
 		<div class="group" v-if="stations.community.length">
 			<div class="group-title">Community Stations</div>
 			<div class="group-stations">
-				<div class="stations-station" v-for="station in stations.community" v-link="{ path: '/community/' + station.name }" @click="this.$dispatch('joinStation', station.id)">
+				<div class="stations-station" v-for="station in stations.community" v-link="{ path: '/community/' + station._id }" @click="this.$dispatch('joinStation', station._id)">
 					<img class="station-image" :src="station.playlist[station.currentSongIndex].thumbnail" />
 					<div class="station-info">
 						<div class="station-grid-left">