// Universal Media Downloader - Enhanced JavaScript
// Professional media downloader with API integration
class UniversalMediaDownloader {
constructor() {
this.apiBaseUrl = '/api';
this.currentFormats = [];
this.currentMediaInfo = null;
this.isProcessing = false;
this.downloadQueue = [];
this.settings = this.loadSettings();
this.activeDownloads = new Map();
this.progressIntervals = new Map();
this.initializeApp();
}
initializeApp() {
this.setupEventListeners();
this.setupKeyboardNavigation();
this.checkApiHealth();
this.setupServiceWorker();
this.loadDownloadHistory();
this.announceAppReady();
}
// Settings Management
loadSettings() {
const defaultSettings = {
autoUpdate: true,
privacyMode: false,
defaultQuality: 'best',
theme: 'dark'
};
try {
const saved = localStorage.getItem('downloader-settings');
return { ...defaultSettings, ...(saved ? JSON.parse(saved) : {}) };
} catch {
return defaultSettings;
}
}
saveSettings() {
try {
localStorage.setItem('downloader-settings', JSON.stringify(this.settings));
this.announceToScreenReader('Settings saved successfully');
} catch (error) {
console.error('Failed to save settings:', error);
}
}
// API Communication
async checkApiHealth() {
try {
const response = await fetch(`${this.apiBaseUrl}/health`);
const data = await response.json();
if (data.status === 'healthy') {
this.updateStatus('API connected and ready', 'success');
this.updateApiInfo(data);
} else {
throw new Error('API not healthy');
}
} catch (error) {
console.error('API health check failed:', error);
this.showError('Unable to connect to download service. Please refresh the page.');
}
}
async getFormats(url) {
try {
const response = await fetch(`${this.apiBaseUrl}/formats`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url })
});
const data = await response.json();
if (!response.ok) {
// Check if we have fallback download options
if (data.fallback && data.download_options) {
return {
success: false,
fallback: true,
download_options: data.download_options,
basic_info: data.basic_info,
message: data.message,
instruction: data.instruction
};
}
throw new Error(data.message || data.error || 'Failed to get formats');
}
return data;
} catch (error) {
console.error('Get formats error:', error);
throw error;
}
}
async startDownload(url, formatId) {
try {
const downloadId = `dl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const response = await fetch(`${this.apiBaseUrl}/download`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
url,
format_id: formatId,
download_id: downloadId
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to start download');
}
// Start monitoring progress
this.startProgressMonitoring(downloadId);
return { ...data, downloadId };
} catch (error) {
console.error('Start download error:', error);
throw error;
}
}
async getProgress(downloadId) {
try {
const response = await fetch(`${this.apiBaseUrl}/progress/${downloadId}`);
const data = await response.json();
return data;
} catch (error) {
console.error('Get progress error:', error);
return null;
}
}
async getSupportedPlatforms() {
try {
const response = await fetch(`${this.apiBaseUrl}/supported-platforms`);
const data = await response.json();
return data;
} catch (error) {
console.error('Get platforms error:', error);
return null;
}
}
async updateYtdlp() {
try {
this.updateStatus('Checking for yt-dlp updates...', 'info');
const response = await fetch(`${this.apiBaseUrl}/update`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
this.updateStatus(`yt-dlp updated to version: ${data.version}`, 'success');
this.announceToScreenReader(`yt-dlp updated to version ${data.version}`);
} else {
this.updateStatus(data.message || 'Update check completed', 'info');
}
return data;
} catch (error) {
console.error('Update yt-dlp error:', error);
this.showError('Failed to update yt-dlp');
throw error;
}
}
// Progress Monitoring
startProgressMonitoring(downloadId) {
if (this.progressIntervals.has(downloadId)) {
clearInterval(this.progressIntervals.get(downloadId));
}
const interval = setInterval(async () => {
const progress = await this.getProgress(downloadId);
if (progress && progress.success) {
this.updateDownloadProgress(progress);
if (progress.progress.status === 'finished' || progress.progress.status === 'error') {
clearInterval(interval);
this.progressIntervals.delete(downloadId);
if (progress.progress.status === 'finished') {
this.onDownloadComplete(downloadId, progress.progress);
} else {
this.onDownloadError(downloadId, progress.progress);
}
}
}
}, 1000);
this.progressIntervals.set(downloadId, interval);
}
updateDownloadProgress(progressData) {
const { downloadId, progress } = progressData;
// Update queue item
const queueItem = document.querySelector(`[data-download-id="${downloadId}"]`);
if (queueItem) {
this.updateQueueItem(queueItem, progress);
}
// Update global progress
if (progress.status === 'downloading') {
this.showProgress(progress.percentage, progress.speed, progress.eta);
}
}
// UI Updates
updateStatus(message, type = 'info') {
const statusContainer = document.getElementById('status-container');
const statusIcon = type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️';
statusContainer.innerHTML = `
${statusIcon}
${message}
`;
this.announceToScreenReader(message);
}
showError(message) {
const errorSection = document.getElementById('error-section');
const errorContainer = document.getElementById('error-messages');
errorContainer.innerHTML = `
❌
${message}
`;
errorSection.style.display = 'block';
this.announceToScreenReader(`Error: ${message}`);
}
hideError() {
const errorSection = document.getElementById('error-section');
errorSection.style.display = 'none';
}
showProgress(percentage, speed = null, eta = null) {
const progressSection = document.getElementById('progress-section');
const progressBar = document.getElementById('progress-bar');
const progressPercentage = document.getElementById('progress-percentage');
const progressSpeed = document.getElementById('progress-speed');
const progressTitle = document.getElementById('progress-title');
progressSection.style.display = 'block';
progressBar.value = percentage;
progressPercentage.textContent = `${Math.round(percentage)}%`;
if (speed) {
progressSpeed.textContent = this.formatSpeed(speed);
}
if (eta) {
progressTitle.textContent = `Downloading... ETA: ${this.formatTime(eta)}`;
}
}
hideProgress() {
const progressSection = document.getElementById('progress-section');
progressSection.style.display = 'none';
}
displayMediaInfo(info) {
const section = document.getElementById('media-info-section');
const title = document.getElementById('media-title');
const uploader = document.getElementById('media-uploader');
const platform = document.getElementById('media-platform');
const duration = document.getElementById('media-duration');
const views = document.getElementById('media-views');
const likes = document.getElementById('media-likes');
const thumb = document.getElementById('media-thumb');
const placeholder = document.getElementById('thumb-placeholder');
// Update content
title.textContent = info.title || 'Unknown Title';
uploader.textContent = info.uploader || 'Unknown Uploader';
platform.textContent = info.platform || 'Unknown';
platform.className = `platform-badge platform-${(info.platform || '').toLowerCase()}`;
duration.textContent = info.duration ? this.formatTime(info.duration) : '';
views.textContent = info.view_count ? `${this.formatNumber(info.view_count)} views` : '';
likes.textContent = info.like_count ? `${this.formatNumber(info.like_count)} likes` : '';
// Handle thumbnail
if (info.thumbnail) {
thumb.src = info.thumbnail;
thumb.alt = `Thumbnail for ${info.title}`;
thumb.style.display = 'block';
placeholder.style.display = 'none';
} else {
thumb.style.display = 'none';
placeholder.style.display = 'flex';
}
section.style.display = 'block';
}
displayFormats(formats) {
const section = document.getElementById('formats-section');
const formatList = document.getElementById('format-list');
// Clear existing formats
formatList.innerHTML = '';
// Sort formats by quality and type
const sortedFormats = this.sortFormats(formats);
sortedFormats.forEach((format, index) => {
const formatElement = this.createFormatElement(format, index);
formatList.appendChild(formatElement);
});
section.style.display = 'block';
this.currentFormats = sortedFormats;
}
createFormatElement(format, index) {
const element = document.createElement('div');
element.className = 'format-item';
element.setAttribute('role', 'listitem');
element.setAttribute('data-format-id', format.id);
element.setAttribute('data-format-type', format.type);
element.setAttribute('tabindex', '0');
const fileSize = format.filesize ? this.formatFileSize(format.filesize) : 'Unknown size';
const quality = format.width && format.height ? `${format.height}p` : format.format_note || 'Unknown';
const codec = format.vcodec !== 'none' ? format.vcodec.split('.')[0] : format.acodec.split('.')[0];
element.innerHTML = `
`;
// Add event listeners
element.addEventListener('click', (e) => {
if (!e.target.closest('.download-btn')) {
this.selectFormat(format);
}
});
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.selectFormat(format);
}
});
const downloadBtn = element.querySelector('.download-btn');
downloadBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.handleDownload(format, downloadBtn);
});
return element;
}
createQueueItem(progress) {
const element = document.createElement('div');
element.className = 'queue-item';
element.setAttribute('data-download-id', progress.download_id);
const fileName = progress.filename ? progress.filename.split('/').pop() : 'Downloading...';
element.innerHTML = `
${fileName}
${progress.url || 'Downloading...'}
`;
return element;
}
updateQueueItem(element, progress) {
const progressBar = element.querySelector('.progress-bar');
const statusText = element.querySelector('.queue-status');
if (progress.status === 'downloading') {
progressBar.value = progress.percentage || 0;
statusText.textContent = `${Math.round(progress.percentage || 0)}%`;
} else if (progress.status === 'finished') {
element.classList.add('completed');
statusText.textContent = 'Complete';
progressBar.value = 100;
} else if (progress.status === 'error') {
element.classList.add('error');
statusText.textContent = 'Error';
}
}
// Event Handlers
setupEventListeners() {
// Form submission
const form = document.getElementById('download-form');
form.addEventListener('submit', (e) => this.handleFormSubmit(e));
// URL input
const urlInput = document.getElementById('url-input');
urlInput.addEventListener('input', () => this.validateURL());
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.handleFormSubmit(e);
}
});
// Format filters
const filterBtns = document.querySelectorAll('.filter-btn');
filterBtns.forEach(btn => {
btn.addEventListener('click', (e) => this.handleFilterClick(e));
});
// Settings modal
const settingsBtn = document.getElementById('settings-btn');
const settingsModal = document.getElementById('settings-modal');
const modalClose = document.querySelector('.modal-close');
const saveSettingsBtn = document.getElementById('save-settings');
settingsBtn.addEventListener('click', () => this.openSettings());
modalClose.addEventListener('click', () => this.closeSettings());
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) this.closeSettings();
});
saveSettingsBtn.addEventListener('click', () => this.saveSettingsAndClose());
// Update button
const updateBtn = document.getElementById('update-btn');
updateBtn.addEventListener('click', () => this.handleUpdateYtdlp());
// Clear queue button
const clearQueueBtn = document.getElementById('clear-queue-btn');
clearQueueBtn.addEventListener('click', () => this.clearQueue());
// Bottom navigation
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', (e) => this.handleNavClick(e));
});
// Settings inputs
this.setupSettingsInputs();
}
setupSettingsInputs() {
const autoUpdate = document.getElementById('auto-update');
const privacyMode = document.getElementById('privacy-mode');
const defaultQuality = document.getElementById('default-quality');
autoUpdate.checked = this.settings.autoUpdate;
privacyMode.checked = this.settings.privacyMode;
defaultQuality.value = this.settings.defaultQuality;
}
async handleFormSubmit(event) {
event.preventDefault();
if (this.isProcessing) {
return;
}
this.hideError();
this.hideProgress();
const url = document.getElementById('url-input').value.trim();
if (!this.isValidURL(url)) {
this.showError('Please enter a valid URL. Make sure it starts with http:// or https://');
return;
}
this.setLoadingState(true);
this.updateStatus('Analyzing URL and fetching available formats...', 'info');
try {
const result = await this.getFormats(url);
// Handle fallback case with download options
if (result.fallback && result.download_options) {
this.displayDownloadOptions(result);
this.updateStatus(result.message || 'Format extraction failed, but direct download options are available.', 'warning');
return;
}
if (result.success) {
this.currentMediaInfo = {
title: result.title,
uploader: result.uploader,
platform: result.platform,
duration: result.duration,
thumbnail: result.thumbnail,
view_count: result.view_count,
like_count: result.like_count
};
this.displayMediaInfo(this.currentMediaInfo);
this.displayFormats(result.formats);
this.updateStatus(`Found ${result.formats.length} available formats for ${result.platform}. Select your preferred option below.`, 'success');
// Add to queue
this.addToQueue(result);
} else {
throw new Error(result.message || result.error || 'Failed to analyze URL');
}
} catch (error) {
console.error('Form submission error:', error);
this.showError(error.message || 'Failed to analyze URL. Please check if the URL is valid and try again.');
} finally {
this.setLoadingState(false);
}
}
async handleDownload(format, button) {
if (this.isProcessing) {
return;
}
try {
this.setDownloadButtonState(button, 'downloading');
this.updateStatus(`Starting download: ${this.getFormatTitle(format)}`, 'info');
const result = await this.startDownload(
document.getElementById('url-input').value.trim(),
format.id
);
// Add to active downloads
this.activeDownloads.set(result.downloadId, { format, startTime: Date.now() });
// Add to queue display
const queueList = document.getElementById('queue-list');
const queueItem = this.createQueueItem({
download_id: result.downloadId,
filename: this.getFormatTitle(format),
url: document.getElementById('url-input').value.trim()
});
queueList.appendChild(queueItem);
this.updateStatus(`Download started: ${this.getFormatTitle(format)}`, 'success');
this.announceToScreenReader(`Download started: ${this.getFormatTitle(format)}`);
} catch (error) {
console.error('Download error:', error);
this.setDownloadButtonState(button, 'error');
this.showError(`Download failed: ${error.message}`);
}
}
async handleUpdateYtdlp() {
const updateBtn = document.getElementById('update-btn');
updateBtn.disabled = true;
updateBtn.style.opacity = '0.6';
try {
await this.updateYtdlp();
} catch (error) {
console.error('Update error:', error);
} finally {
updateBtn.disabled = false;
updateBtn.style.opacity = '1';
}
}
handleFilterClick(event) {
const filter = event.target.dataset.filter;
const buttons = document.querySelectorAll('.filter-btn');
// Update active filter
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// Filter formats
const formatItems = document.querySelectorAll('.format-item');
formatItems.forEach(item => {
const formatType = item.dataset.formatType;
if (filter === 'all' || formatType === filter) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
handleNavClick(event) {
const tab = event.currentTarget.dataset.tab;
const items = document.querySelectorAll('.nav-item');
items.forEach(item => item.classList.remove('active'));
event.currentTarget.classList.add('active');
// Show/hide relevant sections based on tab
this.showTabContent(tab);
}
showTabContent(tab) {
// This would show/hide different content areas
// For now, we'll just announce the tab change
this.announceToScreenReader(`Switched to ${tab} tab`);
}
selectFormat(format) {
// Highlight selected format
const formatItems = document.querySelectorAll('.format-item');
formatItems.forEach(item => item.classList.remove('selected'));
const selectedItem = document.querySelector(`[data-format-id="${format.id}"]`);
if (selectedItem) {
selectedItem.classList.add('selected');
this.announceToScreenReader(`Selected ${this.getFormatTitle(format)}`);
}
}
// Utility Methods
isValidURL(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
validateURL() {
const urlInput = document.getElementById('url-input');
const url = urlInput.value.trim();
if (url === '') {
urlInput.setCustomValidity('');
return true;
}
if (!this.isValidURL(url)) {
urlInput.setCustomValidity('Please enter a valid URL');
urlInput.reportValidity();
return false;
}
urlInput.setCustomValidity('');
return true;
}
setLoadingState(isLoading) {
this.isProcessing = isLoading;
const button = document.getElementById('analyze-btn');
const urlInput = document.getElementById('url-input');
if (isLoading) {
button.classList.add('loading');
button.disabled = true;
urlInput.disabled = true;
} else {
button.classList.remove('loading');
button.disabled = false;
urlInput.disabled = false;
urlInput.focus();
}
}
setDownloadButtonState(button, state) {
button.disabled = state === 'downloading';
button.classList.remove('downloading', 'error');
if (state === 'downloading') {
button.classList.add('downloading');
button.innerHTML = 'Downloading...';
} else if (state === 'error') {
button.classList.add('error');
button.innerHTML = 'Error';
} else {
button.innerHTML = 'Download';
}
}
addToQueue(result) {
// Add media to download queue for easy access
this.downloadQueue.push({
url: document.getElementById('url-input').value.trim(),
title: result.title,
platform: result.platform,
formats: result.formats,
timestamp: Date.now()
});
}
clearQueue() {
this.downloadQueue = [];
const queueList = document.getElementById('queue-list');
queueList.innerHTML = '';
this.announceToScreenReader('Download queue cleared');
}
onDownloadComplete(downloadId, progress) {
this.setDownloadCompleted(downloadId);
this.updateStatus('Download completed successfully!', 'success');
this.announceToScreenReader('Download completed successfully');
this.hideProgress();
}
onDownloadError(downloadId, progress) {
this.setDownloadError(downloadId);
this.updateStatus('Download failed. Please try again.', 'error');
this.announceToScreenReader('Download failed');
this.hideProgress();
}
setDownloadCompleted(downloadId) {
const queueItem = document.querySelector(`[data-download-id="${downloadId}"]`);
if (queueItem) {
queueItem.classList.add('completed');
const statusText = queueItem.querySelector('.queue-status');
if (statusText) statusText.textContent = 'Complete';
}
}
setDownloadError(downloadId) {
const queueItem = document.querySelector(`[data-download-id="${downloadId}"]`);
if (queueItem) {
queueItem.classList.add('error');
const statusText = queueItem.querySelector('.queue-status');
if (statusText) statusText.textContent = 'Error';
}
}
// Settings Modal
openSettings() {
const modal = document.getElementById('settings-modal');
modal.style.display = 'flex';
this.announceToScreenReader('Settings opened');
}
closeSettings() {
const modal = document.getElementById('settings-modal');
modal.style.display = 'none';
this.announceToScreenReader('Settings closed');
}
saveSettingsAndClose() {
const autoUpdate = document.getElementById('auto-update').checked;
const privacyMode = document.getElementById('privacy-mode').checked;
const defaultQuality = document.getElementById('default-quality').value;
this.settings = {
...this.settings,
autoUpdate,
privacyMode,
defaultQuality
};
this.saveSettings();
this.closeSettings();
}
// Formatting Utilities
getFormatTitle(format) {
const type = format.type === 'video' ? 'Video' : 'Audio';
const quality = format.height ? `${format.height}p` : format.format_note || 'Best';
return `${type} - ${quality}`;
}
sortFormats(formats) {
// Sort by type (video first), then by quality
return formats.sort((a, b) => {
if (a.type !== b.type) {
return a.type === 'video' ? -1 : 1;
}
if (a.height && b.height) {
return b.height - a.height;
}
return 0;
});
}
formatFileSize(bytes) {
if (!bytes) return 'Unknown';
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
formatSpeed(bytesPerSec) {
if (!bytesPerSec) return '';
return this.formatFileSize(bytesPerSec) + '/s';
}
formatTime(seconds) {
if (!seconds) return '';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
}
updateApiInfo(data) {
// Update any UI elements showing API info
console.log('API Info:', data);
}
// Accessibility
announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
setupKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
// Global keyboard shortcuts
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
case 'u':
e.preventDefault();
document.getElementById('url-input').focus();
break;
case 's':
e.preventDefault();
this.openSettings();
break;
}
}
if (e.key === 'Escape') {
this.closeSettings();
}
});
}
// Service Worker
setupServiceWorker() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
}
}
loadDownloadHistory() {
// Load download history from localStorage
try {
const history = localStorage.getItem('download-history');
if (history) {
const parsed = JSON.parse(history);
console.log('Loaded download history:', parsed.length, 'items');
}
} catch (error) {
console.error('Failed to load download history:', error);
}
}
displayDownloadOptions(result) {
const section = document.getElementById('formats-section');
const formatList = document.getElementById('format-list');
// Clear existing content
formatList.innerHTML = '';
// Show basic information if available
if (result.basic_info) {
const info = result.basic_info;
this.currentMediaInfo = {
title: info.title,
uploader: info.uploader,
platform: info.platform,
url: info.url
};
this.displayMediaInfo(this.currentMediaInfo);
}
// Create download options section
const optionsContainer = document.createElement('div');
optionsContainer.className = 'download-options-container';
const titleElement = document.createElement('h3');
titleElement.textContent = 'Direct File Downloads';
titleElement.className = 'download-options-title';
const instructionElement = document.createElement('p');
instructionElement.textContent = result.instruction || 'Click on any direct file link below to download immediately:';
instructionElement.className = 'download-instruction';
optionsContainer.appendChild(titleElement);
optionsContainer.appendChild(instructionElement);
// Create download links for direct files
if (result.download_options && Array.isArray(result.download_options)) {
result.download_options.forEach((option, index) => {
const linkElement = document.createElement('a');
linkElement.href = option.url;
linkElement.target = '_blank';
linkElement.rel = 'noopener noreferrer';
linkElement.className = 'download-link';
// Create icon based on type
const icon = document.createElement('span');
icon.className = 'download-icon';
icon.innerHTML = this.getFileTypeIcon(option.type);
// Create link text with details
const textContainer = document.createElement('div');
textContainer.className = 'download-text-container';
const mainText = document.createElement('div');
mainText.className = 'download-text';
mainText.textContent = option.description || `${option.type} ${option.quality}`;
const fileInfo = document.createElement('div');
fileInfo.className = 'download-file-info';
const fileDetails = [];
if (option.ext && option.ext !== 'unknown') {
fileDetails.push(option.ext.toUpperCase());
}
if (option.filesize) {
fileDetails.push(option.filesize);
}
if (option.format_id && option.format_id !== 'unknown') {
fileDetails.push(`Format: ${option.format_id}`);
}
fileInfo.textContent = fileDetails.join(' • ');
textContainer.appendChild(mainText);
textContainer.appendChild(fileInfo);
linkElement.appendChild(icon);
linkElement.appendChild(textContainer);
optionsContainer.appendChild(linkElement);
});
} else {
// Fallback for simple URL links
result.download_options.forEach((url, index) => {
const linkElement = document.createElement('a');
linkElement.href = url;
linkElement.target = '_blank';
linkElement.rel = 'noopener noreferrer';
linkElement.className = 'download-link';
const icon = document.createElement('span');
icon.className = 'download-icon';
icon.innerHTML = this.getDownloadIcon();
const text = document.createElement('span');
text.className = 'download-text';
if (url.includes('youtube.com/watch')) {
text.textContent = `YouTube Direct Link ${index + 1}`;
} else if (url.includes('youtu.be/')) {
text.textContent = `YouTube Short Link ${index + 1}`;
} else {
text.textContent = `Direct Link ${index + 1}`;
}
linkElement.appendChild(icon);
linkElement.appendChild(text);
optionsContainer.appendChild(linkElement);
});
}
formatList.appendChild(optionsContainer);
section.style.display = 'block';
// Update current formats to empty since we don't have format-specific downloads
this.currentFormats = [];
}
getDownloadIcon() {
return `
`;
}
getFileTypeIcon(type) {
const icons = {
'video': `
`,
'audio': `
`,
'image': `
`,
'thumbnail': `
`,
'direct': `
`
};
return icons[type] || icons['direct'];
}
announceAppReady() {
setTimeout(() => {
this.announceToScreenReader('Universal Media Downloader ready. Enter a URL to begin downloading.');
document.getElementById('url-input').focus();
}, 1000);
}
}
// Initialize the application
document.addEventListener('DOMContentLoaded', () => {
window.downloader = new UniversalMediaDownloader();
});
// Global error handling
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
if (window.downloader) {
window.downloader.showError('An unexpected error occurred. Please refresh the page and try again.');
}
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
if (window.downloader) {
window.downloader.showError('A background process failed. Please check your connection and try again.');
}
});