akborana4 commited on
Commit
a73ed1d
·
verified ·
1 Parent(s): a875640

Update server/server.js

Browse files
Files changed (1) hide show
  1. server/server.js +118 -43
server/server.js CHANGED
@@ -4,8 +4,13 @@ import cors from 'cors';
4
  import { Server as SocketIOServer } from 'socket.io';
5
  import path from 'path';
6
  import { fileURLToPath } from 'url';
7
- import axios from 'axios';
8
- import { searchSongs, resolveStreamUrl } from './jiosaavn.js';
 
 
 
 
 
9
 
10
  const __filename = fileURLToPath(import.meta.url);
11
  const __dirname = path.dirname(__filename);
@@ -14,28 +19,22 @@ const app = express();
14
  app.use(cors());
15
  app.use(express.json());
16
 
17
- // In-memory rooms: swap to Redis for production
 
 
 
 
 
 
18
  const rooms = new Map();
19
- /*
20
- room = {
21
- id,
22
- hostId,
23
- users: Set<socketId>,
24
- track: { url, title, meta },
25
- isPlaying: false,
26
- // hostTime bookkeeping:
27
- // 'anchor' is the playback position at 'anchorAt' (ms, Date.now())
28
- anchor: 0,
29
- anchorAt: 0
30
- }
31
- */
32
 
33
  function ensureRoom(roomId) {
34
  if (!rooms.has(roomId)) {
35
  rooms.set(roomId, {
36
  id: roomId,
 
37
  hostId: null,
38
- users: new Set(),
39
  track: null,
40
  isPlaying: false,
41
  anchor: 0,
@@ -45,18 +44,90 @@ function ensureRoom(roomId) {
45
  return rooms.get(roomId);
46
  }
47
 
48
- // Search proxy to avoid CORS issues on client
49
- app.get('/api/search', async (req, res) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  try {
51
  const q = req.query.q || '';
52
- const results = await searchSongs(q);
53
- res.json(results);
54
  } catch (e) {
55
  res.status(500).json({ error: e.message });
56
  }
57
  });
58
 
59
- // Optional: direct URL validation/proxy header check
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  app.get('/api/ping', (req, res) => res.json({ ok: true }));
61
 
62
  // Serve frontend build
@@ -74,29 +145,37 @@ const io = new SocketIOServer(server, {
74
  io.on('connection', (socket) => {
75
  let joinedRoom = null;
76
 
77
- socket.on('join_room', ({ roomId, name, asHost = false }, ack) => {
78
  const room = ensureRoom(roomId);
79
- room.users.add(socket.id);
 
80
  if (asHost || !room.hostId) room.hostId = socket.id;
 
81
 
82
  socket.join(roomId);
83
  joinedRoom = roomId;
84
 
85
- // Acknowledge with current state
86
  ack?.({
87
  roomId,
88
  isHost: socket.id === room.hostId,
89
- state: {
90
- track: room.track,
91
- isPlaying: room.isPlaying,
92
- anchor: room.anchor,
93
- anchorAt: room.anchorAt
94
- }
95
  });
96
 
97
  socket.to(roomId).emit('system', { type: 'join', name, id: socket.id });
 
98
  });
99
 
 
 
 
 
 
 
 
 
 
 
100
  socket.on('chat_message', ({ roomId, name, text }) => {
101
  socket.to(roomId).emit('chat_message', { name, text, at: Date.now() });
102
  });
@@ -116,16 +195,14 @@ io.on('connection', (socket) => {
116
  socket.on('play', ({ roomId }) => {
117
  const room = rooms.get(roomId);
118
  if (!room || socket.id !== room.hostId) return;
119
- // Start from current anchor
120
  room.isPlaying = true;
121
- room.anchorAt = Date.now(); // resume ticking
122
  io.to(roomId).emit('play', { anchor: room.anchor, anchorAt: room.anchorAt });
123
  });
124
 
125
  socket.on('pause', ({ roomId }) => {
126
  const room = rooms.get(roomId);
127
  if (!room || socket.id !== room.hostId) return;
128
- // Freeze anchor at current logical time
129
  const elapsed = Math.max(0, Date.now() - room.anchorAt) / 1000;
130
  if (room.isPlaying) room.anchor += elapsed;
131
  room.isPlaying = false;
@@ -144,12 +221,7 @@ io.on('connection', (socket) => {
144
  socket.on('sync_request', ({ roomId }, ack) => {
145
  const room = rooms.get(roomId);
146
  if (!room) return;
147
- ack?.({
148
- track: room.track,
149
- isPlaying: room.isPlaying,
150
- anchor: room.anchor,
151
- anchorAt: room.anchorAt
152
- });
153
  });
154
 
155
  socket.on('disconnect', () => {
@@ -158,12 +230,15 @@ io.on('connection', (socket) => {
158
  if (!room) return;
159
  room.users.delete(socket.id);
160
  if (socket.id === room.hostId) {
161
- // Promote next user as host if available
162
- const [nextHost] = room.users;
163
  room.hostId = nextHost || null;
164
  io.to(joinedRoom).emit('host_changed', { hostId: room.hostId });
165
  }
166
- if (room.users.size === 0) rooms.delete(joinedRoom);
 
 
 
 
167
  });
168
  });
169
 
 
4
  import { Server as SocketIOServer } from 'socket.io';
5
  import path from 'path';
6
  import { fileURLToPath } from 'url';
7
+ import {
8
+ searchUniversal,
9
+ getSong,
10
+ getAlbum,
11
+ getPlaylist,
12
+ getLyrics
13
+ } from './jiosaavn.js';
14
 
15
  const __filename = fileURLToPath(import.meta.url);
16
  const __dirname = path.dirname(__filename);
 
19
  app.use(cors());
20
  app.use(express.json());
21
 
22
+ // Health check for Hugging Face Spaces
23
+ app.get('/', (req, res) => {
24
+ res.send('OK');
25
+ });
26
+
27
+ // In-memory rooms
28
+ // room = { id, name, hostId, users: Map<socketId, {name}>, track, isPlaying, anchor, anchorAt }
29
  const rooms = new Map();
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  function ensureRoom(roomId) {
32
  if (!rooms.has(roomId)) {
33
  rooms.set(roomId, {
34
  id: roomId,
35
+ name: null,
36
  hostId: null,
37
+ users: new Map(),
38
  track: null,
39
  isPlaying: false,
40
  anchor: 0,
 
44
  return rooms.get(roomId);
45
  }
46
 
47
+ function broadcastMembers(roomId) {
48
+ const room = rooms.get(roomId);
49
+ if (!room) return;
50
+ const members = [...room.users.entries()].map(([id, u]) => ({
51
+ id,
52
+ name: u.name || 'Guest',
53
+ isHost: id === room.hostId
54
+ }));
55
+ io.to(roomId).emit('members', { members, roomName: room.name || room.id });
56
+ }
57
+
58
+ function currentState(room) {
59
+ return {
60
+ track: room.track,
61
+ isPlaying: room.isPlaying,
62
+ anchor: room.anchor,
63
+ anchorAt: room.anchorAt
64
+ };
65
+ }
66
+
67
+ // ---------- JioSaavn proxy endpoints ----------
68
+ app.get('/api/result', async (req, res) => {
69
+ try {
70
+ const q = req.query.q || '';
71
+ const data = await searchUniversal(q);
72
+ res.json(data);
73
+ } catch (e) {
74
+ res.status(500).json({ error: e.message });
75
+ }
76
+ });
77
+
78
+ app.get('/api/song', async (req, res) => {
79
+ try {
80
+ const q = req.query.q || '';
81
+ const data = await getSong(q);
82
+ res.json(data);
83
+ } catch (e) {
84
+ res.status(500).json({ error: e.message });
85
+ }
86
+ });
87
+
88
+ app.get('/api/album', async (req, res) => {
89
  try {
90
  const q = req.query.q || '';
91
+ const data = await getAlbum(q);
92
+ res.json(data);
93
  } catch (e) {
94
  res.status(500).json({ error: e.message });
95
  }
96
  });
97
 
98
+ app.get('/api/playlist', async (req, res) => {
99
+ try {
100
+ const q = req.query.q || '';
101
+ const data = await getPlaylist(q);
102
+ res.json(data);
103
+ } catch (e) {
104
+ res.status(500).json({ error: e.message });
105
+ }
106
+ });
107
+
108
+ app.get('/api/lyrics', async (req, res) => {
109
+ try {
110
+ const q = req.query.q || '';
111
+ const data = await getLyrics(q);
112
+ res.json(data);
113
+ } catch (e) {
114
+ res.status(500).json({ error: e.message });
115
+ }
116
+ });
117
+
118
+ // Active rooms (lobby list)
119
+ app.get('/api/rooms', (req, res) => {
120
+ const data = [...rooms.values()]
121
+ .filter(r => r.users.size > 0)
122
+ .map(r => ({
123
+ id: r.id,
124
+ name: r.name || r.id,
125
+ members: r.users.size,
126
+ isPlaying: r.isPlaying
127
+ }));
128
+ res.json({ rooms: data });
129
+ });
130
+
131
  app.get('/api/ping', (req, res) => res.json({ ok: true }));
132
 
133
  // Serve frontend build
 
145
  io.on('connection', (socket) => {
146
  let joinedRoom = null;
147
 
148
+ socket.on('join_room', ({ roomId, name, asHost = false, roomName = null }, ack) => {
149
  const room = ensureRoom(roomId);
150
+ room.users.set(socket.id, { name: name || 'Guest' });
151
+
152
  if (asHost || !room.hostId) room.hostId = socket.id;
153
+ if (!room.name && roomName) room.name = String(roomName).trim().slice(0, 60) || null;
154
 
155
  socket.join(roomId);
156
  joinedRoom = roomId;
157
 
 
158
  ack?.({
159
  roomId,
160
  isHost: socket.id === room.hostId,
161
+ state: currentState(room),
162
+ roomName: room.name || room.id
 
 
 
 
163
  });
164
 
165
  socket.to(roomId).emit('system', { type: 'join', name, id: socket.id });
166
+ broadcastMembers(roomId);
167
  });
168
 
169
+ socket.on('rename', ({ roomId, newName }) => {
170
+ const room = rooms.get(roomId);
171
+ if (!room) return;
172
+ if (room.users.has(socket.id)) {
173
+ room.users.set(socket.id, { name: String(newName || 'Guest').slice(0, 40) });
174
+ broadcastMembers(roomId);
175
+ }
176
+ });
177
+
178
+ // Chat
179
  socket.on('chat_message', ({ roomId, name, text }) => {
180
  socket.to(roomId).emit('chat_message', { name, text, at: Date.now() });
181
  });
 
195
  socket.on('play', ({ roomId }) => {
196
  const room = rooms.get(roomId);
197
  if (!room || socket.id !== room.hostId) return;
 
198
  room.isPlaying = true;
199
+ room.anchorAt = Date.now();
200
  io.to(roomId).emit('play', { anchor: room.anchor, anchorAt: room.anchorAt });
201
  });
202
 
203
  socket.on('pause', ({ roomId }) => {
204
  const room = rooms.get(roomId);
205
  if (!room || socket.id !== room.hostId) return;
 
206
  const elapsed = Math.max(0, Date.now() - room.anchorAt) / 1000;
207
  if (room.isPlaying) room.anchor += elapsed;
208
  room.isPlaying = false;
 
221
  socket.on('sync_request', ({ roomId }, ack) => {
222
  const room = rooms.get(roomId);
223
  if (!room) return;
224
+ ack?.(currentState(room));
 
 
 
 
 
225
  });
226
 
227
  socket.on('disconnect', () => {
 
230
  if (!room) return;
231
  room.users.delete(socket.id);
232
  if (socket.id === room.hostId) {
233
+ const nextHost = [...room.users.keys()][0];
 
234
  room.hostId = nextHost || null;
235
  io.to(joinedRoom).emit('host_changed', { hostId: room.hostId });
236
  }
237
+ if (room.users.size === 0) {
238
+ rooms.delete(joinedRoom);
239
+ } else {
240
+ broadcastMembers(joinedRoom);
241
+ }
242
  });
243
  });
244