diff --git a/jam-ui/src/components/client/JKSessionScreen.js b/jam-ui/src/components/client/JKSessionScreen.js
index 102f84540..06898c3b5 100644
--- a/jam-ui/src/components/client/JKSessionScreen.js
+++ b/jam-ui/src/components/client/JKSessionScreen.js
@@ -8,17 +8,19 @@ import useGearUtils from '../../hooks/useGearUtils'
import useSessionUtils from '../../hooks/useSessionUtils.js';
import useSessionEnter from '../../hooks/useSessionEnter.js'
import useSessionLeave from '../../hooks/useSessionLeave.js';
-
+import useRecordingHelpers from '../../hooks/useRecordingHelpers.js';
import { useCurrentSession } from '../../context/CurrentSessionContext.js';
+import { useJamKazamApp } from '../../context/JamKazamAppContext.js';
-import { getSessionHistory, getSession } from '../../helpers/rest';
+import { getSessionHistory, getSession, joinSession as joinSessionRest } from '../../helpers/rest';
import { useParams } from 'react-router-dom';
import { CLIENT_ROLE } from '../../helpers/globals';
import { MessageType } from '../../helpers/MessageFactory.js';
const JKSessionScreen = () => {
- const logger = console; // Replace with your logging mechanism if needed
+ const logger = console; // Replace with another logging mechanism if needed
+ const app = useJamKazamApp();
const {
isConnected,
connectionStatus,
@@ -41,97 +43,221 @@ const JKSessionScreen = () => {
const { performLeaveSession, leaveSessionRest } = useSessionLeave();
- const { currentSession, setCurrentSession, inSession } = useCurrentSession();
+ const { currentSession, setCurrentSession, currentSessionIdRef, setCurrentSessionId, inSession } = useCurrentSession();
- //get session ID from URL params
- const { id: sessionId } = useParams();
+ const { getCurrentRecordingState, reset: resetRecordingState } = useRecordingHelpers();
+
+ const { id: sessionId } = useParams();
// State to hold session data
const [userTracks, setUserTracks] = useState([]);
- const [sessionState, setSessionState] = useState({});
const [showConnectionAlert, setShowConnectionAlert] = useState(false);
const [hasJoined, setHasJoined] = useState(false);
const [requestingSessionRefresh, setRequestingSessionRefresh] = useState(false);
const [pendingSessionRefresh, setPendingSessionRefresh] = useState(false);
+ const [sessionRules, setSessionRules] = useState(null);
+ const [subscriptionRules, setSubscriptionRules] = useState(null);
+ const [currentOrLastSession, setCurrentOrLastSession] = useState(null);
useEffect(() => {
if (!isConnected || !jamClient) return;
- guardJoinSession();
+ guardOnJoinSession();
}, [isConnected]);
- const guardJoinSession = async () => {
+ const guardOnJoinSession = async () => {
try {
- const musicSession = await getSessionHistory(sessionId);
- if (musicSession) {
- const musicianAccessOnJoin = musicSession.musician_access;
- const shouldVerifyNetwork = musicSession.musician_access;
- const clientRole = await jamClient.getClientParentChildRole();
+
+ const musicSessionResp = await getSessionHistory(sessionId);
+ const musicSession = await musicSessionResp.json();
+ logger.log("fetched session history: ", musicSession);
+ setCurrentSessionId(musicSession.id); // use the ref setter to set the current session ID
- //store current session in context
- setCurrentSession(prev => ({ ...prev, id: musicSession.id }));
+ const musicianAccessOnJoin = musicSession.musician_access;
+ const shouldVerifyNetwork = musicSession.musician_access;
+ const clientRole = await jamClient.getClientParentChildRole();
- if (clientRole === CLIENT_ROLE.CHILD) {
- logger.debug("client is configured to act as child. skipping all checks. assuming 0 tracks");
- setUserTracks([]);
- await joinSession();
- }
+ logger.log("musicianAccessOnJoin when joining session: " + musicianAccessOnJoin);
+ logger.log("clientRole when joining session: " + clientRole);
+ logger.log("currentSessionId when joining session: " + currentSessionIdRef.current);
+
+ if (clientRole === CLIENT_ROLE.CHILD) {
+ logger.debug("client is configured to act as child. skipping all checks. assuming 0 tracks");
+ setUserTracks([]);
+
+ //skipping all checks. assuming 0 tracks
+ await joinSession();
+ return;
+ }
+
+ try {
+ await guardAgainstInvalidConfiguration(app, shouldVerifyNetwork);
+ const result = await SessionPageEnter();
+ logger.log("SessionPageEnter result: ", result);
try {
- await guardAgainstInvalidConfiguration({}, shouldVerifyNetwork); // TODO: provide proper app object
- const result = await SessionPageEnter();
-
+ await guardAgainstActiveProfileMissing(app, result);
+ logger.log("user has an active profile");
try {
- await guardAgainstActiveProfileMissing({}, result); // TODO: provide proper app object
-
+ const tracks = await waitForSessionPageEnterDone();
+ setUserTracks(tracks);
+ logger.log("userTracks: ", tracks);
try {
- const tracks = await waitForSessionPageEnterDone();
- setUserTracks(tracks);
+ await ensureAppropriateProfile(musicianAccessOnJoin)
+ logger.log("user has passed all session guards")
+
+ // all checks passed; join the session
+ await joinSession();
- try {
- await ensureAppropriateProfile(musicianAccessOnJoin)
- logger.debug("user has passed all session guards")
- await joinSession()
- } catch (error) {
- if (!error.controlled_location) {
- //SessionActions.leaveSession.trigger({ location: "/client#/home" });
- //TODO: show some error and redirect to home
- }
- }
} catch (error) {
- if (error === "timeout") {
- //context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.');
- //TODO: show some error
- } else if (error === 'session_over') {
- // do nothing; session ended before we got the user track info. just bail
- logger.debug("session is over; bailing");
- } else {
- //context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + error);
- //TODO: show some error
+ logger.error("User profile is not appropriate for session:", error);
+ if (!error.controlled_location) {
}
-
- //SessionActions.leaveSession.trigger({ location: '/client#/home' });
- await performLeaveSession(); //TODO: handle redirection
}
} catch (error) {
- // Active profile is missing, redirect to home or if the error has a location, redirect there
+ logger.error("Error: waiting for session page enter to complete:", error);
+ if (error === "timeout") {
+ //TODO: show some error
+ } else if (error === 'session_over') {
+ // do nothing; session ended before we got the user track info. just bail
+ logger.debug("Error:: session is over; bailing");
+ } else {
+ //TODO: show some error
+ }
+
+ await performLeaveSession(); //TODO: handle redirection
}
} catch (error) {
- //SessionActions.leaveSession.trigger({ location: '/client#/home' });
- // Invalid configuration, redirect to home
- await performLeaveSession(); //TODO: handle redirection
+ // Active profile is missing, redirect to home or if the error has a location, redirect there
+ logger.error("Error: Active profile is missing or invalid:", error);
}
- } else {
- console.error("Invalid session ID or unable to fetch session history");
- //TODO: Show some error
+ } catch (error) {
+ // Invalid configuration, redirect to home
+ await performLeaveSession(); //TODO: handle redirection
+ logger.error("Error: Invalid configuration:", error);
}
+
} catch (error) {
- console.error("Error fetching session history:", error);
+ logger.error("Error: Error fetching session history:", error);
//TODO: Show some error
}
};
+ const joinSession = async () => {
+ await jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
+ // await jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
+
+ // await jamClient.SessionSetConnectionStatusRefreshRate(1000);
+
+ // let clientRole = await jamClient.getClientParentChildRole();
+ // const parentClientId = await jamClient.getParentClientId();
+
+ // if (clientRole === 0) {
+ // clientRole = 'child';
+ // } else if (clientRole === 1) {
+ // clientRole = 'parent';
+ // }
+
+ // if ((clientRole === '') || !clientRole) {
+ // clientRole = null;
+ // }
+
+ // //subscribe to events from the recording model
+ // //this.recordingRegistration(); //TODO: implement recording registration
+
+ // // tell the server we want to join
+
+ // const clientId = await jamClient.GetClientID();
+ // logger.debug("joining session " + sessionId + " as client " + clientId + " with role " + clientRole + " and parent client " + parentClientId);
+
+ // const latency = await jamClient.FTUEGetExpectedLatency().latency
+
+ // logger.log("currentSession before join: ", currentSessionIdRef.current);
+
+ // joinSessionRest({
+ // client_id: clientId,
+ // ip_address: server.publicIP,
+ // as_musician: true,
+ // tracks: userTracks,
+ // session_id: currentSessionIdRef.current,
+ // client_role: clientRole,
+ // parent_client_id: parentClientId,
+ // audio_latency: latency,
+ // }).then(async (response) => {
+ // setHasJoined(true);
+ // if (!inSession()) {
+ // // the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out
+ // logger.debug("user left before fully joined to session. telling server again that they have left");
+ // leaveSessionRest(currentSessionIdRef.current);
+ // }
+
+ // updateSessionInfo(response, true);
+
+ // // logger.debug("calling jamClient.JoinSession");
+
+ // //TODO: revist this logic later
+ // // // on temporary disconnect scenarios, a user may already be in a session when they enter this path
+ // // // so we avoid double counting
+ // // if (!this.alreadyInSession()) {
+ // // if (this.participants().length === 1) {
+ // // context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create);
+ // // } else {
+ // // context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
+ // // }
+ // // }
+
+ // resetRecordingState(currentSessionIdRef.current); // reset recording state for this session
+
+ // const joinSessionMsg = {
+ // sessionID: currentSessionIdRef.current,
+ // music_session_id_int: response.music_session_id_int
+ // };
+
+ // await jamClient.JoinSession(joinSessionMsg);
+
+ // registerMessageCallback(MessageType.SESSION_JOIN, trackChanges);
+ // registerMessageCallback(MessageType.SESSION_DEPART, trackChanges);
+ // registerMessageCallback(MessageType.TRACKS_CHANGED, trackChanges);
+ // registerMessageCallback(MessageType.HEARTBEAT_ACK, trackChanges);
+
+ // //TODO: revist the logic in following commented section
+ // //if (document) { $(document).trigger(EVENTS.SESSION_STARTED, { session: { id: this.currentSessionId, lesson_session: response.lesson_session } }); }
+
+ // // this.handleAutoOpenJamTrack();
+
+ // // this.watchBackendStats();
+
+ // // ConfigureTracksActions.reset(true);
+ // // this.delayEnableVst();
+ // // logger.debug("completed session join")
+ // }).catch(async (xhr) => {
+ // logger.error("Failed to join session:", xhr);
+ // let leaveBehavior;
+ // await updateCurrentSession(null);
+
+ // if (xhr.status === 404) {
+ // // we tried to join the session, but it is already gone. kick user back to join session screen
+
+ // } else if (xhr.status === 422) { // unprocessable entity - something was wrong with the join request
+ // const response = JSON.parse(xhr.responseText);
+
+ // if (response["errors"] && response["errors"]["tracks"] && (response["errors"]["tracks"][0] === "Please select at least one track")) { // No Inputs Configured
+
+ // } else if (response["errors"] && response["errors"]["music_session"] && (response["errors"]["music_session"][0] == ["is currently recording"])) { //The session is currently recording
+ // } else if (response["errors"] && response["errors"]["remaining_session_play_time"]) { // No Remaining Session Play Time
+
+ // } else if (response["errors"] && response["errors"]["remaining_month_play_time"]) { // No Remaining Month Play Time
+
+ // } else { // Unknown error. Unable to Join Session
+
+ // }
+ // } else { // Unknown error. Unable to Join Session
+
+ // }
+ // })
+
+ }
+
const ensureAppropriateProfile = async (musicianAccess) => {
- const app = {}; // Placeholder for app object if needed
return new Promise(async function (resolve, reject) {
if (musicianAccess) {
try {
@@ -169,7 +295,7 @@ const JKSessionScreen = () => {
refreshCurrentSessionRest(force);
};
-
+
const refreshCurrentSessionRest = async (force) => {
if (!inSession()) {
logger.debug("refreshCurrentSession skipped: ");
@@ -197,205 +323,82 @@ const JKSessionScreen = () => {
}
};
- // updateSessionInfo: `function (session, force) {
- // if ((force === true) || (this.currentTrackChanges < session.track_changes_counter)) {
- // logger.debug("updating current track changes from %o to %o", this.currentTrackChanges, session.track_changes_counter);
- // this.currentTrackChanges = session.track_changes_counter;
- // this.sendClientParticipantChanges(this.currentSession, session);
-
- // this.recordingModel.getCurrentRecordingState().then((recordingState) => {
- // session = { ...session, ...recordingState };
-
- // logger.debug('update current session');
-
- // }).finally(() => {
- // //console.log("_DEBUG_* SessionStore#updateSessionInfo sessionState", session);
- // this.updateCurrentSession(session);
- // });
-
- // } else {
- // return logger.info("ignoring refresh because we already have current: " + this.currentTrackChanges + ", seen: " + session.track_changes_counter);
- // }
- // }`
-
const updateSessionInfo = (session, force) => {
if ((force === true) || (currentSession.track_changes_counter < session.track_changes_counter)) {
logger.debug("updating current track changes from %o to %o", currentSession.track_changes_counter, session.track_changes_counter);
+
+ //TODO: revisit this logic
//this.currentTrackChanges = session.track_changes_counter;
//this.sendClientParticipantChanges(this.currentSession, session);
- setCurrentSession(prev => ({ ...prev, ...session }));
- //TODO: handle recording state
+ getCurrentRecordingState().then((recordingState) => {
+ session = { ...session, ...recordingState };
+ }).finally(async () => {
+ logger.log("_DEBUG_* JKSessionScreen#updateSessionInfo sessionState", session);
+ await updateCurrentSession(session);
+ });
+
} else {
return logger.info("ignoring refresh because we already have current: " + currentSession.track_changes_counter + ", seen: " + session.track_changes_counter);
}
};
- const joinSession = async () => {
- await jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
- await jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
+ const updateCurrentSession = async (sessionData) => {
+ //logger.log("_DEBUG_* SessionStore#updateCurrentSession", sessionData)
+ if (sessionData !== null) {
+ setCurrentOrLastSession(sessionData);
- await jamClient.SessionSetConnectionStatusRefreshRate(1000);
+ if (sessionData.session_rules) {
+ setSessionRules(sessionData.session_rules);
+ // TESTING:
+ //@sessionRules.remaining_session_play_time = 60 * 15 + 15 # 15 minutes and 15 seconds
- let clientRole = await jamClient.getClientParentChildRole();
- const parentClientId = await jamClient.getParentClientId();
- console.debug('role when joining session: ' + clientRole + ', parent client id ' + parentClientId);
-
- if (clientRole === 0) {
- clientRole = 'child';
- } else if (clientRole === 1) {
- clientRole = 'parent';
- }
-
- if ((clientRole === '') || !clientRole) {
- clientRole = null;
- }
-
- // subscribe to events from the recording model
- //this.recordingRegistration(); //TODO: implement recording registration
-
- // tell the server we want to join
-
- const clientId = await jamClient.GetClientID();
- console.debug("joining session " + sessionId + " as client " + clientId + " with role " + clientRole + " and parent client " + parentClientId);
-
- const latency = await jamClient.FTUEGetExpectedLatency().latency
-
- joinSession({
- client_id: clientId,
- ip_address: server.publicIP,
- as_musician: true,
- tracks: userTracks,
- session_id: sessionId,
- client_role: clientRole,
- parent_client_id: parentClientId,
- audio_latency: latency,
- }).then(async (response) => {
- setHasJoined(true);
- if (!inSession()) {
- // the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out
- logger.debug("user left before fully joined to session. telling server again that they have left");
- leaveSessionRest(currentSession.id);
+ // compute timestamp due time
+ if (sessionRules && sessionRules.remaining_session_play_time != null) {
+ let until_time = new Date();
+ until_time = new Date(until_time.getTime() + (sessionRules.remaining_session_play_time * 1000));
+ logger.log("subscription: session has remaining play time", until_time);
+ setSessionRules(prev => ({ ...prev, remaining_session_until: until_time }));
+ }
}
- this.updateSessionInfo(response, true);
- setCurrentSession(prev => ({ ...prev, ...response }));
+ if (sessionData.subscription) {
+ // for the backend - it looks here
+ //sessionData.subscription = sessionData.subscription_rules
+ // let the backend know
+ //context.jamClient.applySubscriptionPolicy()
+ setSubscriptionRules(sessionData.subscription);
+ // TESTING:
+ //@subscriptionRules.remaining_month_play_time = 60 * 15 + 15 # 15 minutes and 15 seconds
- logger.debug("calling jamClient.JoinSession");
+ if (subscriptionRules && subscriptionRules.remaining_month_play_time != null) {
+ let until_time = new Date();
+ until_time = new Date(until_time.getTime() + (subscriptionRules.remaining_month_play_time * 1000));
+ //until_time.setSeconds(until_time.getSeconds() + @subscriptionRules.remaining_month_play_time)
+ logger.log("subscription: month has remaining play time", until_time);
+ setSubscriptionRules(prev => ({ ...prev, remaining_month_until: until_time }));
+ }
+ }
+ }
- //TODO: revist this logic later
- // // on temporary disconnect scenarios, a user may already be in a session when they enter this path
- // // so we avoid double counting
- // if (!this.alreadyInSession()) {
- // if (this.participants().length === 1) {
- // context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create);
- // } else {
- // context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
- // }
- // }
+ logger.log("*_DEBUG_* JKSessionScreen#updateCurrentSession currentSession", sessionData);
+ setCurrentSession(sessionData);
+ //if (jamClient.UpdateSessionInfo != null) {
+ if (sessionData != null) {
+ await jamClient.UpdateSessionInfo(sessionData);
+ } else {
+ await jamClient.UpdateSessionInfo({});
+ }
+ //}
+ //logger.debug("session changed")
- // this.recordingModel.reset(this.currentSessionId); //TODO: implement recording model
+ logger.debug("session updated");
+ // @issueChange() equivalent - since it's React, state updates will trigger re-render
+ };
- const joinSessionMsg = {
- sessionID: currentSession.id,
- music_session_id_int: response.music_session_id_int
- };
+
- await jamClient.JoinSession(joinSessionMsg);
-
- //@refreshCurrentSession(true);
-
- registerMessageCallback(MessageType.SESSION_JOIN, trackChanges);
- registerMessageCallback(MessageType.SESSION_DEPART, trackChanges);
- registerMessageCallback(MessageType.TRACKS_CHANGED, trackChanges);
- registerMessageCallback(MessageType.HEARTBEAT_ACK, trackChanges);
-
- //TODO: revist the logic in following commented section
- //if (document) { $(document).trigger(EVENTS.SESSION_STARTED, { session: { id: this.currentSessionId, lesson_session: response.lesson_session } }); }
-
- // this.handleAutoOpenJamTrack();
-
- // this.watchBackendStats();
-
- // ConfigureTracksActions.reset(true);
- // this.delayEnableVst();
- // logger.debug("completed session join")
- }).catch((xhr) => {
- let leaveBehavior;
- this.updateCurrentSession(null);
-
- // // if (xhr.status === 404) {
- // // // we tried to join the session, but it is already gone. kick user back to join session screen
- // // leaveBehavior = {
- // // location: "/client#/findSession",
- // // notify: {
- // // title: "Unable to Join Session",
- // // text: " The session you attempted to join is over."
- // // }
- // // };
- // // SessionActions.leaveSession.trigger(leaveBehavior);
- // // } else if (xhr.status === 422) {
- // // let buttons;
- // // const response = JSON.parse(xhr.responseText);
- // // if (response["errors"] && response["errors"]["tracks"] && (response["errors"]["tracks"][0] === "Please select at least one track")) {
- // // return this.app.notifyAlert("No Inputs Configured", $('You will need to reconfigure your audio device.'));
-
- // // } else if (response["errors"] && response["errors"]["music_session"] && (response["errors"]["music_session"][0] === ["is currently recording"])) {
- // // leaveBehavior = {
- // // location: "/client#/findSession",
- // // notify: {
- // // title: "Unable to Join Session",
- // // text: "The session is currently recording."
- // // }
- // // };
- // // SessionActions.leaveSession.trigger(leaveBehavior);
- // // } else if (response["errors"] && response["errors"]["remaining_session_play_time"]) {
- // // leaveBehavior =
- // // { location: "/client#/findSession" };
- // // buttons = [];
- // // buttons.push({ name: 'CLOSE', buttonStyle: 'button-grey' });
- // // buttons.push({ name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (this.openBrowserToPlanComparison())) });
- // // buttons.push({
- // // name: 'UPGRADE PLAN',
- // // buttonStyle: 'button-orange',
- // // click: (() => (this.openBrowserToPayment()))
- // // });
- // // context.JK.Banner.show({
- // // title: "Out of Time For This Session",
- // // html: context._.template($('#template-no-remaining-session-play-time').html(), {}, { variable: 'data' }),
- // // buttons
- // // });
- // // SessionActions.leaveSession.trigger(leaveBehavior);
- // // } else if (response["errors"] && response["errors"]["remaining_month_play_time"]) {
- // // leaveBehavior =
- // // { location: "/client#/findSession" };
- // // buttons = [];
- // // buttons.push({ name: 'CLOSE', buttonStyle: 'button-grey' });
- // // buttons.push({ name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (this.openBrowserToPlanComparison())) });
- // // buttons.push({
- // // name: 'UPGRADE PLAN',
- // // buttonStyle: 'button-orange',
- // // click: (() => (this.openBrowserToPayment()))
- // // });
- // // context.JK.Banner.show({
- // // title: "Out of Time for the Month",
- // // html: context._.template($('#template-no-remaining-month-play-time').html(), {}, { variable: 'data' }),
- // // buttons
- // // });
- // // SessionActions.leaveSession.trigger(leaveBehavior);
- // // } else {
- // // this.app.notifyServerError(xhr, 'Unable to Join Session');
- // // }
- // } else {
- // this.app.notifyServerError(xhr, 'Unable to Join Session');
- // }
- })
-
-
-
-
- }
// useEffect(() => {
// if (!isConnected) return;
@@ -411,7 +414,7 @@ const JKSessionScreen = () => {
// ...session
// }));
// } else {
- // console.error("Invalid session ID or unable to fetch session");
+ // logger.error("Invalid session ID or unable to fetch session");
// //TODO: Handle invalid session (e.g., redirect or show error)
// }
// };
@@ -428,25 +431,6 @@ const JKSessionScreen = () => {
}
}, [connectionStatus]);
- // useEffect(() => {
- // if (!isConnected) return;
-
- // // Initialize session callbacks
- // jamClient.SessionRegisterCallback("HandleSessionCallback");
- // jamClient.RegisterRecordingCallbacks(
- // "HandleRecordingStartResult",
- // "HandleRecordingStopResult",
- // "HandleRecordingStarted",
- // "HandleRecordingStopped",
- // "HandleRecordingAborted"
- // );
- // jamClient.SessionSetConnectionStatusRefreshRate(1000);
- // jamClient.RegisterVolChangeCallBack("HandleVolumeChangeCallback");
- // jamClient.setMetronomeOpenCallback("HandleMetronomeCallback");
-
- // loadSessionData();
-
- // }, [isConnected]);
const loadSessionData = async () => {
try {
@@ -454,9 +438,9 @@ const JKSessionScreen = () => {
const controlState = await jamClient.SessionGetAllControlState(true);
const sampleRate = await jamClient.GetSampleRate();
- console.log('Session data loaded:', { audioConfigs, controlState, sampleRate });
+ logger.log('Session data loaded:', { audioConfigs, controlState, sampleRate });
} catch (error) {
- console.error('Error loading session data:', error);
+ logger.error('Error loading session data:', error);
}
};
@@ -482,34 +466,34 @@ const JKSessionScreen = () => {
// // Callback handlers (these would be implemented to handle WebSocket responses)
// const HandleSessionCallback = (data) => {
- // console.log('Session callback:', data);
+ // logger.log('Session callback:', data);
// // Handle session events
// };
// const HandleRecordingStarted = (data) => {
- // console.log('Recording started:', data);
+ // logger.log('Recording started:', data);
// // Update recording state
// };
// const HandleRecordingStopped = (data) => {
- // console.log('Recording stopped:', data);
+ // logger.log('Recording stopped:', data);
// // Update recording state
// };
// const HandleVolumeChangeCallback = (mixerId, isLeft, value, isMuted) => {
- // console.log('Volume changed:', { mixerId, isLeft, value, isMuted });
+ // logger.log('Volume changed:', { mixerId, isLeft, value, isMuted });
// // Update mixer state
// };
// const HandleBridgeCallback = (vuData) => {
- // console.log('Bridge callback:', vuData);
+ // logger.log('Bridge callback:', vuData);
// // Handle VU meter updates
// };
return (
{!isConnected && Connecting to backend...
}
-
+
diff --git a/jam-ui/src/context/CurrentSessionContext.js b/jam-ui/src/context/CurrentSessionContext.js
index 5aa4af1a1..26f3668b1 100644
--- a/jam-ui/src/context/CurrentSessionContext.js
+++ b/jam-ui/src/context/CurrentSessionContext.js
@@ -1,16 +1,29 @@
-import React, { createContext, useContext, useState } from 'react';
+import React, { createContext, useContext, useState, useRef } from 'react';
const CurrentSessionContext = createContext(null);
export const CurrentSessionProvider = ({ children }) => {
const [currentSession, setCurrentSession] = useState({});
+ const currentSessionIdRef = useRef(null);
const inSession = () => {
- return currentSession && currentSession.id;
+ return currentSessionIdRef.current !== null;
+ };
+
+ const setCurrentSessionId = (id) => {
+ console.log("Setting current session ID to: ", id);
+ currentSessionIdRef.current = id;
};
return (
-
+
{children}
);
diff --git a/jam-ui/src/context/JamClientContext.js b/jam-ui/src/context/JamClientContext.js
index 844f5d5b3..d058d22bf 100644
--- a/jam-ui/src/context/JamClientContext.js
+++ b/jam-ui/src/context/JamClientContext.js
@@ -1,15 +1,37 @@
import React, { createContext, useContext, useRef } from 'react';
-// Adjust the import path as necessary. fakeJamClientProxy.js vs jamClientProxy.js
-import { FakeJamClientProxy as JamClientProxy } from '../fakeJamClientProxy';
-//import JamClientProxy from '../jamClientProxy';
+import JamClientProxy from '../jamClientProxy';
+
+import { FakeJamClientProxy } from '../fakeJamClientProxy';
+import { FakeJamClientRecordings } from '../fakeJamClientRecordings';
+import { FakeJamClientMessages } from '../fakeJamClientMessages';
+import { useJamKazamApp } from './JamKazamAppContext';
const JamClientContext = createContext(null);
export const JamClientProvider = ({ children }) => {
//assign an instance of JamClientProxy to a ref so that it persists across renders
+ //if development environment, use FakeJamClientProxy
+ //otherwise use JamClientProxy
+ //initialize the proxy when the provider is mounted
+ //and provide it to the context value
+
+ //get the app instance from JamKazamAppContext
+ const app = useJamKazamApp();
+
const proxyRef = useRef(null);
- const proxy = new JamClientProxy(null, console); // Pass appropriate parameters
- proxyRef.current = proxy.init();
+ if (process.env.NODE_ENV === 'development') {
+ const fakeJamClientMessages = new FakeJamClientMessages();
+ const proxy = new FakeJamClientProxy(app, fakeJamClientMessages); // Pass appropriate parameters
+ proxyRef.current = proxy.init();
+ // For testing purposes, we can add some fake recordings
+ const fakeJamClientRecordings = new FakeJamClientRecordings(app, proxyRef.current, fakeJamClientMessages);
+ proxyRef.current.SetFakeRecordingImpl(fakeJamClientRecordings);
+ } else {
+ if (!proxyRef.current) {
+ const proxy = new JamClientProxy(app, console);
+ proxyRef.current = proxy.init();
+ }
+ }
return (
{children}
@@ -18,4 +40,3 @@ export const JamClientProvider = ({ children }) => {
};
export const useJamClient = () => useContext(JamClientContext);
-
\ No newline at end of file
diff --git a/jam-ui/src/context/JamKazamAppContext.js b/jam-ui/src/context/JamKazamAppContext.js
new file mode 100644
index 000000000..e89c326bc
--- /dev/null
+++ b/jam-ui/src/context/JamKazamAppContext.js
@@ -0,0 +1,14 @@
+import React, { createContext, useContext, useRef } from 'react';
+
+export const JamKazamAppContext = createContext(null);
+
+export const JamKazamAppProvider = ({ children }) => {
+ const value = {}; // Add your context value here
+ return (
+
+ {children}
+
+ );
+};
+
+export const useJamKazamApp = () => useContext(JamKazamAppContext);
diff --git a/jam-ui/src/fakeJamClient.js b/jam-ui/src/fakeJamClient.js
index 5baf48ad4..bbf3d22f9 100644
--- a/jam-ui/src/fakeJamClient.js
+++ b/jam-ui/src/fakeJamClient.js
@@ -845,6 +845,7 @@ export class FakeJamClient {
}
SessionRegisterCallback(callbackName) {
+ console.log("SessionRegisterCallback: " + callbackName);
this.eventCallbackName = callbackName;
if (this.callbackTimer) {
window.clearInterval(this.callbackTimer);
@@ -926,6 +927,7 @@ export class FakeJamClient {
SessionSetUserName(client_id, name) {}
doCallbacks() {
+ console.log("[fakeJamClient] doCallbacks - " + this.vuValue);
const names = ["vu"];
const ids = ["i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1",
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~2"];
@@ -1239,6 +1241,7 @@ export class FakeJamClient {
}
LastUsedProfileName() {
+ console.log("FakeJamClient: LastUsedProfileName");
return 'default';
}
@@ -1366,4 +1369,9 @@ export class FakeJamClient {
listTrackAssignments() {
return {};
}
+
+ UpdateSessionInfo(info) {
+ this.logger.debug("FakeJamClient: UpdateSessionInfo: %o", info);
+ //this.sessionInfo = info;
+ }
}
diff --git a/jam-ui/src/fakeJamClientMessages.js b/jam-ui/src/fakeJamClientMessages.js
new file mode 100644
index 000000000..73c403779
--- /dev/null
+++ b/jam-ui/src/fakeJamClientMessages.js
@@ -0,0 +1,71 @@
+// Fake Jam Client Messages - P2P message factory for recording operations
+import { generateUUID } from './helpers/utils.js';
+
+export class FakeJamClientMessages {
+ constructor() {
+ this.Types = {
+ START_RECORDING: 'start_recording',
+ START_RECORDING_ACK: 'start_recording_ack',
+ STOP_RECORDING: 'stop_recording',
+ STOP_RECORDING_ACK: 'stop_recording_ack',
+ ABORT_RECORDING: 'abort_recording'
+ };
+ }
+
+ startRecording(recordingId) {
+ const msg = {
+ type: this.Types.START_RECORDING,
+ msgId: generateUUID(),
+ recordingId
+ };
+ return msg;
+ }
+
+ startRecordingAck(recordingId, success, reason, detail) {
+ const msg = {
+ type: this.Types.START_RECORDING_ACK,
+ msgId: generateUUID(),
+ recordingId,
+ success,
+ reason,
+ detail
+ };
+ return msg;
+ }
+
+ stopRecording(recordingId, success = true, reason, detail) {
+ const msg = {
+ type: this.Types.STOP_RECORDING,
+ msgId: generateUUID(),
+ recordingId,
+ success,
+ reason,
+ detail
+ };
+ return msg;
+ }
+
+ stopRecordingAck(recordingId, success, reason, detail) {
+ const msg = {
+ type: this.Types.STOP_RECORDING_ACK,
+ msgId: generateUUID(),
+ recordingId,
+ success,
+ reason,
+ detail
+ };
+ return msg;
+ }
+
+ abortRecording(recordingId, reason, detail) {
+ const msg = {
+ type: this.Types.ABORT_RECORDING,
+ msgId: generateUUID(),
+ recordingId,
+ success: false,
+ reason,
+ detail
+ };
+ return msg;
+ }
+}
diff --git a/jam-ui/src/fakeJamClientProxy.js b/jam-ui/src/fakeJamClientProxy.js
index b5a69a893..62491aa59 100644
--- a/jam-ui/src/fakeJamClientProxy.js
+++ b/jam-ui/src/fakeJamClientProxy.js
@@ -10,17 +10,18 @@ export class FakeJamClientProxy {
return function (...args) {
return new Promise((resolve, reject) => {
try {
- console.log('[fakeJamClient]', target, prop, args);
+
if(target[prop]){
+ console.log('[FakeJamClientProxy]', prop, args);
const result = target[prop].apply(target, args);
resolve(result);
}else{
- console.error('[fakeJamClient] error: No such method in FakeJamClient', prop);
+ console.error('[FakeJamClientProxy] error: No such method in FakeJamClient', prop);
reject(`No such method in FakeJamClient: ${prop}`);
}
} catch (error) {
- console.error('[fakeJamClient] error:', prop, error);
+ console.error('[FakeJamClientProxy] error:', prop, error);
reject(error);
}
});
diff --git a/jam-ui/src/fakeJamClientRecordings.js b/jam-ui/src/fakeJamClientRecordings.js
new file mode 100644
index 000000000..4dd8e8fc3
--- /dev/null
+++ b/jam-ui/src/fakeJamClientRecordings.js
@@ -0,0 +1,215 @@
+// Fake Jam Client Recordings - simulates recording functionality for testing
+export class FakeJamClientRecordings {
+ constructor(app, fakeJamClient, p2pMessageFactory) {
+ this.logger = console;
+ this.app = app;
+ this.fakeJamClient = fakeJamClient;
+ this.p2pMessageFactory = p2pMessageFactory;
+
+ this.startRecordingResultCallbackName = null;
+ this.stopRecordingResultCallbackName = null;
+ this.startedRecordingResultCallbackName = null;
+ this.stoppedRecordingEventCallbackName = null;
+ this.abortedRecordingEventCallbackName = null;
+
+ this.startingSessionState = null;
+ this.stoppingSessionState = null;
+
+ this.currentRecordingId = null;
+ this.currentRecordingCreatorClientId = null;
+ this.currentRecordingClientIds = null;
+
+ // Register P2P callbacks
+ const callbacks = {};
+ callbacks[this.p2pMessageFactory.Types.START_RECORDING] = this.onStartRecording.bind(this);
+ callbacks[this.p2pMessageFactory.Types.START_RECORDING_ACK] = this.onStartRecordingAck.bind(this);
+ callbacks[this.p2pMessageFactory.Types.STOP_RECORDING] = this.onStopRecording.bind(this);
+ callbacks[this.p2pMessageFactory.Types.STOP_RECORDING_ACK] = this.onStopRecordingAck.bind(this);
+ callbacks[this.p2pMessageFactory.Types.ABORT_RECORDING] = this.onAbortRecording.bind(this);
+ fakeJamClient.RegisterP2PMessageCallbacks(callbacks);
+ }
+
+ timeoutStartRecordingTimer() {
+ eval(this.startRecordingResultCallbackName).call(this, this.startingSessionState.recordingId, { success: false, reason: 'client-no-response', detail: this.startingSessionState.groupedClientTracks[0] });
+ this.startingSessionState = null;
+ }
+
+ timeoutStopRecordingTimer() {
+ eval(this.stopRecordingResultCallbackName).call(this, this.stoppingSessionState.recordingId, { success: false, reason: 'client-no-response', detail: this.stoppingSessionState.groupedClientTracks[0] });
+ }
+
+ StartRecording(recordingId, clients) {
+ this.startingSessionState = {};
+
+ // Expect all clients to respond within 1 second to mimic reliable UDP layer
+ this.startingSessionState.aggregatingStartResultsTimer = setTimeout(() => this.timeoutStartRecordingTimer(), 1000);
+ this.startingSessionState.recordingId = recordingId;
+ this.startingSessionState.groupedClientTracks = this.copyClientIds(clients, this.app.clientId);
+
+ // Store current recording data
+ this.currentRecordingId = recordingId;
+ this.currentRecordingCreatorClientId = this.app.clientId;
+ this.currentRecordingClientIds = this.copyClientIds(clients, this.app.clientId);
+
+ if (this.startingSessionState.groupedClientTracks.length === 0) {
+ // If no clients but 'self', declare successful recording immediately
+ this.finishSuccessfulStart(recordingId);
+ } else {
+ // Signal all other connected clients that recording has started
+ for (const clientId of this.startingSessionState.groupedClientTracks) {
+ this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.startRecording(recordingId)));
+ }
+ }
+ }
+
+ StopRecording(recordingId, clients, result) {
+ if (this.startingSessionState) {
+ // Currently starting a session
+ // TODO
+ }
+
+ if (!result) {
+ result = { success: true };
+ }
+
+ this.stoppingSessionState = {};
+
+ // Expect all clients to respond within 1 second
+ this.stoppingSessionState.aggregatingStopResultsTimer = setTimeout(() => this.timeoutStopRecordingTimer(), 1000);
+ this.stoppingSessionState.recordingId = recordingId;
+ this.stoppingSessionState.groupedClientTracks = this.copyClientIds(clients, this.app.clientId);
+
+ if (this.stoppingSessionState.groupedClientTracks.length === 0) {
+ this.finishSuccessfulStop(recordingId);
+ } else {
+ // Signal all other connected clients that recording has stopped
+ for (const clientId of this.stoppingSessionState.groupedClientTracks) {
+ this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.stopRecording(recordingId, result.success, result.reason, result.detail)));
+ }
+ }
+ }
+
+ AbortRecording(recordingId, errorReason, errorDetail) {
+ // TODO: check recordingId
+ this.fakeJamClient.SendP2PMessage(this.currentRecordingCreatorClientId, JSON.stringify(this.p2pMessageFactory.abortRecording(recordingId, errorReason, errorDetail)));
+ }
+
+ onStartRecording(from, payload) {
+ this.logger.debug("received start recording request from " + from);
+ if (window.JK?.SessionStore?.isRecording) {
+ // Reject the request
+ this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.startRecordingAck(payload.recordingId, false, "already-recording", null)));
+ } else {
+ // Accept and tell frontend we are recording
+ this.currentRecordingId = payload.recordingId;
+ this.currentRecordingCreatorClientId = from;
+
+ this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.startRecordingAck(payload.recordingId, true, null, null)));
+ eval(this.startedRecordingResultCallbackName).call(this, payload.recordingId, { success: true }, from);
+ }
+ }
+
+ onStartRecordingAck(from, payload) {
+ this.logger.debug("received start recording ack from " + from);
+
+ if (this.startingSessionState) {
+ if (payload.success) {
+ const index = this.startingSessionState.groupedClientTracks.indexOf(from);
+ this.startingSessionState.groupedClientTracks.splice(index, 1);
+
+ if (this.startingSessionState.groupedClientTracks.length === 0) {
+ this.finishSuccessfulStart(payload.recordingId);
+ }
+ } else {
+ // TODO: handle error
+ this.logger.warn("received unsuccessful start_record_ack from: " + from);
+ }
+ } else {
+ this.logger.warn("received start_record_ack when no recording starting from: " + from);
+ }
+ }
+
+ onStopRecording(from, payload) {
+ this.logger.debug("received stop recording request from " + from);
+
+ // TODO: check recordingId and if currently recording
+ this.fakeJamClient.SendP2PMessage(from, JSON.stringify(this.p2pMessageFactory.stopRecordingAck(payload.recordingId, true)));
+
+ if (this.stopRecordingResultCallbackName) {
+ eval(this.stopRecordingResultCallbackName).call(this, payload.recordingId, { success: payload.success, reason: payload.reason, detail: from });
+ }
+ }
+
+ onStopRecordingAck(from, payload) {
+ this.logger.debug("received stop recording ack from " + from);
+
+ if (this.stoppingSessionState) {
+ if (payload.success) {
+ const index = this.stoppingSessionState.groupedClientTracks.indexOf(from);
+ this.stoppingSessionState.groupedClientTracks.splice(index, 1);
+
+ if (this.stoppingSessionState.groupedClientTracks.length === 0) {
+ this.finishSuccessfulStop(payload.recordingId);
+ }
+ } else {
+ // TODO: handle error
+ this.logger.error("client responded with error: ", payload);
+ }
+ } else {
+ // TODO: error case
+ }
+ }
+
+ onAbortRecording(from, payload) {
+ this.logger.debug("received abort recording from " + from);
+
+ // TODO: check if currently recording and matches payload.recordingId
+
+ if (this.app.clientId === this.currentRecordingCreatorClientId) {
+ // Ask frontend to stop
+ for (const clientId of this.currentRecordingClientIds) {
+ this.fakeJamClient.SendP2PMessage(clientId, JSON.stringify(this.p2pMessageFactory.abortRecording(this.currentRecordingId, payload.reason, from)));
+ }
+ } else {
+ this.logger.debug("only creator deals with abort request. sent from:" + from + " reason: " + payload.errorReason);
+ }
+
+ eval(this.abortedRecordingEventCallbackName).call(this, payload.recordingId, { success: payload.success, reason: payload.reason, detail: from });
+ }
+
+ RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName, stoppedRecordingCallbackName, abortedRecordingCallbackName) {
+ this.startRecordingResultCallbackName = startRecordingCallbackName;
+ this.stopRecordingResultCallbackName = stopRecordingCallbackName;
+ this.startedRecordingResultCallbackName = startedRecordingCallbackName;
+ this.stoppedRecordingEventCallbackName = stoppedRecordingCallbackName;
+ this.abortedRecordingEventCallbackName = abortedRecordingCallbackName;
+ }
+
+ copyClientIds(clientIds, myClientId) {
+ const newClientIds = [];
+ for (const clientId of clientIds) {
+ if (clientId !== myClientId) {
+ newClientIds.push(clientId);
+ }
+ }
+ return newClientIds;
+ }
+
+ finishSuccessfulStart(recordingId) {
+ clearTimeout(this.startingSessionState.aggregatingStartResultsTimer);
+ this.startingSessionState = null;
+ eval(this.startRecordingResultCallbackName).call(this, recordingId, { success: true });
+ }
+
+ finishSuccessfulStop(recordingId, errorReason) {
+ clearTimeout(this.stoppingSessionState.aggregatingStopResultsTimer);
+ this.stoppingSessionState = null;
+ const result = { success: true };
+ if (errorReason) {
+ result.success = false;
+ result.reason = errorReason;
+ result.detail = "";
+ }
+ eval(this.stopRecordingResultCallbackName).call(this, recordingId, result);
+ }
+}
diff --git a/jam-ui/src/helpers/MessageFactory.js b/jam-ui/src/helpers/MessageFactory.js
index 9947c9441..ab1e1197d 100644
--- a/jam-ui/src/helpers/MessageFactory.js
+++ b/jam-ui/src/helpers/MessageFactory.js
@@ -3,8 +3,6 @@
* Based on the legacy AAB_message_factory.js, updated for ES6+ and React compatibility
*/
-import { getCookieValue } from './utils.js';
-
// Message types for WebSocket communication
export const MessageType = {
LOGIN: "LOGIN",
diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js
index 7e916267a..8a45f45bb 100644
--- a/jam-ui/src/helpers/rest.js
+++ b/jam-ui/src/helpers/rest.js
@@ -230,11 +230,12 @@ export const getSessionHistory = (id, includePending = false) => {
});
};
-const joinSession = (options = {}) => {
+export const joinSession = (options = {}) => {
+ const { session_id, ...rest } = options;
return new Promise((resolve, reject) => {
- apiFetch(`/sessions/${options.session_id}/participants`, {
+ apiFetch(`/sessions/${session_id}/participants`, {
method: 'POST',
- body: JSON.stringify(options)
+ body: JSON.stringify(rest)
})
.then(response => resolve(response))
.catch(error => reject(error));
@@ -735,7 +736,40 @@ export const getClientDownloads = () => {
.then(response => resolve(response))
.catch(error => reject(error));
});
-}
+};
+
+// Recording-related functions
+export const startRecording = (options) => {
+ return new Promise((resolve, reject) => {
+ apiFetch('/recordings/start', {
+ method: 'POST',
+ body: JSON.stringify(options)
+ })
+ .then(response => resolve(response))
+ .catch(error => reject(error));
+ });
+};
+
+export const stopRecording = (options) => {
+ const { id, ...rest } = options;
+ return new Promise((resolve, reject) => {
+ apiFetch(`/recordings/${id}/stop`, {
+ method: 'POST',
+ body: JSON.stringify(rest)
+ })
+ .then(response => resolve(response))
+ .catch(error => reject(error));
+ });
+};
+
+export const getRecordingPromise = (options) => {
+ const { id } = options;
+ return new Promise((resolve, reject) => {
+ apiFetch(`/recordings/${id}`)
+ .then(response => resolve(response))
+ .catch(error => reject(error));
+ });
+};
export const getObsPluginDownloads = () => {
return new Promise((resolve, reject) => {
apiFetch(`/artifacts/OBSPlugin`)
@@ -881,4 +915,4 @@ export const createDiagnostic = (options = {}) => {
.then(response => resolve(response))
.catch(error => reject(error));
});
-}
\ No newline at end of file
+}
diff --git a/jam-ui/src/hooks/useGearUtils.js b/jam-ui/src/hooks/useGearUtils.js
index 4bcbf5889..fd63207a1 100644
--- a/jam-ui/src/hooks/useGearUtils.js
+++ b/jam-ui/src/hooks/useGearUtils.js
@@ -343,13 +343,7 @@ const useGearUtils = () => {
const guardAgainstBadNetworkScore = useCallback((app) => {
return new Promise(async (resolve, reject) => {
if (!(await validNetworkScore())) {
- app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, async () => {
- if (await validNetworkScore()) {
- resolve();
- } else {
- reject();
- }
- });
+ reject();
} else {
resolve();
}
@@ -369,13 +363,7 @@ const useGearUtils = () => {
const guardAgainstInvalidGearConfiguration = useCallback((app) => {
return new Promise(async (resolve, reject) => {
if ((await jamClientFTUEGetAllAudioConfigurations()).length === 0) {
- app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, async () => {
- if (await hasGoodActiveProfile() && await validNetworkScore()) {
- resolve();
- } else {
- reject();
- }
- });
+ reject();
} else {
resolve();
}
@@ -387,21 +375,9 @@ const useGearUtils = () => {
console.log('guardAgainstActiveProfileMissing: backendInfo %o', backendInfo);
if (backendInfo.error && backendInfo.reason === 'no_profile' && (await jamClient.FTUEGetAllAudioConfigurations()).length > 0) {
reject({ reason: 'handled', nav: '/client#/account/audio' });
- // Assuming Banner is available
console.log('No Active Profile', 'We\'ve sent you to the audio profile screen...');
} else if (backendInfo.error && backendInfo.reason === 'device_failure') {
- app.layout.showDialog('audio-profile-invalid-dialog').one(EVENTS.DIALOG_CLOSED, (e, data) => {
- if (!data.result || data.result === 'cancel') {
- reject({ reason: 'handled', nav: 'BACK' });
- } else if (data.result === 'configure_gear') {
- reject({ reason: 'handled', nav: '/client#/account/audio' });
- } else if (data.result === 'session') {
- resolve();
- } else {
- console.error('unknown result condition in audio-profile-invalid-dialog:', data.result);
- reject();
- }
- });
+ reject({ reason: 'handled', nav: '/client#/account/audio' });
} else {
resolve();
}
@@ -432,7 +408,7 @@ const useGearUtils = () => {
}, []);
const validNetworkScore = useCallback(async () => {
- return !window.gon?.global?.network_test_required || isNetworkTestSkipped() || (await jamClient.GetNetworkTestScore()) >= 2;
+ return isNetworkTestSkipped() || (await jamClient.GetNetworkTestScore()) >= 2;
}, [jamClient, isNetworkTestSkipped]);
const isRestartingAudio = useCallback(() => {
@@ -503,33 +479,13 @@ const useGearUtils = () => {
}, [jamClient]);
// Guard against single player profile - simplified, as it depends on app
+ //TODO: revisit this
const guardAgainstSinglePlayerProfile = useCallback((app, beforeCallback) => {
return new Promise(async (resolve, reject) => {
- const canPlayWithOthers = await canPlayWithOthers();
+ const canPlayResult = await canPlayWithOthers();
- if (!canPlayWithOthers.canPlay) {
- console.log('guarding against single player profile');
- const $dialog = app.layout.showDialog('single-player-profile-dialog');
-
- if (beforeCallback) {
- $dialog.one(EVENTS.DIALOG_CLOSED, beforeCallback);
- }
-
- $dialog.one(EVENTS.DIALOG_CLOSED, (e, data) => {
- if (data.canceled) {
- reject({ reason: 'canceled', controlled_location: false });
- } else {
- if (data.result.choice === 'private_session') {
- // Handle private session creation
- reject({ reason: 'private_session', controlled_location: true });
- } else if (data.result.choice === 'gear_setup') {
- reject({ reason: data.result.choice, controlled_location: true });
- } else {
- reject({ reason: 'unknown', controlled_location: false });
- console.error('unknown choice:', data.result.choice);
- }
- }
- });
+ if (!canPlayResult.canPlay) {
+ reject();
} else {
resolve();
}
diff --git a/jam-ui/src/hooks/useMixerHelpers.js b/jam-ui/src/hooks/useMixerHelpers.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/jam-ui/src/hooks/useRecordingHelpers.js b/jam-ui/src/hooks/useRecordingHelpers.js
new file mode 100644
index 000000000..ee7ae6ed1
--- /dev/null
+++ b/jam-ui/src/hooks/useRecordingHelpers.js
@@ -0,0 +1,436 @@
+import { useState, useCallback, useRef, useEffect, useContext } from 'react';
+import { useCurrentSession } from '../context/CurrentSessionContext';
+import { useJamKazamApp } from '../context/JamKazamAppContext';
+import { startRecording as startRecordingRest, stopRecording as stopRecordingRest, getRecordingPromise } from '../helpers/rest';
+
+const useRecordingHelpers = (jamClient) => {
+ const app = useJamKazamApp();
+ const { currentSession } = useCurrentSession();
+
+ // State variables from original RecordingModel
+ const [currentRecording, setCurrentRecording] = useState(null);
+ const [currentOrLastRecordingId, setCurrentOrLastRecordingId] = useState(null);
+ const [currentRecordingId, setCurrentRecordingId] = useState(null);
+ const [thisClientStartedRecording, setThisClientStartedRecording] = useState(false);
+ const [currentlyRecording, setCurrentlyRecording] = useState(false);
+ const [startingRecording, setStartingRecording] = useState(false);
+ const [stoppingRecording, setStoppingRecording] = useState(false);
+ const [waitingOnServerStop, setWaitingOnServerStop] = useState(false);
+ const [waitingOnClientStop, setWaitingOnClientStop] = useState(false);
+
+ const waitingOnStopTimer = useRef(null);
+ const sessionId = currentSession?.id;
+
+ // Get state function
+ const getState = useCallback(() => ({
+ waitingOnClientStop,
+ waitingOnServerStop,
+ stoppingRecording,
+ startingRecording
+ }), [waitingOnClientStop, waitingOnServerStop, stoppingRecording, startingRecording]);
+
+ // Check if recording
+ const isRecording = useCallback((recordingId) => {
+ if (recordingId) {
+ return recordingId === currentRecordingId;
+ } else {
+ return currentlyRecording;
+ }
+ }, [currentRecordingId, currentlyRecording]);
+
+ // Get this client started recording
+ const getThisClientStartedRecording = useCallback(() => thisClientStartedRecording, [thisClientStartedRecording]);
+
+ // Reset function
+ const reset = useCallback((sessionId) => {
+ console.log("[RecordingState]: reset");
+ setCurrentlyRecording(false);
+ setWaitingOnServerStop(false);
+ setWaitingOnClientStop(false);
+ if (waitingOnStopTimer.current) {
+ clearTimeout(waitingOnStopTimer.current);
+ waitingOnStopTimer.current = null;
+ }
+ setCurrentRecording(null);
+ setCurrentRecordingId(null);
+ setStoppingRecording(false);
+ setThisClientStartedRecording(false);
+ }, []);
+
+ // Group tracks to client
+ const groupTracksToClient = useCallback((recording) => {
+ const groupedTracks = {};
+ const recordingTracks = recording.recorded_tracks || [];
+ for (let i = 0; i < recordingTracks.length; i++) {
+ const clientId = recordingTracks[i].client_id;
+ let tracksForClient = groupedTracks[clientId];
+ if (!tracksForClient) {
+ tracksForClient = [];
+ groupedTracks[clientId] = tracksForClient;
+ }
+ tracksForClient.push(recordingTracks[i]);
+ }
+ return Object.keys(groupedTracks);
+ }, []);
+
+ // Start recording
+ const startRecording = useCallback(async (recordSettings) => {
+ const recordVideo = recordSettings.recordingType === 'JK.RECORD_TYPE_BOTH';
+
+ // Trigger startingRecording event
+ // In React, we might use a callback or context to notify parent components
+
+ setCurrentlyRecording(true);
+ setStoppingRecording(false);
+ setThisClientStartedRecording(true);
+
+ // Context RecordingActions.startingRecording would be handled by parent or context
+
+ try {
+ const recording = await startRecordingRest({ music_session_id: sessionId, record_video: recordVideo });
+ setCurrentRecordingId(recording.id);
+ setCurrentOrLastRecordingId(recording.id);
+
+ const groupedTracks = groupTracksToClient(recording);
+ console.log("jamClient#StartMediaRecording", recordSettings);
+ await jamClient.StartMediaRecording(recording.id, groupedTracks, recordSettings);
+ } catch (jqXHR) {
+ console.warn("failed to startRecording due to server issue:", jqXHR.responseJSON);
+ const details = { clientId: app.clientId, reason: 'rest', detail: jqXHR.responseJSON, isRecording: false };
+ // Trigger startedRecording event
+ setCurrentlyRecording(false);
+ // Context RecordingActions.startedRecording(details);
+ }
+
+ return true;
+ }, [sessionId, groupTracksToClient, jamClient, app.clientId]);
+
+ // Transition to stopped
+ const transitionToStopped = useCallback(() => {
+ console.log("[RecordingState] transitionToStopped");
+ setCurrentlyRecording(false);
+ setCurrentRecording(null);
+ setCurrentRecordingId(null);
+ if (waitingOnStopTimer.current) {
+ clearTimeout(waitingOnStopTimer.current);
+ waitingOnStopTimer.current = null;
+ }
+ }, []);
+
+ // Attempt transition to stop
+ const attemptTransitionToStop = useCallback((recordingId, errorReason, errorDetail) => {
+ if (!waitingOnClientStop && !waitingOnServerStop) {
+ transitionToStopped();
+ const details = { recordingId, reason: errorReason, detail: errorDetail, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ }
+ }, [waitingOnClientStop, waitingOnServerStop, transitionToStopped]);
+
+ // Timeout transition to stop
+ const timeoutTransitionToStop = useCallback(() => {
+ waitingOnStopTimer.current = null;
+ transitionToStopped();
+ // Trigger stoppedRecordingFailed event
+ }, [transitionToStopped]);
+
+ // Stop recording
+ const stopRecording = useCallback(async (recordingId, reason, detail) => {
+ const userInitiated = recordingId == null && reason == null && detail == null;
+ const recording = await currentRecording;
+
+ console.log(`[RecordingState]: stopRecording userInitiated=${userInitiated} thisClientStartedRecording=${thisClientStartedRecording} reason=${reason} detail=${detail}`);
+
+ if (stoppingRecording) {
+ console.log("ignoring stopRecording because we are already stopping");
+ return;
+ }
+ setStoppingRecording(true);
+
+ setWaitingOnServerStop(true);
+ setWaitingOnClientStop(true);
+ waitingOnStopTimer.current = setTimeout(timeoutTransitionToStop, 5000);
+
+ // Trigger stoppingRecording event
+ // Context RecordingActions.stoppingRecording
+
+ try {
+ const recording = await currentRecording;
+ const groupedTracks = groupTracksToClient(recording);
+
+ await jamClient.FrontStopRecording(recording.id, groupedTracks);
+
+ if (thisClientStartedRecording) {
+ try {
+ await stopRecordingRest({ id: recording.id });
+ setWaitingOnServerStop(false);
+ attemptTransitionToStop(recording.id, reason, detail);
+ setStoppingRecording(false);
+ } catch (error) {
+ setStoppingRecording(false);
+ if (error.status === 422) {
+ setWaitingOnServerStop(false);
+ attemptTransitionToStop(recording.id, reason, detail);
+ } else {
+ console.error("unable to stop recording", error);
+ transitionToStopped();
+ const details = {
+ recordingId: recording.id,
+ reason: 'rest',
+ details: error,
+ isRecording: false
+ };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ setStoppingRecording(false);
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Error in stopRecording:", error);
+ }
+
+ return true;
+ }, [currentRecording, thisClientStartedRecording, stoppingRecording, groupTracksToClient, jamClient, timeoutTransitionToStop, attemptTransitionToStop, transitionToStopped]);
+
+ // Abort recording
+ const abortRecording = useCallback(async (recordingId, errorReason, errorDetail) => {
+ await jamClient.AbortRecording(recordingId, { reason: errorReason, detail: errorDetail, success: false });
+ }, [jamClient]);
+
+ // Handle recording start result
+ const handleRecordingStartResult = useCallback((recordingId, result) => {
+ console.log("[RecordingState] handleRecordingStartResult", { recordingId, result, currentRecordingId, currentlyRecording });
+
+ const { success, reason, detail } = result;
+
+ if (success) {
+ const details = { clientId: app.clientId, isRecording: true };
+ // Trigger startedRecording event
+ // Context RecordingActions.startedRecording(details)
+ } else {
+ setCurrentlyRecording(false);
+ console.error("unable to start the recording", reason, detail);
+ const details = { clientId: app.clientId, reason, detail, isRecording: false };
+ // Trigger startedRecording event
+ // Context RecordingActions.startedRecording(details)
+ }
+ }, [app.clientId, currentRecordingId, currentlyRecording]);
+
+ // Handle recording stop result
+ const handleRecordingStopResult = useCallback((recordingId, result) => {
+ console.log("[RecordingState] handleRecordingStopResult", result);
+ const { success, reason, detail } = result;
+
+ setWaitingOnClientStop(false);
+
+ if (success) {
+ attemptTransitionToStop(recordingId, reason, detail);
+ } else {
+ transitionToStopped();
+ console.error("backend unable to stop the recording", reason, detail);
+ const details = { recordingId, reason, detail, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ }
+ }, [attemptTransitionToStop]);
+
+ // Handle recording started
+ const handleRecordingStarted = useCallback(async (recordingId, result, clientId) => {
+ reset(sessionId);
+ // Context RecordingActions.resetRecordingState()
+
+ console.log("[RecordingState] handleRecordingStarted called", { recordingId, result, clientId, currentRecordingId, currentlyRecording, sessionId });
+
+ const { success, reason, detail } = result;
+
+ console.log("[RecordingState] Attempting to fetch recording data from server", { recordingId, sessionId });
+
+ try {
+ const recording = await getRecordingPromise({ id: recordingId });
+ console.log("[RecordingState] Successfully fetched recording data from server", { recordingId: recording.id, ownerId: recording.owner?.id });
+
+ postServerRecordingFetch(recording);
+ } catch (error) {
+ console.error("[RecordingState] Failed to fetch recording data", { recordingId, error });
+ }
+ }, [sessionId, reset]);
+
+ // Post server recording fetch
+ const postServerRecordingFetch = useCallback((recording) => {
+ if (currentRecordingId == null) {
+ setCurrentRecordingId(recording.id);
+ setCurrentOrLastRecordingId(recording.id);
+
+ const details = { recordingId: currentRecordingId, isRecording: false };
+ // Trigger startingRecording event
+ // Context RecordingActions.startingRecording(details)
+ setCurrentlyRecording(true);
+
+ const startedDetails = { clientId: app.clientId, recordingId: currentRecordingId, isRecording: true };
+ // Trigger startedRecording event
+ // Context RecordingActions.startedRecording(startedDetails)
+ } else if (currentRecordingId === recording.id) {
+ // noop
+ } else {
+ console.error("[RecordingState] we've missed the stop of previous recording", currentRecordingId, recording.id);
+ // Show alert
+ }
+ }, [currentRecordingId, app.clientId]);
+
+ // Handle recording stopped
+ const handleRecordingStopped = useCallback(async (recordingId, result) => {
+ console.log("[RecordingState] handleRecordingStopped event_id=" + recordingId + " current_id=" + currentRecordingId, result);
+
+ const { success, reason, detail } = result;
+
+ const stoppingDetails = { recordingId, reason, detail, isRecording: true };
+ // Trigger stoppingRecording event
+ // Context RecordingActions.stoppingRecording(stoppingDetails)
+
+ if (recordingId == null || recordingId === "") {
+ transitionToStopped();
+ const stoppedDetails = { recordingId, reason, detail, isRecording: false };
+ // Context RecordingActions.stoppedRecording(stoppedDetails)
+ return;
+ }
+
+ try {
+ await stopRecordingRest({ id: recordingId });
+ transitionToStopped();
+ const details = { recordingId, reason, detail, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ } catch (error) {
+ if (error.status === 422) {
+ console.log("recording already stopped", error);
+ transitionToStopped();
+ const details = { recordingId, reason, detail, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ } else if (error.status === 404) {
+ console.log("recording is already deleted", error);
+ transitionToStopped();
+ const details = { recordingId, reason, detail, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ } else {
+ transitionToStopped();
+ const details = { recordingId, reason: error.message || 'error', detail: error, isRecording: false };
+ // Trigger stoppedRecording event
+ // Context RecordingActions.stoppedRecording(details)
+ }
+ }
+ }, [currentRecordingId, transitionToStopped]);
+
+ // Handle recording aborted
+ const handleRecordingAborted = useCallback(async (recordingId, result) => {
+ console.log("[RecordingState] handleRecordingAborted");
+ if (recordingId === "video") {
+ // Handle video abort
+ return;
+ }
+
+ const { success, reason, detail } = result;
+
+ setStoppingRecording(false);
+
+ const details = { recordingId, reason, detail, isRecording: false };
+ // Trigger abortedRecording event
+ // Context RecordingActions.abortedRecording(details)
+
+ try {
+ await stopRecordingRest({ id: recordingId });
+ } catch (error) {
+ console.error("Error stopping recording in abort:", error);
+ } finally {
+ setCurrentlyRecording(false);
+ }
+ }, []);
+
+ // Stop recording if needed
+ const stopRecordingIfNeeded = useCallback(() => {
+ return new Promise((resolve) => {
+ if (!currentlyRecording) {
+ resolve();
+ } else {
+ // In React, we might use a state or callback to wait for stoppedRecording
+ // For now, assume immediate resolve
+ resolve();
+ }
+ });
+ }, [currentlyRecording]);
+
+ // Get current recording state
+ const getCurrentRecordingState = useCallback(async () => {
+ let recording = null;
+ const session = currentSession;
+ let recordingId = null;
+
+ if (jamClient) {
+ try {
+ recordingId = await jamClient.GetCurrentRecordingId();
+ } catch (error) {
+ console.error("Error getting current recording ID:", error);
+ }
+ }
+
+ const isRecording = recordingId && recordingId !== "";
+
+ if (session && isRecording) {
+ try {
+ recording = await getRecordingPromise({ id: recordingId });
+ } catch (error) {
+ if (error.status !== 404) {
+ console.error("[RecordingState] Failed to fetch server recording state", { recordingId, error });
+ }
+ }
+ }
+
+ if (!session) {
+ console.debug("no session, so no recording");
+ return { isRecording: false, serverRecording: null, thisClientStartedRecording: false };
+ }
+
+ return {
+ isRecording,
+ isServerRecording: !!recording,
+ thisClientStartedRecording
+ };
+ }, [currentSession, jamClient, thisClientStartedRecording]);
+
+ // Initialize
+ useEffect(() => {
+ // Register global handlers if needed
+ if (window) {
+ window.JK = window.JK || {};
+ window.JK.HandleRecordingStartResult = handleRecordingStartResult;
+ window.JK.HandleRecordingStopResult = handleRecordingStopResult;
+ window.JK.HandleRecordingStopped = handleRecordingStopped;
+ window.JK.HandleRecordingStarted = handleRecordingStarted;
+ window.JK.HandleRecordingAborted = handleRecordingAborted;
+ }
+ }, [handleRecordingStartResult, handleRecordingStopResult, handleRecordingStopped, handleRecordingStarted, handleRecordingAborted]);
+
+ return {
+ // State
+ currentlyRecording,
+ startingRecording,
+ stoppingRecording,
+ currentRecordingId,
+ currentOrLastRecordingId,
+
+ // Functions
+ startRecording,
+ stopRecording,
+ abortRecording,
+ reset,
+ isRecording,
+ getThisClientStartedRecording,
+ stopRecordingIfNeeded,
+ getCurrentRecordingState,
+ getState,
+ };
+};
+
+export default useRecordingHelpers;
diff --git a/jam-ui/src/hooks/useSessionEnter.js b/jam-ui/src/hooks/useSessionEnter.js
index ae2c853c5..efc1a5cb2 100644
--- a/jam-ui/src/hooks/useSessionEnter.js
+++ b/jam-ui/src/hooks/useSessionEnter.js
@@ -1,22 +1,24 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import { useJamClient } from '../context/JamClientContext';
import useGearUtils from './useGearUtils';
+import useTrackHelpers from './useTrackHelpers';
export default function useSessionEnter() {
const jamClient = useJamClient();
const { isNoInputProfile } = useGearUtils();
const logger = console; // Replace with your logging mechanism if needed
+ const { getUserTracks } = useTrackHelpers();
+
// State to hold pending promises and their resolvers
const pendingPromisesRef = useRef(new Map());
const resolvePendingPromises = useCallback((inputTracks) => {
- logger.debug("obtained tracks at start of session");
for (const { resolve } of pendingPromisesRef.current.values()) {
resolve(inputTracks);
}
pendingPromisesRef.current.clear();
- }, [logger]);
+ }, []);
const rejectPendingPromises = useCallback((reason) => {
for (const { reject } of pendingPromisesRef.current.values()) {
@@ -28,35 +30,35 @@ export default function useSessionEnter() {
// Event handlers
const onWatchedInputs = useCallback((inputTracks) => {
resolvePendingPromises(inputTracks);
- }, [resolvePendingPromises]);
+ }, []);
const onMixersChanged = useCallback((type, text, trackInfo) => {
if (text === 'RebuildAudioIoControl' && trackInfo.userTracks.length > 0) {
- logger.debug("obtained tracks at start of session");
resolvePendingPromises(trackInfo.userTracks);
}
- }, [resolvePendingPromises, logger]);
+ }, []);
// Register callbacks with jamClient
- useEffect(() => {
- if (jamClient && jamClient.registerCallback) {
- jamClient.registerCallback('onWatchedInputs', onWatchedInputs);
- jamClient.registerCallback('onMixersChanged', onMixersChanged);
+ // useEffect(() => {
+ // if (jamClient && jamClient.registerCallback) {
+ // jamClient.registerCallback('onWatchedInputs', onWatchedInputs);
+ // jamClient.registerCallback('onMixersChanged', onMixersChanged);
- return () => {
- jamClient.unregisterCallback('onWatchedInputs', onWatchedInputs);
- jamClient.unregisterCallback('onMixersChanged', onMixersChanged);
- };
- }
- }, [jamClient, onWatchedInputs, onMixersChanged]);
+ // return () => {
+ // jamClient.unregisterCallback('onWatchedInputs', onWatchedInputs);
+ // jamClient.unregisterCallback('onMixersChanged', onMixersChanged);
+ // };
+ // }
+ // }, [jamClient, onWatchedInputs, onMixersChanged]);
const waitForSessionPageEnterDone = useCallback(() => {
- return new Promise((resolve, reject) => {
+ return new Promise(async (resolve, reject) => {
// Check if tracks are already available
- const inputTracks = jamClient ? jamClient.getUserTracks() : [];
+ const inputTracks = await getUserTracks();
- logger.debug("isNoInputProfile", isNoInputProfile());
- if (inputTracks.length > 0 || isNoInputProfile()) {
+ logger.debug("isNoInputProfile", await isNoInputProfile());
+ logger.debug("inputTracks", inputTracks);
+ if (inputTracks.length > 0 || await isNoInputProfile()) {
logger.debug("on page enter, tracks are already available");
resolve(inputTracks);
return;
@@ -83,14 +85,14 @@ export default function useSessionEnter() {
.then(resolve)
.catch(reject);
});
- }, [jamClient, isNoInputProfile, logger]);
+ }, []);
// Cleanup on unmount
useEffect(() => {
return () => {
rejectPendingPromises('component_unmounted');
};
- }, [rejectPendingPromises]);
+ }, []);
return {
waitForSessionPageEnterDone,
diff --git a/jam-ui/src/hooks/useTrackHelpers.js b/jam-ui/src/hooks/useTrackHelpers.js
new file mode 100644
index 000000000..586d27100
--- /dev/null
+++ b/jam-ui/src/hooks/useTrackHelpers.js
@@ -0,0 +1,103 @@
+import { useCallback } from 'react';
+import { useJamClient } from '../context/JamClientContext';
+import {
+ ChannelGroupIds,
+ server_to_client_instrument_map,
+ client_to_server_instrument_map
+} from '../helpers/globals';
+
+const logger = console;
+
+export default function useTrackHelpers() {
+ const jamClient = useJamClient();
+
+ const getTrackInfo = useCallback(async (masterTracks) => {
+ if (masterTracks === undefined) {
+ masterTracks = await jamClient.SessionGetAllControlState(true);
+ }
+
+ const userTracks = await getUserTracks(masterTracks);
+ const backingTracks = await getBackingTracks(masterTracks);
+ const metronomeTracks = await getTracks(ChannelGroupIds.MetronomeGroup, masterTracks);
+
+ return {
+ userTracks,
+ backingTracks,
+ metronomeTracks
+ };
+ }, [jamClient]);
+
+ const getTracks = useCallback(async (groupId, allTracks) => {
+ const tracks = [];
+
+ if (!allTracks) {
+ allTracks = await jamClient.SessionGetAllControlState(true);
+ }
+
+ for (const track of allTracks) {
+ if (track.group_id === groupId) {
+ tracks.push(track);
+ }
+ }
+
+ return tracks;
+ }, [jamClient]);
+
+ const getBackingTracks = useCallback(async (allTracks) => {
+ const mediaTracks = await getTracks(ChannelGroupIds.MediaTrackGroup, allTracks);
+
+ const backingTracks = [];
+ mediaTracks.forEach((mediaTrack) => {
+ if (mediaTrack.media_type === "BackingTrack" && !mediaTrack.managed) {
+ const track = {};
+ track.client_track_id = mediaTrack.persisted_track_id;
+ track.client_resource_id = mediaTrack.rid;
+ track.filename = mediaTrack.filename;
+ backingTracks.push(track);
+ }
+ });
+
+ return backingTracks;
+ }, [getTracks]);
+
+ const getUserTracks = useCallback(async (allTracks) => {
+ const localMusicTracks = [];
+ const localMidiTracks = [];
+
+ const audioTracks = await getTracks(ChannelGroupIds.AudioInputMusicGroup, allTracks);
+ const midiTracks = await getTracks(ChannelGroupIds.MidiInputMusicGroup, allTracks);
+ localMusicTracks.push(...audioTracks, ...midiTracks);
+
+ const trackObjects = [];
+
+ for (const track of localMusicTracks) {
+ const trackObj = {};
+ trackObj.client_track_id = track.id;
+ trackObj.client_resource_id = track.rid;
+
+ if (track.instrument_id === 0) {
+ trackObj.instrument_id = server_to_client_instrument_map["Other"].server_id;
+ } else {
+ const instrument = client_to_server_instrument_map[track.instrument_id];
+ if (instrument) {
+ trackObj.instrument_id = instrument.server_id;
+ } else {
+ logger.debug("backend reported an invalid instrument ID of " + track.instrument_id);
+ trackObj.instrument_id = 'other';
+ }
+ }
+
+ trackObj.sound = track.stereo ? "stereo" : "mono";
+ trackObjects.push(trackObj);
+ }
+
+ return trackObjects;
+ }, [getTracks]);
+
+ return {
+ getTrackInfo,
+ getTracks,
+ getBackingTracks,
+ getUserTracks
+ };
+}
diff --git a/jam-ui/src/jamClientProxy.js b/jam-ui/src/jamClientProxy.js
index f23f3be9f..b5259cbb4 100644
--- a/jam-ui/src/jamClientProxy.js
+++ b/jam-ui/src/jamClientProxy.js
@@ -324,6 +324,11 @@ class JamClientProxy {
if (!response['execute_script'].match('HandleBridgeCallback2')) {
// this.logger.log(`[jamClientProxy] 3006 execute_script: ${response['execute_script']}`);
}
+ if (response['execute_script'].includes('HandleBridgeCallback2')) {
+ //log this for now, need to investigate why this is causing issues
+ this.logger.log(`[jamClientProxy] 3006 execute_script (skipping eval): ${response['execute_script']}`);
+ break;
+ }
try {
// eslint-disable-next-line no-eval
eval(response['execute_script']);
diff --git a/jam-ui/src/layouts/JKClientLayout.js b/jam-ui/src/layouts/JKClientLayout.js
index 6a5fbb38b..76d11f424 100644
--- a/jam-ui/src/layouts/JKClientLayout.js
+++ b/jam-ui/src/layouts/JKClientLayout.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import ClientRoutes from './JKClientRoutes';
import UserAuth from '../context/UserAuth';
import { BrowserQueryProvider } from '../context/BrowserQuery';
+import { JamKazamAppProvider } from '../context/JamKazamAppContext';
import { AppDataProvider } from '../context/AppDataContext';
import { AppRoutesProvider } from '../context/AppRoutesContext';
import { JamClientProvider } from '../context/JamClientContext';
@@ -15,13 +16,15 @@ const JKClientLayout = ({ location }) => {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/web/app/assets/javascripts/asyncJamClient.js b/web/app/assets/javascripts/asyncJamClient.js
index ca3db4b39..163c009a7 100644
--- a/web/app/assets/javascripts/asyncJamClient.js
+++ b/web/app/assets/javascripts/asyncJamClient.js
@@ -514,7 +514,7 @@
);
} else if (evt_id) {
let method = Object.keys(response)[0]
- // logger.log("[asyncJamClient] event received:", evt_id.toString(), Object.keys(response)[0])
+ logger.log("[asyncJamClient] event received:", evt_id.toString(), Object.keys(response)[0])
// if(evt_id.toString() === '3012'){
// alert(evt_id.toString())
@@ -522,8 +522,8 @@
switch (evt_id.toString()) {
case '3006': //execute_script
- if(!response['execute_script'].match('HandleBridgeCallback2')){
- //logger.log(`[asyncJamClient] 3006 execute_script: ${response['execute_script']}`);
+ if(response['execute_script'].match('HandleBridgeCallback2')){
+ logger.log(`[asyncJamClient] 3006 execute_script: ${response['execute_script']}`);
}
try {
eval(response['execute_script']);
diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js
index d92e1f065..c2b39a166 100644
--- a/web/app/assets/javascripts/sessionModel.js
+++ b/web/app/assets/javascripts/sessionModel.js
@@ -409,34 +409,34 @@
// you should only update currentSession with this function
function updateCurrentSession(sessionData) {
- if(sessionData != null) {
- currentOrLastSession = sessionData;
+ currentOrLastSssion = sessionData;
+ }
}
- var beforeUpdate = currentSession;
+varbeforeUpdate=currentession;
- currentSession = sessionData;
+ csDratSsiData;
- // the 'beforeUpdate != null' makes sure we only do a clean up one time internally
- if(sessionData == null) {
- sessionEnded(beforeUpdate != null);
+ befthe 'befrreUUdadat!= null' !ak= 'smrakws only do a clean up one time internally
+ if(sessionata == null)=
+ sessiofEnded(beforeUpdaote! !=ull
}
- }
+}}
- function updateSession(response) {
- updateSessionInfo(response, null, true);
- }
+ fc updSe(po {
+ }daSeInfo(sonsul, ru
+}
- function updateSessionInfo(response, callback, force) {
- if(force === true || currentTrackChanges < response.track_changes_counter) {
- logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter)
- currentTrackChanges = response.track_changes_counter;
- sendClientParticipantChanges(currentSession, response);
- updateCurrentSession(response);
+funct updSeInfo(po,calback, forc) {teSession(response) {
+ if(forceu===ptrued||acurrentTrackChanges