Skip to content

Commit

Permalink
webui: add cam selection for avatar snapshot (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
sreimers authored Jan 5, 2025
1 parent b6100a8 commit c18123c
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 37 deletions.
44 changes: 33 additions & 11 deletions webui/src/components/WebcamPhoto.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
<template>
<div>
<video ref="video" :class="{ hidden: webcam.preview.value }" playsinline autoplay muted />
<canvas ref="picture" :class="{ hidden: !webcam.preview.value || webcam.picture.value }" class="w-full" />
<img v-if="webcam.picture.value" :src="webcam.picture.value" class="mx-auto rounded-full h-48" />
<video ref="video" :class="{ hidden: Webcam.preview.value }" playsinline autoplay muted />
<canvas ref="picture" :class="{ hidden: !Webcam.preview.value || Webcam.picture.value }" class="w-full" />
<img v-if="Webcam.picture.value" :src="Webcam.picture.value" class="mx-auto rounded-full h-48" />
</div>
<button
v-if="!webcam.preview.value"
v-if="!Webcam.preview.value"
class="flex w-full justify-center rounded-md border border-transparent bg-green-700 py-2 px-4 text-sm font-bold text-white shadow-sm hover:bg-geen-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="preview()"
>
Snapshot
</button>
<div v-if="!Webcam.preview.value">
<label for="camera" class="block text-sm font-medium text-gray-700">Cam</label>
<select
id="cam"
v-model="video_input_id"
name="cam"
class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
>
<template v-for="item in Webcam.deviceInfos.value">
<option v-if="item.kind === 'videoinput'" :key="item.deviceId" :value="item.deviceId">
{{ item.label }}
</option>
</template>
</select>
</div>
<button
v-if="webcam.preview.value && !webcam.picture.value"
v-if="Webcam.preview.value && !Webcam.picture.value"
class="flex w-full justify-center rounded-md border border-transparent bg-green-700 py-2 px-4 text-sm font-bold text-white shadow-sm hover:bg-green-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="webcam.savePicture()"
@click="Webcam.savePicture()"
>
Save Avatar
</button>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import webcam from '../webcam'
import { ref, onMounted, watch } from 'vue'
import Webcam from '../webcam'
const video = ref(null)
const picture = ref(null)
const video_input_id = Webcam.deviceId
function preview() {
webcam.takePicture(picture.value, video.value)
webcam.stop()
Webcam.takePicture(picture.value, video.value)
Webcam.stop()
}
watch(video_input_id, async (newValue: any, oldValue: any) => {
if (oldValue === undefined) return //prevent first auto change
Webcam.stop()
Webcam.start()
})
onMounted(() => {
webcam.video(video.value)
Webcam.video(video.value)
})
</script>
59 changes: 33 additions & 26 deletions webui/src/webcam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,56 @@ let hvideo: HTMLVideoElement | null
function getRoundedCanvas(sourceCanvas: HTMLCanvasElement | undefined) {
if (!sourceCanvas)
return
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = sourceCanvas.width;
const height = sourceCanvas.height;
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const width = sourceCanvas.width
const height = sourceCanvas.height

canvas.width = width;
canvas.height = height;
canvas.width = width
canvas.height = height
if (ctx) {
ctx.imageSmoothingEnabled = true;
ctx.drawImage(sourceCanvas, 0, 0, width, height);
ctx.globalCompositeOperation = 'destination-in';
ctx.beginPath();
ctx.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
ctx.fill();
ctx.imageSmoothingEnabled = true
ctx.drawImage(sourceCanvas, 0, 0, width, height)
ctx.globalCompositeOperation = 'destination-in'
ctx.beginPath()
ctx.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)
ctx.fill()
}
return canvas;
}
const constraintsVideo: any = {
audio: false,
video: {
deviceId: undefined,
},
}

export default {
picture: ref<string | undefined>(),
preview: ref(false),
deviceInfos: ref<MediaDeviceInfo[] | undefined>([]),
deviceId: ref<string | undefined>(undefined),

video(video: HTMLVideoElement | null) {
hvideo = video
},

start() {
navigator.mediaDevices
.getUserMedia({
video: true,
audio: false,
})
.then((stream) => {
videoStream = stream
if (hvideo) hvideo.srcObject = stream
hvideo?.play()
})
.catch((err) => {
console.error(`An error occurred: ${err}`)
})
async start() {
try {
constraintsVideo.video.deviceId = this.deviceId.value
videoStream = await navigator.mediaDevices.getUserMedia(constraintsVideo)
} catch (e) {
console.error(`An error occurred: ${e}`)
}
this.deviceId.value = videoStream?.getVideoTracks()[0].getSettings().deviceId
if (hvideo && videoStream) {
hvideo.srcObject = videoStream
hvideo.play()
}

this.picture.value = undefined
this.preview.value = false
this.deviceInfos.value = await navigator.mediaDevices.enumerateDevices()
},

stop() {
Expand Down

0 comments on commit c18123c

Please sign in to comment.