alpha
Login
or
Join now
flo-bit.dev
/
youtube-party-dj
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
[READ-ONLY] Mirror of https://github.com/flo-bit/youtube-party-dj. democratic youtube player, everyone can vote and add songs, uni project, made with uix
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
Merge branch 'main' into user_name
author
Florian
committer
GitHub
date
2 years ago
(Jul 10, 2024, 12:43 PM +0200)
commit
c02f9f4b
c02f9f4bdc61df405b73f6e77b0202bb5db03d21
parent
565eaf65
565eaf653a88f9a478677b7d67b32767aee0616f
+123
-19
7 changed files
Expand all
Collapse all
Unified
Split
backend
data.tsx
sessions.ts
common
components
QueueItem.tsx
page.tsx
frontend
entrypoint.css
package-lock.json
package.json
+68
-13
backend/data.tsx
View file
Reviewed
···
1
1
import { Innertube } from "https://deno.land/x/youtubei@v10.0.0-deno/deno.ts";
2
2
+
import uniqBy from 'https://cdn.skypack.dev/lodash/uniqBy';
3
3
+
import shuffle from 'https://cdn.skypack.dev/lodash/shuffle';
4
4
+
import take from 'https://cdn.skypack.dev/lodash/take';
2
5
3
6
const youtube = await Innertube.create();
4
7
···
11
14
// @ts-ignore - no type for duration
12
15
const videos = result.videos.filter((item) => item.duration?.seconds).slice(0, 10);
13
16
14
14
-
return videos.map((item) => {
15
15
-
return {
16
16
-
title: item.title.text ?? 'Untitled',
17
17
-
// @ts-ignore - no type for thumbnails
18
18
-
thumbnail: item.thumbnails?.[0].url,
19
19
-
// @ts-ignore - no type for id
20
20
-
id: item.id,
21
21
-
likes: new Set<string>(),
22
22
-
added: Date.now(),
23
23
-
// @ts-ignore - no type for duration
24
24
-
duration: item.duration?.text ?? '-',
25
25
-
};
26
26
-
});
17
17
+
return videos.map(item => normalizeVideo(item));
27
18
} catch (error) {
28
19
console.error(error);
29
20
return [];
30
21
}
31
22
}
23
23
+
24
24
+
const normalizeVideo = (video: any) => {
25
25
+
console.log(video);
26
26
+
return {
27
27
+
title: video.title.text ?? 'Untitled',
28
28
+
// @ts-ignore - no type for thumbnails
29
29
+
thumbnail: video.thumbnails?.[0].url,
30
30
+
// @ts-ignore - no type for id
31
31
+
id: video.id,
32
32
+
likes: new Set<string>(),
33
33
+
added: Date.now(),
34
34
+
// @ts-ignore - no type for duration
35
35
+
duration: video.duration?.text ?? '-',
36
36
+
}
37
37
+
}
38
38
+
39
39
+
async function getRelatedVideos(videoId: string) {
40
40
+
try {
41
41
+
const videoDetails = await youtube.getInfo(videoId);
42
42
+
const related = videoDetails.watch_next_feed.map(related => normalizeVideo(related));
43
43
+
return related;
44
44
+
} catch (error) {
45
45
+
console.error("Error fetching related videos:", error);
46
46
+
throw error;
47
47
+
}
48
48
+
}
49
49
+
50
50
+
export async function getRecommendations(queue: { id: string }[], maxRecommendations = 5) {
51
51
+
let allRecommendations: string[] = [];
52
52
+
53
53
+
for (const video of queue) {
54
54
+
try {
55
55
+
const relatedVideos = await getRelatedVideos(video.id);
56
56
+
allRecommendations.push(...relatedVideos.slice(0, maxRecommendations));
57
57
+
} catch (error) {
58
58
+
console.error(`Error processing video ID ${video.id}:`, error);
59
59
+
}
60
60
+
}
61
61
+
62
62
+
// filter out duplicates that are in queue
63
63
+
const newRecommendations = allRecommendations.filter((video) => !queue.some((item) => item.id === video.id));
64
64
+
65
65
+
const uniqueRecommendations = uniqBy(newRecommendations, 'id');
66
66
+
67
67
+
const shuffledRecommendations = shuffle(uniqueRecommendations);
68
68
+
69
69
+
const finalRecommendations = take(shuffledRecommendations, maxRecommendations);
70
70
+
71
71
+
console.log("FINDAL RECOMMENDATIONS", finalRecommendations);
72
72
+
return finalRecommendations;
73
73
+
}
74
74
+
75
75
+
export async function updateRecommendations(session: any, code: string) {
76
76
+
const recommendations = await getRecommendations(session.queue);
77
77
+
78
78
+
recommendations.forEach(() => {
79
79
+
session?.recommendedQueue.pop()
80
80
+
})
81
81
+
recommendations.forEach((video) => {
82
82
+
session?.recommendedQueue.push(video)
83
83
+
})
84
84
+
85
85
+
return session;
86
86
+
}
+13
backend/sessions.ts
View file
Reviewed
···
23
23
clientIds: Set<string>;
24
24
clients: Record<string, Client>;
25
25
queue: Item[];
26
26
+
recommendedQueue: Item[];
26
27
currentlyPlaying: Item | null;
27
28
};
28
29
···
146
147
clientIds: new Set() as Set<string>,
147
148
clients: {},
148
149
queue: [] as Item[],
150
150
+
recommendedQueue: [] as Item[],
149
151
currentlyPlaying: null as Item | null,
150
152
};
151
153
sessions[code] = session;
···
179
181
if (a.added < b.added) return -1;
180
182
return 0;
181
183
});
184
184
+
});
185
185
+
}
186
186
+
187
187
+
export const getRecommendedQueue = (code: string) => {
188
188
+
const session = sessions[code];
189
189
+
if (!session) {
190
190
+
console.log("no session!")
191
191
+
return $$([])
192
192
+
}
193
193
+
return always(() => {
194
194
+
return session.recommendedQueue
182
195
});
183
196
}
+7
-1
common/components/QueueItem.tsx
View file
Reviewed
···
1
1
import { ObjectRef } from "datex-core-legacy/runtime/pointers.ts";
2
2
import { QueueType } from "./Queue.tsx";
3
3
import { getSessionWithCode, getUserId, Item, toggleLike } from "backend/sessions.ts";
4
4
+
import { getRecommendations, updateRecommendations } from "backend/data.tsx"
5
5
+
6
6
+
const userId = (await getUserId()).userId;
4
7
5
8
const userId = (await getUserId()).userId;
6
9
···
150
153
onclick={async () => {
151
154
await handleButtonClick();
152
155
await sleep(250);
153
153
-
const session = await getSessionWithCode(code);
156
156
+
const clickCode = code.length ? code : code.val
157
157
+
const session = await getSessionWithCode(clickCode);
154
158
if (!session) return;
155
159
156
160
if ((session.queue.some((v) => v.id == item.id) || session.currentlyPlaying?.id == item.id)) {
···
158
162
}
159
163
session?.queue.push(item);
160
164
toggleLike(code, item.id);
165
165
+
166
166
+
await updateRecommendations(session, code);
161
167
}}
162
168
id={`button-${item.id}`}
163
169
class="queueframe bg-white dark:bg-white/5 border border-black dark:border-white/10 rounded-full w-10 h-10 flex items-center justify-center overflow-hidden "
+25
-2
common/page.tsx
View file
Reviewed
···
5
5
6
6
import QRCodeOverlay from "./components/QRCodeOverlay.tsx";
7
7
import UserDisplay from "./components/UserDisplay.tsx";
8
8
-
import { getSessionUserHosts, getSortedQueue } from "backend/sessions.ts";
8
8
+
import { getSessionUserHosts, getSortedQueue, getRecommendedQueue } from "backend/sessions.ts";
9
9
+
9
10
import { NowPlaying } from "./components/NowPlaying.tsx";
10
11
import addDurations from "./helper.tsx";
11
12
···
41
42
});
42
43
43
44
const sorted = await getSortedQueue(code);
45
45
+
const recommended = await getRecommendedQueue(code);
44
46
45
47
const timeLeft = always(() => {
46
48
let timeCounter = "0:00";
···
52
54
53
55
loadInitialTheme();
54
56
55
55
-
57
57
+
58
58
+
const Recommendations = () => {
59
59
+
60
60
+
// console.log('session', session)
61
61
+
if (recommended.$.length === 0) {
62
62
+
return null
63
63
+
}
64
64
+
65
65
+
return (
66
66
+
<div class="flex flex-col gap-3 mt-5">
67
67
+
<div class="text-white">RECOMMENDED:</div>
68
68
+
<div class="space-y-4">{
69
69
+
recommended.$.map(item => {
70
70
+
return <QueueItem item={item} type={'search'} code={code}></QueueItem>
71
71
+
})}
72
72
+
</div>
73
73
+
</div>
74
74
+
)
75
75
+
76
76
+
}
77
77
+
56
78
return (
57
79
<main class="w-screen h-screen relative bg-gray-50 dark:bg-gray-950">
58
80
<div class="mx-auto grid md:grid-cols-2 h-screen">
···
103
125
return <QueueItem item={item} type={'player'} code={code}></QueueItem>
104
126
})}
105
127
</div>
128
128
+
<Recommendations />
106
129
</div>
107
130
</div>
108
131
</div>
+6
frontend/entrypoint.css
View file
Reviewed
···
873
873
874
874
.mt-6 {
875
875
margin-top: 1.5rem;
876
876
+
.mt-5 {
877
877
+
margin-top: 1.25rem;
876
878
}
877
879
878
880
.mt-8 {
···
1063
1065
1064
1066
.gap-4 {
1065
1067
gap: 1rem;
1068
1068
+
}
1069
1069
+
1070
1070
+
.gap-3 {
1071
1071
+
gap: 0.75rem;
1066
1072
}
1067
1073
1068
1074
.gap-6 {
+1
package-lock.json
View file
Reviewed
···
495
495
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
496
496
"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
497
497
"dev": true,
498
498
+
"license": "MIT",
498
499
"dependencies": {
499
500
"chalk": "^4.1.2",
500
501
"date-fns": "^2.30.0",
+3
-3
package.json
View file
Reviewed
···
1
1
{
2
2
"scripts": {
3
3
-
"dev": "concurrently \"uix -l\" \"npx tailwindcss -i ./frontend/main.css -o ./frontend/entrypoint.css --watch\""
3
3
+
"dev": "concurrently \"uix --reload -l\" \"npx tailwindcss -i ./frontend/main.css -o ./frontend/entrypoint.css --watch\""
4
4
},
5
5
"devDependencies": {
6
6
"@tailwindcss/forms": "^0.5.7",
···
10
10
"tailwindcss": "^3.4.3"
11
11
},
12
12
"dependencies": {
13
13
-
"tailwindcss": "^3.4.3",
14
13
"node": "^20.14.0",
15
14
"tailwind-scrollbar": "^3.1.0",
15
15
+
"tailwindcss": "^3.4.3",
16
16
"typescript": "^5.4.5"
17
17
}
18
18
-
}
18
18
+
}