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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +252 -467
index.html CHANGED
@@ -4,8 +4,6 @@
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,34 +75,10 @@
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,7 +115,7 @@
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,7 +131,7 @@
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,13 +139,13 @@
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,23 +153,19 @@
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,50 +173,39 @@
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,31 +215,10 @@
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,33 +248,24 @@
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,27 +273,21 @@
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,62 +304,12 @@
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) {
@@ -452,7 +325,6 @@
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>
@@ -463,16 +335,14 @@
463
  item.addEventListener('click', function() {
464
  const index = parseInt(this.getAttribute('data-index'));
465
  if (!isNaN(index) && index >= 0 && index < filteredVideos.length) {
 
466
  playVideo(filteredVideos[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) {
@@ -518,304 +388,219 @@
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>
 
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
  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
  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
  .popup-content {
132
  width: 90%;
133
  max-width: 1024px;
134
+ background-color: #222;
135
  border-radius: 8px;
136
  overflow: hidden;
137
  position: relative;
 
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
  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
  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
  width: 95%;
216
  }
217
  }
218
+ .no-videos {
219
+ padding: 20px;
220
+ text-align: center;
221
+ color: #777;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
  </style>
224
  </head>
 
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
  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
  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) {
 
325
  videoGrid.innerHTML = currentVideos.map((video, index) => `
326
  <div class="video-item" data-index="${startIndex + index}">
327
  <div class="video-thumbnail">
 
328
  <div class="play-icon">▶</div>
329
  </div>
330
  <div class="video-item-title">${video.title}</div>
 
335
  item.addEventListener('click', function() {
336
  const index = parseInt(this.getAttribute('data-index'));
337
  if (!isNaN(index) && index >= 0 && index < filteredVideos.length) {
338
+ // Call the async function to play the video
339
  playVideo(filteredVideos[index]);
340
  }
341
  });
342
  });
 
 
 
343
  }
344
 
345
+ // Function to render pagination
346
  function renderPagination() {
347
  const totalPages = Math.ceil(filteredVideos.length / videosPerPage);
348
  if (totalPages <= 1) {
 
388
  });
389
  }
390
 
391
+ // Async function to play a video and handle HF Model Hub authentication if needed
392
+ async function playVideo(video) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  currentVideo = video;
394
  currentTitle.textContent = video.title;
 
 
 
395
 
396
+ let videoUrl = video.url;
397
+ let finalUrl = videoUrl;
 
 
 
398
 
399
+ // Check if the URL is from HF Model Hub and might require authentication.
400
+ if (videoUrl.includes("huggingface.co")) {
401
+ // Try to get token from localStorage or prompt the user.
402
+ let token = localStorage.getItem('hf_token');
403
+ if (!token) {
404
+ token = prompt("Enter your HuggingFace token:");
405
+ if (!token) {
406
+ alert("Authentication token required to access HuggingFace video.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  return;
408
  }
409
+ localStorage.setItem('hf_token', token);
410
+ }
411
+ try {
412
+ const response = await fetch(videoUrl, {
413
+ headers: {
414
+ 'Authorization': `Bearer ${token}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
  });
417
+ if (!response.ok) {
418
+ throw new Error(`Failed to load video: ${response.status} ${response.statusText}`);
419
+ }
420
+ const blob = await response.blob();
421
+ finalUrl = URL.createObjectURL(blob);
422
+ } catch (error) {
423
+ alert("Error loading HuggingFace video: " + error.message);
424
+ return;
425
  }
426
  }
427
+
428
+ videoPlayer.src = finalUrl;
429
+ videoPlayer.load();
430
+
431
+ // Show popup and disable background scrolling
432
+ playerPopup.classList.add('active');
433
+ document.body.style.overflow = 'hidden';
434
+
435
+ // Play video after a short delay to allow the popup to render
436
+ setTimeout(() => {
437
+ videoPlayer.play().catch(error => {
438
+ console.error("Error playing video:", error);
439
+ alert(`Error playing video: ${error.message}. Make sure the URL is accessible.`);
 
 
 
 
 
 
440
  });
441
+ }, 300);
 
 
 
 
 
442
  }
443
 
444
+ // Close popup function
445
  function closePlayerPopup() {
446
+ videoPlayer.pause();
447
  playerPopup.classList.remove('active');
448
  document.body.style.overflow = '';
 
449
  }
450
 
451
+ // Search functionality
452
+ searchInput.addEventListener('input', function() {
453
+ const searchTerm = this.value.toLowerCase();
454
+ filteredVideos = videoData.filter(video =>
455
+ video.title.toLowerCase().includes(searchTerm)
456
+ );
457
+ currentPage = 1; // Reset to first page on search
458
+ renderPagination();
459
+ renderVideoGrid();
460
+ });
461
+
462
+ // Close popup when clicking close button
463
  closePopup.addEventListener('click', closePlayerPopup);
464
+
465
+ // Close popup when clicking outside the popup content
466
+ playerPopup.addEventListener('click', function(e) {
467
+ if (e.target === playerPopup) {
468
  closePlayerPopup();
469
  }
470
  });
471
+
472
+ // Video control buttons
473
+ playPauseBtn.addEventListener('click', function() {
474
+ if (videoPlayer.paused) {
475
+ videoPlayer.play();
476
+ } else {
477
+ videoPlayer.pause();
478
+ }
479
+ });
480
+
481
+ backwardBtn.addEventListener('click', function() {
482
+ videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
483
+ });
484
+
485
+ forwardBtn.addEventListener('click', function() {
486
+ videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
487
+ });
488
+
489
+ fullscreenBtn.addEventListener('click', function() {
490
+ if (videoPlayer.requestFullscreen) {
491
+ videoPlayer.requestFullscreen();
492
+ } else if (videoPlayer.webkitRequestFullscreen) {
493
+ videoPlayer.webkitRequestFullscreen();
494
+ } else if (videoPlayer.msRequestFullscreen) {
495
+ videoPlayer.msRequestFullscreen();
496
+ }
497
+ });
498
+
499
+ muteBtn.addEventListener('click', function() {
500
+ videoPlayer.muted = !videoPlayer.muted;
501
+ });
502
+
503
+ // Keyboard controls (active when popup is visible)
504
+ document.addEventListener('keydown', function(e) {
505
+ if (!playerPopup.classList.contains('active')) return;
506
+
507
+ switch(e.key) {
508
+ case ' ':
509
+ if (videoPlayer.paused) {
510
+ videoPlayer.play();
511
+ } else {
512
+ videoPlayer.pause();
513
+ }
514
+ e.preventDefault();
515
+ break;
516
+ case 'ArrowRight':
517
+ videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
518
+ e.preventDefault();
519
+ break;
520
+ case 'ArrowLeft':
521
+ videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
522
+ e.preventDefault();
523
+ break;
524
+ case 'f':
525
+ case 'F':
526
+ if (document.fullscreenElement) {
527
+ document.exitFullscreen();
528
+ } else if (videoPlayer.requestFullscreen) {
529
+ videoPlayer.requestFullscreen();
530
+ }
531
+ e.preventDefault();
532
+ break;
533
+ case 'm':
534
+ case 'M':
535
+ videoPlayer.muted = !videoPlayer.muted;
536
+ e.preventDefault();
537
+ break;
538
+ case 'Escape':
539
+ closePlayerPopup();
540
+ e.preventDefault();
541
+ break;
542
+ }
543
+ });
544
+
545
+ // Double-tap controls for mobile/touch devices
546
+ let lastTap = 0;
547
+ let tapTimeout;
548
 
549
+ videoPlayer.addEventListener('click', function(e) {
550
+ const currentTime = new Date().getTime();
551
+ const tapLength = currentTime - lastTap;
552
+ clearTimeout(tapTimeout);
553
+
554
+ if (tapLength < 500 && tapLength > 0) {
555
+ const playerRect = videoPlayer.getBoundingClientRect();
556
+ const clickX = e.clientX - playerRect.left;
557
+ const playerWidth = playerRect.width;
558
+
559
+ if (clickX < playerWidth / 3) {
560
+ videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10);
561
+ } else if (clickX > (playerWidth * 2) / 3) {
562
+ videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10);
563
+ } else {
564
+ if (videoPlayer.paused) {
565
+ videoPlayer.play();
566
+ } else {
567
+ videoPlayer.pause();
568
+ }
569
+ }
570
+ e.preventDefault();
571
+ } else {
572
+ tapTimeout = setTimeout(function() {
573
+ // Single tap action (no default action here)
574
+ }, 300);
575
+ }
576
+ lastTap = currentTime;
577
+ });
578
+
579
+ // Fix for fullscreen mode changes
580
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
581
+ document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
582
+ document.addEventListener('mozfullscreenchange', handleFullscreenChange);
583
+ document.addEventListener('MSFullscreenChange', handleFullscreenChange);
584
+
585
+ function handleFullscreenChange() {
586
+ if (document.fullscreenElement ||
587
+ document.webkitFullscreenElement ||
588
+ document.mozFullScreenElement) {
589
+ console.log('Entered fullscreen mode');
590
+ const fullscreenVideo = document.fullscreenElement ||
591
+ document.webkitFullscreenElement ||
592
+ document.mozFullScreenElement;
593
+ if (fullscreenVideo === videoPlayer) {
594
+ videoPlayer.style.width = '100%';
595
+ videoPlayer.style.height = '100%';
596
+ }
597
+ } else {
598
+ console.log('Exited fullscreen mode');
599
+ videoPlayer.style.width = '100%';
600
+ videoPlayer.style.height = '100%';
601
+ }
602
+ }
603
+
604
  // Initialize video data on page load
605
  fetchVideoData();
606
  </script>