/** * Video format detection and support level classification * Determines the optimal playback method for different video formats */ export interface VideoFormat { type: 'direct' | 'hls' | 'local-player'; // Removed 'fallback', added 'local-player' url: string; mimeType?: string; qualities?: QualityLevel[]; supportLevel: 'native' | 'hls' | 'local-player-required'; // Updated 'limited' to 'local-player-required' warning?: string; action?: 'launch-local-player'; // New field for local player guidance recommendedPlayers?: string[]; // New field for player recommendations streamInfo?: { contentType: string; acceptRanges: string; supportsSeek: boolean; authentication: string; }; // New field for stream metadata } export interface QualityLevel { html: string; url: string; default?: boolean; } export interface VideoFile { id: number; title: string; path: string; size: number; thumbnail: string; type: string; codec_info?: string; bookmark_count?: number; avg_rating?: number; star_count?: number; } // Native browser support formats (direct streaming) const NATIVE_SUPPORTED_FORMATS = [ 'mp4', // H.264, H.265 'webm', // VP8, VP9 'ogg', // Theora 'ogv' ]; // Formats that work well with HLS streaming (but .ts files should be served directly) const HLS_COMPATIBLE_FORMATS = [ 'mp4', 'm4v', // Note: .ts files are removed from here - they should be served directly 'm2ts', 'mts' ]; // MPEG Transport Stream formats - can be served via HLS or converted const TS_STREAM_FORMATS = [ 'ts', // MPEG Transport Stream - optimal for HLS streaming 'm2ts', // Blu-ray Transport Stream 'mts' // AVCHD Transport Stream ]; // Formats with limited support (may need transcoding) const LIMITED_SUPPORT_FORMATS = [ 'avi', 'wmv', 'flv', 'mkv', 'mov', // Some MOV files may work '3gp', 'vob' ]; /** * Detect video format and determine optimal playback method * TRANSCODING REMOVED: Binary decision - native browser OR local player */ export function detectVideoFormat(video: VideoFile): VideoFormat { console.log('[FormatDetector] Input video:', video); const extension = getFileExtension(video.path).toLowerCase(); console.log('[FormatDetector] Extracted extension:', extension); const codecInfo = parseCodecInfo(video.codec_info); console.log('[FormatDetector] Codec info:', codecInfo); // EXPLICIT TEST: Force .avi files to local player if (extension === 'avi') { console.log('[FormatDetector] .avi file detected, forcing local player'); return createLocalPlayerFormat(video, extension); } // TRANSCODING DISABLED: No more transcoding fallback // If codec specifically needs transcoding, go directly to local player if (codecInfo.needsTranscoding) { console.log('[FormatDetector] Codec needs transcoding, using local player'); return createLocalPlayerFormat(video, extension); } // Tier 1: Native browser support (direct streaming) if (NATIVE_SUPPORTED_FORMATS.includes(extension)) { console.log('[FormatDetector] Native format detected:', extension); return createDirectFormat(video, extension); } // Tier 1.5: MPEG Transport Stream files (optimal for HLS) if (TS_STREAM_FORMATS.includes(extension)) { console.log('[FormatDetector] TS format detected, using HLS streaming:', extension); return createTSHLSFormat(video, extension); } // Tier 2: HLS compatible formats - try direct first, HLS as backup if (HLS_COMPATIBLE_FORMATS.includes(extension)) { console.log('[FormatDetector] HLS compatible format detected:', extension); // For now, try direct streaming first, HLS as fallback // In future, we could detect if HLS is actually needed return createDirectFormat(video, extension); } // TRANSCODING DISABLED: All other formats go directly to local player // No more fallback to transcoding system console.log('[FormatDetector] Using local player for format:', extension); // Check if this is a format from LIMITED_SUPPORT_FORMATS if (LIMITED_SUPPORT_FORMATS.includes(extension)) { console.log('[FormatDetector] Format in LIMITED_SUPPORT_FORMATS, forcing local player'); } return createLocalPlayerFormat(video, extension); } /** * Get file extension from path */ function getFileExtension(path: string): string { const basename = path.split('/').pop() || ''; const parts = basename.split('.'); return parts.length > 1 ? parts[parts.length - 1] : ''; } /** * Parse codec information from video metadata */ function parseCodecInfo(codecInfo?: string): { needsTranscoding: boolean } { if (!codecInfo) return { needsTranscoding: false }; try { const info = JSON.parse(codecInfo); return { needsTranscoding: info.needsTranscoding || false }; } catch { return { needsTranscoding: false }; } } /** * Create direct streaming format configuration */ function createDirectFormat(video: VideoFile, extension: string): VideoFormat { const mimeType = getMimeType(extension); return { type: 'direct', supportLevel: 'native', url: `/api/stream/direct/${video.id}`, mimeType, qualities: [ { html: 'Auto', url: `/api/stream/direct/${video.id}`, default: true } ] }; } /** * Create HLS streaming format configuration */ function createHLSFormat(video: VideoFile, extension: string): VideoFormat { return { type: 'hls', supportLevel: 'hls', url: `/api/stream/hls/${video.id}/playlist.m3u8`, qualities: [ { html: 'Auto', url: `/api/stream/hls/${video.id}/playlist.m3u8`, default: true }, { html: '1080p', url: `/api/stream/hls/${video.id}/playlist.m3u8?quality=1080` }, { html: '720p', url: `/api/stream/hls/${video.id}/playlist.m3u8?quality=720` }, { html: '480p', url: `/api/stream/hls/${video.id}/playlist.m3u8?quality=480` } ] }; } /** * Create HLS format configuration for MPEG Transport Stream files * .ts files are already in HLS-compatible format, so we use HLS streaming */ function createTSHLSFormat(video: VideoFile, extension: string): VideoFormat { return { type: 'hls', supportLevel: 'hls', url: `/api/stream/hls/${video.id}/playlist.m3u8`, qualities: [ { html: 'Auto (HLS)', url: `/api/stream/hls/${video.id}/playlist.m3u8`, default: true }, { html: 'Direct Stream', url: `/api/stream/direct/${video.id}` } ] }; } /** * Create direct TS format configuration for MPEG Transport Stream files * Alternative approach - serve .ts directly (may not work in all browsers) */ function createTSDirectFormat(video: VideoFile, extension: string): VideoFormat { const mimeType = getMimeType(extension); return { type: 'direct', supportLevel: 'native', url: `/api/stream/direct/${video.id}`, mimeType, qualities: [ { html: 'Original (TS)', url: `/api/stream/direct/${video.id}`, default: true } ] }; } /** * Create local player format configuration (replaces transcoding fallback) */ function createLocalPlayerFormat(video: VideoFile, extension: string): VideoFormat { const contentType = getMimeType(extension); // Use the optimized external streaming endpoint const baseUrl = `/api/external-stream/${video.id}`; return { type: 'local-player', supportLevel: 'local-player-required', url: baseUrl, // Optimized endpoint for external players action: 'launch-local-player', warning: 'This format requires a local video player', recommendedPlayers: getRecommendedPlayersForFormat(extension), streamInfo: { contentType, acceptRanges: 'bytes', supportsSeek: true, authentication: 'none' }, qualities: [ { html: 'Original', url: baseUrl, default: true } ] }; } /** * Get recommended players based on format and platform */ function getRecommendedPlayersForFormat(extension: string): string[] { const players: string[] = ['vlc']; // VLC is always recommended as it's cross-platform // Platform-specific recommendations if (typeof window !== 'undefined') { const userAgent = window.navigator.userAgent.toLowerCase(); const isMac = userAgent.includes('mac'); const isWindows = userAgent.includes('win'); const isLinux = userAgent.includes('linux'); if (isMac) { players.push('iina', 'elmedia'); } else if (isWindows) { players.push('potplayer'); } } else { // Server-side: recommend all major players players.push('iina', 'elmedia', 'potplayer'); } return players; } /** * Get MIME type for file extension */ function getMimeType(extension: string): string { const mimeTypes: Record = { 'mp4': 'video/mp4', 'webm': 'video/webm', 'ogg': 'video/ogg', 'ogv': 'video/ogg', 'm4v': 'video/x-m4v', 'ts': 'video/mp2t', 'm2ts': 'video/mp2t', 'mts': 'video/mp2t' }; return mimeTypes[extension] || 'video/mp4'; // Default fallback } /** * Check if format is supported by ArtPlayer natively */ export function isNativeSupported(format: VideoFormat): boolean { return format.supportLevel === 'native'; } /** * Check if format requires HLS streaming */ export function requiresHLS(format: VideoFormat): boolean { return format.type === 'hls'; } /** * Check if format requires local player (replaces transcoding fallback) */ export function requiresLocalPlayer(format: VideoFormat): boolean { return format.type === 'local-player'; } /** * Check if format requires fallback to transcoding (DEPRECATED - transcoding removed) */ export function requiresFallback(format: VideoFormat): boolean { // TRANSCODING DISABLED: This function is deprecated // All formats now either work natively or require local player return false; } /** * ArtPlayer is now the only player used throughout the application * This function is kept for backward compatibility but always returns 'artplayer' */ export function getOptimalPlayerType(format: VideoFormat): 'artplayer' { // Always return ArtPlayer since it's the only player we use now return 'artplayer'; }