{"version":3,"file":"useAudioPlayer-VYVEFRZN.js","sources":["../../../app/javascript/shared/useAudioPlayer.js"],"sourcesContent":["import { useState, useEffect, useRef, useCallback } from 'react';\nimport { getStorage, ref, getDownloadURL, deleteObject, uploadBytes } from \"firebase/storage\";\nimport { doc, getDoc, updateDoc } from \"firebase/firestore\";\nimport { db } from \"./firebase\";\n\nconst useAudioPlayer = (albumId, albumOwnerId, user, sharedWith, setTracks, setTrack) => {\n const [isPlaying, setIsPlaying] = useState(false);\n const [playingTrackId, setPlayingTrackId] = useState(null);\n const [playingSegmentIndex, setPlayingSegmentIndex] = useState(0);\n const [selectedTrackId, setSelectedTrackId] = useState(null);\n const [pausedTime, setPausedTime] = useState(0);\n const audioRef = useRef(new Audio());\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const currentTrackRef = useRef(null);\n const [isRecording, setIsRecording] = useState(false);\n const [recordingTrackId, setRecordingTrackId] = useState(null);\n const mediaRecorderRef = useRef(null);\n const chunksRef = useRef([]);\n const [recordingStartTime, setRecordingStartTime] = useState(null);\n const [remainingTime, setRemainingTime] = useState(2700); // 45 minutes\n const [browserNotSupported, setBrowserNotSupported] = useState(false);\n const [alertDialogOpen, setAlertDialogOpen] = useState(false);\n const [dialogTitle, setDialogTitle] = useState('');\n const [dialogContent, setDialogContent] = useState('');\n\n const userIdRef = useRef(user ? user.uid : null);\n\n useEffect(() => {\n userIdRef.current = user ? user.uid : null;\n }, [user]);\n\n // Handle audio events\n useEffect(() => {\n const audio = audioRef.current;\n\n const handleTimeUpdate = () => {\n setCurrentTime(audio.currentTime);\n };\n\n const handleDurationChange = () => {\n setDuration(audio.duration);\n };\n\n const handleAudioEnd = () => {\n if (currentTrackRef.current) {\n const nextSegmentIndex = playingSegmentIndex + 1;\n if (nextSegmentIndex < currentTrackRef.current.audioSegments.length) {\n // Play next segment\n playSegment(currentTrackRef.current, nextSegmentIndex);\n } else {\n // End of track\n setIsPlaying(false);\n setPlayingTrackId(null);\n setPlayingSegmentIndex(0);\n currentTrackRef.current = null;\n }\n } else {\n setIsPlaying(false);\n }\n };\n\n audio.addEventListener('timeupdate', handleTimeUpdate);\n audio.addEventListener('durationchange', handleDurationChange);\n audio.addEventListener('ended', handleAudioEnd);\n\n return () => {\n audio.removeEventListener('timeupdate', handleTimeUpdate);\n audio.removeEventListener('durationchange', handleDurationChange);\n audio.removeEventListener('ended', handleAudioEnd);\n };\n }, [playingSegmentIndex]);\n\n const playSegment = useCallback(async (track, segmentIndex) => {\n const segment = track.audioSegments[segmentIndex];\n const audioId = segment.id;\n\n const storage = getStorage();\n const m4aPath = `audio_segments/${segment.recordedBy}/${albumId}/segment-${audioId}.m4a`;\n\n try {\n const m4aRef = ref(storage, m4aPath);\n const audioUrl = await getDownloadURL(m4aRef);\n\n audioRef.current.src = audioUrl;\n await audioRef.current.play();\n setPlayingTrackId(audioId);\n setSelectedTrackId(audioId);\n setPlayingSegmentIndex(segmentIndex);\n setIsPlaying(true);\n currentTrackRef.current = track;\n } catch (error) {\n console.error(\"Error loading or playing audio file:\", error);\n }\n }, [albumId]);\n\n const handlePause = useCallback(() => {\n audioRef.current.pause();\n setPausedTime(audioRef.current.currentTime);\n setIsPlaying(false);\n }, []);\n\n const handlePlayPause = useCallback(\n async (track, segmentIndex = 0) => {\n if (!track.audioSegments || track.audioSegments.length === 0) {\n console.error(\"No audio segments found for track:\", track);\n return;\n }\n\n const segment = track.audioSegments[segmentIndex];\n\n if (isPlaying && segment.id === playingTrackId) {\n handlePause();\n } else if (!isPlaying && segment.id === playingTrackId) {\n // Resume playback from the paused position\n audioRef.current.currentTime = pausedTime;\n await audioRef.current.play();\n setIsPlaying(true);\n } else {\n // Start playing a new segment\n await playSegment(track, segmentIndex);\n }\n\n setSelectedTrackId(segment.id);\n },\n [isPlaying, playingTrackId, playSegment, pausedTime, handlePause]\n );\n\n const handleSeek = (time) => {\n audioRef.current.currentTime = time;\n };\n\n const handleDeleteSegment = useCallback(async (trackId, segmentId) => {\n try {\n // Fetch the album document\n const albumRef = doc(db, \"albums\", albumId);\n const albumSnapshot = await getDoc(albumRef);\n\n if (!albumSnapshot.exists()) {\n console.error(\"Album not found\");\n return;\n }\n\n const albumData = albumSnapshot.data();\n const tracks = albumData.tracks || [];\n\n // Find the track\n const trackIndex = tracks.findIndex((track) => track.id === trackId);\n if (trackIndex === -1) {\n console.error(\"Track not found\");\n return;\n }\n\n const track = tracks[trackIndex];\n\n // Find the segment\n const segmentIndex = track.audioSegments.findIndex((segment) => segment.id === segmentId);\n if (segmentIndex === -1) {\n console.error(\"Segment not found\");\n return;\n }\n\n const segment = track.audioSegments[segmentIndex];\n\n // Check if the current user is authorized to delete the segment\n if (!user || user.uid !== segment.recordedBy) {\n console.error(\"User not authorized to delete this segment\");\n return;\n }\n\n // Remove the segment from the track\n track.audioSegments.splice(segmentIndex, 1);\n\n // Update the track in the tracks array\n tracks[trackIndex] = track;\n\n // Update the album document\n await updateDoc(albumRef, {\n tracks: tracks,\n updatedAt: new Date(),\n });\n\n // Update the track state in Track.jsx\n if (setTrack) {\n setTrack(track);\n }\n\n // Delete the audio file from storage\n const storage = getStorage();\n const m4aPath = `audio_segments/${segment.recordedBy}/${albumId}/segment-${segmentId}.m4a`;\n await deleteObject(ref(storage, m4aPath));\n\n console.log(\"Segment deleted successfully\");\n } catch (error) {\n console.error(\"Error deleting segment:\", error);\n }\n }, [albumId, user, setTrack]);\n\n const startRecording = useCallback(\n async (trackId) => {\n console.log(\"startRecording called with trackId:\", trackId);\n\n if (!trackId) {\n console.error(\"No trackId provided to startRecording\");\n return;\n }\n\n if (!user || (user.uid !== albumOwnerId && !sharedWith.includes(user.uid))) {\n setDialogTitle('Permission Denied');\n setDialogContent(\"You don't have permission to record on this album.\");\n setAlertDialogOpen(true);\n return;\n }\n\n try {\n console.log(\"Requesting microphone permission...\");\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n });\n console.log(\"Microphone permission granted\");\n\n const mimeType = \"audio/mp4\";\n\n console.log(\"Using mimeType:\", mimeType);\n\n mediaRecorderRef.current = new MediaRecorder(stream, {\n mimeType\n });\n chunksRef.current = [];\n\n mediaRecorderRef.current.ondataavailable = (event) => {\n if (event.data.size > 0) {\n chunksRef.current.push(event.data);\n }\n };\n\n mediaRecorderRef.current.onstop = async () => {\n console.log(\"Recording stopped, processing audio...\");\n const audioBlob = new Blob(chunksRef.current, { type: \"audio/mp4\" });\n\n console.log(\"User ID:\", userIdRef.current);\n\n try {\n const timestamp = Date.now();\n const m4aFileName = `segment-${timestamp}.m4a`;\n\n const storage = getStorage();\n const m4aPath = `audio_segments/${userIdRef.current}/${albumId}/${m4aFileName}`;\n\n // Upload M4A file\n const m4aRef = ref(storage, m4aPath);\n await uploadBytes(m4aRef, audioBlob);\n\n const albumRef = doc(db, \"albums\", albumId);\n const albumSnapshot = await getDoc(albumRef);\n const albumData = albumSnapshot.data();\n const tracks = albumData.tracks || [];\n\n const trackIndex = tracks.findIndex(\n (track) => track.id === recordingTrackId\n );\n if (trackIndex !== -1) {\n const updatedTracks = [...tracks];\n const newAudioSegment = {\n id: timestamp.toString(),\n recordedBy: userIdRef.current,\n updatedAt: new Date(),\n createdAt: new Date(),\n };\n updatedTracks[trackIndex].audioSegments =\n updatedTracks[trackIndex].audioSegments || [];\n updatedTracks[trackIndex].audioSegments.push(newAudioSegment);\n updatedTracks[trackIndex].updatedAt = new Date();\n\n await updateDoc(albumRef, {\n tracks: updatedTracks,\n updatedAt: new Date(),\n });\n\n // After update, fetch the latest data to get the server-generated timestamps\n const updatedDoc = await getDoc(albumRef);\n if (updatedDoc.exists()) {\n const updatedTracks = updatedDoc.data().tracks;\n setTracks(updatedTracks);\n\n // Update the track state in Track.jsx\n const updatedTrack = updatedTracks.find(track => track.id === recordingTrackId);\n if (setTrack) {\n setTrack(updatedTrack);\n }\n }\n\n console.log(\"Recording uploaded and database updated\");\n // Resolve the new audio segment\n return newAudioSegment;\n }\n } catch (error) {\n console.error(\"Error uploading recording:\", error);\n }\n return null;\n };\n\n mediaRecorderRef.current.start();\n setIsRecording(true);\n setRecordingTrackId(trackId);\n setRecordingStartTime(Date.now());\n setRemainingTime(2700); // 45 minutes\n console.log(\"Recording started for track:\", trackId);\n } catch (error) {\n console.error(\"Error starting recording:\", error);\n if (error.name === \"NotAllowedError\") {\n setDialogTitle('Microphone Access Denied');\n setDialogContent('Microphone access was denied. Please check your browser settings and ensure that you\\'ve granted permission for this site to use your microphone.');\n setAlertDialogOpen(true);\n } else if (error.name === \"NotFoundError\") {\n setDialogTitle('Microphone Not Found');\n setDialogContent('No microphone was found. Please ensure that a microphone is connected and try again.');\n setAlertDialogOpen(true);\n } else if (error.name === \"NotSupportedError\") {\n setBrowserNotSupported(true);\n } else {\n setDialogTitle('Error');\n setDialogContent(`An error occurred while trying to start recording: ${error.message}`);\n setAlertDialogOpen(true);\n }\n }\n },\n [albumOwnerId, sharedWith, user, setTrack]\n );\n\n const stopRecording = useCallback(async () => {\n if (mediaRecorderRef.current && isRecording) {\n return new Promise((resolve) => {\n let recordingProcessed = false; // Flag to prevent duplicate processing\n\n mediaRecorderRef.current.onstop = async () => {\n if (recordingProcessed) return;\n recordingProcessed = true;\n\n setIsRecording(false);\n setRecordingTrackId(null);\n setRecordingStartTime(null);\n setRemainingTime(2700); // 45 minutes\n\n const audioBlob = new Blob(chunksRef.current, { type: \"audio/mp4\" });\n const timestamp = Date.now();\n const m4aFileName = `segment-${timestamp}.m4a`;\n\n const storage = getStorage();\n const m4aPath = `audio_segments/${userIdRef.current}/${albumId}/${m4aFileName}`;\n\n try {\n // Upload M4A file\n const m4aRef = ref(storage, m4aPath);\n await uploadBytes(m4aRef, audioBlob);\n\n const newAudioSegment = {\n id: timestamp.toString(),\n recordedBy: userIdRef.current,\n updatedAt: new Date(),\n createdAt: new Date(),\n };\n\n const albumRef = doc(db, \"albums\", albumId);\n const albumSnapshot = await getDoc(albumRef);\n const albumData = albumSnapshot.data();\n const tracks = albumData.tracks || [];\n\n const trackIndex = tracks.findIndex(\n (track) => track.id === recordingTrackId\n );\n if (trackIndex !== -1) {\n const updatedTracks = [...tracks];\n updatedTracks[trackIndex].audioSegments =\n updatedTracks[trackIndex].audioSegments || [];\n updatedTracks[trackIndex].audioSegments.push(newAudioSegment);\n updatedTracks[trackIndex].updatedAt = new Date();\n\n await updateDoc(albumRef, {\n tracks: updatedTracks,\n updatedAt: new Date(),\n });\n\n // Re-fetch the updated album data\n const updatedDoc = await getDoc(albumRef);\n if (updatedDoc.exists()) {\n const updatedTracks = updatedDoc.data().tracks;\n if (setTracks) {\n setTracks(updatedTracks);\n }\n\n // Update the track state in Track.jsx\n const updatedTrack = updatedTracks.find(track => track.id === recordingTrackId);\n if (setTrack) {\n setTrack(updatedTrack);\n }\n }\n\n console.log(\"Recording uploaded and database updated\");\n resolve(newAudioSegment);\n } else {\n resolve(null);\n }\n } catch (error) {\n console.error(\"Error uploading recording:\", error);\n resolve(null);\n }\n };\n\n mediaRecorderRef.current.stop();\n });\n }\n }, [isRecording, albumId, recordingTrackId, setTracks, setTrack]);\n\n useEffect(() => {\n let intervalId;\n if (isRecording && recordingStartTime) {\n intervalId = setInterval(() => {\n const elapsedTime = Math.floor((Date.now() - recordingStartTime) / 1000);\n const remaining = Math.max(2700 - elapsedTime, 0); // 45 minutes\n setRemainingTime(remaining);\n\n if (remaining === 0) {\n stopRecording();\n }\n }, 1000);\n }\n\n return () => {\n if (intervalId) {\n clearInterval(intervalId);\n }\n };\n }, [isRecording, recordingStartTime, stopRecording]);\n\n return {\n // Playback\n isPlaying,\n playingTrackId,\n playingSegmentIndex,\n selectedTrackId,\n currentTime,\n duration,\n handlePause,\n handlePlayPause,\n handleSeek,\n audioRef,\n\n // Recording\n isRecording,\n startRecording,\n stopRecording,\n recordingTrackId,\n remainingTime,\n\n // Segment Management\n handleDeleteSegment,\n\n // Dialog Management\n browserNotSupported,\n alertDialogOpen,\n setAlertDialogOpen,\n dialogTitle,\n setDialogTitle,\n dialogContent,\n setDialogContent,\n };\n};\n\nexport default useAudioPlayer;\n"],"names":["useAudioPlayer","albumId","albumOwnerId","user","sharedWith","setTracks","setTrack","isPlaying","setIsPlaying","useState","playingTrackId","setPlayingTrackId","playingSegmentIndex","setPlayingSegmentIndex","selectedTrackId","setSelectedTrackId","pausedTime","setPausedTime","audioRef","useRef","currentTime","setCurrentTime","duration","setDuration","currentTrackRef","isRecording","setIsRecording","recordingTrackId","setRecordingTrackId","mediaRecorderRef","chunksRef","recordingStartTime","setRecordingStartTime","remainingTime","setRemainingTime","browserNotSupported","setBrowserNotSupported","alertDialogOpen","setAlertDialogOpen","dialogTitle","setDialogTitle","dialogContent","setDialogContent","userIdRef","useEffect","audio","handleTimeUpdate","handleDurationChange","handleAudioEnd","nextSegmentIndex","playSegment","useCallback","track","segmentIndex","segment","audioId","storage","getStorage","m4aPath","m4aRef","ref","audioUrl","getDownloadURL","error","handlePause","handlePlayPause","handleSeek","time","handleDeleteSegment","trackId","segmentId","albumRef","doc","db","albumSnapshot","getDoc","tracks","trackIndex","updateDoc","deleteObject","startRecording","stream","mimeType","event","audioBlob","timestamp","m4aFileName","uploadBytes","updatedTracks","newAudioSegment","updatedDoc","updatedTrack","stopRecording","resolve","recordingProcessed","intervalId","elapsedTime","remaining"],"mappings":"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"}