优化声音和安卓端适配
This commit is contained in:
@@ -89,6 +89,9 @@ interface ScanConfigOptions {
|
|||||||
webScanType?: ("qrCode" | "barCode")[];
|
webScanType?: ("qrCode" | "barCode")[];
|
||||||
webScanVideoMirror?: boolean;
|
webScanVideoMirror?: boolean;
|
||||||
webScanVideoMirrorVertical?: boolean;
|
webScanVideoMirrorVertical?: boolean;
|
||||||
|
webScanImageFallbackOnVideoError?: boolean;
|
||||||
|
webScanVideoAccessTimeout?: number;
|
||||||
|
webScanVideoReadyTimeout?: number;
|
||||||
scanBeepAudio?: string;
|
scanBeepAudio?: string;
|
||||||
scanBeepEnabled?: boolean;
|
scanBeepEnabled?: boolean;
|
||||||
initWechatJssdk?: {
|
initWechatJssdk?: {
|
||||||
@@ -120,6 +123,9 @@ interface ScanConfigOptions {
|
|||||||
| `webScanType` | WebScan 扫码类型 | `["qrCode", "barCode"]` |
|
| `webScanType` | WebScan 扫码类型 | `["qrCode", "barCode"]` |
|
||||||
| `webScanVideoMirror` | WebScan 视频是否水平镜像;不配置时自动判断:前置/PC 镜像,后置不镜像 | 自动 |
|
| `webScanVideoMirror` | WebScan 视频是否水平镜像;不配置时自动判断:前置/PC 镜像,后置不镜像 | 自动 |
|
||||||
| `webScanVideoMirrorVertical` | WebScan 视频是否垂直镜像 | `false` |
|
| `webScanVideoMirrorVertical` | WebScan 视频是否垂直镜像 | `false` |
|
||||||
|
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
||||||
|
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
||||||
|
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
||||||
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
||||||
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
||||||
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
||||||
|
|||||||
12
dist/index.d.ts
vendored
12
dist/index.d.ts
vendored
@@ -89,6 +89,18 @@ interface ScanConfigOptions {
|
|||||||
* 网页扫码视频是否垂直镜像,默认不镜像
|
* 网页扫码视频是否垂直镜像,默认不镜像
|
||||||
*/
|
*/
|
||||||
webScanVideoMirrorVertical?: boolean,
|
webScanVideoMirrorVertical?: boolean,
|
||||||
|
/**
|
||||||
|
* 摄像头不可用(如部分安卓内置浏览器禁用 getUserMedia)时是否自动回退为拍照/选图识别,默认启用
|
||||||
|
*/
|
||||||
|
webScanImageFallbackOnVideoError?: boolean,
|
||||||
|
/**
|
||||||
|
* 打开摄像头超时时间(毫秒),超时后触发图片回退,默认 10000
|
||||||
|
*/
|
||||||
|
webScanVideoAccessTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
||||||
|
*/
|
||||||
|
webScanVideoReadyTimeout?: number,
|
||||||
/**
|
/**
|
||||||
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
||||||
*/
|
*/
|
||||||
|
|||||||
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
6
dist/index.md
vendored
6
dist/index.md
vendored
@@ -89,6 +89,9 @@ interface ScanConfigOptions {
|
|||||||
webScanType?: ("qrCode" | "barCode")[];
|
webScanType?: ("qrCode" | "barCode")[];
|
||||||
webScanVideoMirror?: boolean;
|
webScanVideoMirror?: boolean;
|
||||||
webScanVideoMirrorVertical?: boolean;
|
webScanVideoMirrorVertical?: boolean;
|
||||||
|
webScanImageFallbackOnVideoError?: boolean;
|
||||||
|
webScanVideoAccessTimeout?: number;
|
||||||
|
webScanVideoReadyTimeout?: number;
|
||||||
scanBeepAudio?: string;
|
scanBeepAudio?: string;
|
||||||
scanBeepEnabled?: boolean;
|
scanBeepEnabled?: boolean;
|
||||||
initWechatJssdk?: {
|
initWechatJssdk?: {
|
||||||
@@ -120,6 +123,9 @@ interface ScanConfigOptions {
|
|||||||
| `webScanType` | WebScan 扫码类型 | `["qrCode", "barCode"]` |
|
| `webScanType` | WebScan 扫码类型 | `["qrCode", "barCode"]` |
|
||||||
| `webScanVideoMirror` | WebScan 视频是否水平镜像;不配置时自动判断:前置/PC 镜像,后置不镜像 | 自动 |
|
| `webScanVideoMirror` | WebScan 视频是否水平镜像;不配置时自动判断:前置/PC 镜像,后置不镜像 | 自动 |
|
||||||
| `webScanVideoMirrorVertical` | WebScan 视频是否垂直镜像 | `false` |
|
| `webScanVideoMirrorVertical` | WebScan 视频是否垂直镜像 | `false` |
|
||||||
|
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
||||||
|
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
||||||
|
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
||||||
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
||||||
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
||||||
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { inRuntime, bridgeAsync } from "../bridge";
|
import { inRuntime, bridgeAsync } from "../bridge";
|
||||||
import { isSupportWebScan, startScanForWeb, stopScanForWeb, isSupportImageScan, startScanForImage, unlockScanBeep, playScanBeep } from "../web";
|
import {
|
||||||
|
isSupportWebScan,
|
||||||
|
startScanForWeb,
|
||||||
|
stopScanForWeb,
|
||||||
|
isSupportImageScan,
|
||||||
|
startScanForImage,
|
||||||
|
unlockScanBeep,
|
||||||
|
playScanBeep,
|
||||||
|
isWebScanImageFallbackEnabled
|
||||||
|
} from "../web";
|
||||||
import { isSupportWxScan, startScanForWx } from "../wx";
|
import { isSupportWxScan, startScanForWx } from "../wx";
|
||||||
import { startScanner, stopScanner } from "../scanner";
|
import { startScanner, stopScanner } from "../scanner";
|
||||||
import { getConfig } from "../config";
|
import { getConfig } from "../config";
|
||||||
@@ -273,6 +282,35 @@ function __startWxScan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __startWebScan() {
|
||||||
|
return startScanForWeb(__result).then(resp => {
|
||||||
|
if (!isScanning()) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (!resp || !resp.result) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (__result(resp.result)) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (isScanning()) {
|
||||||
|
return __startWebScan();
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}).catch(err => {
|
||||||
|
if (!isScanning()) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (err && err.cancel || err && err.imageFallbackUsed) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (isWebScanImageFallbackEnabled()) {
|
||||||
|
return __startImageScan();
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function __startImageScan() {
|
function __startImageScan() {
|
||||||
return startScanForImage().then(resp => {
|
return startScanForImage().then(resp => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
@@ -414,7 +452,7 @@ export function startScan() {
|
|||||||
} else if (isSupportWxScan()) {
|
} else if (isSupportWxScan()) {
|
||||||
scanPromise = __startWxScan();
|
scanPromise = __startWxScan();
|
||||||
} else if (isSupportWebScan()) {
|
} else if (isSupportWebScan()) {
|
||||||
scanPromise = startScanForWeb(__result);
|
scanPromise = __startWebScan();
|
||||||
} else if (isSupportImageScan()) {
|
} else if (isSupportImageScan()) {
|
||||||
scanPromise = __startImageScan();
|
scanPromise = __startImageScan();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ const currentScriptSrc = typeof document !== "undefined"
|
|||||||
let barcodeDetectorPreparePromise = null;
|
let barcodeDetectorPreparePromise = null;
|
||||||
let scanBeepAudioEl = null;
|
let scanBeepAudioEl = null;
|
||||||
let scanBeepAudioSrc = null;
|
let scanBeepAudioSrc = null;
|
||||||
|
let scanBeepUnlockAudioEl = null;
|
||||||
let scanBeepUnlocked = false;
|
let scanBeepUnlocked = false;
|
||||||
|
let scanBeepUnlocking = false;
|
||||||
let scanBeepGestureUnlockInstalled = false;
|
let scanBeepGestureUnlockInstalled = false;
|
||||||
|
/** 极短静音 WAV,仅用于在用户手势内解锁自动播放,不播放真实提示音文件 */
|
||||||
|
const SILENT_BEEP_UNLOCK_AUDIO = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=";
|
||||||
|
|
||||||
function removeEl(id, uuid) {
|
function removeEl(id, uuid) {
|
||||||
try {
|
try {
|
||||||
@@ -357,6 +361,24 @@ export function playScanBeep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getScanBeepUnlockAudio() {
|
||||||
|
if (typeof Audio === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!scanBeepUnlockAudioEl) {
|
||||||
|
scanBeepUnlockAudioEl = new Audio(SILENT_BEEP_UNLOCK_AUDIO);
|
||||||
|
scanBeepUnlockAudioEl.preload = "auto";
|
||||||
|
scanBeepUnlockAudioEl.muted = true;
|
||||||
|
scanBeepUnlockAudioEl.volume = 0;
|
||||||
|
scanBeepUnlockAudioEl.setAttribute("playsinline", "");
|
||||||
|
try {
|
||||||
|
scanBeepUnlockAudioEl.load && scanBeepUnlockAudioEl.load();
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scanBeepUnlockAudioEl;
|
||||||
|
}
|
||||||
|
|
||||||
function getScanBeepAudio() {
|
function getScanBeepAudio() {
|
||||||
const audioSrc = getScanBeepAudioSrc();
|
const audioSrc = getScanBeepAudioSrc();
|
||||||
if (!audioSrc || typeof Audio === 'undefined') {
|
if (!audioSrc || typeof Audio === 'undefined') {
|
||||||
@@ -367,6 +389,7 @@ function getScanBeepAudio() {
|
|||||||
scanBeepUnlocked = false;
|
scanBeepUnlocked = false;
|
||||||
scanBeepAudioEl = new Audio(audioSrc);
|
scanBeepAudioEl = new Audio(audioSrc);
|
||||||
scanBeepAudioEl.preload = "auto";
|
scanBeepAudioEl.preload = "auto";
|
||||||
|
scanBeepAudioEl.setAttribute("playsinline", "");
|
||||||
try {
|
try {
|
||||||
scanBeepAudioEl.load && scanBeepAudioEl.load();
|
scanBeepAudioEl.load && scanBeepAudioEl.load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -377,43 +400,35 @@ function getScanBeepAudio() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function unlockScanBeep() {
|
export function unlockScanBeep() {
|
||||||
if (!isScanBeepEnabled() || scanBeepUnlocked) {
|
if (!isScanBeepEnabled() || scanBeepUnlocked || scanBeepUnlocking) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const audio = getScanBeepAudio();
|
getScanBeepAudio();
|
||||||
|
const audio = getScanBeepUnlockAudio();
|
||||||
if (!audio) {
|
if (!audio) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prevMuted = audio.muted;
|
scanBeepUnlocking = true;
|
||||||
const prevVolume = audio.volume;
|
|
||||||
try {
|
try {
|
||||||
audio.muted = true;
|
audio.muted = true;
|
||||||
audio.volume = 0;
|
audio.volume = 0;
|
||||||
audio.currentTime = 0;
|
audio.currentTime = 0;
|
||||||
const playPromise = audio.play();
|
const playPromise = audio.play();
|
||||||
if (!playPromise || !playPromise.then) {
|
const finishUnlock = () => {
|
||||||
resetScanBeepPlayback(audio);
|
resetScanBeepPlayback(audio);
|
||||||
audio.muted = prevMuted;
|
scanBeepUnlocking = false;
|
||||||
audio.volume = prevVolume;
|
|
||||||
scanBeepUnlocked = true;
|
scanBeepUnlocked = true;
|
||||||
|
};
|
||||||
|
if (!playPromise || !playPromise.then) {
|
||||||
|
finishUnlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playPromise.then(() => {
|
playPromise.then(finishUnlock).catch(() => {
|
||||||
resetScanBeepPlayback(audio);
|
resetScanBeepPlayback(audio);
|
||||||
audio.muted = prevMuted;
|
scanBeepUnlocking = false;
|
||||||
audio.volume = prevVolume;
|
|
||||||
scanBeepUnlocked = true;
|
|
||||||
}).catch(() => {
|
|
||||||
resetScanBeepPlayback(audio);
|
|
||||||
audio.muted = prevMuted;
|
|
||||||
audio.volume = prevVolume;
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
scanBeepUnlocking = false;
|
||||||
audio.muted = prevMuted;
|
|
||||||
audio.volume = prevVolume;
|
|
||||||
} catch (_e) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,17 +446,120 @@ export function isSupportImageScan() {
|
|||||||
&& !!URL.createObjectURL;
|
&& !!URL.createObjectURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isWebScanImageFallbackEnabled() {
|
||||||
|
return isSupportImageScan() && getConfig("webScanImageFallbackOnVideoError") !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebScanVideoAccessTimeout() {
|
||||||
|
const timeout = getConfig("webScanVideoAccessTimeout");
|
||||||
|
return typeof timeout === "number" && timeout > 0 ? timeout : 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebScanVideoReadyTimeout() {
|
||||||
|
const timeout = getConfig("webScanVideoReadyTimeout");
|
||||||
|
return typeof timeout === "number" && timeout > 0 ? timeout : 8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserMediaWithTimeout(constraints, timeoutMs) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let settled = false;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
reject(new Error("getUserMedia timeout"));
|
||||||
|
}, timeoutMs);
|
||||||
|
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
|
||||||
|
if (settled) {
|
||||||
|
stopMediaStream(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(stream);
|
||||||
|
}).catch(err => {
|
||||||
|
if (settled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runWebScanImageFallback(onResult, resolve, reject) {
|
||||||
|
stopActiveWebScan();
|
||||||
|
unlockScanBeep();
|
||||||
|
return createBarcodeDetector(getConfig("webScanType")).then(detector => {
|
||||||
|
return chooseImageFile({ preferCapture: isMobile() }).then(file => {
|
||||||
|
if (!file) {
|
||||||
|
return Promise.reject({ cancel: 1 });
|
||||||
|
}
|
||||||
|
return detectImageFile(detector, file);
|
||||||
|
}).then(code => {
|
||||||
|
if (code && code.rawValue) {
|
||||||
|
if (!onResult || !onResult(code.rawValue)) {
|
||||||
|
return Promise.reject({ cancel: 1 });
|
||||||
|
}
|
||||||
|
scanWeb.finish = true;
|
||||||
|
resolve({
|
||||||
|
result: code.rawValue
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return Promise.reject({
|
||||||
|
success: false,
|
||||||
|
error: "未识别到二维码或条形码",
|
||||||
|
imageFallbackUsed: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
if (err && err.cancel) {
|
||||||
|
reject({ cancel: 1, imageFallbackUsed: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err && err.result) {
|
||||||
|
reject(Object.assign({ imageFallbackUsed: true }, err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject({
|
||||||
|
imageFallbackUsed: true,
|
||||||
|
error: err && err.error ? err.error : err
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryWebScanImageFallback(onResult, resolve, reject, state) {
|
||||||
|
if (!isWebScanImageFallbackEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state && state.imageFallbackStarted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state) {
|
||||||
|
state.imageFallbackStarted = true;
|
||||||
|
}
|
||||||
|
runWebScanImageFallback(onResult, resolve, reject);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function stopScanForWeb() {
|
export function stopScanForWeb() {
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
stopActiveWebScan();
|
stopActiveWebScan();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseImageFile() {
|
function chooseImageFile(options) {
|
||||||
|
options = options || {};
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.accept = "image/*";
|
input.accept = "image/*";
|
||||||
|
if (options.preferCapture) {
|
||||||
|
input.setAttribute("capture", options.captureMode || "environment");
|
||||||
|
}
|
||||||
input.style.display = "none";
|
input.style.display = "none";
|
||||||
let finished = false;
|
let finished = false;
|
||||||
let finish = file => {
|
let finish = file => {
|
||||||
@@ -523,6 +641,7 @@ export function startScanForImage() {
|
|||||||
|
|
||||||
export function startScanForWeb(onResult) {
|
export function startScanForWeb(onResult) {
|
||||||
let currentUuid = null;
|
let currentUuid = null;
|
||||||
|
const fallbackState = { imageFallbackStarted: false };
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
stopActiveWebScan();
|
stopActiveWebScan();
|
||||||
@@ -581,11 +700,11 @@ export function startScanForWeb(onResult) {
|
|||||||
videoEl.uuid = scanWeb.uuid;
|
videoEl.uuid = scanWeb.uuid;
|
||||||
canvasEl.uuid = scanWeb.uuid;
|
canvasEl.uuid = scanWeb.uuid;
|
||||||
createBarcodeDetector(getConfig("webScanType")).then(detector => {
|
createBarcodeDetector(getConfig("webScanType")).then(detector => {
|
||||||
return navigator.mediaDevices.getUserMedia({
|
return getUserMediaWithTimeout({
|
||||||
video: {
|
video: {
|
||||||
facingMode: "environment"
|
facingMode: "environment"
|
||||||
}
|
}
|
||||||
}).then(stream => {
|
}, getWebScanVideoAccessTimeout()).then(stream => {
|
||||||
return {
|
return {
|
||||||
detector,
|
detector,
|
||||||
stream
|
stream
|
||||||
@@ -605,16 +724,38 @@ export function startScanForWeb(onResult) {
|
|||||||
const mirrorVideoVertical = shouldMirrorWebVideoVertical();
|
const mirrorVideoVertical = shouldMirrorWebVideoVertical();
|
||||||
videoEl.srcObject = stream;
|
videoEl.srcObject = stream;
|
||||||
videoEl.setAttribute("playsinline", true); // iOS使用
|
videoEl.setAttribute("playsinline", true); // iOS使用
|
||||||
videoEl.play();
|
|
||||||
canvasEl.style.display = "none";
|
canvasEl.style.display = "none";
|
||||||
let detecting = false;
|
let detecting = false;
|
||||||
let displayed = false;
|
let displayed = false;
|
||||||
let closed = false;
|
let closed = false;
|
||||||
|
let videoReadyTimer = null;
|
||||||
|
const clearVideoReadyTimer = () => {
|
||||||
|
if (videoReadyTimer) {
|
||||||
|
clearTimeout(videoReadyTimer);
|
||||||
|
videoReadyTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const scheduleVideoReadyTimeout = () => {
|
||||||
|
clearVideoReadyTimer();
|
||||||
|
videoReadyTimer = setTimeout(() => {
|
||||||
|
if (scanWeb.uuid !== currentUuid || closed || scanWeb.finish) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (videoEl.readyState >= videoEl.HAVE_ENOUGH_DATA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
close();
|
||||||
|
tryWebScanImageFallback(onResult, resolve, reject, fallbackState);
|
||||||
|
}, getWebScanVideoReadyTimeout());
|
||||||
|
};
|
||||||
|
scheduleVideoReadyTimeout();
|
||||||
let close = () => {
|
let close = () => {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
closed = true;
|
closed = true;
|
||||||
|
clearVideoReadyTimer();
|
||||||
stopMediaStream(stream);
|
stopMediaStream(stream);
|
||||||
if (scanWeb.uuid === currentUuid || scanWeb.stream === stream) {
|
if (scanWeb.uuid === currentUuid || scanWeb.stream === stream) {
|
||||||
scanWeb.stream = null;
|
scanWeb.stream = null;
|
||||||
@@ -631,6 +772,16 @@ export function startScanForWeb(onResult) {
|
|||||||
pickImageButtonEl.style.display = "none";
|
pickImageButtonEl.style.display = "none";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const videoPlayPromise = videoEl.play && videoEl.play();
|
||||||
|
if (videoPlayPromise && videoPlayPromise.catch) {
|
||||||
|
videoPlayPromise.catch(() => {
|
||||||
|
if (scanWeb.uuid !== currentUuid || closed || scanWeb.finish) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
tryWebScanImageFallback(onResult, resolve, reject, fallbackState);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (pickImageButtonEl) {
|
if (pickImageButtonEl) {
|
||||||
pickImageButtonEl.onclick = event => {
|
pickImageButtonEl.onclick = event => {
|
||||||
event.preventDefault && event.preventDefault();
|
event.preventDefault && event.preventDefault();
|
||||||
@@ -664,6 +815,7 @@ export function startScanForWeb(onResult) {
|
|||||||
let tick = () => {
|
let tick = () => {
|
||||||
try {
|
try {
|
||||||
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
|
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
|
||||||
|
clearVideoReadyTimer();
|
||||||
canvasEl.width = canvasDisplaySize.width;
|
canvasEl.width = canvasDisplaySize.width;
|
||||||
canvasEl.height = canvasDisplaySize.height;
|
canvasEl.height = canvasDisplaySize.height;
|
||||||
const cover = getCoverDrawOptions(videoEl.videoWidth, videoEl.videoHeight, canvasEl.width, canvasEl.height);
|
const cover = getCoverDrawOptions(videoEl.videoWidth, videoEl.videoHeight, canvasEl.width, canvasEl.height);
|
||||||
@@ -730,7 +882,9 @@ export function startScanForWeb(onResult) {
|
|||||||
tick();
|
tick();
|
||||||
});
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
reject({ error: err });
|
if (!tryWebScanImageFallback(onResult, resolve, reject, fallbackState)) {
|
||||||
|
reject({ error: err });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject({ error: e });
|
reject({ error: e });
|
||||||
|
|||||||
12
types/index.d.ts
vendored
12
types/index.d.ts
vendored
@@ -89,6 +89,18 @@ interface ScanConfigOptions {
|
|||||||
* 网页扫码视频是否垂直镜像,默认不镜像
|
* 网页扫码视频是否垂直镜像,默认不镜像
|
||||||
*/
|
*/
|
||||||
webScanVideoMirrorVertical?: boolean,
|
webScanVideoMirrorVertical?: boolean,
|
||||||
|
/**
|
||||||
|
* 摄像头不可用(如部分安卓内置浏览器禁用 getUserMedia)时是否自动回退为拍照/选图识别,默认启用
|
||||||
|
*/
|
||||||
|
webScanImageFallbackOnVideoError?: boolean,
|
||||||
|
/**
|
||||||
|
* 打开摄像头超时时间(毫秒),超时后触发图片回退,默认 10000
|
||||||
|
*/
|
||||||
|
webScanVideoAccessTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
||||||
|
*/
|
||||||
|
webScanVideoReadyTimeout?: number,
|
||||||
/**
|
/**
|
||||||
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user