1- import React , { useEffect , useRef } from "react"
1+ import React , { useEffect , useRef , useState } from "react"
22import videojs from "video.js"
33import type Player from "video.js/dist/types/player"
44import "video.js/dist/video-js.css"
@@ -8,12 +8,88 @@ interface VideoJsPlayerProps {
88 caption ?: string
99}
1010
11+ const getVideoType = ( url : string ) : string => {
12+ const lowerUrl = url . toLowerCase ( )
13+ if ( lowerUrl . endsWith ( ".m3u8" ) ) {
14+ return "application/x-mpegURL"
15+ }
16+ if ( lowerUrl . endsWith ( ".mp4" ) ) {
17+ return "video/mp4"
18+ }
19+ // Default to mp4 for other cases
20+ return "video/mp4"
21+ }
22+
23+ const deriveMediaUrls = (
24+ m3u8Url : string ,
25+ ) : { subtitlesUrl : string ; posterUrl : string } | null => {
26+ try {
27+ // Extract directory and filename
28+ const lastSlashIndex = m3u8Url . lastIndexOf ( "/" )
29+ if ( lastSlashIndex === - 1 ) return null
30+
31+ const directory = m3u8Url . substring ( 0 , lastSlashIndex )
32+ const filename = m3u8Url . substring ( lastSlashIndex + 1 )
33+
34+ // Extract base name (remove .5M.m3u8 or similar pattern)
35+ // Pattern: video_HLS1.5M.m3u8 -> video_HLS1
36+ const baseMatch = filename . match ( / ^ ( .+ ?) (?: \. \d + [ M K ] ) ? \. m 3 u 8 $ / )
37+ if ( ! baseMatch ) return null
38+
39+ const baseName = baseMatch [ 1 ]
40+
41+ return {
42+ subtitlesUrl : `${ directory } /${ baseName } .srt` ,
43+ posterUrl : `${ directory } /${ baseName } _cover.jpeg` ,
44+ }
45+ } catch {
46+ return null
47+ }
48+ }
49+
50+ const checkUrlExists = async ( url : string ) : Promise < boolean > => {
51+ try {
52+ const response = await fetch ( url , { method : "HEAD" } )
53+ return response . ok
54+ } catch {
55+ return false
56+ }
57+ }
58+
1159export const VideoJsPlayer : React . FC < VideoJsPlayerProps > = ( {
1260 src,
1361 caption,
1462} ) => {
1563 const videoRef = useRef < HTMLDivElement > ( null )
1664 const playerRef = useRef < Player | null > ( null )
65+ const [ posterUrl , setPosterUrl ] = useState < string | undefined > ( )
66+ const [ subtitlesUrl , setSubtitlesUrl ] = useState < string | undefined > ( )
67+
68+ // Check for related media files (poster and subtitles) for m3u8 videos
69+ useEffect ( ( ) => {
70+ const loadRelatedMedia = async ( ) => {
71+ if ( ! src . toLowerCase ( ) . endsWith ( ".m3u8" ) ) {
72+ return
73+ }
74+
75+ const derivedUrls = deriveMediaUrls ( src )
76+ if ( ! derivedUrls ) return
77+ console . log ( "Derived URLs:" , derivedUrls )
78+ // Check if poster exists
79+ const posterExists = await checkUrlExists ( derivedUrls . posterUrl )
80+ if ( posterExists ) {
81+ setPosterUrl ( derivedUrls . posterUrl )
82+ }
83+
84+ // Check if subtitles exist
85+ const subtitlesExist = await checkUrlExists ( derivedUrls . subtitlesUrl )
86+ if ( subtitlesExist ) {
87+ setSubtitlesUrl ( derivedUrls . subtitlesUrl )
88+ }
89+ }
90+
91+ loadRelatedMedia ( )
92+ } , [ src ] )
1793
1894 useEffect ( ( ) => {
1995 // Make sure Video.js player is only initialized once
@@ -27,7 +103,9 @@ export const VideoJsPlayer: React.FC<VideoJsPlayerProps> = ({
27103 controls : true ,
28104 responsive : true ,
29105 fluid : true ,
106+ aspectRatio : "16:9" ,
30107 preload : "auto" ,
108+ poster : posterUrl ,
31109 html5 : {
32110 vhs : {
33111 // Enable HLS.js integration
@@ -37,18 +115,32 @@ export const VideoJsPlayer: React.FC<VideoJsPlayerProps> = ({
37115 sources : [
38116 {
39117 src,
40- type : "application/x-mpegURL" ,
118+ type : getVideoType ( src ) ,
41119 } ,
42120 ] ,
43121 } ) )
44122
123+ // Add subtitles track if available
124+ if ( subtitlesUrl ) {
125+ player . addRemoteTextTrack (
126+ {
127+ kind : "captions" ,
128+ src : subtitlesUrl ,
129+ srclang : "en" ,
130+ label : "English" ,
131+ default : true ,
132+ } ,
133+ false ,
134+ )
135+ }
136+
45137 // Error handling
46138 player . on ( "error" , ( ) => {
47139 const error = player . error ( )
48140 console . error ( "Video.js error:" , error )
49141 } )
50142 }
51- } , [ src ] )
143+ } , [ src , posterUrl , subtitlesUrl ] )
52144
53145 // Dispose the Video.js player when the component unmounts
54146 useEffect ( ( ) => {
0 commit comments