wip
This commit is contained in:
parent
f1764420f5
commit
c64b4548f2
|
|
@ -13,4 +13,5 @@ REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||||
PUBLIC_URL=
|
PUBLIC_URL=
|
||||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
|
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
|
||||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||||
|
REACT_APP_WEBSOCKET_GATEWAY_URL=ws://localhost:6767
|
||||||
|
|
@ -10,4 +10,5 @@ REACT_APP_ENV=development
|
||||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||||
REACT_APP_BRAINTREE_TOKEN=
|
REACT_APP_BRAINTREE_TOKEN=
|
||||||
|
REACT_APP_WEBSOCKET_GATEWAY_URL=
|
||||||
|
|
@ -11,3 +11,4 @@ REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
|
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
|
||||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||||
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh
|
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh
|
||||||
|
REACT_APP_WEBSOCKET_GATEWAY_URL=
|
||||||
|
|
@ -11,3 +11,4 @@ REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
|
||||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
|
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
|
||||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
|
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
|
||||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||||
|
REACT_APP_WEBSOCKET_GATEWAY_URL=
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
import React, { useEffect, useContext, useState } from 'react'
|
import React, { useEffect, useContext, useState } from 'react'
|
||||||
import { Alert, Col, Row, Button, Card, CardBody, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader, Badge } from 'reactstrap';
|
import { Alert, Col, Row, Button, Card, CardBody, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader, Badge } from 'reactstrap';
|
||||||
import FalconCardHeader from '../common/FalconCardHeader';
|
import FalconCardHeader from '../common/FalconCardHeader';
|
||||||
import useJamWebSocketClient, { ConnectionStatus } from '../../hooks/useJamWebSocketClient'
|
import useJamClientService, { ConnectionStatus } from '../../hooks/useJamClientService'
|
||||||
import AppContext from '../../context/Context';
|
import { useJamClient } from '../../context/JamClientContext';
|
||||||
import { getPageName } from '../../helpers/utils';
|
|
||||||
|
|
||||||
const JKSessionScreen = () => {
|
const JKSessionScreen = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -13,16 +12,8 @@ const JKSessionScreen = () => {
|
||||||
reconnectAttempts,
|
reconnectAttempts,
|
||||||
lastError,
|
lastError,
|
||||||
jamClient,
|
jamClient,
|
||||||
mixers,
|
} = useJamClientService(process.env.REACT_APP_WEBSOCKET_GATEWAY_URL);
|
||||||
participants,
|
|
||||||
isRecording,
|
|
||||||
isPlaying,
|
|
||||||
currentPosition
|
|
||||||
} = useJamWebSocketClient();
|
|
||||||
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
|
|
||||||
const isKanban = getPageName('kanban');
|
|
||||||
|
|
||||||
const [isBackendClientReady, setIsBackendClientReady] = useState(false);
|
|
||||||
const [sessionState, setSessionState] = useState({
|
const [sessionState, setSessionState] = useState({
|
||||||
sessionId: 'session_12345',
|
sessionId: 'session_12345',
|
||||||
participants: [],
|
participants: [],
|
||||||
|
|
@ -34,7 +25,7 @@ const JKSessionScreen = () => {
|
||||||
// Monitor connection status changes
|
// Monitor connection status changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connectionStatus === ConnectionStatus.DISCONNECTED ||
|
if (connectionStatus === ConnectionStatus.DISCONNECTED ||
|
||||||
connectionStatus === ConnectionStatus.ERROR) {
|
connectionStatus === ConnectionStatus.ERROR) {
|
||||||
setShowConnectionAlert(true);
|
setShowConnectionAlert(true);
|
||||||
} else if (connectionStatus === ConnectionStatus.CONNECTED) {
|
} else if (connectionStatus === ConnectionStatus.CONNECTED) {
|
||||||
setShowConnectionAlert(false);
|
setShowConnectionAlert(false);
|
||||||
|
|
@ -42,25 +33,25 @@ const JKSessionScreen = () => {
|
||||||
}, [connectionStatus]);
|
}, [connectionStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isConnected) {
|
if (!isConnected) return;
|
||||||
setIsBackendClientReady(true);
|
|
||||||
|
|
||||||
// Initialize session callbacks
|
// Initialize session callbacks
|
||||||
jamClient.SessionRegisterCallback("HandleSessionCallback");
|
console.log("__DEBUG__", jamClient)
|
||||||
jamClient.RegisterRecordingCallbacks(
|
//jamClient.SessionRegisterCallback("HandleSessionCallback");
|
||||||
"HandleRecordingStartResult",
|
// jamClient.RegisterRecordingCallbacks(
|
||||||
"HandleRecordingStopResult",
|
// "HandleRecordingStartResult",
|
||||||
"HandleRecordingStarted",
|
// "HandleRecordingStopResult",
|
||||||
"HandleRecordingStopped",
|
// "HandleRecordingStarted",
|
||||||
"HandleRecordingAborted"
|
// "HandleRecordingStopped",
|
||||||
);
|
// "HandleRecordingAborted"
|
||||||
jamClient.SessionSetConnectionStatusRefreshRate(1000);
|
// );
|
||||||
jamClient.RegisterVolChangeCallBack("HandleVolumeChangeCallback");
|
// jamClient.SessionSetConnectionStatusRefreshRate(1000);
|
||||||
jamClient.setMetronomeOpenCallback("HandleMetronomeCallback");
|
// jamClient.RegisterVolChangeCallBack("HandleVolumeChangeCallback");
|
||||||
|
// jamClient.setMetronomeOpenCallback("HandleMetronomeCallback");
|
||||||
|
|
||||||
|
// Load initial session data
|
||||||
|
//loadSessionData();
|
||||||
|
|
||||||
// Load initial session data
|
|
||||||
loadSessionData();
|
|
||||||
}
|
|
||||||
}, [isConnected]);
|
}, [isConnected]);
|
||||||
|
|
||||||
const loadSessionData = async () => {
|
const loadSessionData = async () => {
|
||||||
|
|
@ -68,108 +59,65 @@ const JKSessionScreen = () => {
|
||||||
const audioConfigs = await jamClient.FTUEGetAllAudioConfigurations();
|
const audioConfigs = await jamClient.FTUEGetAllAudioConfigurations();
|
||||||
const controlState = await jamClient.SessionGetAllControlState(true);
|
const controlState = await jamClient.SessionGetAllControlState(true);
|
||||||
const sampleRate = await jamClient.GetSampleRate();
|
const sampleRate = await jamClient.GetSampleRate();
|
||||||
|
|
||||||
console.log('Session data loaded:', { audioConfigs, controlState, sampleRate });
|
console.log('Session data loaded:', { audioConfigs, controlState, sampleRate });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading session data:', error);
|
console.error('Error loading session data:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartRecording = () => {
|
// const handleStartRecording = () => {
|
||||||
jamClient.StartRecording({ recordingId: `rec_${Date.now()}` });
|
// jamClient.StartRecording({ recordingId: `rec_${Date.now()}` });
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleStopRecording = () => {
|
// const handleStopRecording = () => {
|
||||||
jamClient.StopRecording({});
|
// jamClient.StopRecording({});
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handlePlayPause = () => {
|
// const handlePlayPause = () => {
|
||||||
if (isPlaying) {
|
// if (isPlaying) {
|
||||||
jamClient.SessionPausePlay();
|
// jamClient.SessionPausePlay();
|
||||||
} else {
|
// } else {
|
||||||
jamClient.SessionStartPlay();
|
// jamClient.SessionStartPlay();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleStopPlayback = () => {
|
// const handleStopPlayback = () => {
|
||||||
jamClient.SessionStopPlay();
|
// jamClient.SessionStopPlay();
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Callback handlers (these would be implemented to handle WebSocket responses)
|
// // Callback handlers (these would be implemented to handle WebSocket responses)
|
||||||
const HandleSessionCallback = (data) => {
|
// const HandleSessionCallback = (data) => {
|
||||||
console.log('Session callback:', data);
|
// console.log('Session callback:', data);
|
||||||
// Handle session events
|
// // Handle session events
|
||||||
};
|
// };
|
||||||
|
|
||||||
const HandleRecordingStarted = (data) => {
|
// const HandleRecordingStarted = (data) => {
|
||||||
console.log('Recording started:', data);
|
// console.log('Recording started:', data);
|
||||||
// Update recording state
|
// // Update recording state
|
||||||
};
|
// };
|
||||||
|
|
||||||
const HandleRecordingStopped = (data) => {
|
// const HandleRecordingStopped = (data) => {
|
||||||
console.log('Recording stopped:', data);
|
// console.log('Recording stopped:', data);
|
||||||
// Update recording state
|
// // Update recording state
|
||||||
};
|
// };
|
||||||
|
|
||||||
const HandleVolumeChangeCallback = (mixerId, isLeft, value, isMuted) => {
|
// const HandleVolumeChangeCallback = (mixerId, isLeft, value, isMuted) => {
|
||||||
console.log('Volume changed:', { mixerId, isLeft, value, isMuted });
|
// console.log('Volume changed:', { mixerId, isLeft, value, isMuted });
|
||||||
// Update mixer state
|
// // Update mixer state
|
||||||
};
|
// };
|
||||||
|
|
||||||
const HandleBridgeCallback = (vuData) => {
|
// const HandleBridgeCallback = (vuData) => {
|
||||||
console.log('Bridge callback:', vuData);
|
// console.log('Bridge callback:', vuData);
|
||||||
// Handle VU meter updates
|
// // Handle VU meter updates
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
{ !isConnected && <div className='d-flex align-items-center'>Connecting to backend...</div>}
|
{!isConnected && <div className='d-flex align-items-center'>Connecting to backend...</div>}
|
||||||
<FalconCardHeader title={`Session ${sessionState.sessionId}`} titleClass="font-weight-bold">
|
<FalconCardHeader title={`Session ${sessionState.sessionId}`} titleClass="font-weight-bold">
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={handleStartRecording}
|
|
||||||
disabled={!isConnected || isRecording}
|
|
||||||
>
|
|
||||||
{isRecording ? 'Recording...' : 'Start Recording'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={handleStopRecording}
|
|
||||||
disabled={!isRecording}
|
|
||||||
>
|
|
||||||
Stop Recording
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="success"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={handlePlayPause}
|
|
||||||
disabled={!isConnected}
|
|
||||||
>
|
|
||||||
{isPlaying ? 'Pause' : 'Play'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="warning"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={handleStopPlayback}
|
|
||||||
disabled={!isPlaying}
|
|
||||||
>
|
|
||||||
Stop
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="danger"
|
|
||||||
className="me-2 mr-2 fs--1"
|
|
||||||
onClick={() => {
|
|
||||||
// Manually disconnect for testing reconnection
|
|
||||||
fetch('http://localhost:8080/simulate-drop', { method: 'POST' })
|
|
||||||
.catch(() => console.log('Connection drop simulated'));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Test Disconnect
|
|
||||||
</Button>
|
|
||||||
</FalconCardHeader>
|
</FalconCardHeader>
|
||||||
|
|
||||||
<CardHeader className="bg-light border-bottom border-top py-2 border-3">
|
<CardHeader className="bg-light border-bottom border-top py-2 border-3">
|
||||||
<div className="d-flex flex-nowrap overflow-auto" style={{ gap: '0.5rem' }}>
|
<div className="d-flex flex-nowrap overflow-auto" style={{ gap: '0.5rem' }}>
|
||||||
<Button className='btn-custom-outline' outline size="md">Settings</Button>
|
<Button className='btn-custom-outline' outline size="md">Settings</Button>
|
||||||
|
|
@ -184,13 +132,13 @@ const JKSessionScreen = () => {
|
||||||
<Button className='btn-custom-outline' outline size="md">Resync</Button>
|
<Button className='btn-custom-outline' outline size="md">Resync</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardBody className="pl-4" style={{ backgroundColor: '#edf2f9f5' }}>
|
<CardBody className="pl-4" style={{ backgroundColor: '#edf2f9f5' }}>
|
||||||
<div className='d-flex' style={{ gap: '1rem' }}>
|
<div className='d-flex' style={{ gap: '1rem' }}>
|
||||||
<div className='audioInputs'>
|
<div className='audioInputs'>
|
||||||
<h5>Audio Inputs ({participants.length} participants)</h5>
|
<h5>Audio Inputs</h5>
|
||||||
<div className='d-flex' style={{ gap: '0.5rem', borderRight: '1px #ddd solid', paddingRight: '1rem' }}>
|
<div className='d-flex' style={{ gap: '0.5rem', borderRight: '1px #ddd solid', paddingRight: '1rem' }}>
|
||||||
{participants.map((participant, index) => (
|
{/* {participants.map((participant, index) => (
|
||||||
<div key={participant.clientId} className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>
|
<div key={participant.clientId} className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>
|
||||||
<div className='d-flex flex-column' style={{ height: '100%' }}>
|
<div className='d-flex flex-column' style={{ height: '100%' }}>
|
||||||
<div className='p-2'>{participant.name}</div>
|
<div className='p-2'>{participant.name}</div>
|
||||||
|
|
@ -200,14 +148,14 @@ const JKSessionScreen = () => {
|
||||||
<div className='p-2'>Controls</div>
|
<div className='p-2'>Controls</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='sessionMix'>
|
<div className='sessionMix'>
|
||||||
<h5>Session Mix ({mixers.length} mixers)</h5>
|
<h5>Session Mix</h5>
|
||||||
<div className='d-flex' style={{ gap: '0.5rem' }}>
|
<div className='d-flex' style={{ gap: '0.5rem' }}>
|
||||||
{mixers.slice(0, 4).map((mixer, index) => (
|
{/* {mixers.slice(0, 4).map((mixer, index) => (
|
||||||
<div key={mixer.id} className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>
|
<div key={mixer.id} className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>
|
||||||
<div className='d-flex flex-column align-items-center' style={{ height: '100%', padding: '10px' }}>
|
<div className='d-flex flex-column align-items-center' style={{ height: '100%', padding: '10px' }}>
|
||||||
<div className='mb-2'>Mixer {index + 1}</div>
|
<div className='mb-2'>Mixer {index + 1}</div>
|
||||||
|
|
@ -217,10 +165,10 @@ const JKSessionScreen = () => {
|
||||||
<div className='mb-2'>Type: {mixer.group_id}</div>
|
<div className='mb-2'>Type: {mixer.group_id}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='attachMedia'>
|
<div className='attachMedia'>
|
||||||
<h5>Attach Media</h5>
|
<h5>Attach Media</h5>
|
||||||
<div className='d-flex flex-column' style={{ gap: '0.5rem' }}>
|
<div className='d-flex flex-column' style={{ gap: '0.5rem' }}>
|
||||||
|
|
@ -231,14 +179,14 @@ const JKSessionScreen = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Connection Status Alerts */}
|
{/* Connection Status Alerts */}
|
||||||
{showConnectionAlert && (
|
{showConnectionAlert && (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<Alert color={
|
<Alert color={
|
||||||
connectionStatus === ConnectionStatus.DISCONNECTED ? 'warning' :
|
connectionStatus === ConnectionStatus.DISCONNECTED ? 'warning' :
|
||||||
connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
|
connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
|
||||||
connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'success'
|
connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'success'
|
||||||
}>
|
}>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
<div className='me-2'>
|
<div className='me-2'>
|
||||||
|
|
@ -285,9 +233,9 @@ const JKSessionScreen = () => {
|
||||||
<Badge
|
<Badge
|
||||||
color={
|
color={
|
||||||
connectionStatus === ConnectionStatus.CONNECTED ? 'success' :
|
connectionStatus === ConnectionStatus.CONNECTED ? 'success' :
|
||||||
connectionStatus === ConnectionStatus.CONNECTING ? 'warning' :
|
connectionStatus === ConnectionStatus.CONNECTING ? 'warning' :
|
||||||
connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
|
connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
|
||||||
connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'secondary'
|
connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'secondary'
|
||||||
}
|
}
|
||||||
className='me-2'
|
className='me-2'
|
||||||
>
|
>
|
||||||
|
|
@ -314,17 +262,8 @@ const JKSessionScreen = () => {
|
||||||
<div className='col-md-3'>
|
<div className='col-md-3'>
|
||||||
<strong>Status:</strong> {connectionStatus}
|
<strong>Status:</strong> {connectionStatus}
|
||||||
</div>
|
</div>
|
||||||
<div className='col-md-3'>
|
|
||||||
<strong>Recording:</strong> {isRecording ? '🔴 Recording' : '⏹️ Not Recording'}
|
|
||||||
</div>
|
|
||||||
<div className='col-md-3'>
|
|
||||||
<strong>Playback:</strong> {isPlaying ? '▶️ Playing' : '⏸️ Paused'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='row mt-2'>
|
<div className='row mt-2'>
|
||||||
<div className='col-md-3'>
|
|
||||||
<strong>Position:</strong> {Math.floor(currentPosition / 1000)}s
|
|
||||||
</div>
|
|
||||||
<div className='col-md-3'>
|
<div className='col-md-3'>
|
||||||
<strong>Reconnect Attempts:</strong> {reconnectAttempts}
|
<strong>Reconnect Attempts:</strong> {reconnectAttempts}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React, { createContext } from 'react';
|
||||||
|
import { useStun } from '../hooks/useStun';
|
||||||
|
|
||||||
|
|
||||||
|
// Create a global context
|
||||||
|
export const GlobalContext = createContext({});
|
||||||
|
|
||||||
|
export const GlobalProvider = ({ children }) => {
|
||||||
|
// Add global state or functions here as needed
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GlobalContext.Provider value={{}}>
|
||||||
|
{children}
|
||||||
|
</GlobalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { createContext, useContext, useRef } from 'react';
|
||||||
|
import JamClientProxy from '../helpers/jamClientProxy';
|
||||||
|
|
||||||
|
const JamClientContext = createContext(null);
|
||||||
|
|
||||||
|
export const JamClientProvider = ({ children }) => {
|
||||||
|
//assign an instance of JamClientProxy to a ref so that it persists across renders
|
||||||
|
const proxyRef = useRef(null);
|
||||||
|
const proxy = new JamClientProxy(null, console); // Pass appropriate parameters
|
||||||
|
proxyRef.current = proxy.init();
|
||||||
|
return (
|
||||||
|
<JamClientContext.Provider value={proxyRef.current}>
|
||||||
|
{children}
|
||||||
|
</JamClientContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useJamClient = () => useContext(JamClientContext);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
/**
|
||||||
|
* Modern JavaScript Message Factory for JamKazam WebSocket Communication
|
||||||
|
* Based on the legacy AAB_message_factory.js, updated for ES6+ and React compatibility
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Message types for WebSocket communication
|
||||||
|
export const MessageType = {
|
||||||
|
LOGIN: "LOGIN",
|
||||||
|
LOGIN_ACK: "LOGIN_ACK",
|
||||||
|
LOGIN_MUSIC_SESSION: "LOGIN_MUSIC_SESSION",
|
||||||
|
LOGIN_MUSIC_SESSION_ACK: "LOGIN_MUSIC_SESSION_ACK",
|
||||||
|
LEAVE_MUSIC_SESSION: "LEAVE_MUSIC_SESSION",
|
||||||
|
LEAVE_MUSIC_SESSION_ACK: "LEAVE_MUSIC_SESSION_ACK",
|
||||||
|
HEARTBEAT: "HEARTBEAT",
|
||||||
|
HEARTBEAT_ACK: "HEARTBEAT_ACK",
|
||||||
|
SUBSCRIBE: "SUBSCRIBE",
|
||||||
|
UNSUBSCRIBE: "UNSUBSCRIBE",
|
||||||
|
SUBSCRIPTION_MESSAGE: "SUBSCRIPTION_MESSAGE",
|
||||||
|
SUBSCRIBE_BULK: "SUBSCRIBE_BULK",
|
||||||
|
USER_STATUS: "USER_STATUS",
|
||||||
|
|
||||||
|
// Friend notifications
|
||||||
|
FRIEND_UPDATE: "FRIEND_UPDATE",
|
||||||
|
FRIEND_REQUEST: "FRIEND_REQUEST",
|
||||||
|
FRIEND_REQUEST_ACCEPTED: "FRIEND_REQUEST_ACCEPTED",
|
||||||
|
FRIEND_SESSION_JOIN: "FRIEND_SESSION_JOIN",
|
||||||
|
NEW_USER_FOLLOWER: "NEW_USER_FOLLOWER",
|
||||||
|
NEW_BAND_FOLLOWER: "NEW_BAND_FOLLOWER",
|
||||||
|
|
||||||
|
// Session notifications
|
||||||
|
SESSION_INVITATION: "SESSION_INVITATION",
|
||||||
|
SESSION_ENDED: "SESSION_ENDED",
|
||||||
|
JOIN_REQUEST: "JOIN_REQUEST",
|
||||||
|
JOIN_REQUEST_APPROVED: "JOIN_REQUEST_APPROVED",
|
||||||
|
JOIN_REQUEST_REJECTED: "JOIN_REQUEST_REJECTED",
|
||||||
|
SESSION_JOIN: "SESSION_JOIN",
|
||||||
|
SESSION_DEPART: "SESSION_DEPART",
|
||||||
|
TRACKS_CHANGED: "TRACKS_CHANGED",
|
||||||
|
MUSICIAN_SESSION_JOIN: "MUSICIAN_SESSION_JOIN",
|
||||||
|
BAND_SESSION_JOIN: "BAND_SESSION_JOIN",
|
||||||
|
SCHEDULED_SESSION_INVITATION: "SCHEDULED_SESSION_INVITATION",
|
||||||
|
SCHEDULED_SESSION_RSVP: "SCHEDULED_SESSION_RSVP",
|
||||||
|
SCHEDULED_SESSION_RSVP_APPROVED: "SCHEDULED_SESSION_RSVP_APPROVED",
|
||||||
|
SCHEDULED_SESSION_RSVP_CANCELLED: "SCHEDULED_SESSION_RSVP_CANCELLED",
|
||||||
|
SCHEDULED_SESSION_RSVP_CANCELLED_ORG: "SCHEDULED_SESSION_RSVP_CANCELLED_ORG",
|
||||||
|
SCHEDULED_SESSION_CANCELLED: "SCHEDULED_SESSION_CANCELLED",
|
||||||
|
SCHEDULED_SESSION_RESCHEDULED: "SCHEDULED_SESSION_RESCHEDULED",
|
||||||
|
SCHEDULED_SESSION_REMINDER: "SCHEDULED_SESSION_REMINDER",
|
||||||
|
SCHEDULED_SESSION_COMMENT: "SCHEDULED_SESSION_COMMENT",
|
||||||
|
|
||||||
|
SCHEDULED_JAMCLASS_INVITATION: "SCHEDULED_JAMCLASS_INVITATION",
|
||||||
|
LESSON_MESSAGE: "LESSON_MESSAGE",
|
||||||
|
|
||||||
|
// Recording notifications
|
||||||
|
MUSICIAN_RECORDING_SAVED: "MUSICIAN_RECORDING_SAVED",
|
||||||
|
BAND_RECORDING_SAVED: "BAND_RECORDING_SAVED",
|
||||||
|
RECORDING_STARTED: "RECORDING_STARTED",
|
||||||
|
RECORDING_ENDED: "RECORDING_ENDED",
|
||||||
|
RECORDING_MASTER_MIX_COMPLETE: "RECORDING_MASTER_MIX_COMPLETE",
|
||||||
|
DOWNLOAD_AVAILABLE: "DOWNLOAD_AVAILABLE",
|
||||||
|
RECORDING_STREAM_MIX_COMPLETE: "RECORDING_STREAM_MIX_COMPLETE",
|
||||||
|
|
||||||
|
// Band notifications
|
||||||
|
BAND_INVITATION: "BAND_INVITATION",
|
||||||
|
BAND_INVITATION_ACCEPTED: "BAND_INVITATION_ACCEPTED",
|
||||||
|
|
||||||
|
// Text message
|
||||||
|
TEXT_MESSAGE: "TEXT_MESSAGE",
|
||||||
|
CHAT_MESSAGE: "CHAT_MESSAGE",
|
||||||
|
SEND_CHAT_MESSAGE: "SEND_CHAT_MESSAGE",
|
||||||
|
|
||||||
|
// Broadcast notifications
|
||||||
|
SOURCE_UP_REQUESTED: "SOURCE_UP_REQUESTED",
|
||||||
|
SOURCE_DOWN_REQUESTED: "SOURCE_DOWN_REQUESTED",
|
||||||
|
SOURCE_UP: "SOURCE_UP",
|
||||||
|
SOURCE_DOWN: "SOURCE_DOWN",
|
||||||
|
|
||||||
|
TEST_SESSION_MESSAGE: "TEST_SESSION_MESSAGE",
|
||||||
|
PING_REQUEST: "PING_REQUEST",
|
||||||
|
PING_ACK: "PING_ACK",
|
||||||
|
PEER_MESSAGE: "PEER_MESSAGE",
|
||||||
|
CLIENT_UPDATE: "CLIENT_UPDATE",
|
||||||
|
GENERIC_MESSAGE: "GENERIC_MESSAGE",
|
||||||
|
RELOAD: "RELOAD",
|
||||||
|
RESTART_APPLICATION: "RESTART_APPLICATION",
|
||||||
|
STOP_APPLICATION: "STOP_APPLICATION",
|
||||||
|
SERVER_BAD_STATE_RECOVERED: "SERVER_BAD_STATE_RECOVERED",
|
||||||
|
SERVER_GENERIC_ERROR: "SERVER_GENERIC_ERROR",
|
||||||
|
SERVER_REJECTION_ERROR: "SERVER_REJECTION_ERROR",
|
||||||
|
SERVER_BAD_STATE_ERROR: "SERVER_BAD_STATE_ERROR",
|
||||||
|
SERVER_DUPLICATE_CLIENT_ERROR: "SERVER_DUPLICATE_CLIENT_ERROR"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Route prefixes for message routing
|
||||||
|
export const RouteToPrefix = {
|
||||||
|
CLIENT: "client",
|
||||||
|
SESSION: "session",
|
||||||
|
SERVER: "server",
|
||||||
|
USER: "user"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility function to get a simple cookie value (replacement for $.cookie)
|
||||||
|
const getCookie = (name) => {
|
||||||
|
const value = `; ${document.cookie}`;
|
||||||
|
const parts = value.split(`; ${name}=`);
|
||||||
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to create client container
|
||||||
|
const createClientContainer = (type, target, inner) => {
|
||||||
|
const typeField = type.toLowerCase();
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
route_to: target,
|
||||||
|
[typeField]: inner
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to create route to client
|
||||||
|
const routeToClient = (clientId) => `${RouteToPrefix.CLIENT}:${clientId}`;
|
||||||
|
|
||||||
|
// Helper function to create route to session
|
||||||
|
const routeToSession = (sessionId) => `${RouteToPrefix.SESSION}:${sessionId}`;
|
||||||
|
|
||||||
|
// Message Factory class
|
||||||
|
class MessageFactory {
|
||||||
|
// Ping the provided client_id
|
||||||
|
ping(clientId) {
|
||||||
|
const data = {};
|
||||||
|
return createClientContainer(MessageType.PING_REQUEST, routeToClient(clientId), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat message
|
||||||
|
heartbeat(lastNotificationSeen, lastNotificationSeenAt, active) {
|
||||||
|
const data = {
|
||||||
|
notification_seen: lastNotificationSeen,
|
||||||
|
notification_seen_at: lastNotificationSeenAt,
|
||||||
|
active
|
||||||
|
};
|
||||||
|
return createClientContainer(MessageType.HEARTBEAT, RouteToPrefix.SERVER, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Status update message
|
||||||
|
userStatus(active, status) {
|
||||||
|
const data = { active, status };
|
||||||
|
return createClientContainer(MessageType.USER_STATUS, RouteToPrefix.SERVER, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat message (fixed implementation from original)
|
||||||
|
chatMessage(channel, message) {
|
||||||
|
const data = { channel, msg: message };
|
||||||
|
return createClientContainer(MessageType.SEND_CHAT_MESSAGE, RouteToPrefix.SERVER, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a login message using user/pass
|
||||||
|
loginWithUserPass(username, password) {
|
||||||
|
const login = { username, password };
|
||||||
|
return createClientContainer(MessageType.LOGIN, RouteToPrefix.SERVER, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a login message using token
|
||||||
|
// reconnect_music_session_id is optional
|
||||||
|
loginWithToken(token, reconnectMusicSessionId, clientType) {
|
||||||
|
const login = {
|
||||||
|
token,
|
||||||
|
client_id: getCookie("client_id"),
|
||||||
|
client_type: clientType
|
||||||
|
};
|
||||||
|
if (reconnectMusicSessionId) {
|
||||||
|
login.reconnect_music_session_id = reconnectMusicSessionId;
|
||||||
|
}
|
||||||
|
return createClientContainer(MessageType.LOGIN, RouteToPrefix.SERVER, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout message
|
||||||
|
logout() {
|
||||||
|
const logout = {};
|
||||||
|
return createClientContainer("LOGOUT", RouteToPrefix.SERVER, logout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a login message using only the client_id (for latency_tester)
|
||||||
|
loginWithClientId(clientId, clientType) {
|
||||||
|
if (clientType !== 'latency_tester') {
|
||||||
|
throw new Error("client_type must be latency_tester in loginWithClientId");
|
||||||
|
}
|
||||||
|
const login = { client_id: clientId, client_type: clientType };
|
||||||
|
return createClientContainer(MessageType.LOGIN, RouteToPrefix.SERVER, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a music session login message
|
||||||
|
loginMusicSession(musicSession) {
|
||||||
|
const loginMusicSession = { music_session: musicSession };
|
||||||
|
return createClientContainer(MessageType.LOGIN_MUSIC_SESSION, RouteToPrefix.SERVER, loginMusicSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-to-client message
|
||||||
|
clientP2PMessage(senderClientId, receiverClientId, message) {
|
||||||
|
const peerMessage = { message };
|
||||||
|
const result = createClientContainer(MessageType.PEER_MESSAGE, routeToClient(receiverClientId), peerMessage);
|
||||||
|
result.from = senderClientId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to notifications
|
||||||
|
subscribe(type, id) {
|
||||||
|
const subscribeMsg = { type, id };
|
||||||
|
return createClientContainer(MessageType.SUBSCRIBE, RouteToPrefix.SERVER, subscribeMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to multiple types/ids
|
||||||
|
subscribeBulk(types, ids) {
|
||||||
|
const subscribeBulkMsg = { types, ids };
|
||||||
|
return createClientContainer(MessageType.SUBSCRIBE_BULK, RouteToPrefix.SERVER, subscribeBulkMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe from notifications
|
||||||
|
unsubscribe(type, id) {
|
||||||
|
const unsubscribeMsg = { type, id };
|
||||||
|
return createClientContainer(MessageType.UNSUBSCRIBE, RouteToPrefix.SERVER, unsubscribeMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export a singleton instance for easy use
|
||||||
|
export const messageFactory = new MessageFactory();
|
||||||
|
|
||||||
|
// Also export the class for custom instantiation if needed
|
||||||
|
export default MessageFactory;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Example usage of the JKMessageFactory in a React component
|
||||||
|
* This demonstrates how to import and use the modern message factory
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { messageFactory, MessageType } from './MessageFactory';
|
||||||
|
|
||||||
|
// Example React component using the message factory
|
||||||
|
const MessageFactoryExample = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
// Example: Create a heartbeat message
|
||||||
|
const heartbeatMessage = messageFactory.heartbeat(123, '2023-01-01T00:00:00Z', true);
|
||||||
|
console.log('Heartbeat message:', heartbeatMessage);
|
||||||
|
|
||||||
|
// Example: Create a login message with token
|
||||||
|
const loginMessage = messageFactory.loginWithToken('some-token', null, 'web');
|
||||||
|
console.log('Login message:', loginMessage);
|
||||||
|
|
||||||
|
// Example: Create a chat message
|
||||||
|
const chatMessage = messageFactory.chatMessage('general', 'Hello world!');
|
||||||
|
console.log('Chat message:', chatMessage);
|
||||||
|
|
||||||
|
// Example: Subscribe to notifications
|
||||||
|
const subscribeMessage = messageFactory.subscribe('session', 456);
|
||||||
|
console.log('Subscribe message:', subscribeMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>JKMessageFactory Example</h3>
|
||||||
|
<p>Check the console for example message outputs.</p>
|
||||||
|
<p>The message factory is ready to be used in your React components for WebSocket communication.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MessageFactoryExample;
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// Example usage of the ported AsyncJamClient in a React application
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import AsyncJamClient from './asyncJamClient';
|
|
||||||
|
|
||||||
// Example React hook for using AsyncJamClient
|
|
||||||
export const useAsyncJamClient = (logger, context = window) => {
|
|
||||||
const [asyncJamClient, setAsyncJamClient] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Initialize the AsyncJamClient
|
|
||||||
const client = new AsyncJamClient(null, logger, context);
|
|
||||||
const proxy = client.init();
|
|
||||||
setAsyncJamClient(proxy);
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
return () => {
|
|
||||||
// Add cleanup logic if needed
|
|
||||||
};
|
|
||||||
}, [logger, context]);
|
|
||||||
|
|
||||||
return asyncJamClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Example React component using AsyncJamClient
|
|
||||||
const JamSessionComponent = () => {
|
|
||||||
const logger = console; // or your custom logger
|
|
||||||
const asyncJamClient = useAsyncJamClient(logger);
|
|
||||||
|
|
||||||
const handleJoinSession = async () => {
|
|
||||||
if (asyncJamClient) {
|
|
||||||
try {
|
|
||||||
const result = await asyncJamClient.JoinSession('sessionId');
|
|
||||||
console.log('Joined session:', result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to join session:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLeaveSession = async () => {
|
|
||||||
if (asyncJamClient) {
|
|
||||||
try {
|
|
||||||
await asyncJamClient.LeaveSession();
|
|
||||||
console.log('Left session');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to leave session:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button onClick={handleJoinSession}>Join Session</button>
|
|
||||||
<button onClick={handleLeaveSession}>Leave Session</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default JamSessionComponent;
|
|
||||||
|
|
@ -0,0 +1,391 @@
|
||||||
|
export const MIDI_TRACK = 100
|
||||||
|
|
||||||
|
export const CLIENT_ROLE = {
|
||||||
|
CHILD: 0,
|
||||||
|
PARENT: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OS = {
|
||||||
|
WIN32: "Win32",
|
||||||
|
OSX: "MacOSX",
|
||||||
|
UNIX: "Unix"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ASSIGNMENT = {
|
||||||
|
CHAT: -2,
|
||||||
|
OUTPUT: -1,
|
||||||
|
UNASSIGNED: 0,
|
||||||
|
TRACK1: 1,
|
||||||
|
TRACK2: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VOICE_CHAT = {
|
||||||
|
NO_CHAT: "0",
|
||||||
|
CHAT: "1"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AVAILABILITY_US = "United States";
|
||||||
|
export const MASTER_TRACK = "Master";
|
||||||
|
|
||||||
|
export const EVENTS = {
|
||||||
|
DIALOG_CLOSED: 'dialog_closed',
|
||||||
|
SHOW_SIGNUP: 'show_signup',
|
||||||
|
SHOW_SIGNIN: 'show_signin',
|
||||||
|
RSVP_SUBMITTED: 'rsvp_submitted',
|
||||||
|
RSVP_CANCELED: 'rsvp_canceled',
|
||||||
|
USER_UPDATED: 'user_updated',
|
||||||
|
SESSION_STARTED: 'session_started',
|
||||||
|
SESSION_ENDED: 'session_stopped',
|
||||||
|
FILE_MANAGER_CMD_START: 'file_manager_cmd_start',
|
||||||
|
FILE_MANAGER_CMD_STOP: 'file_manager_cmd_stop',
|
||||||
|
FILE_MANAGER_CMD_PROGRESS: 'file_manager_cmd_progress',
|
||||||
|
FILE_MANAGER_CMD_ASAP_UPDATE: 'file_manager_cmd_asap_update',
|
||||||
|
MIXER_MODE_CHANGED: 'mixer_mode_changed',
|
||||||
|
MUTE_SELECTED: 'mute_selected',
|
||||||
|
SUBSCRIBE_NOTIFICATION: 'subscribe_notification',
|
||||||
|
CONNECTION_UP: 'connection_up',
|
||||||
|
CONNECTION_DOWN: 'connection_down',
|
||||||
|
SCREEN_CHANGED: 'screen_changed',
|
||||||
|
JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state',
|
||||||
|
METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected',
|
||||||
|
CHECKOUT_SIGNED_IN: 'checkout_signed_in',
|
||||||
|
CHECKOUT_SKIP_SIGN_IN: 'checkout_skip_sign_in',
|
||||||
|
PREVIEW_PLAYED: 'preview_played',
|
||||||
|
VST_OPERATION_SELECTED: 'vst_operation_selected',
|
||||||
|
VST_EFFECT_SELECTED: 'vst_effect_selected',
|
||||||
|
LESSON_SESSION_ACTION: 'lesson_session_action',
|
||||||
|
JAMBLASTER_ACTION: 'jamblaster_action'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PLAYBACK_MONITOR_MODE = {
|
||||||
|
MEDIA_FILE: 'MEDIA_FILE',
|
||||||
|
JAMTRACK: 'JAMTRACK',
|
||||||
|
METRONOME: 'METRONOME',
|
||||||
|
BROWSER_MEDIA: 'BROWSER_MEDIA'
|
||||||
|
}
|
||||||
|
export const ALERT_NAMES = {
|
||||||
|
NO_EVENT: 0,
|
||||||
|
BACKEND_ERROR: 1, //generic error - eg P2P message error
|
||||||
|
BACKEND_MIXER_CHANGE: 2, //event that controls have been regenerated
|
||||||
|
|
||||||
|
//network related
|
||||||
|
PACKET_JTR: 3,
|
||||||
|
PACKET_LOSS: 4,
|
||||||
|
PACKET_LATE: 5,
|
||||||
|
|
||||||
|
JTR_QUEUE_DEPTH: 6,
|
||||||
|
|
||||||
|
NETWORK_JTR: 7,
|
||||||
|
NETWORK_PING: 8,
|
||||||
|
|
||||||
|
BITRATE_THROTTLE_WARN: 9,
|
||||||
|
BANDWIDTH_LOW: 10,
|
||||||
|
|
||||||
|
//IO related events
|
||||||
|
INPUT_IO_RATE: 11,
|
||||||
|
INPUT_IO_JTR: 12,
|
||||||
|
OUTPUT_IO_RATE: 13,
|
||||||
|
OUTPUT_IO_JTR: 14,
|
||||||
|
|
||||||
|
// CPU load related
|
||||||
|
CPU_LOAD: 15,
|
||||||
|
DECODE_VIOLATIONS: 16,
|
||||||
|
|
||||||
|
LAST_THRESHOLD: 17,
|
||||||
|
|
||||||
|
WIFI_NETWORK_ALERT: 18, //user or peer is using wifi
|
||||||
|
NO_VALID_AUDIO_CONFIG: 19, // alert the user to popup a config
|
||||||
|
AUDIO_DEVICE_NOT_PRESENT: 20, // the audio device is not connected
|
||||||
|
RECORD_PLAYBACK_STATE: 21, // record/playback events have occurred
|
||||||
|
RUN_UPDATE_CHECK_BACKGROUND: 22, //this is auto check - do
|
||||||
|
RUN_UPDATE_CHECK_INTERACTIVE: 23, //this is initiated by user
|
||||||
|
STUN_EVENT: 24, // system completed stun test... come get the result
|
||||||
|
DEAD_USER_WARN_EVENT: 25, //the backend is not receiving audio from this peer
|
||||||
|
DEAD_USER_REMOVE_EVENT: 26, //the backend is removing the user from session as no audio is coming from this peer
|
||||||
|
WINDOW_CLOSE_BACKGROUND_MODE: 27, //the user has closed the window and the client is now in background mode
|
||||||
|
WINDOW_OPEN_FOREGROUND_MODE: 28, //the user has opened the window and the client is now in forground mode/
|
||||||
|
SESSION_LIVEBROADCAST_FAIL: 29, //error of some sort - so can't broadcast
|
||||||
|
SESSION_LIVEBROADCAST_ACTIVE: 30, //active
|
||||||
|
SESSION_LIVEBROADCAST_STOPPED: 31, //stopped by server/user
|
||||||
|
SESSION_LIVEBROADCAST_PINNED: 32, //node pinned by user
|
||||||
|
SESSION_LIVEBROADCAST_UNPINNED: 33, //node unpinned by user
|
||||||
|
BACKEND_STATUS_MSG: 34, //status/informational message
|
||||||
|
LOCAL_NETWORK_VARIANCE_HIGH: 35,//the ping time via a hairpin for the user network is unnaturally high or variable.
|
||||||
|
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
|
||||||
|
LOCAL_NETWORK_LATENCY_HIGH: 36,
|
||||||
|
RECORDING_CLOSE: 37, //update and remove tracks from front-end
|
||||||
|
PEER_REPORTS_NO_AUDIO_RECV: 38, //letting front-end know audio is not being received from a user in session
|
||||||
|
SHOW_PREFERENCES: 39, // tell frontend to show preferences dialog
|
||||||
|
USB_CONNECTED: 40, // tell frontend that a USB device was connected
|
||||||
|
USB_DISCONNECTED: 41, // tell frontend that a USB device was disconnected
|
||||||
|
JAM_TRACK_SERVER_ERROR: 42, //error talking with server
|
||||||
|
BAD_INTERVAL_RATE: 43, //the audio gear is calling back at rate that does not match the expected interval
|
||||||
|
FIRST_AUDIO_PACKET: 44,// we are receiving audio from peer
|
||||||
|
NETWORK_PORT_MANGLED: 45, // packet from peer indicates network port is being mangled
|
||||||
|
NO_GLOBAL_CLOCK_SERVER: 46, //can't reach global clock NTP server
|
||||||
|
GLOBAL_CLOCK_SYNCED: 47, //clock synced
|
||||||
|
RECORDING_DONE: 48, //the recording writer thread is done
|
||||||
|
VIDEO_WINDOW_OPENED: 49, //video window opened
|
||||||
|
VIDEO_WINDOW_CLOSED: 50,
|
||||||
|
VST_CHANGED: 51, // VST state changed
|
||||||
|
SAMPLERATE_CONFIGURATION_BAD: 52,
|
||||||
|
SHOW_NETWORK_TEST: 53,
|
||||||
|
LAST_ALERT: 54
|
||||||
|
}
|
||||||
|
// recreate eThresholdType enum from MixerDialog.h
|
||||||
|
export const ALERT_TYPES = {
|
||||||
|
0: { "title": "", "message": "" }, // NO_EVENT,
|
||||||
|
1: { "title": "", "message": "" }, // BACKEND_ERROR: generic error - eg P2P message error
|
||||||
|
2: { "title": "", "message": "" }, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated
|
||||||
|
3: { "title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // PACKET_JTR,
|
||||||
|
4: { "title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // PACKET_LOSS
|
||||||
|
5: { "title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // PACKET_LATE,
|
||||||
|
6: { "title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // JTR_QUEUE_DEPTH,
|
||||||
|
7: { "title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // NETWORK_JTR,
|
||||||
|
8: { "title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // NETWORK_PING,
|
||||||
|
9: { "title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // BITRATE_THROTTLE_WARN,
|
||||||
|
10: { "title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // BANDWIDTH_LOW
|
||||||
|
|
||||||
|
// IO related events
|
||||||
|
11: { "title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // INPUT_IO_RATE
|
||||||
|
12: { "title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // INPUT_IO_JTR,
|
||||||
|
13: { "title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // OUTPUT_IO_RATE
|
||||||
|
14: { "title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // OUTPUT_IO_JTR,
|
||||||
|
|
||||||
|
// CPU load related
|
||||||
|
15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // CPU_LOAD
|
||||||
|
16: { "title": "Decode Violations", "message": "" }, // DECODE_VIOLATIONS,
|
||||||
|
17: { "title": "", "message": "" }, // LAST_THRESHOLD
|
||||||
|
18: { "title": "Wifi Alert", "message": "" }, // WIFI_NETWORK_ALERT, //user or peer is using wifi
|
||||||
|
19: { "title": "No Audio Configuration", "message": "You cannot join the session because you do not have a valid audio configuration." }, // NO_VALID_AUDIO_CONFIG,
|
||||||
|
20: { "title": "", "message": "" }, // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected
|
||||||
|
//20: {"title": "Audio Device Not Present", "message": ""}, // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected
|
||||||
|
21: { "title": "", "message": "" }, // RECORD_PLAYBACK_STATE, // record/playback events have occurred
|
||||||
|
22: { "title": "", "message": "" }, // RUN_UPDATE_CHECK_BACKGROUND, //this is auto check - do
|
||||||
|
23: { "title": "", "message": "" }, // RUN_UPDATE_CHECK_INTERACTIVE, //this is initiated by user
|
||||||
|
24: { "title": "", "message": "" }, // STUN_EVENT, // system completed stun test... come get the result
|
||||||
|
25: { "title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you." }, // DEAD_USER_WARN_EVENT, //the backend is not receiving audio from this peer
|
||||||
|
26: { "title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you." }, // DEAD_USER_REMOVE_EVENT, //the backend is removing the user from session as no audio is coming from this peer
|
||||||
|
27: { "title": "", "message": "" }, // WINDOW_CLOSE_BACKGROUND_MODE, //the user has closed the window and the client is now in background mode
|
||||||
|
28: { "title": "", "message": "" }, // WINDOW_OPEN_FOREGROUND_MODE, //the user has opened the window and the client is now in forground mode/
|
||||||
|
|
||||||
|
29: { "title": "Failed to Broadcast", "message": "" }, // SESSION_LIVEBROADCAST_FAIL, //error of some sort - so can't broadcast
|
||||||
|
30: { "title": "", "message": "" }, // SESSION_LIVEBROADCAST_ACTIVE, //active
|
||||||
|
31: { "title": "", "message": "" }, // SESSION_LIVEBROADCAST_STOPPED, //stopped by server/user
|
||||||
|
32: { "title": "Client Pinned", "message": "This client will be the source of a broadcast." }, // SESSION_LIVEBROADCAST_PINNED, //node pinned by user
|
||||||
|
33: { "title": "Client No Longer Pinned", "message": "This client is no longer designated as the source of the broadcast." }, // SESSION_LIVEBROADCAST_UNPINNED, //node unpinned by user
|
||||||
|
|
||||||
|
34: { "title": "", "message": "" }, // BACKEND_STATUS_MSG, //status/informational message
|
||||||
|
35: { "title": "LAN Unpredictable", "message": "Your local network is adding considerable variance to transmit times. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
|
||||||
|
|
||||||
|
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
|
||||||
|
36: { "title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, click the '?'.", help: "https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems" }, // LOCAL_NETWORK_LATENCY_HIGH,
|
||||||
|
37: { "title": "", "message": "" }, // RECORDING_CLOSE, //update and remove tracks from front-end
|
||||||
|
38: { "title": "No Audio Sent", "message": "" }, // PEER_REPORTS_NO_AUDIO_RECV, //update and remove tracks from front-end
|
||||||
|
39: { "title": "", "message": "" }, // SHOW_PREFERENCES, //show preferences dialog
|
||||||
|
40: { "title": "", "message": "" }, // USB_CONNECTED
|
||||||
|
41: { "title": "", "message": "" }, // USB_DISCONNECTED, // tell frontend that a USB device was disconnected
|
||||||
|
42: { "title": "", "message": "" }, // JAM_TRACK_SERVER_ERROR
|
||||||
|
43: { "title": "", "message": "" }, // BAD_INTERVAL_RATE
|
||||||
|
44: { "title": "", "message": "" }, // FIRST_AUDIO_PACKET
|
||||||
|
45: { "title": "", "message": "" }, // NETWORK_PORT_MANGLED
|
||||||
|
46: { "title": "", "message": "" }, // NO_GLOBAL_CLOCK_SERVER
|
||||||
|
47: { "title": "", "message": "" }, // GLOBAL_CLOCK_SYNCED
|
||||||
|
48: { "title": "", "message": "" }, // RECORDING_DONE
|
||||||
|
49: { "title": "", "message": "" }, // VIDEO_WINDOW_OPENED
|
||||||
|
50: { "title": "", "message": "" }, // VIDEO_WINDOW_CLOSED
|
||||||
|
51: { "title": "", "message": "" }, // VST_CHANGED
|
||||||
|
52: { "title": "", "message": "" }, // SAMPLERATE_CONFIGURATION_BAD
|
||||||
|
53: { "title": "", "message": "" }, // SHOW_NETWORK_TEST
|
||||||
|
54: { "title": "", "message": "" } // LAST_ALERT
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const MAX_TRACKS = 6;
|
||||||
|
export const MAX_OUTPUTS = 2;
|
||||||
|
|
||||||
|
// TODO: store these client_id values in instruments table, or store
|
||||||
|
// server_id as the client_id to prevent maintenance nightmares. As it's
|
||||||
|
// set up now, we will have to deploy each time we add new instruments.
|
||||||
|
export const server_to_client_instrument_map = {
|
||||||
|
"Acoustic Guitar": { "client_id": 10, "server_id": "acoustic guitar" },
|
||||||
|
"Bass Guitar": { "client_id": 20, "server_id": "bass guitar" },
|
||||||
|
"Computer": { "client_id": 30, "server_id": "computer" },
|
||||||
|
"Drums": { "client_id": 40, "server_id": "drums" },
|
||||||
|
"Percussion": { "client_id": 41, "server_id": "percussion" },
|
||||||
|
"Electric Guitar": { "client_id": 50, "server_id": "electric guitar" },
|
||||||
|
"Keyboard": { "client_id": 60, "server_id": "keyboard" },
|
||||||
|
"Piano": { "client_id": 61, "server_id": "piano" },
|
||||||
|
"Double Bass": { "client_id": 62, "server_id": "double bass" },
|
||||||
|
"Voice": { "client_id": 70, "server_id": "voice" },
|
||||||
|
"Flute": { "client_id": 80, "server_id": "flute" },
|
||||||
|
"Clarinet": { "client_id": 90, "server_id": "clarinet" },
|
||||||
|
"Saxophone": { "client_id": 100, "server_id": "saxophone" },
|
||||||
|
"Trumpet": { "client_id": 110, "server_id": "trumpet" },
|
||||||
|
"Violin": { "client_id": 120, "server_id": "violin" },
|
||||||
|
"Trombone": { "client_id": 130, "server_id": "trombone" },
|
||||||
|
"Banjo": { "client_id": 140, "server_id": "banjo" },
|
||||||
|
"Harmonica": { "client_id": 150, "server_id": "harmonica" },
|
||||||
|
"Accordion": { "client_id": 160, "server_id": "accordion" },
|
||||||
|
"French Horn": { "client_id": 170, "server_id": "french horn" },
|
||||||
|
"Euphonium": { "client_id": 180, "server_id": "euphonium" },
|
||||||
|
"Tuba": { "client_id": 190, "server_id": "tuba" },
|
||||||
|
"Oboe": { "client_id": 200, "server_id": "oboe" },
|
||||||
|
"Ukulele": { "client_id": 210, "server_id": "ukulele" },
|
||||||
|
"Cello": { "client_id": 220, "server_id": "cello" },
|
||||||
|
"Viola": { "client_id": 230, "server_id": "viola" },
|
||||||
|
"Mandolin": { "client_id": 240, "server_id": "mandolin" },
|
||||||
|
"Other": { "client_id": 250, "server_id": "other" }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const client_to_server_instrument_map = {
|
||||||
|
10: { "server_id": "acoustic guitar" },
|
||||||
|
20: { "server_id": "bass guitar" },
|
||||||
|
30: { "server_id": "computer" },
|
||||||
|
40: { "server_id": "drums" },
|
||||||
|
41: { "server_id": "percussion" },
|
||||||
|
50: { "server_id": "electric guitar" },
|
||||||
|
60: { "server_id": "keyboard" },
|
||||||
|
61: { "server_id": "piano" },
|
||||||
|
62: { "server_id": "double bass" },
|
||||||
|
70: { "server_id": "voice" },
|
||||||
|
80: { "server_id": "flute" },
|
||||||
|
90: { "server_id": "clarinet" },
|
||||||
|
100: { "server_id": "saxophone" },
|
||||||
|
110: { "server_id": "trumpet" },
|
||||||
|
120: { "server_id": "violin" },
|
||||||
|
130: { "server_id": "trombone" },
|
||||||
|
140: { "server_id": "banjo" },
|
||||||
|
150: { "server_id": "harmonica" },
|
||||||
|
160: { "server_id": "accordion" },
|
||||||
|
170: { "server_id": "french horn" },
|
||||||
|
180: { "server_id": "euphonium" },
|
||||||
|
190: { "server_id": "tuba" },
|
||||||
|
200: { "server_id": "oboe" },
|
||||||
|
210: { "server_id": "ukulele" },
|
||||||
|
220: { "server_id": "cello" },
|
||||||
|
230: { "server_id": "viola" },
|
||||||
|
240: { "server_id": "mandolin" },
|
||||||
|
250: { "server_id": "other" }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const entityToPrintable = {
|
||||||
|
music_session: "music session",
|
||||||
|
slot: "Requested time"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AUDIO_DEVICE_BEHAVIOR = {
|
||||||
|
MacOSX_builtin: {
|
||||||
|
display: 'MacOSX Built-In',
|
||||||
|
shortName: 'Built-In',
|
||||||
|
videoURL: "https://www.youtube.com/watch?v=7-9PW50ygHk",
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: false
|
||||||
|
},
|
||||||
|
MacOSX_interface: {
|
||||||
|
display: 'MacOSX external interface',
|
||||||
|
shortName: 'External',
|
||||||
|
videoURL: "https://www.youtube.com/watch?v=7BLld6ogm14",
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: false
|
||||||
|
},
|
||||||
|
Win32_wdm: {
|
||||||
|
display: 'Windows WDM',
|
||||||
|
shortName: 'WDM',
|
||||||
|
videoURL: "https://www.youtube.com/watch?v=L36UBkAV14c",
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: false
|
||||||
|
},
|
||||||
|
Win32_asio: {
|
||||||
|
display: 'Windows ASIO',
|
||||||
|
shortName: 'ASIO',
|
||||||
|
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: true
|
||||||
|
},
|
||||||
|
Win32_asio4all: {
|
||||||
|
display: 'Windows ASIO4ALL',
|
||||||
|
shortName: 'ASIO4ALL',
|
||||||
|
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: true
|
||||||
|
},
|
||||||
|
Linux: {
|
||||||
|
display: 'Linux',
|
||||||
|
shortName: 'linux',
|
||||||
|
videoURL: undefined,
|
||||||
|
showKnobs: true,
|
||||||
|
showASIO: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIX_MODES = {
|
||||||
|
MASTER: true,
|
||||||
|
PERSONAL: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** NAMED_MESSAGES means messages that we show to the user (dialogs/banners/whatever), that we have formally named */
|
||||||
|
export const NAMED_MESSAGES = {
|
||||||
|
MASTER_VS_PERSONAL_MIX: 'master_vs_personal_mix',
|
||||||
|
HOWTO_USE_VIDEO_NOSHOW: 'how-to-use-video',
|
||||||
|
CONFIGURE_VIDEO_NOSHOW: 'configure-video',
|
||||||
|
TEACHER_MUSICIAN_PROFILE: 'teacher-musician-profile'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChannelGroupIds = {
|
||||||
|
"MasterGroup": 0,
|
||||||
|
"MonitorGroup": 1,
|
||||||
|
"MasterCatGroup": 2,
|
||||||
|
"MonitorCatGroup": 3,
|
||||||
|
"AudioInputMusicGroup": 4,
|
||||||
|
"AudioInputChatGroup": 5,
|
||||||
|
"MediaTrackGroup": 6,
|
||||||
|
"StreamOutMusicGroup": 7,
|
||||||
|
"StreamOutChatGroup": 8,
|
||||||
|
"StreamOutMediaGroup": 9,
|
||||||
|
"UserMusicInputGroup": 10,
|
||||||
|
"UserChatInputGroup": 11,
|
||||||
|
"UserMediaInputGroup": 12,
|
||||||
|
"PeerAudioInputMusicGroup": 13,
|
||||||
|
"PeerMediaTrackGroup": 14,
|
||||||
|
"JamTrackGroup": 15,
|
||||||
|
"MetronomeGroup": 16,
|
||||||
|
"MidiInputMusicGroup": 17,
|
||||||
|
"PeerMidiInputMusicGroup": 18,
|
||||||
|
"UsbInputMusicGroup": 19,
|
||||||
|
"PeerUsbInputMusicGroup": 20
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChannelGroupLookup = {
|
||||||
|
0: "MasterGroup",
|
||||||
|
1: "MonitorGroup",
|
||||||
|
2: "MasterCatGroup",
|
||||||
|
3: "MonitorCatGroup",
|
||||||
|
4: "AudioInputMusicGroup",
|
||||||
|
5: "AudioInputChatGroup",
|
||||||
|
6: "MediaTrackGroup",
|
||||||
|
7: "StreamOutMusicGroup",
|
||||||
|
8: "StreamOutChatGroup",
|
||||||
|
9: "StreamOutMediaGroup",
|
||||||
|
10: "UserMusicInputGroup",
|
||||||
|
11: "UserChatInputGroup",
|
||||||
|
12: "UserMediaInputGroup",
|
||||||
|
13: "PeerAudioInputMusicGroup",
|
||||||
|
14: "PeerMediaTrackGroup",
|
||||||
|
15: "JamTrackGroup",
|
||||||
|
16: "MetronomeGroup",
|
||||||
|
17: "MidiInputMusicGroup",
|
||||||
|
18: "PeerMidiInputMusicGroup"
|
||||||
|
}
|
||||||
|
export const CategoryGroupIds = {
|
||||||
|
"AudioInputMusic": "AudioInputMusic",
|
||||||
|
"AudioInputChat": "AudioInputChat",
|
||||||
|
"UserMusic": "UserMusic",
|
||||||
|
"UserChat": "UserChat",
|
||||||
|
"UserMedia": "UserMedia",
|
||||||
|
"MediaTrack": "MediaTrack",
|
||||||
|
"Metronome": "Metronome"
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Modern ES6+ version of asyncJamClient.js for React compatibility
|
// Modern ES6+ version of jamClientProxy.js for React compatibility
|
||||||
// Ported from web/app/assets/javascripts/asyncJamClient.js
|
// Ported from web/app/assets/javascripts/jamClientProxy.js
|
||||||
|
|
||||||
import QWebChannel from 'qwebchannel'
|
import { QWebChannel } from 'qwebchannel'
|
||||||
|
|
||||||
class Deferred {
|
class Deferred {
|
||||||
constructor(request_id) {
|
constructor(request_id) {
|
||||||
|
|
@ -13,7 +13,7 @@ class Deferred {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsyncJamClient {
|
class JamClientProxy {
|
||||||
constructor(app, logger, context = window) {
|
constructor(app, logger, context = window) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
@ -257,11 +257,9 @@ class AsyncJamClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupWebSocketConnection() {
|
setupWebSocketConnection() {
|
||||||
// Assuming QWebChannel is available globally or imported
|
const socket = new WebSocket('ws://localhost:3060');
|
||||||
// eslint-disable-next-line no-undef
|
socket.onopen = () => {
|
||||||
if (typeof QWebChannel !== 'undefined') {
|
console.log("QWebChannel socket opened");
|
||||||
const socket = new WebSocket('ws://localhost:12345'); // Adjust URL as needed
|
|
||||||
|
|
||||||
new QWebChannel(socket, channel => {
|
new QWebChannel(socket, channel => {
|
||||||
this.jkfrontendchannel = channel.objects.jkfrontendchannel;
|
this.jkfrontendchannel = channel.objects.jkfrontendchannel;
|
||||||
|
|
||||||
|
|
@ -277,6 +275,7 @@ class AsyncJamClient {
|
||||||
if (deferred) deferred.resolve(null);
|
if (deferred) deferred.resolve(null);
|
||||||
} else {
|
} else {
|
||||||
let msg = JSON.parse(message);
|
let msg = JSON.parse(message);
|
||||||
|
console.log("[jamClientProxy] Message received via QWebChannel: ", msg);
|
||||||
let req_id = msg.request_id;
|
let req_id = msg.request_id;
|
||||||
let response = msg.response;
|
let response = msg.response;
|
||||||
let evt_id = msg.event_id;
|
let evt_id = msg.event_id;
|
||||||
|
|
@ -288,7 +287,7 @@ class AsyncJamClient {
|
||||||
if (this.skipLogMethods.length > 0 && this.skipLogMethods.includes(methodName)) {
|
if (this.skipLogMethods.length > 0 && this.skipLogMethods.includes(methodName)) {
|
||||||
// Skip logging
|
// Skip logging
|
||||||
} else if (this.displayLogMethod.includes(methodName)) {
|
} else if (this.displayLogMethod.includes(methodName)) {
|
||||||
this.logger.log('[asyncJamClient] Message received via QWebChannel: ', msg);
|
this.logger.log('[jamClientProxy] Message received via QWebChannel: ', msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
|
|
@ -299,17 +298,22 @@ class AsyncJamClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.log('[asyncJamClient] Error when receiving message via QWebChannel');
|
this.logger.log('[jamClientProxy] Error when receiving message via QWebChannel');
|
||||||
if (deferred) {
|
if (deferred) {
|
||||||
deferred.reject(e.message);
|
deferred.reject(e.message);
|
||||||
deferred = null;
|
deferred = null;
|
||||||
}
|
}
|
||||||
// Note: Bugsnag integration would need to be handled separately
|
// Note: Bugsnag integration would need to be handled separately
|
||||||
console.error('asyncJamClient error:', e);
|
console.error('jamClientProxy error:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket.onclose = () => { }; // Handle close if needed
|
||||||
|
|
||||||
|
socket.onerror = error => { }; // Handle error if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(evt_id, response) {
|
handleEvent(evt_id, response) {
|
||||||
|
|
@ -318,13 +322,13 @@ class AsyncJamClient {
|
||||||
switch (evt_id.toString()) {
|
switch (evt_id.toString()) {
|
||||||
case '3006': // execute_script
|
case '3006': // execute_script
|
||||||
if (!response['execute_script'].match('HandleBridgeCallback2')) {
|
if (!response['execute_script'].match('HandleBridgeCallback2')) {
|
||||||
// this.logger.log(`[asyncJamClient] 3006 execute_script: ${response['execute_script']}`);
|
// this.logger.log(`[jamClientProxy] 3006 execute_script: ${response['execute_script']}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
eval(response['execute_script']);
|
eval(response['execute_script']);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.log(`[asyncJamClient] error: execute_script: ${response['execute_script']}`);
|
this.logger.log(`[jamClientProxy] error: execute_script: ${response['execute_script']}`);
|
||||||
this.logger.log(error);
|
this.logger.log(error);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -337,13 +341,13 @@ class AsyncJamClient {
|
||||||
this.context.JK.JamServer.sendP2PMessage(clientId, msg);
|
this.context.JK.JamServer.sendP2PMessage(clientId, msg);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.log(`[asyncJamClient] error: sendP2PMessage: ${response['message']}`);
|
this.logger.log(`[jamClientProxy] error: sendP2PMessage: ${response['message']}`);
|
||||||
this.logger.log(error);
|
this.logger.log(error);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '3010': // JKVideoSession
|
case '3010': // JKVideoSession
|
||||||
this.logger.log(`[asyncJamClient] 3010 JKVideoSession: ${response['JKVideoSession']['connect']}`);
|
this.logger.log(`[jamClientProxy] 3010 JKVideoSession: ${response['JKVideoSession']['connect']}`);
|
||||||
const vidConnect = response['JKVideoSession']['connect'];
|
const vidConnect = response['JKVideoSession']['connect'];
|
||||||
if (this.context.ExternalVideoActions) {
|
if (this.context.ExternalVideoActions) {
|
||||||
this.context.ExternalVideoActions.setVideoEnabled(vidConnect);
|
this.context.ExternalVideoActions.setVideoEnabled(vidConnect);
|
||||||
|
|
@ -354,7 +358,7 @@ class AsyncJamClient {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '3011': // AudioFormatChangeEvent
|
case '3011': // AudioFormatChangeEvent
|
||||||
this.logger.log(`[asyncJamClient] 3011 AudioFormatChangeEvent: ${response['AudioFormat']}`);
|
this.logger.log(`[jamClientProxy] 3011 AudioFormatChangeEvent: ${response['AudioFormat']}`);
|
||||||
const audioFormat = response['AudioFormat'];
|
const audioFormat = response['AudioFormat'];
|
||||||
if (this.context.RecordingActions) {
|
if (this.context.RecordingActions) {
|
||||||
this.context.RecordingActions.audioRecordingFormatChanged(`.${audioFormat}`);
|
this.context.RecordingActions.audioRecordingFormatChanged(`.${audioFormat}`);
|
||||||
|
|
@ -381,7 +385,7 @@ class AsyncJamClient {
|
||||||
window.location.href = httpUrl;
|
window.location.href = httpUrl;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.logger.log(`[asyncJamClient] invalid customUrl: ${httpUrl}`);
|
this.logger.log(`[jamClientProxy] invalid customUrl: ${httpUrl}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +440,7 @@ class AsyncJamClient {
|
||||||
if (this.skipLogMethods.length > 0 && this.skipLogMethods.includes(prop)) {
|
if (this.skipLogMethods.length > 0 && this.skipLogMethods.includes(prop)) {
|
||||||
// Skip logging
|
// Skip logging
|
||||||
} else if (this.displayLogMethod.includes(prop)) {
|
} else if (this.displayLogMethod.includes(prop)) {
|
||||||
this.logger.log('[asyncJamClient] diverting to backend:', prop, appMessage);
|
this.logger.log('[jamClientProxy] diverting to backend:', prop, appMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.jkfrontendchannel) {
|
if (this.jkfrontendchannel) {
|
||||||
|
|
@ -445,12 +449,12 @@ class AsyncJamClient {
|
||||||
this.deferredQueue.push(deferred);
|
this.deferredQueue.push(deferred);
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error('[asyncJamClient] Native app not connected', e.message);
|
this.logger.error('[jamClientProxy] Native app not connected', e.message);
|
||||||
deferred.reject('Native app not connected');
|
deferred.reject('Native app not connected');
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.logger.info('[asyncJamClient] jkfrontendchannel is not ready yet');
|
this.logger.info('[jamClientProxy] jkfrontendchannel is not ready yet');
|
||||||
deferred.reject('frontendchannel is not ready yet');
|
deferred.reject('frontendchannel is not ready yet');
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
@ -466,7 +470,7 @@ class AsyncJamClient {
|
||||||
};
|
};
|
||||||
|
|
||||||
const proxy = new Proxy(this, handler);
|
const proxy = new Proxy(this, handler);
|
||||||
this.logger.log('[asyncJamClient] Connected to WebChannel, ready to send/receive messages!');
|
this.logger.log('[jamClientProxy] Connected to WebChannel, ready to send/receive messages!');
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,4 +480,4 @@ class AsyncJamClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AsyncJamClient;
|
export default JamClientProxy;
|
||||||
|
|
@ -251,8 +251,8 @@ export const createSession = (options = {}) => {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: JSON.stringify(options)
|
body: JSON.stringify(options)
|
||||||
})
|
})
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -784,4 +784,29 @@ export const updateEmail = (userId, email, current_password) => {
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// function updateUdpReachable(options) {
|
||||||
|
// var id = getId(options);
|
||||||
|
|
||||||
|
// return $.ajax({
|
||||||
|
// type: "POST",
|
||||||
|
// dataType: "json",
|
||||||
|
// contentType: 'application/json',
|
||||||
|
// url: "/api/users/" + id + "/udp_reachable",
|
||||||
|
// data: JSON.stringify(options),
|
||||||
|
// processData: false
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const updateUdpReachable = (options = {}) => {
|
||||||
|
const { id, ...rest } = options;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
apiFetch(`/users/${id}/udp_reachable`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(rest)
|
||||||
|
})
|
||||||
|
.then(response => resolve(response))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -81,11 +81,11 @@ export const numberFormatter = (number, fixed = 2) => {
|
||||||
? (Math.abs(Number(number)) / 1.0e9).toFixed(fixed) + 'B'
|
? (Math.abs(Number(number)) / 1.0e9).toFixed(fixed) + 'B'
|
||||||
: // Six Zeroes for Millions
|
: // Six Zeroes for Millions
|
||||||
Math.abs(Number(number)) >= 1.0e6
|
Math.abs(Number(number)) >= 1.0e6
|
||||||
? (Math.abs(Number(number)) / 1.0e6).toFixed(fixed) + 'M'
|
? (Math.abs(Number(number)) / 1.0e6).toFixed(fixed) + 'M'
|
||||||
: // Three Zeroes for Thousands
|
: // Three Zeroes for Thousands
|
||||||
Math.abs(Number(number)) >= 1.0e3
|
Math.abs(Number(number)) >= 1.0e3
|
||||||
? (Math.abs(Number(number)) / 1.0e3).toFixed(fixed) + 'K'
|
? (Math.abs(Number(number)) / 1.0e3).toFixed(fixed) + 'K'
|
||||||
: Math.abs(Number(number)).toFixed(fixed);
|
: Math.abs(Number(number)).toFixed(fixed);
|
||||||
};
|
};
|
||||||
|
|
||||||
//===============================
|
//===============================
|
||||||
|
|
@ -203,21 +203,21 @@ export const capitalize = str => (str.charAt(0).toUpperCase() + str.slice(1)).re
|
||||||
|
|
||||||
export const titleize = (str) => {
|
export const titleize = (str) => {
|
||||||
return str.replace(
|
return str.replace(
|
||||||
/\w\S*/g,
|
/\w\S*/g,
|
||||||
(txt) => {
|
(txt) => {
|
||||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const truncate = (str, length=100) => {
|
export const truncate = (str, length = 100) => {
|
||||||
if(!str) return;
|
if (!str) return;
|
||||||
if (str.length <= length) {
|
if (str.length <= length) {
|
||||||
return str;
|
return str;
|
||||||
} else {
|
} else {
|
||||||
return `${str.substring(0, length)}...`;
|
return `${str.substring(0, length)}...`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routesSlicer = ({ routes, columns = 3, rows }) => {
|
export const routesSlicer = ({ routes, columns = 3, rows }) => {
|
||||||
const routesCollection = [];
|
const routesCollection = [];
|
||||||
|
|
@ -256,15 +256,15 @@ export const copyToClipBoard = textFieldRef => {
|
||||||
export const currencyFormat = (num) => {
|
export const currencyFormat = (num) => {
|
||||||
return new Intl.NumberFormat('en-US',
|
return new Intl.NumberFormat('en-US',
|
||||||
{ style: 'currency', currency: 'USD' }
|
{ style: 'currency', currency: 'USD' }
|
||||||
).format(num);
|
).format(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
const days = new Array("Sun", "Mon", "Tue",
|
const days = new Array("Sun", "Mon", "Tue",
|
||||||
"Wed", "Thu", "Fri", "Sat");
|
"Wed", "Thu", "Fri", "Sat");
|
||||||
|
|
||||||
const months = new Array("January", "February", "March",
|
const months = new Array("January", "February", "March",
|
||||||
"April", "May", "June", "July", "August", "September",
|
"April", "May", "June", "July", "August", "September",
|
||||||
"October", "November", "December");
|
"October", "November", "December");
|
||||||
|
|
||||||
export const monthName = (monthNumber) => {
|
export const monthName = (monthNumber) => {
|
||||||
return months[monthNumber];
|
return months[monthNumber];
|
||||||
|
|
@ -272,7 +272,7 @@ export const monthName = (monthNumber) => {
|
||||||
|
|
||||||
export const formatDateShort = (dateString) => {
|
export const formatDateShort = (dateString) => {
|
||||||
const date = dateString instanceof Date ? dateString : new Date(dateString);
|
const date = dateString instanceof Date ? dateString : new Date(dateString);
|
||||||
return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
|
return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns Fri May 20, 2013
|
// returns Fri May 20, 2013
|
||||||
|
|
@ -287,7 +287,7 @@ export const formatDate = (dateString, options = {}) => {
|
||||||
return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + padString(date.getDate(), 2) + ', ' + date.getFullYear();
|
return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + padString(date.getDate(), 2) + ', ' + date.getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const groupByKey = (list, key) => list.reduce((hash, obj) => ({...hash, [obj[key]]:( hash[obj[key]] || [] ).concat(obj)}), {})
|
export const groupByKey = (list, key) => list.reduce((hash, obj) => ({ ...hash, [obj[key]]: (hash[obj[key]] || []).concat(obj) }), {})
|
||||||
|
|
||||||
|
|
||||||
const padString = (str, max) => {
|
const padString = (str, max) => {
|
||||||
|
|
@ -302,9 +302,9 @@ const padString = (str, max) => {
|
||||||
export const detectOS = () => {
|
export const detectOS = () => {
|
||||||
let platform;
|
let platform;
|
||||||
|
|
||||||
if(navigator.platform) {
|
if (navigator.platform) {
|
||||||
platform = navigator.platform.toLowerCase();
|
platform = navigator.platform.toLowerCase();
|
||||||
}else if(navigator.userAgentData){
|
} else if (navigator.userAgentData) {
|
||||||
platform = navigator.userAgentData.platform.toLowerCase();
|
platform = navigator.userAgentData.platform.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,25 +336,52 @@ export const isAppleSilicon = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatUtcTime = (date, change) => {
|
export const formatUtcTime = (date, change) => {
|
||||||
if (change) {
|
if (change) {
|
||||||
date.setMinutes(Math.ceil(date.getMinutes() / 30) * 30);
|
date.setMinutes(Math.ceil(date.getMinutes() / 30) * 30);
|
||||||
|
}
|
||||||
|
var h12h = date.getHours();
|
||||||
|
var m12h = date.getMinutes();
|
||||||
|
var ampm;
|
||||||
|
if (h12h >= 0 && h12h < 12) {
|
||||||
|
if (h12h === 0) {
|
||||||
|
h12h = 12; // 0 becomes 12
|
||||||
}
|
}
|
||||||
var h12h = date.getHours();
|
ampm = "AM";
|
||||||
var m12h = date.getMinutes();
|
}
|
||||||
var ampm;
|
else {
|
||||||
if (h12h >= 0 && h12h < 12) {
|
if (h12h > 12) {
|
||||||
if (h12h === 0) {
|
h12h -= 12; // 13-23 becomes 1-11
|
||||||
h12h = 12; // 0 becomes 12
|
|
||||||
}
|
|
||||||
ampm = "AM";
|
|
||||||
}
|
}
|
||||||
else {
|
ampm = "PM";
|
||||||
if (h12h > 12) {
|
}
|
||||||
h12h -= 12; // 13-23 becomes 1-11
|
var timeString = ("00" + h12h).slice(-2) + ":" + ("00" + m12h).slice(-2) + " " + ampm;
|
||||||
}
|
|
||||||
ampm = "PM";
|
|
||||||
}
|
|
||||||
var timeString = ("00" + h12h).slice(-2) + ":" + ("00" + m12h).slice(-2) + " " + ampm;
|
|
||||||
|
|
||||||
return timeString;
|
return timeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://stackoverflow.com/a/8809472/834644
|
||||||
|
export const generateUUID = function () {
|
||||||
|
var d = new Date().getTime();
|
||||||
|
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = (d + Math.random() * 16) % 16 | 0;
|
||||||
|
d = Math.floor(d / 16);
|
||||||
|
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
||||||
|
});
|
||||||
|
return uuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toQueryString = (obj, prefix) => {
|
||||||
|
const str = [];
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
const k = prefix ? `${prefix}[${key}]` : key;
|
||||||
|
const v = obj[key];
|
||||||
|
if (typeof v === "object" && v !== null) {
|
||||||
|
str.push(toQueryString(v, k));
|
||||||
|
} else {
|
||||||
|
str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.join("&");
|
||||||
|
}
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
export default function useClientWebSocket(url) {
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
|
||||||
const [messages, setMessages] = useState([]);
|
|
||||||
const ws = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
ws.current = new WebSocket(url);
|
|
||||||
|
|
||||||
ws.current.onopen = () => {
|
|
||||||
console.log('Connected to WebSocket');
|
|
||||||
setIsConnected(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onmessage = (event) => {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
setMessages(prev => [...prev, data]);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onclose = () => {
|
|
||||||
console.log('WebSocket disconnected');
|
|
||||||
setIsConnected(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onerror = (error) => {
|
|
||||||
console.error('WebSocket error:', error);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [url]);
|
|
||||||
|
|
||||||
const sendMessage = (type, params = {}) => {
|
|
||||||
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
|
|
||||||
ws.current.send(JSON.stringify({ type, params }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { isConnected, messages, sendMessage };
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
// jam-ui/src/hooks/useJamWebSocketClient.js
|
||||||
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
|
import { generateUUID, getCookieValue, createCookie } from '../helpers/utils';
|
||||||
|
import { messageFactory, MessageType } from '../helpers/MessageFactory';
|
||||||
|
import { useJamClient } from '../context/JamClientContext';
|
||||||
|
import { useStun } from './useStun';
|
||||||
|
|
||||||
|
export const ConnectionStatus = {
|
||||||
|
DISCONNECTED: 'disconnected',
|
||||||
|
CONNECTING: 'connecting',
|
||||||
|
CONNECTED: 'connected',
|
||||||
|
RECONNECTING: 'reconnecting',
|
||||||
|
ERROR: 'error'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function useJamClientService(url) {
|
||||||
|
const [connectionStatus, setConnectionStatus] = useState(ConnectionStatus.DISCONNECTED);
|
||||||
|
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
||||||
|
const [lastError, setLastError] = useState(null);
|
||||||
|
const [hasReachedMaxAttempts, setHasReachedMaxAttempts] = useState(false);
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
|
||||||
|
const ws = useRef(null);
|
||||||
|
const callbacks = useRef({});
|
||||||
|
const requestId = useRef(0);
|
||||||
|
const reconnectTimeoutRef = useRef(null);
|
||||||
|
const reconnectIntervalRef = useRef(null);
|
||||||
|
const currentAttemptRef = useRef(0);
|
||||||
|
const maxReconnectAttempts = 10;
|
||||||
|
const baseReconnectDelay = 1000; // 1 second
|
||||||
|
const maxReconnectDelay = 30000; // 30 seconds
|
||||||
|
const shouldReconnect = useRef(true);
|
||||||
|
const server = useRef({});
|
||||||
|
|
||||||
|
const jamClient = useJamClient();
|
||||||
|
console.log("useJamClientService jamClient", jamClient)
|
||||||
|
//server.current.jamClient = jamClient;
|
||||||
|
const stun = useStun(server.current);
|
||||||
|
|
||||||
|
// Calculate exponential backoff delay
|
||||||
|
const getReconnectDelay = useCallback((attempt) => {
|
||||||
|
const delay = Math.min(baseReconnectDelay * Math.pow(2, attempt), maxReconnectDelay);
|
||||||
|
return delay + Math.random() * 1000; // Add jitter
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Connect to WebSocket
|
||||||
|
const connect = useCallback(async () => {
|
||||||
|
if (ws.current && (ws.current.readyState === WebSocket.OPEN || ws.current.readyState === WebSocket.CONNECTING)) {
|
||||||
|
return; // Already connected or connecting
|
||||||
|
}
|
||||||
|
|
||||||
|
setConnectionStatus(ConnectionStatus.CONNECTING);
|
||||||
|
console.log('_WEBSOCKET_ Attempting to connect to WebSocket...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clientType = 'client'; //TODO: make dynamic
|
||||||
|
console.log("_WEBSOCKET_ jamClient?.clientID: ", jamClient.clientID());
|
||||||
|
const clientId = getCookieValue('client_id') || await jamClient.clientID() || '';
|
||||||
|
|
||||||
|
// Gather necessary parameters before establishing the connection
|
||||||
|
// Use Promise.all to wait for all async calls to complete
|
||||||
|
const operatingModePromise = await jamClient.getOperatingMode();
|
||||||
|
const macHashPromise = await jamClient.SessionGetMacHash();
|
||||||
|
const osStringPromise = await jamClient.GetDetailedOS();
|
||||||
|
|
||||||
|
const allPromises = [
|
||||||
|
operatingModePromise,
|
||||||
|
macHashPromise,
|
||||||
|
osStringPromise,
|
||||||
|
];
|
||||||
|
|
||||||
|
Promise.all(allPromises).then(async ([operatingMode, machine, osString]) => {
|
||||||
|
const channelId = generateUUID();
|
||||||
|
const rememberToken = getCookieValue('remember_token');
|
||||||
|
|
||||||
|
if (!rememberToken) {
|
||||||
|
console.log("_WEBSOCKET_ No remember_token found, user needs to log in.");
|
||||||
|
//TODO: trigger login UI
|
||||||
|
}
|
||||||
|
|
||||||
|
machine = machine?.all || '';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
channel_id: channelId,
|
||||||
|
token: rememberToken,
|
||||||
|
client_type: clientType,
|
||||||
|
client_id: clientId,
|
||||||
|
machine: machine,
|
||||||
|
os: osString,
|
||||||
|
product: "JamClientModern",
|
||||||
|
udp_reachable: await stun.sync(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = new URLSearchParams(params)
|
||||||
|
const fullUrl = `${url}?${queryString}`;
|
||||||
|
|
||||||
|
console.log('_WEBSOCKET_ Connecting to WebSocket URL:', fullUrl);
|
||||||
|
|
||||||
|
ws.current = new WebSocket(fullUrl);
|
||||||
|
|
||||||
|
ws.current.channelId = channelId;
|
||||||
|
|
||||||
|
ws.current.onopen = () => {
|
||||||
|
console.log('_WEBSOCKET_ Connected to JamKazam WebSocket');
|
||||||
|
setConnectionStatus(ConnectionStatus.CONNECTED);
|
||||||
|
setReconnectAttempts(0);
|
||||||
|
currentAttemptRef.current = 0;
|
||||||
|
setLastError(null);
|
||||||
|
setHasReachedMaxAttempts(false); // Reset the flag when we successfully connect
|
||||||
|
|
||||||
|
// Re-initialize session after reconnection
|
||||||
|
if (reconnectAttempts > 0) {
|
||||||
|
console.log('_WEBSOCKET_ Reconnected successfully, reinitializing session...');
|
||||||
|
// You can add session recovery logic here
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("_WEBSOCKET_ remember_token: ", rememberToken);
|
||||||
|
if (rememberToken) {
|
||||||
|
rememberLogin(rememberToken);
|
||||||
|
} else {
|
||||||
|
//no remember token, show login screen
|
||||||
|
console.log("_WEBSOCKET_ No remember_token found, user needs to log in.");
|
||||||
|
//TODO: trigger login UI
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
const { type, header, payload } = message;
|
||||||
|
console.log('_WEBSOCKET_ Message received:', message);
|
||||||
|
|
||||||
|
//Handle response callbacks
|
||||||
|
if (message.id && callbacks.current[MessageType.RESPONSE]) {
|
||||||
|
callbacks.current[MessageType.RESPONSE].forEach(callback => callback(header, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle event callbacks
|
||||||
|
if (type && callbacks.current[type]) {
|
||||||
|
callbacks.current[type].forEach(callback => callback(header, payload));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('_WEBSOCKET_ Error parsing message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onclose = (event) => {
|
||||||
|
console.log('_WEBSOCKET_ JamKazam WebSocket disconnected:', event.code, event.reason);
|
||||||
|
setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
||||||
|
|
||||||
|
// Attempt reconnection if it wasn't intentional and we haven't reached max attempts
|
||||||
|
if (shouldReconnect.current && !event.wasClean && !hasReachedMaxAttempts) {
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.onerror = (error) => {
|
||||||
|
console.error('JamKazam WebSocket error:', error);
|
||||||
|
setConnectionStatus(ConnectionStatus.ERROR);
|
||||||
|
setLastError(error);
|
||||||
|
|
||||||
|
// Attempt reconnection if we haven't reached max attempts
|
||||||
|
if (shouldReconnect.current && !hasReachedMaxAttempts) {
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('_WEBSOCKET_ Failed to create WebSocket connection:', error);
|
||||||
|
setConnectionStatus(ConnectionStatus.ERROR);
|
||||||
|
setLastError(error);
|
||||||
|
scheduleReconnect();
|
||||||
|
}
|
||||||
|
}, [url, reconnectAttempts]);
|
||||||
|
|
||||||
|
const rememberLogin = useCallback((rememberToken) => {
|
||||||
|
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
|
||||||
|
const loginMessage = messageFactory.loginWithToken(rememberToken, null, 'browser');
|
||||||
|
ws.current.send(JSON.stringify(loginMessage));
|
||||||
|
console.log('_WEBSOCKET_ Sent remember login message', loginMessage);
|
||||||
|
} else {
|
||||||
|
console.warn('_WEBSOCKET_ Cannot send remember login, WebSocket not connected');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Schedule reconnection with exponential backoff
|
||||||
|
const scheduleReconnect = useCallback(() => {
|
||||||
|
if (!shouldReconnect.current) return;
|
||||||
|
|
||||||
|
const attempt = currentAttemptRef.current;
|
||||||
|
if (attempt >= maxReconnectAttempts) {
|
||||||
|
console.log(`_WEBSOCKET_ Max reconnection attempts reached (${attempt})`);
|
||||||
|
setConnectionStatus(ConnectionStatus.ERROR);
|
||||||
|
setHasReachedMaxAttempts(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delay = getReconnectDelay(attempt);
|
||||||
|
console.log(`_WEBSOCKET_ Scheduling reconnection in ${delay}ms (attempt ${attempt + 1}/${maxReconnectAttempts})`);
|
||||||
|
|
||||||
|
setConnectionStatus(ConnectionStatus.RECONNECTING);
|
||||||
|
const newAttempt = attempt + 1;
|
||||||
|
setReconnectAttempts(newAttempt);
|
||||||
|
currentAttemptRef.current = newAttempt;
|
||||||
|
|
||||||
|
// Clear any existing timeout to prevent overlapping reconnect attempts
|
||||||
|
if (reconnectTimeoutRef.current) {
|
||||||
|
clearTimeout(reconnectTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectTimeoutRef.current = setTimeout(() => {
|
||||||
|
connect();
|
||||||
|
}, delay);
|
||||||
|
}, [maxReconnectAttempts, getReconnectDelay, connect]);
|
||||||
|
|
||||||
|
// Disconnect WebSocket
|
||||||
|
const disconnect = useCallback(() => {
|
||||||
|
shouldReconnect.current = false;
|
||||||
|
|
||||||
|
if (reconnectTimeoutRef.current) {
|
||||||
|
clearTimeout(reconnectTimeoutRef.current);
|
||||||
|
reconnectTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws.current) {
|
||||||
|
ws.current.close(1000, 'Client disconnecting');
|
||||||
|
}
|
||||||
|
|
||||||
|
setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sendMessage = useCallback((type, params = {}) => {
|
||||||
|
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
|
||||||
|
const message = {
|
||||||
|
type,
|
||||||
|
params,
|
||||||
|
id: ++requestId.current
|
||||||
|
};
|
||||||
|
ws.current.send(JSON.stringify(message));
|
||||||
|
return message.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sendRequest = useCallback((type, params = {}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = sendMessage(type, params);
|
||||||
|
if (id) {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('Request timeout'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
const responseHandler = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.id === id) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
ws.current.removeEventListener('message', responseHandler);
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing response:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.current.addEventListener('message', responseHandler);
|
||||||
|
} else {
|
||||||
|
reject(new Error('WebSocket not connected'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [sendMessage]);
|
||||||
|
|
||||||
|
|
||||||
|
const registerMessageCallback = useCallback((messageType, callback) => {
|
||||||
|
if (!callbacks.current[messageType]) {
|
||||||
|
callbacks.current[messageType] = []
|
||||||
|
}
|
||||||
|
callbacks.current[messageType].push(callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unregisterMessageCallback = useCallback((messageType, callback) => {
|
||||||
|
if (callbacks.current[messageType]) {
|
||||||
|
// Remove specific callback from array
|
||||||
|
callbacks.current[messageType] = callbacks.current[messageType].filter(cb => cb !== callback);
|
||||||
|
}
|
||||||
|
// If no callbacks remain for this type, delete the array
|
||||||
|
if (callbacks.current[messageType] && callbacks.current[messageType].length === 0) {
|
||||||
|
delete callbacks.current[messageType];
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
//user logged in to session screen. register login callback with the server
|
||||||
|
useEffect(() => {
|
||||||
|
registerLoginAck()
|
||||||
|
registerServerRejection()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const registerLoginAck = useCallback(() => {
|
||||||
|
// Register the login acknowledgment callback
|
||||||
|
console.log("registering login ack callback")
|
||||||
|
registerMessageCallback(MessageType.LOGIN_ACK, loggedIn)
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const registerServerRejection = useCallback(() => {
|
||||||
|
// Register the login acknowledgment callback
|
||||||
|
console.log("registering server rejection callback")
|
||||||
|
registerMessageCallback(MessageType.SERVER_REJECTION, serverRejection)
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loggedIn = ((header, payload) => {
|
||||||
|
console.log("_WEBSOCKET_ logged in callback from websocket", header, payload);
|
||||||
|
server.current.ws = ws.current;
|
||||||
|
server.current.signedIn = true;
|
||||||
|
server.current.clientId = payload.client_id;
|
||||||
|
server.current.publicIp = payload.public_ip;
|
||||||
|
|
||||||
|
jamClient.clientID = payload.client_id;
|
||||||
|
|
||||||
|
//clearConnectTimeout();
|
||||||
|
//heartbeatStateReset();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const msg = {
|
||||||
|
user_id: payload.user_id,
|
||||||
|
token: payload.token,
|
||||||
|
username: payload.username,
|
||||||
|
arses: payload.arses,
|
||||||
|
client_id_int: payload.client_id_int,
|
||||||
|
subscription: payload.subscription
|
||||||
|
}
|
||||||
|
if (payload.connection_policy) {
|
||||||
|
try {
|
||||||
|
msg.policy = JSON.parse(payload.connection_policy)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
msg.policy = null
|
||||||
|
console.log("unable to parse connection policy", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("logged with new msg", msg)
|
||||||
|
jamClient.OnLoggedIn(msg); // ACTS AS CONTINUATION
|
||||||
|
} catch (error) {
|
||||||
|
console.log("fallback to old callback", error)
|
||||||
|
jamClient.OnLoggedIn(payload.user_id, payload.token, payload.username); // ACTS AS CONTINUATION
|
||||||
|
}
|
||||||
|
setIsLoggedIn(true);
|
||||||
|
|
||||||
|
// set clientId cookie if not already set
|
||||||
|
if (!getCookieValue('client_id')) {
|
||||||
|
createCookie('client_id', payload.client_id, 365);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const serverRejection = ((header, payload) => {
|
||||||
|
console.log("server rejection callback from websocket", header, payload);
|
||||||
|
alert("Server rejected connection: " + payload.reason);
|
||||||
|
jamClient.OnServerRejection(payload.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Initialize connection
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
shouldReconnect.current = false;
|
||||||
|
if (reconnectTimeoutRef.current) {
|
||||||
|
clearTimeout(reconnectTimeoutRef.current);
|
||||||
|
}
|
||||||
|
if (ws.current) {
|
||||||
|
ws.current.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Legacy compatibility - keep isConnected for existing code
|
||||||
|
const isConnected = connectionStatus === ConnectionStatus.CONNECTED;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Legacy properties for backward compatibility
|
||||||
|
isConnected,
|
||||||
|
|
||||||
|
// New connection management properties
|
||||||
|
connectionStatus,
|
||||||
|
reconnectAttempts,
|
||||||
|
lastError,
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
jamClient,
|
||||||
|
sendMessage,
|
||||||
|
sendRequest,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
|
||||||
|
isLoggedIn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,355 +0,0 @@
|
||||||
// jam-ui/src/hooks/useJamWebSocketClient.js
|
|
||||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
||||||
|
|
||||||
export const ConnectionStatus = {
|
|
||||||
DISCONNECTED: 'disconnected',
|
|
||||||
CONNECTING: 'connecting',
|
|
||||||
CONNECTED: 'connected',
|
|
||||||
RECONNECTING: 'reconnecting',
|
|
||||||
ERROR: 'error'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function useJamWebSocketClient(url = 'ws://localhost:8080') {
|
|
||||||
const [connectionStatus, setConnectionStatus] = useState(ConnectionStatus.DISCONNECTED);
|
|
||||||
const [messages, setMessages] = useState([]);
|
|
||||||
const [sessionData, setSessionData] = useState(null);
|
|
||||||
const [mixers, setMixers] = useState([]);
|
|
||||||
const [participants, setParticipants] = useState([]);
|
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
|
||||||
const [currentPosition, setCurrentPosition] = useState(0);
|
|
||||||
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
|
||||||
const [lastError, setLastError] = useState(null);
|
|
||||||
const [hasReachedMaxAttempts, setHasReachedMaxAttempts] = useState(false);
|
|
||||||
|
|
||||||
const ws = useRef(null);
|
|
||||||
const callbacks = useRef({});
|
|
||||||
const requestId = useRef(0);
|
|
||||||
const reconnectTimeoutRef = useRef(null);
|
|
||||||
const reconnectIntervalRef = useRef(null);
|
|
||||||
const currentAttemptRef = useRef(0);
|
|
||||||
const maxReconnectAttempts = 10;
|
|
||||||
const baseReconnectDelay = 1000; // 1 second
|
|
||||||
const maxReconnectDelay = 30000; // 30 seconds
|
|
||||||
const shouldReconnect = useRef(true);
|
|
||||||
|
|
||||||
// Calculate exponential backoff delay
|
|
||||||
const getReconnectDelay = useCallback((attempt) => {
|
|
||||||
const delay = Math.min(baseReconnectDelay * Math.pow(2, attempt), maxReconnectDelay);
|
|
||||||
return delay + Math.random() * 1000; // Add jitter
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Connect to WebSocket
|
|
||||||
const connect = useCallback(() => {
|
|
||||||
if (ws.current && (ws.current.readyState === WebSocket.OPEN || ws.current.readyState === WebSocket.CONNECTING)) {
|
|
||||||
return; // Already connected or connecting
|
|
||||||
}
|
|
||||||
|
|
||||||
setConnectionStatus(ConnectionStatus.CONNECTING);
|
|
||||||
console.log('_WEBSOCKET_ Attempting to connect to WebSocket...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
ws.current = new WebSocket(url);
|
|
||||||
|
|
||||||
ws.current.onopen = () => {
|
|
||||||
console.log('_WEBSOCKET_ Connected to JamKazam WebSocket');
|
|
||||||
setConnectionStatus(ConnectionStatus.CONNECTED);
|
|
||||||
setReconnectAttempts(0);
|
|
||||||
currentAttemptRef.current = 0;
|
|
||||||
setLastError(null);
|
|
||||||
setHasReachedMaxAttempts(false); // Reset the flag when we successfully connect
|
|
||||||
|
|
||||||
// Re-initialize session after reconnection
|
|
||||||
if (reconnectAttempts > 0) {
|
|
||||||
console.log('_WEBSOCKET_ Reconnected successfully, reinitializing session...');
|
|
||||||
// You can add session recovery logic here
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onmessage = (event) => {
|
|
||||||
// try {
|
|
||||||
// const data = JSON.parse(event.data);
|
|
||||||
// setMessages(prev => [...prev, data]);
|
|
||||||
|
|
||||||
// // Handle message based on type
|
|
||||||
// if (messageHandlers[data.type]) {
|
|
||||||
// messageHandlers[data.type](data.data || data);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Call registered callbacks
|
|
||||||
// if (callbacks.current[data.type]) {
|
|
||||||
// callbacks.current[data.type](data.data || data);
|
|
||||||
// }
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Error parsing message:', error);
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onclose = (event) => {
|
|
||||||
console.log('_WEBSOCKET_ JamKazam WebSocket disconnected:', event.code, event.reason);
|
|
||||||
setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
|
||||||
|
|
||||||
// Attempt reconnection if it wasn't intentional and we haven't reached max attempts
|
|
||||||
if (shouldReconnect.current && !event.wasClean && !hasReachedMaxAttempts) {
|
|
||||||
scheduleReconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.onerror = (error) => {
|
|
||||||
console.error('JamKazam WebSocket error:', error);
|
|
||||||
setConnectionStatus(ConnectionStatus.ERROR);
|
|
||||||
setLastError(error);
|
|
||||||
|
|
||||||
// Attempt reconnection if we haven't reached max attempts
|
|
||||||
if (shouldReconnect.current && !hasReachedMaxAttempts) {
|
|
||||||
scheduleReconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('_WEBSOCKET_ Failed to create WebSocket connection:', error);
|
|
||||||
setConnectionStatus(ConnectionStatus.ERROR);
|
|
||||||
setLastError(error);
|
|
||||||
scheduleReconnect();
|
|
||||||
}
|
|
||||||
}, [url, reconnectAttempts]);
|
|
||||||
|
|
||||||
// Schedule reconnection with exponential backoff
|
|
||||||
const scheduleReconnect = useCallback(() => {
|
|
||||||
if (!shouldReconnect.current) return;
|
|
||||||
|
|
||||||
const attempt = currentAttemptRef.current;
|
|
||||||
if (attempt >= maxReconnectAttempts) {
|
|
||||||
console.log(`_WEBSOCKET_ Max reconnection attempts reached (${attempt})`);
|
|
||||||
setConnectionStatus(ConnectionStatus.ERROR);
|
|
||||||
setHasReachedMaxAttempts(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = getReconnectDelay(attempt);
|
|
||||||
console.log(`_WEBSOCKET_ Scheduling reconnection in ${delay}ms (attempt ${attempt + 1}/${maxReconnectAttempts})`);
|
|
||||||
|
|
||||||
setConnectionStatus(ConnectionStatus.RECONNECTING);
|
|
||||||
const newAttempt = attempt + 1;
|
|
||||||
setReconnectAttempts(newAttempt);
|
|
||||||
currentAttemptRef.current = newAttempt;
|
|
||||||
|
|
||||||
// Clear any existing timeout to prevent overlapping reconnect attempts
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
reconnectTimeoutRef.current = setTimeout(() => {
|
|
||||||
connect();
|
|
||||||
}, delay);
|
|
||||||
}, [maxReconnectAttempts, getReconnectDelay, connect]);
|
|
||||||
|
|
||||||
// Disconnect WebSocket
|
|
||||||
const disconnect = useCallback(() => {
|
|
||||||
shouldReconnect.current = false;
|
|
||||||
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current);
|
|
||||||
reconnectTimeoutRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.close(1000, 'Client disconnecting');
|
|
||||||
}
|
|
||||||
|
|
||||||
setConnectionStatus(ConnectionStatus.DISCONNECTED);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Message handler system
|
|
||||||
// const messageHandlers = {
|
|
||||||
// connection: (data) => {
|
|
||||||
// setConnectionStatus(data.status === 'connected' ? ConnectionStatus.CONNECTED : ConnectionStatus.DISCONNECTED);
|
|
||||||
// },
|
|
||||||
|
|
||||||
// SessionGetAllControlState: (data) => {
|
|
||||||
// setMixers(data);
|
|
||||||
// },
|
|
||||||
|
|
||||||
// FTUEGetAllAudioConfigurations: (data) => {
|
|
||||||
// // Handle FTUE audio configurations
|
|
||||||
// return ['default']; // Example response
|
|
||||||
// },
|
|
||||||
|
|
||||||
// ConnectionStatusUpdate: (data) => {
|
|
||||||
// // Handle connection status updates
|
|
||||||
// if (callbacks.current.connectionStatusCallback) {
|
|
||||||
// callbacks.current.connectionStatusCallback(data);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
|
|
||||||
// PlaybackPositionUpdate: (data) => {
|
|
||||||
// setCurrentPosition(data.position);
|
|
||||||
// if (callbacks.current.playbackPositionCallback) {
|
|
||||||
// callbacks.current.playbackPositionCallback(data);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // Add more message handlers...
|
|
||||||
// };
|
|
||||||
|
|
||||||
const sendMessage = useCallback((type, params = {}) => {
|
|
||||||
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
|
|
||||||
const message = {
|
|
||||||
type,
|
|
||||||
params,
|
|
||||||
id: ++requestId.current
|
|
||||||
};
|
|
||||||
ws.current.send(JSON.stringify(message));
|
|
||||||
return message.id;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const sendRequest = useCallback((type, params = {}) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const id = sendMessage(type, params);
|
|
||||||
if (id) {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
reject(new Error('Request timeout'));
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
const responseHandler = (event) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
if (data.id === id) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
ws.current.removeEventListener('message', responseHandler);
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error parsing response:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.current.addEventListener('message', responseHandler);
|
|
||||||
} else {
|
|
||||||
reject(new Error('WebSocket not connected'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [sendMessage]);
|
|
||||||
|
|
||||||
// Initialize connection on mount
|
|
||||||
useEffect(() => {
|
|
||||||
connect();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
disconnect();
|
|
||||||
};
|
|
||||||
}, []); // Empty dependency array - only run once on mount
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
shouldReconnect.current = false;
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current);
|
|
||||||
}
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// API methods that mimic the old jamClient interface
|
|
||||||
// const jamClient = {
|
|
||||||
// // Session Management
|
|
||||||
// JoinSession: (params) => sendMessage('JoinSession', params),
|
|
||||||
// LeaveSession: (params) => sendMessage('LeaveSession', params),
|
|
||||||
// SessionGetAllControlState: (isMaster) => sendRequest('SessionGetAllControlState', { isMaster }),
|
|
||||||
// SessionSetControlState: (mixerId, mode) => sendMessage('SessionSetControlState', { mixerId, mode }),
|
|
||||||
// SessionRegisterCallback: (callbackName) => {
|
|
||||||
// // Register callback for session events
|
|
||||||
// callbacks.current.sessionCallback = callbackName;
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // Recording
|
|
||||||
// StartRecording: (params) => sendMessage('StartMediaRecording', params),
|
|
||||||
// StopRecording: (params) => sendMessage('FrontStopRecording', params),
|
|
||||||
// RegisterRecordingCallbacks: (startCb, stopCb, startedCb, stoppedCb, abortedCb) => {
|
|
||||||
// callbacks.current.recordingStartCallback = startCb;
|
|
||||||
// callbacks.current.recordingStopCallback = stopCb;
|
|
||||||
// callbacks.current.recordingStartedCallback = startedCb;
|
|
||||||
// callbacks.current.recordingStoppedCallback = stoppedCb;
|
|
||||||
// callbacks.current.recordingAbortedCallback = abortedCb;
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // Playback
|
|
||||||
// SessionStartPlay: (mode) => sendMessage('SessionStartPlay', { mode }),
|
|
||||||
// SessionStopPlay: () => sendMessage('SessionStopPlay'),
|
|
||||||
// SessionPausePlay: () => sendMessage('SessionPausePlay'),
|
|
||||||
// SessionCurrrentPlayPosMs: () => sendRequest('SessionCurrrentPlayPosMs'),
|
|
||||||
|
|
||||||
// // Audio Configuration
|
|
||||||
// FTUEGetAllAudioConfigurations: () => sendRequest('FTUEGetAllAudioConfigurations'),
|
|
||||||
// FTUEGetGoodAudioConfigurations: () => sendRequest('FTUEGetGoodAudioConfigurations'),
|
|
||||||
|
|
||||||
// // System Info
|
|
||||||
// GetSampleRate: () => sendRequest('GetSampleRate'),
|
|
||||||
// IsAudioStarted: () => sendRequest('IsAudioStarted'),
|
|
||||||
|
|
||||||
// // Connection Status
|
|
||||||
// SessionSetConnectionStatusRefreshRate: (rate) => sendMessage('SessionSetConnectionStatusRefreshRate', { rate }),
|
|
||||||
|
|
||||||
// // User Management
|
|
||||||
// SessionSetUserName: (clientId, name) => sendMessage('SessionSetUserName', { clientId, name }),
|
|
||||||
|
|
||||||
// // VST
|
|
||||||
// VSTListVsts: () => sendRequest('VSTListVsts'),
|
|
||||||
// VSTLoad: (params) => sendMessage('VSTLoad', params),
|
|
||||||
|
|
||||||
// // Network
|
|
||||||
// NetworkTestResult: () => sendRequest('NetworkTestResult'),
|
|
||||||
// GetNetworkTestScore: () => sendRequest('GetNetworkTestScore'),
|
|
||||||
|
|
||||||
// // Callbacks
|
|
||||||
// RegisterVolChangeCallBack: (callback) => {
|
|
||||||
// callbacks.current.volumeChangeCallback = callback;
|
|
||||||
// },
|
|
||||||
|
|
||||||
// setMetronomeOpenCallback: (callback) => {
|
|
||||||
// callbacks.current.metronomeCallback = callback;
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // Register generic callback for bridge messages
|
|
||||||
// HandleBridgeCallback: (vuData) => {
|
|
||||||
// // Handle VU meter updates and other bridge messages
|
|
||||||
// vuData.forEach(vuInfo => {
|
|
||||||
// if (callbacks.current.bridgeCallback) {
|
|
||||||
// callbacks.current.bridgeCallback([vuInfo]);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Legacy compatibility - keep isConnected for existing code
|
|
||||||
const isConnected = connectionStatus === ConnectionStatus.CONNECTED;
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Legacy properties for backward compatibility
|
|
||||||
isConnected,
|
|
||||||
messages,
|
|
||||||
sessionData,
|
|
||||||
mixers,
|
|
||||||
participants,
|
|
||||||
isRecording,
|
|
||||||
isPlaying,
|
|
||||||
currentPosition,
|
|
||||||
|
|
||||||
// New connection management properties
|
|
||||||
connectionStatus,
|
|
||||||
reconnectAttempts,
|
|
||||||
lastError,
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
//jamClient,
|
|
||||||
sendMessage,
|
|
||||||
sendRequest,
|
|
||||||
connect,
|
|
||||||
disconnect
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { useEffect, useRef, useCallback } from "react";
|
||||||
|
import { useJamClient } from "../context/JamClientContext";
|
||||||
|
import { ALERT_NAMES } from "../helpers/globals";
|
||||||
|
import { updateUdpReachable } from "../helpers/rest";
|
||||||
|
|
||||||
|
// Assumes context.JK, context.jamClient, and jQuery are globally available
|
||||||
|
// You may want to pass these as parameters or import them as needed
|
||||||
|
|
||||||
|
export function useStun(app) {
|
||||||
|
const jamClient = useJamClient();
|
||||||
|
const udpBlockedRef = useRef(null);
|
||||||
|
|
||||||
|
// Syncs the UDP blocked state and optionally calls a callback when changed
|
||||||
|
const sync = useCallback(async (changed) => {
|
||||||
|
|
||||||
|
const result = await jamClient.NetworkTestResult();
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if (
|
||||||
|
udpBlockedRef.current === null ||
|
||||||
|
result.remote_udp_blocked !== udpBlockedRef.current
|
||||||
|
) {
|
||||||
|
// Log the result
|
||||||
|
if (result.remote_udp_blocked) {
|
||||||
|
console.debug("NO STUN: " + JSON.stringify(result));
|
||||||
|
} else {
|
||||||
|
console.debug("STUN capable: " + JSON.stringify(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
udpBlockedRef.current = result.remote_udp_blocked;
|
||||||
|
|
||||||
|
if (changed) changed(result.remote_udp_blocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return udpBlockedRef.current;
|
||||||
|
}, [jamClient]);
|
||||||
|
|
||||||
|
// Handles STUN events and updates the server
|
||||||
|
useEffect(() => {
|
||||||
|
function handleStunEvent() {
|
||||||
|
console.debug("handling stun event...");
|
||||||
|
sync((blocked) => {
|
||||||
|
if (app?.clientId) {
|
||||||
|
updateUdpReachable({
|
||||||
|
client_id: app.clientId,
|
||||||
|
udp_reachable: !blocked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register event listener
|
||||||
|
if (window?.JK?.onBackendEvent && ALERT_NAMES?.STUN_EVENT) {
|
||||||
|
window.JK.onBackendEvent(
|
||||||
|
ALERT_NAMES.STUN_EVENT,
|
||||||
|
"everywhere",
|
||||||
|
handleStunEvent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup (if your event system supports it)
|
||||||
|
return () => {
|
||||||
|
// No built-in way to remove the event in the original code
|
||||||
|
// Add cleanup logic here if available
|
||||||
|
};
|
||||||
|
}, [app, sync]);
|
||||||
|
|
||||||
|
// Optionally, return sync or udpBlockedRef if needed by the component
|
||||||
|
return { sync, udpBlockedRef };
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import UserAuth from '../context/UserAuth';
|
||||||
import { BrowserQueryProvider } from '../context/BrowserQuery';
|
import { BrowserQueryProvider } from '../context/BrowserQuery';
|
||||||
import { AppDataProvider } from '../context/AppDataContext';
|
import { AppDataProvider } from '../context/AppDataContext';
|
||||||
import { AppRoutesProvider } from '../context/AppRoutesContext';
|
import { AppRoutesProvider } from '../context/AppRoutesContext';
|
||||||
|
import { JamClientProvider } from '../context/JamClientContext';
|
||||||
|
|
||||||
const JKClientLayout = ({ location }) => {
|
const JKClientLayout = ({ location }) => {
|
||||||
|
|
||||||
|
|
@ -14,7 +15,9 @@ const JKClientLayout = ({ location }) => {
|
||||||
<AppRoutesProvider>
|
<AppRoutesProvider>
|
||||||
<AppDataProvider>
|
<AppDataProvider>
|
||||||
<BrowserQueryProvider>
|
<BrowserQueryProvider>
|
||||||
<ClientRoutes />
|
<JamClientProvider>
|
||||||
|
<ClientRoutes />
|
||||||
|
</JamClientProvider>
|
||||||
</BrowserQueryProvider>
|
</BrowserQueryProvider>
|
||||||
</AppDataProvider>
|
</AppDataProvider>
|
||||||
</AppRoutesProvider>
|
</AppRoutesProvider>
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import NavbarTop from '../components/navbar/JKNavbarTop';
|
||||||
import NavbarVertical from '../components/navbar/JKNavbarVertical';
|
import NavbarVertical from '../components/navbar/JKNavbarVertical';
|
||||||
import Footer from '../components/footer/JKFooter';
|
import Footer from '../components/footer/JKFooter';
|
||||||
|
|
||||||
import AsyncJamClientExample from '../helpers/asyncJamClientExample';
|
|
||||||
|
|
||||||
// Import your page components here
|
// Import your page components here
|
||||||
import JKSessionScreen from '../components/client/JKSessionScreen';
|
import JKSessionScreen from '../components/client/JKSessionScreen';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ const http = require('http');
|
||||||
* Handles all the internal system functions and provides mock responses
|
* Handles all the internal system functions and provides mock responses
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class FakeClientWebSocket {
|
class FakeJamClientServer {
|
||||||
constructor(port = 8080) {
|
constructor(port = 3060) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.server = null;
|
this.server = null;
|
||||||
this.wss = null;
|
this.wss = null;
|
||||||
|
|
@ -765,21 +765,21 @@ class FakeClientWebSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start the server
|
// Create and start the server
|
||||||
const fakeClient = new FakeClientWebSocket(8080);
|
const fakeServer = new FakeJamClientServer(3060);
|
||||||
fakeClient.start();
|
fakeServer.start();
|
||||||
fakeClient.startPeriodicUpdates();
|
fakeServer.startPeriodicUpdates();
|
||||||
|
|
||||||
// Handle graceful shutdown
|
// Handle graceful shutdown
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log('\nShutting down Fake Client WebSocket server...');
|
console.log('\nShutting down Fake Client WebSocket server...');
|
||||||
fakeClient.stop();
|
fakeServer.stop();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
console.log('\nShutting down Fake Client WebSocket server...');
|
console.log('\nShutting down Fake Client WebSocket server...');
|
||||||
fakeClient.stop();
|
fakeServer.stop();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = FakeClientWebSocket;
|
module.exports = FakeJamClientServer;
|
||||||
|
|
@ -532,7 +532,6 @@ module JamWebsockets
|
||||||
@largest_message_user = client.user_id
|
@largest_message_user = client.user_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# extract the message safely
|
# extract the message safely
|
||||||
websocket_comm(client, nil) do
|
websocket_comm(client, nil) do
|
||||||
if client.encode_json
|
if client.encode_json
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue