第3.3篇图生视频服务——任务轮询机制实现系列HarmonyOS 从入门到实践 · 画伴梦工厂实战难度⭐⭐⭐ 高级前置知识3.2 火山引擎 Seedream 文生图 API 对接涉及源文件products/default/src/main/ets/services/AIGenerationService.ets一、概述AI 视频生成与图片生成有一个本质区别视频生成是异步任务。调用文生图 API如第 3.2 篇的 Seedream通常可以在一次 HTTP 请求-响应周期内返回结果因为图片生成耗时相对较短数秒。但视频生成涉及数十帧图像的逐帧渲染、光流计算、拼接编码耗时可达数分钟。若客户端保持长连接等待不仅会耗尽连接池资源还容易因网络波动导致请求超时断开。画伴梦工厂的图生视频服务采用业界标准的异步任务模式Async Task Pattern客户端提交任务上传图片 Prompt获得一个唯一taskId服务端异步处理视频在服务端后台队列中生成客户端轮询状态以固定间隔查询任务状态直到完成或失败客户端下载结果获取远程视频 URL下载到本地沙箱这一模式并非 HarmonyOS 专属但在 ArkTS 中的实现细节——http.createHttp的使用、setTimeout模拟轮询间隔、onStatus回调传递进度——值得深入拆解。本文聚焦AIGenerationService类中与图生视频相关的核心方法包括任务创建、状态轮询、超时控制、视频下载以及它们如何与RecognitionWaitingPage的 UI 进度系统联动。二、异步任务模式generateVideo 入口generateVideo是整个图生视频流程的入口方法它串联了四个步骤staticasyncgenerateVideo(imageUrl:string,prompt:string,onStatus?:(message:string)void):PromiseGeneratedVideo{constfinalPromptAIGenerationService.enrichPrompt(prompt);// Step 1: 图片压缩 Base64 编码constuploadBase64awaitAIGenerationService.prepareUploadBase64(imageUrl,onStatus);// Step 2: 创建视频生成任务consttaskawaitAIGenerationService.createImg2VideoTask(uploadBase64);consttaskIdtask.taskId;if(onStatus){onStatus(任务已提交正在生成动画);}// Step 3: 轮询等待完成constremoteVideoUrlawaitAIGenerationService.pollImg2VideoTask(taskId,onStatus);if(onStatus){onStatus(动画已生成正在保存到本地);}// Step 4: 下载视频到本地constlocalUriawaitAIGenerationService.downloadVideo(remoteVideoUrl,taskId);return{prompt:finalPrompt,videoUri:localUri,taskId:taskId,remoteVideoUrl:remoteVideoUrl};}设计要点方面说明onStatus回调可选的回调函数用于将服务端处理进度实时传递给 UI 层。页面调用时传入(message) { this.statusText message; }驱动 UI 文本更新四步串行每一步await等待上一步完成天然形成流程控制。若中间某步抛出异常整个 Promise 拒绝由调用方的try-catch统一处理错误传播内部方法不吞异常直接向上抛出。调用方RecognitionWaitingPage.startGeneration捕获后设置failed true返回的GeneratedVideo接口包含四个字段exportinterfaceGeneratedVideo{prompt:string;// 增强后的 PromptvideoUri:string;// 本地沙箱路径 (file://...)taskId:string;// 服务端任务 IDremoteVideoUrl:string;// 远端视频地址}三、任务提交createImg2VideoTask第一步是将经过压缩和 Base64 编码的图片数据提交到图生视频服务端获取一个唯一的taskId。privatestaticasynccreateImg2VideoTask(base64:string):PromiseGeneratedVideoTask{constrequesthttp.createHttp();try{constheaders{Content-Type:application/json};constbody:Img2VideoCreateRequest{base64:base64};constresponseawaitrequest.request(IMG2VIDEO_CREATE_URL,{method:http.RequestMethod.POST,expectDataType:http.HttpDataType.STRING,connectTimeout:30000,readTimeout:180000,// 3 分钟超时header:headers,extraData:JSON.stringify(body)});constresponseTextresponse.result.toString();constresponseBodyJSON.parse(responseText)asImg2VideoCreateResponse;consttaskIdAIGenerationService.pickTaskId(responseBody);return{taskId:taskId,imageUrl:responseBody.imgUrl?responseBody.imgUrl:};}finally{request.destroy();}}3.1 请求配置分析connectTimeout: 30000连接超时 30 秒。若服务端不可达快速失败而非长期等待。readTimeout: 180000读取超时 3 分钟。考虑到上传 Base64 数据可能较大压缩后约 1.2MB 的文本服务端解码和处理需要时间给予充足的超时窗口。expectDataType: STRING期望响应以字符串形式返回便于直接JSON.parse。3.2 taskId 字段兼容处理不同版本的服务端接口可能返回不同大小写的字段名pickTaskId做了兼容privatestaticpickTaskId(responseBody:Img2VideoCreateResponse):string{if(responseBody.taskidresponseBody.taskid!){returnresponseBody.taskid;// 小写格式}if(responseBody.taskIdresponseBody.taskId!){returnresponseBody.taskId;// 驼峰格式}return;// 未找到后续调用方会抛异常}这种防御式编程在对接第三方服务时尤为重要——服务端接口字段命名变化不应导致客户端崩溃。四、轮询机制pollImg2VideoTask获得taskId后客户端进入核心的轮询循环。这是整个图生视频最复杂、最关键的逻辑。privatestaticasyncpollImg2VideoTask(taskId:string,onStatus?:(message:string)void):Promisestring{conststartedAtDate.now();letattempt0;letlastErrorMessage;while(Date.now()-startedAtMAX_SEEDANCE_WAIT_MS){attempt;if(onStatus){onStatus(正在等待动画生成第 attempt.toString() 次检查);}letresponseBody:Img2VideoStatusResponse;try{responseBodyawaitAIGenerationService.queryImg2VideoTask(taskId);}catch(error){lastErrorMessageAIGenerationService.getErrorMessage(errorasError);if(!AIGenerationService.canRetryTaskQuery(lastErrorMessage)){thrownewError(lastErrorMessage);}if(onStatus){onStatus(网络有点慢继续等待动画完成);}awaitAIGenerationService.sleep(POLL_INTERVAL_MS);continue;}constvideoUrlAIGenerationService.pickImg2VideoUrl(responseBody);if(responseBody.status1videoUrl!){returnvideoUrl;}if(responseBody.status!undefinedresponseBody.status0){thrownewError(responseBody.message?responseBody.message:视频生成任务失败);}awaitAIGenerationService.sleep(POLL_INTERVAL_MS);}thrownewError(视频生成超时请稍后重试);}4.1 轮询参数常量值说明POLL_INTERVAL_MS5000 (5s)每次轮询的时间间隔MAX_SEEDANCE_WAIT_MS360000 (6min)最大等待时间超过则判定为超时轮询间隔 5 秒是实践中的平衡值——太短会增加服务端压力太长则让用户等待反馈变得迟钝。4.2 轮询状态机┌─────────────┐ │ 查询任务状态 │ └──────┬──────┘ │ ┌────────────┼────────────┐ ▼ ▼ ▼ 网络异常 status1 status0 │ videoUrl │ ▼ │ ▼ canRetry? ───no──→ 抛出异常 抛出异常 │yes ▼ 等待 5s ──→ 继续轮询4.3 状态码映射服务端返回的status字段含义status 值含义客户端处理1生成完成检查url/sdUrl字段是否有值有则返回视频 URL0或正数非 1正在生成中继续等待5 秒后再次查询-1及以下负数生成失败抛出异常携带message字段中的错误描述undefined未知状态继续等待兜底保守策略关键判断逻辑// 完成条件status 1 且 videoUrl 不为空if(responseBody.status1videoUrl!){returnvideoUrl;}// 失败条件status 为负数if(responseBody.status!undefinedresponseBody.status0){thrownewError(responseBody.message?responseBody.message:视频生成任务失败);}4.4 URL 字段兼容与pickTaskId类似pickImg2VideoUrl也对不同字段名做了兼容privatestaticpickImg2VideoUrl(responseBody:Img2VideoStatusResponse):string{if(responseBody.urlresponseBody.url!){returnresponseBody.url;}if(responseBody.sdUrlresponseBody.sdUrl!){returnresponseBody.sdUrl;}return;}五、超时控制与重试逻辑5.1 硬超时Hard Timeout轮询循环使用Date.now()计算已耗时conststartedAtDate.now();while(Date.now()-startedAtMAX_SEEDANCE_WAIT_MS){// ... 轮询逻辑}thrownewError(视频生成超时请稍后重试);6 分钟是最长等待时间。如果超过这个时间服务端仍未完成客户端不再继续等待。超时异常中还会附带最近一次网络错误的信息lastErrorMessage帮助排查问题。5.2 网络错误重试当queryImg2VideoTask抛出异常时网络断开、DNS 解析失败、HTTP 5xx 等pollImg2VideoTask并不会立即放弃而是判断是否可以重试privatestaticcanRetryTaskQuery(message:string):boolean{returnmessage.indexOf(400)0message.indexOf(401)0message.indexOf(403)0message.indexOf(404)0;}重试策略| HTTP 状态码 | 是否重试 | 原因 ||-------------|---------|