mirror of
https://github.com/hotheadhacker/seedbox-lite.git
synced 2025-09-02 00:51:36 +03:00
Enhance mobile video playback with fullscreen optimizations and MIME type handling
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/leaf.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, minimal-ui, viewport-fit=cover" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<title>SeedBox Lite - Stream Torrents Instantly</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -917,6 +917,67 @@
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/* Mobile Safari specific fullscreen optimizations */
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
/* iOS Safari */
|
||||
.video-element {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* Force native fullscreen behavior */
|
||||
.video-element::-webkit-media-controls-fullscreen-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-player-container.fullscreen {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
z-index: 99999 !important;
|
||||
background: #000 !important;
|
||||
}
|
||||
|
||||
.video-player-container.fullscreen .video-element {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
object-fit: contain !important;
|
||||
background: #000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Force mobile browsers to hide address bar in fullscreen */
|
||||
.video-player-container.mobile-fullscreen {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
height: 100dvh !important; /* Dynamic viewport height for mobile */
|
||||
z-index: 99999 !important;
|
||||
border-radius: 0 !important;
|
||||
background: #000 !important;
|
||||
/* Mobile Safari optimizations */
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.video-player-container.mobile-fullscreen .video-element {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
height: 100dvh !important;
|
||||
max-height: 100vh !important;
|
||||
max-height: 100dvh !important;
|
||||
object-fit: contain !important;
|
||||
background: #000 !important;
|
||||
}
|
||||
|
||||
/* Additional mobile fullscreen enhancements */
|
||||
@media (max-width: 768px) {
|
||||
.video-player-container.fullscreen {
|
||||
@@ -942,6 +1003,39 @@
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
/* Enhanced fullscreen button for mobile */
|
||||
.fullscreen-button {
|
||||
background: rgba(0, 0, 0, 0.8) !important;
|
||||
border: 2px solid #4ade80 !important;
|
||||
color: #4ade80 !important;
|
||||
font-weight: bold !important;
|
||||
min-width: 48px !important;
|
||||
min-height: 48px !important;
|
||||
border-radius: 50% !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.fullscreen-button:hover {
|
||||
background: rgba(74, 222, 128, 0.2) !important;
|
||||
transform: scale(1.1) !important;
|
||||
}
|
||||
|
||||
.fullscreen-button:active {
|
||||
transform: scale(0.95) !important;
|
||||
background: rgba(74, 222, 128, 0.4) !important;
|
||||
}
|
||||
|
||||
/* Add visual feedback for double-tap */
|
||||
.video-element {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resume Dialog */
|
||||
|
||||
@@ -469,6 +469,66 @@ const VideoPlayer = ({
|
||||
};
|
||||
}, [src, initialTime, onTimeUpdate, onProgress, updateBufferedProgress, torrentHash, fileIndex, title, hasShownResumeDialog, hasAppliedInitialTime]);
|
||||
|
||||
// Mobile video initialization
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
if (isMobile) {
|
||||
// Mobile-specific video event handlers
|
||||
const handleLoadStart = () => {
|
||||
console.log('📱 Mobile: Video load started');
|
||||
setIsLoading(true);
|
||||
};
|
||||
|
||||
const handleCanPlay = () => {
|
||||
console.log('📱 Mobile: Video can play');
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleWaiting = () => {
|
||||
console.log('📱 Mobile: Video waiting for data');
|
||||
setIsLoading(true);
|
||||
};
|
||||
|
||||
const handleStalled = () => {
|
||||
console.log('📱 Mobile: Video stalled, retrying...');
|
||||
setIsLoading(true);
|
||||
// On mobile, try to reload the video source if it stalls
|
||||
setTimeout(() => {
|
||||
if (video.paused && !isPlaying) {
|
||||
video.load();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleError = (e) => {
|
||||
console.error('📱 Mobile video error:', e);
|
||||
setIsLoading(false);
|
||||
// Try to recover from error
|
||||
setTimeout(() => {
|
||||
video.load();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
video.addEventListener('loadstart', handleLoadStart);
|
||||
video.addEventListener('canplay', handleCanPlay);
|
||||
video.addEventListener('waiting', handleWaiting);
|
||||
video.addEventListener('stalled', handleStalled);
|
||||
video.addEventListener('error', handleError);
|
||||
|
||||
return () => {
|
||||
video.removeEventListener('loadstart', handleLoadStart);
|
||||
video.removeEventListener('canplay', handleCanPlay);
|
||||
video.removeEventListener('waiting', handleWaiting);
|
||||
video.removeEventListener('stalled', handleStalled);
|
||||
video.removeEventListener('error', handleError);
|
||||
};
|
||||
}
|
||||
}, [src, isPlaying]);
|
||||
|
||||
// Fullscreen event listeners for mobile compatibility
|
||||
useEffect(() => {
|
||||
const handleFullscreenChange = () => {
|
||||
@@ -481,21 +541,76 @@ const VideoPlayer = ({
|
||||
setIsFullscreen(isCurrentlyFullscreen);
|
||||
};
|
||||
|
||||
// iOS Safari specific fullscreen events
|
||||
const handleWebkitFullscreenChange = () => {
|
||||
const video = videoRef.current;
|
||||
if (video) {
|
||||
const isVideoFullscreen = video.webkitDisplayingFullscreen;
|
||||
setIsFullscreen(isVideoFullscreen);
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listeners for all browser prefixes
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
||||
|
||||
// iOS Safari specific
|
||||
const video = videoRef.current;
|
||||
if (video) {
|
||||
video.addEventListener('webkitbeginfullscreen', () => setIsFullscreen(true));
|
||||
video.addEventListener('webkitendfullscreen', () => setIsFullscreen(false));
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('mozfullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
|
||||
|
||||
if (video) {
|
||||
video.removeEventListener('webkitbeginfullscreen', () => setIsFullscreen(true));
|
||||
video.removeEventListener('webkitendfullscreen', () => setIsFullscreen(false));
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Optimized play/pause for instant streaming
|
||||
// Mobile viewport optimization for fullscreen
|
||||
useEffect(() => {
|
||||
const optimizeMobileViewport = () => {
|
||||
// Ensure viewport meta tag allows user scaling for fullscreen
|
||||
let viewportMeta = document.querySelector('meta[name="viewport"]');
|
||||
if (!viewportMeta) {
|
||||
viewportMeta = document.createElement('meta');
|
||||
viewportMeta.name = 'viewport';
|
||||
document.head.appendChild(viewportMeta);
|
||||
}
|
||||
|
||||
if (isFullscreen) {
|
||||
// Optimize for fullscreen - allow zooming and remove address bar
|
||||
viewportMeta.content = 'width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes, minimal-ui, viewport-fit=cover';
|
||||
|
||||
// Additional mobile Safari optimizations
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
// Force viewport recalculation
|
||||
window.scrollTo(0, 1);
|
||||
setTimeout(() => {
|
||||
window.scrollTo(0, 0);
|
||||
// Trigger a resize to ensure fullscreen
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
// Reset viewport for normal viewing
|
||||
viewportMeta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no';
|
||||
}
|
||||
};
|
||||
|
||||
optimizeMobileViewport();
|
||||
}, [isFullscreen]);
|
||||
|
||||
// Optimized play/pause for mobile and instant streaming
|
||||
const togglePlay = async () => {
|
||||
if (!videoRef.current) return;
|
||||
|
||||
@@ -505,65 +620,124 @@ const VideoPlayer = ({
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
const video = videoRef.current;
|
||||
const buffered = video.buffered;
|
||||
const currentTime = video.currentTime;
|
||||
|
||||
// Check for instant play capability
|
||||
let canPlayInstantly = false;
|
||||
// Mobile-specific optimizations
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
if (buffered.length > 0) {
|
||||
for (let i = 0; i < buffered.length; i++) {
|
||||
const start = buffered.start(i);
|
||||
const end = buffered.end(i);
|
||||
if (isMobile) {
|
||||
// For mobile devices, ensure we have user interaction before playing
|
||||
try {
|
||||
// Start loading the video if not already loaded
|
||||
if (video.readyState < 2) { // HAVE_CURRENT_DATA
|
||||
video.load();
|
||||
setIsLoading(true);
|
||||
|
||||
// Wait for enough data to start playing
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timeout')), 10000);
|
||||
|
||||
const onCanPlay = () => {
|
||||
clearTimeout(timeout);
|
||||
video.removeEventListener('canplay', onCanPlay);
|
||||
video.removeEventListener('error', onError);
|
||||
setIsLoading(false);
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onError = (e) => {
|
||||
clearTimeout(timeout);
|
||||
video.removeEventListener('canplay', onCanPlay);
|
||||
video.removeEventListener('error', onError);
|
||||
setIsLoading(false);
|
||||
reject(e);
|
||||
};
|
||||
|
||||
video.addEventListener('canplay', onCanPlay);
|
||||
video.addEventListener('error', onError);
|
||||
});
|
||||
}
|
||||
|
||||
// Check if current position has any buffered data
|
||||
if (start <= currentTime && end > currentTime) {
|
||||
// For instant streaming, require minimal buffer (1 second)
|
||||
if (end - currentTime >= 1) {
|
||||
canPlayInstantly = true;
|
||||
break;
|
||||
// Play with mobile-specific handling
|
||||
const playPromise = video.play();
|
||||
if (playPromise !== undefined) {
|
||||
await playPromise;
|
||||
setIsPlaying(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Mobile playback failed, trying fallback:', error);
|
||||
setIsLoading(false);
|
||||
|
||||
// Fallback: simple play attempt
|
||||
try {
|
||||
await video.play();
|
||||
setIsPlaying(true);
|
||||
} catch (fallbackError) {
|
||||
console.error('Video playback failed:', fallbackError);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Desktop playback with buffering check
|
||||
const buffered = video.buffered;
|
||||
const currentTime = video.currentTime;
|
||||
|
||||
// Check for instant play capability
|
||||
let canPlayInstantly = false;
|
||||
|
||||
if (buffered.length > 0) {
|
||||
for (let i = 0; i < buffered.length; i++) {
|
||||
const start = buffered.start(i);
|
||||
const end = buffered.end(i);
|
||||
|
||||
// Check if current position has any buffered data
|
||||
if (start <= currentTime && end > currentTime) {
|
||||
// For instant streaming, require minimal buffer (1 second)
|
||||
if (end - currentTime >= 1) {
|
||||
canPlayInstantly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instant play logic - be aggressive about starting playback
|
||||
if (canPlayInstantly || bufferHealth > 30 || instantPlayEnabled) {
|
||||
try {
|
||||
await video.play();
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
} catch (playError) {
|
||||
console.log('Instant play failed, buffering...', playError);
|
||||
|
||||
// Desktop instant play logic
|
||||
if (canPlayInstantly || bufferHealth > 30 || instantPlayEnabled) {
|
||||
try {
|
||||
await video.play();
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
} catch (playError) {
|
||||
console.log('Instant play failed, buffering...', playError);
|
||||
setIsLoading(true);
|
||||
// Retry after a short buffer
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await video.play();
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
} catch (retryError) {
|
||||
console.log('Retry play failed:', retryError);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
// Show loading state while building initial buffer
|
||||
setIsLoading(true);
|
||||
// Retry after a short buffer
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await video.play();
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
} catch (retryError) {
|
||||
console.log('Retry play failed:', retryError);
|
||||
setIsLoading(false);
|
||||
console.log('Building buffer for smooth playback...');
|
||||
|
||||
// Try to play after minimal buffer is ready
|
||||
setTimeout(() => {
|
||||
if (videoRef.current && !isPlaying) {
|
||||
videoRef.current.play().then(() => {
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
}).catch(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
// Show loading state while building initial buffer
|
||||
setIsLoading(true);
|
||||
console.log('Building buffer for smooth playback...');
|
||||
|
||||
// Try to play after minimal buffer is ready
|
||||
setTimeout(() => {
|
||||
if (videoRef.current && !isPlaying) {
|
||||
videoRef.current.play().then(() => {
|
||||
setIsPlaying(true);
|
||||
setIsLoading(false);
|
||||
}).catch(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -616,27 +790,47 @@ const VideoPlayer = ({
|
||||
const container = videoRef.current.parentElement;
|
||||
const video = videoRef.current;
|
||||
|
||||
// Detect mobile devices
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
|
||||
('ontouchstart' in window) ||
|
||||
(navigator.maxTouchPoints > 0);
|
||||
|
||||
if (!isFullscreen) {
|
||||
// Try to enter fullscreen
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen();
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
// Safari
|
||||
container.webkitRequestFullscreen();
|
||||
} else if (container.mozRequestFullScreen) {
|
||||
// Firefox
|
||||
container.mozRequestFullScreen();
|
||||
} else if (container.msRequestFullscreen) {
|
||||
// IE/Edge
|
||||
container.msRequestFullscreen();
|
||||
} else if (video.webkitEnterFullscreen) {
|
||||
// iOS Safari - use video element fullscreen
|
||||
video.webkitEnterFullscreen();
|
||||
} else if (video.requestFullscreen) {
|
||||
// Fallback to video element
|
||||
video.requestFullscreen();
|
||||
if (isMobile) {
|
||||
// For mobile devices, especially iOS Safari
|
||||
if (video.webkitEnterFullscreen) {
|
||||
// iOS Safari - use video element fullscreen (hides address bar)
|
||||
video.webkitEnterFullscreen();
|
||||
} else if (video.requestFullscreen) {
|
||||
// Android Chrome/Firefox
|
||||
video.requestFullscreen();
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
// Fallback for mobile Safari
|
||||
container.webkitRequestFullscreen();
|
||||
} else {
|
||||
// Fallback: simulate fullscreen with CSS
|
||||
setIsFullscreen(true);
|
||||
// Trigger viewport change to hide address bar
|
||||
window.scrollTo(0, 1);
|
||||
setTimeout(() => window.scrollTo(0, 0), 100);
|
||||
}
|
||||
} else {
|
||||
// Desktop fullscreen
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen();
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
container.webkitRequestFullscreen();
|
||||
} else if (container.mozRequestFullScreen) {
|
||||
container.mozRequestFullScreen();
|
||||
} else if (container.msRequestFullscreen) {
|
||||
container.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMobile || !video.webkitEnterFullscreen) {
|
||||
setIsFullscreen(true);
|
||||
}
|
||||
setIsFullscreen(true);
|
||||
} else {
|
||||
// Try to exit fullscreen
|
||||
if (document.exitFullscreen) {
|
||||
@@ -650,8 +844,14 @@ const VideoPlayer = ({
|
||||
} else if (video.webkitExitFullscreen) {
|
||||
// iOS Safari
|
||||
video.webkitExitFullscreen();
|
||||
} else {
|
||||
// CSS fullscreen fallback
|
||||
setIsFullscreen(false);
|
||||
}
|
||||
|
||||
if (!document.fullscreenElement && !document.webkitFullscreenElement) {
|
||||
setIsFullscreen(false);
|
||||
}
|
||||
setIsFullscreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -770,7 +970,7 @@ const VideoPlayer = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`video-player-container ${isFullscreen ? 'fullscreen' : ''}`}
|
||||
className={`video-player-container ${isFullscreen ? 'fullscreen' : ''} ${isFullscreen && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ? 'mobile-fullscreen' : ''}`}
|
||||
onMouseMove={showControlsTemporarily}
|
||||
onMouseLeave={() => isPlaying && setShowControls(false)}
|
||||
>
|
||||
@@ -793,6 +993,14 @@ const VideoPlayer = ({
|
||||
onDoubleClick={toggleFullscreen}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
playsInline={false}
|
||||
webkit-playsinline="false"
|
||||
controls={false}
|
||||
preload="none"
|
||||
crossOrigin="anonymous"
|
||||
muted={false}
|
||||
autoPlay={false}
|
||||
poster=""
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
|
||||
@@ -1116,7 +1116,21 @@ app.get('/api/torrents/:identifier/files/:fileIdx/stream', async (req, res) => {
|
||||
|
||||
console.log(`🎬 Streaming: ${file.name} (${(file.length / 1024 / 1024).toFixed(1)} MB)`);
|
||||
|
||||
// Handle range requests
|
||||
// Detect file type for proper MIME type
|
||||
const ext = file.name.split('.').pop().toLowerCase();
|
||||
const mimeTypes = {
|
||||
'mp4': 'video/mp4',
|
||||
'mkv': 'video/x-matroska',
|
||||
'avi': 'video/x-msvideo',
|
||||
'mov': 'video/quicktime',
|
||||
'wmv': 'video/x-ms-wmv',
|
||||
'flv': 'video/x-flv',
|
||||
'webm': 'video/webm',
|
||||
'm4v': 'video/mp4'
|
||||
};
|
||||
const contentType = mimeTypes[ext] || 'video/mp4';
|
||||
|
||||
// Handle range requests (crucial for mobile video playback)
|
||||
const range = req.headers.range;
|
||||
if (range) {
|
||||
const parts = range.replace(/bytes=/, "").split("-");
|
||||
@@ -1128,7 +1142,13 @@ app.get('/api/torrents/:identifier/files/:fileIdx/stream', async (req, res) => {
|
||||
'Content-Range': `bytes ${start}-${end}/${file.length}`,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': chunkSize,
|
||||
'Content-Type': 'video/mp4'
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Range, Content-Type',
|
||||
'Access-Control-Expose-Headers': 'Content-Range, Accept-Ranges, Content-Length'
|
||||
});
|
||||
|
||||
const stream = file.createReadStream({ start, end });
|
||||
@@ -1136,7 +1156,14 @@ app.get('/api/torrents/:identifier/files/:fileIdx/stream', async (req, res) => {
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
'Content-Length': file.length,
|
||||
'Content-Type': 'video/mp4'
|
||||
'Content-Type': contentType,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Range, Content-Type',
|
||||
'Access-Control-Expose-Headers': 'Content-Range, Accept-Ranges, Content-Length'
|
||||
});
|
||||
file.createReadStream().pipe(res);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user