/** * Video Quality Selector * Allows users to switch between quality levels */ class QualitySelector { constructor(videoId, options = {}) { this.videoId = videoId; this.playerElement = options.playerElement || document.querySelector('video'); this.containerElement = options.containerElement || document.querySelector('.video-controls'); this.qualities = []; this.currentQuality = 'auto'; this.init(); } async init() { await this.loadQualities(); this.render(); this.attachEventListeners(); } async loadQualities() { try { const response = await fetch(`/api/cdn.php?action=qualities&video_id=${this.videoId}`); const data = await response.json(); if (data.success) { this.qualities = data.qualities; // Add "Auto" option this.qualities.unshift({ quality_label: 'auto', resolution: 'Auto', cdn_url: null }); } } catch (error) { console.error('Failed to load qualities:', error); } } render() { if (this.qualities.length <= 1) { // No quality options available return; } const container = document.createElement('div'); container.className = 'quality-selector'; container.innerHTML = ` `; if (this.containerElement) { this.containerElement.appendChild(container); } else { // Fallback: insert after video player this.playerElement.parentNode.insertBefore(container, this.playerElement.nextSibling); } } renderQualityOptions() { return this.qualities.map(q => `
${q.resolution || q.quality_label}
`).join(''); } attachEventListeners() { const button = document.getElementById('qualityButton'); const menu = document.getElementById('qualityMenu'); if (!button || !menu) return; // Toggle menu button.addEventListener('click', (e) => { e.stopPropagation(); const isVisible = menu.style.display === 'block'; menu.style.display = isVisible ? 'none' : 'block'; }); // Close menu on outside click document.addEventListener('click', () => { menu.style.display = 'none'; }); // Quality selection menu.addEventListener('click', (e) => { const option = e.target.closest('.quality-option'); if (option) { const quality = option.dataset.quality; const url = option.dataset.url; this.switchQuality(quality, url); menu.style.display = 'none'; } }); } async switchQuality(quality, url) { if (quality === this.currentQuality) return; const wasPlaying = !this.playerElement.paused; const currentTime = this.playerElement.currentTime; // Update active state document.querySelectorAll('.quality-option').forEach(option => { option.classList.toggle('active', option.dataset.quality === quality); }); // Update label const label = document.getElementById('qualityLabel'); if (label) { const selectedQuality = this.qualities.find(q => q.quality_label === quality); label.textContent = selectedQuality?.resolution || quality; } if (quality === 'auto') { // Auto quality - select best based on connection url = await this.selectAutoQuality(); } if (!url) { console.error('No URL available for quality:', quality); return; } // Switch video source this.playerElement.src = url; this.playerElement.currentTime = currentTime; if (wasPlaying) { this.playerElement.play(); } this.currentQuality = quality; // Save preference localStorage.setItem('preferredQuality', quality); } async selectAutoQuality() { // Measure connection speed (simplified) const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; let selectedQuality = 'original'; if (connection) { const effectiveType = connection.effectiveType; // Map connection type to quality if (effectiveType === 'slow-2g' || effectiveType === '2g') { selectedQuality = '360p'; } else if (effectiveType === '3g') { selectedQuality = '480p'; } else if (effectiveType === '4g') { selectedQuality = '1080p'; } else { selectedQuality = '720p'; } } // Find best available quality const available = this.qualities.find(q => q.quality_label === selectedQuality); if (available && available.cdn_url) { return available.cdn_url; } // Fallback to highest available const highestQuality = this.qualities.filter(q => q.cdn_url).pop(); return highestQuality?.cdn_url; } restorePreference() { const preferred = localStorage.getItem('preferredQuality'); if (preferred && preferred !== 'auto') { const quality = this.qualities.find(q => q.quality_label === preferred); if (quality && quality.cdn_url) { this.switchQuality(preferred, quality.cdn_url); } } } } // Auto-initialize document.addEventListener('DOMContentLoaded', () => { const videoElement = document.querySelector('video'); const videoId = new URLSearchParams(window.location.search).get('v'); if (videoElement && videoId) { window.qualitySelector = new QualitySelector(videoId, { playerElement: videoElement, containerElement: document.querySelector('.video-controls') }); // Restore user preference after qualities load setTimeout(() => { if (window.qualitySelector) { window.qualitySelector.restorePreference(); } }, 1000); } });