|
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
|
|
FiLogOut,
|
|
|
|
|
FiMonitor,
|
|
|
|
|
FiWind,
|
|
|
|
|
FiCloudRain,
|
|
|
|
|
} from 'react-icons/fi';
|
|
|
|
|
import { useSnackbar } from 'notistack';
|
|
|
|
|
|
|
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
|
|
getConversationsQueryLIVE,
|
|
|
|
|
getUserById,
|
|
|
|
|
searchUsers,
|
|
|
|
|
sendAudioClipToConversation,
|
|
|
|
|
} from '../firebase/firestore';
|
|
|
|
|
import { Link, AudioClip, Image } from '@nirvana/core/src/models/content.model';
|
|
|
|
|
import { useImmer } from 'use-immer';
|
|
|
|
@@ -66,6 +68,7 @@ import { useDebounce, useEffectOnce, useKeyPressEvent } from 'react-use';
|
|
|
|
|
|
|
|
|
|
import KeyboardShortcutLabel from './KeyboardShortcutLabel';
|
|
|
|
|
import Channels from '../electron/constants';
|
|
|
|
|
import { uploadAudioClip } from '../firebase/firebaseStorage';
|
|
|
|
|
type ConversationMap = {
|
|
|
|
|
[conversationId: string]: Conversation;
|
|
|
|
|
};
|
|
|
|
@@ -89,12 +92,14 @@ interface ITerminalContext {
|
|
|
|
|
getUser?: (userId: string) => Promise<User | undefined>;
|
|
|
|
|
|
|
|
|
|
isUserSpeaking: boolean;
|
|
|
|
|
isCloudDoingMagic: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TerminalContext = React.createContext<ITerminalContext>({
|
|
|
|
|
conversationMap: {},
|
|
|
|
|
|
|
|
|
|
isUserSpeaking: false,
|
|
|
|
|
isCloudDoingMagic: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// global audio data in memory
|
|
|
|
@@ -103,13 +108,6 @@ const handleMediaRecorderData = (e: BlobEvent) => {
|
|
|
|
|
audioChunks.push(e.data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleOnStopRecording = (e: BlobEvent) => {
|
|
|
|
|
console.log(audioChunks);
|
|
|
|
|
const blob = new Blob(audioChunks);
|
|
|
|
|
const audioUrl = URL.createObjectURL(blob);
|
|
|
|
|
const player = new Audio(audioUrl);
|
|
|
|
|
player.autoplay = true;
|
|
|
|
|
};
|
|
|
|
|
const handleOnStartRecording = (e: BlobEvent) => {
|
|
|
|
|
audioChunks = [];
|
|
|
|
|
};
|
|
|
|
@@ -267,6 +265,33 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
|
|
|
|
const [userAudioStream, setUserAudioStream] = useState<MediaStream>(null);
|
|
|
|
|
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder>(null);
|
|
|
|
|
|
|
|
|
|
// from the time of starting to speak to finally sent to everyone out there
|
|
|
|
|
// feeling to user that everyone is listening right now even though it's only recording
|
|
|
|
|
const [isCloudDoingMagic, setIsCloudDoingMagic] = useState<boolean>(false);
|
|
|
|
|
|
|
|
|
|
const handleOnStopRecording = useCallback(async () => {
|
|
|
|
|
console.log(audioChunks[0]);
|
|
|
|
|
const blob = new Blob(audioChunks);
|
|
|
|
|
|
|
|
|
|
const audioUrl = URL.createObjectURL(blob);
|
|
|
|
|
const player = new Audio(audioUrl);
|
|
|
|
|
player.autoplay = true;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const downloadUrl = await uploadAudioClip(`${user.uid}-${new Date().valueOf()}`, blob);
|
|
|
|
|
|
|
|
|
|
const audioClip = new AudioClip(user.uid, downloadUrl);
|
|
|
|
|
await sendAudioClipToConversation(audioClip, selectedConversation.id);
|
|
|
|
|
|
|
|
|
|
enqueueSnackbar('clip sent!', { variant: 'success' });
|
|
|
|
|
setIsCloudDoingMagic(false);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
enqueueSnackbar('Problem in recording clip', { variant: 'error' });
|
|
|
|
|
}
|
|
|
|
|
enqueueSnackbar('clip sent!', { variant: 'success' });
|
|
|
|
|
}, [user.uid, setIsCloudDoingMagic, enqueueSnackbar, selectedConversation?.id]);
|
|
|
|
|
|
|
|
|
|
const handleAskForMicrophonePermissions = useCallback(() => {
|
|
|
|
|
// fix this...has to do with mac configuration
|
|
|
|
|
// window.electronAPI.send(Channels.ASK_MICROPHONE_PERMISSIONS);
|
|
|
|
@@ -290,21 +315,20 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
|
|
|
|
enqueueSnackbar('Check your audio mic permissions in preferences!!', { variant: 'error' });
|
|
|
|
|
console.error(error);
|
|
|
|
|
});
|
|
|
|
|
}, [setUserAudioStream, enqueueSnackbar, setMediaRecorder]);
|
|
|
|
|
}, [setUserAudioStream, enqueueSnackbar, setMediaRecorder, handleOnStopRecording]);
|
|
|
|
|
|
|
|
|
|
// on load, try getting microphone access
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
handleAskForMicrophonePermissions();
|
|
|
|
|
}, [handleAskForMicrophonePermissions]);
|
|
|
|
|
|
|
|
|
|
// const handleRecorderStopped = useCallback(async () => {
|
|
|
|
|
// await
|
|
|
|
|
|
|
|
|
|
// }, [])
|
|
|
|
|
|
|
|
|
|
// when user wants to talk,
|
|
|
|
|
// unmute them and send clip for everyone to hear in the distance
|
|
|
|
|
const handleBroadcast = useCallback(() => {
|
|
|
|
|
if (!selectedConversationId) {
|
|
|
|
|
enqueueSnackbar('You must select a conversation first!!!', { variant: 'warning' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!userAudioStream) {
|
|
|
|
|
enqueueSnackbar('Check your audio mic permissions in preferences!!', { variant: 'error' });
|
|
|
|
|
|
|
|
|
@@ -316,12 +340,19 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
|
|
|
|
// ? better to just start after 200 ms as put below for the delay of stopping recording?
|
|
|
|
|
if (mediaRecorder.state === 'recording') return;
|
|
|
|
|
|
|
|
|
|
mediaRecorder.start();
|
|
|
|
|
|
|
|
|
|
setIsUserSpeaking(true);
|
|
|
|
|
|
|
|
|
|
enqueueSnackbar('started recording');
|
|
|
|
|
}, [setIsUserSpeaking, userAudioStream, mediaRecorder, handleAskForMicrophonePermissions]);
|
|
|
|
|
setIsCloudDoingMagic(true);
|
|
|
|
|
mediaRecorder.start();
|
|
|
|
|
setIsUserSpeaking(true);
|
|
|
|
|
}, [
|
|
|
|
|
selectedConversationId,
|
|
|
|
|
setIsUserSpeaking,
|
|
|
|
|
userAudioStream,
|
|
|
|
|
mediaRecorder,
|
|
|
|
|
handleAskForMicrophonePermissions,
|
|
|
|
|
setIsCloudDoingMagic,
|
|
|
|
|
enqueueSnackbar,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const handleStopBroadcast = useCallback(() => {
|
|
|
|
|
if (isUserSpeaking) {
|
|
|
|
@@ -333,7 +364,7 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
|
|
|
|
mediaRecorder.stop();
|
|
|
|
|
}, 200);
|
|
|
|
|
}
|
|
|
|
|
}, [setIsUserSpeaking, isUserSpeaking]);
|
|
|
|
|
}, [setIsUserSpeaking, isUserSpeaking, mediaRecorder, enqueueSnackbar]);
|
|
|
|
|
|
|
|
|
|
useKeyPressEvent('`', handleBroadcast, handleStopBroadcast);
|
|
|
|
|
|
|
|
|
@@ -386,6 +417,7 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) {
|
|
|
|
|
getUser,
|
|
|
|
|
handleQuickDial,
|
|
|
|
|
selectConversation,
|
|
|
|
|
isCloudDoingMagic,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Grid container spacing={0}>
|
|
|
|
@@ -791,11 +823,12 @@ function MainPanel() {
|
|
|
|
|
|
|
|
|
|
function ConversationDetails() {
|
|
|
|
|
const { user } = useAuth();
|
|
|
|
|
|
|
|
|
|
const { getUser, selectedConversation, selectConversation } = useTerminal();
|
|
|
|
|
|
|
|
|
|
const { getUser, selectedConversation, selectConversation, isCloudDoingMagic } = useTerminal();
|
|
|
|
|
const [conversationUsers, setConversationUsers] = useImmer<User[]>([]);
|
|
|
|
|
|
|
|
|
|
// TODO: at the terminal level, make sure we are listening for audio clips
|
|
|
|
|
// and here it's just an access of that map of content for each conversation's blocks
|
|
|
|
|
|
|
|
|
|
// join the room
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedConversation.membersInRoom?.length > 0) {
|
|
|
|
@@ -866,7 +899,7 @@ function ConversationDetails() {
|
|
|
|
|
</Stack>
|
|
|
|
|
|
|
|
|
|
<Container maxWidth={false} sx={{ position: 'relative', flex: 1 }}>
|
|
|
|
|
{/* <ConversationHistory /> */}
|
|
|
|
|
<ConversationHistory />
|
|
|
|
|
|
|
|
|
|
<Box
|
|
|
|
|
sx={{
|
|
|
|
@@ -887,6 +920,7 @@ function ConversationDetails() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ConversationHistory() {
|
|
|
|
|
const { isCloudDoingMagic } = useTerminal();
|
|
|
|
|
return (
|
|
|
|
|
<Container maxWidth="xs">
|
|
|
|
|
<Stack
|
|
|
|
@@ -951,6 +985,12 @@ function ConversationHistory() {
|
|
|
|
|
</Paper>
|
|
|
|
|
</Stack>
|
|
|
|
|
|
|
|
|
|
{isCloudDoingMagic && (
|
|
|
|
|
<IconButton color="secondary">
|
|
|
|
|
<FiCloudRain />
|
|
|
|
|
</IconButton>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Stack
|
|
|
|
|
justifyContent={'flex-start'}
|
|
|
|
|
alignItems={'center'}
|
|
|
|
|