This commit is contained in:
j3d1 2026-08-23 00:56:03 +02:00
parent 5c3b7fc252
commit bbe52e4a78
9 changed files with 817 additions and 287 deletions

View file

@ -54,12 +54,13 @@
<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 }}
<select
v-if="availableCameras.length > 1"
v-model="selectedCameraId"
class="form-select position-relative w-50 mx-auto mb-5 mt-2 shadow"
aria-label="Select Camera Source">
<option v-for="camera in availableCameras" :key="camera.deviceId" :value="camera.deviceId">
{{ cleanCameraLabel(camera.label) || `Camera ${availableCameras.indexOf(camera) + 1}` }}
</option>
</select>
</div>
@ -74,6 +75,8 @@
</style>
<script>
import cameraManager from '@/cameraManager.js';
export default {
name: "WebcamFileSource",
data: () => ({
@ -81,80 +84,105 @@ export default {
error: false,
show_modal: false,
availableCameras: [],
selectedCamera: undefined,
localSelectedCameraId: null,
capturing: false,
streaming: false,
stream: undefined,
dataImage: undefined
}),
methods: {
async attemptGetUserMedia(constraints) {
this.stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: constraints
}).catch((error) => {
console.error(error);
if (error.name === "NotAllowedError") this.lastError = "Camera Permission Not Granted";
if (error.name === "NotReadableError") this.lastError = "Camera Hardware Error";
this.lastError = "Unknown Error"
});
if (this.stream) this.allowed = true;
emits: ['input'],
computed: {
selectedCameraId: {
get() {
return this.localSelectedCameraId || cameraManager.getSelectedCameraId();
},
set(value) {
if (value) {
this.localSelectedCameraId = value;
this.switchCamera(value);
}
},
},
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}
});
}
},
methods: {
setupVideoStream(stream) {
this.capturing = true;
this.streaming = false;
const {video} = this.$refs;
video.srcObject = this.stream;
video.addEventListener('canplay', () => {
const video = this.$refs.video;
if (!video) return;
video.srcObject = stream;
video.play();
video.oncanplay = () => {
this.streaming = true;
localStorage.setItem("WebcamFileSource#previousDevice", this.selectedCamera.deviceId);
}, false);
await video.play();
this.availableCameras = cameraManager.getAvailableCameras();
this.localSelectedCameraId = cameraManager.getSelectedCameraId();
};
},
async openStream(cameraId) {
this.error = false;
this.lastError = undefined;
try {
const stream = await cameraManager.openStream(cameraId);
this.setupVideoStream(stream);
} catch (err) {
console.error('Failed to open camera stream:', err);
if (err.name === "NotAllowedError") this.lastError = "Camera Permission Not Granted";
else if (err.name === "NotReadableError") this.lastError = "Camera Hardware Error";
else this.lastError = "Unknown Error";
this.error = true;
this.capturing = false;
}
},
async switchCamera(cameraId) {
if (!this.capturing) return;
this.streaming = false;
try {
const stream = await cameraManager.switchCamera(cameraId);
if (stream) this.setupVideoStream(stream);
} catch (err) {
console.error('Failed to switch camera:', err);
}
},
closeStream() {
if (this.capturing) {
this.stream.getTracks().forEach(s => s.stop());
cameraManager.closeStream();
this.capturing = false;
this.streaming = false;
this.stream = undefined;
}
},
async enumerateCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
this.availableCameras = devices.filter(device => device.kind === "videoinput");
cleanCameraLabel(label) {
if (!label) return '';
const parts = label.split(':').map((p) => p.trim());
if (parts.length === 2 && parts[0] === parts[1]) {
return parts[0];
}
return label;
},
handleCameraDisconnected(event) {
console.error('Camera disconnected:', event.detail);
this.streaming = false;
this.error = true;
this.lastError = 'Camera Disconnected';
},
handleCameraReconnected() {
const stream = cameraManager.getActiveStream();
if (stream) {
this.error = false;
this.lastError = undefined;
this.setupVideoStream(stream);
}
},
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();
}
await this.openStream();
},
async close() {
this.closeStream();
this.capturing = false;
this.show_modal = false;
this.dataImage = undefined;
this.error = false;
this.lastError = undefined;
},
captureVideoImage() {
const {video, canvas} = this.$refs;
@ -165,11 +193,14 @@ export default {
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();
this.openStream();
},
async refreshCameraList() {
await cameraManager.enumerateDevices();
this.availableCameras = cameraManager.getAvailableCameras();
},
async save() {
const mimeType = this.dataImage.split(';')[0].split(':')[1];
@ -194,14 +225,15 @@ export default {
}
},
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();
}
});
}
window.addEventListener('camera-disconnected', this.handleCameraDisconnected);
window.addEventListener('camera-reconnected', this.handleCameraReconnected);
navigator.mediaDevices?.addEventListener('devicechange', this.refreshCameraList);
},
beforeUnmount() {
window.removeEventListener('camera-disconnected', this.handleCameraDisconnected);
window.removeEventListener('camera-reconnected', this.handleCameraReconnected);
navigator.mediaDevices?.removeEventListener('devicechange', this.refreshCameraList);
this.closeStream();
},
}
</script>
</script>