Spaces:
Running
on
A100
Running
on
A100
| import React from 'react'; | |
| import { useTranscriptionStore } from '../stores/transcriptionStore'; | |
| import { generateWebVTT } from '../utils/subtitleUtils'; | |
| import {LANGUAGE_MAP} from '../utils/languages'; | |
| interface MediaPlayerProps { | |
| audioRef: React.RefObject<HTMLAudioElement>; | |
| videoRef: React.RefObject<HTMLVideoElement>; | |
| onTimeUpdate?: () => void; | |
| } | |
| export default function MediaPlayer({ | |
| audioRef, | |
| videoRef, | |
| onTimeUpdate, | |
| }: MediaPlayerProps) { | |
| const { | |
| file, | |
| mediaUrl, | |
| isVideoFile, | |
| currentSegments, | |
| selectedLanguage, | |
| setCurrentTime | |
| } = useTranscriptionStore(); | |
| const handleSeeked = (event: React.SyntheticEvent<HTMLMediaElement>) => { | |
| const target = event.target as HTMLMediaElement; | |
| setCurrentTime(target.currentTime); | |
| // Call onTimeUpdate to trigger segment selection logic | |
| if (onTimeUpdate) { | |
| onTimeUpdate(); | |
| } | |
| }; | |
| const handleLoadedMetadata = (event: React.SyntheticEvent<HTMLMediaElement>) => { | |
| const target = event.target as HTMLMediaElement; | |
| setCurrentTime(target.currentTime); | |
| // Call onTimeUpdate to trigger segment selection logic | |
| if (onTimeUpdate) { | |
| onTimeUpdate(); | |
| } | |
| }; | |
| // Helper function to encode UTF-8 string to base64 | |
| const utf8ToBase64 = (str: string): string => { | |
| // Convert string to UTF-8 bytes, then to base64 | |
| const encoder = new TextEncoder(); | |
| const bytes = encoder.encode(str); | |
| let binary = ''; | |
| bytes.forEach(byte => binary += String.fromCharCode(byte)); | |
| return btoa(binary); | |
| }; | |
| // Get language info for subtitles | |
| const getLanguageInfo = () => { | |
| if (!selectedLanguage) { | |
| return { code: 'en', name: 'English' }; | |
| } | |
| const languageName = (LANGUAGE_MAP as Record<string, string>)[selectedLanguage]; | |
| return { | |
| code: selectedLanguage, | |
| name: languageName || 'Unknown' | |
| }; | |
| }; | |
| // Early return if no file is selected | |
| if (!file) { | |
| return null; | |
| } | |
| // Early return if no media URL is available | |
| if (!mediaUrl) { | |
| return ( | |
| <div className="p-6 bg-gray-800"> | |
| <div className="max-w-4xl mx-auto text-center text-gray-300"> | |
| Loading media... | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="p-6 bg-gray-800"> | |
| <div className="max-w-4xl mx-auto"> | |
| {isVideoFile ? ( | |
| <video | |
| ref={videoRef} | |
| src={mediaUrl || ""} | |
| className="w-full max-h-96 rounded-lg" | |
| onSeeked={handleSeeked} | |
| onLoadedMetadata={handleLoadedMetadata} | |
| controls | |
| controlsList="nodownload nofullscreen noremoteplayback" | |
| disablePictureInPicture | |
| > | |
| {currentSegments && currentSegments.length > 0 && (() => { | |
| const { code, name } = getLanguageInfo(); | |
| return ( | |
| <track | |
| kind="subtitles" | |
| src={`data:text/vtt;base64,${utf8ToBase64(generateWebVTT(currentSegments))}`} | |
| srcLang={code} | |
| label={name} | |
| default | |
| /> | |
| ); | |
| })()} | |
| </video> | |
| ) : ( | |
| <div className="bg-gray-700 p-8 rounded-lg"> | |
| <audio | |
| ref={audioRef} | |
| src={mediaUrl || ""} | |
| className="w-full" | |
| onSeeked={handleSeeked} | |
| onLoadedMetadata={handleLoadedMetadata} | |
| controls | |
| controlsList="nodownload" | |
| /> | |
| <div className="mt-4 text-center text-gray-300"> | |
| <div className="text-lg font-medium">Audio File</div> | |
| <div className="text-sm">{file.name}</div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |