const handleDownloadClick = useCallback(async (url: string, formatExt: VideoFormat) => {
const title = videoInfo.title;
const filename = `${title.replace(/[^a-z0-9\-_ ]/gi, '').replace(/\s+/g, '_') || 'download'}.${formatExt.ext}`;
try {
console.log('Starting download for:', filename);
// Show loading indicator
const loadingElement = document.createElement('div');
loadingElement.textContent = 'Preparing download...';
loadingElement.style.cssText = `
position: fixed; top: 20px; right: 20px;
background: #333; color: white; padding: 10px;
border-radius: 4px; z-index: 9999;
`;
document.body.appendChild(loadingElement);
// Fetch with streaming
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
// Get content length for progress (if available)
const contentLength = response.headers.get('content-length');
const total = parseInt(contentLength, 10);
// Create readable stream
const reader = response.body.getReader();
const chunks = [];
let receivedLength = 0;
// Read chunks progressively
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
// Update progress if needed
if (total) {
const percent = Math.round((receivedLength / total) * 100);
loadingElement.textContent = `Downloading... ${percent}%`;
}
}
// Combine chunks into blob
const blob = new Blob(chunks);
// Create download link
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// Cleanup
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
if (loadingElement.parentNode) {
document.body.removeChild(loadingElement);
}
}, 1000);
} catch (error) {
console.error('Download failed:', error);
alert('Download failed. Please try again or copy the URL manually.');
}
}, [videoInfo.title]);
const handleDownloadClick = useCallback(async (url: string, formatExt: VideoFormat) => {
const title = videoInfo.title;
const filename = `${title.replace(/[^a-z0-9\-_ ]/gi, '').replace(/\s+/g, '_') || 'download'}.${formatExt.ext}`;
try {
// Simple fetch approach
const response = await fetch(url, {
method: 'GET',
headers: {
// Add headers that might help
'Accept': '*/*',
'User-Agent': navigator.userAgent
}
});
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// Cleanup
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
}, 100);
} catch (error) {
console.error('Download failed:', error);
// Fallback to simple window.open
window.open(url, '_blank');
}
}, [videoInfo.title]);
const handleDownloadClick = useCallback((url: string, formatExt: VideoFormat) => {
const title = videoInfo.title;
const filename = `${title.replace(/[^a-z0-9\-_ ]/gi, '').replace(/\s+/g, '_') || 'download'}.${formatExt.ext}`;
const xhr = new XMLHttpRequest();
// Show progress
const progressDiv = document.createElement('div');
progressDiv.style.cssText = `
position: fixed; top: 20px; right: 20px;
background: #333; color: white; padding: 10px;
border-radius: 4px; z-index: 9999;
`;
progressDiv.textContent = 'Starting download...';
document.body.appendChild(progressDiv);
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
progressDiv.textContent = `Downloading... ${Math.round(percentComplete)}%`;
}
};
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const blobUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// Cleanup
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
if (progressDiv.parentNode) {
document.body.removeChild(progressDiv);
}
}, 1000);
} else {
console.error('Download failed with status:', xhr.status);
if (progressDiv.parentNode) {
document.body.removeChild(progressDiv);
}
alert('Download failed. Opening in new tab instead.');
window.open(url, '_blank');
}
};
xhr.onerror = function() {
console.error('Network error during download');
if (progressDiv.parentNode) {
document.body.removeChild(progressDiv);
}
alert('Network error. Opening in new tab instead.');
window.open(url, '_blank');
};
xhr.send();
}, [videoInfo.title]);
Since Facebook videos work with curl but not directly in browser, create a simple proxy endpoint:
// Server-side endpoint (Express.js example)
app.get('/api/proxy-download', async (req, res) => {
const { url, filename } = req.query;
try {
const response = await fetch(decodeURIComponent(url));
if (!response.ok) {
return res.status(response.status).send('Failed to fetch video');
}
// Set headers to force download
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.setHeader('Content-Type', response.headers.get('content-type') || 'application/octet-stream');
// Stream the response
response.body.pipe(res);
} catch (error) {
res.status(500).send('Download failed');
}
});
Then use it in your frontend:
const handleDownloadClick = useCallback((url: string, formatExt: VideoFormat) => {
const title = videoInfo.title;
const filename = `${title.replace(/[^a-z0-9\-_ ]/gi, '').replace(/\s+/g, '_') || 'download'}.${formatExt.ext}`;
// Use your proxy endpoint
const proxyUrl = `/api/proxy-download?url=${encodeURIComponent(url)}&filename=${encodeURIComponent(filename)}`;
window.location.href = proxyUrl;
}, [videoInfo.title]);