157 lines
4.0 KiB
TypeScript
157 lines
4.0 KiB
TypeScript
/**
|
|
* Video utilities for handling YouTube embeds and video URLs
|
|
*/
|
|
|
|
export type VideoPosition = 'top' | 'inline' | 'bottom' | 'none';
|
|
export type VideoPlatform = 'youtube' | 'vimeo' | 'dailymotion' | 'twitch' | 'uploaded' | 'external';
|
|
|
|
/**
|
|
* Extract YouTube video ID from various URL formats
|
|
* Supported formats:
|
|
* - https://www.youtube.com/watch?v=VIDEO_ID
|
|
* - https://youtu.be/VIDEO_ID
|
|
* - https://www.youtube.com/embed/VIDEO_ID
|
|
* - https://www.youtube.com/v/VIDEO_ID
|
|
* - https://www.youtube.com/shorts/VIDEO_ID
|
|
*/
|
|
export function extractYouTubeVideoId(url: string): string | null {
|
|
if (!url) return null;
|
|
|
|
const patterns = [
|
|
// Standard watch URL
|
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
|
|
// Just the video ID
|
|
/^([a-zA-Z0-9_-]{11})$/,
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = url.match(pattern);
|
|
if (match && match[1]) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if a URL is a valid YouTube URL
|
|
*/
|
|
export function isValidYouTubeUrl(url: string): boolean {
|
|
return extractYouTubeVideoId(url) !== null;
|
|
}
|
|
|
|
/**
|
|
* Get YouTube thumbnail URL for a video ID
|
|
* Quality options: default, mq (medium), hq (high), sd (standard), maxres (maximum resolution)
|
|
*/
|
|
export function getYouTubeThumbnail(
|
|
videoId: string,
|
|
quality: 'default' | 'mq' | 'hq' | 'sd' | 'maxres' = 'hq'
|
|
): string {
|
|
const qualityMap = {
|
|
default: 'default.jpg',
|
|
mq: 'mqdefault.jpg',
|
|
hq: 'hqdefault.jpg',
|
|
sd: 'sddefault.jpg',
|
|
maxres: 'maxresdefault.jpg',
|
|
};
|
|
|
|
return `https://img.youtube.com/vi/${videoId}/${qualityMap[quality]}`;
|
|
}
|
|
|
|
/**
|
|
* Detect video platform from URL
|
|
*/
|
|
export function detectVideoPlatform(url: string): VideoPlatform {
|
|
if (!url) return 'external';
|
|
|
|
if (url.includes('youtube.com') || url.includes('youtu.be')) return 'youtube';
|
|
if (url.includes('vimeo.com')) return 'vimeo';
|
|
if (url.includes('dailymotion.com')) return 'dailymotion';
|
|
if (url.includes('twitch.tv')) return 'twitch';
|
|
if (url.match(/\.(mp4|webm|ogg|mov|avi|wmv|flv|mkv)$/i)) return 'uploaded';
|
|
|
|
return 'external';
|
|
}
|
|
|
|
/**
|
|
* Generate YouTube embed URL with privacy-enhanced mode and parameters
|
|
*/
|
|
export function generateYouTubeEmbedUrl(
|
|
videoId: string,
|
|
options: {
|
|
autoplay?: boolean;
|
|
controls?: boolean;
|
|
modestbranding?: boolean;
|
|
rel?: boolean;
|
|
playsinline?: boolean;
|
|
start?: number;
|
|
end?: number;
|
|
} = {}
|
|
): string {
|
|
const {
|
|
autoplay = false,
|
|
controls = true,
|
|
modestbranding = true,
|
|
rel = false,
|
|
playsinline = true,
|
|
start,
|
|
end,
|
|
} = options;
|
|
|
|
const params = new URLSearchParams({
|
|
autoplay: autoplay ? '1' : '0',
|
|
controls: controls ? '1' : '0',
|
|
modestbranding: modestbranding ? '1' : '0',
|
|
rel: rel ? '1' : '0',
|
|
playsinline: playsinline ? '1' : '0',
|
|
});
|
|
|
|
if (start !== undefined) params.set('start', start.toString());
|
|
if (end !== undefined) params.set('end', end.toString());
|
|
|
|
// Use privacy-enhanced mode (youtube-nocookie.com)
|
|
return `https://www.youtube-nocookie.com/embed/${videoId}?${params.toString()}`;
|
|
}
|
|
|
|
/**
|
|
* Get CSS classes for video positioning
|
|
*/
|
|
export function getVideoPositionClasses(position: VideoPosition): string {
|
|
switch (position) {
|
|
case 'top':
|
|
return 'mb-8';
|
|
case 'inline':
|
|
return 'my-6';
|
|
case 'bottom':
|
|
return 'mt-8';
|
|
case 'none':
|
|
return '';
|
|
default:
|
|
return 'my-6';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate video URL
|
|
*/
|
|
export function validateVideoUrl(url: string): { isValid: boolean; platform: VideoPlatform; error?: string } {
|
|
if (!url) {
|
|
return { isValid: false, platform: 'external', error: 'Video URL is required' };
|
|
}
|
|
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
return { isValid: false, platform: 'external', error: 'Invalid URL format' };
|
|
}
|
|
|
|
const platform = detectVideoPlatform(url);
|
|
|
|
if (platform === 'youtube' && !isValidYouTubeUrl(url)) {
|
|
return { isValid: false, platform: 'youtube', error: 'Invalid YouTube URL' };
|
|
}
|
|
|
|
return { isValid: true, platform };
|
|
} |