Subham9126 commited on
Commit
3adfd32
·
verified ·
1 Parent(s): e7a2fbb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +450 -617
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 Player</title>
7
  <style>
8
  * {
9
  box-sizing: border-box;
@@ -11,772 +11,605 @@
11
  padding: 0;
12
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
  }
14
-
15
  body {
16
  background-color: #f5f5f5;
17
- color: #333;
18
- line-height: 1.6;
19
- }
20
-
21
- .container {
22
  max-width: 1200px;
23
  margin: 0 auto;
24
- padding: 20px;
25
  }
26
-
27
- header {
28
- background-color: #2c3e50;
29
- color: white;
30
- padding: 20px 0;
31
- text-align: center;
32
- margin-bottom: 30px;
33
- border-radius: 5px;
34
  }
35
-
36
- h1 {
37
- font-size: 2.5rem;
 
 
 
38
  }
39
-
40
  .search-container {
41
- margin: 20px 0;
42
- display: flex;
43
- justify-content: center;
 
44
  }
45
-
46
  #search {
47
- width: 70%;
48
- padding: 12px;
49
  border: 1px solid #ddd;
50
- border-radius: 25px;
51
  font-size: 16px;
52
- outline: none;
53
- transition: all 0.3s;
54
  }
55
-
56
- #search:focus {
57
- border-color: #3498db;
58
- box-shadow: 0 0 8px rgba(52, 152, 219, 0.5);
59
- }
60
-
61
  .video-list {
 
 
 
 
 
 
62
  display: grid;
63
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
64
- gap: 20px;
65
- margin-bottom: 30px;
66
  }
67
-
68
  .video-item {
69
- background-color: white;
 
70
  border-radius: 8px;
71
- overflow: hidden;
72
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
73
- transition: transform 0.3s, box-shadow 0.3s;
74
  cursor: pointer;
 
 
75
  }
76
-
77
  .video-item:hover {
78
  transform: translateY(-5px);
79
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
80
  }
81
-
82
  .video-thumbnail {
83
- position: relative;
84
- padding-top: 56.25%; /* 16:9 aspect ratio */
85
- background-color: #000;
86
- }
87
-
88
- .video-thumbnail img {
89
- position: absolute;
90
- top: 0;
91
- left: 0;
92
  width: 100%;
93
- height: 100%;
94
- object-fit: cover;
 
 
 
 
 
 
95
  }
96
-
97
- .video-info {
98
- padding: 15px;
99
  }
100
-
101
- .video-title {
102
- font-size: 1.1rem;
103
- margin-bottom: 5px;
104
- font-weight: 600;
105
- display: -webkit-box;
106
- -webkit-line-clamp: 2;
107
- -webkit-box-orient: vertical;
108
  overflow: hidden;
 
109
  }
110
-
111
- .video-player-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  position: fixed;
113
  top: 0;
114
  left: 0;
115
  width: 100%;
116
  height: 100%;
117
- background-color: rgba(0, 0, 0, 0.9);
118
  display: flex;
119
- flex-direction: column;
120
- justify-content: center;
121
  align-items: center;
 
122
  z-index: 1000;
123
  opacity: 0;
124
- pointer-events: none;
125
- transition: opacity 0.3s;
126
  }
127
-
128
- .video-player-container.show {
129
  opacity: 1;
130
- pointer-events: all;
131
  }
132
-
133
- .video-player-wrapper {
134
  width: 90%;
135
- max-width: 1000px;
136
- position: relative;
137
- }
138
-
139
- #videoPlayer {
140
- width: 100%;
141
  border-radius: 8px;
142
- background-color: #000;
143
- }
144
-
145
- .player-controls {
146
- display: flex;
147
- justify-content: space-between;
148
- align-items: center;
149
- background-color: rgba(0, 0, 0, 0.7);
150
- padding: 10px;
151
- border-radius: 0 0 8px 8px;
152
- margin-top: -5px;
153
- }
154
-
155
- .control-group {
156
- display: flex;
157
- align-items: center;
158
- gap: 15px;
159
  }
160
-
161
- .control-btn {
162
- background: none;
163
- border: none;
 
164
  color: white;
165
- font-size: 1.2rem;
166
- cursor: pointer;
167
- padding: 5px;
168
  border-radius: 50%;
169
- width: 36px;
170
- height: 36px;
171
  display: flex;
172
  align-items: center;
173
  justify-content: center;
174
- transition: background-color 0.2s;
175
- }
176
-
177
- .control-btn:hover {
178
- background-color: rgba(255, 255, 255, 0.2);
179
- }
180
-
181
- .progress-container {
182
- flex-grow: 1;
183
- margin: 0 15px;
184
- height: 5px;
185
- background-color: rgba(255, 255, 255, 0.2);
186
- border-radius: 5px;
187
  cursor: pointer;
188
- position: relative;
 
189
  }
190
-
191
- .progress-bar {
192
- height: 100%;
193
- background-color: #3498db;
194
- border-radius: 5px;
195
- width: 0%;
196
  position: relative;
 
 
197
  }
198
-
199
- .progress-time {
200
- position: absolute;
201
- top: -25px;
202
- transform: translateX(-50%);
203
- background-color: rgba(0, 0, 0, 0.7);
204
- color: white;
205
- padding: 3px 6px;
206
- border-radius: 4px;
207
- font-size: 0.8rem;
208
- display: none;
209
- }
210
-
211
- .progress-container:hover .progress-time {
212
- display: block;
213
- }
214
-
215
- .close-player {
216
  position: absolute;
217
- top: -40px;
218
- right: 0;
219
- background: none;
220
- border: none;
221
- color: white;
222
- font-size: 1.5rem;
223
- cursor: pointer;
224
  }
225
-
226
- .time-display {
227
  color: white;
228
- font-size: 0.9rem;
229
- min-width: 100px;
230
- text-align: center;
231
  }
232
-
233
- .volume-container {
234
  display: flex;
235
- align-items: center;
236
- gap: 5px;
237
- }
238
-
239
- .volume-slider {
240
- width: 80px;
241
- cursor: pointer;
242
- }
243
-
244
- .no-results {
245
- text-align: center;
246
- padding: 40px;
247
- color: #666;
248
- font-size: 1.2rem;
249
- grid-column: 1 / -1;
250
- }
251
-
252
- .auth-container {
253
- margin: 20px 0;
254
  padding: 15px;
255
- background-color: #f8f9fa;
256
- border-radius: 5px;
257
- display: none;
258
- }
259
-
260
- .auth-container.show {
261
- display: block;
262
  }
263
-
264
- .auth-input {
265
- width: 100%;
266
- padding: 10px;
267
- margin: 10px 0;
268
- border: 1px solid #ddd;
269
- border-radius: 4px;
270
- }
271
-
272
- .auth-btn {
273
- background-color: #2c3e50;
274
  color: white;
275
  border: none;
276
- padding: 10px 15px;
277
  border-radius: 4px;
278
  cursor: pointer;
 
279
  }
280
-
281
- .auth-btn:hover {
282
- background-color: #1a252f;
283
  }
284
-
285
- .error-message {
286
- color: #e74c3c;
287
- margin-top: 10px;
 
288
  }
289
-
290
- @media (max-width: 768px) {
291
- .video-list {
292
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
293
- }
294
-
295
- .control-group {
296
- gap: 8px;
297
- }
298
-
299
- .time-display {
300
- min-width: 80px;
301
- font-size: 0.8rem;
302
- }
303
-
304
- .volume-slider {
305
- width: 60px;
306
- }
307
  }
308
-
309
- @media (max-width: 480px) {
310
- .video-list {
311
- grid-template-columns: 1fr;
312
- }
313
-
314
- #search {
315
- width: 90%;
316
  }
317
-
318
- .video-player-wrapper {
319
  width: 95%;
320
  }
321
-
322
- .control-btn {
323
- font-size: 1rem;
324
- width: 30px;
325
- height: 30px;
326
- }
327
  }
328
  </style>
329
  </head>
330
  <body>
331
  <div class="container">
332
- <header>
333
- <h1>Video Player</h1>
334
- </header>
335
-
336
- <div class="auth-container" id="authContainer">
337
- <h3>Private Repository Authentication</h3>
338
- <input type="text" class="auth-input" id="hfToken" placeholder="Hugging Face Access Token">
339
- <button class="auth-btn" id="authBtn">Authenticate</button>
340
- <div class="error-message" id="authError"></div>
341
  </div>
342
 
343
  <div class="search-container">
344
  <input type="text" id="search" placeholder="Search videos...">
345
  </div>
346
 
347
- <div class="video-list" id="videoList">
348
- <!-- Videos will be loaded here -->
 
 
 
 
 
349
  </div>
350
  </div>
351
-
352
- <div class="video-player-container" id="playerContainer">
353
- <div class="video-player-wrapper">
354
- <button class="close-player" id="closePlayer">&times;</button>
355
- <video id="videoPlayer" crossorigin="anonymous"></video>
356
- <div class="player-controls">
357
- <div class="control-group">
358
- <button class="control-btn" id="playPauseBtn">&#9658;</button>
359
- <button class="control-btn" id="skipBackBtn">&#8630;</button>
360
- <button class="control-btn" id="skipForwardBtn">&#8631;</button>
361
- <div class="time-display">
362
- <span id="currentTime">0:00</span> / <span id="duration">0:00</span>
363
- </div>
364
- </div>
365
-
366
- <div class="progress-container" id="progressContainer">
367
- <div class="progress-bar" id="progressBar"></div>
368
- <div class="progress-time" id="progressTime">0:00</div>
369
- </div>
370
-
371
- <div class="control-group">
372
- <div class="volume-container">
373
- <button class="control-btn" id="volumeBtn">&#128266;</button>
374
- <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.01" value="1">
375
- </div>
376
- <button class="control-btn" id="fullscreenBtn">&#9974;</button>
377
- </div>
378
  </div>
379
  </div>
380
  </div>
381
-
382
  <script>
383
  // Global variables
384
- let videos = [];
385
- let lastTapTime = 0;
386
- let hfToken = null;
387
- let isPrivateRepo = false;
388
-
389
- // DOM elements
390
- const videoListElement = document.getElementById('videoList');
 
 
391
  const searchInput = document.getElementById('search');
392
- const playerContainer = document.getElementById('playerContainer');
393
  const videoPlayer = document.getElementById('videoPlayer');
 
 
394
  const playPauseBtn = document.getElementById('playPauseBtn');
395
- const skipBackBtn = document.getElementById('skipBackBtn');
396
- const skipForwardBtn = document.getElementById('skipForwardBtn');
397
- const progressBar = document.getElementById('progressBar');
398
- const progressContainer = document.getElementById('progressContainer');
399
- const progressTime = document.getElementById('progressTime');
400
- const currentTimeDisplay = document.getElementById('currentTime');
401
- const durationDisplay = document.getElementById('duration');
402
- const volumeBtn = document.getElementById('volumeBtn');
403
- const volumeSlider = document.getElementById('volumeSlider');
404
  const fullscreenBtn = document.getElementById('fullscreenBtn');
405
- const closePlayer = document.getElementById('closePlayer');
406
- const authContainer = document.getElementById('authContainer');
407
- const hfTokenInput = document.getElementById('hfToken');
408
- const authBtn = document.getElementById('authBtn');
409
- const authError = document.getElementById('authError');
410
-
411
- // Load videos from JSON
412
- async function loadVideos() {
413
  try {
414
  const response = await fetch('database.json');
415
  if (!response.ok) {
416
- throw new Error('Failed to load videos');
417
  }
418
- videos = await response.json();
419
 
420
- // Check if any video URL contains 'huggingface.co' to determine if we need auth
421
- isPrivateRepo = videos.some(video =>
422
- video.url.includes('huggingface.co') &&
423
- (video.url.includes('/blob/') || video.url.includes('/resolve/'))
424
- );
425
 
426
- if (isPrivateRepo) {
427
- // Check if we already have a token in localStorage
428
- const storedToken = localStorage.getItem('hfToken');
429
- if (storedToken) {
430
- hfToken = storedToken;
431
- authContainer.classList.remove('show');
432
- displayVideos(videos);
433
- } else {
434
- authContainer.classList.add('show');
435
- }
436
- } else {
437
- authContainer.classList.remove('show');
438
- displayVideos(videos);
439
- }
440
  } catch (error) {
441
- console.error('Error loading videos:', error);
442
- videoListElement.innerHTML = '<div class="no-results">Failed to load videos. Please try again later.</div>';
443
  }
444
  }
445
-
446
- // Display videos in the list
447
- function displayVideos(videosToDisplay) {
448
- if (videosToDisplay.length === 0) {
449
- videoListElement.innerHTML = '<div class="no-results">No videos found. Try a different search.</div>';
 
450
  return;
451
  }
452
-
453
- videoListElement.innerHTML = videosToDisplay.map(video => `
454
- <div class="video-item" data-url="${video.url}" data-title="${video.title}">
 
 
 
 
 
455
  <div class="video-thumbnail">
456
- <img src="${video.thumbnail || 'https://via.placeholder.com/300x169?text=No+Thumbnail'}" alt="${video.title}">
457
- </div>
458
- <div class="video-info">
459
- <h3 class="video-title">${video.title}</h3>
460
  </div>
 
461
  </div>
462
  `).join('');
463
-
464
- // Add click event to video items
465
  document.querySelectorAll('.video-item').forEach(item => {
466
- item.addEventListener('click', () => {
467
- const videoUrl = item.getAttribute('data-url');
468
- const videoTitle = item.getAttribute('data-title');
469
- playVideo(videoUrl, videoTitle);
 
470
  });
471
  });
472
  }
473
-
474
- // Play selected video
475
- async function playVideo(url, title) {
476
- try {
477
- // For HF Hub videos, modify the URL to use raw content
478
- let finalUrl = url;
479
-
480
- if (url.includes('huggingface.co')) {
481
- // Convert blob/resolve URLs to raw URLs
482
- finalUrl = url
483
- .replace('/blob/', '/resolve/')
484
- .replace('https://huggingface.co/', 'https://cdn-lfs.huggingface.co/');
485
-
486
- if (isPrivateRepo && hfToken) {
487
- // Add token for private repos
488
- finalUrl += `?token=${hfToken}`;
489
- }
490
  }
 
 
 
491
 
492
- // Set the video source
493
- videoPlayer.src = finalUrl;
494
- document.title = `${title} - Video Player`;
495
-
496
- // Show player
497
- playerContainer.classList.add('show');
498
-
499
- // Attempt to play (may need user interaction on some browsers)
500
- const playPromise = videoPlayer.play();
501
 
502
- if (playPromise !== undefined) {
503
- playPromise.catch(error => {
504
- console.log('Auto-play prevented:', error);
505
- // Show play button to indicate user needs to click
506
- playPauseBtn.innerHTML = '&#9658;';
507
- });
508
  }
509
 
510
- // Set initial volume
511
- videoPlayer.volume = volumeSlider.value;
 
 
512
 
513
- } catch (error) {
514
- console.error('Error playing video:', error);
515
- alert('Failed to play video. Please check your authentication for private repositories.');
516
- closeVideoPlayer();
517
  }
518
- }
519
-
520
- // Close video player
521
- function closeVideoPlayer() {
522
- videoPlayer.pause();
523
- playerContainer.classList.remove('show');
524
- document.title = 'Video Player';
525
 
526
- // Exit fullscreen if active
527
- if (document.fullscreenElement) {
528
- document.exitFullscreen();
529
- }
530
- }
531
-
532
- // Format time (seconds to MM:SS)
533
- function formatTime(seconds) {
534
- const minutes = Math.floor(seconds / 60);
535
- const remainingSeconds = Math.floor(seconds % 60);
536
- return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
537
- }
538
-
539
- // Update progress bar
540
- function updateProgressBar() {
541
- const currentTime = videoPlayer.currentTime;
542
- const duration = videoPlayer.duration;
543
 
544
- if (duration) {
545
- const progressPercent = (currentTime / duration) * 100;
546
- progressBar.style.width = `${progressPercent}%`;
547
- currentTimeDisplay.textContent = formatTime(currentTime);
548
- durationDisplay.textContent = formatTime(duration);
549
- }
550
- }
551
-
552
- // Skip forward or backward
553
- function skip(seconds) {
554
- videoPlayer.currentTime += seconds;
555
- }
556
-
557
- // Toggle play/pause
558
- function togglePlayPause() {
559
- if (videoPlayer.paused) {
560
- videoPlayer.play();
561
- playPauseBtn.innerHTML = '&#10074;&#10074;';
562
- } else {
563
- videoPlayer.pause();
564
- playPauseBtn.innerHTML = '&#9658;';
565
- }
566
- }
567
-
568
- // Toggle mute
569
- function toggleMute() {
570
- videoPlayer.muted = !videoPlayer.muted;
571
- if (videoPlayer.muted) {
572
- volumeBtn.innerHTML = '&#128263;';
573
- volumeSlider.value = 0;
574
- } else {
575
- volumeBtn.innerHTML = '&#128266;';
576
- volumeSlider.value = videoPlayer.volume;
577
- }
578
- }
579
-
580
- // Update volume
581
- function updateVolume() {
582
- videoPlayer.volume = volumeSlider.value;
583
- videoPlayer.muted = (videoPlayer.volume === 0);
584
- if (videoPlayer.volume === 0) {
585
- volumeBtn.innerHTML = '&#128263;';
586
- } else {
587
- volumeBtn.innerHTML = '&#128266;';
588
- }
589
- }
590
-
591
- // Seek to position in video
592
- function seek(e) {
593
- const width = this.clientWidth;
594
- const clickX = e.offsetX;
595
- const duration = videoPlayer.duration;
596
- videoPlayer.currentTime = (clickX / width) * duration;
597
  }
598
-
599
- // Update progress time indicator
600
- function updateProgressTime(e) {
601
- const width = this.clientWidth;
602
- const clickX = e.offsetX;
603
- const duration = videoPlayer.duration;
604
- const seekTime = (clickX / width) * duration;
605
- progressTime.textContent = formatTime(seekTime);
606
 
607
- // Position the time indicator
608
- const progressContainerRect = progressContainer.getBoundingClientRect();
609
- const progressTimeWidth = progressTime.offsetWidth;
610
- let leftPosition = e.clientX - progressContainerRect.left - (progressTimeWidth / 2);
611
 
612
- // Keep within bounds
613
- leftPosition = Math.max(0, Math.min(leftPosition, progressContainerRect.width - progressTimeWidth));
614
- progressTime.style.left = `${leftPosition}px`;
615
- }
616
-
617
- // Toggle fullscreen
618
- function toggleFullscreen() {
619
- if (!document.fullscreenElement) {
620
- playerContainer.requestFullscreen().catch(err => {
621
- console.error(`Error attempting to enable fullscreen: ${err.message}`);
622
- });
623
- } else {
624
- document.exitFullscreen();
625
  }
626
- }
627
-
628
- // Handle fullscreen change
629
- function handleFullscreenChange() {
630
- if (document.fullscreenElement) {
631
- // When entering fullscreen, ensure video is playing
632
- if (videoPlayer.paused) {
633
- videoPlayer.play();
634
- playPauseBtn.innerHTML = '&#10074;&#10074;';
635
- }
636
- }
637
- }
638
-
639
- // Handle double tap for skip
640
- function handleDoubleTap(e) {
641
- const currentTime = Date.now();
642
- const tapLength = currentTime - lastTapTime;
643
 
644
- if (tapLength < 300 && tapLength > 0) {
645
- // Double tap detected
646
- const rect = videoPlayer.getBoundingClientRect();
647
- const tapPosition = e.clientX - rect.left;
648
- const halfWidth = rect.width / 2;
649
-
650
- if (tapPosition < halfWidth) {
651
- // Left side - skip backward
652
- skip(-10);
653
- } else {
654
- // Right side - skip forward
655
- skip(10);
656
- }
657
- }
658
 
659
- lastTapTime = currentTime;
660
- }
661
-
662
- // Handle authentication
663
- function handleAuth() {
664
- const token = hfTokenInput.value.trim();
665
- if (!token) {
666
- authError.textContent = 'Please enter a valid Hugging Face access token';
667
- return;
668
- }
669
 
670
- // Test the token with a simple API call
671
- fetch('https://huggingface.co/api/whoami', {
672
- headers: {
673
- 'Authorization': `Bearer ${token}`
674
- }
675
- })
676
- .then(response => {
677
- if (!response.ok) {
678
- throw new Error('Invalid token');
679
- }
680
- return response.json();
681
- })
682
- .then(data => {
683
- // Token is valid
684
- hfToken = token;
685
- localStorage.setItem('hfToken', token);
686
- authContainer.classList.remove('show');
687
- authError.textContent = '';
688
- displayVideos(videos);
689
- })
690
- .catch(error => {
691
- console.error('Authentication failed:', error);
692
- authError.textContent = 'Authentication failed. Please check your token.';
693
- });
694
  }
695
-
696
- // Event listeners
697
- searchInput.addEventListener('input', () => {
698
- const searchTerm = searchInput.value.toLowerCase();
699
- const filteredVideos = videos.filter(video =>
 
 
 
 
 
 
 
700
  video.title.toLowerCase().includes(searchTerm)
701
  );
702
- displayVideos(filteredVideos);
 
 
703
  });
704
-
705
- videoPlayer.addEventListener('timeupdate', updateProgressBar);
706
- videoPlayer.addEventListener('ended', () => {
707
- playPauseBtn.innerHTML = '&#9658;';
 
 
 
 
 
708
  });
709
- videoPlayer.addEventListener('click', togglePlayPause);
710
- videoPlayer.addEventListener('dblclick', handleDoubleTap);
711
- videoPlayer.addEventListener('volumechange', () => {
712
- volumeSlider.value = videoPlayer.volume;
713
- if (videoPlayer.muted || videoPlayer.volume === 0) {
714
- volumeBtn.innerHTML = '&#128263;';
715
  } else {
716
- volumeBtn.innerHTML = '&#128266;';
717
  }
718
  });
719
-
720
- playPauseBtn.addEventListener('click', togglePlayPause);
721
- skipBackBtn.addEventListener('click', () => skip(-10));
722
- skipForwardBtn.addEventListener('click', () => skip(10));
723
-
724
- progressContainer.addEventListener('click', seek);
725
- progressContainer.addEventListener('mousemove', updateProgressTime);
726
-
727
- volumeBtn.addEventListener('click', toggleMute);
728
- volumeSlider.addEventListener('input', updateVolume);
729
-
730
- fullscreenBtn.addEventListener('click', toggleFullscreen);
731
- closePlayer.addEventListener('click', closeVideoPlayer);
732
- authBtn.addEventListener('click', handleAuth);
733
-
734
- // Fullscreen change events
735
- document.addEventListener('fullscreenchange', handleFullscreenChange);
736
- document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
737
- document.addEventListener('mozfullscreenchange', handleFullscreenChange);
738
- document.addEventListener('MSFullscreenChange', handleFullscreenChange);
739
-
740
- // Keyboard controls
741
- document.addEventListener('keydown', (e) => {
742
- if (!playerContainer.classList.contains('show')) return;
 
 
743
 
744
- switch (e.key) {
745
- case ' ':
746
- case 'k':
747
- togglePlayPause();
 
 
 
748
  e.preventDefault();
749
  break;
750
- case 'ArrowLeft':
751
- skip(-5);
752
- break;
753
  case 'ArrowRight':
754
- skip(5);
 
755
  break;
756
- case 'ArrowUp':
757
- videoPlayer.volume = Math.min(1, videoPlayer.volume + 0.1);
758
- volumeSlider.value = videoPlayer.volume;
759
- updateVolume();
760
  break;
761
- case 'ArrowDown':
762
- videoPlayer.volume = Math.max(0, videoPlayer.volume - 0.1);
763
- volumeSlider.value = videoPlayer.volume;
764
- updateVolume();
 
 
 
 
765
  break;
766
  case 'm':
767
- toggleMute();
768
- break;
769
- case 'f':
770
- toggleFullscreen();
771
  break;
772
  case 'Escape':
773
- closeVideoPlayer();
 
774
  break;
775
  }
776
  });
777
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  // Initialize
779
- loadVideos();
780
  </script>
781
  </body>
782
  </html>
 
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;
 
11
  padding: 0;
12
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
  }
 
14
  body {
15
  background-color: #f5f5f5;
16
+ padding: 20px;
 
 
 
 
17
  max-width: 1200px;
18
  margin: 0 auto;
 
19
  }
20
+ .container {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 20px;
 
 
 
 
24
  }
25
+ .header {
26
+ background-color: #333;
27
+ color: white;
28
+ padding: 20px;
29
+ border-radius: 8px;
30
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
31
  }
 
32
  .search-container {
33
+ padding: 15px;
34
+ background-color: white;
35
+ border-radius: 8px;
36
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
37
  }
 
38
  #search {
39
+ width: 100%;
40
+ padding: 10px;
41
  border: 1px solid #ddd;
42
+ border-radius: 4px;
43
  font-size: 16px;
 
 
44
  }
 
 
 
 
 
 
45
  .video-list {
46
+ background-color: white;
47
+ border-radius: 8px;
48
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
49
+ padding: 15px;
50
+ }
51
+ .video-grid {
52
  display: grid;
53
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
54
+ gap: 15px;
 
55
  }
 
56
  .video-item {
57
+ padding: 15px;
58
+ border: 1px solid #eee;
59
  border-radius: 8px;
 
 
 
60
  cursor: pointer;
61
+ transition: all 0.2s;
62
+ background-color: white;
63
  }
 
64
  .video-item:hover {
65
  transform: translateY(-5px);
66
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
67
  }
 
68
  .video-thumbnail {
 
 
 
 
 
 
 
 
 
69
  width: 100%;
70
+ height: 156px;
71
+ background-color: #333;
72
+ border-radius: 4px;
73
+ display: flex;
74
+ align-items: center;
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;
85
+ margin-top: 10px;
86
+ white-space: nowrap;
 
 
 
 
87
  overflow: hidden;
88
+ text-overflow: ellipsis;
89
  }
90
+ .pagination {
91
+ display: flex;
92
+ justify-content: center;
93
+ gap: 5px;
94
+ margin-top: 20px;
95
+ }
96
+ .page-btn {
97
+ padding: 8px 12px;
98
+ background-color: white;
99
+ border: 1px solid #ddd;
100
+ border-radius: 4px;
101
+ cursor: pointer;
102
+ transition: background-color 0.2s;
103
+ }
104
+ .page-btn:hover {
105
+ background-color: #f0f0f0;
106
+ }
107
+ .page-btn.active {
108
+ background-color: #333;
109
+ color: white;
110
+ border-color: #333;
111
+ }
112
+ .popup-overlay {
113
  position: fixed;
114
  top: 0;
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;
122
  z-index: 1000;
123
  opacity: 0;
124
+ visibility: hidden;
125
+ transition: opacity 0.3s, visibility 0.3s;
126
  }
127
+ .popup-overlay.active {
 
128
  opacity: 1;
129
+ visibility: visible;
130
  }
131
+ .popup-content {
 
132
  width: 90%;
133
+ max-width: 1024px;
134
+ background-color: #222;
 
 
 
 
135
  border-radius: 8px;
136
+ overflow: hidden;
137
+ position: relative;
138
+ box-shadow: 0 10px 25px rgba(0,0,0,0.5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
152
  justify-content: 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;
172
+ padding: 15px;
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 {
212
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
 
 
213
  }
214
+ .popup-content {
 
215
  width: 95%;
216
  }
217
+ }
218
+ .no-videos {
219
+ padding: 20px;
220
+ text-align: center;
221
+ color: #777;
 
222
  }
223
  </style>
224
  </head>
225
  <body>
226
  <div class="container">
227
+ <div class="header">
228
+ <h1>Video Library</h1>
 
 
 
 
 
 
 
229
  </div>
230
 
231
  <div class="search-container">
232
  <input type="text" id="search" placeholder="Search videos...">
233
  </div>
234
 
235
+ <div class="video-list">
236
+ <div class="video-grid" id="videoGrid">
237
+ <div class="video-item">
238
+ <div class="video-thumbnail">Loading...</div>
239
+ </div>
240
+ </div>
241
+ <div class="pagination" id="pagination"></div>
242
  </div>
243
  </div>
244
+
245
+ <!-- Video Player Popup -->
246
+ <div class="popup-overlay" id="playerPopup">
247
+ <div class="popup-content">
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 = [];
272
+ let filteredVideos = [];
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 {
294
  const response = await fetch('database.json');
295
  if (!response.ok) {
296
+ throw new Error('Failed to fetch video data');
297
  }
298
+ const data = await response.json();
299
 
300
+ // Check if data has videos array or is itself an array
301
+ videoData = Array.isArray(data) ? data :
302
+ (data.videos && Array.isArray(data.videos) ? data.videos : []);
 
 
303
 
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) {
316
+ videoGrid.innerHTML = '<div class="no-videos">No videos found</div>';
317
+ pagination.innerHTML = '';
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);
325
+
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'));
339
+ if (!isNaN(index) && index >= 0 && index < filteredVideos.length) {
340
+ playVideo(filteredVideos[index]);
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'));
399
+ renderPagination();
400
+ renderVideoGrid();
401
+ window.scrollTo(0, 0);
402
+ });
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>