akborana4 commited on
Commit
bf98e79
·
verified ·
1 Parent(s): fa22d8a

Update client/src/Player.jsx

Browse files
Files changed (1) hide show
  1. client/src/Player.jsx +49 -27
client/src/Player.jsx CHANGED
@@ -2,19 +2,19 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
  import { detectMediaTypeFromUrl, getThumb, safeTitle } from './utils.js';
3
  import { log } from './logger.js';
4
  import { useToasts } from './Toasts.jsx';
5
- import YouTubePlayer from './YouTubePlayer.jsx';
6
 
7
  export default function Player({ socket, roomId, state, isHost }) {
8
  const { push } = useToasts();
9
  const audioRef = useRef(null);
10
  const videoRef = useRef(null);
11
  const [err, setErr] = useState(null);
 
 
12
 
13
  const mediaType = useMemo(() => {
14
  if (!state?.track?.url) return 'none';
15
  if (state?.track?.kind) return state.track.kind;
16
- const detected = detectMediaTypeFromUrl(state.track.url);
17
- return detected || 'audio';
18
  }, [state?.track?.url, state?.track?.kind]);
19
 
20
  const logicalTime = () => {
@@ -25,44 +25,62 @@ export default function Player({ socket, roomId, state, isHost }) {
25
  return Math.max(0, (anchor || 0) + elapsed);
26
  };
27
 
28
- const loadInto = async (el) => {
29
  if (!el || !state?.track?.url) return;
30
- el.src = state.track.url;
 
31
  try {
32
- el.load();
 
 
 
 
 
33
  const target = logicalTime();
34
- el.currentTime = Number.isFinite(target) ? target : 0;
35
- if (state.isPlaying) await el.play(); else el.pause();
 
 
 
 
 
 
36
  setErr(null);
37
  } catch (e) {
38
- log.error('Playback error for', state.track.url, e);
39
  setErr(e?.message || 'Playback failed');
40
  push(`Playback failed: ${e?.message || 'Unknown error'}`, 'bad', 4500);
 
 
41
  }
42
  };
43
 
 
44
  useEffect(() => {
45
- if (mediaType === 'audio') {
46
- const el = audioRef.current;
47
- if (el) loadInto(el);
48
- } else if (mediaType === 'video') {
49
- const el = videoRef.current;
50
- if (el) loadInto(el);
51
- }
52
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
  }, [state?.track?.url, mediaType]);
54
 
 
55
  useEffect(() => {
56
- if (mediaType === 'youtube') return;
57
  const el = mediaType === 'video' ? videoRef.current : audioRef.current;
58
  if (!el) return;
 
59
  const target = logicalTime();
60
- const drift = target - el.currentTime;
61
- if (Math.abs(drift) > 0.3) el.currentTime = Math.max(0, target);
62
- if (state.isPlaying && el.paused) el.play().catch(e => log.warn('Autoplay blocked or error', e));
63
- if (!state.isPlaying && !el.paused) el.pause();
 
 
 
 
 
 
64
  // eslint-disable-next-line react-hooks/exhaustive-deps
65
- }, [state.isPlaying, state.anchor, state.anchorAt, mediaType]);
66
 
67
  useEffect(() => {
68
  const a = audioRef.current;
@@ -81,10 +99,16 @@ export default function Player({ socket, roomId, state, isHost }) {
81
 
82
  return (
83
  <div style={{ position:'relative' }}>
 
 
 
 
 
 
84
  <div className="ambient"></div>
85
 
86
- <div className="now-playing" style={{ marginBottom: 10 }}>
87
- <div className="thumb" style={{ width:64, height:64, backgroundImage: thumb ? `url("${thumb}")` : undefined }} />
88
  <div style={{ minWidth:0, flex:1 }}>
89
  <div style={{ fontWeight:700, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{title}</div>
90
  <div style={{ fontSize:12, color:'var(--muted)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
@@ -94,9 +118,7 @@ export default function Player({ socket, roomId, state, isHost }) {
94
  {!isHost && <div className="tag">Listener</div>}
95
  </div>
96
 
97
- {mediaType === 'youtube' ? (
98
- <YouTubePlayer socket={socket} roomId={roomId} state={state} isHost={isHost} />
99
- ) : mediaType === 'video' ? (
100
  <video ref={videoRef} controls={isHost} playsInline style={{ width:'100%', maxHeight: '56vh', background:'#000' }} crossOrigin="anonymous" />
101
  ) : (
102
  <audio ref={audioRef} controls={isHost} style={{ width: '100%' }} crossOrigin="anonymous" />
 
2
  import { detectMediaTypeFromUrl, getThumb, safeTitle } from './utils.js';
3
  import { log } from './logger.js';
4
  import { useToasts } from './Toasts.jsx';
 
5
 
6
  export default function Player({ socket, roomId, state, isHost }) {
7
  const { push } = useToasts();
8
  const audioRef = useRef(null);
9
  const videoRef = useRef(null);
10
  const [err, setErr] = useState(null);
11
+ const busyRef = useRef(false); // gate play/pause race
12
+ const lastAppliedRef = useRef({ url: null });
13
 
14
  const mediaType = useMemo(() => {
15
  if (!state?.track?.url) return 'none';
16
  if (state?.track?.kind) return state.track.kind;
17
+ return detectMediaTypeFromUrl(state.track.url) || 'audio';
 
18
  }, [state?.track?.url, state?.track?.kind]);
19
 
20
  const logicalTime = () => {
 
25
  return Math.max(0, (anchor || 0) + elapsed);
26
  };
27
 
28
+ const applyMediaState = async (el) => {
29
  if (!el || !state?.track?.url) return;
30
+ if (busyRef.current) return;
31
+ busyRef.current = true;
32
  try {
33
+ // set src only when url changes
34
+ if (lastAppliedRef.current.url !== state.track.url) {
35
+ el.src = state.track.url;
36
+ el.load();
37
+ lastAppliedRef.current.url = state.track.url;
38
+ }
39
  const target = logicalTime();
40
+ if (Number.isFinite(target)) {
41
+ try { el.currentTime = Math.max(0, target); } catch {}
42
+ }
43
+ if (state.isPlaying) {
44
+ await el.play().catch(() => {}); // ignore gesture errors
45
+ } else {
46
+ el.pause();
47
+ }
48
  setErr(null);
49
  } catch (e) {
50
+ log.error('Playback error', e);
51
  setErr(e?.message || 'Playback failed');
52
  push(`Playback failed: ${e?.message || 'Unknown error'}`, 'bad', 4500);
53
+ } finally {
54
+ busyRef.current = false;
55
  }
56
  };
57
 
58
+ // On track/url or media type change
59
  useEffect(() => {
60
+ lastAppliedRef.current.url = null; // force src reload
61
+ const el = mediaType === 'video' ? videoRef.current : audioRef.current;
62
+ if (el) applyMediaState(el);
 
 
 
 
63
  // eslint-disable-next-line react-hooks/exhaustive-deps
64
  }, [state?.track?.url, mediaType]);
65
 
66
+ // On play/pause/seek drift
67
  useEffect(() => {
 
68
  const el = mediaType === 'video' ? videoRef.current : audioRef.current;
69
  if (!el) return;
70
+ // drift correction without causing play/pause races
71
  const target = logicalTime();
72
+ const delta = target - el.currentTime;
73
+ if (Math.abs(delta) > 0.35) {
74
+ try { el.currentTime = Math.max(0, target); } catch {}
75
+ }
76
+ // only adjust if needed
77
+ if (state.isPlaying && el.paused) {
78
+ el.play().catch(() => {});
79
+ } else if (!state.isPlaying && !el.paused) {
80
+ el.pause();
81
+ }
82
  // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, [state.isPlaying, state.anchor, state.anchorAt]);
84
 
85
  useEffect(() => {
86
  const a = audioRef.current;
 
99
 
100
  return (
101
  <div style={{ position:'relative' }}>
102
+ {/* DJ ambient + hero artwork */}
103
+ <div
104
+ className="hero-thumb"
105
+ style={{ backgroundImage: thumb ? `url("${thumb}")` : undefined }}
106
+ aria-hidden
107
+ />
108
  <div className="ambient"></div>
109
 
110
+ <div className="now-playing" style={{ marginBottom: 10, position:'relative' }}>
111
+ <div className="thumb" style={{ width:72, height:72, backgroundImage: thumb ? `url("${thumb}")` : undefined }} />
112
  <div style={{ minWidth:0, flex:1 }}>
113
  <div style={{ fontWeight:700, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{title}</div>
114
  <div style={{ fontSize:12, color:'var(--muted)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
 
118
  {!isHost && <div className="tag">Listener</div>}
119
  </div>
120
 
121
+ {mediaType === 'video' ? (
 
 
122
  <video ref={videoRef} controls={isHost} playsInline style={{ width:'100%', maxHeight: '56vh', background:'#000' }} crossOrigin="anonymous" />
123
  ) : (
124
  <audio ref={audioRef} controls={isHost} style={{ width: '100%' }} crossOrigin="anonymous" />