web-sdk-pp-detection 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -15
- package/dist/browser-global.js +255 -85
- package/dist/browser-global.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +255 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
基于 ONNX Runtime Web 的浏览器端 PP-Detection 目标检测 SDK,支持 PC、移动端与各类 H5 页面。
|
|
6
6
|
|
|
7
|
-
当前 SDK 版本为 **0.3.
|
|
7
|
+
当前 SDK 版本为 **0.3.2**,完整变更见[发布说明](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/release-0.3.2.md)。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pnpm add web-sdk-pp-detection@0.3.
|
|
12
|
+
pnpm add web-sdk-pp-detection@0.3.2
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
也可以使用 `npm install web-sdk-pp-detection@0.3.
|
|
15
|
+
也可以使用 `npm install web-sdk-pp-detection@0.3.2`。
|
|
16
16
|
|
|
17
17
|
## 快速开始
|
|
18
18
|
|
|
@@ -37,17 +37,17 @@ await detector.dispose();
|
|
|
37
37
|
|
|
38
38
|
`classThresholds` 按 manifest 标签名称覆盖目标检测置信度过滤阈值,未配置的类别回退到全局 `threshold`。未知类别名称或超出 `0` 到 `1` 的值会被拒绝。
|
|
39
39
|
|
|
40
|
-
工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.1
|
|
40
|
+
工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.2 与 PP-YOLOE 0.1.1 的六个 stable 变体清单,npm 包不内置清单或 ONNX 模型本体。自定义模型也应传入经过验证的 runtime manifest 或清单对象。
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
两份当前 manifest 和在线 Demo 均默认 ModelScope,并可显式选择 ModelScope 或 Hugging Face。
|
|
43
43
|
|
|
44
44
|
模型初始化耗时可以通过 `detector.loadTimings` 查看。`totalMs` 是初始化总耗时,同时提供 `modelDownloadMs`(网络下载)、`modelCacheReadMs`(缓存读取)、`integrityMs`(SHA-256 完整性校验)和 `sessionMs`(ONNX Runtime Session 创建)。从 0.2.0 起,`loadTimings.modelSource` 区分 `network`、`cache`、`memory`,`runtime.runtimeVersion/environment` 记录实际 ORT 版本和环境快照。初始化总耗时包含 manifest 获取,详细语义见[性能文档](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/zh-CN/performance.md)。
|
|
45
45
|
|
|
46
46
|
## 运行后端与精度
|
|
47
47
|
|
|
48
48
|
- `backend: "auto"` 优先使用 WebGPU;设置 `allowFallback: true` 后,WebGPU 会话或推理失败才会尝试 WASM(CPU)。也可手动指定 `"webgpu"` 或 `"wasm"`。
|
|
49
|
-
- `precision: "auto"`
|
|
50
|
-
-
|
|
49
|
+
- `precision: "auto"` 选择清单默认的稳定精度,当前两份清单均为 FP32;`precision: "fp16"` 选择 FP16,`precision: "int8"` 选择 W8A32。
|
|
50
|
+
- 两模型的 FP32、FP16、W8A32 均为 stable。FP16/W8A32 证据仅覆盖 2026-09-12 桌面 WASM/WebGPU 固定 64 图三轮验证;小米 15 实测仅覆盖原 FP32。
|
|
51
51
|
- 使用默认模型时,显式请求清单中未声明的组合会抛出 `CAPABILITY_UNSUPPORTED`,不会改写无效组合;自定义清单可在单独验证后声明其他组合。上游模型是 float32,不支持 FP64;FP32 约为 FP16 两倍大小并可能更慢、更占显存。
|
|
52
52
|
- `detect` 可接收图片 Blob/File、Canvas/ImageData、`HTMLVideoElement` 或单帧 `VideoFrame`。摄像头和视频播放的权限、帧率控制由宿主页面负责;每次提交一帧后应等待 Promise 完成,并在停止媒体时调用 `dispose()`。
|
|
53
53
|
|
|
@@ -82,15 +82,15 @@ const detector = await createPPDetection({
|
|
|
82
82
|
|
|
83
83
|
A browser-first PP-Detection object detection SDK powered by ONNX Runtime Web for desktop, mobile, and H5 pages.
|
|
84
84
|
|
|
85
|
-
The current SDK version is **0.3.
|
|
85
|
+
The current SDK version is **0.3.2**. See the [release notes](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/release-0.3.2.md) for the complete changes.
|
|
86
86
|
|
|
87
87
|
### Installation
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
|
-
pnpm add web-sdk-pp-detection@0.3.
|
|
90
|
+
pnpm add web-sdk-pp-detection@0.3.2
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
`npm install web-sdk-pp-detection@0.3.
|
|
93
|
+
`npm install web-sdk-pp-detection@0.3.2` is also supported.
|
|
94
94
|
|
|
95
95
|
### Quick start
|
|
96
96
|
|
|
@@ -115,17 +115,17 @@ await detector.dispose();
|
|
|
115
115
|
|
|
116
116
|
`classThresholds` overrides object-detection confidence filtering for matching manifest label names and falls back to the global `threshold` for unspecified classes. Unknown class names and values outside `0` through `1` are rejected.
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
工厂必须显式传入 `model` 或 `manifest`,两者均缺省时抛出 `INVALID_MANIFEST`;同时传入时优先使用 `model`。仓库提供 PicoDet 1.0.2 与 PP-YOLOE 0.1.1 的六个 stable 变体清单,npm 包不内置清单或 ONNX 模型本体。
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
两份当前 manifest 和在线 Demo 均默认 ModelScope,并可显式选择 ModelScope 或 Hugging Face。
|
|
121
121
|
|
|
122
122
|
Detailed initialization timings are available through `detector.loadTimings`. `totalMs` is the full initialization duration. The additive fields `modelDownloadMs`, `modelCacheReadMs`, `integrityMs`, and `sessionMs` separate network download, cache reads, SHA-256 verification, and Session creation. From 0.2.0, `loadTimings.modelSource` distinguishes `network`, `cache`, and `memory`, while `runtime.runtimeVersion/environment` capture the actual ORT version and environment. Initialization totals include manifest retrieval; see [performance](https://github.com/chenmohan123/web-sdk-PP-Detection/blob/main/docs/en/performance.md) for the full semantics.
|
|
123
123
|
|
|
124
124
|
### Backend and precision
|
|
125
125
|
|
|
126
126
|
- `backend: "auto"` prefers WebGPU; with `allowFallback: true`, a failed WebGPU session or inference attempts WASM (CPU). Use `"webgpu"` or `"wasm"` for an explicit choice.
|
|
127
|
-
- `precision: "auto"`
|
|
128
|
-
-
|
|
127
|
+
- `precision: "auto"` 选择清单默认的稳定精度,当前两份清单均为 FP32;`precision: "fp16"` 选择 FP16,`precision: "int8"` 选择 W8A32。
|
|
128
|
+
- 两模型的 FP32、FP16、W8A32 均为 stable。FP16/W8A32 证据仅覆盖 2026-09-12 桌面 WASM/WebGPU 固定 64 图三轮验证;小米 15 实测仅覆盖原 FP32。
|
|
129
129
|
- `detect` accepts image Blob/File, Canvas/ImageData, `HTMLVideoElement`, or a single `VideoFrame`. Hosts own camera/video permissions and frame pacing; await each frame Promise and call `dispose()` when media stops.
|
|
130
130
|
- Explicit pairs absent from the default manifest throw `CAPABILITY_UNSUPPORTED` instead of rewriting an invalid pair. The upstream model is float32, not FP64; FP64 inference is unsupported. FP32 is about twice the size of FP16 and may be slower or use more GPU memory.
|
|
131
131
|
|
|
@@ -151,7 +151,11 @@ WeChat official-account pages and other H5/WebView integrations are supported. N
|
|
|
151
151
|
|
|
152
152
|
### PP-YOLOE 稳定模型(0.3.0 起)
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
当前 PP-YOLOE+ S 640 为 `0.1.1`,与 PicoDet 1.0.2 一样提供 FP32、FP16、W8A32 stable 变体。见[六变体示例](https://github.com/chenmohan123/web-sdk-PP-Detection/tree/main/examples/model-variants)。`allowExperimental` 默认关闭,仅运行旧 labs 候选时显式开启;blocked 仍被拒绝。Demo 默认 PicoDet、ModelScope、FP32。
|
|
155
|
+
|
|
156
|
+
### 下载配置(0.3.2 起)
|
|
157
|
+
|
|
158
|
+
0.3.2 新增 `download.timeoutMs`、`download.idleTimeoutMs` 和 `download.maxRetries`。默认每次请求总时限 180 秒、无新增字节时限 30 秒、最多重试 2 次。仅 ONNX 权重下载使用此策略,清单 JSON 加载沿用既有流程。
|
|
155
159
|
|
|
156
160
|
### Documentation
|
|
157
161
|
|
package/dist/browser-global.js
CHANGED
|
@@ -14007,34 +14007,58 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
14007
14007
|
const rounded = value >> BICUBIC_PRECISION_BITS;
|
|
14008
14008
|
return Math.max(0, Math.min(255, rounded));
|
|
14009
14009
|
}
|
|
14010
|
-
function
|
|
14010
|
+
function writeBicubicChannels(raster, resizedWidth, resizedHeight, target) {
|
|
14011
14011
|
const horizontal = createBicubicAxis(raster.width, resizedWidth);
|
|
14012
14012
|
const vertical = createBicubicAxis(raster.height, resizedHeight);
|
|
14013
|
-
const
|
|
14013
|
+
const rowStride = resizedWidth * 3;
|
|
14014
|
+
const intermediate = new Uint8Array(raster.height * rowStride);
|
|
14014
14015
|
for (let y = 0; y < raster.height; y += 1) {
|
|
14016
|
+
const sourceRow = y * raster.width * 4;
|
|
14017
|
+
const targetRow = y * rowStride;
|
|
14015
14018
|
for (let x = 0; x < resizedWidth; x += 1) {
|
|
14016
14019
|
const [start, count] = horizontal.bounds[x];
|
|
14017
14020
|
const coefficients = horizontal.coefficients[x];
|
|
14018
|
-
let
|
|
14021
|
+
let red = BICUBIC_ROUNDING;
|
|
14022
|
+
let green = BICUBIC_ROUNDING;
|
|
14023
|
+
let blue = BICUBIC_ROUNDING;
|
|
14024
|
+
let source2 = sourceRow + start * 4;
|
|
14019
14025
|
for (let index = 0; index < count; index += 1) {
|
|
14020
|
-
|
|
14026
|
+
const coefficient = coefficients[index];
|
|
14027
|
+
red += raster.rgba[source2] * coefficient;
|
|
14028
|
+
green += raster.rgba[source2 + 1] * coefficient;
|
|
14029
|
+
blue += raster.rgba[source2 + 2] * coefficient;
|
|
14030
|
+
source2 += 4;
|
|
14021
14031
|
}
|
|
14022
|
-
|
|
14032
|
+
const offset = targetRow + x * 3;
|
|
14033
|
+
intermediate[offset] = clipBicubic(red);
|
|
14034
|
+
intermediate[offset + 1] = clipBicubic(green);
|
|
14035
|
+
intermediate[offset + 2] = clipBicubic(blue);
|
|
14023
14036
|
}
|
|
14024
14037
|
}
|
|
14025
|
-
const
|
|
14038
|
+
const plane = target.inputWidth * target.inputHeight;
|
|
14026
14039
|
for (let y = 0; y < resizedHeight; y += 1) {
|
|
14027
14040
|
const [start, count] = vertical.bounds[y];
|
|
14028
14041
|
const coefficients = vertical.coefficients[y];
|
|
14042
|
+
const sourceRow = start * rowStride;
|
|
14043
|
+
const targetRow = (y + target.padTop) * target.inputWidth + target.padLeft;
|
|
14029
14044
|
for (let x = 0; x < resizedWidth; x += 1) {
|
|
14030
|
-
let
|
|
14045
|
+
let red = BICUBIC_ROUNDING;
|
|
14046
|
+
let green = BICUBIC_ROUNDING;
|
|
14047
|
+
let blue = BICUBIC_ROUNDING;
|
|
14048
|
+
let source2 = sourceRow + x * 3;
|
|
14031
14049
|
for (let index = 0; index < count; index += 1) {
|
|
14032
|
-
|
|
14050
|
+
const coefficient = coefficients[index];
|
|
14051
|
+
red += intermediate[source2] * coefficient;
|
|
14052
|
+
green += intermediate[source2 + 1] * coefficient;
|
|
14053
|
+
blue += intermediate[source2 + 2] * coefficient;
|
|
14054
|
+
source2 += rowStride;
|
|
14033
14055
|
}
|
|
14034
|
-
|
|
14056
|
+
const offset = targetRow + x;
|
|
14057
|
+
target.data[offset] = target.values[clipBicubic(red)];
|
|
14058
|
+
target.data[plane + offset] = target.values[256 + clipBicubic(green)];
|
|
14059
|
+
target.data[plane * 2 + offset] = target.values[512 + clipBicubic(blue)];
|
|
14035
14060
|
}
|
|
14036
14061
|
}
|
|
14037
|
-
return output;
|
|
14038
14062
|
}
|
|
14039
14063
|
function preprocessImage(raster, preprocessing) {
|
|
14040
14064
|
const inputWidth = preprocessing.size.width;
|
|
@@ -14061,18 +14085,35 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
14061
14085
|
const mean = normalize ? preprocessing.mean ?? [0, 0, 0] : [0, 0, 0];
|
|
14062
14086
|
const std = normalize ? preprocessing.std ?? [1, 1, 1] : [1, 1, 1];
|
|
14063
14087
|
const interpolation2 = preprocessing.interpolation ?? "bilinear";
|
|
14088
|
+
const bicubicValues = doResize && interpolation2 === "bicubic" ? new Float32Array(256 * 3) : void 0;
|
|
14064
14089
|
for (let channel = 0; channel < 3; channel += 1) {
|
|
14065
14090
|
const padding = normalize && mean[channel] !== 0 ? -mean[channel] / std[channel] : 0;
|
|
14066
14091
|
data.fill(padding, channel * plane, (channel + 1) * plane);
|
|
14067
|
-
|
|
14092
|
+
if (bicubicValues) {
|
|
14093
|
+
for (let pixel = 0; pixel < 256; pixel += 1) {
|
|
14094
|
+
const scaled = rescale ? pixel * preprocessing.rescaleFactor : pixel;
|
|
14095
|
+
bicubicValues[channel * 256 + pixel] = normalize ? (scaled - mean[channel]) / std[channel] : scaled;
|
|
14096
|
+
}
|
|
14097
|
+
continue;
|
|
14098
|
+
}
|
|
14068
14099
|
for (let y = 0; y < resizedHeight; y += 1) {
|
|
14069
14100
|
for (let x = 0; x < resizedWidth; x += 1) {
|
|
14070
|
-
const pixel =
|
|
14101
|
+
const pixel = doResize ? bilinearChannel(raster, x, y, resizedWidth, resizedHeight, channel) : sampleChannel(raster, x, y, channel);
|
|
14071
14102
|
const scaled = rescale ? pixel * preprocessing.rescaleFactor : pixel;
|
|
14072
14103
|
data[channel * plane + (y + padTop) * inputWidth + x + padLeft] = normalize ? (scaled - mean[channel]) / std[channel] : scaled;
|
|
14073
14104
|
}
|
|
14074
14105
|
}
|
|
14075
14106
|
}
|
|
14107
|
+
if (bicubicValues) {
|
|
14108
|
+
writeBicubicChannels(raster, resizedWidth, resizedHeight, {
|
|
14109
|
+
data,
|
|
14110
|
+
inputWidth,
|
|
14111
|
+
inputHeight,
|
|
14112
|
+
padLeft,
|
|
14113
|
+
padTop,
|
|
14114
|
+
values: bicubicValues
|
|
14115
|
+
});
|
|
14116
|
+
}
|
|
14076
14117
|
return {
|
|
14077
14118
|
data,
|
|
14078
14119
|
dims: [1, 3, inputHeight, inputWidth],
|
|
@@ -15069,11 +15110,36 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15069
15110
|
}
|
|
15070
15111
|
|
|
15071
15112
|
// src/model/download.ts
|
|
15113
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
15114
|
+
var RetryableDownloadError = class extends PPDetectionError {
|
|
15115
|
+
constructor(message, details = {}, cause) {
|
|
15116
|
+
super("MODEL_DOWNLOAD_FAILED", message, details, { cause });
|
|
15117
|
+
}
|
|
15118
|
+
};
|
|
15119
|
+
function resolveDownloadOptions(options = {}) {
|
|
15120
|
+
if (options === null || typeof options !== "object")
|
|
15121
|
+
throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u914D\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
15122
|
+
const resolved = {
|
|
15123
|
+
timeoutMs: options.timeoutMs === void 0 ? 18e4 : options.timeoutMs,
|
|
15124
|
+
idleTimeoutMs: options.idleTimeoutMs === void 0 ? 3e4 : options.idleTimeoutMs,
|
|
15125
|
+
maxRetries: options.maxRetries === void 0 ? 2 : options.maxRetries
|
|
15126
|
+
};
|
|
15127
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
15128
|
+
const maximum = key === "maxRetries" ? 5 : 2147483647;
|
|
15129
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > maximum)
|
|
15130
|
+
throw new PPDetectionError("INVALID_INPUT", "\u4E0B\u8F7D\u53C2\u6570\u5FC5\u987B\u4E3A\u5141\u8BB8\u8303\u56F4\u5185\u7684\u5B89\u5168\u6574\u6570", {
|
|
15131
|
+
parameter: key,
|
|
15132
|
+
value,
|
|
15133
|
+
maximum
|
|
15134
|
+
});
|
|
15135
|
+
}
|
|
15136
|
+
return resolved;
|
|
15137
|
+
}
|
|
15072
15138
|
function now() {
|
|
15073
15139
|
return globalThis.performance?.now() ?? Date.now();
|
|
15074
15140
|
}
|
|
15075
15141
|
function aborted(error, signal) {
|
|
15076
|
-
return signal?.aborted === true || error instanceof
|
|
15142
|
+
return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
|
|
15077
15143
|
}
|
|
15078
15144
|
function throwIfAborted3(signal) {
|
|
15079
15145
|
if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88");
|
|
@@ -15101,106 +15167,206 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15101
15167
|
});
|
|
15102
15168
|
}
|
|
15103
15169
|
}
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
throwIfAborted3(signal);
|
|
15109
|
-
onProgress?.({ loadedBytes: bytes2.byteLength, totalBytes });
|
|
15110
|
-
return bytes2;
|
|
15170
|
+
function cancelQuietly(cancel) {
|
|
15171
|
+
try {
|
|
15172
|
+
void Promise.resolve(cancel()).catch(() => void 0);
|
|
15173
|
+
} catch {
|
|
15111
15174
|
}
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15175
|
+
}
|
|
15176
|
+
async function downloadAttempt(asset, fetcher, options, policy, attempt) {
|
|
15177
|
+
const controller = new AbortController();
|
|
15178
|
+
let response;
|
|
15179
|
+
let reader;
|
|
15180
|
+
let active = true;
|
|
15181
|
+
let cancelledBody = false;
|
|
15182
|
+
let stopError;
|
|
15183
|
+
let totalTimer;
|
|
15184
|
+
let idleTimer;
|
|
15185
|
+
let rejectStop;
|
|
15186
|
+
const stopped = new Promise((_resolve, reject) => {
|
|
15187
|
+
rejectStop = reject;
|
|
15188
|
+
});
|
|
15189
|
+
void stopped.catch(() => void 0);
|
|
15190
|
+
const cancelBody = (reason) => {
|
|
15191
|
+
if (cancelledBody || !reader && !response?.body) return;
|
|
15192
|
+
cancelledBody = true;
|
|
15193
|
+
if (reader) cancelQuietly(() => reader.cancel(reason));
|
|
15194
|
+
else cancelQuietly(() => response.body.cancel(reason));
|
|
15195
|
+
};
|
|
15196
|
+
const stop = (error) => {
|
|
15197
|
+
if (!active || stopError) return;
|
|
15198
|
+
stopError = error;
|
|
15199
|
+
rejectStop(error);
|
|
15200
|
+
controller.abort();
|
|
15201
|
+
cancelBody(error);
|
|
15202
|
+
};
|
|
15203
|
+
const abort = () => stop(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
|
|
15204
|
+
const renewIdle = () => {
|
|
15205
|
+
clearTimeout(idleTimer);
|
|
15206
|
+
if (policy.idleTimeoutMs > 0)
|
|
15207
|
+
idleTimer = setTimeout(
|
|
15208
|
+
() => stop(
|
|
15209
|
+
new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u957F\u65F6\u95F4\u6CA1\u6709\u65B0\u589E\u5B57\u8282", {
|
|
15210
|
+
idleTimeoutMs: policy.idleTimeoutMs
|
|
15211
|
+
})
|
|
15212
|
+
),
|
|
15213
|
+
policy.idleTimeoutMs
|
|
15214
|
+
);
|
|
15215
|
+
};
|
|
15216
|
+
const checkActive = () => {
|
|
15217
|
+
if (stopError) throw stopError;
|
|
15218
|
+
throwIfAborted3(options.signal);
|
|
15219
|
+
};
|
|
15220
|
+
const network = async (operation) => {
|
|
15221
|
+
try {
|
|
15222
|
+
checkActive();
|
|
15223
|
+
const value = await Promise.race([operation(), stopped]);
|
|
15224
|
+
checkActive();
|
|
15225
|
+
return value;
|
|
15226
|
+
} catch (error) {
|
|
15227
|
+
if (stopError) throw stopError;
|
|
15228
|
+
if (aborted(error, options.signal))
|
|
15229
|
+
throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
|
|
15230
|
+
if (error instanceof PPDetectionError) throw error;
|
|
15231
|
+
throw new RetryableDownloadError(
|
|
15232
|
+
"\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u6216\u54CD\u5E94\u8BFB\u53D6\u5931\u8D25",
|
|
15233
|
+
{ sourceKind: asset.source.kind, downloadUrl: asset.source.downloadUrl },
|
|
15234
|
+
error
|
|
15235
|
+
);
|
|
15236
|
+
}
|
|
15237
|
+
};
|
|
15238
|
+
const report = (loadedBytes) => {
|
|
15239
|
+
checkActive();
|
|
15240
|
+
options.onProgress?.({
|
|
15241
|
+
loadedBytes,
|
|
15242
|
+
totalBytes: asset.source.bytes,
|
|
15243
|
+
attempt,
|
|
15244
|
+
maxAttempts: policy.maxRetries + 1
|
|
15245
|
+
});
|
|
15246
|
+
checkActive();
|
|
15247
|
+
};
|
|
15248
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
15115
15249
|
try {
|
|
15250
|
+
throwIfAborted3(options.signal);
|
|
15251
|
+
if (policy.timeoutMs > 0)
|
|
15252
|
+
totalTimer = setTimeout(
|
|
15253
|
+
() => stop(
|
|
15254
|
+
new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8D85\u8FC7\u8BF7\u6C42\u603B\u65F6\u9650", { timeoutMs: policy.timeoutMs })
|
|
15255
|
+
),
|
|
15256
|
+
policy.timeoutMs
|
|
15257
|
+
);
|
|
15258
|
+
renewIdle();
|
|
15259
|
+
report(0);
|
|
15260
|
+
response = await network(
|
|
15261
|
+
() => Promise.resolve(
|
|
15262
|
+
fetcher(asset.source.downloadUrl, {
|
|
15263
|
+
signal: controller.signal,
|
|
15264
|
+
...attempt > 1 ? { cache: "reload" } : {}
|
|
15265
|
+
})
|
|
15266
|
+
).then((value) => {
|
|
15267
|
+
response = value;
|
|
15268
|
+
if (!active || stopError) cancelBody(stopError);
|
|
15269
|
+
return value;
|
|
15270
|
+
})
|
|
15271
|
+
);
|
|
15272
|
+
if (!response.ok) {
|
|
15273
|
+
const details = {
|
|
15274
|
+
sourceKind: asset.source.kind,
|
|
15275
|
+
status: response.status,
|
|
15276
|
+
statusText: response.statusText
|
|
15277
|
+
};
|
|
15278
|
+
if (RETRYABLE_STATUSES.has(response.status))
|
|
15279
|
+
throw new RetryableDownloadError("\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u53EF\u91CD\u8BD5\u72B6\u6001", details);
|
|
15280
|
+
throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", details);
|
|
15281
|
+
}
|
|
15282
|
+
validatePartialResponse(response, asset.source.bytes);
|
|
15283
|
+
contentLength(response, asset.source.bytes);
|
|
15284
|
+
if (!response.body) {
|
|
15285
|
+
const bytes2 = await network(() => response.arrayBuffer());
|
|
15286
|
+
report(bytes2.byteLength);
|
|
15287
|
+
return bytes2;
|
|
15288
|
+
}
|
|
15289
|
+
reader = response.body.getReader();
|
|
15290
|
+
const chunks = [];
|
|
15291
|
+
let loadedBytes = 0;
|
|
15116
15292
|
while (true) {
|
|
15117
|
-
|
|
15118
|
-
const chunk = await reader.read();
|
|
15293
|
+
const chunk = await network(() => reader.read());
|
|
15119
15294
|
if (chunk.done) break;
|
|
15295
|
+
if (chunk.value.byteLength === 0) continue;
|
|
15120
15296
|
loadedBytes += chunk.value.byteLength;
|
|
15121
|
-
if (loadedBytes >
|
|
15297
|
+
if (loadedBytes > asset.source.bytes)
|
|
15122
15298
|
throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u54CD\u5E94\u8D85\u8FC7\u6E05\u5355\u58F0\u660E\u5927\u5C0F", {
|
|
15123
|
-
expectedBytes,
|
|
15299
|
+
expectedBytes: asset.source.bytes,
|
|
15124
15300
|
loadedBytes
|
|
15125
15301
|
});
|
|
15126
|
-
}
|
|
15127
15302
|
chunks.push(chunk.value);
|
|
15128
|
-
|
|
15303
|
+
renewIdle();
|
|
15304
|
+
report(loadedBytes);
|
|
15305
|
+
}
|
|
15306
|
+
const bytes = new Uint8Array(loadedBytes);
|
|
15307
|
+
let offset = 0;
|
|
15308
|
+
for (const chunk of chunks) {
|
|
15309
|
+
bytes.set(chunk, offset);
|
|
15310
|
+
offset += chunk.byteLength;
|
|
15129
15311
|
}
|
|
15312
|
+
report(loadedBytes);
|
|
15313
|
+
return bytes.buffer;
|
|
15130
15314
|
} catch (error) {
|
|
15315
|
+
controller.abort();
|
|
15316
|
+
cancelBody(error);
|
|
15317
|
+
throw error;
|
|
15318
|
+
} finally {
|
|
15319
|
+
active = false;
|
|
15320
|
+
clearTimeout(totalTimer);
|
|
15321
|
+
clearTimeout(idleTimer);
|
|
15322
|
+
options.signal?.removeEventListener("abort", abort);
|
|
15131
15323
|
try {
|
|
15132
|
-
|
|
15324
|
+
reader?.releaseLock();
|
|
15133
15325
|
} catch {
|
|
15134
15326
|
}
|
|
15135
|
-
throw error;
|
|
15136
|
-
} finally {
|
|
15137
|
-
reader.releaseLock();
|
|
15138
15327
|
}
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15328
|
+
}
|
|
15329
|
+
function waitForRetry(delayMs, signal) {
|
|
15330
|
+
throwIfAborted3(signal);
|
|
15331
|
+
return new Promise((resolve, reject) => {
|
|
15332
|
+
const abort = () => {
|
|
15333
|
+
clearTimeout(timer);
|
|
15334
|
+
signal?.removeEventListener("abort", abort);
|
|
15335
|
+
reject(new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88"));
|
|
15336
|
+
};
|
|
15337
|
+
const timer = setTimeout(() => {
|
|
15338
|
+
signal?.removeEventListener("abort", abort);
|
|
15339
|
+
resolve();
|
|
15340
|
+
}, delayMs);
|
|
15341
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
15342
|
+
if (signal?.aborted) abort();
|
|
15343
|
+
});
|
|
15147
15344
|
}
|
|
15148
15345
|
async function loadModelAsset(asset, options = {}) {
|
|
15149
15346
|
throwIfAborted3(options.signal);
|
|
15347
|
+
const policy = resolveDownloadOptions(options.download);
|
|
15150
15348
|
const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
|
|
15151
15349
|
if (!fetcher)
|
|
15152
15350
|
throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709 fetch API", {
|
|
15153
15351
|
sourceKind: asset.source.kind
|
|
15154
15352
|
});
|
|
15155
15353
|
const downloadStarted = now();
|
|
15156
|
-
let response;
|
|
15157
|
-
try {
|
|
15158
|
-
response = await fetcher(asset.source.downloadUrl, { signal: options.signal });
|
|
15159
|
-
} catch (error) {
|
|
15160
|
-
if (aborted(error, options.signal))
|
|
15161
|
-
throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
|
|
15162
|
-
throw new PPDetectionError(
|
|
15163
|
-
"MODEL_DOWNLOAD_FAILED",
|
|
15164
|
-
"\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25",
|
|
15165
|
-
{
|
|
15166
|
-
sourceKind: asset.source.kind,
|
|
15167
|
-
downloadUrl: asset.source.downloadUrl
|
|
15168
|
-
},
|
|
15169
|
-
{ cause: error }
|
|
15170
|
-
);
|
|
15171
|
-
}
|
|
15172
|
-
if (!response.ok) {
|
|
15173
|
-
throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
|
|
15174
|
-
sourceKind: asset.source.kind,
|
|
15175
|
-
status: response.status,
|
|
15176
|
-
statusText: response.statusText
|
|
15177
|
-
});
|
|
15178
|
-
}
|
|
15179
|
-
validatePartialResponse(response, asset.source.bytes);
|
|
15180
15354
|
let bytes;
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
{ cause: error }
|
|
15192
|
-
);
|
|
15355
|
+
for (let attempt = 1; ; attempt++) {
|
|
15356
|
+
throwIfAborted3(options.signal);
|
|
15357
|
+
try {
|
|
15358
|
+
bytes = await downloadAttempt(asset, fetcher, options, policy, attempt);
|
|
15359
|
+
break;
|
|
15360
|
+
} catch (error) {
|
|
15361
|
+
throwIfAborted3(options.signal);
|
|
15362
|
+
if (!(error instanceof RetryableDownloadError) || attempt > policy.maxRetries) throw error;
|
|
15363
|
+
await waitForRetry(Math.min(500 * 2 ** (attempt - 1), 4e3), options.signal);
|
|
15364
|
+
}
|
|
15193
15365
|
}
|
|
15194
15366
|
const modelDownloadMs = now() - downloadStarted;
|
|
15195
15367
|
const integrityStarted = now();
|
|
15196
15368
|
await verifyModelIntegrity(bytes, asset.source, options.signal);
|
|
15197
|
-
return {
|
|
15198
|
-
bytes,
|
|
15199
|
-
timings: {
|
|
15200
|
-
modelDownloadMs,
|
|
15201
|
-
integrityMs: now() - integrityStarted
|
|
15202
|
-
}
|
|
15203
|
-
};
|
|
15369
|
+
return { bytes, timings: { modelDownloadMs, integrityMs: now() - integrityStarted } };
|
|
15204
15370
|
}
|
|
15205
15371
|
|
|
15206
15372
|
// src/model/source-resolver.ts
|
|
@@ -15267,6 +15433,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15267
15433
|
}
|
|
15268
15434
|
var ModelManager = class {
|
|
15269
15435
|
fetcher;
|
|
15436
|
+
download;
|
|
15270
15437
|
cache;
|
|
15271
15438
|
coordinator;
|
|
15272
15439
|
lifecycle = new AbortController();
|
|
@@ -15275,6 +15442,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15275
15442
|
disposed = false;
|
|
15276
15443
|
disposePromise;
|
|
15277
15444
|
constructor(options = {}) {
|
|
15445
|
+
this.download = resolveDownloadOptions(options.download);
|
|
15278
15446
|
this.fetcher = options.fetcher;
|
|
15279
15447
|
this.cache = createCache(options.cache);
|
|
15280
15448
|
this.coordinator = coordinateCache(this.cache);
|
|
@@ -15371,6 +15539,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15371
15539
|
try {
|
|
15372
15540
|
loaded = await loadModelAsset(asset, {
|
|
15373
15541
|
fetcher: this.fetcher,
|
|
15542
|
+
download: this.download,
|
|
15374
15543
|
signal: options.signal,
|
|
15375
15544
|
onProgress: options.onProgress
|
|
15376
15545
|
});
|
|
@@ -15855,7 +16024,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15855
16024
|
};
|
|
15856
16025
|
|
|
15857
16026
|
// src/index.ts
|
|
15858
|
-
var CURRENT_SDK_VERSION = "0.3.
|
|
16027
|
+
var CURRENT_SDK_VERSION = "0.3.2";
|
|
15859
16028
|
function probePPDetectionCapabilities(options = {}) {
|
|
15860
16029
|
return probeCapabilities(options);
|
|
15861
16030
|
}
|
|
@@ -15980,6 +16149,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15980
16149
|
simpleManifest(runtimeManifest)
|
|
15981
16150
|
);
|
|
15982
16151
|
const modelManager = new ModelManager({
|
|
16152
|
+
download: options.download,
|
|
15983
16153
|
cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
|
|
15984
16154
|
});
|
|
15985
16155
|
let executor;
|