akborana4 commited on
Commit
0b928ef
·
verified ·
1 Parent(s): b38ec5a

Update client/src/Room.jsx

Browse files
Files changed (1) hide show
  1. client/src/Room.jsx +22 -56
client/src/Room.jsx CHANGED
@@ -4,34 +4,25 @@ import { io } from 'socket.io-client';
4
  import Player from './Player.jsx';
5
  import Chat from './Chat.jsx';
6
  import MemberList from './MemberList.jsx';
7
- import { detectMediaTypeFromUrl, getThumb, prettyError, safeTitle } from './utils.js';
8
  import { useToasts } from './Toasts.jsx';
9
- import { log } from './logger.js';
10
 
11
  const socket = io('', { transports: ['websocket'] });
12
 
13
  function DirectLinkModal({ open, onClose, onPick }) {
14
  const [url, setUrl] = useState('');
15
  const [type, setType] = useState('auto');
16
-
17
- useEffect(() => {
18
- if (!open) { setUrl(''); setType('auto'); }
19
- }, [open]);
20
-
21
  if (!open) return null;
22
  return (
23
  <div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
24
  <div className="modal">
25
  <div className="section-title">Play a direct link</div>
26
  <div style={{ color:'var(--muted)', fontSize:13, marginBottom:8 }}>
27
- Paste a direct media URL (MP4, WEBM, MP3, M4A, etc.) or a YouTube URL/ID.
28
  </div>
29
- <input
30
- className="input"
31
- placeholder="https://example.com/video.mp4 or https://youtu.be/xxxx or 11-char ID"
32
- value={url}
33
- onChange={e => setUrl(e.target.value)}
34
- />
35
  <div style={{ display:'flex', gap:10, marginTop:10 }}>
36
  <select className="select" value={type} onChange={e => setType(e.target.value)}>
37
  <option value="auto">Auto-detect type</option>
@@ -47,44 +38,27 @@ function DirectLinkModal({ open, onClose, onPick }) {
47
  );
48
  }
49
 
50
- function YouTubeModal({ open, onClose, onPick, ytError, onTryFallback }) {
51
  const [idOrUrl, setIdOrUrl] = useState('');
52
-
53
- useEffect(() => {
54
- if (!open) { setIdOrUrl(''); }
55
- }, [open]);
56
-
57
  if (!open) return null;
58
  return (
59
  <div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
60
  <div className="modal">
61
- <div className="section-title">Play YouTube</div>
62
  <div style={{ color:'var(--muted)', fontSize:13, marginBottom:8 }}>
63
- Enter a YouTube URL or 11‑character ID. We’ll try Vidfly first and fall back to yt‑dlp if needed.
64
  </div>
65
- <input
66
- className="input"
67
- placeholder="https://www.youtube.com/watch?v=XXXXXXXXXXX or XXXXXXXID"
68
- value={idOrUrl}
69
- onChange={e => setIdOrUrl(e.target.value)}
70
- />
71
  <div style={{ display:'flex', gap:8, marginTop:10 }}>
72
- <button className="btn good" onClick={() => onPick(idOrUrl, 'vidfly')}>Play with Vidfly</button>
73
- <button className="btn" onClick={() => onPick(idOrUrl, 'yt-dlp')}>Play with yt‑dlp</button>
74
  <button className="btn" onClick={onClose}>Close</button>
75
  </div>
76
-
77
  {ytError && (
78
  <div className="room-card" style={{ marginTop:12 }}>
79
  <div style={{ fontWeight:700, color:'var(--bad)' }}>YouTube failed</div>
80
  <div className="meta" style={{ marginTop:6 }}>{ytError.message}</div>
81
- {ytError.canFallback && (
82
- <div style={{ marginTop:8 }}>
83
- <button className="btn warn" onClick={() => onTryFallback(ytError.url, ytError.from === 'vidfly' ? 'yt-dlp' : 'vidfly')}>
84
- Try {ytError.from === 'vidfly' ? 'yt‑dlp' : 'Vidfly'} instead
85
- </button>
86
- </div>
87
- )}
88
  </div>
89
  )}
90
  </div>
@@ -153,7 +127,7 @@ export default function Room({ roomId, name, asHost, roomName }) {
153
  socket.on('system', ({ text }) => { if (text) console.log(text); });
154
  socket.on('queue_update', ({ queue }) => setState(s => ({ ...s, queue: queue || [] })));
155
 
156
- // Host receives a request → attach a preview (best effort via /api/result)
157
  socket.on('song_request', async (req) => {
158
  if (!isHost) return;
159
  const enriched = { ...req, preview: null };
@@ -178,8 +152,7 @@ export default function Room({ roomId, name, asHost, roomName }) {
178
  };
179
  }, [roomId, name, asHost, roomName, isHost]);
180
 
181
- // DIRECT LINKS AND YOUTUBE RESOLUTION
182
-
183
  const setTrackAndClose = (track) => {
184
  socket.emit('set_track', { roomId, track });
185
  setShowDirect(false);
@@ -203,10 +176,10 @@ export default function Room({ roomId, name, asHost, roomName }) {
203
  setTrackAndClose(track);
204
  };
205
 
206
- const resolveYouTube = async (idOrUrl, api) => {
 
207
  setYtError(null);
208
- const q = new URLSearchParams({ url: idOrUrl, api }).toString();
209
- const resp = await fetch(`/api/yt/source?${q}`);
210
  const data = await resp.json();
211
  if (!resp.ok) {
212
  const message = data?.error || 'Failed to resolve YouTube';
@@ -215,29 +188,22 @@ export default function Room({ roomId, name, asHost, roomName }) {
215
  return data;
216
  };
217
 
218
- const onPickYouTube = async (idOrUrl, api = 'vidfly') => {
219
  try {
220
- const data = await resolveYouTube(idOrUrl, api);
221
  const track = {
222
  url: data.url,
223
  title: data.title || idOrUrl,
224
- meta: { thumb: data.thumbnail, source: data.source || api, yt: true },
225
  kind: data.kind || 'video',
226
  thumb: data.thumbnail
227
  };
228
  setTrackAndClose(track);
229
  } catch (e) {
230
- setYtError({
231
- message: e.message,
232
- canFallback: api === 'vidfly' || api === 'yt-dlp',
233
- from: api,
234
- url: idOrUrl
235
- });
236
  }
237
  };
238
 
239
- const onTryFallback = (idOrUrl, api) => onPickYouTube(idOrUrl, api);
240
-
241
  const Controls = useMemo(() => (
242
  isHost ? (
243
  <div style={{ display:'flex', flexWrap:'wrap', gap:8 }}>
@@ -313,7 +279,7 @@ export default function Room({ roomId, name, asHost, roomName }) {
313
  </div>
314
 
315
  <DirectLinkModal open={showDirect} onClose={() => setShowDirect(false)} onPick={pickDirectUrl} />
316
- <YouTubeModal open={showYT} onClose={() => setShowYT(false)} onPick={onPickYouTube} ytError={ytError} onTryFallback={onTryFallback} />
317
  </div>
318
  );
319
  }
 
4
  import Player from './Player.jsx';
5
  import Chat from './Chat.jsx';
6
  import MemberList from './MemberList.jsx';
7
+ import { detectMediaTypeFromUrl, getThumb } from './utils.js';
8
  import { useToasts } from './Toasts.jsx';
 
9
 
10
  const socket = io('', { transports: ['websocket'] });
11
 
12
  function DirectLinkModal({ open, onClose, onPick }) {
13
  const [url, setUrl] = useState('');
14
  const [type, setType] = useState('auto');
15
+ useEffect(() => { if (!open) { setUrl(''); setType('auto'); } }, [open]);
 
 
 
 
16
  if (!open) return null;
17
  return (
18
  <div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
19
  <div className="modal">
20
  <div className="section-title">Play a direct link</div>
21
  <div style={{ color:'var(--muted)', fontSize:13, marginBottom:8 }}>
22
+ Paste a direct media URL (MP4, WEBM, MP3, M4A) or a YouTube URL/ID.
23
  </div>
24
+ <input className="input" value={url} onChange={e => setUrl(e.target.value)}
25
+ placeholder="https://example.com/video.mp4 or https://youtu.be/xxxx or 11-char ID" />
 
 
 
 
26
  <div style={{ display:'flex', gap:10, marginTop:10 }}>
27
  <select className="select" value={type} onChange={e => setType(e.target.value)}>
28
  <option value="auto">Auto-detect type</option>
 
38
  );
39
  }
40
 
41
+ function YouTubeModal({ open, onClose, onPick, ytError }) {
42
  const [idOrUrl, setIdOrUrl] = useState('');
43
+ useEffect(() => { if (!open) setIdOrUrl(''); }, [open]);
 
 
 
 
44
  if (!open) return null;
45
  return (
46
  <div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
47
  <div className="modal">
48
+ <div className="section-title">Play YouTube (Vidfly)</div>
49
  <div style={{ color:'var(--muted)', fontSize:13, marginBottom:8 }}>
50
+ Enter a YouTube URL or 11‑character ID. Playback auto-switches to proxy if CORS blocks it.
51
  </div>
52
+ <input className="input" value={idOrUrl} onChange={e => setIdOrUrl(e.target.value)}
53
+ placeholder="https://www.youtube.com/watch?v=XXXXXXXXXXX or XXXXXXXID" />
 
 
 
 
54
  <div style={{ display:'flex', gap:8, marginTop:10 }}>
55
+ <button className="btn good" onClick={() => onPick(idOrUrl)}>Play</button>
 
56
  <button className="btn" onClick={onClose}>Close</button>
57
  </div>
 
58
  {ytError && (
59
  <div className="room-card" style={{ marginTop:12 }}>
60
  <div style={{ fontWeight:700, color:'var(--bad)' }}>YouTube failed</div>
61
  <div className="meta" style={{ marginTop:6 }}>{ytError.message}</div>
 
 
 
 
 
 
 
62
  </div>
63
  )}
64
  </div>
 
127
  socket.on('system', ({ text }) => { if (text) console.log(text); });
128
  socket.on('queue_update', ({ queue }) => setState(s => ({ ...s, queue: queue || [] })));
129
 
130
+ // Host receives a request → attach a preview via JioSaavn search (best effort)
131
  socket.on('song_request', async (req) => {
132
  if (!isHost) return;
133
  const enriched = { ...req, preview: null };
 
152
  };
153
  }, [roomId, name, asHost, roomName, isHost]);
154
 
155
+ // Direct links
 
156
  const setTrackAndClose = (track) => {
157
  socket.emit('set_track', { roomId, track });
158
  setShowDirect(false);
 
176
  setTrackAndClose(track);
177
  };
178
 
179
+ // YouTube via Vidfly only
180
+ const resolveYouTube = async (idOrUrl) => {
181
  setYtError(null);
182
+ const resp = await fetch(`/api/yt/source?url=${encodeURIComponent(idOrUrl)}`);
 
183
  const data = await resp.json();
184
  if (!resp.ok) {
185
  const message = data?.error || 'Failed to resolve YouTube';
 
188
  return data;
189
  };
190
 
191
+ const onPickYouTube = async (idOrUrl) => {
192
  try {
193
+ const data = await resolveYouTube(idOrUrl);
194
  const track = {
195
  url: data.url,
196
  title: data.title || idOrUrl,
197
+ meta: { thumb: data.thumbnail, source: data.source || 'vidfly', yt: true },
198
  kind: data.kind || 'video',
199
  thumb: data.thumbnail
200
  };
201
  setTrackAndClose(track);
202
  } catch (e) {
203
+ setYtError({ message: e.message });
 
 
 
 
 
204
  }
205
  };
206
 
 
 
207
  const Controls = useMemo(() => (
208
  isHost ? (
209
  <div style={{ display:'flex', flexWrap:'wrap', gap:8 }}>
 
279
  </div>
280
 
281
  <DirectLinkModal open={showDirect} onClose={() => setShowDirect(false)} onPick={pickDirectUrl} />
282
+ <YouTubeModal open={showYT} onClose={() => setShowYT(false)} onPick={onPickYouTube} ytError={ytError} />
283
  </div>
284
  );
285
  }