wip session screen
This commit is contained in:
parent
daf530fae6
commit
5eceb287c0
|
|
@ -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", $('<span>You will need to reconfigure your audio device.</span>'));
|
||||
|
||||
// // } 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 (
|
||||
<Card>
|
||||
{!isConnected && <div className='d-flex align-items-center'>Connecting to backend...</div>}
|
||||
<FalconCardHeader title={`Session ${sessionId}`} titleClass="font-weight-bold">
|
||||
<FalconCardHeader title={`Session ${currentSessionIdRef.current}`} titleClass="font-weight-bold">
|
||||
</FalconCardHeader>
|
||||
|
||||
<CardHeader className="bg-light border-bottom border-top py-2 border-3">
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<CurrentSessionContext.Provider value={{ currentSession, setCurrentSession, inSession }}>
|
||||
<CurrentSessionContext.Provider value={{
|
||||
currentSession,
|
||||
setCurrentSession,
|
||||
inSession,
|
||||
setCurrentSessionId,
|
||||
currentSessionIdRef,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CurrentSessionContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<JamClientContext.Provider value={proxyRef.current}>
|
||||
{children}
|
||||
|
|
@ -18,4 +40,3 @@ export const JamClientProvider = ({ children }) => {
|
|||
};
|
||||
|
||||
export const useJamClient = () => useContext(JamClientContext);
|
||||
|
||||
|
|
@ -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 (
|
||||
<JamKazamAppContext.Provider value={value}>
|
||||
{children}
|
||||
</JamKazamAppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useJamKazamApp = () => useContext(JamKazamAppContext);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
|||
<UserAuth path={location.pathname}>
|
||||
<AppRoutesProvider>
|
||||
<AppDataProvider>
|
||||
<BrowserQueryProvider>
|
||||
<JamClientProvider>
|
||||
<CurrentSessionProvider>
|
||||
<ClientRoutes />
|
||||
</CurrentSessionProvider>
|
||||
</JamClientProvider>
|
||||
</BrowserQueryProvider>
|
||||
<JamKazamAppProvider>
|
||||
<BrowserQueryProvider>
|
||||
<JamClientProvider>
|
||||
<CurrentSessionProvider>
|
||||
<ClientRoutes />
|
||||
</CurrentSessionProvider>
|
||||
</JamClientProvider>
|
||||
</BrowserQueryProvider>
|
||||
</JamKazamAppProvider>
|
||||
</AppDataProvider>
|
||||
</AppRoutesProvider>
|
||||
</UserAuth>
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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<rpse.trck_chnge_uter
|
||||
unction updelegges.dnnug("updfting ourrretneraclbcaang,srfromc%oeto %o",=currt|tTr ckChunget, reschas .trock_chsngea_cnugtes)nter) {
|
||||
logger.ducurr(npTrngeChangest=TrespkCsa.grack_ch ngs__csu_ter; sendClientParticipantChanges(currentSession, response);
|
||||
updateCuetSndClseotP(rrie;Cngs(cr elSesson, rspos
|
||||
callback();pdCrreS(sps
|
||||
if(callback != null) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}abck(;
|
||||
}}
|
||||
}
|
||||
else {
|
||||
logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue