63 lines
2.9 KiB
HTML
63 lines
2.9 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Live Scanner</title>
|
||
<style>
|
||
body{margin:0;background:#0b0b0b;color:#eee;font-family:-apple-system,system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||
header{padding:10px 14px;background:#141414;position:sticky;top:0}
|
||
h1{margin:0;font-size:16px}
|
||
#wrap{position:relative}
|
||
video{width:100vw;height:calc(100vh - 48px);object-fit:cover;background:#000}
|
||
.box{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}
|
||
.frame{width:min(70vw,70vh);height:min(70vw,70vh);border:2px solid #6cf;border-radius:12px;box-shadow:0 0 8px #6cf inset}
|
||
.hint{position:absolute;bottom:10px;left:0;right:0;text-align:center;font-size:12px;color:#bbb}
|
||
</style>
|
||
<header><h1>Сканирование… наведи на штрихкод</h1></header>
|
||
<div id="wrap">
|
||
<video id="preview" playsinline autoplay muted></video>
|
||
<div class="box"><div class="frame"></div></div>
|
||
<div class="hint">Доступ к камере запросится автоматически (только HTTPS)</div>
|
||
</div>
|
||
|
||
<!-- ZXing -->
|
||
<script src="https://unpkg.com/@zxing/library@latest"></script>
|
||
<script>
|
||
(async () => {
|
||
// Форматы: 1D (EAN/UPC/Code128/39/ITF/Codabar) + QR
|
||
const formats = [
|
||
ZXing.BarcodeFormat.EAN_13, ZXing.BarcodeFormat.EAN_8,
|
||
ZXing.BarcodeFormat.UPC_A, ZXing.BarcodeFormat.UPC_E,
|
||
ZXing.BarcodeFormat.CODE_128, ZXing.BarcodeFormat.CODE_39,
|
||
ZXing.BarcodeFormat.ITF, ZXing.BarcodeFormat.CODABAR,
|
||
ZXing.BarcodeFormat.QR_CODE
|
||
];
|
||
const hints = new Map();
|
||
hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);
|
||
hints.set(ZXing.DecodeHintType.TRY_HARDER, true);
|
||
|
||
const reader = new ZXing.BrowserMultiFormatReader(hints);
|
||
const video = document.getElementById('preview');
|
||
|
||
// Автозапуск потока с тыльной камеры
|
||
const constraints = { audio: false, video: { facingMode: { ideal: "environment" } } };
|
||
|
||
try {
|
||
// decodeFromConstraints сам откроет getUserMedia и будет читать кадры
|
||
await reader.decodeFromConstraints(constraints, video, (result, err) => {
|
||
if (result) {
|
||
const text = result.getText ? result.getText() : (result.text || "");
|
||
const fmt = result.getBarcodeFormat ? String(result.getBarcodeFormat()) : (result.barcodeFormat || "CODE");
|
||
// Отправляем в Scriptable через кастомную схему и останавливаем сканер
|
||
try { reader.reset(); } catch(e) {}
|
||
location.href = "scriptable://decoded?text=" + encodeURIComponent(text) + "&format=" + encodeURIComponent(fmt);
|
||
}
|
||
// Ошибки распознавания приходят часто — их игнорируем
|
||
});
|
||
} catch (e) {
|
||
alert("Не удалось открыть камеру: " + e);
|
||
}
|
||
})();
|
||
</script>
|
||
</html>
|