.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +96,9 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
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<VideoElementProps> = ({
|
||||
|
||||
// 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<VideoElementProps> = ({
|
||||
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<VideoElementProps> = ({
|
||||
}
|
||||
})
|
||||
)
|
||||
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<VideoElementProps> = ({
|
||||
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<VideoElementProps> = ({
|
||||
|
||||
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<VideoElementProps> = ({
|
||||
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<VideoElementProps> = ({
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user