web-sdk-pp-detection 0.3.1 → 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 +202 -73
- package/dist/browser-global.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +202 -73
- 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
|
@@ -15110,11 +15110,36 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15110
15110
|
}
|
|
15111
15111
|
|
|
15112
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
|
+
}
|
|
15113
15138
|
function now() {
|
|
15114
15139
|
return globalThis.performance?.now() ?? Date.now();
|
|
15115
15140
|
}
|
|
15116
15141
|
function aborted(error, signal) {
|
|
15117
|
-
return signal?.aborted === true || error instanceof
|
|
15142
|
+
return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
|
|
15118
15143
|
}
|
|
15119
15144
|
function throwIfAborted3(signal) {
|
|
15120
15145
|
if (signal?.aborted) throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88");
|
|
@@ -15142,106 +15167,206 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15142
15167
|
});
|
|
15143
15168
|
}
|
|
15144
15169
|
}
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
throwIfAborted3(signal);
|
|
15150
|
-
onProgress?.({ loadedBytes: bytes2.byteLength, totalBytes });
|
|
15151
|
-
return bytes2;
|
|
15170
|
+
function cancelQuietly(cancel) {
|
|
15171
|
+
try {
|
|
15172
|
+
void Promise.resolve(cancel()).catch(() => void 0);
|
|
15173
|
+
} catch {
|
|
15152
15174
|
}
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
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 });
|
|
15156
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;
|
|
15157
15292
|
while (true) {
|
|
15158
|
-
|
|
15159
|
-
const chunk = await reader.read();
|
|
15293
|
+
const chunk = await network(() => reader.read());
|
|
15160
15294
|
if (chunk.done) break;
|
|
15295
|
+
if (chunk.value.byteLength === 0) continue;
|
|
15161
15296
|
loadedBytes += chunk.value.byteLength;
|
|
15162
|
-
if (loadedBytes >
|
|
15297
|
+
if (loadedBytes > asset.source.bytes)
|
|
15163
15298
|
throw new PPDetectionError("MODEL_INTEGRITY_FAILED", "\u6A21\u578B\u54CD\u5E94\u8D85\u8FC7\u6E05\u5355\u58F0\u660E\u5927\u5C0F", {
|
|
15164
|
-
expectedBytes,
|
|
15299
|
+
expectedBytes: asset.source.bytes,
|
|
15165
15300
|
loadedBytes
|
|
15166
15301
|
});
|
|
15167
|
-
}
|
|
15168
15302
|
chunks.push(chunk.value);
|
|
15169
|
-
|
|
15303
|
+
renewIdle();
|
|
15304
|
+
report(loadedBytes);
|
|
15170
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;
|
|
15311
|
+
}
|
|
15312
|
+
report(loadedBytes);
|
|
15313
|
+
return bytes.buffer;
|
|
15171
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);
|
|
15172
15323
|
try {
|
|
15173
|
-
|
|
15324
|
+
reader?.releaseLock();
|
|
15174
15325
|
} catch {
|
|
15175
15326
|
}
|
|
15176
|
-
throw error;
|
|
15177
|
-
} finally {
|
|
15178
|
-
reader.releaseLock();
|
|
15179
|
-
}
|
|
15180
|
-
const bytes = new Uint8Array(loadedBytes);
|
|
15181
|
-
let offset = 0;
|
|
15182
|
-
for (const chunk of chunks) {
|
|
15183
|
-
bytes.set(chunk, offset);
|
|
15184
|
-
offset += chunk.byteLength;
|
|
15185
15327
|
}
|
|
15186
|
-
|
|
15187
|
-
|
|
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
|
+
});
|
|
15188
15344
|
}
|
|
15189
15345
|
async function loadModelAsset(asset, options = {}) {
|
|
15190
15346
|
throwIfAborted3(options.signal);
|
|
15347
|
+
const policy = resolveDownloadOptions(options.download);
|
|
15191
15348
|
const fetcher = options.fetcher ?? globalThis.fetch?.bind(globalThis);
|
|
15192
15349
|
if (!fetcher)
|
|
15193
15350
|
throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u5F53\u524D\u73AF\u5883\u6CA1\u6709 fetch API", {
|
|
15194
15351
|
sourceKind: asset.source.kind
|
|
15195
15352
|
});
|
|
15196
15353
|
const downloadStarted = now();
|
|
15197
|
-
let response;
|
|
15198
|
-
try {
|
|
15199
|
-
response = await fetcher(asset.source.downloadUrl, { signal: options.signal });
|
|
15200
|
-
} catch (error) {
|
|
15201
|
-
if (aborted(error, options.signal))
|
|
15202
|
-
throw new PPDetectionError("ABORTED", "\u6A21\u578B\u4E0B\u8F7D\u5DF2\u53D6\u6D88", {}, { cause: error });
|
|
15203
|
-
throw new PPDetectionError(
|
|
15204
|
-
"MODEL_DOWNLOAD_FAILED",
|
|
15205
|
-
"\u6A21\u578B\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25",
|
|
15206
|
-
{
|
|
15207
|
-
sourceKind: asset.source.kind,
|
|
15208
|
-
downloadUrl: asset.source.downloadUrl
|
|
15209
|
-
},
|
|
15210
|
-
{ cause: error }
|
|
15211
|
-
);
|
|
15212
|
-
}
|
|
15213
|
-
if (!response.ok) {
|
|
15214
|
-
throw new PPDetectionError("MODEL_DOWNLOAD_FAILED", "\u6A21\u578B\u4E0B\u8F7D\u8FD4\u56DE\u975E\u6210\u529F\u72B6\u6001", {
|
|
15215
|
-
sourceKind: asset.source.kind,
|
|
15216
|
-
status: response.status,
|
|
15217
|
-
statusText: response.statusText
|
|
15218
|
-
});
|
|
15219
|
-
}
|
|
15220
|
-
validatePartialResponse(response, asset.source.bytes);
|
|
15221
15354
|
let bytes;
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
{ cause: error }
|
|
15233
|
-
);
|
|
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
|
+
}
|
|
15234
15365
|
}
|
|
15235
15366
|
const modelDownloadMs = now() - downloadStarted;
|
|
15236
15367
|
const integrityStarted = now();
|
|
15237
15368
|
await verifyModelIntegrity(bytes, asset.source, options.signal);
|
|
15238
|
-
return {
|
|
15239
|
-
bytes,
|
|
15240
|
-
timings: {
|
|
15241
|
-
modelDownloadMs,
|
|
15242
|
-
integrityMs: now() - integrityStarted
|
|
15243
|
-
}
|
|
15244
|
-
};
|
|
15369
|
+
return { bytes, timings: { modelDownloadMs, integrityMs: now() - integrityStarted } };
|
|
15245
15370
|
}
|
|
15246
15371
|
|
|
15247
15372
|
// src/model/source-resolver.ts
|
|
@@ -15308,6 +15433,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15308
15433
|
}
|
|
15309
15434
|
var ModelManager = class {
|
|
15310
15435
|
fetcher;
|
|
15436
|
+
download;
|
|
15311
15437
|
cache;
|
|
15312
15438
|
coordinator;
|
|
15313
15439
|
lifecycle = new AbortController();
|
|
@@ -15316,6 +15442,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15316
15442
|
disposed = false;
|
|
15317
15443
|
disposePromise;
|
|
15318
15444
|
constructor(options = {}) {
|
|
15445
|
+
this.download = resolveDownloadOptions(options.download);
|
|
15319
15446
|
this.fetcher = options.fetcher;
|
|
15320
15447
|
this.cache = createCache(options.cache);
|
|
15321
15448
|
this.coordinator = coordinateCache(this.cache);
|
|
@@ -15412,6 +15539,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15412
15539
|
try {
|
|
15413
15540
|
loaded = await loadModelAsset(asset, {
|
|
15414
15541
|
fetcher: this.fetcher,
|
|
15542
|
+
download: this.download,
|
|
15415
15543
|
signal: options.signal,
|
|
15416
15544
|
onProgress: options.onProgress
|
|
15417
15545
|
});
|
|
@@ -15896,7 +16024,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
15896
16024
|
};
|
|
15897
16025
|
|
|
15898
16026
|
// src/index.ts
|
|
15899
|
-
var CURRENT_SDK_VERSION = "0.3.
|
|
16027
|
+
var CURRENT_SDK_VERSION = "0.3.2";
|
|
15900
16028
|
function probePPDetectionCapabilities(options = {}) {
|
|
15901
16029
|
return probeCapabilities(options);
|
|
15902
16030
|
}
|
|
@@ -16021,6 +16149,7 @@ ${u}`, c = n.createShaderModule({ code: d, label: e.name });
|
|
|
16021
16149
|
simpleManifest(runtimeManifest)
|
|
16022
16150
|
);
|
|
16023
16151
|
const modelManager = new ModelManager({
|
|
16152
|
+
download: options.download,
|
|
16024
16153
|
cache: options.cache === false ? false : options.cache === "memory" ? "memory" : void 0
|
|
16025
16154
|
});
|
|
16026
16155
|
let executor;
|