feat(05-02): create JKSessionJamTrackPlayer component skeleton
- Component structure with 11 props matching Phase 4 design - Initialization pattern: buildFqId → check sync → autoPlay - Local state: error, isLoadingSync, isOperating, selectedMixdownId - Redux connections: jamTrackState, downloadState, availableMixdowns - Cleanup on unmount prevents stale state with closeJamTrack - mountedRef prevents state updates after unmount - Placeholder render shows sync/download status - Ready for playback controls in Plan 3 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
db06357d32
commit
2cf7205ffd
|
|
@ -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 (
|
||||
<div className="jamtrack-player">
|
||||
<h3>JamTrack Player (Skeleton)</h3>
|
||||
{isLoadingSync && <p>Checking sync status...</p>}
|
||||
{error && <p style={{ color: 'red' }}>{error.message}</p>}
|
||||
{downloadState.state !== 'idle' && (
|
||||
<p>Download State: {downloadState.state} ({downloadState.progress}%)</p>
|
||||
)}
|
||||
<p>Ready for playback controls (Plan 3)</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKSessionJamTrackPlayer;
|
||||
Loading…
Reference in New Issue