diff --git a/backend/__pycache__/api_routes.cpython-314.pyc b/backend/__pycache__/api_routes.cpython-314.pyc index 78e5b27..ee62c8f 100644 Binary files a/backend/__pycache__/api_routes.cpython-314.pyc and b/backend/__pycache__/api_routes.cpython-314.pyc differ diff --git a/backend/api_routes.py b/backend/api_routes.py index 4575134..7ad8841 100644 --- a/backend/api_routes.py +++ b/backend/api_routes.py @@ -445,6 +445,10 @@ def api_get_post(post_id: int): row = get_audio_post_by_id(post_id) if not row: return _error("Post not found.", 404) + + # CRITICAL: Add audio URL to the response + row = _add_audio_url(row) + return jsonify(row) diff --git a/backend/uploads/2b06e3ac-4366-45c9-bfb5-cf7856392a51_data.m4a b/backend/uploads/2b06e3ac-4366-45c9-bfb5-cf7856392a51_data.m4a new file mode 100644 index 0000000..38e5740 Binary files /dev/null and b/backend/uploads/2b06e3ac-4366-45c9-bfb5-cf7856392a51_data.m4a differ diff --git a/frontend/src/api.js b/frontend/src/api.js index 9cbc904..2add92c 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -150,7 +150,7 @@ class ApiClient { } async getAudioUrl(postId, expiresIn = 3600) { - return this.request(`/posts/${postId}/audio?expires_in=${expiresIn}`); + return this.request(`/posts/${postId}/audio-url`); } // ==================== Post Files ==================== diff --git a/frontend/src/components/AudioPlayer.jsx b/frontend/src/components/AudioPlayer.jsx new file mode 100644 index 0000000..69265e6 --- /dev/null +++ b/frontend/src/components/AudioPlayer.jsx @@ -0,0 +1,139 @@ +import { Play, Pause, Volume2 } from 'lucide-react' +import { useState, useRef, useEffect } from 'react' + +export default function AudioPlayer({ src, onEnded, className = '' }) { + const [isPlaying, setIsPlaying] = useState(false) + const [currentTime, setCurrentTime] = useState(0) + const [duration, setDuration] = useState(0) + const [volume, setVolume] = useState(1) + const audioRef = useRef(null) + + useEffect(() => { + if (audioRef.current && src) { + audioRef.current.src = src + audioRef.current.load() + } + }, [src]) + + const formatTime = (seconds) => { + if (!seconds || isNaN(seconds)) return '0:00' + const mins = Math.floor(seconds / 60) + const secs = Math.floor(seconds % 60) + return `${mins}:${secs.toString().padStart(2, '0')}` + } + + const togglePlay = () => { + if (!audioRef.current || !src) return + + if (isPlaying) { + audioRef.current.pause() + } else { + audioRef.current.play() + } + setIsPlaying(!isPlaying) + } + + const handleTimeUpdate = () => { + if (audioRef.current) { + setCurrentTime(audioRef.current.currentTime) + } + } + + const handleLoadedMetadata = () => { + if (audioRef.current) { + setDuration(audioRef.current.duration) + } + } + + const handleSeek = (e) => { + const rect = e.currentTarget.getBoundingClientRect() + const x = e.clientX - rect.left + const percentage = x / rect.width + const newTime = percentage * duration + + if (audioRef.current) { + audioRef.current.currentTime = newTime + setCurrentTime(newTime) + } + } + + const handleVolumeChange = (e) => { + const newVolume = parseFloat(e.target.value) + setVolume(newVolume) + if (audioRef.current) { + audioRef.current.volume = newVolume + } + } + + const handleEnded = () => { + setIsPlaying(false) + setCurrentTime(0) + if (onEnded) onEnded() + } + + const handleAudioError = (e) => { + console.error('Audio error:', e) + console.error('Audio element error:', audioRef.current?.error) + } + + const progress = duration > 0 ? (currentTime / duration) * 100 : 0 + + if (!src) return null + + return ( +
+
{/* Audio Player */} - {post.status === 'ready' && ( + {post.status === 'ready' && post.audio_url && (

Audio Player

@@ -303,50 +303,64 @@ useEffect(() => { onTimeUpdate={handleTimeUpdate} onLoadedMetadata={handleLoadedMetadata} onEnded={handleEnded} + onError={handleAudioError} + onCanPlay={() => console.log('Audio can play')} preload="metadata" + crossOrigin="anonymous" /> -
- - -
-
+
+
-
- {formatTime(currentTime)} - {formatTime(duration)} -
-
+ {isPlaying ? ( + + ) : ( + + )} + -
- - +
+
+
+
+
+ {formatTime(currentTime)} + {formatTime(duration)} +
+
+ +
+ + +
+ + {!post.audio_url && ( +

+ Audio file not available +

+ )}
)} diff --git a/frontend/src/pages/Search.jsx b/frontend/src/pages/Search.jsx index 831e7bb..c0ef850 100644 --- a/frontend/src/pages/Search.jsx +++ b/frontend/src/pages/Search.jsx @@ -169,14 +169,7 @@ export default function Search({ user, initialQuery = '', onViewPost}) { )} - {result.confidence && ( - <> - - - {Math.round(result.confidence * 100)}% confidence - - - )} +