任务 API
任务 API(xunlei.tasks)是微应用的核心能力:创建下载任务、查询进度、访问已下载文件。页面与事件脚本均可调用完整的任务 API,每个方法需要在 manifest 中声明对应的具体权限;事件上下文的任务控制方法另见钩子事件。
任务模型
一个任务(Task)的关键结构:
ts
interface Task {
type: 'single'; // 任务类型:普通任务为 single,任务组为 group
id: string; // 任务 ID
protocol: 'http' | 'bt' | 'ed2k'; // 协议类型
name: string; // 显示名称
meta: {
req: Request; // 下载请求(详情不返回 extra 请求头)
res: Resource; // 资源信息(文件列表、大小等)
opts: Options; // 下载选项(详情不返回 extra)
};
status: TaskStatus; // 任务状态
progress: {
used: number; // 下载已用时间(纳秒)
speed: number; // 下载速度 (byte/s)
downloaded: number; // 已下载字节数
};
size: number; // 总大小 (byte)
createdAt: string; // 创建时间(ISO 8601)
updatedAt: string;
}任务状态(TaskStatus):
| 状态 | 含义 |
|---|---|
ready | 已创建,等待下载 |
running | 下载中 |
pause | 已暂停 |
wait | 排队等待 |
error | 出错 |
done | 完成 |
创建任务 tasks.create
ts
const task = await xunlei.tasks.create({
req: {
url: 'https://example.com/file.zip',
},
opts: {
name: '示例文件.zip',
},
});入参:
ts
interface TaskCreateInput {
req: Request;
opts?: Options;
}
interface Request {
url: string; // 支持 http(s)、磁力链接、电驴链接和本地种子文件路径
extra?: {
header?: {
// 请求头(当前支持以下键,运行时名称透传、由宿主适配)
'User-Agent'?: string;
Referer?: string;
Cookie?: string;
Authorization?: string;
};
};
labels?: Record<string, string>; // 请求标签
}
interface Options {
name?: string; // 指定保存文件名
path?: string; // 指定保存路径,未指定时使用宿主默认下载目录
extra?: {
connections?: number; // 并发连接数
};
}示例:带请求头创建任务(部分下载源需要携带 Referer / Cookie 等请求头):
ts
await xunlei.tasks.create({
req: {
url: 'https://example.com/video.mp4',
extra: {
header: {
Referer: 'https://example.com/watch/123',
Cookie: savedCookie,
},
},
},
opts: { name: '视频.mp4' },
});示例:创建磁力 / BT 任务:
ts
await xunlei.tasks.create({
req: { url: 'magnet:?xt=urn:btih:...' },
});TIP
创建任务需要 tasks.create 权限。
创建任务组 tasks.createGroup
多个任务需要落在同一个下载目录时(如一个剧集的多集、一个 Release 的多个附件),用 createGroup 一次创建任务组:
ts
const group = await xunlei.tasks.createGroup({
name: 'my-repo-release', // 组目录名,不允许路径分隔符、. 或 ..
tasks: [
{ req: { url: 'https://example.com/a.zip' } },
{ req: { url: 'https://example.com/b.zip' }, opts: { name: '第二个文件.zip' } },
],
// opts: { path: '/parent-dir' }, // 可选:父目录,未指定时使用宿主默认下载目录
});入参与 create 的对应关系:
| 字段 | 说明 |
|---|---|
name | 组目录名(单个目录名,不能包含路径分隔符) |
tasks | 子任务列表,每项与 create 的入参结构相同(req + 可选 opts) |
opts.path | 组所在的父目录,可省略 |
子任务的 opts.path 只支持相对路径(相对组目录);调用 createGroup 也需要声明 tasks.create 权限,无需额外权限。
查询任务列表 tasks.list
list 返回任务 ID 列表,需要结合 detail 获取详情:
ts
const result = await xunlei.tasks.list({
offset: 0,
limit: 50,
status: ['running', 'wait'], // 可选:按状态过滤,支持单个或数组
sort: 'createdAtDesc', // createdAtDesc(默认)| createdAtAsc
});
for (const id of result.ids) {
const task = await xunlei.tasks.detail({ id });
xunlei.logger.info(task.name, task.status, task.progress.speed);
}返回结构:
ts
interface TaskListResult {
ids: string[]; // 当前页的任务 ID 列表
total: number; // 符合筛选条件的任务总数
}查询任务详情 tasks.detail
ts
const task = await xunlei.tasks.detail({ id: 'task-id' });
xunlei.logger.info(task.status); // 'running'
xunlei.logger.info(task.progress.speed); // 下载速度
xunlei.logger.info(task.meta.res.files); // 文件列表 [{ name, path, size, ... }]detail 的返回值可能是普通任务(type: 'single')或任务组(type: 'group'),两者都以 id 标识,通过 type 字段区分:
ts
const result = await xunlei.tasks.detail({ id: resultId });
if (result.type === 'group') {
// 任务组:result.opts 为 { path, name },result.children 为子任务列表(不嵌套任务组)
for (const child of result.children) {
xunlei.logger.info(child.name, child.status);
}
} else {
// 普通任务
xunlei.logger.info(result.progress.speed);
}渲染任务进度的典型模式(页面轮询):
ts
async function renderTasks() {
const { ids } = await xunlei.tasks.list({ limit: 20, sort: 'createdAtDesc' });
const tasks = await Promise.all(ids.map((id) => xunlei.tasks.detail({ id })));
// 更新页面 UI
}
setInterval(renderTasks, 1000);修改任务 tasks.update
ts
const task = await xunlei.tasks.update({
id: 'task-id',
req: {
labels: { source: 'miniapp', channel: 'github' }, // 替换任务请求的标签(全量覆盖)
},
});当前仅支持更新 req.labels;不支持修改文件名 / 下载地址,也不支持通过此 API 暂停 / 恢复任务。修改下载地址或请求头的场景在事件脚本中完成,见钩子事件 · onStart。
删除任务 tasks.delete
ts
const result = await xunlei.tasks.delete({
id: 'task-id',
deleteFiles: true, // 可选:同时删除已下载的文件,默认 false
});删除不可恢复;删除不存在的任务返回 { id, deleted: false } 而不报错。
访问任务文件 tasks.file.access
获取任务文件的只读临时 HTTP URL,可在页面中直接 fetch 读取内容(如预览已下载的图片、解析已完成的页面文件):
ts
const access = await xunlei.tasks.file.access({
taskId: 'task-id',
fileIndex: 0, // 文件下标,对应 task.meta.res.files 的下标
});
const response = await fetch(access.url);
const buffer = await response.arrayBuffer();- 返回的
url是临时地址,不保证长期有效,请即取即用; - 获得该 URL 等同于获得该文件内容的只读能力,不包含写入 / 删除能力;
- 需要
tasks.file.access权限(不要求同时声明tasks.detail)。
权限对照
| API | 所需权限 |
|---|---|
tasks.create | tasks.create |
tasks.createGroup | tasks.create(复用) |
tasks.list | tasks.list |
tasks.detail | tasks.detail |
tasks.file.access | tasks.file.access |
tasks.update | tasks.update |
tasks.delete | tasks.delete |
runtime.blob.* | blob |
Blob 与数据下载
页面生成的数据(Canvas 导出的图片、拼装的文本、合成的媒体流)没有下载地址,无法直接创建任务。xunlei.runtime.blob 负责把内存数据转换为可下载的临时 URL(需要在 manifest 中声明 blob 权限):
ts
const url: string = await xunlei.runtime.blob.createObjectURL(source, options?);
await xunlei.runtime.blob.revokeObjectURL(url);Blob 方式(简单场景)
ts
// Canvas 导出图片并创建下载任务
const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('图片生成失败'))), 'image/jpeg', 0.9);
});
const url = await xunlei.runtime.blob.createObjectURL(blob);
await xunlei.tasks.create({
req: { url },
opts: { name: 'cropped.jpg' },
});非空 Blob 自动使用自身的类型与大小,并支持断点续传(Range)。
Opener 方式(大文件 / 流式场景)
数据源是惰性打开的可重复读取流时,使用 opener 函数。典型场景:边从远端拉取边供下载器分片读取:
ts
const url = await xunlei.runtime.blob.createObjectURL(
async ({ offset, end }) => {
const response = await fetch(sourceUrl, {
headers: { Range: `bytes=${offset}-${end >= 0 ? end : ''}` },
});
return response.body!;
},
{
contentType: 'video/mp4',
size: contentLength, // 启用 Range 时必须提供总大小
range: true, // 声明数据源支持按区间读取
},
);opener 启用 Range 后,每次 HTTP 请求或并发分片都可能独立调用 opener,每次调用必须返回新的 ReadableStream。
使用注意
- 不要在
tasks.create返回后立即revokeObjectURL——下载器可能尚未开始读取;撤销后的 URL 禁止新请求,已开始的读取可以完成; - 运行环境销毁、微应用停用或数据源长时间空闲后,URL 会被自动回收,不要持久化保存该 URL;
runtime.blob需要blob权限,用它创建下载任务仍需tasks.create。


