UNPKG

react-edge-tts

Version:

Generate text-to-speech narration for React content using Microsoft Edge TTS

131 lines 6.99 kB
import React, { useEffect, useRef, useState, useCallback } from 'react'; import { Play, Pause, Volume2, Volume1, VolumeX } from 'lucide-react'; import '../styles/NarrationPlayer.css'; const defaultTheme = { primary: '#2563eb', secondary: '#94a3b8', background: '#f8fafc', text: '#1e293b', }; const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; export const NarrationPlayer = ({ title, src, className = '', style = {}, showTime = true, showVolume = true, defaultVolume = 1, theme = defaultTheme, metadata, onPlay, onPause, onEnded, onError, }) => { const audioRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [volume, setVolume] = useState(defaultVolume); const audioSrc = src || `narration-audio/${title}.mp3`; // MediaSession API setup useEffect(() => { if ('mediaSession' in navigator) { navigator.mediaSession.metadata = new MediaMetadata({ title: (metadata === null || metadata === void 0 ? void 0 : metadata.title) || `Narration: ${title}`, artist: (metadata === null || metadata === void 0 ? void 0 : metadata.artist) || 'React Edge TTS', album: metadata === null || metadata === void 0 ? void 0 : metadata.album, artwork: (metadata === null || metadata === void 0 ? void 0 : metadata.artwork) || [ { src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="%232563eb" d="M12 3v9.28a4.39 4.39 0 0 0-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/%3E%3C/svg%3E', sizes: '96x96', type: 'image/svg+xml', }, ], }); navigator.mediaSession.setActionHandler('play', () => { var _a; return (_a = audioRef.current) === null || _a === void 0 ? void 0 : _a.play(); }); navigator.mediaSession.setActionHandler('pause', () => { var _a; return (_a = audioRef.current) === null || _a === void 0 ? void 0 : _a.pause(); }); navigator.mediaSession.setActionHandler('seekto', (details) => { if (details.seekTime && audioRef.current) { audioRef.current.currentTime = details.seekTime; } }); } }, [title, metadata]); // Audio event handlers const handlePlay = useCallback(() => { setIsPlaying(true); onPlay === null || onPlay === void 0 ? void 0 : onPlay(); navigator.mediaSession.playbackState = 'playing'; }, [onPlay]); const handlePause = useCallback(() => { setIsPlaying(false); onPause === null || onPause === void 0 ? void 0 : onPause(); navigator.mediaSession.playbackState = 'paused'; }, [onPause]); const handleTimeUpdate = useCallback(() => { if (audioRef.current) { setCurrentTime(audioRef.current.currentTime); navigator.mediaSession.setPositionState({ duration: audioRef.current.duration, playbackRate: audioRef.current.playbackRate, position: audioRef.current.currentTime, }); } }, []); const handleLoadedMetadata = useCallback(() => { if (audioRef.current) { setDuration(audioRef.current.duration); } }, []); const handleVolumeChange = useCallback((e) => { const newVolume = parseFloat(e.target.value); setVolume(newVolume); if (audioRef.current) { audioRef.current.volume = newVolume; } }, []); const handleSeek = useCallback((e) => { const time = parseFloat(e.target.value); if (audioRef.current) { audioRef.current.currentTime = time; setCurrentTime(time); } }, []); const togglePlayPause = useCallback(() => { if (audioRef.current) { if (isPlaying) { audioRef.current.pause(); } else { audioRef.current.play(); } } }, [isPlaying]); const VolumeIcon = volume === 0 ? VolumeX : volume < 0.5 ? Volume1 : Volume2; return (React.createElement("div", { className: `react-edge-tts-player ${className}`, style: { padding: '1rem', borderRadius: '0.5rem', backgroundColor: theme.background, color: theme.text, ...style, } }, React.createElement("audio", { ref: audioRef, src: audioSrc, onPlay: handlePlay, onPause: handlePause, onEnded: onEnded, onError: (e) => onError === null || onError === void 0 ? void 0 : onError(new Error('Audio playback error')), onTimeUpdate: handleTimeUpdate, onLoadedMetadata: handleLoadedMetadata }), React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '1rem' } }, React.createElement("button", { onClick: togglePlayPause, style: { backgroundColor: theme.primary, color: 'white', border: 'none', borderRadius: '50%', width: '2.5rem', height: '2.5rem', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0.5rem', }, "aria-label": isPlaying ? 'Pause' : 'Play' }, isPlaying ? (React.createElement(Pause, { size: 20, "aria-hidden": "true" })) : (React.createElement(Play, { size: 20, "aria-hidden": "true" }))), React.createElement("div", { style: { flex: 1 } }, React.createElement("input", { type: "range", min: 0, max: duration, value: currentTime, onChange: handleSeek, style: { width: '100%', accentColor: theme.primary, } }), showTime && (React.createElement("div", { style: { display: 'flex', justifyContent: 'space-between', fontSize: '0.875rem' } }, React.createElement("span", null, formatTime(currentTime)), React.createElement("span", null, formatTime(duration))))), showVolume && (React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', minWidth: '100px' } }, React.createElement(VolumeIcon, { size: 20, style: { color: theme.text }, "aria-label": "Volume" }), React.createElement("input", { type: "range", min: 0, max: 1, step: 0.1, value: volume, onChange: handleVolumeChange, style: { accentColor: theme.primary } })))))); }; //# sourceMappingURL=NarrationPlayer.js.map