Browse Source

feat: added user station state display

Kristian Vos 1 year ago
parent
commit
ca6598c277

+ 6 - 0
backend/logic/actions/stations.js

@@ -2786,5 +2786,11 @@ export default {
 				return cb({ status: "success", message: "Successfully removed DJ." });
 			}
 		);
+	},
+
+	setStationState(session, newStationState, cb) {
+		session.stationState = newStationState;
+
+		cb({ status: "success" });
 	}
 };

+ 26 - 2
backend/logic/tasks.js

@@ -15,6 +15,17 @@ let UtilsModule;
 let WSModule;
 let DBModule;
 
+const stationStateWorth = {
+	unknown: 0,
+	no_song: 1,
+	station_paused: 2,
+	participate: 3,
+	local_paused: 4,
+	muted: 5,
+	buffering: 6,
+	playing: 7
+};
+
 class _TasksModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
 	constructor() {
@@ -403,8 +414,20 @@ class _TasksModule extends CoreClass {
 								(user, next) => {
 									if (!user) return next("User not found.");
 
-									if (usersPerStation[stationId].loggedIn.some(u => user.username === u.username))
+									const state = socket.session.stationState ?? "unknown";
+
+									const existingUserObject = usersPerStation[stationId].loggedIn.findLast(
+										u => user.username === u.username
+									);
+
+									if (existingUserObject) {
+										if (stationStateWorth[state] > stationStateWorth[existingUserObject.state]) {
+											usersPerStation[stationId].loggedIn[
+												usersPerStation[stationId].loggedIn.indexOf(existingUserObject)
+											].state = state;
+										}
 										return next("User already in the list.");
+									}
 
 									usersPerStationCount[stationId] += 1; // increment user count for station
 
@@ -412,7 +435,8 @@ class _TasksModule extends CoreClass {
 										_id: user._id,
 										username: user.username,
 										name: user.name,
-										avatar: user.avatar
+										avatar: user.avatar,
+										state
 									});
 								}
 							],

+ 122 - 69
frontend/src/pages/Station/Sidebar/Users.vue

@@ -212,55 +212,97 @@ onMounted(async () => {
 								}"
 								target="_blank"
 							>
-								<profile-picture
-									:avatar="user.avatar"
-									:name="user.name || user.username"
-								/>
-
-								{{ user.name || user.username }}
-
-								<span
-									v-if="isOwner(user._id)"
-									class="material-icons user-rank"
-									content="Station Owner"
-									v-tippy="{ theme: 'info' }"
-									>local_police</span
-								>
-								<span
-									v-else-if="isDj(user._id)"
-									class="material-icons user-rank"
-									content="Station DJ"
-									v-tippy="{ theme: 'info' }"
-									>shield</span
-								>
-
-								<button
-									v-if="
-										hasPermission('stations.djs.add') &&
-										station.type === 'community' &&
-										!isDj(user._id) &&
-										!isOwner(user._id)
-									"
-									class="button is-primary material-icons"
-									@click.prevent="addDj(user._id)"
-									content="Promote user to DJ"
-									v-tippy
-								>
-									add_moderator
-								</button>
-								<button
-									v-else-if="
-										hasPermission('stations.djs.remove') &&
-										station.type === 'community' &&
-										isDj(user._id)
-									"
-									class="button is-danger material-icons"
-									@click.prevent="removeDj(user._id)"
-									content="Demote user from DJ"
-									v-tippy
-								>
-									remove_moderator
-								</button>
+								<div class="left">
+									<profile-picture
+										:avatar="user.avatar"
+										:name="user.name || user.username"
+									/>
+
+									{{ user.name || user.username }}
+
+									<span
+										v-if="isOwner(user._id)"
+										class="material-icons user-rank"
+										content="Station Owner"
+										v-tippy="{ theme: 'info' }"
+										>local_police</span
+									>
+									<span
+										v-else-if="isDj(user._id)"
+										class="material-icons user-rank"
+										content="Station DJ"
+										v-tippy="{ theme: 'info' }"
+										>shield</span
+									>
+
+									<button
+										v-if="
+											hasPermission('stations.djs.add') &&
+											station.type === 'community' &&
+											!isDj(user._id) &&
+											!isOwner(user._id)
+										"
+										class="button is-primary material-icons"
+										@click.prevent="addDj(user._id)"
+										content="Promote user to DJ"
+										v-tippy
+									>
+										add_moderator
+									</button>
+									<button
+										v-else-if="
+											hasPermission(
+												'stations.djs.remove'
+											) &&
+											station.type === 'community' &&
+											isDj(user._id)
+										"
+										class="button is-danger material-icons"
+										@click.prevent="removeDj(user._id)"
+										content="Demote user from DJ"
+										v-tippy
+									>
+										remove_moderator
+									</button>
+								</div>
+
+								<div class="user-state">
+									<i
+										class="material-icons"
+										v-if="user.state === 'participate'"
+										v-tippy
+										content="Participating"
+										>group</i
+									>
+									<i
+										class="material-icons"
+										v-if="user.state === 'local_paused'"
+										v-tippy
+										content="Paused"
+										>pause</i
+									>
+									<i
+										class="material-icons"
+										v-if="user.state === 'muted'"
+										v-tippy
+										content="Muted"
+										>volume_mute</i
+									>
+									<i
+										class="material-icons"
+										v-if="user.state === 'playing'"
+										v-tippy
+										content="Listening to music"
+										>play_arrow</i
+									>
+									<i
+										class="material-icons"
+										v-if="user.state === 'buffering'"
+										v-tippy
+										content="Buffering"
+										>warning</i
+									>
+								</div>
 							</router-link>
 						</li>
 					</ul>
@@ -517,6 +559,7 @@ onMounted(async () => {
 
 					a {
 						display: flex;
+						flex-direction: row;
 						align-items: center;
 						padding: 5px 10px;
 						border: 0.5px var(--light-grey-3) solid;
@@ -528,28 +571,38 @@ onMounted(async () => {
 							color: var(--black);
 						}
 
-						.profile-picture {
-							margin-right: 10px;
-							width: 36px;
-							height: 36px;
-						}
-
-						:deep(.profile-picture.using-initials span) {
-							font-size: calc(
-								36px / 5 * 2
-							); // 2/5th of .profile-picture height/width
-						}
-
-						.user-rank {
-							color: var(--primary-color);
-							font-size: 18px;
-							margin: 0 5px;
+						.left {
+							display: flex;
+							align-items: center;
+							flex: 1;
+
+							.profile-picture {
+								margin-right: 10px;
+								width: 36px;
+								height: 36px;
+							}
+
+							:deep(.profile-picture.using-initials span) {
+								font-size: calc(
+									36px / 5 * 2
+								); // 2/5th of .profile-picture height/width
+							}
+
+							.user-rank {
+								color: var(--primary-color);
+								font-size: 18px;
+								margin: 0 5px;
+							}
+
+							.button {
+								margin-left: auto;
+								font-size: 18px;
+								width: 36px;
+							}
 						}
 
-						.button {
-							margin-left: auto;
-							font-size: 18px;
-							width: 36px;
+						.user-state {
+							display: flex;
 						}
 					}
 				}

+ 54 - 0
frontend/src/pages/Station/index.vue

@@ -80,6 +80,7 @@ const activityWatchVideoDataInterval = ref(null);
 const activityWatchVideoLastStatus = ref("");
 const activityWatchVideoLastYouTubeId = ref("");
 const activityWatchVideoLastStartDuration = ref(0);
+const reportStationStateInterval = ref(null);
 const nextCurrentSong = ref(null);
 const mediaModalWatcher = ref(null);
 const beforeMediaModalLocalPausedLock = ref(false);
@@ -124,6 +125,10 @@ const {
 	history
 } = storeToRefs(stationStore);
 
+const youtubePlayerState = ref<
+	null | "UNSTARTED" | "ENDED" | "PLAYING" | "PAUSED" | "BUFFERING" | "CUED"
+>(null);
+
 const skipVotesLoaded = computed(
 	() =>
 		!noSong.value &&
@@ -152,6 +157,23 @@ const currentUserQueueSongs = computed(
 		).length
 );
 
+const stationState = computed(() => {
+	if (noSong.value) return "no_song";
+	if (stationPaused.value) return "station_paused";
+	if (
+		experimentalChangableListenModeEnabled.value &&
+		experimentalChangableListenMode.value === "participate"
+	)
+		return "participate";
+	if (localPaused.value) return "local_paused";
+	if (volumeSliderValue.value === 0 || muted.value) return "muted";
+	if (playerReady.value && youtubePlayerState.value === "PLAYING")
+		return "playing";
+	if (playerReady.value && youtubePlayerState.value === "BUFFERING")
+		return "buffering";
+	return "unknown";
+});
+
 const {
 	joinStation,
 	leaveStation,
@@ -533,6 +555,29 @@ const youtubeReady = () => {
 					}
 				},
 				onStateChange: event => {
+					switch (event.data) {
+						case window.YT.PlayerState.UNSTARTED:
+							youtubePlayerState.value = "UNSTARTED";
+							break;
+						case window.YT.PlayerState.ENDED:
+							youtubePlayerState.value = "ENDED";
+							break;
+						case window.YT.PlayerState.PLAYING:
+							youtubePlayerState.value = "PLAYING";
+							break;
+						case window.YT.PlayerState.PAUSED:
+							youtubePlayerState.value = "PAUSED";
+							break;
+						case window.YT.PlayerState.BUFFERING:
+							youtubePlayerState.value = "BUFFERING";
+							break;
+						case window.YT.PlayerState.CUED:
+							youtubePlayerState.value = "CUED";
+							break;
+						default:
+							youtubePlayerState.value = null;
+					}
+
 					if (
 						event.data === window.YT.PlayerState.PLAYING &&
 						videoLoading.value === true
@@ -920,6 +965,7 @@ const experimentalChangableListenModeChange = newMode => {
 			player.value.destroy();
 			player.value = null;
 			playerReady.value = false;
+			youtubePlayerState.value = null;
 		}
 	} else {
 		// Recreate the YouTube player
@@ -960,6 +1006,13 @@ onMounted(async () => {
 	activityWatchVideoDataInterval.value = setInterval(() => {
 		sendActivityWatchVideoData();
 	}, 1000);
+	reportStationStateInterval.value = setInterval(() => {
+		socket.dispatch(
+			"stations.setStationState",
+			stationState.value,
+			() => {}
+		);
+	}, 5000);
 	persistentToastCheckerInterval.value = setInterval(() => {
 		persistentToasts.value.filter(
 			persistentToast => !persistentToast.checkIfCanRemove()
@@ -1579,6 +1632,7 @@ onBeforeUnmount(() => {
 	clearInterval(activityWatchVideoDataInterval.value);
 	clearTimeout(window.stationNextSongTimeout);
 	clearTimeout(persistentToastCheckerInterval.value);
+	clearInterval(reportStationStateInterval.value);
 	persistentToasts.value.forEach(persistentToast => {
 		persistentToast.toast.destroy();
 	});