- VideoPlayer: auto-detects YouTube/Vimeo embed or direct video file - PhotoCarousel: accepts videoUrl prop, shows video as first slide with play icon thumbnail - PropertyDetailPage: renders video in carousel (position=carousel) or as standalone section (position=section) - PriceBox: fix sticky offset for navbar height - PropertyForm: video URL input with live preview + position selector - AdminPropertiesPage: include video fields in edit initial data - types/property: add video_url and video_position to PropertyDetail - utils/getEmbedUrl: helper to resolve YouTube/Vimeo/direct URLs
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
export type VideoType = 'youtube' | 'vimeo' | 'direct' | 'unknown'
|
|
|
|
export interface EmbedResult {
|
|
type: VideoType
|
|
embedUrl: string | null
|
|
}
|
|
|
|
export function getEmbedUrl(url: string): EmbedResult {
|
|
const trimmed = url.trim()
|
|
if (!trimmed) return { type: 'unknown', embedUrl: null }
|
|
|
|
// YouTube — watch?v=, youtu.be/, embed/
|
|
const ytWatch = trimmed.match(
|
|
/(?:youtube\.com\/watch\?(?:.*&)?v=|youtu\.be\/)([A-Za-z0-9_-]{11})/
|
|
)
|
|
if (ytWatch) {
|
|
return { type: 'youtube', embedUrl: `https://www.youtube.com/embed/${ytWatch[1]}` }
|
|
}
|
|
if (/youtube\.com\/embed\/[A-Za-z0-9_-]{11}/.test(trimmed)) {
|
|
return { type: 'youtube', embedUrl: trimmed }
|
|
}
|
|
|
|
// Vimeo — vimeo.com/ID ou player.vimeo.com/video/ID
|
|
const vimeoMatch = trimmed.match(/vimeo\.com\/(?:video\/)?(\d+)/)
|
|
if (vimeoMatch) {
|
|
return { type: 'vimeo', embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}` }
|
|
}
|
|
if (/player\.vimeo\.com\/video\/\d+/.test(trimmed)) {
|
|
return { type: 'vimeo', embedUrl: trimmed }
|
|
}
|
|
|
|
// Arquivo direto de vídeo (.mp4 ou .webm)
|
|
if (/\.(mp4|webm)(\?.*)?$/i.test(trimmed)) {
|
|
return { type: 'direct', embedUrl: trimmed }
|
|
}
|
|
|
|
return { type: 'unknown', embedUrl: null }
|
|
}
|