jeanma's picture
Omnilingual ASR transcription demo
ae238b3 verified
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>
);
}