Subham9126 commited on
Commit
758b8b7
·
verified ·
1 Parent(s): ecf9fba

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +472 -264
index.html CHANGED
@@ -4,6 +4,8 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Video Library</title>
 
 
7
  <style>
8
  * {
9
  box-sizing: border-box;
@@ -75,10 +77,34 @@
75
  justify-content: center;
76
  margin-bottom: 10px;
77
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
- .video-thumbnail i {
80
- font-size: 48px;
 
 
81
  color: white;
 
 
 
 
 
 
 
82
  }
83
  .video-item-title {
84
  font-weight: 500;
@@ -115,7 +141,7 @@
115
  left: 0;
116
  width: 100%;
117
  height: 100%;
118
- background-color: rgba(0,0,0,0.8);
119
  display: flex;
120
  align-items: center;
121
  justify-content: center;
@@ -131,7 +157,7 @@
131
  .popup-content {
132
  width: 90%;
133
  max-width: 1024px;
134
- background-color: #222;
135
  border-radius: 8px;
136
  overflow: hidden;
137
  position: relative;
@@ -139,13 +165,13 @@
139
  }
140
  .close-popup {
141
  position: absolute;
142
- top: 10px;
143
- right: 10px;
144
  background-color: rgba(0,0,0,0.5);
145
  color: white;
146
  border: none;
147
- width: 30px;
148
- height: 30px;
149
  border-radius: 50%;
150
  display: flex;
151
  align-items: center;
@@ -153,19 +179,23 @@
153
  cursor: pointer;
154
  z-index: 10;
155
  font-size: 18px;
 
 
 
 
156
  }
157
  .player-wrapper {
158
  position: relative;
159
  width: 100%;
160
  padding-top: 56.25%; /* 16:9 Aspect Ratio */
 
161
  }
162
- #videoPlayer {
163
  position: absolute;
164
  top: 0;
165
  left: 0;
166
  width: 100%;
167
  height: 100%;
168
- background-color: #000;
169
  }
170
  .popup-title {
171
  color: white;
@@ -173,39 +203,50 @@
173
  font-size: 1.2em;
174
  background-color: #111;
175
  }
176
- .controls {
177
- display: flex;
178
- gap: 10px;
179
- padding: 15px;
180
- background-color: #111;
181
- flex-wrap: wrap;
182
  }
183
- .btn {
184
- padding: 8px 16px;
185
- background-color: #333;
 
 
 
 
 
 
 
186
  color: white;
187
- border: none;
188
- border-radius: 4px;
189
- cursor: pointer;
190
- transition: background-color 0.2s;
 
 
 
 
 
 
191
  }
192
- .btn:hover {
193
- background-color: #555;
194
  }
195
- .instructions {
196
- color: #ccc;
197
  padding: 10px 15px;
198
- background-color: #111;
199
- font-size: 0.9em;
 
 
 
200
  }
201
- .play-icon {
202
- font-size: 50px;
 
203
  color: white;
204
- opacity: 0.8;
205
- transition: opacity 0.2s;
206
- }
207
- .video-item:hover .play-icon {
208
- opacity: 1;
209
  }
210
  @media (max-width: 768px) {
211
  .video-grid {
@@ -215,10 +256,31 @@
215
  width: 95%;
216
  }
217
  }
218
- .no-videos {
219
- padding: 20px;
220
- text-align: center;
221
- color: #777;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
  </style>
224
  </head>
@@ -248,24 +310,33 @@
248
  <button class="close-popup" id="closePopup">✕</button>
249
  <div class="popup-title" id="currentTitle">Video Title</div>
250
  <div class="player-wrapper">
251
- <video id="videoPlayer" controls>
252
- Your browser does not support the video tag.
253
- </video>
254
- </div>
255
- <div class="controls">
256
- <button class="btn" id="backwardBtn">⏪ 10s</button>
257
- <button class="btn" id="playPauseBtn">⏯️ Play/Pause</button>
258
- <button class="btn" id="forwardBtn"> 10s</button>
259
- <button class="btn" id="fullscreenBtn">⛶ Fullscreen</button>
260
- <button class="btn" id="muteBtn">🔊 Mute/Unmute</button>
261
  </div>
262
- <div class="instructions">
263
- <p><strong>Keyboard Controls:</strong> Space (Play/Pause) • ← (Back 10s) • → (Forward 10s) • F (Fullscreen) • M (Mute/Unmute)</p>
264
- <p><strong>Double-tap:</strong> Left side (Back 10s) • Center (Play/Pause) • Right side (Forward 10s)</p>
 
 
 
 
 
 
265
  </div>
266
  </div>
267
  </div>
268
 
 
 
 
 
 
269
  <script>
270
  // Global variables
271
  let videoData = [];
@@ -273,21 +344,27 @@
273
  let currentVideo = null;
274
  let currentPage = 1;
275
  const videosPerPage = 10;
 
 
 
 
 
 
 
276
 
277
  // DOM Elements
278
  const videoGrid = document.getElementById('videoGrid');
279
  const pagination = document.getElementById('pagination');
280
  const searchInput = document.getElementById('search');
281
  const playerPopup = document.getElementById('playerPopup');
282
- const videoPlayer = document.getElementById('videoPlayer');
283
  const currentTitle = document.getElementById('currentTitle');
284
  const closePopup = document.getElementById('closePopup');
285
- const playPauseBtn = document.getElementById('playPauseBtn');
286
- const backwardBtn = document.getElementById('backwardBtn');
287
- const forwardBtn = document.getElementById('forwardBtn');
288
- const fullscreenBtn = document.getElementById('fullscreenBtn');
289
- const muteBtn = document.getElementById('muteBtn');
290
-
291
  // Function to fetch video data
292
  async function fetchVideoData() {
293
  try {
@@ -304,12 +381,62 @@
304
  filteredVideos = [...videoData];
305
  renderPagination();
306
  renderVideoGrid();
 
 
 
307
  } catch (error) {
308
  console.error('Error fetching video data:', error);
309
  videoGrid.innerHTML = `<div class="no-videos">Error loading videos: ${error.message}</div>`;
310
  }
311
  }
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  // Function to render video grid
314
  function renderVideoGrid() {
315
  if (filteredVideos.length === 0) {
@@ -318,7 +445,6 @@
318
  return;
319
  }
320
 
321
- // Calculate start and end index for current page
322
  const startIndex = (currentPage - 1) * videosPerPage;
323
  const endIndex = Math.min(startIndex + videosPerPage, filteredVideos.length);
324
  const currentVideos = filteredVideos.slice(startIndex, endIndex);
@@ -326,13 +452,13 @@
326
  videoGrid.innerHTML = currentVideos.map((video, index) => `
327
  <div class="video-item" data-index="${startIndex + index}">
328
  <div class="video-thumbnail">
 
329
  <div class="play-icon">▶</div>
330
  </div>
331
  <div class="video-item-title">${video.title}</div>
332
  </div>
333
  `).join('');
334
 
335
- // Add click event listeners to video items
336
  document.querySelectorAll('.video-item').forEach(item => {
337
  item.addEventListener('click', function() {
338
  const index = parseInt(this.getAttribute('data-index'));
@@ -341,58 +467,47 @@
341
  }
342
  });
343
  });
 
 
 
344
  }
345
 
346
- // Function to render pagination
347
  function renderPagination() {
348
  const totalPages = Math.ceil(filteredVideos.length / videosPerPage);
349
-
350
  if (totalPages <= 1) {
351
  pagination.innerHTML = '';
352
  return;
353
  }
354
 
355
- // Determine which page buttons to show
356
  let buttons = [];
357
  if (totalPages <= 7) {
358
- // Show all pages if 7 or fewer
359
  for (let i = 1; i <= totalPages; i++) {
360
  buttons.push(i);
361
  }
362
  } else {
363
- // Always include first page
364
  buttons.push(1);
365
-
366
- // Add ellipsis if current page is more than 3
367
  if (currentPage > 3) {
368
  buttons.push('...');
369
  }
370
-
371
- // Add pages around current page
372
  const startPage = Math.max(2, currentPage - 1);
373
  const endPage = Math.min(totalPages - 1, currentPage + 1);
374
  for (let i = startPage; i <= endPage; i++) {
375
  buttons.push(i);
376
  }
377
-
378
- // Add ellipsis if current page is less than totalPages - 2
379
  if (currentPage < totalPages - 2) {
380
  buttons.push('...');
381
  }
382
-
383
- // Always include last page
384
  buttons.push(totalPages);
385
  }
386
 
387
- // Create pagination HTML
388
  pagination.innerHTML = buttons.map(button => {
389
  if (button === '...') {
390
  return '<span class="page-btn">...</span>';
391
  }
392
  return `<button class="page-btn ${button === currentPage ? 'active' : ''}" data-page="${button}">${button}</button>`;
393
  }).join('');
394
-
395
- // Add click event listeners to page buttons
396
  document.querySelectorAll('.page-btn[data-page]').forEach(button => {
397
  button.addEventListener('click', function() {
398
  currentPage = parseInt(this.getAttribute('data-page'));
@@ -403,213 +518,306 @@
403
  });
404
  }
405
 
406
- // Function to play video
407
- function playVideo(video) {
408
- currentVideo = video;
409
- currentTitle.textContent = video.title;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
- // Handle different types of URLs (direct or Hugging Face)
412
- let videoUrl = video.url;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
- // Check if this is a Hugging Face URL and handle accordingly
415
- if (videoUrl.includes('huggingface.co')) {
416
- // URLs for private repositories should already include token
 
 
417
  }
418
 
419
- videoPlayer.src = videoUrl;
420
- videoPlayer.load();
 
 
 
 
 
 
 
 
421
 
422
- // Show popup
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  playerPopup.classList.add('active');
 
424
 
425
- // Play video after showing popup
426
- setTimeout(() => {
427
- videoPlayer.play().catch(error => {
428
- console.error('Error playing video:', error);
429
- alert(`Error playing video: ${error.message}. Make sure the URL is accessible.`);
430
- });
431
- }, 300);
432
 
433
- // Disable scrolling on body when popup is active
 
434
  document.body.style.overflow = 'hidden';
435
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
- // Close popup function
438
  function closePlayerPopup() {
439
- videoPlayer.pause();
440
  playerPopup.classList.remove('active');
441
  document.body.style.overflow = '';
 
442
  }
443
 
444
- // Search functionality
445
- searchInput.addEventListener('input', function() {
446
- const searchTerm = this.value.toLowerCase();
447
- filteredVideos = videoData.filter(video =>
448
- video.title.toLowerCase().includes(searchTerm)
449
- );
450
- currentPage = 1; // Reset to first page on search
451
- renderPagination();
452
- renderVideoGrid();
453
- });
454
-
455
- // Close popup when clicking close button
456
  closePopup.addEventListener('click', closePlayerPopup);
457
-
458
- // Close popup when clicking outside content
459
- playerPopup.addEventListener('click', function(e) {
460
- if (e.target === playerPopup) {
461
  closePlayerPopup();
462
  }
463
  });
464
-
465
- // Video controls
466
- playPauseBtn.addEventListener('click', function() {
467
- if (videoPlayer.paused) {
468
- videoPlayer.play();
469
- } else {
470
- videoPlayer.pause();
471
- }
472
- });
473
-
474
- backwardBtn.addEventListener('click', function() {
475
- videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
476
- });
477
-
478
- forwardBtn.addEventListener('click', function() {
479
- videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
480
- });
481
-
482
- fullscreenBtn.addEventListener('click', function() {
483
- if (videoPlayer.requestFullscreen) {
484
- videoPlayer.requestFullscreen();
485
- } else if (videoPlayer.webkitRequestFullscreen) { /* Safari */
486
- videoPlayer.webkitRequestFullscreen();
487
- } else if (videoPlayer.msRequestFullscreen) { /* IE11 */
488
- videoPlayer.msRequestFullscreen();
489
- }
490
- });
491
-
492
- muteBtn.addEventListener('click', function() {
493
- videoPlayer.muted = !videoPlayer.muted;
494
- });
495
-
496
- // Keyboard controls (only active when popup is visible)
497
- document.addEventListener('keydown', function(e) {
498
- if (!playerPopup.classList.contains('active')) return;
499
-
500
- switch(e.key) {
501
- case ' ': // Space
502
- if (videoPlayer.paused) {
503
- videoPlayer.play();
504
- } else {
505
- videoPlayer.pause();
506
- }
507
- e.preventDefault();
508
- break;
509
- case 'ArrowRight':
510
- videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
511
- e.preventDefault();
512
- break;
513
- case 'ArrowLeft':
514
- videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
515
- e.preventDefault();
516
- break;
517
- case 'f':
518
- case 'F':
519
- if (document.fullscreenElement) {
520
- document.exitFullscreen();
521
- } else if (videoPlayer.requestFullscreen) {
522
- videoPlayer.requestFullscreen();
523
- }
524
- e.preventDefault();
525
- break;
526
- case 'm':
527
- case 'M':
528
- videoPlayer.muted = !videoPlayer.muted;
529
- e.preventDefault();
530
- break;
531
- case 'Escape':
532
- closePlayerPopup();
533
- e.preventDefault();
534
- break;
535
- }
536
- });
537
-
538
- // Double tap controls for mobile/touch devices
539
- let lastTap = 0;
540
- let tapTimeout;
541
 
542
- videoPlayer.addEventListener('click', function(e) {
543
- const currentTime = new Date().getTime();
544
- const tapLength = currentTime - lastTap;
545
- clearTimeout(tapTimeout);
546
-
547
- if (tapLength < 500 && tapLength > 0) {
548
- // Double tap detected
549
- const playerRect = videoPlayer.getBoundingClientRect();
550
- const clickX = e.clientX - playerRect.left;
551
- const playerWidth = playerRect.width;
552
-
553
- // Determine click position (left, center, or right third)
554
- if (clickX < playerWidth / 3) {
555
- // Left side - backward
556
- videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
557
- } else if (clickX > (playerWidth * 2) / 3) {
558
- // Right side - forward
559
- videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
560
- } else {
561
- // Center - play/pause
562
- if (videoPlayer.paused) {
563
- videoPlayer.play();
564
- } else {
565
- videoPlayer.pause();
566
- }
567
- }
568
- e.preventDefault();
569
- } else {
570
- // Single tap - wait to see if it becomes a double tap
571
- tapTimeout = setTimeout(function() {
572
- // Single tap action (do nothing or implement something)
573
- }, 300);
574
- }
575
- lastTap = currentTime;
576
- });
577
-
578
- // Fix for fullscreen and sound issues
579
- videoPlayer.addEventListener('loadedmetadata', function() {
580
- // Ensure volume is set
581
- videoPlayer.volume = 1.0;
582
- });
583
-
584
- // Fix for fullscreen mode
585
- document.addEventListener('fullscreenchange', handleFullscreenChange);
586
- document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
587
- document.addEventListener('mozfullscreenchange', handleFullscreenChange);
588
- document.addEventListener('MSFullscreenChange', handleFullscreenChange);
589
-
590
- function handleFullscreenChange() {
591
- if (document.fullscreenElement ||
592
- document.webkitFullscreenElement ||
593
- document.mozFullScreenElement) {
594
- console.log('Entered fullscreen mode');
595
- // Force video to take full size of screen
596
- const fullscreenVideo = document.fullscreenElement ||
597
- document.webkitFullscreenElement ||
598
- document.mozFullScreenElement;
599
- if (fullscreenVideo === videoPlayer) {
600
- videoPlayer.style.width = '100%';
601
- videoPlayer.style.height = '100%';
602
- }
603
- } else {
604
- console.log('Exited fullscreen mode');
605
- // Reset styles when exiting fullscreen
606
- videoPlayer.style.width = '100%';
607
- videoPlayer.style.height = '100%';
608
- }
609
- }
610
-
611
- // Initialize
612
  fetchVideoData();
613
  </script>
614
  </body>
615
- </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Video Library</title>
7
+ <!-- Load Plyr.io for enhanced video playback -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/plyr/3.7.8/plyr.min.css" />
9
  <style>
10
  * {
11
  box-sizing: border-box;
 
77
  justify-content: center;
78
  margin-bottom: 10px;
79
  overflow: hidden;
80
+ position: relative;
81
+ }
82
+ .video-thumbnail img {
83
+ width: 100%;
84
+ height: 100%;
85
+ object-fit: cover;
86
+ }
87
+ .video-thumbnail::after {
88
+ content: "";
89
+ position: absolute;
90
+ top: 0;
91
+ left: 0;
92
+ width: 100%;
93
+ height: 100%;
94
+ background-color: rgba(0,0,0,0.3);
95
  }
96
+ .play-icon {
97
+ position: absolute;
98
+ z-index: 2;
99
+ font-size: 50px;
100
  color: white;
101
+ opacity: 0.8;
102
+ transition: opacity 0.2s;
103
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
104
+ }
105
+ .video-item:hover .play-icon {
106
+ opacity: 1;
107
+ transform: scale(1.1);
108
  }
109
  .video-item-title {
110
  font-weight: 500;
 
141
  left: 0;
142
  width: 100%;
143
  height: 100%;
144
+ background-color: rgba(0,0,0,0.9);
145
  display: flex;
146
  align-items: center;
147
  justify-content: center;
 
157
  .popup-content {
158
  width: 90%;
159
  max-width: 1024px;
160
+ background-color: #111;
161
  border-radius: 8px;
162
  overflow: hidden;
163
  position: relative;
 
165
  }
166
  .close-popup {
167
  position: absolute;
168
+ top: 15px;
169
+ right: 15px;
170
  background-color: rgba(0,0,0,0.5);
171
  color: white;
172
  border: none;
173
+ width: 36px;
174
+ height: 36px;
175
  border-radius: 50%;
176
  display: flex;
177
  align-items: center;
 
179
  cursor: pointer;
180
  z-index: 10;
181
  font-size: 18px;
182
+ transition: background-color 0.2s;
183
+ }
184
+ .close-popup:hover {
185
+ background-color: rgba(255,0,0,0.7);
186
  }
187
  .player-wrapper {
188
  position: relative;
189
  width: 100%;
190
  padding-top: 56.25%; /* 16:9 Aspect Ratio */
191
+ background-color: #000;
192
  }
193
+ .plyr {
194
  position: absolute;
195
  top: 0;
196
  left: 0;
197
  width: 100%;
198
  height: 100%;
 
199
  }
200
  .popup-title {
201
  color: white;
 
203
  font-size: 1.2em;
204
  background-color: #111;
205
  }
206
+ .no-videos {
207
+ padding: 20px;
208
+ text-align: center;
209
+ color: #777;
 
 
210
  }
211
+ .player-loading {
212
+ position: absolute;
213
+ top: 0;
214
+ left: 0;
215
+ width: 100%;
216
+ height: 100%;
217
+ background-color: rgba(0,0,0,0.7);
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
  color: white;
222
+ font-size: 1.2em;
223
+ z-index: 5;
224
+ }
225
+ .player-loading .spinner {
226
+ width: 50px;
227
+ height: 50px;
228
+ border: 5px solid rgba(255,255,255,0.3);
229
+ border-radius: 50%;
230
+ border-top-color: white;
231
+ animation: spin 1s ease-in-out infinite;
232
  }
233
+ @keyframes spin {
234
+ to { transform: rotate(360deg); }
235
  }
236
+ .quality-selector {
 
237
  padding: 10px 15px;
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 10px;
241
+ background-color: #1a1a1a;
242
+ color: #ddd;
243
  }
244
+ .quality-selector select {
245
+ padding: 5px 10px;
246
+ background-color: #333;
247
  color: white;
248
+ border: none;
249
+ border-radius: 4px;
 
 
 
250
  }
251
  @media (max-width: 768px) {
252
  .video-grid {
 
256
  width: 95%;
257
  }
258
  }
259
+ .debug-overlay {
260
+ position: absolute;
261
+ bottom: 70px;
262
+ left: 10px;
263
+ background-color: rgba(0,0,0,0.7);
264
+ color: white;
265
+ padding: 10px;
266
+ border-radius: 4px;
267
+ font-size: 12px;
268
+ max-width: 300px;
269
+ z-index: 10;
270
+ display: none;
271
+ }
272
+ .debug-toggle {
273
+ background-color: #333;
274
+ color: white;
275
+ border: none;
276
+ padding: 5px 10px;
277
+ border-radius: 4px;
278
+ cursor: pointer;
279
+ font-size: 12px;
280
+ position: absolute;
281
+ bottom: 10px;
282
+ left: 10px;
283
+ z-index: 10;
284
  }
285
  </style>
286
  </head>
 
310
  <button class="close-popup" id="closePopup">✕</button>
311
  <div class="popup-title" id="currentTitle">Video Title</div>
312
  <div class="player-wrapper">
313
+ <div id="playerContainer"></div>
314
+ <div class="player-loading" id="playerLoading">
315
+ <div class="spinner"></div>
316
+ </div>
317
+ <div class="debug-overlay" id="debugOverlay">
318
+ Loading metrics...
319
+ </div>
320
+ <button class="debug-toggle" id="debugToggle">Show Stats</button>
 
 
321
  </div>
322
+ <div class="quality-selector">
323
+ <span>Playback Quality:</span>
324
+ <select id="qualitySelector">
325
+ <option value="auto">Auto</option>
326
+ <option value="1080">1080p</option>
327
+ <option value="720">720p</option>
328
+ <option value="480">480p</option>
329
+ <option value="360">360p</option>
330
+ </select>
331
  </div>
332
  </div>
333
  </div>
334
 
335
+ <!-- Load required scripts -->
336
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.14/hls.min.js"></script>
337
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/dashjs/4.7.1/dash.all.min.js"></script>
338
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/plyr/3.7.8/plyr.min.js"></script>
339
+
340
  <script>
341
  // Global variables
342
  let videoData = [];
 
344
  let currentVideo = null;
345
  let currentPage = 1;
346
  const videosPerPage = 10;
347
+ let player = null;
348
+ let hls = null;
349
+ let dash = null;
350
+ let videoPreloadList = [];
351
+ let videoPreloadIndex = 0;
352
+ let videoBuffers = new Map();
353
+ let debugInterval = null;
354
 
355
  // DOM Elements
356
  const videoGrid = document.getElementById('videoGrid');
357
  const pagination = document.getElementById('pagination');
358
  const searchInput = document.getElementById('search');
359
  const playerPopup = document.getElementById('playerPopup');
 
360
  const currentTitle = document.getElementById('currentTitle');
361
  const closePopup = document.getElementById('closePopup');
362
+ const playerContainer = document.getElementById('playerContainer');
363
+ const playerLoading = document.getElementById('playerLoading');
364
+ const qualitySelector = document.getElementById('qualitySelector');
365
+ const debugOverlay = document.getElementById('debugOverlay');
366
+ const debugToggle = document.getElementById('debugToggle');
367
+
368
  // Function to fetch video data
369
  async function fetchVideoData() {
370
  try {
 
381
  filteredVideos = [...videoData];
382
  renderPagination();
383
  renderVideoGrid();
384
+
385
+ // Preload thumbnails for first page
386
+ preloadThumbnails();
387
  } catch (error) {
388
  console.error('Error fetching video data:', error);
389
  videoGrid.innerHTML = `<div class="no-videos">Error loading videos: ${error.message}</div>`;
390
  }
391
  }
392
 
393
+ // Function to extract thumbnail from video URL
394
+ function getThumbnailUrl(videoUrl) {
395
+ // For Hugging Face videos, we can use a placeholder
396
+ return `/api/placeholder/280/156`;
397
+ }
398
+
399
+ // Function to preload thumbnails for better UX
400
+ function preloadThumbnails() {
401
+ // Calculate start and end index for current page
402
+ const startIndex = (currentPage - 1) * videosPerPage;
403
+ const endIndex = Math.min(startIndex + videosPerPage, filteredVideos.length);
404
+ const currentVideos = filteredVideos.slice(startIndex, endIndex);
405
+
406
+ currentVideos.forEach(video => {
407
+ const img = new Image();
408
+ img.src = getThumbnailUrl(video.url);
409
+ });
410
+
411
+ // Preload video data for the first few videos
412
+ videoPreloadList = currentVideos.slice(0, 3);
413
+ startVideoPreloading();
414
+ }
415
+
416
+ // Function to preload video data for smoother playback
417
+ function startVideoPreloading() {
418
+ if (videoPreloadList.length === 0 || videoPreloadIndex >= videoPreloadList.length) return;
419
+
420
+ const video = videoPreloadList[videoPreloadIndex];
421
+
422
+ // Create a minimal fetch request to start preloading the video
423
+ fetch(video.url, { method: 'HEAD' })
424
+ .then(() => {
425
+ console.log(`Preloaded headers for ${video.title}`);
426
+ videoPreloadIndex++;
427
+ if (videoPreloadIndex < videoPreloadList.length) {
428
+ startVideoPreloading();
429
+ }
430
+ })
431
+ .catch(err => {
432
+ console.warn(`Failed to preload ${video.title}:`, err);
433
+ videoPreloadIndex++;
434
+ if (videoPreloadIndex < videoPreloadList.length) {
435
+ startVideoPreloading();
436
+ }
437
+ });
438
+ }
439
+
440
  // Function to render video grid
441
  function renderVideoGrid() {
442
  if (filteredVideos.length === 0) {
 
445
  return;
446
  }
447
 
 
448
  const startIndex = (currentPage - 1) * videosPerPage;
449
  const endIndex = Math.min(startIndex + videosPerPage, filteredVideos.length);
450
  const currentVideos = filteredVideos.slice(startIndex, endIndex);
 
452
  videoGrid.innerHTML = currentVideos.map((video, index) => `
453
  <div class="video-item" data-index="${startIndex + index}">
454
  <div class="video-thumbnail">
455
+ <img src="${getThumbnailUrl(video.url)}" alt="${video.title}" loading="lazy">
456
  <div class="play-icon">▶</div>
457
  </div>
458
  <div class="video-item-title">${video.title}</div>
459
  </div>
460
  `).join('');
461
 
 
462
  document.querySelectorAll('.video-item').forEach(item => {
463
  item.addEventListener('click', function() {
464
  const index = parseInt(this.getAttribute('data-index'));
 
467
  }
468
  });
469
  });
470
+
471
+ // Preload thumbnails again
472
+ preloadThumbnails();
473
  }
474
 
475
+ // Function to render pagination controls
476
  function renderPagination() {
477
  const totalPages = Math.ceil(filteredVideos.length / videosPerPage);
 
478
  if (totalPages <= 1) {
479
  pagination.innerHTML = '';
480
  return;
481
  }
482
 
 
483
  let buttons = [];
484
  if (totalPages <= 7) {
 
485
  for (let i = 1; i <= totalPages; i++) {
486
  buttons.push(i);
487
  }
488
  } else {
 
489
  buttons.push(1);
 
 
490
  if (currentPage > 3) {
491
  buttons.push('...');
492
  }
 
 
493
  const startPage = Math.max(2, currentPage - 1);
494
  const endPage = Math.min(totalPages - 1, currentPage + 1);
495
  for (let i = startPage; i <= endPage; i++) {
496
  buttons.push(i);
497
  }
 
 
498
  if (currentPage < totalPages - 2) {
499
  buttons.push('...');
500
  }
 
 
501
  buttons.push(totalPages);
502
  }
503
 
 
504
  pagination.innerHTML = buttons.map(button => {
505
  if (button === '...') {
506
  return '<span class="page-btn">...</span>';
507
  }
508
  return `<button class="page-btn ${button === currentPage ? 'active' : ''}" data-page="${button}">${button}</button>`;
509
  }).join('');
510
+
 
511
  document.querySelectorAll('.page-btn[data-page]').forEach(button => {
512
  button.addEventListener('click', function() {
513
  currentPage = parseInt(this.getAttribute('data-page'));
 
518
  });
519
  }
520
 
521
+ // Function to cleanup previous player instances
522
+ function cleanupPlayerInstances() {
523
+ if (player) {
524
+ player.destroy();
525
+ player = null;
526
+ }
527
+ if (hls) {
528
+ hls.destroy();
529
+ hls = null;
530
+ }
531
+ if (dash) {
532
+ dash.reset();
533
+ dash = null;
534
+ }
535
+ if (debugInterval) {
536
+ clearInterval(debugInterval);
537
+ debugInterval = null;
538
+ }
539
+ playerContainer.innerHTML = '';
540
+ }
541
+
542
+ // Function to initialize and show debug overlay with playback metrics
543
+ function initDebugOverlay() {
544
+ if (!player) return;
545
 
546
+ debugToggle.addEventListener('click', function() {
547
+ if (debugOverlay.style.display === 'block') {
548
+ debugOverlay.style.display = 'none';
549
+ if (debugInterval) {
550
+ clearInterval(debugInterval);
551
+ debugInterval = null;
552
+ }
553
+ } else {
554
+ debugOverlay.style.display = 'block';
555
+ updateDebugInfo();
556
+ debugInterval = setInterval(updateDebugInfo, 1000);
557
+ }
558
+ });
559
+ }
560
+
561
+ // Update debug information
562
+ function updateDebugInfo() {
563
+ if (!player || !player.media) return;
564
+
565
+ let info = '';
566
+ const video = player.media;
567
+ info += `Resolution: ${video.videoWidth}x${video.videoHeight}<br>`;
568
+ info += `Current Time: ${formatTime(video.currentTime)}/${formatTime(video.duration)}<br>`;
569
+ info += `Playback Rate: ${video.playbackRate}x<br>`;
570
+ info += `Volume: ${Math.round(video.volume * 100)}%<br><br>`;
571
 
572
+ const buffered = video.buffered;
573
+ if (buffered.length) {
574
+ info += `Buffered: ${formatTime(buffered.start(0))} - ${formatTime(buffered.end(0))}<br>`;
575
+ const bufferedAhead = buffered.end(0) - video.currentTime;
576
+ info += `Buffer ahead: ${bufferedAhead.toFixed(2)}s<br>`;
577
  }
578
 
579
+ if (video.networkState) {
580
+ const states = ['NETWORK_EMPTY', 'NETWORK_IDLE', 'NETWORK_LOADING', 'NETWORK_NO_SOURCE'];
581
+ info += `Network State: ${states[video.networkState]}<br>`;
582
+ }
583
+
584
+ if (hls) {
585
+ info += `<br>HLS Metrics:<br>`;
586
+ info += `Level: ${hls.currentLevel} (${hls.levels[hls.currentLevel]?.height || '?'}p)<br>`;
587
+ info += `Latency: ${hls.latency?.toFixed(2) || '?'}ms<br>`;
588
+ }
589
 
590
+ if (dash) {
591
+ info += `<br>DASH Metrics:<br>`;
592
+ const dashMetrics = dash.getMetricsFor('video');
593
+ const videoQualities = dash.getBitrateInfoListFor('video');
594
+ info += `Quality: ${videoQualities[dash.getQualityFor('video')]?.width || '?'}x${videoQualities[dash.getQualityFor('video')]?.height || '?'}<br>`;
595
+ info += `Buffer: ${dash.getBufferLength('video').toFixed(2)}s<br>`;
596
+ }
597
+
598
+ debugOverlay.innerHTML = info;
599
+ }
600
+
601
+ // Format time for debug info
602
+ function formatTime(seconds) {
603
+ if (isNaN(seconds)) return '0:00';
604
+ const mins = Math.floor(seconds / 60);
605
+ const secs = Math.floor(seconds % 60);
606
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
607
+ }
608
+
609
+ // Function to play video with optimized streaming
610
+ function playVideo(video) {
611
+ currentVideo = video;
612
+ currentTitle.textContent = video.title;
613
+ playerLoading.style.display = 'flex';
614
  playerPopup.classList.add('active');
615
+ cleanupPlayerInstances();
616
 
617
+ const videoElement = document.createElement('video');
618
+ videoElement.setAttribute('playsinline', '');
619
+ videoElement.setAttribute('controls', '');
620
+ videoElement.classList.add('plyr__video-embed');
621
+ playerContainer.appendChild(videoElement);
 
 
622
 
623
+ const videoUrl = video.url;
624
+ initializePlayer(videoElement, videoUrl);
625
  document.body.style.overflow = 'hidden';
626
  }
627
+
628
+ // Determine if the URL is for Hugging Face videos (or similar criteria)
629
+ function isHuggingFaceURL(url) {
630
+ return url.includes('huggingface.co');
631
+ }
632
+
633
+ // Initialize the player with the best streaming technology
634
+ function initializePlayer(videoElement, videoUrl) {
635
+ const initPlyr = () => {
636
+ const options = {
637
+ controls: [
638
+ 'play-large', 'play', 'progress', 'current-time', 'mute',
639
+ 'volume', 'settings', 'fullscreen'
640
+ ],
641
+ settings: ['quality', 'speed'],
642
+ keyboard: { focused: true, global: true },
643
+ tooltips: { controls: true, seek: true },
644
+ seekTime: 10,
645
+ speed: { selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 2] },
646
+ blankVideo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
647
+ previewThumbnails: {
648
+ enabled: false,
649
+ },
650
+ autoplay: true,
651
+ disableContextMenu: false
652
+ };
653
+
654
+ player = new Plyr(videoElement, options);
655
+
656
+ player.on('ready', () => {
657
+ playerLoading.style.display = 'none';
658
+ initDebugOverlay();
659
+ setTimeout(() => {
660
+ player.play().catch(error => {
661
+ console.warn('Autoplay prevented:', error);
662
+ });
663
+ }, 100);
664
+ });
665
+
666
+ player.on('enterfullscreen', () => {
667
+ if (screen.orientation && screen.orientation.lock) {
668
+ screen.orientation.lock('landscape').catch(e => {
669
+ console.warn("Orientation lock failed:", e);
670
+ });
671
+ }
672
+ });
673
+
674
+ player.on('exitfullscreen', () => {
675
+ if (screen.orientation && screen.orientation.unlock) {
676
+ screen.orientation.unlock();
677
+ }
678
+ });
679
+
680
+ player.on('seeking', () => {
681
+ playerLoading.style.display = 'flex';
682
+ });
683
+
684
+ player.on('seeked', () => {
685
+ playerLoading.style.display = 'none';
686
+ });
687
+
688
+ qualitySelector.addEventListener('change', function() {
689
+ const quality = this.value;
690
+ if (quality === 'auto') {
691
+ if (hls) {
692
+ hls.currentLevel = -1;
693
+ }
694
+ if (dash) {
695
+ dash.setAutoSwitchQualityFor('video', true);
696
+ }
697
+ return;
698
+ }
699
+ const height = parseInt(quality);
700
+
701
+ if (hls && hls.levels) {
702
+ const levelIndex = hls.levels.findIndex(level => level.height <= height);
703
+ if (levelIndex !== -1) {
704
+ hls.currentLevel = levelIndex;
705
+ }
706
+ }
707
+
708
+ if (dash) {
709
+ const videoQualities = dash.getBitrateInfoListFor('video');
710
+ const qualityIndex = videoQualities.findIndex(q => q.height <= height);
711
+ if (qualityIndex !== -1) {
712
+ dash.setAutoSwitchQualityFor('video', false);
713
+ dash.setQualityFor('video', qualityIndex);
714
+ }
715
+ }
716
+ });
717
+ };
718
+
719
+ if (videoUrl.includes('.m3u8') || isHuggingFaceURL(videoUrl)) {
720
+ if (Hls.isSupported()) {
721
+ hls = new Hls({
722
+ startLevel: -1,
723
+ maxBufferLength: 30,
724
+ maxMaxBufferLength: 60,
725
+ progressive: true,
726
+ lowLatencyMode: true,
727
+ backBufferLength: 90,
728
+ enableWorker: true,
729
+ testBandwidth: true,
730
+ abrEwmaDefaultEstimate: 1000000,
731
+ abrBandWidthFactor: 0.9,
732
+ abrBandWidthUpFactor: 0.7,
733
+ maxLoadingDelay: 4,
734
+ manifestLoadingTimeOut: 10000,
735
+ fragLoadingTimeOut: 20000,
736
+ enableSoftwareAES: true,
737
+ });
738
+
739
+ hls.loadSource(videoUrl);
740
+ hls.attachMedia(videoElement);
741
+
742
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
743
+ initPlyr();
744
+ qualitySelector.innerHTML = '<option value="auto">Auto</option>';
745
+ hls.levels.forEach(level => {
746
+ qualitySelector.innerHTML += `<option value="${level.height}">${level.height}p</option>`;
747
+ });
748
+ });
749
+
750
+ hls.on(Hls.Events.ERROR, (event, data) => {
751
+ console.error('HLS Error:', data);
752
+ if (data.fatal) {
753
+ switch(data.type) {
754
+ case Hls.ErrorTypes.NETWORK_ERROR:
755
+ console.log('Fatal network error, trying to recover');
756
+ hls.startLoad();
757
+ break;
758
+ case Hls.ErrorTypes.MEDIA_ERROR:
759
+ console.log('Fatal media error, trying to recover');
760
+ hls.recoverMediaError();
761
+ break;
762
+ default:
763
+ console.error('Fatal HLS error, cannot recover');
764
+ hls.destroy();
765
+ alert('Could not play this video. Please try another one.');
766
+ closePlayerPopup();
767
+ break;
768
+ }
769
+ }
770
+ });
771
+ } else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
772
+ videoElement.src = videoUrl;
773
+ initPlyr();
774
+ }
775
+ }
776
+ else if (videoUrl.includes('.mpd')) {
777
+ dash = dashjs.MediaPlayer().create();
778
+ dash.initialize(videoElement, videoUrl, true);
779
+ dash.updateSettings({
780
+ streaming: {
781
+ buffer: {
782
+ fastSwitchEnabled: true,
783
+ stableBufferTime: 20,
784
+ }
785
+ }
786
+ });
787
+
788
+ dash.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, function() {
789
+ initPlyr();
790
+ const videoQualities = dash.getBitrateInfoListFor('video');
791
+ qualitySelector.innerHTML = '<option value="auto">Auto</option>';
792
+ videoQualities.forEach(quality => {
793
+ qualitySelector.innerHTML += `<option value="${quality.height}">${quality.height}p</option>`;
794
+ });
795
+ });
796
+ }
797
+ else {
798
+ // Fallback for standard video files
799
+ videoElement.src = videoUrl;
800
+ initPlyr();
801
+ }
802
+ }
803
 
804
+ // Function to close the player popup and cleanup the player
805
  function closePlayerPopup() {
 
806
  playerPopup.classList.remove('active');
807
  document.body.style.overflow = '';
808
+ cleanupPlayerInstances();
809
  }
810
 
811
+ // Add event listener for closing the popup
 
 
 
 
 
 
 
 
 
 
 
812
  closePopup.addEventListener('click', closePlayerPopup);
813
+ window.addEventListener('keydown', function(e) {
814
+ if (e.key === 'Escape') {
 
 
815
  closePlayerPopup();
816
  }
817
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
 
819
+ // Initialize video data on page load
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  fetchVideoData();
821
  </script>
822
  </body>
823
+ </html>