aboutsummaryrefslogtreecommitdiff
path: root/src/tools/camera-recorder/camera-recorder.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/camera-recorder/camera-recorder.vue')
-rw-r--r--src/tools/camera-recorder/camera-recorder.vue225
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>