webcam getCapabilities not supported on firefox

This commit is contained in:
j3d1 2024-02-29 22:50:29 +01:00
parent c7c91f87ba
commit 751f3deaab

View file

@ -1,71 +1,72 @@
<template> <template>
<div class="d-inline-block"> <div class="d-inline-block">
<label @click="open"> <label @click="open">
<slot></slot> <slot></slot>
</label> </label>
<div class="modal" :class="{'d-block': show_modal}" tabindex="-1"> <div class="modal" :class="{'d-block': show_modal}" tabindex="-1">
<div class="modal-dialog modal-fullscreen"> <div class="modal-dialog modal-fullscreen">
<div class="modal-content p-2"> <div class="modal-content p-2">
<button type="button" class="btn-close position-absolute fixed-top m-2" style="right: 1rem; left: auto;" <button type="button" class="btn-close position-absolute fixed-top m-2"
@click="close"></button> style="right: 1rem; left: auto;"
<div v-if="error" class="alert alert-danger" role="alert"> @click="close"></button>
{{ lastError }} <div v-if="error" class="alert alert-danger" role="alert">
</div> {{ lastError }}
<div class="row" v-if="capturing && !streaming"> </div>
<div class="spinner-grow text-danger mx-auto" role="status"> <div class="row" v-if="capturing && !streaming">
<span class="sr-only">Loading...</span> <div class="spinner-grow text-danger mx-auto" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<img
v-if="!capturing && dataImage"
class="img-fluid rounded mx-auto d-block w-75"
:src="dataImage"
alt="Image not available."
/>
<video
ref="video"
class="img-fluid rounded d-block img-preview w-75 mx-auto"
:class="{'d-none': !capturing}"
>
Video stream not available.
</video>
<canvas ref="canvas" class="img-fluid d-none img-preview"/>
<div class="position-absolute fixed-bottom text-center">
<div class="btn-group shadow">
<button
class="btn btn-success"
:class="{'disabled': dataImage}"
@click="(!dataImage) && captureVideoImage()">
<b-icon-camera></b-icon-camera>&nbsp;Capture
</button>
<button
class="btn btn-danger"
:class="{'disabled': !dataImage}"
@click="(dataImage) && retake()"
>
<b-icon-trash></b-icon-trash>&nbsp;Retake
</button>
<button
class="btn btn-primary"
:class="{'disabled': !dataImage}"
@click="(dataImage) && save()"
>
<b-icon-save></b-icon-save>&nbsp;Save
</button>
</div>
<select v-model="selectedCamera" v-on:change="onUserSelect"
class="form-select position-relative w-50 mx-auto mb-5 mt-2 shadow"
aria-label="Select Camera Source">
<option disabled value="">Select Camera Source</option>
<option v-for="camera in availableCameras" :key="camera.deviceId" :value="camera">
{{ camera.label }}
</option>
</select>
</div>
</div>
</div> </div>
</div>
<img
v-if="!capturing && dataImage"
class="img-fluid rounded mx-auto d-block w-75"
:src="dataImage"
alt="Image not available."
/>
<video
ref="video"
class="img-fluid rounded d-block img-preview w-75 mx-auto"
:class="{'d-none': !capturing}"
>
Video stream not available.
</video>
<canvas ref="canvas" class="img-fluid d-none img-preview"/>
<div class="position-absolute fixed-bottom text-center">
<div class="btn-group shadow">
<button
class="btn btn-success"
:class="{'disabled': dataImage}"
@click="(!dataImage) && captureVideoImage()">
<b-icon-camera></b-icon-camera>&nbsp;Capture
</button>
<button
class="btn btn-danger"
:class="{'disabled': !dataImage}"
@click="(dataImage) && retake()"
>
<b-icon-trash></b-icon-trash>&nbsp;Retake
</button>
<button
class="btn btn-primary"
:class="{'disabled': !dataImage}"
@click="(dataImage) && save()"
>
<b-icon-save></b-icon-save>&nbsp;Save
</button>
</div>
<select v-model="selectedCamera" v-on:change="onUserSelect"
class="form-select position-relative w-50 mx-auto mb-5 mt-2 shadow"
aria-label="Select Camera Source">
<option disabled value="">Select Camera Source</option>
<option v-for="camera in availableCameras" :key="camera.deviceId" :value="camera">
{{ camera.label }}
</option>
</select>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
@ -74,123 +75,127 @@
<script> <script>
export default { export default {
name: "WebcamFileSource", name: "WebcamFileSource",
data: () => ({ data: () => ({
lastError: undefined, lastError: undefined,
error: false, error: false,
show_modal: false, show_modal: false,
availableCameras: [], availableCameras: [],
selectedCamera: undefined, selectedCamera: undefined,
capturing: false, capturing: false,
streaming: false, streaming: false,
stream: undefined, stream: undefined,
dataImage: undefined dataImage: undefined
}), }),
methods: { methods: {
async attemptGetUserMedia(constraints) { async attemptGetUserMedia(constraints) {
this.stream = await navigator.mediaDevices.getUserMedia({ this.stream = await navigator.mediaDevices.getUserMedia({
audio: false, audio: false,
video: constraints video: constraints
}).catch((error) => { }).catch((error) => {
console.error(error); console.error(error);
if (error.name === "NotAllowedError") this.lastError = "Camera Permission Not Granted"; if (error.name === "NotAllowedError") this.lastError = "Camera Permission Not Granted";
if (error.name === "NotReadableError") this.lastError = "Camera Hardware Error"; if (error.name === "NotReadableError") this.lastError = "Camera Hardware Error";
this.lastError = "Unknown Error" this.lastError = "Unknown Error"
}); });
if (this.stream) this.allowed = true; if (this.stream) this.allowed = true;
},
async assignStream() {
console.log(this.stream.getTracks()[0]);
const track = this.stream.getTracks()[0];
if (track.getCapabilities) {
const capabilities = this.stream.getTracks()[0].getCapabilities();
await this.attemptGetUserMedia({
deviceId: capabilities.deviceId,
width: {ideal: capabilities.width.max},
height: {ideal: capabilities.height.max}
});
}
this.capturing = true;
this.streaming = false;
const {video} = this.$refs;
video.srcObject = this.stream;
video.addEventListener('canplay', () => {
this.streaming = true;
localStorage.setItem("WebcamFileSource#previousDevice", this.selectedCamera.deviceId);
}, false);
await video.play();
},
closeStream() {
if (this.capturing) {
this.stream.getTracks().forEach(s => s.stop());
this.streaming = false;
this.stream = undefined;
}
},
async enumerateCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
this.availableCameras = devices.filter(device => device.kind === "videoinput");
},
async open() {
this.show_modal = true;
const previousDevice = localStorage.getItem("WebcamFileSource#previousDevice");
if (previousDevice) await this.attemptGetUserMedia({deviceId: previousDevice});
if (!this.stream) await this.attemptGetUserMedia({facingMode: "environment"});
if (!this.stream) await this.attemptGetUserMedia(true);
if (!this.stream) this.error = true;
await this.enumerateCameras();
this.selectedCamera = this.availableCameras.find(({deviceId}) => deviceId === this.stream.getTracks()[0].getSettings().deviceId);
await this.assignStream();
},
async onUserSelect() {
this.closeStream();
if (!this.dataImage) {
await this.attemptGetUserMedia({deviceId: this.selectedCamera.deviceId})
await this.assignStream();
}
},
async close() {
this.closeStream();
this.capturing = false;
this.show_modal = false;
this.dataImage = undefined;
},
captureVideoImage() {
const {video, canvas} = this.$refs;
const context = canvas.getContext('2d');
const {videoWidth, videoHeight} = video;
canvas.width = videoWidth;
canvas.height = videoHeight;
context.drawImage(video, 0, 0, videoWidth, videoHeight);
this.dataImage = canvas.toDataURL('image/jpeg', 0.5);
this.closeStream();
this.capturing = false;
},
retake() {
this.dataImage = undefined;
this.open();
},
save() {
const mimeType = this.dataImage.split(';')[0].split(':')[1];
const data = this.dataImage.split(',')[1];
const raw_data = atob(data);
const hash = nacl.crypto_hash(raw_data).reduce((a, b) => a + b.toString(16).padStart(2, "0"), "");
const image = {
name: hash.slice(0, 12) + ".jpg",
size: raw_data.length,
mime_type: mimeType,
data: data,
hash: hash,
};
this.$emit('input', [image]);
this.close();
}
}, },
async assignStream() { mounted() {
const capabilities = this.stream.getTracks()[0].getCapabilities(); navigator.mediaDevices.addEventListener("devicechange", (event) => {
await this.attemptGetUserMedia({ console.log("devicechange");
deviceId: capabilities.deviceId, this.enumerateCameras();
width: {exact: capabilities.width.max}, if (this.availableCameras.findIndex(({deviceId}) => deviceId === this.selectedCamera.deviceId) === -1) {
height: {exact: capabilities.height.max} this.closeStream();
}) this.open();
this.capturing = true; }
this.streaming = false; });
const {video} = this.$refs;
video.srcObject = this.stream;
video.addEventListener('canplay', () => {
this.streaming = true;
localStorage.setItem("WebcamFileSource#previousDevice", this.selectedCamera.deviceId);
}, false);
await video.play();
},
closeStream() {
if (this.capturing) {
this.stream.getTracks().forEach(s => s.stop());
this.streaming = false;
this.stream = undefined;
}
},
async enumerateCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
this.availableCameras = devices.filter(device => device.kind === "videoinput");
},
async open() {
this.show_modal = true;
const previousDevice = localStorage.getItem("WebcamFileSource#previousDevice");
if (previousDevice) await this.attemptGetUserMedia({deviceId: previousDevice});
if (!this.stream) await this.attemptGetUserMedia({facingMode: "environment"});
if (!this.stream) await this.attemptGetUserMedia(true);
if (!this.stream) this.error = true;
await this.enumerateCameras();
this.selectedCamera = this.availableCameras.find(({deviceId}) => deviceId === this.stream.getTracks()[0].getSettings().deviceId);
await this.assignStream();
},
async onUserSelect() {
this.closeStream();
if (!this.dataImage) {
await this.attemptGetUserMedia({deviceId: this.selectedCamera.deviceId})
await this.assignStream();
}
},
async close() {
this.closeStream();
this.capturing = false;
this.show_modal = false;
this.dataImage = undefined;
},
captureVideoImage() {
const {video, canvas} = this.$refs;
const context = canvas.getContext('2d');
const {videoWidth, videoHeight} = video;
canvas.width = videoWidth;
canvas.height = videoHeight;
context.drawImage(video, 0, 0, videoWidth, videoHeight);
this.dataImage = canvas.toDataURL('image/jpeg', 0.5);
this.closeStream();
this.capturing = false;
},
retake() {
this.dataImage = undefined;
this.open();
},
save() {
const mimeType = this.dataImage.split(';')[0].split(':')[1];
const data = this.dataImage.split(',')[1];
const raw_data = atob(data);
const hash = nacl.crypto_hash(raw_data).reduce((a, b) => a + b.toString(16).padStart(2, "0"), "");
const image = {
name: hash.slice(0, 12) + ".jpg",
size: raw_data.length,
mime_type: mimeType,
data: data,
hash: hash,
};
this.$emit('input', [image]);
this.close();
} }
},
mounted() {
navigator.mediaDevices.addEventListener("devicechange", (event) => {
console.log("devicechange");
this.enumerateCameras();
if (this.availableCameras.findIndex(({deviceId}) => deviceId === this.selectedCamera.deviceId) === -1) {
this.closeStream();
this.open();
}
});
}
} }
</script> </script>