From ad0c32785bda93d18b36c79816254f716aad2f75 Mon Sep 17 00:00:00 2001 From: hibna Date: Wed, 29 Oct 2025 14:37:33 +0300 Subject: [PATCH] . --- src/components/VideoElement.css | 94 +++++++++++++++++++++++--- src/components/VideoElement.tsx | 115 +++++++++++++++++++++++++------- 2 files changed, 176 insertions(+), 33 deletions(-) diff --git a/src/components/VideoElement.css b/src/components/VideoElement.css index f0a94ff..d0e4bcd 100644 --- a/src/components/VideoElement.css +++ b/src/components/VideoElement.css @@ -25,24 +25,100 @@ display: none !important; } -/* Subtitle styling */ +/* Modern Subtitle Styling */ .video-element::cue { - background-color: rgba(0, 0, 0, 0.8); - color: white; - font-size: 1.2em; - font-family: Arial, sans-serif; + /* Typography */ + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-size: 1.75rem; + font-weight: 700; line-height: 1.4; - padding: 0.2em 0.5em; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9); + letter-spacing: 0.5px; + + /* Colors - No background, only text with strong shadow */ + color: #ffffff; + background-color: transparent; + + /* Visual Effects - Strong shadow for readability */ + text-shadow: + /* Strong black outline */ + -2px -2px 0 #000, + 2px -2px 0 #000, + -2px 2px 0 #000, + 2px 2px 0 #000, + 0 -2px 0 #000, + 0 2px 0 #000, + -2px 0 0 #000, + 2px 0 0 #000, + /* Additional shadow for depth */ + 0 4px 8px rgba(0, 0, 0, 0.9), + 0 0 12px rgba(0, 0, 0, 0.8); + + /* Better rendering */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } -/* Ensure text tracks are visible */ +/* Fullscreen subtitle adjustments */ +:fullscreen .video-element::cue, +.video-element:fullscreen::cue { + font-size: 2.25rem; +} + +/* Ensure text tracks are properly positioned - above controls */ .video-element::-webkit-media-text-track-container { overflow: visible !important; - position: relative !important; + position: absolute !important; + bottom: 0 !important; + left: 0 !important; + right: 0 !important; z-index: 1 !important; + display: flex !important; + flex-direction: column !important; + justify-content: flex-end !important; + align-items: center !important; + /* Position above controls bar (controls bar is ~120px high with padding) */ + padding-bottom: 130px !important; + pointer-events: none !important; } .video-element::-webkit-media-text-track-display { overflow: visible !important; + width: 100% !important; + max-width: 85% !important; + text-align: center !important; +} + +/* Multi-line subtitle support */ +.video-element::cue-region { + width: 85%; +} + +/* Better contrast for different cue types */ +.video-element::cue(b) { + font-weight: 900; +} + +.video-element::cue(i) { + font-style: italic; +} + +.video-element::cue(u) { + text-decoration: underline; +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + .video-element::cue { + font-size: 1.25rem; + } + + .video-element::-webkit-media-text-track-container { + padding-bottom: 110px !important; + } + + :fullscreen .video-element::cue, + .video-element:fullscreen::cue { + font-size: 1.75rem; + } } diff --git a/src/components/VideoElement.tsx b/src/components/VideoElement.tsx index 4d16834..ea6ac8d 100644 --- a/src/components/VideoElement.tsx +++ b/src/components/VideoElement.tsx @@ -96,15 +96,9 @@ export const VideoElement: React.FC = ({ if (tracks && processedSubtitles.length > 0) { const defaultSubtitle = processedSubtitles.find((sub) => sub.default) if (defaultSubtitle) { - // Find the corresponding track and set it as showing - for (let i = 0; i < tracks.length; i++) { - const track = tracks[i] - if (track.language === defaultSubtitle.lang) { - track.mode = 'showing' - setSubtitle(defaultSubtitle) - break - } - } + console.log(`🎯 Found default subtitle in metadata: ${defaultSubtitle.label}`) + // Set subtitle in context (this will trigger the useEffect that enables it) + setSubtitle(defaultSubtitle) } } @@ -218,6 +212,8 @@ export const VideoElement: React.FC = ({ // Process subtitles - convert SRT to VTT blob URLs useEffect(() => { + let cancelled = false + // Clean up old blob URLs subtitleBlobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)) subtitleBlobUrlsRef.current = [] @@ -239,8 +235,17 @@ export const VideoElement: React.FC = ({ throw new Error(`Failed to fetch subtitle: ${response.status} ${response.statusText}`) } const srtContent = await response.text() + console.log(`SRT content length: ${srtContent.length} chars`) + const blobUrl = createSubtitleBlobURL(srtContent, 'srt') subtitleBlobUrlsRef.current.push(blobUrl) + + // Debug: fetch the blob URL to verify VTT content + const vttResponse = await fetch(blobUrl) + const vttContent = await vttResponse.text() + console.log(`VTT content preview (first 500 chars):`, vttContent.substring(0, 500)) + console.log(`Total VTT length: ${vttContent.length} chars`) + console.log(`Processed SRT subtitle "${subtitle.label}": ${subtitle.src} -> ${blobUrl}`) return { ...subtitle, src: blobUrl } } @@ -253,13 +258,18 @@ export const VideoElement: React.FC = ({ } }) ) - setProcessedSubtitles(processed) + + // Only update state if not cancelled + if (!cancelled) { + setProcessedSubtitles(processed) + } } processSubtitles() // Cleanup function return () => { + cancelled = true subtitleBlobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)) subtitleBlobUrlsRef.current = [] } @@ -475,20 +485,50 @@ export const VideoElement: React.FC = ({ const tracks = video.textTracks if (!tracks || tracks.length === 0) return - // Disable all tracks first - for (let i = 0; i < tracks.length; i++) { - tracks[i].mode = 'hidden' + const enableSubtitle = () => { + // Disable all tracks first + for (let i = 0; i < tracks.length; i++) { + tracks[i].mode = 'hidden' + } + + // Enable the selected subtitle track + if (settings.subtitle) { + for (let i = 0; i < tracks.length; i++) { + const track = tracks[i] + if (track.language === settings.subtitle.lang) { + // Wait for track to have cues before showing + if (track.cues && track.cues.length > 0) { + track.mode = 'showing' + console.log(`🔊 Enabled subtitle track: ${track.label} (${track.language})`) + console.log(` - cues available: ${track.cues.length}`) + console.log(` - track.mode: ${track.mode}`) + } else { + console.warn(`⚠️ Track ${track.label} has no cues yet, waiting...`) + // Track not ready yet, will be handled by load event + track.mode = 'showing' + } + break + } + } + } } - // Enable the selected subtitle track - if (settings.subtitle) { + // Try to enable immediately + enableSubtitle() + + // Also listen for track load events to retry + const handleTrackChange = () => { + console.log(`🔄 Track changed, re-enabling subtitle`) + enableSubtitle() + } + + for (let i = 0; i < tracks.length; i++) { + tracks[i].addEventListener('load', handleTrackChange) + } + + return () => { for (let i = 0; i < tracks.length; i++) { - const track = tracks[i] - if (track.language === settings.subtitle.lang) { - track.mode = 'showing' - console.log(`Enabled subtitle track: ${track.label} (${track.language})`) - break - } + tracks[i].removeEventListener('load', handleTrackChange) } } }, [settings.subtitle, videoRef]) @@ -500,12 +540,26 @@ export const VideoElement: React.FC = ({ const handleTrackLoad = (e: Event) => { const track = e.target as HTMLTrackElement - console.log(`Track loaded: ${track.label} (${track.srclang})`, track.readyState) + const textTrack = track.track + console.log(`✅ Track loaded: ${track.label} (${track.srclang})`) + console.log(` - readyState: ${track.readyState}`) + console.log(` - track.mode: ${textTrack.mode}`) + console.log(` - track.cues: ${textTrack.cues?.length || 0}`) + console.log(` - src: ${track.src}`) + + // Log first few cues if available + if (textTrack.cues && textTrack.cues.length > 0) { + console.log(` - First cue: ${(textTrack.cues[0] as VTTCue).startTime}s - ${(textTrack.cues[0] as VTTCue).endTime}s: "${(textTrack.cues[0] as VTTCue).text}"`) + } else { + console.warn(` ⚠️ No cues found in track!`) + } } const handleTrackError = (e: Event) => { const track = e.target as HTMLTrackElement - console.error(`Track error: ${track.label} (${track.srclang})`, track.track.cues?.length) + console.error(`❌ Track error: ${track.label} (${track.srclang})`) + console.error(` - src: ${track.src}`) + console.error(` - readyState: ${track.readyState}`) } const trackElements = video.querySelectorAll('track') @@ -520,7 +574,11 @@ export const VideoElement: React.FC = ({ for (let i = 0; i < textTracks.length; i++) { const track = textTracks[i] if (track.mode === 'showing') { - console.log(`Active track: ${track.label}, cues: ${track.cues?.length || 0}, active cues: ${track.activeCues?.length || 0}`) + console.log(`🎬 Cuechange: ${track.label}, cues: ${track.cues?.length || 0}, active cues: ${track.activeCues?.length || 0}`) + if (track.activeCues && track.activeCues.length > 0) { + const cue = track.activeCues[0] as VTTCue + console.log(` - Active cue text: "${cue.text}"`) + } } } } @@ -529,6 +587,15 @@ export const VideoElement: React.FC = ({ textTracks[i].addEventListener('cuechange', handleCueChange) } + // Log all text tracks after a delay to see their state + setTimeout(() => { + console.log(`📊 Text tracks summary (${textTracks.length} total):`) + for (let i = 0; i < textTracks.length; i++) { + const track = textTracks[i] + console.log(` [${i}] ${track.label} (${track.language}): mode=${track.mode}, cues=${track.cues?.length || 0}`) + } + }, 1000) + return () => { trackElements.forEach((track) => { track.removeEventListener('load', handleTrackLoad)