Implement recording modal for jam-ui session screen

- Add JKSessionRecordingModal component with form for recording controls
- Integrate modal into JKSessionScreen with Record button
- Use component state instead of Redux for modal management
- Include audio/video options, video source selection, and chat inclusion
- Connect to existing useRecordingHelpers hook for recording logic
- Match functionality from web project's PopupRecordingStartStop
This commit is contained in:
Nuwan 2025-11-28 19:37:57 +05:30
parent bff9cf826f
commit d64769919a
2 changed files with 275 additions and 1 deletions

View File

@ -0,0 +1,265 @@
import React, { useState, useEffect } from 'react';
import {
Modal,
ModalHeader,
ModalBody,
ModalFooter,
Button,
Form,
FormGroup,
Label,
Input,
Alert
} from 'reactstrap';
import { useJamServerContext } from '../../context/JamServerContext';
import useRecordingHelpers from '../../hooks/useRecordingHelpers';
const JKSessionRecordingModal = ({ isOpen, toggle }) => {
const { jamClient } = useJamServerContext();
const recordingHelpers = useRecordingHelpers(jamClient);
// Form state
const [recordingType, setRecordingType] = useState('audio-only');
const [videoSource, setVideoSource] = useState('webcam-only');
const [includeChat, setIncludeChat] = useState(false);
const [availableVideoSources, setAvailableVideoSources] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// Recording state from helpers
const isRecording = recordingHelpers.currentlyRecording;
const isStarting = recordingHelpers.startingRecording;
const isStopping = recordingHelpers.stoppingRecording;
// Video source options (matching web project)
const videoSourceOptions = [
{ key: 'webcam-only', text: 'Record my webcam only' },
{ key: 'webcam-only-2', text: 'Record my 2nd webcam only' },
{ key: 'desktop-only', text: 'Record my computer desktop' },
{ key: 'video-window', text: 'Record session video window' }
];
// Load available video sources when modal opens
useEffect(() => {
if (isOpen && jamClient) {
loadAvailableVideoSources();
}
}, [isOpen, jamClient]);
const loadAvailableVideoSources = async () => {
try {
if (jamClient && jamClient.getOpenVideoSources) {
const openSources = await jamClient.getOpenVideoSources();
console.log('Available video sources:', openSources);
// Map backend sources to frontend options
const sources = [];
if (openSources?.webcam1) sources.push(1); // WebCamRecordActive
if (openSources?.webcam2) sources.push(3); // WebCam2RecordActive
if (openSources?.screen_capture) sources.push(4); // DesktopRecordActive
if (openSources?.session_video) sources.push(2); // ScreenRecordActive
setAvailableVideoSources(sources);
}
} catch (error) {
console.error('Error loading video sources:', error);
}
};
const handleStartStopRecording = async () => {
setIsLoading(true);
setError(null);
try {
if (isRecording) {
// Stop recording
await recordingHelpers.stopRecording();
} else {
// Start recording
const recordVideo = recordingType === 'audio-video';
let recordVideoType = 0; // NoVideoRecordActive
if (recordVideo) {
// Map video source selection to backend values
switch (videoSource) {
case 'webcam-only':
recordVideoType = 1; // WebCamRecordActive
break;
case 'webcam-only-2':
recordVideoType = 3; // WebCam2RecordActive
break;
case 'desktop-only':
recordVideoType = 4; // DesktopRecordActive
break;
case 'video-window':
recordVideoType = 2; // ScreenRecordActive
break;
}
// Validate video source availability
if (!availableVideoSources.includes(recordVideoType)) {
setError(`Selected video source "${videoSourceOptions.find(opt => opt.key === videoSource)?.text}" is not available.`);
setIsLoading(false);
return;
}
}
const recordSettings = {
recordingType: recordVideo ? 'JK.RECORD_TYPE_BOTH' : 'JK.RECORD_TYPE_AUDIO',
recordVideo: recordVideo,
recordChat: includeChat,
videoType: recordVideoType
};
await recordingHelpers.startRecording(recordSettings);
}
} catch (error) {
console.error('Recording operation failed:', error);
setError('Failed to start/stop recording. Please try again.');
} finally {
setIsLoading(false);
}
};
const handleCancel = () => {
setError(null);
toggle();
};
const getRecordingButtonText = () => {
if (isStarting) return 'Starting Recording...';
if (isStopping) return 'Stopping Recording...';
if (isRecording) return 'Stop Recording';
return 'Start Recording';
};
const getRecordingButtonColor = () => {
if (isRecording) return 'danger';
return 'success';
};
// Filter available video options
const availableVideoOptions = videoSourceOptions.filter(option => {
switch (option.key) {
case 'webcam-only':
return availableVideoSources.includes(1);
case 'webcam-only-2':
return availableVideoSources.includes(3);
case 'desktop-only':
return availableVideoSources.includes(4);
case 'video-window':
return availableVideoSources.includes(2);
default:
return false;
}
});
return (
<Modal isOpen={isOpen} toggle={handleCancel} modalClassName="theme-modal" contentClassName="border" size="md">
<ModalHeader toggle={handleCancel} className="bg-light d-flex flex-between-center border-bottom-0">
Recording Controls
</ModalHeader>
<ModalBody>
<div className="recording-start-stop">
{/* Important Note */}
<div className="important-note mb-3">
<h6>Important Note</h6>
<div className="text-muted small">
While playing in your session, you are listening to your own personal mix. This recording will use the master mix,
which may sound very different. To hear and adjust your master mix settings, click the MIXER button in the session toolbar.
</div>
</div>
{/* Recording Type Selection */}
<Form>
<FormGroup tag="fieldset">
<legend className="h6">Recording Type</legend>
<FormGroup check>
<Label check>
<Input
type="radio"
name="recordingType"
value="audio-only"
checked={recordingType === 'audio-only'}
onChange={(e) => setRecordingType(e.target.value)}
disabled={isRecording || isStarting || isStopping}
/>
Audio only
</Label>
</FormGroup>
<FormGroup check>
<Label check>
<Input
type="radio"
name="recordingType"
value="audio-video"
checked={recordingType === 'audio-video'}
onChange={(e) => setRecordingType(e.target.value)}
disabled={isRecording || isStarting || isStopping}
/>
Audio and video
</Label>
</FormGroup>
</FormGroup>
{/* Video Source Selection */}
{recordingType === 'audio-video' && (
<FormGroup>
<Label for="videoSource">Video Source</Label>
<Input
type="select"
name="videoSource"
id="videoSource"
value={videoSource}
onChange={(e) => setVideoSource(e.target.value)}
disabled={isRecording || isStarting || isStopping}
>
{availableVideoOptions.map(option => (
<option key={option.key} value={option.key}>
{option.text}
</option>
))}
</Input>
</FormGroup>
)}
{/* Include Chat Option */}
<FormGroup check>
<Label check>
<Input
type="checkbox"
name="includeChat"
checked={includeChat}
onChange={(e) => setIncludeChat(e.target.checked)}
disabled={isRecording || isStarting || isStopping}
/>
Include voice chat in recorded audio
</Label>
</FormGroup>
</Form>
{/* Error Message */}
{error && (
<Alert color="danger" className="mt-3">
{error}
</Alert>
)}
</div>
</ModalBody>
<ModalFooter>
<Button
color={getRecordingButtonColor()}
onClick={handleStartStopRecording}
disabled={isLoading || isStarting || isStopping}
>
{getRecordingButtonText()}
</Button>
<Button color="secondary" onClick={handleCancel} disabled={isLoading}>
Close
</Button>
</ModalFooter>
</Modal>
);
};
export default JKSessionRecordingModal;

View File

@ -27,6 +27,7 @@ import SessionTrackVU from './SessionTrackVU.js';
import JKSessionSettingsModal from './JKSessionSettingsModal.js';
import JKSessionInviteModal from './JKSessionInviteModal.js';
import JKSessionVolumeModal from './JKSessionVolumeModal.js';
import JKSessionRecordingModal from './JKSessionRecordingModal.js';
import { SESSION_PRIVACY_MAP } from '../../helpers/globals.js';
import { toast } from 'react-toastify';
@ -87,6 +88,9 @@ const JKSessionScreen = () => {
const [showVolumeModal, setShowVolumeModal] = useState(false);
const [volumeLevel, setVolumeLevel] = useState(100);
//state for recording modal
const [showRecordingModal, setShowRecordingModal] = useState(false);
useEffect(() => {
if (!isConnected || !jamClient) return;
console.debug("JKSessionScreen: -DEBUG- isConnected changed to true");
@ -465,7 +469,7 @@ const JKSessionScreen = () => {
<Button className='btn-custom-outline' outline size="md" onClick={() => setShowInviteModal(true)}>Invite</Button>
<Button className='btn-custom-outline' outline size="md" onClick={() => setShowVolumeModal(true)}>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" onClick={() => setShowRecordingModal(true)}>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>
@ -704,6 +708,11 @@ const JKSessionScreen = () => {
isOpen={showVolumeModal}
toggle={() => setShowVolumeModal(!showVolumeModal)}
/>
<JKSessionRecordingModal
isOpen={showRecordingModal}
toggle={() => setShowRecordingModal(!showRecordingModal)}
/>
</Card>
)
}