wip session base page

This commit is contained in:
Nuwan 2025-09-07 21:55:51 +05:30
parent 4ffc0d9b3b
commit 4b3db7fed4
17 changed files with 893 additions and 15 deletions

View File

@ -12467,6 +12467,14 @@
"version": "5.7.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
"integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg=="
},
"ws": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.4.tgz",
"integrity": "sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
@ -21860,12 +21868,9 @@
}
},
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "~1.0.0"
}
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="
},
"xml-name-validator": {
"version": "3.0.0",

View File

@ -87,7 +87,8 @@
"react-typed": "^1.2.0",
"reactstrap": "^8.6.0",
"slick-carousel": "^1.8.1",
"uuid": "^3.4.0"
"uuid": "^3.4.0",
"ws": "^8.18.3"
},
"scripts": {
"start": "react-scripts start",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,4 +13,5 @@
@import './custom/partner_agreement_v1';
@import './custom/audio_player';
@import './custom/landing';
@import './custom/buttons';

View File

@ -0,0 +1,13 @@
.btn-custom-outline {
border-color: #d6d6d6;
background-color: transparent;
color: #8a8787; /* change text to match */
border-width: 1px 2px 2px 1px; /* thicker on left & bottom */
box-shadow: .5px .5px 0 #ffffffd7; /* shadow for depth */
transition: all 0.2s ease-in-out;
}
.btn-custom-outline:hover {
background-color: #aca9a8;
color: #494848;
}

View File

@ -0,0 +1,90 @@
import React, { useEffect, useContext } from 'react'
import { Alert, Col, Row, Button, Card, CardBody, Form, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
import useClientWebSocket from '../../hooks/useClientWebsocket'
import NavbarTop from '../navbar/JKNavbarTop';
import AppContext from '../../context/Context';
import { getPageName } from '../../helpers/utils';
const JKSessionClient = () => {
const { isConnected, messages, sendMessage } = useClientWebSocket('ws://localhost:8080');
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
const isKanban = getPageName('kanban');
useEffect(() => {
if (isConnected) {
sendMessage('FTUEGetStatus', { content: 'Hello, server!' });
}
}, [isConnected]);
return (
<Card>
<FalconCardHeader title={`Session`} titleClass="font-weight-bold">
<Button
color="primary"
className="me-2 mr-2 fs--1"
>
Leave Session
</Button>
</FalconCardHeader>
<CardHeader className="bg-light border-bottom border-top py-2 border-3">
<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">Invite</Button>
<Button className='btn-custom-outline' outline size="md">Volume</Button>
<Button className='btn-custom-outline' outline size="md">Video</Button>
<Button className='btn-custom-outline' outline size="md">Record</Button>
<Button className='btn-custom-outline' outline size="md">Broadcast</Button>
<Button className='btn-custom-outline' outline size="md">Open</Button>
<Button className='btn-custom-outline' outline size="md">Chat</Button>
<Button className='btn-custom-outline' outline size="md">Attach</Button>
<Button className='btn-custom-outline' outline size="md">Resync</Button>
</div>
</CardHeader>
<CardBody className="pl-4" style={{ backgroundColor: '#edf2f9f5' }}>
<div className='d-flex' style={{ gap: '1rem' }}>
<div className='audioInputs'>
<h5>Audio Inputs</h5>
<div className='d-flex' style={{ gap: '0.5rem', borderRight: '1px #ddd solid', paddingRight: '1rem' }}>
<div 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='p-2'>Clive Leonard</div>
<div className='p-2'>Image</div>
<div className='p-2'>Instrument Icon</div>
<div className='p-2'>Volume Slider</div>
<div className='p-2'>Controls</div>
</div>
</div>
<div className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>audio input 2</div>
</div>
</div>
<div className='sessionMix'>
<h5>Session Mix</h5>
<div className='d-flex' style={{ gap: '0.5rem' }}>
<div className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>audio input 1</div>
<div className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>audio input 2</div>
<div className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>audio input 1</div>
<div className='shadow-sm' style={{ border: '1px #ddd solid', width: '100px', height: '600px', backgroundColor: 'white' }}>audio input 2</div>
</div>
</div>
<div className='attachMedia'>
<h5>Attach Media</h5>
</div>
</div>
{/* <>
<h1>WebSocket Client</h1>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<h2>Messages:</h2>
<ul>
{messages.map((msg, index) => (
<li key={index}>{JSON.stringify(msg)}</li>
))}
</ul>
</> */}
</CardBody>
</Card>
)
}
export default JKSessionClient

View File

@ -5,8 +5,9 @@ import AppContext from '../../context/Context';
import Logo from './Logo';
import TopNavRightSideNavItem from './JKTopNavRightSideNavItem';
import { navbarBreakPoint, topNavbarBreakpoint } from '../../config';
import PropTypes from 'prop-types';
const JKNavbarTop = () => {
const JKNavbarTop = ({ logoWidth}) => {
const {
showBurgerMenu,
setShowBurgerMenu,
@ -42,7 +43,7 @@ const JKNavbarTop = () => {
</span>
</button>
</div>
<Logo at="navbar-top" id="topLogo" width={140} />
<Logo at="navbar-top" id="topLogo" width={logoWidth} />
{/* {isTopNav ? (
<Collapse navbar isOpen={navbarCollapsed} className="scrollbar">
<Nav navbar>
@ -62,4 +63,12 @@ const JKNavbarTop = () => {
);
};
JKNavbarTop.propTypes = {
logoWidth: PropTypes.number
};
JKNavbarTop.defaultProps = {
logoWidth: 140
};
export default JKNavbarTop;

View File

@ -0,0 +1,44 @@
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 };
}

View File

@ -0,0 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types';
import ClientRoutes from './JKClientRoutes';
import UserAuth from '../context/UserAuth';
import { BrowserQueryProvider } from '../context/BrowserQuery';
import { AppDataProvider } from '../context/AppDataContext';
import { AppRoutesProvider } from '../context/AppRoutesContext';
const JKClientLayout = ({ location }) => {
return (
<UserAuth path={location.pathname}>
<AppRoutesProvider>
<AppDataProvider>
<BrowserQueryProvider>
<ClientRoutes />
</BrowserQueryProvider>
</AppDataProvider>
</AppRoutesProvider>
</UserAuth>
)
}
JKClientLayout.propTypes = { location: PropTypes.object.isRequired };
export default JKClientLayout

View File

@ -0,0 +1,36 @@
import React, { useContext } from 'react';
import { Switch, Redirect, withRouter } from 'react-router-dom';
import PrivateRoute from '../helpers/privateRoute';
import AppContext from '../context/Context';
import { getPageName } from '../helpers/utils';
import PropTypes from 'prop-types';
import NavbarTop from '../components/navbar/JKNavbarTop';
import NavbarVertical from '../components/navbar/JKNavbarVertical';
import Footer from '../components/footer/JKFooter';
// Import your page components here
import JKSessionClient from '../components/client/JKSessionClient';
const JKClientRoutes = ({ match: { url } }) => {
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
const isKanban = getPageName('kanban');
return (
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
{/* {isVertical && <NavbarVertical isKanban={isKanban} navbarStyle={navbarStyle} />} */}
<div className="content">
<NavbarTop logoWidth={240} />
<Switch>
<PrivateRoute exact path={`${url}/`} component={JKSessionClient} />
{/*Redirect*/}
<Redirect to="/errors/404" />
</Switch>
<Footer />
</div>
</div>
)
}
JKClientRoutes.propTypes = { match: PropTypes.object.isRequired };
export default withRouter(JKClientRoutes);

View File

@ -10,6 +10,7 @@ import BuildMeta from "./JKBuildMeta";
import loadable from '@loadable/component';
const AuthBasicLayout = loadable(() => import('./JKAuthBasicLayout'));
const PublicLayout = loadable(() => import('./JKPublicLayout'));
const ClientLayout = loadable(() => import('./JKClientLayout'));
const Layout = () => {
useEffect(() => {
@ -33,6 +34,7 @@ const Layout = () => {
<Route path="/errors" component={ErrorLayout} />
<Route path="/auth" component={AuthBasicLayout} />
<Route path="/public" component={PublicLayout} />
<Route path="/client" component={ClientLayout} />
<Route component={DashboardLayout} />
</Switch>
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_RIGHT} />

View File

@ -0,0 +1,650 @@
const WebSocket = require('ws');
const http = require('http');
/**
* Fake Client WebSocket Server
* Simulates the JamKazam C++ backend client application
* Handles all the internal system functions and provides mock responses
*/
class FakeClientWebSocket {
constructor(port = 8080) {
this.port = port;
this.server = null;
this.wss = null;
this.clients = new Set();
// Mock data storage
this.mockData = {
audioConfigurations: [
{ id: 1, name: 'Default Audio Config', sampleRate: 44100, bufferSize: 256 },
{ id: 2, name: 'Low Latency Config', sampleRate: 48000, bufferSize: 128 },
{ id: 3, name: 'High Quality Config', sampleRate: 96000, bufferSize: 512 }
],
goodAudioConfigurations: [
{ id: 1, name: 'Default Audio Config', sampleRate: 44100, bufferSize: 256 },
{ id: 2, name: 'Low Latency Config', sampleRate: 48000, bufferSize: 128 }
],
sessionInfo: {
sessionId: 'session_12345',
sessionName: 'Mock Jam Session',
participants: ['user1', 'user2', 'user3'],
isActive: false,
connectionStatus: 'disconnected'
},
userProfile: {
username: 'MockUser',
lastUsedProfile: 'Default Profile',
macHash: 'mock_mac_hash_12345',
clientId: 'client_67890',
parentClientId: null,
role: 'participant'
},
vstData: {
loadedVsts: [],
availableVsts: [
{ id: 1, name: 'Reverb VST', path: '/mock/path/reverb.vst' },
{ id: 2, name: 'Compressor VST', path: '/mock/path/compressor.vst' }
],
trackAssignments: [],
searchPaths: ['/mock/vst/path1', '/mock/vst/path2'],
midiDevices: ['Mock MIDI Device 1', 'Mock MIDI Device 2']
},
recordingData: {
currentRecordingId: null,
isRecording: false,
localRecordingState: 'stopped',
recordingPreference: 'local'
},
playbackData: {
isPlaying: false,
isPaused: false,
currentPosition: 0,
duration: 0,
backingTrackFile: null,
jamTrackDetails: null
},
systemInfo: {
operatingMode: 'normal',
os: 'macOS Monterey',
detailedOS: 'macOS 12.6.1 (21G217)',
version: '1.0.0',
sampleRate: 44100,
isAppInWritableVolume: true,
isAudioStarted: false
},
networkData: {
testScore: 85,
expectedLatency: 25,
connectionDetails: {
ping: 15,
jitter: 2,
packetLoss: 0.1
}
}
};
this.setupMessageHandlers();
}
start() {
this.server = http.createServer();
this.wss = new WebSocket.Server({ server: this.server });
this.wss.on('connection', (ws, req) => {
console.log('Client connected from:', req.socket.remoteAddress);
this.clients.add(ws);
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
this.handleMessage(ws, data);
} catch (error) {
console.error('Error parsing message:', error);
this.sendError(ws, 'Invalid JSON format');
}
});
ws.on('close', () => {
console.log('Client disconnected');
this.clients.delete(ws);
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.clients.delete(ws);
});
// Send welcome message
this.sendMessage(ws, {
type: 'connection',
status: 'connected',
message: 'Connected to Fake JamKazam Client'
});
});
this.server.listen(this.port, () => {
console.log(`Fake Client WebSocket server running on port ${this.port}`);
});
}
stop() {
if (this.wss) {
this.wss.close();
}
if (this.server) {
this.server.close();
}
console.log('Fake Client WebSocket server stopped');
}
sendMessage(ws, message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
}
sendError(ws, error) {
this.sendMessage(ws, {
type: 'error',
error: error
});
}
broadcast(message) {
this.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
}
setupMessageHandlers() {
this.messageHandlers = {
// FTUE (First Time User Experience) Functions
FTUEGetAllAudioConfigurations: () => ({
type: 'FTUEGetAllAudioConfigurations',
data: this.mockData.audioConfigurations
}),
FTUEGetGoodAudioConfigurations: () => ({
type: 'FTUEGetGoodAudioConfigurations',
data: this.mockData.goodAudioConfigurations
}),
FTUEGetChatInputVolume: () => ({
type: 'FTUEGetChatInputVolume',
data: { volume: 75 }
}),
FTUEGetStatus: () => ({
type: 'FTUEGetStatus',
data: { status: 'completed', progress: 100 }
}),
FTUEGetChannels: () => ({
type: 'FTUEGetChannels',
data: { inputChannels: 2, outputChannels: 2 }
}),
FTUEGetExpectedLatency: () => ({
type: 'FTUEGetExpectedLatency',
data: { latency: this.mockData.networkData.expectedLatency }
}),
// Session Management Functions
SessionSetAlertCallback: () => ({
type: 'SessionSetAlertCallback',
data: { status: 'callback_registered' }
}),
SessionGetMacHash: () => ({
type: 'SessionGetMacHash',
data: { macHash: this.mockData.userProfile.macHash }
}),
SessionGetAllControlState: () => ({
type: 'SessionGetAllControlState',
data: {
volume: 80,
mute: false,
solo: false,
effects: []
}
}),
SessionPageEnter: () => ({
type: 'SessionPageEnter',
data: { status: 'entered' }
}),
SessionPageLeave: () => ({
type: 'SessionPageLeave',
data: { status: 'left' }
}),
SessionRegisterCallback: () => ({
type: 'SessionRegisterCallback',
data: { status: 'callback_registered' }
}),
SessionSetConnectionStatusRefreshRate: (params) => ({
type: 'SessionSetConnectionStatusRefreshRate',
data: { refreshRate: params.rate || 1000 }
}),
SessionSetUserName: (params) => {
this.mockData.userProfile.username = params.username || 'MockUser';
return {
type: 'SessionSetUserName',
data: { username: this.mockData.userProfile.username }
};
},
SessionSetTrackVolumeData: (params) => ({
type: 'SessionSetTrackVolumeData',
data: { trackId: params.trackId, volume: params.volume }
}),
SessionAudioResync: () => ({
type: 'SessionAudioResync',
data: { status: 'resynced' }
}),
// System Functions
RegisterQuitCallback: () => ({
type: 'RegisterQuitCallback',
data: { status: 'callback_registered' }
}),
ResetPageCounters: () => ({
type: 'ResetPageCounters',
data: { status: 'counters_reset' }
}),
getOperatingMode: () => ({
type: 'getOperatingMode',
data: { mode: this.mockData.systemInfo.operatingMode }
}),
GetDetailedOS: () => ({
type: 'GetDetailedOS',
data: { os: this.mockData.systemInfo.detailedOS }
}),
GetOSAsString: () => ({
type: 'GetOSAsString',
data: { os: this.mockData.systemInfo.os }
}),
LastUsedProfileName: () => ({
type: 'LastUsedProfileName',
data: { profileName: this.mockData.userProfile.lastUsedProfile }
}),
SetLatencyTestBlocked: (params) => ({
type: 'SetLatencyTestBlocked',
data: { blocked: params.blocked || false }
}),
SetScoreWorkTimingInterval: (params) => ({
type: 'SetScoreWorkTimingInterval',
data: { interval: params.interval || 1000 }
}),
IsAppInWritableVolume: () => ({
type: 'IsAppInWritableVolume',
data: { writable: this.mockData.systemInfo.isAppInWritableVolume }
}),
RegisterSessionJoinLeaveRequestCallBack: () => ({
type: 'RegisterSessionJoinLeaveRequestCallBack',
data: { status: 'callback_registered' }
}),
OnLoggedIn: () => ({
type: 'OnLoggedIn',
data: { status: 'logged_in', user: this.mockData.userProfile.username }
}),
RegisterRecordingManagerCallbacks: () => ({
type: 'RegisterRecordingManagerCallbacks',
data: { status: 'callbacks_registered' }
}),
SetVURefreshRate: (params) => ({
type: 'SetVURefreshRate',
data: { refreshRate: params.rate || 30 }
}),
RegisterGenericCallBack: () => ({
type: 'RegisterGenericCallBack',
data: { status: 'callback_registered' }
}),
ClientUpdateVersion: () => ({
type: 'ClientUpdateVersion',
data: { version: this.mockData.systemInfo.version }
}),
P2PMessageReceived: (params) => ({
type: 'P2PMessageReceived',
data: { message: params.message, from: params.from }
}),
RegisterVolChangeCallBack: () => ({
type: 'RegisterVolChangeCallBack',
data: { status: 'callback_registered' }
}),
setMetronomeOpenCallback: () => ({
type: 'setMetronomeOpenCallback',
data: { status: 'callback_registered' }
}),
IsAudioStarted: () => ({
type: 'IsAudioStarted',
data: { started: this.mockData.systemInfo.isAudioStarted }
}),
NetworkTestResult: () => ({
type: 'NetworkTestResult',
data: this.mockData.networkData
}),
UpdateSessionInfo: (params) => {
Object.assign(this.mockData.sessionInfo, params);
return {
type: 'UpdateSessionInfo',
data: this.mockData.sessionInfo
};
},
getClientParentChildRole: () => ({
type: 'getClientParentChildRole',
data: { role: this.mockData.userProfile.role }
}),
getParentClientId: () => ({
type: 'getParentClientId',
data: { parentClientId: this.mockData.userProfile.parentClientId }
}),
ClientJoinedSession: (params) => ({
type: 'ClientJoinedSession',
data: { clientId: params.clientId, sessionId: params.sessionId }
}),
ClientLeftSession: (params) => ({
type: 'ClientLeftSession',
data: { clientId: params.clientId, sessionId: params.sessionId }
}),
JoinSession: (params) => {
this.mockData.sessionInfo.isActive = true;
this.mockData.sessionInfo.connectionStatus = 'connected';
return {
type: 'JoinSession',
data: { sessionId: params.sessionId, status: 'joined' }
};
},
LeaveSession: () => {
this.mockData.sessionInfo.isActive = false;
this.mockData.sessionInfo.connectionStatus = 'disconnected';
return {
type: 'LeaveSession',
data: { status: 'left' }
};
},
// VST Functions
IsVstLoaded: (params) => ({
type: 'IsVstLoaded',
data: { loaded: this.mockData.vstData.loadedVsts.includes(params.vstId) }
}),
hasVstAssignment: (params) => ({
type: 'hasVstAssignment',
data: { hasAssignment: this.mockData.vstData.trackAssignments.some(a => a.vstId === params.vstId) }
}),
VSTListVsts: () => ({
type: 'VSTListVsts',
data: { vsts: this.mockData.vstData.availableVsts }
}),
VSTListTrackAssignments: () => ({
type: 'VSTListTrackAssignments',
data: { assignments: this.mockData.vstData.trackAssignments }
}),
VST_GetMidiDeviceList: () => ({
type: 'VST_GetMidiDeviceList',
data: { devices: this.mockData.vstData.midiDevices }
}),
VSTListSearchPaths: () => ({
type: 'VSTListSearchPaths',
data: { paths: this.mockData.vstData.searchPaths }
}),
getConnectionDetail: () => ({
type: 'getConnectionDetail',
data: this.mockData.networkData.connectionDetails
}),
VSTLoad: (params) => {
if (!this.mockData.vstData.loadedVsts.includes(params.vstId)) {
this.mockData.vstData.loadedVsts.push(params.vstId);
}
return {
type: 'VSTLoad',
data: { vstId: params.vstId, status: 'loaded' }
};
},
GetNetworkTestScore: () => ({
type: 'GetNetworkTestScore',
data: { score: this.mockData.networkData.testScore }
}),
setSessionMixerCategoryPlayoutState: (params) => ({
type: 'setSessionMixerCategoryPlayoutState',
data: { category: params.category, state: params.state }
}),
// Recording Functions
StartMediaRecording: () => {
this.mockData.recordingData.isRecording = true;
this.mockData.recordingData.currentRecordingId = 'rec_' + Date.now();
return {
type: 'StartMediaRecording',
data: { recordingId: this.mockData.recordingData.currentRecordingId }
};
},
FrontStopRecording: () => {
this.mockData.recordingData.isRecording = false;
return {
type: 'FrontStopRecording',
data: { status: 'stopped' }
};
},
RegisterRecordingCallbacks: () => ({
type: 'RegisterRecordingCallbacks',
data: { status: 'callbacks_registered' }
}),
GetAudioRecordingPreference: () => ({
type: 'GetAudioRecordingPreference',
data: { preference: this.mockData.recordingData.recordingPreference }
}),
GetCurrentRecordingId: () => ({
type: 'GetCurrentRecordingId',
data: { recordingId: this.mockData.recordingData.currentRecordingId }
}),
GetLocalRecordingState: () => ({
type: 'GetLocalRecordingState',
data: { state: this.mockData.recordingData.localRecordingState }
}),
GetSampleRate: () => ({
type: 'GetSampleRate',
data: { sampleRate: this.mockData.systemInfo.sampleRate }
}),
// Playback Functions
ShowSelectBackingTrackDialog: () => ({
type: 'ShowSelectBackingTrackDialog',
data: { status: 'dialog_shown' }
}),
SessionOpenBackingTrackFile: (params) => {
this.mockData.playbackData.backingTrackFile = params.filePath;
return {
type: 'SessionOpenBackingTrackFile',
data: { filePath: params.filePath, status: 'opened' }
};
},
SessionCurrrentPlayPosMs: () => ({
type: 'SessionCurrrentPlayPosMs',
data: { position: this.mockData.playbackData.currentPosition }
}),
SessionGetTracksPlayDurationMs: () => ({
type: 'SessionGetTracksPlayDurationMs',
data: { duration: this.mockData.playbackData.duration }
}),
isSessionTrackPlaying: () => ({
type: 'isSessionTrackPlaying',
data: { playing: this.mockData.playbackData.isPlaying }
}),
SessionStartPlay: () => {
this.mockData.playbackData.isPlaying = true;
this.mockData.playbackData.isPaused = false;
return {
type: 'SessionStartPlay',
data: { status: 'playing' }
};
},
SessionPausePlay: () => {
this.mockData.playbackData.isPaused = true;
return {
type: 'SessionPausePlay',
data: { status: 'paused' }
};
},
SessionStopPlay: () => {
this.mockData.playbackData.isPlaying = false;
this.mockData.playbackData.isPaused = false;
this.mockData.playbackData.currentPosition = 0;
return {
type: 'SessionStopPlay',
data: { status: 'stopped' }
};
},
SessionCloseBackingTrackFile: () => {
this.mockData.playbackData.backingTrackFile = null;
return {
type: 'SessionCloseBackingTrackFile',
data: { status: 'closed' }
};
},
JamTrackGetTrackDetail: (params) => ({
type: 'JamTrackGetTrackDetail',
data: {
trackId: params.trackId,
name: 'Mock Jam Track',
duration: 180000,
artist: 'Mock Artist'
}
}),
SessionCurrrentJamTrackPlayPosMs: () => ({
type: 'SessionCurrrentJamTrackPlayPosMs',
data: { position: this.mockData.playbackData.currentPosition }
}),
SessionGetJamTracksPlayDurationMs: () => ({
type: 'SessionGetJamTracksPlayDurationMs',
data: { duration: this.mockData.playbackData.duration }
})
};
}
handleMessage(ws, data) {
const { type, params = {} } = data;
console.log(`Received message: ${type}`, params);
if (this.messageHandlers[type]) {
try {
const response = this.messageHandlers[type](params);
this.sendMessage(ws, response);
} catch (error) {
console.error(`Error handling ${type}:`, error);
this.sendError(ws, `Error processing ${type}: ${error.message}`);
}
} else {
console.warn(`Unknown message type: ${type}`);
this.sendError(ws, `Unknown message type: ${type}`);
}
}
// Simulate periodic updates
startPeriodicUpdates() {
// Simulate connection status updates
setInterval(() => {
this.broadcast({
type: 'ConnectionStatusUpdate',
data: {
ping: Math.floor(Math.random() * 50) + 10,
jitter: Math.floor(Math.random() * 10),
packetLoss: Math.random() * 0.5
}
});
}, 5000);
// Simulate playback position updates when playing
setInterval(() => {
if (this.mockData.playbackData.isPlaying && !this.mockData.playbackData.isPaused) {
this.mockData.playbackData.currentPosition += 1000;
this.broadcast({
type: 'PlaybackPositionUpdate',
data: { position: this.mockData.playbackData.currentPosition }
});
}
}, 1000);
}
}
// Create and start the server
const fakeClient = new FakeClientWebSocket(8080);
fakeClient.start();
fakeClient.startPeriodicUpdates();
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down Fake Client WebSocket server...');
fakeClient.stop();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\nShutting down Fake Client WebSocket server...');
fakeClient.stop();
process.exit(0);
});
module.exports = FakeClientWebSocket;