<script lang="ts">
import { defineComponent, onMounted, ref, reactive } from "vue";
-import useMessageItemComp from "@/composables/messageItem/useMessageItemComp";
-import useMessageItemAnims from "@/composables/messageItem/useMessageItemAnims";
-import useTextWrap from "@/composables/textWrap/useTextWrap";
+import useMessageItemComp from "@/core/messenger/message/messageDetails/useComputed";
+import useMessageItemAnims from "@/core/messenger/message/messageDetails/useAnimations";
+import useTextWrap from "@/utils/textWrap/useTextWrap";
export default defineComponent({
name: "MessageItem",
props: {
</template>
<script lang="ts">
-import useMessageRenderer from "@/composables/messageRenderer/useMessageRenderer";
-import useScrollView from "@/composables/scrollView/useScrollView";
+import useRenderView from '@/core/messenger/renderView/useRenderView';
+import useScrollView from "@/core/UI/scrollView/useScrollView";
import {
defineComponent,
reactive,
});
}
- const { beforeEnter, enter, afterEnter } = useMessageRenderer();
- const {scrollView} = useScrollView({ targetId: "messageRenderer"});
+ const { beforeEnter, enter, afterEnter } = useRenderView();
+ const { scrollView } = useScrollView({
+ targetId: "messageRenderer"
+ });
return {
renderArr,
</template>
<script lang="ts">
-import {defineComponent} from 'vue'
+import { defineComponent } from 'vue'
import TextInput from './inputDetails/textInput.vue';
import AudioInput from './inputDetails/audioInput.vue';
-import useInputRenderer from "@/composables/inputItem/useInputRenderer";
+import useInputController from "@/core/UI/input/useInputController";
export default defineComponent({
name: "InputItem",
components: {TextInput, AudioInput},
renderAudio,
reactiveWidth,
textInputXoffset,
- paths } = useInputRenderer();
+ paths } = useInputController();
return {
input,
+++ /dev/null
-export default function useAudioRenderer(stream: MediaStream) {
- const audioCtx = new (window.AudioContext)();
- const analyzer = audioCtx.createAnalyser();
- const source = audioCtx.createMediaStreamSource(stream);
- source.connect(analyzer);
-
- analyzer.fftSize = 2048;
- const xInitial = 24;
- const xFinal = 640;
- analyzer.minDecibels = -90;
- analyzer.smoothingTimeConstant = 0.85;
- const dataArray = new Uint8Array(analyzer.frequencyBinCount);
- // const sampleRate = 16000;
-
- const renderAudio = (audio: Float32Array, sampleRate: number) => {
-
- // step 2: source audio frequency {ffft} data
- const buffer = audioCtx.createBuffer(1, audio.length, sampleRate);
- buffer.copyToChannel(audio, 0, 0);
- const source = audioCtx.createBufferSource();
- source.buffer = buffer;
- source.connect(analyzer);
- source.start();
- analyzer.getByteFrequencyData(dataArray);
-
- // step 3: process audio frequency data
- const dataView = dataArray.slice(xInitial, xFinal);
- const sum = dataView.reduce((a, b) => a + b);
- const average = sum / dataView.length;
- const scaled = average / 255;
- const zeroed = scaled - 0.6;
- return average;
- }
-
- return {
- renderAudio
- }
-}
+++ /dev/null
-import {ref, watch, onUnmounted, onMounted } from "vue";
-import useKeyDownHandler from "@/composables/keyDownHandler/useKeyDownHandler";
-import useMediaStream from "@/composables/mediaStream/useMediaStream";
-import useStreamRecorder from "@/composables/streamRecorder/useStreamRecorder";
-import useTextInput from '@/composables/inputItem/useTextInput';
-/*
-* Renders input to the user
-*/
-export default function useInputRenderer() {
- // program ref varaiables
- const renderAudio = ref(false);
- const reactiveWidth = ref(10);
- const pathOffset = ref(10);
- const paths = ref([]);
-
- // get input feedback & handler function
- const { keyDownHandler, input } = useKeyDownHandler();
- // get text Offset and & textRender function
- const { textInputXoffset, renderTextInput } = useTextInput(input, renderAudio);
-
- // why do we have audio stuff here ?
- let stream: MediaStream;
- const {renderStream} = useStreamRecorder(renderAudio)
- onMounted(async () => {
- const streamRes = await useMediaStream()
- stream = streamRes.stream;
- })
-
- // stop stream recording on space keyup event
- function keyupHandler(e: any) {
- if (e.key === " " && renderAudio.value) {
- console.log("stop recording");
- renderAudio.value = false;
- input.value = "";
- reactiveWidth.value = 10;
- pathOffset.value = 10;
- }
- }
-
- // add window event listener
- window.addEventListener("keydown", keyDownHandler);
- window.addEventListener('keyup', keyupHandler);
-
- // remove Event Listeners on component unMount
- onUnmounted(() => {
- window.removeEventListener("keydown", keyDownHandler);
- window.removeEventListener("keyup", keyupHandler);
- });
-
- // determine whether to render text or audio based one the keyboard input
- watch(input, async (input, prevInput) => {
- if (prevInput !== "" || prevInput.length > 0) {
- if (!renderAudio.value) {
- renderTextInput();
- }
- return;
- }
- if (input === " " && renderAudio.value === false) {
- console.log("record");
- renderAudio.value = true;
- renderStream(stream, reactiveWidth, pathOffset, paths);
- return;
- }
- renderTextInput();
- });
-
- return {
- input,
- renderAudio,
- reactiveWidth,
- textInputXoffset,
- paths
- }
-
-}
\ No newline at end of file
+++ /dev/null
-import { ref, onMounted } from "vue";
-import {Ref} from "@/types/vueRef/index";
-export default function useMessageItemAnims({
- mr,
- tr,
-}: {
- mr: Ref<string>;
- tr: Ref<string>;
-}) {
- const signatureTranslate = ref("");
- let messageRect: SVGSVGElement & SVGRectElement;
- let messageText: SVGSVGElement & SVGTextElement;
-
- onMounted(async () => {
- messageRect = (mr.value as unknown) as SVGSVGElement & SVGRectElement;
- messageText = (tr.value as unknown) as SVGSVGElement & SVGTextElement;
- });
-
- function adjustMessageRect(): void {
- // must get dimensions of text box to fit bubble around
- const padding = 12.5;
- const textBox = messageText.getBBox();
- messageRect.setAttribute("x", String(textBox.x - padding));
- messageRect.setAttribute("y", String(textBox.y - padding));
- messageRect.setAttribute("width", String(textBox.width + 2 * padding));
- messageRect.setAttribute("height", String(textBox.height + 2 * padding));
- }
-
- function translateMsgSignature(): void {
- if (messageRect.getAttribute("height") !== null) {
- const height = Number(messageRect.getAttribute("height"));
- signatureTranslate.value = `translate(-10 ${height})`;
- }
- }
-
- async function drawMessage() {
- // ignore lint error: await needed for proper render
- await adjustMessageRect();
- translateMsgSignature();
- }
-
- return {
- drawMessage,
- signatureTranslate,
- };
-}
+++ /dev/null
-import { computed, onMounted, ref } from "vue";
-export default function useMessageItemComp(m: string, id: string) {
- const x = ref(0)
- const paddingLeft = 30;
- const winW = 350;
- const messageWidth = ref(0);
- const messageMarginTop = 75;
- const test = ref("");
-
- const textFill = computed(() => {
- switch (m) {
- case "sf":
- return "#fff";
- default:
- return "#000";
- }
- });
-
- const bubbleFill = computed(() => {
- switch (m) {
- case "sf":
- return "#61b4f4";
- default:
- return "#fff";
- }
- });
-
- const setMessageWidthRef = async () => {
- const msg = await document.getElementById(id);
- if (msg) {
- messageWidth.value = msg.getBoundingClientRect().width;
- }
- }
-
- const selfMessageMarginRight = 2.5;
- const calculateXoffset = async () => {
- switch (m) {
- case "sf":
- x.value = winW - messageWidth.value - selfMessageMarginRight;
- break;
- default:
- x.value = paddingLeft;
- // to center a message
- // x = messageMargin + (winW -messageWidth) / 2;
- }
- }
-
- const startingOffset = computed(() => {
- const messageRenderer = document.getElementById('messageRenderer');
- if (messageRenderer) {
- const rect = messageRenderer.getBoundingClientRect();
- let Y = 600;
- if (rect.height > 500) {
- Y = rect.height + messageMarginTop;
- }
- return `translate(${x.value} ${Y})`;
- }
- });
-
- onMounted(async () => {
- // to calculate X offset:
- // First: set message width ref
- await setMessageWidthRef();
- // Then: calculate message X offset
- calculateXoffset();
- })
-
- return {
- textFill,
- bubbleFill,
- startingOffset
- };
-}
+++ /dev/null
-import useRendererAnims from "./useRendererAnims";
-import useOffsetCalculator from './useOffsetCalculator';
-export default function useMessageRenderer() {
-
- const { calculateMessageOffsets } = useOffsetCalculator();
- const { shiftAnimate, stackAnimate } = useRendererAnims();
-
- let messageList: NodeList;
- let message: HTMLElement | null;
- let messageOffset = { x: 0, y: 0 };
- let shift = false;
- let shiftValue = 0;
-
- function animateMessage(el: SVGGElement): void {
- const transform = {
- initialTranslate: `translate(${messageOffset.x} ${messageOffset.y})`,
- finalTranslate: `translate(${messageOffset.x} ${messageOffset.y})`
- }
- shift ? shiftAnimate(el, transform, shiftValue, messageList) : stackAnimate(el, transform);
- }
-
- function beforeEnter(el: SVGGElement): void {
- const queryResult = document.querySelectorAll(".messageList");
- if (queryResult.length) messageList = queryResult;
- }
-
- function enter(el: SVGGElement): void {
- message = document.getElementById(el.id);
- }
-
- function afterEnter(el: SVGGElement) {
- const messagePadding = 15;
-
- if (message) {
- messageOffset = calculateMessageOffsets(message);
- shift = messageOffset.y > 445 ? true : false;
- }
- if (shift) {
- shiftValue -= el.getBBox().height + messagePadding;
- }
- animateMessage(el);
- }
-
- return {
- beforeEnter,
- enter,
- afterEnter
- };
-}
--- /dev/null
+export default function useStreamRecord(): {
+ initRecorder: (stream: MediaStream) => MediaRecorder;
+} {
+ let streamRecorder: MediaRecorder;
+ const chunks: Blob[] = [];
+
+ const initRecorder = (stream: MediaStream) => {
+ streamRecorder = new MediaRecorder(stream);
+ streamRecorder.ondataavailable = (e: any) => { chunks.push(e.data) }
+ streamRecorder.onstop = (e: any) => {
+ // send audio to BE
+ const audioBlob = new Blob(chunks, { 'type': 'audio/ogg; codecs=opus' });
+ }
+ return streamRecorder
+ }
+
+ return {
+ initRecorder,
+ }
+}
import {Ref} from '@/types/vueRef/index';
-import { ref } from 'vue';
-export default function useStreamRecorder(render: Ref<boolean>) {
- let streamRecorder: MediaRecorder;
- const chunks: Blob[] = [];
+export default function useStreamRender(render: Ref<boolean>) {
// window.AudioContext = window.AudioContext;
const audioContext = new AudioContext();
const analyzer = audioContext.createAnalyser();
analyzer.fftSize = 2048;
+ const xInitial = 24;
+ const xFinal = 640;
const dataArray = new Uint8Array(analyzer.frequencyBinCount);
- const initRecorder = async () => {
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
- streamRecorder = new MediaRecorder(stream);
- streamRecorder.ondataavailable = (e: any) => { chunks.push(e.data) }
- streamRecorder.onstop = (e: any) => {
- // send audio to BE
- const audioBlob = new Blob(chunks, { 'type': 'audio/ogg; codecs=opus' });
- }
- return {
- streamRecorder
- }
- }
-
interface AmplitudePath {
id: string;
name: string;
return path;
}
- const xInitial = 24;
- const xFinal = 640;
function visualize(w: Ref<number>, p: Ref<number>, paths: Ref<any>) {
}
return {
- initRecorder,
renderStream
}
}
import AnimeFunc from "@/types/animejs/index";
import {inject} from "vue";
import {Ref} from "@/types/vueRef/index";
-export default function useInputAnims() {
+/*
+ * Anime js animations used by input textController
+*/
+export default function useTextInputAnims() {
// imports animejs safely
let anime: AnimeFunc;
-import {ref, computed, watch, inject} from 'vue';
-import useInputAnims from "./useInputAnims";
-import useInputRenderer from './useInputRenderer';
-export default function useTextInput(input: any, audioRender: any) {
+import {ref, computed, inject} from 'vue';
+import useTextInputAnims from "./textDetails/useAnimations";
+import { Ref } from "@/types/vueRef/index";
+/*
+ * text rendering logic used by inputController
+*/
+export default function useTextRender(input: Ref<string>) {
const emitter: any = inject("mitt");
- const {animateSend, verticalShiftInput, inputAppear} = useInputAnims();
+ const {animateSend, verticalShiftInput, inputAppear} = useTextInputAnims();
const inputHeight = ref(42);
const inputWidth = ref(0);
--- /dev/null
+import {ref, watch, onUnmounted, onMounted } from "vue";
+import useKeyDownHandler from "@/utils/keyDownHandler/useKeyDownHandler";
+import useMediaStream from "@/utils/mediaStream/useMediaStream";
+import useStreamRecord from "./audio/useStreamRecord";
+import useStreamRender from './audio/useStreamRender';
+import useTextRender from './text/useTextRender';
+
+/*
+* Input controller logic
+* Will render text input by default as user types or
+* if user holds space bar down, audio media-stream
+* will be rendered & recorded
+*/
+export default function useInputController() {
+ // program ref varaiables
+ const renderAudio = ref(false);
+ const reactiveWidth = ref(10);
+ const pathOffset = ref(10);
+ const paths = ref([]);
+ // audioStream variables
+ let stream: MediaStream;
+ let recorder: MediaRecorder;
+
+ // use input ref & keydown handler function
+ const { keyDownHandler, input } = useKeyDownHandler();
+ // use text Offset computed prop and & textRender function
+ const { textInputXoffset, renderTextInput } = useTextRender(input);
+ // use stream render and record logic
+ const { renderStream } = useStreamRender(renderAudio);
+ const { initRecorder } = useStreamRecord()
+
+ // get stream & initialize recorder object on mount
+ onMounted(async () => {
+ stream = await useMediaStream();
+ recorder = initRecorder(stream);
+ })
+
+ // callback function called on key up event
+ function keyupHandler(e: any) {
+ // stop stream recording on space key up
+ if (e.key === " " && renderAudio.value) {
+ console.log("stop recording");
+ recorder.stop();
+ // reset controller ref variables
+ renderAudio.value = false;
+ input.value = "";
+ reactiveWidth.value = 10;
+ pathOffset.value = 10;
+ }
+ }
+
+ // step 1: add window event keyboard listeners
+ window.addEventListener("keydown", keyDownHandler);
+ window.addEventListener('keyup', keyupHandler);
+
+ // step 2: determine whether to render text or audio based one the keyboard input
+ watch(input, (input, prevInput) => {
+ if (prevInput !== "" || prevInput.length > 0) {
+ if (!renderAudio.value) {
+ renderTextInput();
+ }
+ return;
+ }
+ if (input === " " && renderAudio.value === false) {
+ console.log("record");
+ renderAudio.value = true;
+ recorder.start();
+ renderStream(stream, reactiveWidth, pathOffset, paths);
+ return;
+ }
+ renderTextInput();
+ });
+
+ // remove Event Listeners on component unMount
+ onUnmounted(() => {
+ window.removeEventListener("keydown", keyDownHandler);
+ window.removeEventListener("keyup", keyupHandler);
+ });
+
+ return {
+ input,
+ renderAudio,
+ reactiveWidth,
+ textInputXoffset,
+ paths
+ }
+
+}
\ No newline at end of file
import {ref, computed, onMounted} from "vue";
export default function useScrollView({ targetId }: {targetId: string}) {
-
const scroll = ref(0);
let scrollTarget: HTMLElement | SVGElement | null;
const targetHeight = ref(0);
--- /dev/null
+import { ref, onMounted } from "vue";
+import { Ref } from "@/types/vueRef/index";
+export default function useMessageItemAnims({
+ mr,
+ tr,
+}: {
+ mr: Ref<string>;
+ tr: Ref<string>;
+}) {
+ const signatureTranslate = ref("");
+ let messageRect: SVGSVGElement & SVGRectElement;
+ let messageText: SVGSVGElement & SVGTextElement;
+
+ onMounted(async () => {
+ messageRect = (mr.value as unknown) as SVGSVGElement & SVGRectElement;
+ messageText = (tr.value as unknown) as SVGSVGElement & SVGTextElement;
+ });
+
+ function adjustMessageRect(): void {
+ // must get dimensions of text box to fit bubble around
+ const padding = 12.5;
+ const textBox = messageText.getBBox();
+ messageRect.setAttribute("x", String(textBox.x - padding));
+ messageRect.setAttribute("y", String(textBox.y - padding));
+ messageRect.setAttribute("width", String(textBox.width + 2 * padding));
+ messageRect.setAttribute("height", String(textBox.height + 2 * padding));
+ }
+
+ function translateMsgSignature(): void {
+ if (messageRect.getAttribute("height") !== null) {
+ const height = Number(messageRect.getAttribute("height"));
+ signatureTranslate.value = `translate(-10 ${height})`;
+ }
+ }
+
+ async function drawMessage() {
+ // ignore lint error: await needed for proper render
+ await adjustMessageRect();
+ translateMsgSignature();
+ }
+
+ return {
+ drawMessage,
+ signatureTranslate,
+ };
+}
--- /dev/null
+import { computed, onMounted, ref } from "vue";
+export default function useMessageItemComp(m: string, id: string) {
+ const x = ref(0)
+ const paddingLeft = 30;
+ const winW = 350;
+ const messageWidth = ref(0);
+ const messageMarginTop = 75;
+
+ const textFill = computed(() => {
+ switch (m) {
+ case "sf":
+ return "#fff";
+ default:
+ return "#000";
+ }
+ });
+
+ const bubbleFill = computed(() => {
+ switch (m) {
+ case "sf":
+ return "#61b4f4";
+ default:
+ return "#fff";
+ }
+ });
+
+ const setMessageWidthRef = async () => {
+ const msg = await document.getElementById(id);
+ if (msg) {
+ messageWidth.value = msg.getBoundingClientRect().width;
+ }
+ }
+
+ const selfMessageMarginRight = 2.5;
+ const calculateXoffset = async () => {
+ switch (m) {
+ case "sf":
+ x.value = winW - messageWidth.value - selfMessageMarginRight;
+ break;
+ default:
+ x.value = paddingLeft;
+ // to center a message
+ // x = messageMargin + (winW -messageWidth) / 2;
+ }
+ }
+
+ const startingOffset = computed(() => {
+ const messageRenderer = document.getElementById('messageRenderer');
+ if (messageRenderer) {
+ const rect = messageRenderer.getBoundingClientRect();
+ let Y = 600;
+ if (rect.height > 500) {
+ Y = rect.height + messageMarginTop;
+ }
+ return `translate(${x.value} ${Y})`;
+ }
+ });
+
+ onMounted(async () => {
+ // to calculate X offset:
+ // First: set message width ref
+ await setMessageWidthRef();
+ // Then: calculate message X offset
+ calculateXoffset();
+ })
+
+ return {
+ textFill,
+ bubbleFill,
+ startingOffset
+ };
+}
import { inject } from "vue";
import AnimeFunc from "@/types/animejs/index";
import { MessageAnimeFunc } from '@/types/message/index';
-export default function useRendererAnims() {
+
+/**
+ * anime js animations used by message renderer
+ */
+export default function useRendererAnims(): {
+ shiftAnimate: MessageAnimeFunc;
+ stackAnimate: MessageAnimeFunc;
+} {
// imports animejs safely
let anime: AnimeFunc;
const animeInject: AnimeFunc | undefined = inject("animejs");
if (animeInject) anime = animeInject;
+ // shifts message list by a given value
function shiftList({ targetList, sv }: { targetList: NodeList | undefined; sv: number | undefined }) {
if (targetList) {
for (const element of targetList) {
}
}
+ // adds new message at the bottom of the list and shifts existing message list
const shiftAnimate: MessageAnimeFunc = (el, transform, shiftValue, targetList) => {
anime({
- targets: el,
+ targets: el,
transform: transform.initialTranslate,
easing: "easeInOutQuad",
duration: 500,
});
}
+ // stacks message from top to bottom
const stackAnimate: MessageAnimeFunc = (el, transform) => {
anime({
targets: el,
import { ref } from "vue";
+/**
+ * Calculates vertical and horizontal message offsets
+ */
export default function useOffsetCalculator() {
const xOffset = ref(0);
const yOffset = ref(0);
let previousMessageHeight = 0;
const messagePadding = 15;
- function setXoffset(message: HTMLElement): void {
- const computedQuery = window.getComputedStyle(message)
+ function calculateX(message: HTMLElement): void {
+ const computedQuery = window.getComputedStyle(message);
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
xOffset.value = matrix.m41;
}
- function setYoffset(messageHeight: number): void {
+ function calculateY(messageHeight: number): void {
if (previousMessageHeight === 0) {
yOffset.value = firstChildMarginTop;
} else {
previousMessageHeight = messageHeight;
}
- const calculateMessageOffsets = (msg: HTMLElement) => {
+ const calculateMessageOffsets = (msg: HTMLElement): { x: number; y: number } => {
const msgRect = msg.getBoundingClientRect();
- setXoffset(msg);
- setYoffset(msgRect.height);
+ calculateX(msg);
+ calculateY(msgRect.height);
const offsets = {
x: xOffset.value as number,
y: yOffset.value as number
--- /dev/null
+import useRendererAnims from "./renderDetails/useAnimations";
+import useOffsetCalculator from './renderDetails/useOffsetCalculator';
+import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
+
+
+/**
+ * renderView component logic
+ * handles rendering of exisiting user messages
+ * as well as incoming messages
+ */
+export default function useRenderView(): {
+ beforeEnter: VueTransitionCallback;
+ enter: VueTransitionCallback;
+ afterEnter: VueTransitionCallback;
+} {
+ // uses offsetCalculator logic and renderer animations
+ const { calculateMessageOffsets } = useOffsetCalculator();
+ const { shiftAnimate, stackAnimate } = useRendererAnims();
+
+ const messagePadding = 15;
+ let messageList: NodeList;
+ let message: HTMLElement | null;
+ let messageOffset = { x: 0, y: 0 };
+ let shift = false;
+ let shiftOffset = 0;
+
+
+ // animates message item to 'enter' renderView
+ const animateMessage = (el: SVGGElement) => {
+
+ const transform = {
+ initialTranslate: `translate(${messageOffset.x} ${messageOffset.y})`,
+ finalTranslate: `translate(${messageOffset.x} ${messageOffset.y})`
+ }
+ shift ? shiftAnimate(el, transform, shiftOffset, messageList) : stackAnimate(el, transform);
+ }
+
+ // callback called before message enter
+ const beforeEnter: VueTransitionCallback = () => {
+ const queryResult = document.querySelectorAll(".messageList");
+ if (queryResult.length) messageList = queryResult;
+ }
+
+ // callback called on message enter
+ const enter: VueTransitionCallback = (el) => {
+ // query DOM for latest message as soon as it enters
+ if (el) {
+ message = document.getElementById(el.id);
+ }
+ }
+
+ // callback called after message enter
+ const afterEnter: VueTransitionCallback = (el) => {
+ // get message positioning & shift state before animating
+ if (message) {
+ messageOffset = calculateMessageOffsets(message);
+ shift = messageOffset.y > 445 ? true : false;
+ }
+ if (el) {
+ if (shift) {
+ shiftOffset -= el.getBBox().height + messagePadding;
+ }
+ animateMessage(el);
+ }
+ }
+
+ return {
+ beforeEnter,
+ enter,
+ afterEnter
+ };
+}
--- /dev/null
+export type VueTransitionCallback = {
+ (el?: SVGGElement | null): void;
+}
import { ref, inject } from "vue";
-import keyboardNameMap from "./keyboardMaps/keyboardNameMap";
-import keyboardCharMap from "./keyboardMaps/keyboardCharMap";
+import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
+import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
// This catches keys from a physical keyboard, a soft keyboard on a tablet, or a
// scanner attached via USB
export default function useKeyDownHandler() {
keyDownHandler,
input
};
-}
+}
\ No newline at end of file
export default async function useMediaStream() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
- return {
- stream
- }
+ return stream;
}
\ No newline at end of file