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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +440 -105
index.html CHANGED
@@ -75,10 +75,17 @@
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;
@@ -179,6 +186,7 @@
179
  padding: 15px;
180
  background-color: #111;
181
  flex-wrap: wrap;
 
182
  }
183
  .btn {
184
  padding: 8px 16px;
@@ -192,6 +200,9 @@
192
  .btn:hover {
193
  background-color: #555;
194
  }
 
 
 
195
  .instructions {
196
  color: #ccc;
197
  padding: 10px 15px;
@@ -214,13 +225,80 @@
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">
@@ -246,9 +324,12 @@
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>
@@ -258,6 +339,10 @@
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>
@@ -273,6 +358,9 @@
273
  let currentVideo = null;
274
  let currentPage = 1;
275
  const videosPerPage = 10;
 
 
 
276
 
277
  // DOM Elements
278
  const videoGrid = document.getElementById('videoGrid');
@@ -287,29 +375,66 @@
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) {
@@ -326,6 +451,7 @@
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>
@@ -341,6 +467,21 @@
341
  }
342
  });
343
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  }
345
 
346
  // Function to render pagination
@@ -408,37 +549,302 @@
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
@@ -462,70 +868,31 @@
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':
@@ -553,61 +920,29 @@
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>
 
75
  justify-content: center;
76
  margin-bottom: 10px;
77
  overflow: hidden;
78
+ position: relative;
79
+ }
80
+ .video-thumbnail img {
81
+ width: 100%;
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;
 
186
  padding: 15px;
187
  background-color: #111;
188
  flex-wrap: wrap;
189
+ align-items: center;
190
  }
191
  .btn {
192
  padding: 8px 16px;
 
200
  .btn:hover {
201
  background-color: #555;
202
  }
203
+ .btn.active {
204
+ background-color: #007bff;
205
+ }
206
  .instructions {
207
  color: #ccc;
208
  padding: 10px 15px;
 
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%;
260
+ left: 50%;
261
+ transform: translate(-50%, -50%);
262
+ width: 40px;
263
+ height: 40px;
264
+ border: 4px solid rgba(255, 255, 255, 0.3);
265
+ border-radius: 50%;
266
+ border-top: 4px solid #fff;
267
+ animation: spin 1s linear infinite;
268
+ z-index: 10;
269
+ display: none;
270
+ }
271
+ @keyframes spin {
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;
281
+ padding: 5px 10px;
282
+ border-radius: 4px;
283
+ font-size: 12px;
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">
 
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
  <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
  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
  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
  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
  }
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
 
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
+
637
+ function setupRegularPlayer(url) {
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);
663
+
664
+ // Backward/Forward buttons
665
+ backwardBtn.addEventListener('click', () => seekRelative(-10));
666
+ forwardBtn.addEventListener('click', () => seekRelative(10));
667
+
668
+ // Fullscreen button
669
+ fullscreenBtn.addEventListener('click', toggleFullscreen);
670
+
671
+ // Mute button
672
+ muteBtn.addEventListener('click', toggleMute);
673
+
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', () => {
686
+ bufferingIndicator.style.display = 'block';
687
+ });
688
+
689
+ videoPlayer.addEventListener('playing', () => {
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) => {
702
+ console.error('Video error:', videoPlayer.error);
703
+ loadingSpinner.style.display = 'none';
704
+ bufferingIndicator.style.display = 'none';
705
+ });
706
+ }
707
+
708
+ function togglePlayPause() {
709
+ if (videoPlayer.paused) {
710
+ videoPlayer.play().catch(error => {
711
+ console.error('Error playing video:', error);
712
+ });
713
+ } else {
714
+ videoPlayer.pause();
715
+ }
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) {
733
+ document.exitFullscreen();
734
+ } else if (document.webkitExitFullscreen) {
735
+ document.webkitExitFullscreen();
736
+ } else if (document.msExitFullscreen) {
737
+ document.msExitFullscreen();
738
+ }
739
+ }
740
+ }
741
+
742
+ function toggleMute() {
743
+ videoPlayer.muted = !videoPlayer.muted;
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
 
868
  }
869
  });
870
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
871
  // Keyboard controls (only active when popup is visible)
872
  document.addEventListener('keydown', function(e) {
873
  if (!playerPopup.classList.contains('active')) return;
874
 
875
  switch(e.key) {
876
  case ' ': // Space
877
+ togglePlayPause();
 
 
 
 
878
  e.preventDefault();
879
  break;
880
  case 'ArrowRight':
881
+ seekRelative(10);
882
  e.preventDefault();
883
  break;
884
  case 'ArrowLeft':
885
+ seekRelative(-10);
886
  e.preventDefault();
887
  break;
888
  case 'f':
889
  case 'F':
890
+ toggleFullscreen();
 
 
 
 
891
  e.preventDefault();
892
  break;
893
  case 'm':
894
  case 'M':
895
+ toggleMute();
896
  e.preventDefault();
897
  break;
898
  case 'Escape':
 
920
  // Determine click position (left, center, or right third)
921
  if (clickX < playerWidth / 3) {
922
  // Left side - backward
923
+ seekRelative(-10);
924
  } else if (clickX > (playerWidth * 2) / 3) {
925
  // Right side - forward
926
+ seekRelative(10);
927
  } else {
928
  // Center - play/pause
929
+ togglePlayPause();
 
 
 
 
930
  }
931
  e.preventDefault();
932
  } else {
933
  // Single tap - wait to see if it becomes a double tap
934
  tapTimeout = setTimeout(function() {
935
+ // Single tap action (toggle controls visibility)
936
+ if (videoPlayer.hasAttribute('controls')) {
937
+ videoPlayer.removeAttribute('controls');
938
+ } else {
939
+ videoPlayer.setAttribute('controls', '');
940
+ }
941
  }, 300);
942
  }
943
  lastTap = currentTime;
944
  });
945
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
946
  // Initialize
947
  fetchVideoData();
948
  </script>