kacapower commited on
Commit
ee5e49d
·
verified ·
1 Parent(s): 5da52f1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +42 -60
index.html CHANGED
@@ -29,7 +29,6 @@
29
  justify-content: center;
30
  overflow-x: hidden;
31
  color: var(--text-color);
32
- /* Dynamic Animated Background */
33
  background: linear-gradient(125deg, #0f172a, #312e81, #4c1d95, #831843);
34
  background-size: 400% 400%;
35
  animation: gradientBG 15s ease infinite;
@@ -42,7 +41,6 @@
42
  100% { background-position: 0% 50%; }
43
  }
44
 
45
- /* Glassmorphism Card */
46
  .container {
47
  width: 100%;
48
  max-width: 500px;
@@ -61,7 +59,6 @@
61
  h1 { margin: 0 0 5px 0; font-weight: 700; letter-spacing: -1px; font-size: 2.5rem; text-shadow: 0 2px 10px rgba(0,0,0,0.2); }
62
  h2 { font-weight: 300; font-size: 1.2rem; opacity: 0.9; margin-bottom: 2rem; }
63
 
64
- /* Buttons */
65
  .btn {
66
  background: linear-gradient(135deg, var(--primary), var(--accent));
67
  color: white;
@@ -78,6 +75,8 @@
78
  letter-spacing: 1px;
79
  position: relative;
80
  overflow: hidden;
 
 
81
  }
82
 
83
  .btn:hover { transform: translateY(-2px) scale(1.02); box-shadow: 0 8px 25px rgba(99, 102, 241, 0.5); }
@@ -86,7 +85,6 @@
86
  .btn-secondary { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255,255,255,0.2); margin-bottom: 10px; }
87
  .btn-secondary:hover { background: rgba(255, 255, 255, 0.2); }
88
 
89
- /* Inputs */
90
  input[type="text"], input[type="file"]::file-selector-button { font-family: 'Outfit', sans-serif; }
91
 
92
  input[type="text"] {
@@ -103,10 +101,8 @@
103
  }
104
  input[type="text"]:focus { border-color: var(--accent); background: rgba(0,0,0,0.4); }
105
 
106
- /* Custom File Input */
107
  input[type="file"] { width: 100%; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 10px; border: 1px dashed rgba(255,255,255,0.3); }
108
 
109
- /* Audio Player Styling */
110
  audio {
111
  width: 100%;
112
  margin-top: 20px;
@@ -114,13 +110,11 @@
114
  opacity: 0.9;
115
  }
116
 
117
- /* Utility Classes */
118
  .hidden { display: none !important; }
119
  .fade-in { animation: fadeIn 0.5s ease forwards; }
120
 
121
  @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
122
 
123
- /* Room Key Display */
124
  .room-key-box {
125
  background: rgba(0,0,0,0.3);
126
  padding: 15px;
@@ -130,11 +124,9 @@
130
  }
131
  .key-text { font-size: 2.5rem; font-weight: 700; color: var(--accent); letter-spacing: 5px; font-family: monospace; display: block; }
132
 
133
- /* Download Toggle */
134
  .toggle-wrapper { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 15px; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 50px; }
135
  input[type="checkbox"] { accent-color: var(--accent); width: 20px; height: 20px; }
136
 
137
- /* Visualizer Animation */
138
  .visualizer { display: flex; justify-content: center; gap: 5px; height: 40px; align-items: flex-end; margin-bottom: 20px; }
139
  .bar { width: 8px; background: white; border-radius: 5px; animation: bounce 1s infinite; animation-play-state: paused; }
140
  .bar:nth-child(1) { height: 20%; animation-duration: 0.6s; }
@@ -147,15 +139,23 @@
147
 
148
  @keyframes bounce { 0%, 100% { height: 10%; } 50% { height: 100%; } }
149
 
150
- /* Iframe Start Overlay */
151
- #iframeStartBtn { background: #22c55e; margin-top: 10px; display: none; }
 
 
 
 
 
 
 
 
 
152
 
153
  </style>
154
  </head>
155
  <body>
156
 
157
  <div class="container fade-in">
158
- <!-- Visualizer -->
159
  <div class="visualizer" id="visualizer">
160
  <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
161
  </div>
@@ -163,13 +163,11 @@
163
  <h1>VibeSync</h1>
164
  <h2 id="subtitle">Live Zero-Latency Audio</h2>
165
 
166
- <!-- Role Selection -->
167
  <div id="roleSelector">
168
  <button class="btn btn-secondary" onclick="selectRole('streamer')">Host a Party (Streamer)</button>
169
  <button class="btn" onclick="selectRole('listener')">Join a Party (Listener)</button>
170
  </div>
171
 
172
- <!-- Streamer View -->
173
  <div id="streamerView" class="hidden fade-in">
174
  <input type="file" id="audioUpload" accept="audio/mp3, audio/wav">
175
  <button class="btn" onclick="uploadMusic()" id="uploadBtn">Upload & Start Room</button>
@@ -179,7 +177,7 @@
179
  <small>SHARE THIS CODE</small>
180
  <span id="displayRoomKey" class="key-text"></span>
181
  </div>
182
- <audio id="streamerAudio" controls></audio>
183
 
184
  <div class="toggle-wrapper">
185
  <input type="checkbox" id="allowDownload" onchange="toggleDownload()">
@@ -188,22 +186,18 @@
188
  </div>
189
  </div>
190
 
191
- <!-- Listener View -->
192
  <div id="listenerView" class="hidden fade-in">
193
  <input type="text" id="joinRoomKey" placeholder="Paste Code Here" autocomplete="off" maxlength="6">
194
  <button class="btn" onclick="joinRoom()" id="joinBtn">Join Room</button>
195
 
196
  <div id="listenerControls" class="hidden">
197
- <p>Connected. Waiting for host...</p>
198
- <audio id="listenerAudio" controls></audio>
 
 
199
  <a id="downloadBtn" class="btn hidden" href="#" download>Download MP3</a>
200
  </div>
201
  </div>
202
-
203
- <!-- Iframe Overlay -->
204
- <div id="overlay" class="hidden">
205
- <button id="iframeStartBtn" class="btn" onclick="unlockAudio()">Tap to Unlock Audio</button>
206
- </div>
207
 
208
  </div>
209
 
@@ -215,36 +209,24 @@
215
  let ws;
216
  let isStreamer = false;
217
  let currentRoomKey = "";
 
218
 
219
  // Elements
220
  const streamerAudio = document.getElementById('streamerAudio');
221
  const listenerAudio = document.getElementById('listenerAudio');
222
  const visualizer = document.getElementById('visualizer');
223
- const overlay = document.getElementById('overlay');
224
- const iframeBtn = document.getElementById('iframeStartBtn');
225
 
226
  // --- DYNAMIC UI & EFFECTS ---
227
-
228
- function unlockAudio() {
229
- // Fix for Iframes: User interaction is required to play audio
230
- const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
231
- audioCtx.resume().then(() => {
232
- iframeBtn.style.display = 'none';
233
- overlay.classList.add('hidden');
234
- listenerAudio.play().catch(e => console.log("Waiting for stream..."));
235
- });
236
- }
237
-
238
  function setPlayingState(isPlaying) {
239
  if (isPlaying) {
240
  visualizer.classList.add('playing');
241
- // Shift colors when playing
242
  document.body.style.setProperty('--hue-rotate', '45deg');
243
- document.body.style.animationDuration = '3s'; // Speed up background
244
  } else {
245
  visualizer.classList.remove('playing');
246
  document.body.style.setProperty('--hue-rotate', '0deg');
247
- document.body.style.animationDuration = '15s'; // Slow down background
248
  }
249
  }
250
 
@@ -255,14 +237,11 @@
255
  if (role === 'streamer') {
256
  isStreamer = true;
257
  document.getElementById('streamerView').classList.remove('hidden');
258
- document.body.style.setProperty('--primary', '#ec4899'); // Pink theme for host
259
  } else {
260
  document.getElementById('listenerView').classList.remove('hidden');
261
- document.body.style.setProperty('--primary', '#3b82f6'); // Blue theme for listener
262
-
263
- // Show "Unlock Audio" button immediately for listeners to bypass autoplay restrictions
264
- iframeBtn.style.display = 'block';
265
- overlay.classList.remove('hidden');
266
  }
267
  }
268
 
@@ -287,12 +266,12 @@
287
  const data = await response.json();
288
 
289
  currentRoomKey = data.room_key;
 
290
 
291
  document.getElementById('displayRoomKey').innerText = currentRoomKey;
292
  document.getElementById('streamerControls').classList.remove('hidden');
293
- uploadBtn.classList.add('hidden'); // Hide upload button after success
294
 
295
- // Load audio
296
  streamerAudio.src = `${BACKEND_URL}/audio/${currentRoomKey}`;
297
 
298
  connectWebSocket(currentRoomKey);
@@ -308,7 +287,8 @@
308
 
309
  function setupStreamerEvents() {
310
  streamerAudio.addEventListener('play', () => {
311
- ws.send(JSON.stringify({ action: "play", time: streamerAudio.currentTime }));
 
312
  setPlayingState(true);
313
  });
314
 
@@ -321,7 +301,6 @@
321
  ws.send(JSON.stringify({ action: "seek", time: streamerAudio.currentTime }));
322
  });
323
 
324
- // Sync background to music end
325
  streamerAudio.addEventListener('ended', () => {
326
  setPlayingState(false);
327
  });
@@ -341,10 +320,13 @@
341
  document.getElementById('joinBtn').classList.add('hidden');
342
  document.getElementById('listenerControls').classList.remove('hidden');
343
 
344
- // Preload audio
 
 
 
 
345
  listenerAudio.src = `${BACKEND_URL}/audio/${currentRoomKey}`;
346
 
347
- // Set up download link
348
  const downloadBtn = document.getElementById('downloadBtn');
349
  downloadBtn.href = `${BACKEND_URL}/audio/${currentRoomKey}`;
350
 
@@ -361,18 +343,19 @@
361
  if (isStreamer) return;
362
 
363
  const data = JSON.parse(event.data);
364
- const networkOffset = 0.1; // Increased slightly for buffer safety
365
 
366
  if (data.action === "play") {
 
 
 
 
 
 
367
  listenerAudio.currentTime = data.time + networkOffset;
368
- // Attempt to play. If blocked by browser policy, the "Unlock" button handles it earlier.
369
  listenerAudio.play()
370
  .then(() => setPlayingState(true))
371
- .catch(e => {
372
- console.log("Autoplay blocked. User needs to interact.");
373
- iframeBtn.style.display = 'block'; // Show button again if blocked
374
- iframeBtn.innerText = "Tap to Sync Audio";
375
- });
376
  }
377
  else if (data.action === "pause") {
378
  listenerAudio.pause();
@@ -389,7 +372,6 @@
389
 
390
  ws.onerror = (error) => console.error("WebSocket Error:", error);
391
 
392
- // Listener visualizer sync
393
  listenerAudio.addEventListener('play', () => setPlayingState(true));
394
  listenerAudio.addEventListener('pause', () => setPlayingState(false));
395
  listenerAudio.addEventListener('ended', () => setPlayingState(false));
 
29
  justify-content: center;
30
  overflow-x: hidden;
31
  color: var(--text-color);
 
32
  background: linear-gradient(125deg, #0f172a, #312e81, #4c1d95, #831843);
33
  background-size: 400% 400%;
34
  animation: gradientBG 15s ease infinite;
 
41
  100% { background-position: 0% 50%; }
42
  }
43
 
 
44
  .container {
45
  width: 100%;
46
  max-width: 500px;
 
59
  h1 { margin: 0 0 5px 0; font-weight: 700; letter-spacing: -1px; font-size: 2.5rem; text-shadow: 0 2px 10px rgba(0,0,0,0.2); }
60
  h2 { font-weight: 300; font-size: 1.2rem; opacity: 0.9; margin-bottom: 2rem; }
61
 
 
62
  .btn {
63
  background: linear-gradient(135deg, var(--primary), var(--accent));
64
  color: white;
 
75
  letter-spacing: 1px;
76
  position: relative;
77
  overflow: hidden;
78
+ text-decoration: none; /* For download link */
79
+ display: inline-block;
80
  }
81
 
82
  .btn:hover { transform: translateY(-2px) scale(1.02); box-shadow: 0 8px 25px rgba(99, 102, 241, 0.5); }
 
85
  .btn-secondary { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255,255,255,0.2); margin-bottom: 10px; }
86
  .btn-secondary:hover { background: rgba(255, 255, 255, 0.2); }
87
 
 
88
  input[type="text"], input[type="file"]::file-selector-button { font-family: 'Outfit', sans-serif; }
89
 
90
  input[type="text"] {
 
101
  }
102
  input[type="text"]:focus { border-color: var(--accent); background: rgba(0,0,0,0.4); }
103
 
 
104
  input[type="file"] { width: 100%; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 10px; border: 1px dashed rgba(255,255,255,0.3); }
105
 
 
106
  audio {
107
  width: 100%;
108
  margin-top: 20px;
 
110
  opacity: 0.9;
111
  }
112
 
 
113
  .hidden { display: none !important; }
114
  .fade-in { animation: fadeIn 0.5s ease forwards; }
115
 
116
  @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
117
 
 
118
  .room-key-box {
119
  background: rgba(0,0,0,0.3);
120
  padding: 15px;
 
124
  }
125
  .key-text { font-size: 2.5rem; font-weight: 700; color: var(--accent); letter-spacing: 5px; font-family: monospace; display: block; }
126
 
 
127
  .toggle-wrapper { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 15px; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 50px; }
128
  input[type="checkbox"] { accent-color: var(--accent); width: 20px; height: 20px; }
129
 
 
130
  .visualizer { display: flex; justify-content: center; gap: 5px; height: 40px; align-items: flex-end; margin-bottom: 20px; }
131
  .bar { width: 8px; background: white; border-radius: 5px; animation: bounce 1s infinite; animation-play-state: paused; }
132
  .bar:nth-child(1) { height: 20%; animation-duration: 0.6s; }
 
139
 
140
  @keyframes bounce { 0%, 100% { height: 10%; } 50% { height: 100%; } }
141
 
142
+ /* Styles for the unclickable filename text */
143
+ .filename-display {
144
+ font-size: 1.1rem;
145
+ color: var(--accent);
146
+ font-weight: 500;
147
+ margin-top: 15px;
148
+ word-break: break-all;
149
+ background: rgba(0,0,0,0.2);
150
+ padding: 10px;
151
+ border-radius: 8px;
152
+ }
153
 
154
  </style>
155
  </head>
156
  <body>
157
 
158
  <div class="container fade-in">
 
159
  <div class="visualizer" id="visualizer">
160
  <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
161
  </div>
 
163
  <h1>VibeSync</h1>
164
  <h2 id="subtitle">Live Zero-Latency Audio</h2>
165
 
 
166
  <div id="roleSelector">
167
  <button class="btn btn-secondary" onclick="selectRole('streamer')">Host a Party (Streamer)</button>
168
  <button class="btn" onclick="selectRole('listener')">Join a Party (Listener)</button>
169
  </div>
170
 
 
171
  <div id="streamerView" class="hidden fade-in">
172
  <input type="file" id="audioUpload" accept="audio/mp3, audio/wav">
173
  <button class="btn" onclick="uploadMusic()" id="uploadBtn">Upload & Start Room</button>
 
177
  <small>SHARE THIS CODE</small>
178
  <span id="displayRoomKey" class="key-text"></span>
179
  </div>
180
+ <audio id="streamerAudio" controls controlsList="nodownload"></audio>
181
 
182
  <div class="toggle-wrapper">
183
  <input type="checkbox" id="allowDownload" onchange="toggleDownload()">
 
186
  </div>
187
  </div>
188
 
 
189
  <div id="listenerView" class="hidden fade-in">
190
  <input type="text" id="joinRoomKey" placeholder="Paste Code Here" autocomplete="off" maxlength="6">
191
  <button class="btn" onclick="joinRoom()" id="joinBtn">Join Room</button>
192
 
193
  <div id="listenerControls" class="hidden">
194
+ <p>Connected. Listening to stream...</p>
195
+ <p id="guestFileName" class="filename-display hidden">Waiting for host to play...</p>
196
+
197
+ <audio id="listenerAudio"></audio>
198
  <a id="downloadBtn" class="btn hidden" href="#" download>Download MP3</a>
199
  </div>
200
  </div>
 
 
 
 
 
201
 
202
  </div>
203
 
 
209
  let ws;
210
  let isStreamer = false;
211
  let currentRoomKey = "";
212
+ let currentFilename = ""; // Store filename to broadcast to guests
213
 
214
  // Elements
215
  const streamerAudio = document.getElementById('streamerAudio');
216
  const listenerAudio = document.getElementById('listenerAudio');
217
  const visualizer = document.getElementById('visualizer');
218
+ const guestFileNameDisplay = document.getElementById('guestFileName');
 
219
 
220
  // --- DYNAMIC UI & EFFECTS ---
 
 
 
 
 
 
 
 
 
 
 
221
  function setPlayingState(isPlaying) {
222
  if (isPlaying) {
223
  visualizer.classList.add('playing');
 
224
  document.body.style.setProperty('--hue-rotate', '45deg');
225
+ document.body.style.animationDuration = '3s';
226
  } else {
227
  visualizer.classList.remove('playing');
228
  document.body.style.setProperty('--hue-rotate', '0deg');
229
+ document.body.style.animationDuration = '15s';
230
  }
231
  }
232
 
 
237
  if (role === 'streamer') {
238
  isStreamer = true;
239
  document.getElementById('streamerView').classList.remove('hidden');
240
+ document.body.style.setProperty('--primary', '#ec4899');
241
  } else {
242
  document.getElementById('listenerView').classList.remove('hidden');
243
+ document.body.style.setProperty('--primary', '#3b82f6');
244
+ // Overlay logic removed here
 
 
 
245
  }
246
  }
247
 
 
266
  const data = await response.json();
267
 
268
  currentRoomKey = data.room_key;
269
+ currentFilename = data.filename; // Save filename
270
 
271
  document.getElementById('displayRoomKey').innerText = currentRoomKey;
272
  document.getElementById('streamerControls').classList.remove('hidden');
273
+ uploadBtn.classList.add('hidden');
274
 
 
275
  streamerAudio.src = `${BACKEND_URL}/audio/${currentRoomKey}`;
276
 
277
  connectWebSocket(currentRoomKey);
 
287
 
288
  function setupStreamerEvents() {
289
  streamerAudio.addEventListener('play', () => {
290
+ // Broadcast filename along with play command
291
+ ws.send(JSON.stringify({ action: "play", time: streamerAudio.currentTime, filename: currentFilename }));
292
  setPlayingState(true);
293
  });
294
 
 
301
  ws.send(JSON.stringify({ action: "seek", time: streamerAudio.currentTime }));
302
  });
303
 
 
304
  streamerAudio.addEventListener('ended', () => {
305
  setPlayingState(false);
306
  });
 
320
  document.getElementById('joinBtn').classList.add('hidden');
321
  document.getElementById('listenerControls').classList.remove('hidden');
322
 
323
+ // CHANGED: Unlock the browser's audio policy instantly upon the user clicking "Join Room"
324
+ listenerAudio.play().then(() => {
325
+ listenerAudio.pause();
326
+ }).catch(e => console.log("Audio unlocked successfully via click gesture."));
327
+
328
  listenerAudio.src = `${BACKEND_URL}/audio/${currentRoomKey}`;
329
 
 
330
  const downloadBtn = document.getElementById('downloadBtn');
331
  downloadBtn.href = `${BACKEND_URL}/audio/${currentRoomKey}`;
332
 
 
343
  if (isStreamer) return;
344
 
345
  const data = JSON.parse(event.data);
346
+ const networkOffset = 0.1;
347
 
348
  if (data.action === "play") {
349
+ // Display filename to guest if host sent it
350
+ if (data.filename) {
351
+ guestFileNameDisplay.innerText = "Playing: " + data.filename;
352
+ guestFileNameDisplay.classList.remove('hidden');
353
+ }
354
+
355
  listenerAudio.currentTime = data.time + networkOffset;
 
356
  listenerAudio.play()
357
  .then(() => setPlayingState(true))
358
+ .catch(e => console.log("Waiting for host...", e));
 
 
 
 
359
  }
360
  else if (data.action === "pause") {
361
  listenerAudio.pause();
 
372
 
373
  ws.onerror = (error) => console.error("WebSocket Error:", error);
374
 
 
375
  listenerAudio.addEventListener('play', () => setPlayingState(true));
376
  listenerAudio.addEventListener('pause', () => setPlayingState(false));
377
  listenerAudio.addEventListener('ended', () => setPlayingState(false));