diff --git a/jam-ui/src/components/client/JKSessionScreen.js b/jam-ui/src/components/client/JKSessionScreen.js index 3478cc953..0996a979b 100644 --- a/jam-ui/src/components/client/JKSessionScreen.js +++ b/jam-ui/src/components/client/JKSessionScreen.js @@ -1,9 +1,10 @@ // jam-ui/src/components/client/JKSessionScreen.js -import React, { useEffect, useRef, useState, memo, useMemo, useCallback } from 'react' +import React, { useEffect, useRef, useState, memo, useMemo, useCallback } from 'react'; import { useParams, useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; +import { debounce } from 'lodash'; -import useGearUtils from '../../hooks/useGearUtils' +import useGearUtils from '../../hooks/useGearUtils'; import useSessionUtils from '../../hooks/useSessionUtils.js'; import useSessionModel from '../../hooks/useSessionModel.js'; import useSessionHelper from '../../hooks/useSessionHelper.js'; @@ -19,11 +20,32 @@ import useMediaActions from '../../hooks/useMediaActions'; import { dkeys } from '../../helpers/utils.js'; -import { getSessionHistory, getSession, joinSession as joinSessionRest, updateSessionSettings, getFriends, startRecording, stopRecording, submitSessionFeedback, getVideoConferencingRoomUrl, getJamTrack, closeJamTrack, openMetronome } from '../../helpers/rest'; +import { + getSessionHistory, + getSession, + joinSession as joinSessionRest, + updateSessionSettings, + getFriends, + startRecording, + stopRecording, + submitSessionFeedback, + getVideoConferencingRoomUrl, + getJamTrack, + closeJamTrack, + openMetronome +} from '../../helpers/rest'; import { syncTracksToServer } from '../../services/trackSyncService'; // Redux imports -import { openModal, closeModal, toggleModal, selectModal, selectIsRecording, setRecordingStarted, setRecordingStopped } from '../../store/features/sessionUISlice'; +import { + openModal, + closeModal, + toggleModal, + selectModal, + selectIsRecording, + setRecordingStarted, + setRecordingStopped +} from '../../store/features/sessionUISlice'; import { selectMediaSummary, selectMetronomeTrackMixers, selectMixersReady } from '../../store/features/mixersSlice'; import { fetchActiveSession, @@ -54,14 +76,39 @@ import { selectBackingTrackData, selectJamTrackData } from '../../store/features/activeSessionSlice'; -import { addMessageFromWebSocket, uploadAttachment, selectIsUploading, selectUploadError, selectUploadFileName, selectUploadStatus, clearUploadError, fetchChatHistory, clearAllMessages } from '../../store/features/sessionChatSlice'; +import { + addMessageFromWebSocket, + uploadAttachment, + selectIsUploading, + selectUploadError, + selectUploadFileName, + selectUploadStatus, + clearUploadError, + fetchChatHistory, + clearAllMessages +} from '../../store/features/sessionChatSlice'; import { validateFile } from '../../services/attachmentValidation'; import { checkJamTrackSync } from '../../store/features/mediaSlice'; import { CLIENT_ROLE, RECORD_TYPE_AUDIO, RECORD_TYPE_BOTH } from '../../helpers/globals'; import { MessageType } from '../../helpers/MessageFactory.js'; -import { Alert, Col, Row, Button, Card, CardBody, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader, Badge, UncontrolledTooltip, Spinner } from 'reactstrap'; +import { + Alert, + Col, + Row, + Button, + Card, + CardBody, + Modal, + ModalHeader, + ModalBody, + ModalFooter, + CardHeader, + Badge, + UncontrolledTooltip, + Spinner +} from 'reactstrap'; import FalconCardHeader from '../common/FalconCardHeader'; //import SessionTrackVU from './SessionTrackVU.js'; //import JKSessionMyTrack from './JKSessionMyTrack.js'; @@ -111,12 +158,13 @@ const JKSessionScreen = () => { guardAgainstInvalidConfiguration, guardAgainstActiveProfileMissing, guardAgainstSinglePlayerProfile, - resyncAudio, + resyncAudio } = useGearUtils(); const { initialize: initializeMixer, onSessionChange } = useMixerStore(); const mixerHelper = useMixersContext(); - const { isConnected, + const { + isConnected, ConnectionStatus, connectionStatus, reconnectAttempts, @@ -124,7 +172,8 @@ const JKSessionScreen = () => { jamClient, server, registerMessageCallback, - unregisterMessageCallback } = useJamServerContext(); + unregisterMessageCallback + } = useJamServerContext(); // Phase 4: Replace CurrentSessionContext with Redux const currentSession = useSelector(selectActiveSession); @@ -354,7 +403,7 @@ const JKSessionScreen = () => { // console.debug("-DEBUG- JKSessionScreen: isConnected changed to true"); guardOnJoinSession(); - }, [isConnected, jamClient]); // Added jamClient to dependencies for stability + }, [guardOnJoinSession, isConnected, jamClient]); // Added jamClient to dependencies for stability const guardOnJoinSession = async () => { // console.log("-DEBUG- JKSessionScreen: guardOnJoinSession") @@ -394,11 +443,10 @@ const JKSessionScreen = () => { dispatch(setUserTracks(tracks)); // logger.log("-DEBUG- JKSessionScreen: userTracks: ", tracks); try { - await ensureAppropriateProfile(musicianAccessOnJoin) + await ensureAppropriateProfile(musicianAccessOnJoin); // logger.log("-DEBUG- JKSessionScreen: user has passed all session guards") - dispatch(setGuardsPassed(true)) - + dispatch(setGuardsPassed(true)); } catch (error) { // logger.error("-DEBUG- JKSessionScreen: User profile is not appropriate for session:", error); // If error doesn't control navigation, show alert (legacy behavior shows dialog) @@ -407,8 +455,8 @@ const JKSessionScreen = () => { // TODO: Replace with proper JKSessionProfileDialog component alert( 'Your current audio profile is not suitable for multi-user sessions.\n\n' + - 'Please configure a proper audio interface in your audio settings, ' + - 'or create a private session for solo practice.' + 'Please configure a proper audio interface in your audio settings, ' + + 'or create a private session for solo practice.' ); await sessionModel.handleLeaveSession(); history.push('/'); // Redirect to dashboard @@ -418,7 +466,7 @@ const JKSessionScreen = () => { } } catch (error) { // logger.error("-DEBUG- JKSessionScreen: Error: waiting for session page enter to complete:", error); - if (error === "timeout") { + 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 @@ -438,7 +486,6 @@ const JKSessionScreen = () => { await sessionModel.handleLeaveSession(); //TODO: handle redirection // logger.error("-DEBUG- JKSessionScreen: Error: Invalid configuration:", error); } - } catch (error) { // logger.error("-DEBUG- JKSessionScreen: Error: Error fetching session history:", error); //TODO: Show some error @@ -446,43 +493,36 @@ const JKSessionScreen = () => { }; useEffect(() => { - if (!sessionGuardsPassed || userTracks.length === 0 || hasJoined) { return } + if (!sessionGuardsPassed || userTracks.length === 0 || hasJoined) { + return; + } joinSession(); - }, [sessionGuardsPassed, userTracks, hasJoined]) + }, [sessionGuardsPassed, userTracks, hasJoined, joinSession]); - // Track sync: Sync tracks to server when session joined (3-call pattern matching legacy) + // Create stable debounced sync function + const syncTracksDebounced = useMemo( + () => + debounce((sid, cid, d) => { + d(syncTracksToServer(sid, cid)); + }, 1500), // 1.5s delay - adequate for mixer initialization + [] + ); + + // Track sync: Single debounced call when session joined + // Replaces legacy 3-timer pattern (1s, 1.4s, 6s) with single 1.5s debounced call // IMPORTANT: Wait for mixers to be ready before syncing to avoid race condition with mixer initialization useEffect(() => { if (!hasJoined || !sessionId || !server?.clientId || !mixersReady) { return; } - // console.log('[Track Sync] Mixers ready, scheduling track sync calls'); + // console.log('[Track Sync] Mixers ready, scheduling single debounced sync'); + syncTracksDebounced(sessionId, server.clientId, dispatch); - // First sync: Initial setup (~1s after join) - const timer1 = setTimeout(() => { - dispatch(syncTracksToServer(sessionId, server.clientId)); - }, 1000); - - // Second sync: Refinement (~1.4s after join) - const timer2 = setTimeout(() => { - dispatch(syncTracksToServer(sessionId, server.clientId)); - }, 1400); - - // Third sync: Final config (~6s after join) - const timer3 = setTimeout(() => { - dispatch(syncTracksToServer(sessionId, server.clientId)); - }, 6000); - - // Cleanup timers on unmount or if hasJoined/sessionId changes - return () => { - clearTimeout(timer1); - clearTimeout(timer2); - clearTimeout(timer3); - }; + return () => syncTracksDebounced.cancel(); // Note: server intentionally NOT in deps to avoid re-running on server reference changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hasJoined, sessionId, mixersReady, dispatch]) + }, [hasJoined, sessionId, mixersReady, dispatch, syncTracksDebounced]); // Fetch chat history when session joins to populate unread badge // This ensures unread count persists across page reloads @@ -491,18 +531,25 @@ const JKSessionScreen = () => { return; } - dispatch(fetchChatHistory({ - channel: sessionId, - sessionId: sessionId - })); + dispatch( + fetchChatHistory({ + channel: sessionId, + sessionId: sessionId + }) + ); }, [hasJoined, sessionId, dispatch]); - const joinSession = async () => { - + const joinSession = useCallback(async () => { await jamClient.SetVURefreshRate(150); - await jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2"); - await jamClient.SessionSetAlertCallback("JK.HandleAlertCallback"); - await jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted"); + await jamClient.SessionRegisterCallback('JK.HandleBridgeCallback2'); + await jamClient.SessionSetAlertCallback('JK.HandleAlertCallback'); + await jamClient.RegisterRecordingCallbacks( + 'JK.HandleRecordingStartResult', + 'JK.HandleRecordingStopResult', + 'JK.HandleRecordingStarted', + 'JK.HandleRecordingStopped', + 'JK.HandleRecordingAborted' + ); await jamClient.SessionSetConnectionStatusRefreshRate(1000); @@ -510,15 +557,13 @@ const JKSessionScreen = () => { const parentClientId = await jamClient.getParentClientId(); // console.debug('role when joining session: ' + clientRole + ', parentClientId: ' + parentClientId); - - if (clientRole === 0) { clientRole = 'child'; } else if (clientRole === 1) { clientRole = 'parent'; } - if ((clientRole === '') || !clientRole) { + if (clientRole === '' || !clientRole) { clientRole = null; } @@ -527,13 +572,11 @@ const JKSessionScreen = () => { // tell the server we want to join - - //const clientId = await jamClient.clientID(); const clientId = server.clientId; // console.log("joining session " + sessionId + " as client " + JSON.stringify(clientId) + " with role " + clientRole + " and parent client " + parentClientId); - const latency = await jamClient.FTUEGetExpectedLatency().latency + const latency = await jamClient.FTUEGetExpectedLatency().latency; // console.log("joinSession parameters: ", { // client_id: clientId, @@ -554,150 +597,147 @@ const JKSessionScreen = () => { session_id: sessionId, client_role: clientRole, parent_client_id: parentClientId, - audio_latency: latency, - }).then(async (response) => { - // console.debug("joinSessionRest response received", response.errors); - if (response.errors) { - throw new Error("Unable to join session: " + JSON.stringify(response.errors)); - } else { - - const data = await response.json(); - // console.debug("join session response xxx: ", data); - - // Update Redux state - user has successfully joined - dispatch(joinActiveSession.fulfilled(data, '', { sessionId, options: {} })); - - 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"); - sessionModel.leaveSessionRest(); - } - - sessionModel.updateSessionInfo(data, true); - dispatch(updateSessionData(data)); // Phase 4: dispatch to Redux - - //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); - // } - // } - - // this.recordingModel.reset(this.currentSessionId); //TODO: implement recording model - - const joinSessionMsg = { - sessionID: currentSession.id, - music_session_id_int: response.music_session_id_int - }; - - await jamClient.JoinSession(joinSessionMsg); - - //@refreshCurrentSession(true); - - // Chat message handler - receives messages from WebSocket and dispatches to Redux - // Callback signature: (fullMessage, payload) where payload contains the chat_message data - const handleChatMessage = (fullMessage, payload) => { - // Transform Protocol Buffer format to Redux format - // payload contains: {sender_name, sender_id, msg, msg_id, created_at, channel, ...} - const message = { - id: payload.msg_id, - senderId: payload.sender_id, - senderName: payload.sender_name, - message: payload.msg, - createdAt: payload.created_at, - channel: payload.channel || 'session', - sessionId: currentSession.id, - // Attachment fields (null/undefined if not an attachment) - purpose: payload.purpose, // 'Notation File', 'Audio File', or undefined - attachmentId: payload.attachment_id, // MusicNotation UUID - attachmentType: payload.attachment_type, // 'notation' or 'audio' - attachmentName: payload.attachment_name, // filename - attachmentSize: payload.attachment_size // file size in bytes (may be null) - }; - dispatch(addMessageFromWebSocket(message)); - }; - - // Register message callbacks and store references for cleanup - const callbacksToRegister = [ - { type: MessageType.SESSION_JOIN, callback: trackChanges }, - { type: MessageType.SESSION_DEPART, callback: trackChanges }, - { type: MessageType.TRACKS_CHANGED, callback: trackChanges }, - { type: MessageType.HEARTBEAT_ACK, callback: trackChanges }, - { type: MessageType.CHAT_MESSAGE, callback: handleChatMessage } - ]; - - callbacksToRegister.forEach(({ type, callback }) => { - registerMessageCallback(type, callback); - }); - - // Store registered callbacks for cleanup (both state and ref for reliable cleanup) - setRegisteredCallbacks(callbacksToRegister); - registeredCallbacksRef.current = callbacksToRegister; - - //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) => { - // console.error("joinSessionRest error: ", xhr); - let leaveBehavior; - sessionModel.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) { - //console.error("unable to join session - 422 error"); - // const response = JSON.parse(xhr.responseText); - // if (response["errors"] && response["errors"]["tracks"] && (response["errors"]["tracks"][0] === "Please select at least one track")) { - // // You will need to reconfigure your audio device. show an alert - // } else if (response["errors"] && response["errors"]["music_session"] && (response["errors"]["music_session"][0] == ["is currently recording"])) { - // //The session is currently recording. You can not join a session that is recording. show an alert - // } else if (response["errors"] && response["errors"]["remaining_session_play_time"]) { - // //user has no available playtime. upgrade - // } else if (response["errors"] && response["errors"]["remaining_month_play_time"]) { - // //user has no available playtime. upgrade - // } else { - // // unknown 422 error. alert unable to join sessio - // } - } else { - // unable to join session - } + audio_latency: latency }) + .then(async response => { + // console.debug("joinSessionRest response received", response.errors); + if (response.errors) { + throw new Error('Unable to join session: ' + JSON.stringify(response.errors)); + } else { + const data = await response.json(); + // console.debug("join session response xxx: ", data); - } + // Update Redux state - user has successfully joined + dispatch(joinActiveSession.fulfilled(data, '', { sessionId, options: {} })); + + 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"); + sessionModel.leaveSessionRest(); + } + + sessionModel.updateSessionInfo(data, true); + dispatch(updateSessionData(data)); // Phase 4: dispatch to Redux + + //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); + // } + // } + + // this.recordingModel.reset(this.currentSessionId); //TODO: implement recording model + + const joinSessionMsg = { + sessionID: currentSession.id, + music_session_id_int: response.music_session_id_int + }; + + await jamClient.JoinSession(joinSessionMsg); + + //@refreshCurrentSession(true); + + // Chat message handler - receives messages from WebSocket and dispatches to Redux + // Callback signature: (fullMessage, payload) where payload contains the chat_message data + const handleChatMessage = (fullMessage, payload) => { + // Transform Protocol Buffer format to Redux format + // payload contains: {sender_name, sender_id, msg, msg_id, created_at, channel, ...} + const message = { + id: payload.msg_id, + senderId: payload.sender_id, + senderName: payload.sender_name, + message: payload.msg, + createdAt: payload.created_at, + channel: payload.channel || 'session', + sessionId: currentSession.id, + // Attachment fields (null/undefined if not an attachment) + purpose: payload.purpose, // 'Notation File', 'Audio File', or undefined + attachmentId: payload.attachment_id, // MusicNotation UUID + attachmentType: payload.attachment_type, // 'notation' or 'audio' + attachmentName: payload.attachment_name, // filename + attachmentSize: payload.attachment_size // file size in bytes (may be null) + }; + dispatch(addMessageFromWebSocket(message)); + }; + + // Register message callbacks and store references for cleanup + const callbacksToRegister = [ + { type: MessageType.SESSION_JOIN, callback: trackChanges }, + { type: MessageType.SESSION_DEPART, callback: trackChanges }, + { type: MessageType.TRACKS_CHANGED, callback: trackChanges }, + { type: MessageType.HEARTBEAT_ACK, callback: trackChanges }, + { type: MessageType.CHAT_MESSAGE, callback: handleChatMessage } + ]; + + callbacksToRegister.forEach(({ type, callback }) => { + registerMessageCallback(type, callback); + }); + + // Store registered callbacks for cleanup (both state and ref for reliable cleanup) + setRegisteredCallbacks(callbacksToRegister); + registeredCallbacksRef.current = callbacksToRegister; + + //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 => { + // console.error("joinSessionRest error: ", xhr); + let leaveBehavior; + sessionModel.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) { + //console.error("unable to join session - 422 error"); + // const response = JSON.parse(xhr.responseText); + // if (response["errors"] && response["errors"]["tracks"] && (response["errors"]["tracks"][0] === "Please select at least one track")) { + // // You will need to reconfigure your audio device. show an alert + // } else if (response["errors"] && response["errors"]["music_session"] && (response["errors"]["music_session"][0] == ["is currently recording"])) { + // //The session is currently recording. You can not join a session that is recording. show an alert + // } else if (response["errors"] && response["errors"]["remaining_session_play_time"]) { + // //user has no available playtime. upgrade + // } else if (response["errors"] && response["errors"]["remaining_month_play_time"]) { + // //user has no available playtime. upgrade + // } else { + // // unknown 422 error. alert unable to join sessio + // } + } else { + // unable to join session + } + }); + }); useEffect(() => { if (!isConnected || !hasJoined) return; onSessionChange(sessionHelper); - }, [isConnected, hasJoined, sessionHelper.id()]); + }, [isConnected, hasJoined, onSessionChange, sessionHelper]); - const ensureAppropriateProfile = async (musicianAccess) => { - return new Promise(async function (resolve, reject) { + const ensureAppropriateProfile = async musicianAccess => { + return new Promise(async function(resolve, reject) { if (musicianAccess) { try { await guardAgainstSinglePlayerProfile(app); resolve(); } catch (error) { - reject(error) + reject(error); } } else { resolve(); } - }) + }); }; // Use sessionModel functions @@ -708,7 +748,7 @@ const JKSessionScreen = () => { const musicianAccess = useMemo(() => { if (!currentSession) return null; return sessionModel.getMusicianAccess(); - }, [currentSession]); + }, [currentSession, sessionModel]); // Memoize chat object to prevent unnecessary re-renders const chat = useMemo(() => { @@ -742,15 +782,21 @@ const JKSessionScreen = () => { // Monitor connection status changes useEffect(() => { - if (connectionStatus === ConnectionStatus.DISCONNECTED || - connectionStatus === ConnectionStatus.ERROR) { + if (connectionStatus === ConnectionStatus.DISCONNECTED || connectionStatus === ConnectionStatus.ERROR) { dispatch(setConnectionStatus('disconnected')); } else if (connectionStatus === ConnectionStatus.CONNECTED) { dispatch(setConnectionStatus('connected')); } else if (connectionStatus === ConnectionStatus.RECONNECTING) { dispatch(setConnectionStatus('reconnecting')); } - }, [connectionStatus, dispatch]); + }, [ + ConnectionStatus.CONNECTED, + ConnectionStatus.DISCONNECTED, + ConnectionStatus.ERROR, + ConnectionStatus.RECONNECTING, + connectionStatus, + dispatch + ]); // Handlers for recording and playback // const handleStartRecording = () => { @@ -799,11 +845,9 @@ const JKSessionScreen = () => { // // Handle VU meter updates // }; - - useEffect(() => { fetchFriends(); - }, []); + }, [fetchFriends]); const fetchFriends = () => { if (currentUser) { @@ -819,12 +863,12 @@ const JKSessionScreen = () => { } }; - const handleRecordingSubmit = async (settings) => { + const handleRecordingSubmit = async settings => { settings.volume = getCurrentRecordingState().inputVolumeLevel; try { - localStorage.setItem("recordSettings", JSON.stringify(settings)); + localStorage.setItem('recordSettings', JSON.stringify(settings)); } catch (e) { - logger.info("error while saving recordSettings to localStorage"); + logger.info('error while saving recordSettings to localStorage'); logger.log(e.stack); } @@ -834,7 +878,7 @@ const JKSessionScreen = () => { audioFormat: settings.audioFormat, audioStoreType: settings.audioStoreType, includeChat: settings.includeChat, - volume: settings.volume, + volume: settings.volume }; if (params.recordingType === RECORD_TYPE_BOTH) { @@ -844,25 +888,28 @@ const JKSessionScreen = () => { const obsAvailable = await jamClient.IsOBSAvailable(); if (!obsAvailable) { - toast.warning("OBS Studio is not available. Please ensure OBS Studio is installed and running to record video."); + toast.warning( + 'OBS Studio is not available. Please ensure OBS Studio is installed and running to record video.' + ); return; } if (!globalObject.JK.videoIsOngoing) { - toast.warning("To make a video recording in JamKazam you must have an ongoing video. You can start a video by clicking the Video button on session tool bar."); + toast.warning( + 'To make a video recording in JamKazam you must have an ongoing video. You can start a video by clicking the Video button on session tool bar.' + ); return; } } //this.startStopRecording(params); //TODO: handle startStopRecording doStartRecording(params); - - } + }; function groupTracksToClient(recording) { // group N tracks to the same client Id let groupedTracks = {}; - let recordingTracks = recording["recorded_tracks"]; + let recordingTracks = recording['recorded_tracks']; for (let i = 0; i < recordingTracks.length; i++) { let clientId = recordingTracks[i].client_id; @@ -876,27 +923,29 @@ const JKSessionScreen = () => { return dkeys(groupedTracks); } - const doStartRecording = (params) => { - startRecording({ music_session_id: currentSession.id, recordVideo: params.recordVideo }).then(async (recording) => { - const currentRecordingId = recording.id; - // console.debug("Recording started with ID: ", currentRecordingId); - const groupedTracks = groupTracksToClient(recording); - try { - await jamClient.StartMediaRecording(currentRecordingId, groupedTracks, params); - } catch (error) { - // console.error("Error starting media recording:", error); - } - }).catch((error) => { - // console.error("Error starting recording:", error); - }); - } + const doStartRecording = params => { + startRecording({ music_session_id: currentSession.id, recordVideo: params.recordVideo }) + .then(async recording => { + const currentRecordingId = recording.id; + // console.debug("Recording started with ID: ", currentRecordingId); + const groupedTracks = groupTracksToClient(recording); + try { + await jamClient.StartMediaRecording(currentRecordingId, groupedTracks, params); + } catch (error) { + // console.error("Error starting media recording:", error); + } + }) + .catch(error => { + // console.error("Error starting recording:", error); + }); + }; const handleLeaveSession = () => { // Just show the modal - no leave operations yet dispatch(openModal('leave')); }; - const handleLeaveSubmit = async (feedbackData) => { + const handleLeaveSubmit = async feedbackData => { try { setLeaveLoading(true); @@ -959,7 +1008,7 @@ const JKSessionScreen = () => { // Clear Redux session state on unmount dispatch(clearSession()); }; - }, [metronomeState.isOpen, resetMetronome, dispatch]); + }, [metronomeState.isOpen, resetMetronome, dispatch, unregisterMessageCallbacks]); // Check if user can use video (subscription/permission check) const canVideo = () => { @@ -969,7 +1018,7 @@ const JKSessionScreen = () => { }; // Open external link in new window/tab - const openExternalLink = (url) => { + const openExternalLink = url => { window.open(url, '_blank', 'noopener,noreferrer'); }; @@ -991,7 +1040,6 @@ const JKSessionScreen = () => { // Open video URL in new browser window/tab // console.debug("JKSessionScreen: Opening video conferencing URL", videoUrl); openExternalLink(videoUrl); - } catch (error) { // console.error('Failed to get video room URL:', error); // Handle error - could show error message to user @@ -1014,16 +1062,19 @@ const JKSessionScreen = () => {
The video feature requires a premium subscription. Please upgrade your plan to access video conferencing.
-Set the input level of your Audio Inputs for each of your tracks to a healthy level. It's important to set your input level correctly. If your level is set too high, you'll get distortion or clipping of your audio. If set too low, your audio signal will be too weak, which can cause noise and degrade your audio quality when you and others use the session mix to increase your volume in the mix.
-For instructions on how to set your Audio Input levels, read this help article.
++ Set the input level of your Audio Inputs for each of your tracks to a healthy level. It's important to set + your input level correctly. If your level is set too high, you'll get distortion or clipping of your audio. + If set too low, your audio signal will be too weak, which can cause noise and degrade your audio quality + when you and others use the session mix to increase your volume in the mix. +
++ For instructions on how to set your Audio Input levels, read{' '} + + this help article + + . +
Adjust the volume of each audio track (yours and others) in the Session Mix to get the mix where you want it (i.e. where it sounds good and well balanced to you). Any volume changes you make will affect only what you hear. They don’t affect the volume of what others hear in the session. Everyone has their own customizable session mix.
-Note that your session mix is the mix that will be used for any recordings you make and for any broadcasts you stream. If another musician in your session makes a recording or streams a broadcast, it will use that musician’s session mix, not yours.
-For instructions on how to set your Session Mix levels, read this help article.
++ Adjust the volume of each audio track (yours and others) in the Session Mix to get the mix where you want it + (i.e. where it sounds good and well balanced to you). Any volume changes you make will affect only what you + hear. They don’t affect the volume of what others hear in the session. Everyone has their own customizable + session mix. +
++ Note that your session mix is the mix that will be used for any recordings you make and for any broadcasts + you stream. If another musician in your session makes a recording or streams a broadcast, it will use that + musician’s session mix, not yours. +
++ For instructions on how to set your Session Mix levels,{' '} + + read this help article + + . +