jam-cloud/jam-ui/src/hooks/useMixerStore.js

538 lines
18 KiB
JavaScript

import { useState, useCallback, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { selectInSession } from '../store/features/activeSessionSlice';
import {
selectMetronomeSettings,
selectNoAudioUsers,
selectClientsWithAudioOverride
} from '../store/features/mixersSlice';
import { throttle } from 'lodash'; // Add lodash for throttling
//import { useJamClient } from '../context/JamClientContext';
import { MIX_MODES } from '../helpers/globals';
import { putTrackSyncChange } from '../helpers/rest';
import useVuHelpers from './useVuHelpers';
import { useJamServerContext } from '../context/JamServerContext';
import { useMixersContext } from '../context/MixersContext';
import { vuStore } from '../stores/vuStore';
/**
* Custom hook for managing mixer store state and actions.
* Converted from the original CoffeeScript Reflux store to modern React.
*/
export default function useMixerStore() {
//const jamClient = useJamClient();
// Phase 4: Replace CurrentSessionContext with Redux
const inSession = useSelector(selectInSession);
// Phase 5: Replace duplicate state with Redux selectors
const metro = useSelector(selectMetronomeSettings);
const noAudioUsers = useSelector(selectNoAudioUsers);
const clientsWithAudioOverride = useSelector(selectClientsWithAudioOverride);
const {
jamClient,
} = useJamServerContext();
const mixerHelper = useMixersContext();
const mixerHelperRef = useRef(null);
const logger = console; // Replace with your logging mechanism if needed
//const { updateVU } = useVuHelpers();
// Local state (not duplicated in Redux)
const [checkingMissingPeers, setCheckingMissingPeers] = useState({});
const [missingMixerPeers, setMissingMixerPeers] = useState({});
const [vuMeterUpdatePrefMap, setVuMeterUpdatePrefMap] = useState({ count: 0 });
const [session, setSession] = useState(null);
//const [masterMixers, setMasterMixers] = useState(null);
//const [personalMixers, setPersonalMixers] = useState(null);
//const [mixers, setMixers] = useState(null); //holds MixerHelper instance
// Refs for persistent values
const appRef = useRef(null);
const gearUtilsRef = useRef(null);
const sessionUtilsRef = useRef(null);
const recheckTimeoutRef = useRef(null);
// Constants
const METRO_SOUND_LOOKUP = {
0: "BuiltIn",
1: "SineWave",
2: "Beep",
3: "Click",
4: "Kick",
5: "Snare",
6: "MetroFile"
};
// Keep mixerHelperRef in sync with current mixerHelper
useEffect(() => {
mixerHelperRef.current = mixerHelper;
}, [mixerHelper]);
// Initialize global callbacks
useEffect(() => {
if (!window.JK) window.JK = {};
console.debug("MixerStore: setting up global callbacks", mixerHelper.mixers.current);
//window.JK.HandleVolumeChangeCallback2 = handleVolumeChangeCallback;
//window.JK.HandleMetronomeCallback2 = handleMetronomeCallback;
window.JK.HandleBridgeCallback2 = handleBridgeCallback;
//window.JK.HandleBackingTrackSelectedCallback2 = handleBackingTrackSelectedCallback;
return () => {
// Cleanup
if (window.JK) {
//delete window.JK.HandleVolumeChangeCallback2;
//delete window.JK.HandleMetronomeCallback2;
delete window.JK.HandleBridgeCallback2;
//delete window.JK.HandleBackingTrackSelectedCallback2;
}
if (recheckTimeoutRef.current) {
clearTimeout(recheckTimeoutRef.current);
}
};
}, []);
// Initialize on app ready
const initialize = useCallback(async (app) => {
// appRef.current = app;
// gearUtilsRef.current = window.JK?.GearUtilsInstance;
// sessionUtilsRef.current = window.JK?.SessionUtils;
// if (jamClient) {
// await jamClient.SetVURefreshRate(150);
// await jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback2");
// await jamClient.setMetronomeOpenCallback("JK.HandleMetronomeCallback2");
// }
}, [jamClient]);
// // Callback handlers
// const handleVolumeChangeCallback = useCallback((mixerId, isLeft, value, isMuted) => {
// // TODO: Visually update mixer
// // Original had TODO comments about updating fader values
// console.debug("volume change");
// }, []);
// const handleMetronomeCallback = useCallback((args) => {
// console.debug("MetronomeCallback: ", args);
// setMetro(prev => ({
// ...prev,
// tempo: args.bpm,
// cricket: args.cricket,
// sound: METRO_SOUND_LOOKUP[args.sound]
// }));
// // Update mixers with new metro data
// setMixers(prevMixers => {
// if (!prevMixers) return null;
// return new window.MixerHelper(session, masterMixers, personalMixers, metro, noAudioUsers, clientsWithAudioOverride, prevMixers.mixMode || MIX_MODES.PERSONAL);
// });
// issueChange();
// }, [session, masterMixers, personalMixers, noAudioUsers, clientsWithAudioOverride, issueChange]);
const handleBridgeCallback = useCallback((vuData) => {
let eventName = null;
let mixerId = null;
const value = null;
let vuInfo = null;
// const vuMeterUpdateRate = localStorage.getItem('vuMeterUpdateRate') || 'fast';
// this.vuMeterUpdatePrefMap['count'] = (this.vuMeterUpdatePrefMap['count'] || 0) + 1;
// if ((vuMeterUpdateRate === 'medium') && ((this.vuMeterUpdatePrefMap['count'] % 3) !== 0)) {
// // skip this update
// return;
// }
// if ((vuMeterUpdateRate === 'slow') && ((this.vuMeterUpdatePrefMap['count'] % 9) !== 0)) {
// // skip this update
// return;
// }
//console.log('vuMeterUpdateRate', vuMeterUpdateRate, @vuMeterUpdatePrefMap['count'])
//this.vuMeterUpdatePrefMap['count'] = 0;
//console.log("_DEBUG_ XXX: ", vuData);
//const result = [];
for (vuInfo of vuData) {
eventName = vuInfo[0];
const vuVal = 0.0;
if (eventName === "vu") {
mixerId = vuInfo[1];
const mode = vuInfo[2];
const leftValue = vuInfo[3];
const leftClipping = vuInfo[4];
const rightValue = vuInfo[5];
const rightClipping = vuInfo[6];
// TODO - no guarantee range will be -80 to 20. Get from the
// GetControlState for this mixer which returns min/max
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0
// Convert dB to 0.0-1.0 range (existing logic)
const normalizedLevel = (leftValue + 80) / 80;
// Create qualified ID matching existing pattern
const fqId = (mode ? 'M' : 'P') + mixerId;
// Route to external store instead of mixerHelper
vuStore.updateLevel(fqId, normalizedLevel, leftClipping);
}
}
}, []);
// const handleBackingTrackSelectedCallback = useCallback(() => {
// console.debug("backing track selected");
// }, []);
// Session management
const sessionEnded = useCallback(() => {
// Phase 5: Local state only - Redux handles noAudioUsers cleanup
setCheckingMissingPeers({});
setMissingMixerPeers({});
if (recheckTimeoutRef.current) {
clearTimeout(recheckTimeoutRef.current);
recheckTimeoutRef.current = null;
}
}, []);
const onSessionChange = useCallback(async (newSession) => {
console.log("[MixerStore] onSessionChange START:", {
sessionId: newSession.id(),
timestamp: Date.now()
});
console.debug("MixerStore: onSessionChange", newSession);
if (!newSession) return;
if (!inSession) {
sessionEnded();
}
setSession(newSession);
if (jamClient) {
console.log("[MixerStore] Fetching mixer control state...");
const newMasterMixers = await jamClient.SessionGetAllControlState(true);
const newPersonalMixers = await jamClient.SessionGetAllControlState(false);
console.log("[MixerStore] Fetched mixers:", {
masterCount: newMasterMixers.length,
personalCount: newPersonalMixers.length
});
// Initialize the mixerHelper with the constructor parameters
console.debug("MixerStore: onSessionChange: initializing mixerHelper", { newSession, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride });
mixerHelper.updateMixerData(newSession, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride, MIX_MODES.PERSONAL);
// const m = useMixerHelper(newSession, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride, mixers?.mixMode || MIX_MODES.PERSONAL);
// console.debug("MixerStore: onSessionChange: ", m);
// setMixers(m);
}
console.log("[MixerStore] onSessionChange END");
}, [jamClient, mixerHelper, metro, noAudioUsers, clientsWithAudioOverride, inSession, sessionEnded]);
// // Mixer actions
// const onMute = useCallback(async (mixersToMute, muting) => {
// const mixersArray = Array.isArray(mixersToMute) ? mixersToMute : [mixersToMute];
// for (const mixer of mixersArray) {
// if (mixers) {
// await mixers.mute(mixer.id, mixer.mode, muting);
// }
// }
// issueChange();
// }, [mixers, issueChange]);
// const onFaderChanged = useCallback(async (data, mixersList, gainType, controlGroup) => {
// if (mixers) {
// await mixers.faderChanged(data, mixersList, gainType, controlGroup);
// }
// issueChange();
// }, [mixers, issueChange]);
// const onPanChanged = useCallback((data, mixersList, groupId) => {
// if (mixers) {
// mixers.panChanged(data, mixersList, groupId);
// }
// issueChange();
// }, [mixers, issueChange]);
// const onLoopChanged = useCallback(async (mixer, shouldLoop) => {
// if (mixers) {
// await mixers.loopChanged(mixer, shouldLoop);
// }
// }, [mixers]);
// const onOpenMetronome = useCallback(async () => {
// if (jamClient && mixers) {
// await jamClient.SessionStopPlay();
// await jamClient.SessionOpenMetronome(mixers.metro.tempo, mixers.metro.sound, 1, 0);
// }
// }, [jamClient, mixers]);
// const onMetronomeChanged = useCallback(async (tempo, sound) => {
// console.debug("onMetronomeChanged", tempo, sound);
// setMetro(prev => ({ ...prev, tempo, sound }));
// if (jamClient) {
// await jamClient.SessionSetMetronome(tempo, sound, 1, 0);
// }
// setMixers(prevMixers => {
// if (!prevMixers) return null;
// return new window.MixerHelper(session, masterMixers, personalMixers, { ...metro, tempo, sound }, noAudioUsers, clientsWithAudioOverride, prevMixers.mixMode || MIX_MODES.PERSONAL);
// });
// issueChange();
// }, [jamClient, session, masterMixers, personalMixers, noAudioUsers, clientsWithAudioOverride, issueChange]);
// // Media close handlers
// const onCloseMedia = useCallback((codeInitiated) => {
// console.debug("MixerStore: onCloseMedia", codeInitiated);
// if (session?.recordedTracks()) {
// closeRecording(codeInitiated);
// } else if (session?.jamTracks() || session?.downloadingJamTrack) {
// closeJamTrack(codeInitiated);
// } else if (session?.backingTrack()?.path) {
// closeBackingTrack(codeInitiated);
// } else if (session?.isMetronomeOpen()) {
// closeMetronomeTrack(codeInitiated);
// } else {
// console.error("don't know how to close open media", session);
// }
// }, [session]);
// const closeRecording = useCallback((codeInitiated) => {
// console.debug("closing recording");
// if (!mixers?.mediaSummary.isOpener) {
// console.debug("not recording as not opener");
// return;
// }
// // TODO: Implement SessionActions.closeMedia
// // SessionActions.closeMedia(codeInitiated);
// }, [mixers]);
// const closeMetronomeTrack = useCallback((codeInitiated) => {
// console.debug("closing metronome");
// // TODO: Implement SessionActions.closeMedia
// // SessionActions.closeMedia(codeInitiated);
// }, []);
// const closeBackingTrack = useCallback((codeInitiated) => {
// console.debug("closing backing track");
// if (!mixers?.mediaSummary.isOpener) {
// console.debug("not closing backing as not opener");
// return;
// }
// // TODO: Implement SessionActions.closeMedia
// // SessionActions.closeMedia(codeInitiated);
// }, [mixers]);
// const closeJamTrack = useCallback((codeInitiated) => {
// console.debug("closing jam track");
// if (!mixers?.mediaSummary.isOpener) {
// console.debug("not closing jamtrack as not opener");
// return;
// }
// // TODO: Implement SessionActions.closeMedia
// // SessionActions.closeMedia(codeInitiated);
// }, [mixers]);
// // Peer management
// const onDeadUserRemove = useCallback((clientId) => {
// if (!session?.inSession()) return;
// const participant = session.participantsEverSeen?.[clientId];
// if (participant) {
// console.debug("todo: notify dead user");
// // TODO: trigger some notification store
// }
// setNoAudioUsers(prev => ({ ...prev, [clientId]: true }));
// issueChange();
// }, [session, issueChange]);
// const onClientsWithAudio = useCallback((clients) => {
// setClientsWithAudioOverride(clients);
// }, []);
// const onMissingPeerMixer = useCallback((clientId) => {
// const missingPeerAttempts = missingMixerPeers[clientId];
// if (!missingPeerAttempts || missingPeerAttempts < 5) {
// setCheckingMissingPeers(prev => ({ ...prev, [clientId]: true }));
// if (!recheckTimeoutRef.current) {
// recheckTimeoutRef.current = setTimeout(recheckForMixers, 2000);
// }
// } else {
// console.debug("ignoring missing peer recheck. missingPeerAttempts:", missingPeerAttempts);
// }
// }, [missingMixerPeers]);
// const recheckForMixers = useCallback(async () => {
// setCheckingMissingPeers(prev => {
// const newChecking = { ...prev };
// for (const clientId in newChecking) {
// setMissingMixerPeers(prevMissing => {
// const attempts = (prevMissing[clientId] || 0) + 1;
// return { ...prevMissing, [clientId]: attempts };
// });
// }
// return {};
// });
// recheckTimeoutRef.current = null;
// if (jamClient) {
// const newMasterMixers = await jamClient.SessionGetAllControlState(true);
// const newPersonalMixers = await jamClient.SessionGetAllControlState(false);
// setMasterMixers(newMasterMixers);
// setPersonalMixers(newPersonalMixers);
// console.debug("MixerStore: recheckForMixers");
// setMixers(prevMixers => {
// if (!prevMixers) return null;
// return new window.MixerHelper(session, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride, prevMixers.mixMode || MIX_MODES.PERSONAL);
// });
// }
// issueChange();
// }, [jamClient, session, metro, noAudioUsers, clientsWithAudioOverride, issueChange]);
// // Other handlers
// const onInitGain = useCallback((mixer) => {
// if (mixers) {
// mixers.initGain(mixer);
// }
// }, [mixers]);
// const onInitPan = useCallback((mixer) => {
// if (mixers) {
// mixers.initPan(mixer);
// }
// }, [mixers]);
// const onMixersChanged = useCallback(async (type, text) => {
// if (jamClient) {
// const newMasterMixers = await jamClient.SessionGetAllControlState(true);
// const newPersonalMixers = await jamClient.SessionGetAllControlState(false);
// setMasterMixers(newMasterMixers);
// setPersonalMixers(newPersonalMixers);
// console.debug("MixerStore: onMixersChanged");
// setMixers(prevMixers => {
// if (!prevMixers) return null;
// return new window.MixerHelper(session, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride, prevMixers.mixMode || MIX_MODES.PERSONAL);
// });
// // TODO: Implement SessionActions.mixersChanged.trigger
// // SessionActions.mixersChanged.trigger(type, text, mixers.getTrackInfo());
// }
// issueChange();
// }, [jamClient, session, metro, noAudioUsers, clientsWithAudioOverride, issueChange]);
// const onMixerModeChanged = useCallback((mode) => {
// if (mode === MIX_MODES.MASTER && appRef.current?.layout) {
// appRef.current.layout.showDialog('session-master-mix-dialog');
// } else if (appRef.current?.layout) {
// appRef.current.layout.closeDialog('session-master-mix-dialog');
// }
// }, []);
// const onSyncTracks = useCallback(async () => {
// console.debug("MixerStore: onSyncTracks");
// if (!session?.inSession()) {
// console.debug("dropping sync tracks because no longer in session");
// return;
// }
// if (mixers) {
// const allTracks = await mixers.getTrackInfo();
// console.log('allTracks', allTracks);
// const inputTracks = allTracks.userTracks;
// const backingTracks = allTracks.backingTracks;
// const metronomeTracks = allTracks.metronomeTracks;
// // create a trackSync request based on backend data
// const syncTrackRequest = {
// client_id: appRef.current?.clientId,
// tracks: inputTracks,
// backing_tracks: backingTracks,
// metronome_open: metronomeTracks.length > 0,
// id: session.id()
// };
// try {
// await putTrackSyncChange(syncTrackRequest);
// } catch (error) {
// if (error.status !== 404) {
// appRef.current?.notify({
// "title": "Can't Sync Local Tracks",
// "text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
// "icon_url": "/assets/content/icon_alert_big.png"
// });
// } else {
// console.debug("Unable to sync local tracks because session is gone.");
// }
// }
// }
// }, [session, mixers]);
// Return the state and handlers
return {
// State
// session,
// metro,
// noAudioUsers,
// clientsWithAudioOverride,
// Initialization
initialize,
// Handlers
onSessionChange,
// onMute,
// onFaderChanged,
// onPanChanged,
// onLoopChanged,
// onOpenMetronome,
// onMetronomeChanged,
// onCloseMedia,
// onDeadUserRemove,
// onClientsWithAudio,
// onMissingPeerMixer,
// onInitGain,
// onInitPan,
// onMixersChanged,
// onMixerModeChanged,
// onSyncTracks,
// // Callbacks
// handleVolumeChangeCallback,
// handleMetronomeCallback,
// handleBridgeCallback,
// handleBackingTrackSelectedCallback,
};
}