diff --git a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js new file mode 100644 index 000000000..818fe134e --- /dev/null +++ b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js @@ -0,0 +1,121 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + loadJamTrack, + checkJamTrackSync, + closeJamTrack, + setJamTrackState +} from '../../store/features/mediaSlice'; +import { setOpenJamTrack, clearOpenJamTrack } from '../../store/features/sessionUISlice'; + +const JKSessionJamTrackPlayer = ({ + isOpen, + onClose, + isPopup = false, + jamTrack, + jamClient, + session, + currentUser, + initialMixdownId = null, + autoPlay = false +}) => { + // Local state + const [error, setError] = useState(null); + const [isLoadingSync, setIsLoadingSync] = useState(false); + const [isOperating, setIsOperating] = useState(false); + const [selectedMixdownId, setSelectedMixdownId] = useState(initialMixdownId); + + // Redux state + const dispatch = useDispatch(); + const jamTrackState = useSelector(state => state.media.jamTrackState); + const downloadState = useSelector(state => state.media.downloadState); + const availableMixdowns = useSelector(state => state.activeSession.availableMixdowns); + + // Refs + const fqIdRef = useRef(null); + const mountedRef = useRef(true); + + // Helper: Build fqId + const buildFqId = useCallback(async () => { + if (!jamClient || !jamTrack) return null; + const sampleRate = await jamClient.GetSampleRate(); + return `${jamTrack.id}-${sampleRate === 48 ? '48' : '44'}`; + }, [jamClient, jamTrack]); + + // Initialization + useEffect(() => { + if (!isOpen && !isPopup) return; + if (!jamClient || !jamTrack) return; + + const initializePlayer = async () => { + try { + setIsLoadingSync(true); + setError(null); + + // Build fqId + const fqId = await buildFqId(); + fqIdRef.current = fqId; + + // Check sync state + const syncResult = await dispatch(checkJamTrackSync({ jamTrack, jamClient })).unwrap(); + + if (!syncResult.isSynchronized) { + // Download flow will be triggered by loadJamTrack + console.log('[JamTrack] Not synchronized, will download'); + } + + // Set open in Redux + dispatch(setOpenJamTrack(jamTrack.id)); + + // Load and play if autoPlay + if (autoPlay) { + await dispatch(loadJamTrack({ + jamTrack, + mixdownId: selectedMixdownId, + autoPlay: true, + jamClient + })).unwrap(); + } + + } catch (err) { + console.error('[JamTrack] Initialization error:', err); + setError({ type: 'initialization', message: err.message || 'Failed to initialize JamTrack' }); + } finally { + if (mountedRef.current) { + setIsLoadingSync(false); + } + } + }; + + initializePlayer(); + }, [isOpen, isPopup, jamTrack, jamClient, autoPlay, selectedMixdownId, dispatch, buildFqId]); + + // Cleanup on unmount + useEffect(() => { + return () => { + mountedRef.current = false; + + if (fqIdRef.current && jamClient) { + dispatch(closeJamTrack({ fqId: fqIdRef.current, jamClient })); + dispatch(clearOpenJamTrack()); + } + }; + }, [dispatch, jamClient]); + + // Placeholder render (will be filled in Plan 3) + if (!isOpen && !isPopup) return null; + + return ( +
Checking sync status...
} + {error &&{error.message}
} + {downloadState.state !== 'idle' && ( +Download State: {downloadState.state} ({downloadState.progress}%)
+ )} +Ready for playback controls (Plan 3)
+