logic for uploading and all with magic and all

This commit is contained in:
talksik
2022-05-29 10:42:29 -05:00
parent 754f9ce300
commit 3e76a67f66
5 changed files with 102 additions and 28 deletions
+6 -4
View File
@@ -1,6 +1,6 @@
import { Timestamp } from 'firebase/firestore';
interface IContent {
export interface IContent {
id: string;
creatorUserId: string;
contentUrl: string; // remote resource of file/media
@@ -12,8 +12,9 @@ interface IContent {
// ? persist length of clip for easier viewing for others...
// ?they have to load it anyway and will get metadata anyway?
export class AudioClip implements IContent {
id: string;
constructor(
public id: string,
public creatorUserId: string,
public contentUrl: string,
public createdDate: Timestamp = Timestamp.now(),
@@ -21,8 +22,8 @@ export class AudioClip implements IContent {
}
export class Link implements IContent {
id: string;
constructor(
public id: string,
public creatorUserId: string,
public contentUrl: string,
public createdDate: Timestamp = Timestamp.now(),
@@ -30,8 +31,9 @@ export class Link implements IContent {
}
export class Image implements IContent {
id: string;
constructor(
public id: string,
public creatorUserId: string,
public contentUrl: string,
public thumbnailUrl?: string,
+63 -23
View File
@@ -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'}
@@ -2,7 +2,7 @@
"apiKey": "AIzaSyAbTV9bb0TOtagzQMRMdXpSX5RhkS3z8NE",
"authDomain": "nirvana-v3.firebaseapp.com",
"projectId": "nirvana-v3",
"storageBucket": "nirvana-v3.appspot.com",
"storageBucket": "gs://nirvana-v3.appspot.com/",
"messagingSenderId": "423533244953",
"appId": "1:423533244953:web:b492f91669886c065db088",
"measurementId": "G-0276T4D8NY",
@@ -0,0 +1,18 @@
import { getDownloadURL, getStorage, ref, uploadBytes } from 'firebase/storage';
const storage = getStorage();
export const uploadAudioClip = async (uniqueFileName: string, blob: Blob) => {
try {
const audioClipsRef = ref(storage, `audioClips/${uniqueFileName}`);
// 'file' comes from the Blob or File API
const snapshot = await uploadBytes(audioClipsRef, blob);
return await getDownloadURL(audioClipsRef);
} catch (error) {
console.warn('problem in uploading audio clip');
throw Error('problem in uploading audio clip');
}
};
@@ -29,6 +29,7 @@ import Conversation, {
MemberState,
} from '@nirvana/core/src/models/conversation.model';
import useAuth from '../providers/AuthProvider';
import { AudioClip } from '@nirvana/core/src/models/content.model';
interface Document {
id: string;
@@ -60,6 +61,9 @@ const db = {
user: (userId: string) => docPoint<User>(`users/${userId}`),
conversations: collectionPoint<Conversation>(`conversations`),
conversation: (conversationId: string) => docPoint<User>(`conversations/${conversationId}`),
conversationAudioClips: (conversationId: string) =>
collectionPoint<AudioClip>(`conversations/${conversationId}/audioClips`),
};
// enum COLLECTION {
@@ -194,3 +198,13 @@ export const createGroupConversation = async (
throw new Error('Error creating user');
}
};
export const sendAudioClipToConversation = async (audioClip: AudioClip, conversationId: string) => {
try {
await addDoc(db.conversationAudioClips(conversationId), audioClip);
} catch (e) {
console.error('Error creating user: ', e);
throw new Error('Error creating user');
}
};