add recording functionality
This commit is contained in:
parent
0fab4532c6
commit
f417e16319
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { useJamServerContext } from '../../context/JamServerContext';
|
||||
import useRecordingHelpers from '../../hooks/useRecordingHelpers';
|
||||
import { AUDIO_STORE_TYPE_MIX_AND_STEMS, AUDIO_STORE_TYPE_MIX_ONLY, RECORD_TYPE_AUDIO, RECORD_TYPE_BOTH } from '../../helpers/globals.js';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
||||
const { jamClient } = useJamServerContext();
|
||||
|
|
@ -46,6 +47,26 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
{ key: 'video-window', text: 'Record session video window' }
|
||||
];
|
||||
|
||||
//On mount get the audio store type from backend and set the radio button accordingly using jamClient.GetAudioRecordingPreference()
|
||||
useEffect(() => {
|
||||
const fetchAudioStoreType = async () => {
|
||||
try {
|
||||
if (jamClient) {
|
||||
const pref = await jamClient.GetAudioRecordingPreference();
|
||||
if (AUDIO_STORE_TYPE_MIX_AND_STEMS['backendValues'].includes(pref)) {
|
||||
setAudioStoreType(AUDIO_STORE_TYPE_MIX_AND_STEMS['key']);
|
||||
} else if (AUDIO_STORE_TYPE_MIX_ONLY['backendValues'].includes(pref)) {
|
||||
setAudioStoreType(AUDIO_STORE_TYPE_MIX_ONLY['key']);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching audio store type preference:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAudioStoreType();
|
||||
}, [jamClient]);
|
||||
|
||||
// Load available video sources when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen && jamClient) {
|
||||
|
|
@ -76,7 +97,7 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
const handleStartStopRecording = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
alert('Processing recording request. Please wait...');
|
||||
try {
|
||||
if (isRecording) {
|
||||
// Stop recording
|
||||
|
|
@ -112,13 +133,24 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
}
|
||||
|
||||
const recordSettings = {
|
||||
recordingType: recordVideo ? 'JK.RECORD_TYPE_BOTH' : 'JK.RECORD_TYPE_AUDIO',
|
||||
recordingType: recordVideo ? RECORD_TYPE_BOTH : RECORD_TYPE_AUDIO,
|
||||
recordVideo: recordVideo,
|
||||
recordChat: includeChat,
|
||||
videoType: recordVideoType
|
||||
};
|
||||
|
||||
await recordingHelpers.startRecording(recordSettings);
|
||||
const success = await recordingHelpers.startRecording(recordSettings);
|
||||
if (!success) {
|
||||
setError('Failed to start recording. Please try again.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
setIsLoading(false);
|
||||
setRecordingName('');
|
||||
setAudioFormat('');
|
||||
alert('Recording started successfully.');
|
||||
handleCancel();
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Recording operation failed:', error);
|
||||
|
|
@ -161,6 +193,29 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
}
|
||||
});
|
||||
|
||||
const onRecordingTypeChange = async (e) => {
|
||||
const selectedType = e.target.value;
|
||||
setRecordingType(selectedType);
|
||||
|
||||
if (selectedType === RECORD_TYPE_BOTH) {
|
||||
const obsAvailable = await jamClient.IsOBSAvailable();
|
||||
if (!obsAvailable) {
|
||||
alert("OBS Studio is required for video recording. Please install OBS Studio and try again.");
|
||||
setRecordingType(RECORD_TYPE_AUDIO);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onAudioStoreTypeChange = (e) => {
|
||||
const value = e.target.value;
|
||||
if (value === AUDIO_STORE_TYPE_MIX_AND_STEMS['key']) {
|
||||
jamClient.SetAudioRecordingPreference(AUDIO_STORE_TYPE_MIX_AND_STEMS['backendValues'][0]);
|
||||
} else if (value === AUDIO_STORE_TYPE_MIX_ONLY['key']) {
|
||||
jamClient.SetAudioRecordingPreference(AUDIO_STORE_TYPE_MIX_ONLY['backendValues'][0]);
|
||||
}
|
||||
setAudioStoreType(value);
|
||||
}
|
||||
|
||||
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">
|
||||
|
|
@ -181,7 +236,7 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
name="recordingType"
|
||||
value="audio-only"
|
||||
checked={recordingType === 'audio-only'}
|
||||
onChange={(e) => setRecordingType(e.target.value)}
|
||||
onChange={onRecordingTypeChange}
|
||||
disabled={isRecording || isStarting || isStopping}
|
||||
/>
|
||||
Audio only
|
||||
|
|
@ -194,7 +249,7 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
name="recordingType"
|
||||
value="audio-video"
|
||||
checked={recordingType === 'audio-video'}
|
||||
onChange={(e) => setRecordingType(e.target.value)}
|
||||
onChange={onRecordingTypeChange}
|
||||
disabled={isRecording || isStarting || isStopping}
|
||||
/>
|
||||
Audio and video
|
||||
|
|
@ -214,7 +269,7 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
name="audioStoreType"
|
||||
value={AUDIO_STORE_TYPE_MIX_AND_STEMS['key']}
|
||||
checked={AUDIO_STORE_TYPE_MIX_AND_STEMS['key'] === audioStoreType}
|
||||
onChange={(e) => setAudioStoreType(e.target.value)}
|
||||
onChange={onAudioStoreTypeChange}
|
||||
disabled={isRecording || isStarting || isStopping}
|
||||
/>
|
||||
Session mix & individual parts (streams)
|
||||
|
|
@ -270,11 +325,20 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
<Row className='mt-2'>
|
||||
<Col xs="12" md="4" lg="3"><label htmlFor="voiceChat">Voice Chat</label></Col>
|
||||
<Col>
|
||||
<Input
|
||||
type="check"
|
||||
name='voiceChat'
|
||||
value=""
|
||||
/>
|
||||
<FormGroup tag="fieldset">
|
||||
<div style={{ }}>
|
||||
<Label >
|
||||
<Input
|
||||
type="checkbox"
|
||||
checked={includeChat}
|
||||
onChange={(e) => setIncludeChat(e.target.checked)}
|
||||
disabled={isRecording || isStarting || isStopping}
|
||||
name="includeChat"
|
||||
/>
|
||||
Include voice chat in recording
|
||||
</Label>
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -282,6 +346,7 @@ const JKSessionRecordingModal = ({ isOpen, toggle }) => {
|
|||
<Col xs="12" md="4" lg="3"><label htmlFor="videoFormat">Video Format</label></Col>
|
||||
<Col>
|
||||
<Input
|
||||
disabled={recordingType === RECORD_TYPE_AUDIO || isRecording || isStarting || isStopping}
|
||||
type='select'
|
||||
name=""
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@ import useMixerStore from '../../hooks/useMixerStore.js';
|
|||
|
||||
import { useCurrentSessionContext } from '../../context/CurrentSessionContext.js';
|
||||
import { useJamServerContext } from '../../context/JamServerContext.js';
|
||||
import { useGlobalContext } from '../../context/GlobalContext.js';
|
||||
import { useJamKazamApp } from '../../context/JamKazamAppContext.js';
|
||||
import { useMixersContext } from '../../context/MixersContext.js';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
|
||||
import { getSessionHistory, getSession, joinSession as joinSessionRest, updateSessionSettings, getFriends } from '../../helpers/rest';
|
||||
import { dkeys } from '../../helpers/utils.js';
|
||||
|
||||
import { CLIENT_ROLE } from '../../helpers/globals';
|
||||
import { getSessionHistory, getSession, joinSession as joinSessionRest, updateSessionSettings, getFriends, startRecording, stopRecording } from '../../helpers/rest';
|
||||
|
||||
import { CLIENT_ROLE, RECORD_TYPE_AUDIO, RECORD_TYPE_BOTH } from '../../helpers/globals';
|
||||
import { MessageType } from '../../helpers/MessageFactory.js';
|
||||
|
||||
import { Alert, Col, Row, Button, Card, CardBody, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader, Badge } from 'reactstrap';
|
||||
|
|
@ -53,7 +56,8 @@ const JKSessionScreen = () => {
|
|||
server,
|
||||
registerMessageCallback } = useJamServerContext();
|
||||
const { currentSession, setCurrentSession, currentSessionIdRef, setCurrentSessionId, inSession } = useCurrentSessionContext();
|
||||
const { getCurrentRecordingState, reset: resetRecordingState } = useRecordingHelpers();
|
||||
const { globalObject } = useGlobalContext();
|
||||
const { getCurrentRecordingState, reset: resetRecordingState, currentlyRecording } = useRecordingHelpers();
|
||||
const { SessionPageEnter } = useSessionUtils();
|
||||
|
||||
// Use the session model hook
|
||||
|
|
@ -457,6 +461,78 @@ const JKSessionScreen = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRecordingSubmit = async (settings) => {
|
||||
settings.volume = getCurrentRecordingState().inputVolumeLevel;
|
||||
try {
|
||||
localStorage.setItem("recordSettings", JSON.stringify(settings));
|
||||
} catch (e) {
|
||||
logger.info("error while saving recordSettings to localStorage");
|
||||
logger.log(e.stack);
|
||||
}
|
||||
|
||||
const params = {
|
||||
recordingType: settings.recordingType,
|
||||
name: settings.recordingName,
|
||||
audioFormat: settings.audioFormat,
|
||||
audioStoreType: settings.audioStoreType,
|
||||
includeChat: settings.includeChat,
|
||||
volume: settings.volume,
|
||||
};
|
||||
|
||||
if (params.recordingType === RECORD_TYPE_BOTH) {
|
||||
params['videoFormat'] = settings.videoFormat;
|
||||
params['audioDelay'] = settings.audioDelay;
|
||||
|
||||
const obsAvailable = await jamClient.IsOBSAvailable();
|
||||
|
||||
if (!obsAvailable) {
|
||||
toast.warning("OBS Studio is not available. Please ensure OBS Studio is installed and running to record video.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!globalObject.JK.videoIsOngoing) {
|
||||
toast.warning("To make a video recording in JamKazam you must have an ongoing video. You can start a video by clicking the Video button on session tool bar.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
//this.startStopRecording(params);
|
||||
//TODO: handle startStopRecording
|
||||
doStartRecording(params);
|
||||
|
||||
}
|
||||
|
||||
function groupTracksToClient(recording) {
|
||||
// group N tracks to the same client Id
|
||||
let groupedTracks = {};
|
||||
let recordingTracks = recording["recorded_tracks"];
|
||||
for (let i = 0; i < recordingTracks.length; i++) {
|
||||
let clientId = recordingTracks[i].client_id;
|
||||
|
||||
let tracksForClient = groupedTracks[clientId];
|
||||
if (!tracksForClient) {
|
||||
tracksForClient = [];
|
||||
groupedTracks[clientId] = tracksForClient;
|
||||
}
|
||||
tracksForClient.push(recordingTracks[i]);
|
||||
}
|
||||
return dkeys(groupedTracks);
|
||||
}
|
||||
|
||||
const doStartRecording = (params) => {
|
||||
startRecording({ music_session_id: currentSession.id, recordVideo: params.recordVideo }).then(async (recording) => {
|
||||
const currentRecordingId = recording.id;
|
||||
console.debug("Recording started with ID: ", currentRecordingId);
|
||||
const groupedTracks = groupTracksToClient(recording);
|
||||
try {
|
||||
await jamClient.StartMediaRecording(currentRecordingId, groupedTracks, params);
|
||||
} catch (error) {
|
||||
console.error("Error starting media recording:", error);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error("Error starting recording:", error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{!isConnected && <div className='d-flex align-items-center'>Connecting to backend...</div>}
|
||||
|
|
@ -712,6 +788,7 @@ const JKSessionScreen = () => {
|
|||
<JKSessionRecordingModal
|
||||
isOpen={showRecordingModal}
|
||||
toggle={() => setShowRecordingModal(!showRecordingModal)}
|
||||
onSubmit={handleRecordingSubmit}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,14 +23,24 @@ export const GlobalProvider = ({ children }) => {
|
|||
wigetID: ""
|
||||
});
|
||||
|
||||
const [globalObject, setGlobalObject] = useState({
|
||||
JK: window.JK || {},
|
||||
//ExternalVideoActions: window.ExternalVideoActions || null,
|
||||
//RecordingActions: window.RecordingActions || null,
|
||||
});
|
||||
|
||||
const [videoEnabled, setVideoEnabled] = useState(false);
|
||||
|
||||
return (
|
||||
<GlobalContext.Provider value={{
|
||||
trackVolumeObject,
|
||||
setTrackVolumeObject,
|
||||
globalObject,
|
||||
setGlobalObject,
|
||||
}}>
|
||||
{children}
|
||||
</GlobalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useGlobalContext = () => React.useContext(GlobalContext);
|
||||
export const useGlobalContext = () => React.useContext(GlobalContext);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { FakeJamClientProxy } from '../fakeJamClientProxy';
|
|||
import { FakeJamClientRecordings } from '../fakeJamClientRecordings';
|
||||
import { FakeJamClientMessages } from '../fakeJamClientMessages';
|
||||
import { useJamKazamApp } from './JamKazamAppContext';
|
||||
import { useGlobalContext } from './GlobalContext';
|
||||
|
||||
const JamClientContext = createContext(null);
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ export const JamClientProvider = ({ children }) => {
|
|||
|
||||
//get the app instance from JamKazamAppContext
|
||||
const app = useJamKazamApp();
|
||||
const { globalObject } = useGlobalContext();
|
||||
|
||||
const proxyRef = useRef(null);
|
||||
|
||||
|
|
@ -29,13 +31,13 @@ export const JamClientProvider = ({ children }) => {
|
|||
// proxyRef.current.SetFakeRecordingImpl(fakeJamClientRecordings);
|
||||
// } else {
|
||||
// if (!proxyRef.current) {
|
||||
// const proxy = new JamClientProxy(app, console);
|
||||
// const proxy = new JamClientProxy(app, console, globalObject);
|
||||
// proxyRef.current = proxy.init();
|
||||
// }
|
||||
// }
|
||||
|
||||
if (!proxyRef.current) {
|
||||
const proxy = new JamClientProxy(app, console);
|
||||
const proxy = new JamClientProxy(app, console, globalObject);
|
||||
proxyRef.current = proxy.init();
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -540,4 +540,14 @@ export const SKIPPED_NETWORK_TEST = -1;
|
|||
|
||||
export const getAvatarUrl = (photo_url) => {
|
||||
return photo_url ? photo_url : "/assets/shared/avatar_generic.png";
|
||||
};
|
||||
|
||||
export const dkeys = (d) => {
|
||||
var keys = []
|
||||
for (var i in d) {
|
||||
if (d.hasOwnProperty(i)) {
|
||||
keys.push(i);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
|
@ -2,6 +2,8 @@ import { useState, useCallback, useRef, useEffect, useContext } from 'react';
|
|||
import { useCurrentSessionContext } from '../context/CurrentSessionContext';
|
||||
import { useJamKazamApp } from '../context/JamKazamAppContext';
|
||||
import { startRecording as startRecordingRest, stopRecording as stopRecordingRest, getRecordingPromise } from '../helpers/rest';
|
||||
import { useGlobalContext } from '../context/GlobalContext';
|
||||
import { RECORD_TYPE_BOTH } from '../helpers/globals'
|
||||
|
||||
const useRecordingHelpers = (jamClient) => {
|
||||
const app = useJamKazamApp();
|
||||
|
|
@ -18,6 +20,8 @@ const useRecordingHelpers = (jamClient) => {
|
|||
const [waitingOnServerStop, setWaitingOnServerStop] = useState(false);
|
||||
const [waitingOnClientStop, setWaitingOnClientStop] = useState(false);
|
||||
|
||||
const { globalObject, setGlobalObject } = useGlobalContext();
|
||||
|
||||
const waitingOnStopTimer = useRef(null);
|
||||
const sessionId = currentSession?.id;
|
||||
|
||||
|
|
@ -75,7 +79,7 @@ const useRecordingHelpers = (jamClient) => {
|
|||
|
||||
// Start recording
|
||||
const startRecording = useCallback(async (recordSettings) => {
|
||||
const recordVideo = recordSettings.recordingType === 'JK.RECORD_TYPE_BOTH';
|
||||
const recordVideo = recordSettings.recordingType === RECORD_TYPE_BOTH;
|
||||
|
||||
// Trigger startingRecording event
|
||||
// In React, we might use a callback or context to notify parent components
|
||||
|
|
|
|||
Loading…
Reference in New Issue