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:
parent
bff9cf826f
commit
d64769919a
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue