Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Video Library</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background-color: #f5f5f5; | |
| padding: 20px; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .header { | |
| background-color: #333; | |
| color: white; | |
| padding: 20px; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .search-container { | |
| padding: 15px; | |
| background-color: white; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| } | |
| #search { | |
| width: 100%; | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| font-size: 16px; | |
| } | |
| .video-list { | |
| background-color: white; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| padding: 15px; | |
| } | |
| .video-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 15px; | |
| } | |
| .video-item { | |
| padding: 15px; | |
| border: 1px solid #eee; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| background-color: white; | |
| } | |
| .video-item:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| } | |
| .video-thumbnail { | |
| width: 100%; | |
| height: 156px; | |
| background-color: #333; | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 10px; | |
| overflow: hidden; | |
| } | |
| .video-thumbnail i { | |
| font-size: 48px; | |
| color: white; | |
| } | |
| .video-item-title { | |
| font-weight: 500; | |
| margin-top: 10px; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .pagination { | |
| display: flex; | |
| justify-content: center; | |
| gap: 5px; | |
| margin-top: 20px; | |
| } | |
| .page-btn { | |
| padding: 8px 12px; | |
| background-color: white; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| .page-btn:hover { | |
| background-color: #f0f0f0; | |
| } | |
| .page-btn.active { | |
| background-color: #333; | |
| color: white; | |
| border-color: #333; | |
| } | |
| .popup-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.8); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 1000; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.3s, visibility 0.3s; | |
| } | |
| .popup-overlay.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .popup-content { | |
| width: 90%; | |
| max-width: 1024px; | |
| background-color: #222; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| position: relative; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.5); | |
| } | |
| .close-popup { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| background-color: rgba(0,0,0,0.5); | |
| color: white; | |
| border: none; | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| z-index: 10; | |
| font-size: 18px; | |
| } | |
| .player-wrapper { | |
| position: relative; | |
| width: 100%; | |
| padding-top: 56.25%; /* 16:9 Aspect Ratio */ | |
| } | |
| #videoPlayer { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: #000; | |
| } | |
| .popup-title { | |
| color: white; | |
| padding: 15px; | |
| font-size: 1.2em; | |
| background-color: #111; | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 10px; | |
| padding: 15px; | |
| background-color: #111; | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| padding: 8px 16px; | |
| background-color: #333; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| .btn:hover { | |
| background-color: #555; | |
| } | |
| .instructions { | |
| color: #ccc; | |
| padding: 10px 15px; | |
| background-color: #111; | |
| font-size: 0.9em; | |
| } | |
| .play-icon { | |
| font-size: 50px; | |
| color: white; | |
| opacity: 0.8; | |
| transition: opacity 0.2s; | |
| } | |
| .video-item:hover .play-icon { | |
| opacity: 1; | |
| } | |
| @media (max-width: 768px) { | |
| .video-grid { | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| } | |
| .popup-content { | |
| width: 95%; | |
| } | |
| } | |
| .no-videos { | |
| padding: 20px; | |
| text-align: center; | |
| color: #777; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>Video Library</h1> | |
| </div> | |
| <div class="search-container"> | |
| <input type="text" id="search" placeholder="Search videos..."> | |
| </div> | |
| <div class="video-list"> | |
| <div class="video-grid" id="videoGrid"> | |
| <div class="video-item"> | |
| <div class="video-thumbnail">Loading...</div> | |
| </div> | |
| </div> | |
| <div class="pagination" id="pagination"></div> | |
| </div> | |
| </div> | |
| <!-- Video Player Popup --> | |
| <div class="popup-overlay" id="playerPopup"> | |
| <div class="popup-content"> | |
| <button class="close-popup" id="closePopup">✕</button> | |
| <div class="popup-title" id="currentTitle">Video Title</div> | |
| <div class="player-wrapper"> | |
| <video id="videoPlayer" controls> | |
| Your browser does not support the video tag. | |
| </video> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn" id="backwardBtn">⏪ 10s</button> | |
| <button class="btn" id="playPauseBtn">⏯️ Play/Pause</button> | |
| <button class="btn" id="forwardBtn">⏩ 10s</button> | |
| <button class="btn" id="fullscreenBtn">⛶ Fullscreen</button> | |
| <button class="btn" id="muteBtn">🔊 Mute/Unmute</button> | |
| </div> | |
| <div class="instructions"> | |
| <p><strong>Keyboard Controls:</strong> Space (Play/Pause) • ← (Back 10s) • → (Forward 10s) • F (Fullscreen) • M (Mute/Unmute)</p> | |
| <p><strong>Double-tap:</strong> Left side (Back 10s) • Center (Play/Pause) • Right side (Forward 10s)</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let videoData = []; | |
| let filteredVideos = []; | |
| let currentVideo = null; | |
| let currentPage = 1; | |
| const videosPerPage = 10; | |
| // DOM Elements | |
| const videoGrid = document.getElementById('videoGrid'); | |
| const pagination = document.getElementById('pagination'); | |
| const searchInput = document.getElementById('search'); | |
| const playerPopup = document.getElementById('playerPopup'); | |
| const videoPlayer = document.getElementById('videoPlayer'); | |
| const currentTitle = document.getElementById('currentTitle'); | |
| const closePopup = document.getElementById('closePopup'); | |
| const playPauseBtn = document.getElementById('playPauseBtn'); | |
| const backwardBtn = document.getElementById('backwardBtn'); | |
| const forwardBtn = document.getElementById('forwardBtn'); | |
| const fullscreenBtn = document.getElementById('fullscreenBtn'); | |
| const muteBtn = document.getElementById('muteBtn'); | |
| // Function to fetch video data | |
| async function fetchVideoData() { | |
| try { | |
| const response = await fetch('database.json'); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch video data'); | |
| } | |
| const data = await response.json(); | |
| // Check if data has videos array or is itself an array | |
| videoData = Array.isArray(data) ? data : | |
| (data.videos && Array.isArray(data.videos) ? data.videos : []); | |
| filteredVideos = [...videoData]; | |
| renderPagination(); | |
| renderVideoGrid(); | |
| } catch (error) { | |
| console.error('Error fetching video data:', error); | |
| videoGrid.innerHTML = `<div class="no-videos">Error loading videos: ${error.message}</div>`; | |
| } | |
| } | |
| // Function to render video grid | |
| function renderVideoGrid() { | |
| if (filteredVideos.length === 0) { | |
| videoGrid.innerHTML = '<div class="no-videos">No videos found</div>'; | |
| pagination.innerHTML = ''; | |
| return; | |
| } | |
| const startIndex = (currentPage - 1) * videosPerPage; | |
| const endIndex = Math.min(startIndex + videosPerPage, filteredVideos.length); | |
| const currentVideos = filteredVideos.slice(startIndex, endIndex); | |
| videoGrid.innerHTML = currentVideos.map((video, index) => ` | |
| <div class="video-item" data-index="${startIndex + index}"> | |
| <div class="video-thumbnail"> | |
| <div class="play-icon">▶</div> | |
| </div> | |
| <div class="video-item-title">${video.title}</div> | |
| </div> | |
| `).join(''); | |
| document.querySelectorAll('.video-item').forEach(item => { | |
| item.addEventListener('click', function() { | |
| const index = parseInt(this.getAttribute('data-index')); | |
| if (!isNaN(index) && index >= 0 && index < filteredVideos.length) { | |
| // Call the async function to play the video | |
| playVideo(filteredVideos[index]); | |
| } | |
| }); | |
| }); | |
| } | |
| // Function to render pagination | |
| function renderPagination() { | |
| const totalPages = Math.ceil(filteredVideos.length / videosPerPage); | |
| if (totalPages <= 1) { | |
| pagination.innerHTML = ''; | |
| return; | |
| } | |
| let buttons = []; | |
| if (totalPages <= 7) { | |
| for (let i = 1; i <= totalPages; i++) { | |
| buttons.push(i); | |
| } | |
| } else { | |
| buttons.push(1); | |
| if (currentPage > 3) { | |
| buttons.push('...'); | |
| } | |
| const startPage = Math.max(2, currentPage - 1); | |
| const endPage = Math.min(totalPages - 1, currentPage + 1); | |
| for (let i = startPage; i <= endPage; i++) { | |
| buttons.push(i); | |
| } | |
| if (currentPage < totalPages - 2) { | |
| buttons.push('...'); | |
| } | |
| buttons.push(totalPages); | |
| } | |
| pagination.innerHTML = buttons.map(button => { | |
| if (button === '...') { | |
| return '<span class="page-btn">...</span>'; | |
| } | |
| return `<button class="page-btn ${button === currentPage ? 'active' : ''}" data-page="${button}">${button}</button>`; | |
| }).join(''); | |
| document.querySelectorAll('.page-btn[data-page]').forEach(button => { | |
| button.addEventListener('click', function() { | |
| currentPage = parseInt(this.getAttribute('data-page')); | |
| renderPagination(); | |
| renderVideoGrid(); | |
| window.scrollTo(0, 0); | |
| }); | |
| }); | |
| } | |
| // Async function to play a video and handle HF Model Hub authentication if needed | |
| async function playVideo(video) { | |
| currentVideo = video; | |
| currentTitle.textContent = video.title; | |
| let videoUrl = video.url; | |
| let finalUrl = videoUrl; | |
| // Check if the URL is from HF Model Hub and might require authentication. | |
| if (videoUrl.includes("huggingface.co")) { | |
| // Try to get token from localStorage or prompt the user. | |
| let token = localStorage.getItem('hf_token'); | |
| if (!token) { | |
| token = prompt("Enter your HuggingFace token:"); | |
| if (!token) { | |
| alert("Authentication token required to access HuggingFace video."); | |
| return; | |
| } | |
| localStorage.setItem('hf_token', token); | |
| } | |
| try { | |
| const response = await fetch(videoUrl, { | |
| headers: { | |
| 'Authorization': `Bearer ${token}` | |
| } | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to load video: ${response.status} ${response.statusText}`); | |
| } | |
| const blob = await response.blob(); | |
| finalUrl = URL.createObjectURL(blob); | |
| } catch (error) { | |
| alert("Error loading HuggingFace video: " + error.message); | |
| return; | |
| } | |
| } | |
| videoPlayer.src = finalUrl; | |
| videoPlayer.load(); | |
| // Show popup and disable background scrolling | |
| playerPopup.classList.add('active'); | |
| document.body.style.overflow = 'hidden'; | |
| // Play video after a short delay to allow the popup to render | |
| setTimeout(() => { | |
| videoPlayer.play().catch(error => { | |
| console.error("Error playing video:", error); | |
| alert(`Error playing video: ${error.message}. Make sure the URL is accessible.`); | |
| }); | |
| }, 300); | |
| } | |
| // Close popup function | |
| function closePlayerPopup() { | |
| videoPlayer.pause(); | |
| playerPopup.classList.remove('active'); | |
| document.body.style.overflow = ''; | |
| } | |
| // Search functionality | |
| searchInput.addEventListener('input', function() { | |
| const searchTerm = this.value.toLowerCase(); | |
| filteredVideos = videoData.filter(video => | |
| video.title.toLowerCase().includes(searchTerm) | |
| ); | |
| currentPage = 1; // Reset to first page on search | |
| renderPagination(); | |
| renderVideoGrid(); | |
| }); | |
| // Close popup when clicking close button | |
| closePopup.addEventListener('click', closePlayerPopup); | |
| // Close popup when clicking outside the popup content | |
| playerPopup.addEventListener('click', function(e) { | |
| if (e.target === playerPopup) { | |
| closePlayerPopup(); | |
| } | |
| }); | |
| // Video control buttons | |
| playPauseBtn.addEventListener('click', function() { | |
| if (videoPlayer.paused) { | |
| videoPlayer.play(); | |
| } else { | |
| videoPlayer.pause(); | |
| } | |
| }); | |
| backwardBtn.addEventListener('click', function() { | |
| videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10); | |
| }); | |
| forwardBtn.addEventListener('click', function() { | |
| videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10); | |
| }); | |
| fullscreenBtn.addEventListener('click', function() { | |
| if (videoPlayer.requestFullscreen) { | |
| videoPlayer.requestFullscreen(); | |
| } else if (videoPlayer.webkitRequestFullscreen) { | |
| videoPlayer.webkitRequestFullscreen(); | |
| } else if (videoPlayer.msRequestFullscreen) { | |
| videoPlayer.msRequestFullscreen(); | |
| } | |
| }); | |
| muteBtn.addEventListener('click', function() { | |
| videoPlayer.muted = !videoPlayer.muted; | |
| }); | |
| // Keyboard controls (active when popup is visible) | |
| document.addEventListener('keydown', function(e) { | |
| if (!playerPopup.classList.contains('active')) return; | |
| switch(e.key) { | |
| case ' ': | |
| if (videoPlayer.paused) { | |
| videoPlayer.play(); | |
| } else { | |
| videoPlayer.pause(); | |
| } | |
| e.preventDefault(); | |
| break; | |
| case 'ArrowRight': | |
| videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10); | |
| e.preventDefault(); | |
| break; | |
| case 'ArrowLeft': | |
| videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10); | |
| e.preventDefault(); | |
| break; | |
| case 'f': | |
| case 'F': | |
| if (document.fullscreenElement) { | |
| document.exitFullscreen(); | |
| } else if (videoPlayer.requestFullscreen) { | |
| videoPlayer.requestFullscreen(); | |
| } | |
| e.preventDefault(); | |
| break; | |
| case 'm': | |
| case 'M': | |
| videoPlayer.muted = !videoPlayer.muted; | |
| e.preventDefault(); | |
| break; | |
| case 'Escape': | |
| closePlayerPopup(); | |
| e.preventDefault(); | |
| break; | |
| } | |
| }); | |
| // Double-tap controls for mobile/touch devices | |
| let lastTap = 0; | |
| let tapTimeout; | |
| videoPlayer.addEventListener('click', function(e) { | |
| const currentTime = new Date().getTime(); | |
| const tapLength = currentTime - lastTap; | |
| clearTimeout(tapTimeout); | |
| if (tapLength < 500 && tapLength > 0) { | |
| const playerRect = videoPlayer.getBoundingClientRect(); | |
| const clickX = e.clientX - playerRect.left; | |
| const playerWidth = playerRect.width; | |
| if (clickX < playerWidth / 3) { | |
| videoPlayer.currentTime = Math.max(0, videoPlayer.currentTime - 10); | |
| } else if (clickX > (playerWidth * 2) / 3) { | |
| videoPlayer.currentTime = Math.min(videoPlayer.duration, videoPlayer.currentTime + 10); | |
| } else { | |
| if (videoPlayer.paused) { | |
| videoPlayer.play(); | |
| } else { | |
| videoPlayer.pause(); | |
| } | |
| } | |
| e.preventDefault(); | |
| } else { | |
| tapTimeout = setTimeout(function() { | |
| // Single tap action (no default action here) | |
| }, 300); | |
| } | |
| lastTap = currentTime; | |
| }); | |
| // Fix for fullscreen mode changes | |
| document.addEventListener('fullscreenchange', handleFullscreenChange); | |
| document.addEventListener('webkitfullscreenchange', handleFullscreenChange); | |
| document.addEventListener('mozfullscreenchange', handleFullscreenChange); | |
| document.addEventListener('MSFullscreenChange', handleFullscreenChange); | |
| function handleFullscreenChange() { | |
| if (document.fullscreenElement || | |
| document.webkitFullscreenElement || | |
| document.mozFullScreenElement) { | |
| console.log('Entered fullscreen mode'); | |
| const fullscreenVideo = document.fullscreenElement || | |
| document.webkitFullscreenElement || | |
| document.mozFullScreenElement; | |
| if (fullscreenVideo === videoPlayer) { | |
| videoPlayer.style.width = '100%'; | |
| videoPlayer.style.height = '100%'; | |
| } | |
| } else { | |
| console.log('Exited fullscreen mode'); | |
| videoPlayer.style.width = '100%'; | |
| videoPlayer.style.height = '100%'; | |
| } | |
| } | |
| // Initialize video data on page load | |
| fetchVideoData(); | |
| </script> | |
| </body> | |
| </html> | |