video func implemented
This commit is contained in:
1 parent
3bc534489d
commit
89b8687431
13 files changed
+504
-9
No files matched your search
@@ -13,6 +13,9 @@ export interface Article {
|
||||
featuredImage: string;
|
||||
imagePosition: 'top' | 'left' | 'right' | 'none';
|
||||
imageSize: 'small' | 'medium' | 'large';
|
||||
videoUrl: string;
|
||||
videoPosition: 'top' | 'inline' | 'bottom' | 'none';
|
||||
videoCaption: string;
|
||||
tags: string[];
|
||||
status: 'draft' | 'published' | 'archived';
|
||||
views: number;
|
||||
@@ -131,6 +134,9 @@ export interface LiveBlog {
|
||||
featuredImage: string;
|
||||
imagePosition: 'top' | 'left' | 'right' | 'none';
|
||||
imageSize: 'small' | 'medium' | 'large';
|
||||
videoUrl: string;
|
||||
videoPosition: 'top' | 'inline' | 'bottom' | 'none';
|
||||
videoCaption: string;
|
||||
viewCount: number;
|
||||
author?: {
|
||||
id: string;
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
Reference in new issue
Block a user