Optimized camera barcode scanner component

This commit is contained in:
Bernd Bestel
2025-01-12 23:51:00 +01:00
parent a06991f81b
commit 96059a1b32
10 changed files with 89 additions and 89 deletions

View File

@@ -209,7 +209,7 @@ a:not([href]) {
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Barcodescanner Quagga */ /* Barcodescanner Quagga2 */
#barcodescanner-container { #barcodescanner-container {
max-height: 90vw; max-height: 90vw;
} }

View File

@@ -1,7 +1,10 @@
Grocy.Components.BarcodeScanner = {}; Grocy.Components.CameraBarcodeScanner = {};
Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = false; Grocy.Components.CameraBarcodeScanner.LiveVideoSizeAdjusted = false;
Grocy.Components.BarcodeScanner.CheckCapabilities = async function() Grocy.Components.CameraBarcodeScanner.CameraSelectLoaded = false;
Grocy.Components.CameraBarcodeScanner.TorchIsOn = false;
Grocy.Components.CameraBarcodeScanner.CheckCapabilities = async function()
{ {
var track = Quagga.CameraAccess.getActiveTrack(); var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = {}; var capabilities = {};
@@ -10,31 +13,42 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
capabilities = track.getCapabilities(); capabilities = track.getCapabilities();
} }
// If there is more than 1 camera, show the camera selection // Init camera select dropdown
var cameras = await Quagga.CameraAccess.enumerateVideoDevices(); if (!Grocy.Components.CameraBarcodeScanner.CameraSelectLoaded)
var cameraSelect = document.querySelector('.cameraSelect-wrapper');
if (cameraSelect)
{ {
// Ignore devices without any name var cameraSelect = document.querySelector('.cameraSelect');
cameraSelect.style.display = cameras.filter(x => x.label || x.deviceId).length > 1 ? 'inline-block' : 'none'; var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
cameras.forEach(camera =>
{
var option = document.createElement("option");
option.text = camera.label;
option.value = camera.deviceId;
if (track.label == camera.label)
{
option.selected = "selected";
}
cameraSelect.appendChild(option);
});
Grocy.Components.CameraBarcodeScanner.CameraSelectLoaded = true;
} }
// Check if the camera is capable to turn on a torch. // Check if the camera is capable to turn on a torch
var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch var hasTorch = typeof capabilities.torch === 'boolean' && capabilities.torch;
// Remove the torch button, if either the device can not torch or AutoTorchOn is set.
var node = document.querySelector('.torch'); // Remove the torch button if the select camera doesn't have a torch
if (node) var button = document.querySelector('.torch');
if (!hasTorch)
{ {
node.style.display = canTorch && !Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none'; button.classList.add('disabled');
} }
// If AutoTorchOn is set, turn on the torch. else
if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
{ {
Grocy.Components.BarcodeScanner.TorchOn(track); button.classList.remove('disabled');
} }
// Reduce the height of the video, if it's higher than then the viewport // Reduce the height of the video, if it's higher than then the viewport
if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted) if (!Grocy.Components.CameraBarcodeScanner.LiveVideoSizeAdjusted)
{ {
var bc = document.getElementById('barcodescanner-container'); var bc = document.getElementById('barcodescanner-container');
if (bc) if (bc)
@@ -43,7 +57,7 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
var settings = track.getSettings(); var settings = track.getSettings();
if (bcAspectRatio > settings.aspectRatio) if (bcAspectRatio > settings.aspectRatio)
{ {
var v = document.querySelector('#barcodescanner-livestream video') var v = document.querySelector('#barcodescanner-livestream video');
if (v) if (v)
{ {
var c = document.querySelector('#barcodescanner-livestream canvas') var c = document.querySelector('#barcodescanner-livestream canvas')
@@ -53,15 +67,15 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
} }
} }
Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = true; Grocy.Components.CameraBarcodeScanner.LiveVideoSizeAdjusted = true;
} }
} }
} }
Grocy.Components.BarcodeScanner.StartScanning = function() Grocy.Components.CameraBarcodeScanner.StartScanning = function()
{ {
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0; Grocy.Components.CameraBarcodeScanner.DecodedCodesCount = 0;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0; Grocy.Components.CameraBarcodeScanner.DecodedCodesErrorCount = 0;
Quagga.init({ Quagga.init({
inputStream: { inputStream: {
@@ -70,7 +84,7 @@ Grocy.Components.BarcodeScanner.StartScanning = function()
target: document.querySelector("#barcodescanner-livestream"), target: document.querySelector("#barcodescanner-livestream"),
constraints: { constraints: {
facingMode: "environment", facingMode: "environment",
...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera ...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }), // If preferred cameraId is set, request to use that specific camera
} }
}, },
locator: { locator: {
@@ -130,30 +144,39 @@ Grocy.Components.BarcodeScanner.StartScanning = function()
return; return;
} }
Grocy.Components.BarcodeScanner.CheckCapabilities(); Grocy.Components.CameraBarcodeScanner.CheckCapabilities();
Quagga.start(); Quagga.start();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
{
setTimeout(function()
{
Grocy.Components.CameraBarcodeScanner.TorchToggle(Quagga.CameraAccess.getActiveTrack());
}, 250);
}
}); });
} }
Grocy.Components.BarcodeScanner.StopScanning = function() Grocy.Components.CameraBarcodeScanner.StopScanning = function()
{ {
Quagga.stop(); Quagga.stop();
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0; Grocy.Components.CameraBarcodeScanner.DecodedCodesCount = 0;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0; Grocy.Components.CameraBarcodeScanner.DecodedCodesErrorCount = 0;
Grocy.Components.CameraBarcodeScanner.LiveVideoSizeAdjusted = false;
$(".modal").last().modal("hide"); Grocy.Components.CameraBarcodeScanner.CameraSelectLoaded = false;
Grocy.Components.CameraBarcodeScanner.TorchIsOn = false;
} }
Grocy.Components.BarcodeScanner.TorchOn = function(track) Grocy.Components.CameraBarcodeScanner.TorchToggle = function(track)
{ {
if (track) if (track)
{ {
Grocy.Components.CameraBarcodeScanner.TorchIsOn = !Grocy.Components.CameraBarcodeScanner.TorchIsOn;
track.applyConstraints({ track.applyConstraints({
advanced: [ advanced: [
{ {
torch: true torch: Grocy.Components.CameraBarcodeScanner.TorchIsOn
} }
] ]
}); });
@@ -166,16 +189,16 @@ Quagga.onDetected(function(result)
{ {
if (error.error != undefined) if (error.error != undefined)
{ {
Grocy.Components.BarcodeScanner.DecodedCodesCount++; Grocy.Components.CameraBarcodeScanner.DecodedCodesCount++;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount += Number.parseFloat(error.error); Grocy.Components.CameraBarcodeScanner.DecodedCodesErrorCount += Number.parseFloat(error.error);
} }
}); });
if ((Grocy.Components.BarcodeScanner.DecodedCodesErrorCount / Grocy.Components.BarcodeScanner.DecodedCodesCount < 0.15) || if ((Grocy.Components.CameraBarcodeScanner.DecodedCodesErrorCount / Grocy.Components.CameraBarcodeScanner.DecodedCodesCount < 0.15) ||
(Grocy.Components.BarcodeScanner.DecodedCodesErrorCount == 0 && Grocy.Components.BarcodeScanner.DecodedCodesCount == 0 && result.codeResult.code.length != 0)) (Grocy.Components.CameraBarcodeScanner.DecodedCodesErrorCount == 0 && Grocy.Components.CameraBarcodeScanner.DecodedCodesCount == 0 && result.codeResult.code.length != 0))
{ {
Grocy.Components.BarcodeScanner.StopScanning(); Grocy.Components.CameraBarcodeScanner.StopScanning();
$(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, Grocy.Components.BarcodeScanner.CurrentTarget]); $(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, Grocy.Components.CameraBarcodeScanner.CurrentTarget]);
} }
}); });
@@ -221,72 +244,49 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
return; return;
} }
Grocy.Components.BarcodeScanner.CurrentTarget = inputElement.attr("data-target"); Grocy.Components.CameraBarcodeScanner.CurrentTarget = inputElement.attr("data-target");
var dialog = bootbox.dialog({ var dialog = bootbox.dialog({
message: '<div id="barcodescanner-container" class="col"><div id="barcodescanner-livestream"></div></div>', message: '<div id="barcodescanner-container" class="col"><div id="barcodescanner-livestream"></div></div>',
title: __t('Scan a barcode'), title: __t('Scan a barcode'),
onEscape: function() size: 'large',
{
Grocy.Components.BarcodeScanner.StopScanning();
},
size: 'big',
backdrop: true, backdrop: true,
closeButton: true, closeButton: true,
className: "form",
buttons: { buttons: {
torch: { torch: {
label: '<i class="fa-solid fa-lightbulb"></i>', label: '<i class="fa-solid fa-lightbulb"></i>',
className: 'btn-warning responsive-button torch', className: 'btn-warning responsive-button torch',
callback: function() callback: function()
{ {
Grocy.Components.BarcodeScanner.TorchOn(Quagga.CameraAccess.getActiveTrack()); Grocy.Components.CameraBarcodeScanner.TorchToggle(Quagga.CameraAccess.getActiveTrack());
return false; return false;
} }
},
cancel: {
label: __t('Cancel'),
className: 'btn-secondary responsive-button',
callback: function()
{
Grocy.Components.BarcodeScanner.StopScanning();
}
} }
},
onHide: function(e)
{
Grocy.Components.CameraBarcodeScanner.StopScanning();
} }
}); });
// Add camera select to existing dialog // Add camera select to existing dialog
dialog.find('.bootbox-body').append('<div class="form-group py-0 my-1 d-block cameraSelect-wrapper"><select class="custom-control custom-select cameraSelect"><select class="custom-control custom-select cameraSelect" style="display: none"></select></div>'); dialog.find('.bootbox-body').append('<div class="form-group py-0 my-1 d-block cameraSelect-wrapper"><select class="custom-control custom-select cameraSelect"></select></div>');
var cameraSelect = document.querySelector('.cameraSelect'); var cameraSelect = document.querySelector('.cameraSelect');
var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
cameras.forEach(camera =>
{
var option = document.createElement("option");
if (camera.label || camera.deviceId) // Ignore devices without any name
{
option.text = camera.label ? camera.label : camera.deviceId; // Use camera label if it exists, else show device id
option.value = camera.deviceId;
cameraSelect.appendChild(option);
}
});
// Set initial value to preferred camera if one exists - and if not, start out empty
cameraSelect.value = window.localStorage.getItem('cameraId');
cameraSelect.onchange = function() cameraSelect.onchange = function()
{ {
window.localStorage.setItem('cameraId', cameraSelect.value); window.localStorage.setItem('cameraId', cameraSelect.value);
Quagga.stop(); Quagga.stop();
Grocy.Components.BarcodeScanner.StartScanning(); Grocy.Components.CameraBarcodeScanner.StartScanning();
}; };
Grocy.Components.BarcodeScanner.StartScanning(); Grocy.Components.CameraBarcodeScanner.StartScanning();
}); });
Grocy.Components.BarcodeScanner.InitDone = false; Grocy.Components.CameraBarcodeScanner.InitDone = false;
Grocy.Components.BarcodeScanner.Init = function() Grocy.Components.CameraBarcodeScanner.Init = function()
{ {
if (Grocy.Components.BarcodeScanner.InitDone) if (Grocy.Components.CameraBarcodeScanner.InitDone)
{ {
return; return;
} }
@@ -302,11 +302,11 @@ Grocy.Components.BarcodeScanner.Init = function()
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white" data-target="' + $(this).attr("data-target") + '"><i class="fa-solid fa-camera"></i></a>'); $(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white" data-target="' + $(this).attr("data-target") + '"><i class="fa-solid fa-camera"></i></a>');
} }
Grocy.Components.BarcodeScanner.InitDone = true; Grocy.Components.CameraBarcodeScanner.InitDone = true;
}); });
} }
setTimeout(function() setTimeout(function()
{ {
Grocy.Components.BarcodeScanner.Init(); Grocy.Components.CameraBarcodeScanner.Init();
}, 50); }, 50);

View File

@@ -475,7 +475,7 @@ $("#add-recipe-modal").on("shown.bs.modal", function(e)
{ {
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING) if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
{ {
Grocy.Components.BarcodeScanner.Init(); Grocy.Components.CameraBarcodeScanner.Init();
} }
Grocy.Components.RecipePicker.GetInputElement().focus(); Grocy.Components.RecipePicker.GetInputElement().focus();
@@ -490,7 +490,7 @@ $("#add-product-modal").on("shown.bs.modal", function(e)
{ {
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING) if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
{ {
Grocy.Components.BarcodeScanner.Init(); Grocy.Components.CameraBarcodeScanner.Init();
} }
Grocy.Components.ProductPicker.GetInputElement().focus(); Grocy.Components.ProductPicker.GetInputElement().focus();

View File

@@ -61,5 +61,5 @@
</div> </div>
</div> </div>
@include('components.barcodescanner') @include('components.camerabarcodescanner')
@stop @stop

View File

@@ -60,5 +60,5 @@
</div> </div>
</div> </div>
@include('components.barcodescanner') @include('components.camerabarcodescanner')
@stop @stop

View File

@@ -75,5 +75,5 @@
</div> </div>
</div> </div>
@include('components.barcodescanner') @include('components.camerabarcodescanner')
@stop @stop

View File

@@ -4,7 +4,7 @@
@once @once
@push('componentScripts') @push('componentScripts')
<script src="{{ $U('/viewjs/components/barcodescanner.js', true) }}?v={{ $version }}"></script> <script src="{{ $U('/viewjs/components/camerabarcodescanner.js', true) }}?v={{ $version }}"></script>
@endpush @endpush
@endonce @endonce

View File

@@ -77,4 +77,4 @@
class="form-text text-info small d-none"><strong><span id="InplaceAddBarcodeToExistingProduct"></span></strong> {{ $__t('will be added to the list of barcodes for the selected product on submit') }}</div> class="form-text text-info small d-none"><strong><span id="InplaceAddBarcodeToExistingProduct"></span></strong> {{ $__t('will be added to the list of barcodes for the selected product on submit') }}</div>
</div> </div>
@include('components.barcodescanner') @include('components.camerabarcodescanner')

View File

@@ -41,4 +41,4 @@
<div class="invalid-feedback">{{ $__t('You have to select a recipe') }}</div> <div class="invalid-feedback">{{ $__t('You have to select a recipe') }}</div>
</div> </div>
@include('components.barcodescanner') @include('components.camerabarcodescanner')

View File

@@ -57,7 +57,7 @@
name="barcode" name="barcode"
value="@if($mode == 'edit'){{ $barcode->barcode }}@endif" value="@if($mode == 'edit'){{ $barcode->barcode }}@endif"
data-target="#barcode"> data-target="#barcode">
@include('components.barcodescanner') @include('components.camerabarcodescanner')
</div> </div>
</div> </div>