367 lines
10 KiB
TypeScript
367 lines
10 KiB
TypeScript
/**
|
|
* 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<string, string> = {
|
|
'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';
|
|
} |