Subham9126 commited on
Commit
11fdd39
·
verified ·
1 Parent(s): 7997284

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +233 -308
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
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;
@@ -82,11 +82,6 @@
82
  height: 100%;
83
  object-fit: cover;
84
  }
85
- .video-thumbnail i {
86
- font-size: 48px;
87
- color: white;
88
- position: absolute;
89
- }
90
  .video-item-title {
91
  font-weight: 500;
92
  margin-top: 10px;
@@ -186,7 +181,6 @@
186
  padding: 15px;
187
  background-color: #111;
188
  flex-wrap: wrap;
189
- align-items: center;
190
  }
191
  .btn {
192
  padding: 8px 16px;
@@ -214,46 +208,11 @@
214
  color: white;
215
  opacity: 0.8;
216
  transition: opacity 0.2s;
 
217
  }
218
  .video-item:hover .play-icon {
219
  opacity: 1;
220
  }
221
- @media (max-width: 768px) {
222
- .video-grid {
223
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
224
- }
225
- .popup-content {
226
- width: 95%;
227
- }
228
- .controls {
229
- gap: 5px;
230
- padding: 10px;
231
- }
232
- .btn {
233
- padding: 6px 12px;
234
- font-size: 0.9em;
235
- }
236
- }
237
- .no-videos {
238
- padding: 20px;
239
- text-align: center;
240
- color: #777;
241
- }
242
- .quality-selector {
243
- margin-left: auto;
244
- }
245
- .quality-btn {
246
- padding: 6px 12px;
247
- background-color: #444;
248
- color: white;
249
- border: none;
250
- border-radius: 4px;
251
- cursor: pointer;
252
- margin-left: 5px;
253
- }
254
- .quality-btn.active {
255
- background-color: #007bff;
256
- }
257
  .loading-spinner {
258
  position: absolute;
259
  top: 50%;
@@ -272,9 +231,9 @@
272
  0% { transform: translate(-50%, -50%) rotate(0deg); }
273
  100% { transform: translate(-50%, -50%) rotate(360deg); }
274
  }
275
- .network-indicator {
276
  position: absolute;
277
- top: 10px;
278
  left: 10px;
279
  background-color: rgba(0,0,0,0.5);
280
  color: white;
@@ -284,26 +243,46 @@
284
  z-index: 10;
285
  display: none;
286
  }
287
- .buffering-indicator {
288
  position: absolute;
289
- bottom: 60px;
290
- left: 10px;
291
- background-color: rgba(0,0,0,0.5);
 
292
  color: white;
293
  padding: 5px 10px;
294
  border-radius: 4px;
295
- font-size: 12px;
296
  z-index: 10;
297
  display: none;
298
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  </style>
300
- <!-- HLS.js for adaptive streaming -->
301
- <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
302
  </head>
303
  <body>
304
  <div class="container">
305
  <div class="header">
306
- <h1>Video Library</h1>
307
  </div>
308
 
309
  <div class="search-container">
@@ -324,12 +303,12 @@
324
  <div class="popup-overlay" id="playerPopup">
325
  <div class="popup-content">
326
  <button class="close-popup" id="closePopup">✕</button>
327
- <div class="network-indicator" id="networkIndicator"></div>
328
  <div class="buffering-indicator" id="bufferingIndicator">Buffering...</div>
 
329
  <div class="popup-title" id="currentTitle">Video Title</div>
330
  <div class="player-wrapper">
331
  <div class="loading-spinner" id="loadingSpinner"></div>
332
- <video id="videoPlayer" controls preload="auto">
333
  Your browser does not support the video tag.
334
  </video>
335
  </div>
@@ -339,10 +318,6 @@
339
  <button class="btn" id="forwardBtn">⏩ 10s</button>
340
  <button class="btn" id="fullscreenBtn">⛶ Fullscreen</button>
341
  <button class="btn" id="muteBtn">🔊 Mute/Unmute</button>
342
- <div class="quality-selector" id="qualitySelector">
343
- <span>Quality:</span>
344
- <button class="quality-btn" data-quality="auto">Auto</button>
345
- </div>
346
  </div>
347
  <div class="instructions">
348
  <p><strong>Keyboard Controls:</strong> Space (Play/Pause) • ← (Back 10s) • → (Forward 10s) • F (Fullscreen) • M (Mute/Unmute)</p>
@@ -358,9 +333,8 @@
358
  let currentVideo = null;
359
  let currentPage = 1;
360
  const videosPerPage = 10;
361
- let hls = null;
362
- let lastNetworkSpeedTest = 0;
363
- let networkSpeed = 0;
364
 
365
  // DOM Elements
366
  const videoGrid = document.getElementById('videoGrid');
@@ -375,66 +349,45 @@
375
  const forwardBtn = document.getElementById('forwardBtn');
376
  const fullscreenBtn = document.getElementById('fullscreenBtn');
377
  const muteBtn = document.getElementById('muteBtn');
378
- const qualitySelector = document.getElementById('qualitySelector');
379
  const loadingSpinner = document.getElementById('loadingSpinner');
380
- const networkIndicator = document.getElementById('networkIndicator');
381
  const bufferingIndicator = document.getElementById('bufferingIndicator');
 
382
 
383
  // Function to fetch video data
384
  async function fetchVideoData() {
385
  try {
386
- // Try to get from cache first
387
- const cachedData = localStorage.getItem('videoLibraryData');
388
- if (cachedData) {
389
- const parsedData = JSON.parse(cachedData);
390
- if (parsedData && (Array.isArray(parsedData) ||
391
- (parsedData.videos && Array.isArray(parsedData.videos)))) {
392
- processVideoData(parsedData);
393
-
394
- // Check if cache is stale (older than 1 hour)
395
- const cacheTime = localStorage.getItem('videoLibraryCacheTime');
396
- if (cacheTime && (Date.now() - parseInt(cacheTime)) < 3600000) {
397
- return;
398
- }
399
- }
400
- }
401
-
402
- // Fetch fresh data
403
  const response = await fetch('database.json');
404
  if (!response.ok) {
405
  throw new Error('Failed to fetch video data');
406
  }
407
  const data = await response.json();
408
 
409
- // Cache the data
410
- localStorage.setItem('videoLibraryData', JSON.stringify(data));
411
- localStorage.setItem('videoLibraryCacheTime', Date.now().toString());
412
 
413
- processVideoData(data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  } catch (error) {
415
  console.error('Error fetching video data:', error);
416
  videoGrid.innerHTML = `<div class="no-videos">Error loading videos: ${error.message}</div>`;
417
  }
418
  }
419
 
420
- function processVideoData(data) {
421
- // Check if data has videos array or is itself an array
422
- videoData = Array.isArray(data) ? data :
423
- (data.videos && Array.isArray(data.videos) ? data.videos : []);
424
-
425
- // Generate thumbnails for videos that don't have them
426
- videoData.forEach(video => {
427
- if (!video.thumbnail) {
428
- // Create a thumbnail URL based on video URL (this is a placeholder)
429
- video.thumbnail = `https://img.youtube.com/vi/${Math.random().toString(36).substring(2)}/mqdefault.jpg`;
430
- }
431
- });
432
-
433
- filteredVideos = [...videoData];
434
- renderPagination();
435
- renderVideoGrid();
436
- }
437
-
438
  // Function to render video grid
439
  function renderVideoGrid() {
440
  if (filteredVideos.length === 0) {
@@ -451,7 +404,6 @@
451
  videoGrid.innerHTML = currentVideos.map((video, index) => `
452
  <div class="video-item" data-index="${startIndex + index}">
453
  <div class="video-thumbnail">
454
- <img src="${video.thumbnail}" alt="${video.title}" onerror="this.style.display='none'">
455
  <div class="play-icon">▶</div>
456
  </div>
457
  <div class="video-item-title">${video.title}</div>
@@ -467,21 +419,6 @@
467
  }
468
  });
469
  });
470
-
471
- // Preload first video for better UX
472
- if (currentVideos.length > 0) {
473
- preloadVideo(currentVideos[0]);
474
- }
475
- }
476
-
477
- // Function to preload video metadata
478
- function preloadVideo(video) {
479
- const tempVideo = document.createElement('video');
480
- tempVideo.src = video.url;
481
- tempVideo.preload = 'metadata';
482
- tempVideo.onloadedmetadata = () => {
483
- console.log(`Preloaded metadata for: ${video.title}`);
484
- };
485
  }
486
 
487
  // Function to render pagination
@@ -544,93 +481,100 @@
544
  });
545
  }
546
 
547
- // Function to play video
548
  function playVideo(video) {
549
  currentVideo = video;
550
  currentTitle.textContent = video.title;
551
 
 
 
 
 
552
  // Show loading spinner
553
  loadingSpinner.style.display = 'block';
 
554
 
555
- // Destroy previous HLS instance if exists
556
- if (hls) {
557
- hls.destroy();
558
- hls = null;
 
 
 
 
 
559
  }
560
 
561
- // Show popup
562
- playerPopup.classList.add('active');
563
-
564
- // Check if this is an HLS stream (.m3u8)
565
- if (video.url.includes('.m3u8')) {
566
- setupHLSPlayer(video.url);
567
  } else {
568
- setupRegularPlayer(video.url);
569
  }
570
 
 
 
 
571
  // Disable scrolling on body when popup is active
572
  document.body.style.overflow = 'hidden';
573
  }
574
 
575
- function setupHLSPlayer(url) {
576
- if (Hls.isSupported()) {
577
- hls = new Hls({
578
- maxBufferLength: 30,
579
- maxMaxBufferLength: 600,
580
- maxBufferSize: 60 * 1000 * 1000,
581
- maxBufferHole: 0.5,
582
- lowLatencyMode: true,
583
- enableWorker: true,
584
- startLevel: -1, // Auto quality
585
- abrEwmaDefaultEstimate: 500000, // 500kbps initial estimate
586
- abrBandWidthFactor: 0.95,
587
- abrBandWidthUpFactor: 0.7,
588
- abrEwmaSlowVoD: 1,
589
- abrEwmaFastVoD: 1,
590
- abrEwmaSlowLive: 1,
591
- abrEwmaFastLive: 1
592
- });
593
-
594
- hls.loadSource(url);
595
- hls.attachMedia(videoPlayer);
596
 
597
- hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
598
- // Add quality options
599
- updateQualityOptions(data.levels);
600
-
601
- // Start playing
602
- videoPlayer.play().catch(error => {
603
- console.error('Error playing video:', error);
604
- loadingSpinner.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
605
  });
606
- });
607
 
608
- hls.on(Hls.Events.ERROR, (event, data) => {
609
- if (data.fatal) {
610
- console.error('HLS fatal error, falling back to regular player');
611
- hls.destroy();
612
- setupRegularPlayer(url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  }
614
  });
615
-
616
- hls.on(Hls.Events.FRAG_BUFFERED, () => {
617
- loadingSpinner.style.display = 'none';
618
- });
619
- } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
620
- // Native HLS support (Safari)
621
- videoPlayer.src = url;
622
- videoPlayer.addEventListener('loadedmetadata', () => {
623
- videoPlayer.play().catch(error => {
624
- console.error('Error playing video:', error);
625
- loadingSpinner.style.display = 'none';
626
- });
627
- });
628
- } else {
629
- console.error('HLS not supported, falling back to regular player');
630
- setupRegularPlayer(url);
631
- }
632
 
633
- // Setup event listeners
634
  setupPlayerEventListeners();
635
  }
636
 
@@ -638,25 +582,89 @@
638
  videoPlayer.src = url;
639
  videoPlayer.load();
640
 
641
- // Clear any existing quality options
642
- updateQualityOptions([]);
643
-
644
- // Play video after showing popup
645
- videoPlayer.addEventListener('loadedmetadata', () => {
646
- videoPlayer.play().catch(error => {
647
- console.error('Error playing video:', error);
648
- loadingSpinner.style.display = 'none';
649
- });
650
- });
651
-
652
- videoPlayer.addEventListener('canplay', () => {
653
  loadingSpinner.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  });
655
 
656
- // Setup event listeners
657
  setupPlayerEventListeners();
658
  }
659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  function setupPlayerEventListeners() {
661
  // Play/Pause button
662
  playPauseBtn.addEventListener('click', togglePlayPause);
@@ -674,12 +682,10 @@
674
  // Player events
675
  videoPlayer.addEventListener('play', () => {
676
  playPauseBtn.textContent = '⏸️ Pause';
677
- startNetworkMonitoring();
678
  });
679
 
680
  videoPlayer.addEventListener('pause', () => {
681
  playPauseBtn.textContent = '⏯️ Play';
682
- stopNetworkMonitoring();
683
  });
684
 
685
  videoPlayer.addEventListener('waiting', () => {
@@ -690,12 +696,9 @@
690
  bufferingIndicator.style.display = 'none';
691
  });
692
 
693
- videoPlayer.addEventListener('seeking', () => {
694
- bufferingIndicator.style.display = 'block';
695
- });
696
-
697
- videoPlayer.addEventListener('seeked', () => {
698
- bufferingIndicator.style.display = 'none';
699
  });
700
 
701
  videoPlayer.addEventListener('error', (e) => {
@@ -716,17 +719,31 @@
716
  }
717
 
718
  function seekRelative(seconds) {
719
- videoPlayer.currentTime = Math.max(0, Math.min(videoPlayer.duration, videoPlayer.currentTime + seconds));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  }
721
 
722
  function toggleFullscreen() {
723
  if (!document.fullscreenElement) {
724
- if (videoPlayer.requestFullscreen) {
725
- videoPlayer.requestFullscreen();
726
- } else if (videoPlayer.webkitRequestFullscreen) {
727
- videoPlayer.webkitRequestFullscreen();
728
- } else if (videoPlayer.msRequestFullscreen) {
729
- videoPlayer.msRequestFullscreen();
730
  }
731
  } else {
732
  if (document.exitFullscreen) {
@@ -744,107 +761,15 @@
744
  muteBtn.textContent = videoPlayer.muted ? '🔇 Unmute' : '🔊 Mute';
745
  }
746
 
747
- function updateQualityOptions(levels) {
748
- // Clear existing quality buttons (except Auto)
749
- const existingBtns = document.querySelectorAll('.quality-btn:not([data-quality="auto"])');
750
- existingBtns.forEach(btn => btn.remove());
751
-
752
- if (levels.length > 0) {
753
- // Add quality options
754
- levels.forEach((level, index) => {
755
- const btn = document.createElement('button');
756
- btn.className = 'quality-btn';
757
- btn.dataset.quality = index;
758
- btn.textContent = `${Math.round(level.height)}p`;
759
- btn.addEventListener('click', () => setQuality(index));
760
- qualitySelector.appendChild(btn);
761
- });
762
-
763
- // Make Auto button active by default
764
- document.querySelector('.quality-btn[data-quality="auto"]').classList.add('active');
765
- } else {
766
- // Hide quality selector if no levels available
767
- qualitySelector.style.display = 'none';
768
- }
769
- }
770
-
771
- function setQuality(level) {
772
- if (hls) {
773
- if (level === 'auto') {
774
- hls.currentLevel = -1; // Auto
775
- } else {
776
- hls.currentLevel = level;
777
- }
778
-
779
- // Update active button
780
- document.querySelectorAll('.quality-btn').forEach(btn => {
781
- btn.classList.remove('active');
782
- });
783
- document.querySelector(`.quality-btn[data-quality="${level}"]`).classList.add('active');
784
- }
785
- }
786
-
787
- function startNetworkMonitoring() {
788
- lastNetworkSpeedTest = 0;
789
- networkSpeed = 0;
790
- networkIndicator.style.display = 'block';
791
- updateNetworkIndicator();
792
-
793
- // Monitor bandwidth every 5 seconds
794
- const monitorInterval = setInterval(() => {
795
- if (videoPlayer.paused) {
796
- clearInterval(monitorInterval);
797
- return;
798
- }
799
- updateNetworkIndicator();
800
- }, 5000);
801
-
802
- // Also monitor during playback
803
- videoPlayer.addEventListener('timeupdate', updateNetworkIndicator);
804
- }
805
-
806
- function stopNetworkMonitoring() {
807
- networkIndicator.style.display = 'none';
808
- }
809
-
810
- function updateNetworkIndicator() {
811
- const now = performance.now();
812
- const bytesLoaded = videoPlayer.buffered.length > 0 ?
813
- videoPlayer.buffered.end(videoPlayer.buffered.length - 1) * videoPlayer.bandwidth : 0;
814
-
815
- if (lastNetworkSpeedTest > 0) {
816
- const timeDiff = (now - lastNetworkSpeedTest) / 1000; // in seconds
817
- const byteDiff = bytesLoaded - networkSpeed;
818
-
819
- if (timeDiff > 0 && byteDiff > 0) {
820
- networkSpeed = byteDiff / timeDiff; // bytes per second
821
- const speedMbps = (networkSpeed * 8 / (1024 * 1024)).toFixed(2);
822
- networkIndicator.textContent = `${speedMbps} Mbps`;
823
-
824
- // Color code based on speed
825
- if (networkSpeed < 500000) { // < 4 Mbps
826
- networkIndicator.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
827
- } else if (networkSpeed < 1000000) { // < 8 Mbps
828
- networkIndicator.style.backgroundColor = 'rgba(255, 165, 0, 0.5)';
829
- } else {
830
- networkIndicator.style.backgroundColor = 'rgba(0, 128, 0, 0.5)';
831
- }
832
- }
833
- }
834
-
835
- lastNetworkSpeedTest = now;
836
- }
837
-
838
  // Close popup function
839
  function closePlayerPopup() {
840
- if (hls) {
841
- hls.destroy();
842
- hls = null;
843
- }
844
  videoPlayer.pause();
 
 
 
 
845
  playerPopup.classList.remove('active');
846
  document.body.style.overflow = '';
847
- stopNetworkMonitoring();
848
  }
849
 
850
  // Search functionality
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HF Video Library</title>
7
  <style>
8
  * {
9
  box-sizing: border-box;
 
82
  height: 100%;
83
  object-fit: cover;
84
  }
 
 
 
 
 
85
  .video-item-title {
86
  font-weight: 500;
87
  margin-top: 10px;
 
181
  padding: 15px;
182
  background-color: #111;
183
  flex-wrap: wrap;
 
184
  }
185
  .btn {
186
  padding: 8px 16px;
 
208
  color: white;
209
  opacity: 0.8;
210
  transition: opacity 0.2s;
211
+ position: absolute;
212
  }
213
  .video-item:hover .play-icon {
214
  opacity: 1;
215
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  .loading-spinner {
217
  position: absolute;
218
  top: 50%;
 
231
  0% { transform: translate(-50%, -50%) rotate(0deg); }
232
  100% { transform: translate(-50%, -50%) rotate(360deg); }
233
  }
234
+ .buffering-indicator {
235
  position: absolute;
236
+ bottom: 60px;
237
  left: 10px;
238
  background-color: rgba(0,0,0,0.5);
239
  color: white;
 
243
  z-index: 10;
244
  display: none;
245
  }
246
+ .seek-preview {
247
  position: absolute;
248
+ bottom: 100px;
249
+ left: 50%;
250
+ transform: translateX(-50%);
251
+ background-color: rgba(0,0,0,0.7);
252
  color: white;
253
  padding: 5px 10px;
254
  border-radius: 4px;
255
+ font-size: 14px;
256
  z-index: 10;
257
  display: none;
258
  }
259
+ .no-videos {
260
+ padding: 20px;
261
+ text-align: center;
262
+ color: #777;
263
+ }
264
+ @media (max-width: 768px) {
265
+ .video-grid {
266
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
267
+ }
268
+ .popup-content {
269
+ width: 95%;
270
+ }
271
+ .controls {
272
+ gap: 5px;
273
+ padding: 10px;
274
+ }
275
+ .btn {
276
+ padding: 6px 12px;
277
+ font-size: 0.9em;
278
+ }
279
+ }
280
  </style>
 
 
281
  </head>
282
  <body>
283
  <div class="container">
284
  <div class="header">
285
+ <h1>HF Video Library</h1>
286
  </div>
287
 
288
  <div class="search-container">
 
303
  <div class="popup-overlay" id="playerPopup">
304
  <div class="popup-content">
305
  <button class="close-popup" id="closePopup">✕</button>
 
306
  <div class="buffering-indicator" id="bufferingIndicator">Buffering...</div>
307
+ <div class="seek-preview" id="seekPreview"></div>
308
  <div class="popup-title" id="currentTitle">Video Title</div>
309
  <div class="player-wrapper">
310
  <div class="loading-spinner" id="loadingSpinner"></div>
311
+ <video id="videoPlayer" preload="auto" playsinline>
312
  Your browser does not support the video tag.
313
  </video>
314
  </div>
 
318
  <button class="btn" id="forwardBtn">⏩ 10s</button>
319
  <button class="btn" id="fullscreenBtn">⛶ Fullscreen</button>
320
  <button class="btn" id="muteBtn">🔊 Mute/Unmute</button>
 
 
 
 
321
  </div>
322
  <div class="instructions">
323
  <p><strong>Keyboard Controls:</strong> Space (Play/Pause) • ← (Back 10s) • → (Forward 10s) • F (Fullscreen) • M (Mute/Unmute)</p>
 
333
  let currentVideo = null;
334
  let currentPage = 1;
335
  const videosPerPage = 10;
336
+ let isSeeking = false;
337
+ let preloadedSegments = new Set();
 
338
 
339
  // DOM Elements
340
  const videoGrid = document.getElementById('videoGrid');
 
349
  const forwardBtn = document.getElementById('forwardBtn');
350
  const fullscreenBtn = document.getElementById('fullscreenBtn');
351
  const muteBtn = document.getElementById('muteBtn');
 
352
  const loadingSpinner = document.getElementById('loadingSpinner');
 
353
  const bufferingIndicator = document.getElementById('bufferingIndicator');
354
+ const seekPreview = document.getElementById('seekPreview');
355
 
356
  // Function to fetch video data
357
  async function fetchVideoData() {
358
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  const response = await fetch('database.json');
360
  if (!response.ok) {
361
  throw new Error('Failed to fetch video data');
362
  }
363
  const data = await response.json();
364
 
365
+ // Check if data has videos array or is itself an array
366
+ videoData = Array.isArray(data) ? data :
367
+ (data.videos && Array.isArray(data.videos) ? data.videos : []);
368
 
369
+ // Optimize HF URLs
370
+ videoData.forEach(video => {
371
+ if (video.url.includes('huggingface.co')) {
372
+ // Force no-transform for HF URLs
373
+ if (!video.url.includes('?')) {
374
+ video.url += '?';
375
+ }
376
+ if (!video.url.includes('download=true')) {
377
+ video.url += (video.url.endsWith('?') ? '' : '&') + 'download=true';
378
+ }
379
+ }
380
+ });
381
+
382
+ filteredVideos = [...videoData];
383
+ renderPagination();
384
+ renderVideoGrid();
385
  } catch (error) {
386
  console.error('Error fetching video data:', error);
387
  videoGrid.innerHTML = `<div class="no-videos">Error loading videos: ${error.message}</div>`;
388
  }
389
  }
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  // Function to render video grid
392
  function renderVideoGrid() {
393
  if (filteredVideos.length === 0) {
 
404
  videoGrid.innerHTML = currentVideos.map((video, index) => `
405
  <div class="video-item" data-index="${startIndex + index}">
406
  <div class="video-thumbnail">
 
407
  <div class="play-icon">▶</div>
408
  </div>
409
  <div class="video-item-title">${video.title}</div>
 
419
  }
420
  });
421
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
 
424
  // Function to render pagination
 
481
  });
482
  }
483
 
484
+ // Function to play video with ultra-fast seeking
485
  function playVideo(video) {
486
  currentVideo = video;
487
  currentTitle.textContent = video.title;
488
 
489
+ // Reset state
490
+ isSeeking = false;
491
+ preloadedSegments.clear();
492
+
493
  // Show loading spinner
494
  loadingSpinner.style.display = 'block';
495
+ bufferingIndicator.style.display = 'none';
496
 
497
+ // Optimize HF URL
498
+ let videoUrl = video.url;
499
+ if (videoUrl.includes('huggingface.co')) {
500
+ if (!videoUrl.includes('?')) {
501
+ videoUrl += '?';
502
+ }
503
+ if (!videoUrl.includes('download=true')) {
504
+ videoUrl += (videoUrl.endsWith('?') ? '' : '&') + 'download=true';
505
+ }
506
  }
507
 
508
+ // Use MediaSource for advanced buffering control if supported
509
+ if (window.MediaSource && MediaSource.isTypeSupported('video/mp4')) {
510
+ setupMediaSourcePlayer(videoUrl);
 
 
 
511
  } else {
512
+ setupRegularPlayer(videoUrl);
513
  }
514
 
515
+ // Show popup
516
+ playerPopup.classList.add('active');
517
+
518
  // Disable scrolling on body when popup is active
519
  document.body.style.overflow = 'hidden';
520
  }
521
 
522
+ function setupMediaSourcePlayer(url) {
523
+ const mediaSource = new MediaSource();
524
+ videoPlayer.src = URL.createObjectURL(mediaSource);
525
+
526
+ mediaSource.addEventListener('sourceopen', function() {
527
+ const sourceBuffer = mediaSource.addSourceBuffer('video/mp4');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
+ // Start loading the initial segment
530
+ fetchSegment(url, 0, 1024 * 1024 * 2) // First 2MB
531
+ .then(segment => {
532
+ sourceBuffer.addEventListener('updateend', function() {
533
+ if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
534
+ mediaSource.endOfStream();
535
+ videoPlayer.play().catch(e => console.error('Play error:', e));
536
+ loadingSpinner.style.display = 'none';
537
+
538
+ // Start aggressive preloading
539
+ preloadNextSegments(videoPlayer.duration, url);
540
+ }
541
+ }, { once: true });
542
+
543
+ sourceBuffer.appendBuffer(segment);
544
+ })
545
+ .catch(error => {
546
+ console.error('Error loading initial segment:', error);
547
+ // Fallback to regular player
548
+ setupRegularPlayer(url);
549
  });
 
550
 
551
+ // Handle seeking
552
+ videoPlayer.addEventListener('seeking', function() {
553
+ if (!isSeeking && mediaSource.readyState === 'open') {
554
+ isSeeking = true;
555
+ const targetTime = videoPlayer.currentTime;
556
+ seekPreview.textContent = formatTime(targetTime);
557
+ seekPreview.style.display = 'block';
558
+
559
+ // Load segment around seek position
560
+ const segmentStart = Math.max(0, targetTime - 2);
561
+ const segmentEnd = Math.min(videoPlayer.duration, targetTime + 2);
562
+
563
+ fetchByteRange(url, segmentStart, segmentEnd)
564
+ .then(segment => {
565
+ sourceBuffer.appendBuffer(segment);
566
+ seekPreview.style.display = 'none';
567
+ isSeeking = false;
568
+ })
569
+ .catch(error => {
570
+ console.error('Seek error:', error);
571
+ seekPreview.style.display = 'none';
572
+ isSeeking = false;
573
+ });
574
  }
575
  });
576
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
 
578
  setupPlayerEventListeners();
579
  }
580
 
 
582
  videoPlayer.src = url;
583
  videoPlayer.load();
584
 
585
+ // Set up event listeners for immediate playback
586
+ videoPlayer.addEventListener('canplaythrough', function() {
 
 
 
 
 
 
 
 
 
 
587
  loadingSpinner.style.display = 'none';
588
+ videoPlayer.play().catch(e => console.error('Play error:', e));
589
+
590
+ // Start aggressive preloading
591
+ preloadNextSegments(videoPlayer.duration, url);
592
+ }, { once: true });
593
+
594
+ videoPlayer.addEventListener('seeking', function() {
595
+ const targetTime = videoPlayer.currentTime;
596
+ seekPreview.textContent = formatTime(targetTime);
597
+ seekPreview.style.display = 'block';
598
+
599
+ setTimeout(() => {
600
+ seekPreview.style.display = 'none';
601
+ }, 1000);
602
  });
603
 
 
604
  setupPlayerEventListeners();
605
  }
606
 
607
+ function preloadNextSegments(duration, url) {
608
+ if (!duration || duration === Infinity) return;
609
+
610
+ const currentTime = videoPlayer.currentTime;
611
+ const segmentSize = 1024 * 1024 * 4; // 4MB segments
612
+
613
+ // Preload next 3 segments ahead
614
+ for (let i = 1; i <= 3; i++) {
615
+ const segmentStart = currentTime + (i * 5);
616
+ if (segmentStart >= duration) break;
617
+
618
+ const segmentEnd = Math.min(duration, segmentStart + 5);
619
+ const segmentKey = `${Math.floor(segmentStart)}-${Math.floor(segmentEnd)}`;
620
+
621
+ if (!preloadedSegments.has(segmentKey)) {
622
+ preloadedSegments.add(segmentKey);
623
+
624
+ // Just trigger the download, we don't need the data for regular playback
625
+ fetchByteRange(url, segmentStart, segmentEnd)
626
+ .catch(error => console.error('Preload error:', error));
627
+ }
628
+ }
629
+ }
630
+
631
+ async function fetchSegment(url, start, end) {
632
+ const response = await fetch(url, {
633
+ headers: {
634
+ 'Range': `bytes=${start}-${end}`
635
+ }
636
+ });
637
+
638
+ if (!response.ok) {
639
+ throw new Error(`HTTP error! status: ${response.status}`);
640
+ }
641
+
642
+ return response.arrayBuffer();
643
+ }
644
+
645
+ async function fetchByteRange(url, startTime, endTime) {
646
+ // Estimate byte range based on time (approximation)
647
+ const duration = videoPlayer.duration || 60; // Fallback to 60s if unknown
648
+ const fileSize = await getFileSize(url);
649
+ const startByte = Math.floor((startTime / duration) * fileSize);
650
+ const endByte = Math.floor((endTime / duration) * fileSize);
651
+
652
+ return fetchSegment(url, startByte, endByte);
653
+ }
654
+
655
+ async function getFileSize(url) {
656
+ try {
657
+ const response = await fetch(url, { method: 'HEAD' });
658
+ if (response.ok) {
659
+ const contentLength = response.headers.get('Content-Length');
660
+ return parseInt(contentLength) || 100 * 1024 * 1024; // Fallback to 100MB
661
+ }
662
+ } catch (error) {
663
+ console.error('Error getting file size:', error);
664
+ }
665
+ return 100 * 1024 * 1024; // Default fallback
666
+ }
667
+
668
  function setupPlayerEventListeners() {
669
  // Play/Pause button
670
  playPauseBtn.addEventListener('click', togglePlayPause);
 
682
  // Player events
683
  videoPlayer.addEventListener('play', () => {
684
  playPauseBtn.textContent = '⏸️ Pause';
 
685
  });
686
 
687
  videoPlayer.addEventListener('pause', () => {
688
  playPauseBtn.textContent = '⏯️ Play';
 
689
  });
690
 
691
  videoPlayer.addEventListener('waiting', () => {
 
696
  bufferingIndicator.style.display = 'none';
697
  });
698
 
699
+ videoPlayer.addEventListener('timeupdate', () => {
700
+ // Continuously preload ahead
701
+ preloadNextSegments(videoPlayer.duration, videoPlayer.src);
 
 
 
702
  });
703
 
704
  videoPlayer.addEventListener('error', (e) => {
 
719
  }
720
 
721
  function seekRelative(seconds) {
722
+ const newTime = Math.max(0, Math.min(videoPlayer.duration, videoPlayer.currentTime + seconds));
723
+ videoPlayer.currentTime = newTime;
724
+
725
+ // Show seek preview
726
+ seekPreview.textContent = formatTime(newTime);
727
+ seekPreview.style.display = 'block';
728
+ setTimeout(() => {
729
+ seekPreview.style.display = 'none';
730
+ }, 1000);
731
+ }
732
+
733
+ function formatTime(seconds) {
734
+ const mins = Math.floor(seconds / 60);
735
+ const secs = Math.floor(seconds % 60);
736
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
737
  }
738
 
739
  function toggleFullscreen() {
740
  if (!document.fullscreenElement) {
741
+ if (playerPopup.requestFullscreen) {
742
+ playerPopup.requestFullscreen();
743
+ } else if (playerPopup.webkitRequestFullscreen) {
744
+ playerPopup.webkitRequestFullscreen();
745
+ } else if (playerPopup.msRequestFullscreen) {
746
+ playerPopup.msRequestFullscreen();
747
  }
748
  } else {
749
  if (document.exitFullscreen) {
 
761
  muteBtn.textContent = videoPlayer.muted ? '🔇 Unmute' : '🔊 Mute';
762
  }
763
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
  // Close popup function
765
  function closePlayerPopup() {
 
 
 
 
766
  videoPlayer.pause();
767
+ if (videoPlayer.src.startsWith('blob:')) {
768
+ URL.revokeObjectURL(videoPlayer.src);
769
+ }
770
+ videoPlayer.src = '';
771
  playerPopup.classList.remove('active');
772
  document.body.style.overflow = '';
 
773
  }
774
 
775
  // Search functionality