diff options
Diffstat (limited to 'src/tools/camera-recorder/camera-recorder.vue')
-rw-r--r-- | src/tools/camera-recorder/camera-recorder.vue | 225 |
1 files changed, 118 insertions, 107 deletions
diff --git a/src/tools/camera-recorder/camera-recorder.vue b/src/tools/camera-recorder/camera-recorder.vue index 81fec42..19fe30b 100644 --- a/src/tools/camera-recorder/camera-recorder.vue +++ b/src/tools/camera-recorder/camera-recorder.vue @@ -1,6 +1,110 @@ +<script setup lang="ts"> +import _ from 'lodash'; + +import { useMediaRecorder } from './useMediaRecorder'; + +interface Media { type: 'image' | 'video'; value: string; createdAt: Date } + +const { + videoInputs: cameras, + audioInputs: microphones, + permissionGranted, + isSupported, + ensurePermissions, +} = useDevicesList({ + requestPermissions: true, + constraints: { video: true, audio: true }, + onUpdated() { + refreshCurrentDevices(); + }, +}); + +const video = ref<HTMLVideoElement>(); +const medias = ref<Media[]>([]); +const currentCamera = ref(cameras.value[0]?.deviceId); +const currentMicrophone = ref(microphones.value[0]?.deviceId); +const permissionCannotBePrompted = ref(false); + +const { + stream, + start, + enabled: isMediaStreamAvailable, +} = useUserMedia({ + constraints: computed(() => ({ + video: { deviceId: currentCamera.value }, + ...(currentMicrophone.value ? { audio: { deviceId: currentMicrophone.value } } : {}), + })), + autoSwitch: true, +}); + +const { + isRecordingSupported, + onRecordAvailable, + startRecording, + stopRecording, + pauseRecording, + recordingState, + resumeRecording, +} = useMediaRecorder({ + stream, +}); + +onRecordAvailable((value) => { + medias.value.unshift({ type: 'video', value, createdAt: new Date() }); +}); + +function refreshCurrentDevices() { + if (_.isNil(currentCamera) || !cameras.value.find(i => i.deviceId === currentCamera.value)) { + currentCamera.value = cameras.value[0]?.deviceId; + } + + if (_.isNil(microphones) || !microphones.value.find(i => i.deviceId === currentMicrophone.value)) { + currentMicrophone.value = microphones.value[0]?.deviceId; + } +} + +function takeScreenshot() { + if (!video.value) { + return; + } + + const canvas = document.createElement('canvas'); + canvas.width = video.value.videoWidth; + canvas.height = video.value.videoHeight; + canvas.getContext('2d')?.drawImage(video.value, 0, 0); + const image = canvas.toDataURL('image/png'); + + medias.value.unshift({ type: 'image', value: image, createdAt: new Date() }); +} + +watchEffect(() => { + if (video.value && stream.value) { + video.value.srcObject = stream.value; + } +}); + +async function requestPermissions() { + try { + await ensurePermissions(); + } + catch (e) { + permissionCannotBePrompted.value = true; + } +} + +function downloadMedia({ type, value, createdAt }: Media) { + const link = document.createElement('a'); + link.href = value; + link.download = `${type}-${createdAt.getTime()}.${type === 'image' ? 'png' : 'webm'}`; + link.click(); +} +</script> + <template> <div> - <c-card v-if="!isSupported"> Your browser does not support recording video from camera </c-card> + <c-card v-if="!isSupported"> + Your browser does not support recording video from camera + </c-card> <c-card v-else-if="!permissionGranted" text-center> You need to grant permission to use your camera and microphone @@ -11,7 +115,9 @@ </c-alert> <div v-else mt-4 flex justify-center> - <c-button @click="requestPermissions">Grant permission</c-button> + <c-button @click="requestPermissions"> + Grant permission + </c-button> </div> </c-card> @@ -36,7 +142,9 @@ </div> <div v-if="!isMediaStreamAvailable" mt-3 flex justify-center> - <c-button type="primary" @click="start">Start webcam</c-button> + <c-button type="primary" @click="start"> + Start webcam + </c-button> </div> <div v-else> @@ -71,19 +179,23 @@ Stop </c-button> </div> - <div v-else italic op-60>Video recording is not supported in your browser</div> + <div v-else italic op-60> + Video recording is not supported in your browser + </div> </div> </div> </c-card> <div grid grid-cols-2 mt-5 gap-2> <c-card v-for="({ type, value, createdAt }, index) in medias" :key="index"> - <img v-if="type === 'image'" :src="value" max-h-full w-full alt="screenshot" /> + <img v-if="type === 'image'" :src="value" max-h-full w-full alt="screenshot"> <video v-else :src="value" controls max-h-full w-full /> <div flex items-center justify-between> - <div font-bold>{{ type === 'image' ? 'Screenshot' : 'Video' }}</div> + <div font-bold> + {{ type === 'image' ? 'Screenshot' : 'Video' }} + </div> <div flex gap-2> <c-button @click="downloadMedia({ type, value, createdAt })"> @@ -99,104 +211,3 @@ </div> </div> </template> - -<script setup lang="ts"> -import _ from 'lodash'; - -import { useMediaRecorder } from './useMediaRecorder'; - -type Media = { type: 'image' | 'video'; value: string; createdAt: Date }; - -const { - videoInputs: cameras, - audioInputs: microphones, - permissionGranted, - isSupported, - ensurePermissions, -} = useDevicesList({ - requestPermissions: true, - constraints: { video: true, audio: true }, - onUpdated() { - refreshCurrentDevices(); - }, -}); - -const video = ref<HTMLVideoElement>(); -const medias = ref<Media[]>([]); -const currentCamera = ref(cameras.value[0]?.deviceId); -const currentMicrophone = ref(microphones.value[0]?.deviceId); -const permissionCannotBePrompted = ref(false); - -const { - stream, - start, - enabled: isMediaStreamAvailable, -} = useUserMedia({ - constraints: computed(() => ({ - video: { deviceId: currentCamera.value }, - ...(currentMicrophone.value ? { audio: { deviceId: currentMicrophone.value } } : {}), - })), - autoSwitch: true, -}); - -const { - isRecordingSupported, - onRecordAvailable, - startRecording, - stopRecording, - pauseRecording, - recordingState, - resumeRecording, -} = useMediaRecorder({ - stream, -}); - -onRecordAvailable((value) => { - medias.value.unshift({ type: 'video', value, createdAt: new Date() }); -}); - -function refreshCurrentDevices() { - console.log('refreshCurrentDevices'); - - if (_.isNil(currentCamera) || !cameras.value.find((i) => i.deviceId === currentCamera.value)) { - currentCamera.value = cameras.value[0]?.deviceId; - } - - if (_.isNil(microphones) || !microphones.value.find((i) => i.deviceId === currentMicrophone.value)) { - currentMicrophone.value = microphones.value[0]?.deviceId; - } -} - -function takeScreenshot() { - if (!video.value) return; - - const canvas = document.createElement('canvas'); - canvas.width = video.value.videoWidth; - canvas.height = video.value.videoHeight; - canvas.getContext('2d')?.drawImage(video.value, 0, 0); - const image = canvas.toDataURL('image/png'); - - medias.value.unshift({ type: 'image', value: image, createdAt: new Date() }); -} - -watchEffect(() => { - if (video.value && stream.value) video.value.srcObject = stream.value; -}); - -async function requestPermissions() { - try { - await ensurePermissions(); - } catch (e) { - permissionCannotBePrompted.value = true; - } -} - -function downloadMedia({ type, value, createdAt }: Media) { - const link = document.createElement('a'); - link.href = value; - link.download = `${type}-${createdAt.getTime()}.${type === 'image' ? 'png' : 'webm'}`; - link.click(); -} -</script> - -<style lang="less" scoped></style> |