From 5150f9716e35b28f6e896408aed9a7444bd94a2d Mon Sep 17 00:00:00 2001 From: Braden Wong <13159333+braden-w@users.noreply.github.com> Date: Sun, 30 Jun 2024 15:40:12 -0700 Subject: [PATCH] major feat: default keep stream open, user can close it with cancel buton --- ...rService.ts => MediaRecorderService.svelte.ts} | 15 +++++++-------- apps/app/src/lib/stores/recorder.svelte.ts | 8 +++++--- apps/app/src/routes/(app)/+page.svelte | 14 +++++++++++++- 3 files changed, 25 insertions(+), 12 deletions(-) rename apps/app/src/lib/services/{MediaRecorderService.ts => MediaRecorderService.svelte.ts} (94%) diff --git a/apps/app/src/lib/services/MediaRecorderService.ts b/apps/app/src/lib/services/MediaRecorderService.svelte.ts similarity index 94% rename from apps/app/src/lib/services/MediaRecorderService.ts rename to apps/app/src/lib/services/MediaRecorderService.svelte.ts index f649cc9f..fea51449 100644 --- a/apps/app/src/lib/services/MediaRecorderService.ts +++ b/apps/app/src/lib/services/MediaRecorderService.svelte.ts @@ -5,7 +5,7 @@ import { Data, Effect, Either } from 'effect'; import { nanoid } from 'nanoid/non-secure'; import { ToastService } from './ToastService.js'; -const enumerateRecordingDevices = Effect.tryPromise({ +export const enumerateRecordingDevices = Effect.tryPromise({ try: async () => { const allAudioDevicesStream = await navigator.mediaDevices.getUserMedia({ audio: true }); const devices = await navigator.mediaDevices.enumerateDevices(); @@ -20,6 +20,7 @@ const enumerateRecordingDevices = Effect.tryPromise({ error: error, }), }); + class GetStreamError extends Data.TaggedError('GetStreamError')<{ recordingDeviceId: string; }> {} @@ -28,14 +29,12 @@ class TryResuseStreamError extends Data.TaggedError('TryResuseStreamError') {} export const MediaRecorderService = Effect.gen(function* () { const { toast } = yield* ToastService; - const mediaStreamService = yield* MediaStreamService; let mediaRecorder: MediaRecorder | null = null; const recordedChunks: Blob[] = []; const resetRecorder = () => { recordedChunks.length = 0; mediaRecorder = null; - mediaStreamService.destroy(); }; return { @@ -52,7 +51,7 @@ export const MediaRecorderService = Effect.gen(function* () { title: 'Connecting to audio input device...', description: 'Please allow access to your microphone if prompted.', }); - const maybeResusedStream = yield* mediaStreamService.init({ + const maybeResusedStream = yield* mediaStream.init({ shouldReuseStream: true, preferredRecordingDeviceId, }); @@ -84,7 +83,7 @@ export const MediaRecorderService = Effect.gen(function* () { title: 'Error initializing media recorder with preferred device', description: 'Trying to find another available audio input device...', }); - const stream = yield* mediaStreamService.init({ shouldReuseStream: false }); + const stream = yield* mediaStream.init({ shouldReuseStream: false }); return new AudioRecorder(stream, { mimeType: 'audio/webm;codecs=opus', sampleRate: 16000, @@ -137,8 +136,8 @@ export const MediaRecorderService = Effect.gen(function* () { }; }); -export const MediaStreamService = Effect.gen(function* () { - let internalStream: MediaStream | null = null; +export const mediaStream = Effect.gen(function* () { + let internalStream = $state(null); const getStreamForDeviceId = (recordingDeviceId: string) => Effect.tryPromise({ @@ -216,4 +215,4 @@ export const MediaStreamService = Effect.gen(function* () { }, enumerateRecordingDevices, }; -}); +}).pipe(Effect.runSync); diff --git a/apps/app/src/lib/stores/recorder.svelte.ts b/apps/app/src/lib/stores/recorder.svelte.ts index 6400bd5d..ba999b14 100644 --- a/apps/app/src/lib/stores/recorder.svelte.ts +++ b/apps/app/src/lib/stores/recorder.svelte.ts @@ -1,5 +1,8 @@ import { sendMessageToExtension } from '$lib/sendMessageToExtension'; -import { MediaRecorderService, MediaStreamService } from '$lib/services/MediaRecorderService'; +import { + MediaRecorderService, + enumerateRecordingDevices, +} from '$lib/services/MediaRecorderService.svelte'; import { NotificationServiceDesktopLive } from '$lib/services/NotificationServiceDesktopLive'; import { NotificationServiceWebLive } from '$lib/services/NotificationServiceWebLive'; import { SetTrayIconService } from '$lib/services/SetTrayIconService'; @@ -46,7 +49,6 @@ const IS_RECORDING_NOTIFICATION_ID = 'WHISPERING_RECORDING_NOTIFICATION'; export const recorder = Effect.gen(function* () { const mediaRecorderService = yield* MediaRecorderService; - const mediaStreamService = yield* MediaStreamService; const { notify } = yield* NotificationService; return { @@ -54,7 +56,7 @@ export const recorder = Effect.gen(function* () { return recorderState.value; }, enumerateRecordingDevices: () => - mediaStreamService.enumerateRecordingDevices.pipe( + enumerateRecordingDevices.pipe( Effect.catchAll((error) => { renderErrorAsToast(error); return Effect.succeed([] as MediaDeviceInfo[]); diff --git a/apps/app/src/routes/(app)/+page.svelte b/apps/app/src/routes/(app)/+page.svelte index 492650fa..7fa668cb 100644 --- a/apps/app/src/routes/(app)/+page.svelte +++ b/apps/app/src/routes/(app)/+page.svelte @@ -4,6 +4,7 @@ import { ClipboardIcon } from '$lib/components/icons'; import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; + import { mediaStream } from '$lib/services/MediaRecorderService.svelte'; import { recorder, recordings, settings } from '$lib/stores'; import { createRecordingViewTransitionName } from '$lib/utils/createRecordingViewTransitionName'; @@ -65,10 +66,21 @@ onclick={() => recorder.cancelRecording(settings)} variant="ghost" size="icon" - class="absolute -right-16 bottom-1.5 transform text-2xl hover:scale-110 focus:scale-110" + class="absolute -right-14 bottom-0 transform text-2xl hover:scale-110 focus:scale-110" > 🚫 + {:else if mediaStream.isStreamOpen} + + + 🔴 + {/if}