feat: 功能优化

This commit is contained in:
2025-12-15 23:33:02 +08:00
parent 7f7551f74f
commit 870ea10351
36 changed files with 3289 additions and 40 deletions

View File

@@ -43,8 +43,10 @@ public class AppTikUserFileController {
@Parameter(description = "文件分类video/generate/audio/mix/voice", required = true)
@RequestParam("fileCategory") String fileCategory,
@Parameter(description = "视频封面 base64可选data URI 格式)")
@RequestParam(value = "coverBase64", required = false) String coverBase64) {
return success(userFileService.uploadFile(file, fileCategory, coverBase64));
@RequestParam(value = "coverBase64", required = false) String coverBase64,
@Parameter(description = "视频时长(秒)")
@RequestParam(value = "duration", required = false) Integer duration) {
return success(userFileService.uploadFile(file, fileCategory, coverBase64, duration));
}
@GetMapping("/page")

View File

@@ -79,5 +79,9 @@ public class TikUserFileDO extends TenantBaseDO {
* 文件描述
*/
private String description;
/**
* 视频时长(秒)
*/
private Integer duration;
}

View File

@@ -20,9 +20,10 @@ public interface TikUserFileService {
* @param file 文件
* @param fileCategory 文件分类video/generate/audio/mix/voice
* @param coverBase64 视频封面 base64可选data URI 格式)
* @param duration 视频时长(秒,可选)
* @return 文件编号
*/
Long uploadFile(MultipartFile file, String fileCategory, String coverBase64);
Long uploadFile(MultipartFile file, String fileCategory, String coverBase64, Integer duration);
/**
* 分页查询文件列表

View File

@@ -73,7 +73,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
private FileConfigService fileConfigService;
@Override
public Long uploadFile(MultipartFile file, String fileCategory, String coverBase64) {
public Long uploadFile(MultipartFile file, String fileCategory, String coverBase64, Integer duration) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
Long tenantId = TenantContextHolder.getTenantId();
@@ -151,7 +151,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
// ========== 第三阶段保存数据库在事务中如果失败则删除OSS文件 ==========
try {
return saveFileRecord(userId, file, fileCategory, fileUrl, filePath, coverBase64, baseDirectory, infraFileId);
return saveFileRecord(userId, file, fileCategory, fileUrl, filePath, coverBase64, baseDirectory, infraFileId, duration);
} catch (Exception e) {
// 数据库保存失败删除已上传的OSS文件
log.error("[uploadFile][保存数据库失败]", e);
@@ -165,7 +165,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
*/
@Transactional(rollbackFor = Exception.class)
public Long saveFileRecord(Long userId, MultipartFile file, String fileCategory,
String fileUrl, String filePath, String coverBase64, String baseDirectory, Long infraFileId) {
String fileUrl, String filePath, String coverBase64, String baseDirectory, Long infraFileId, Integer duration) {
// 7. 验证 infraFileId 不为空(必须在保存记录之前检查)
if (infraFileId == null) {
log.error("[saveFileRecord][infra_file.id 为空,无法保存文件记录,用户({})URL({})]", userId, fileUrl);
@@ -231,7 +231,8 @@ public class TikUserFileServiceImpl implements TikUserFileService {
.setFileUrl(fileUrl)
.setFilePath(filePath) // 保存完整的OSS路径由FileService生成
.setCoverUrl(coverUrl) // 设置封面URL如果有
.setCoverBase64(StrUtil.isNotBlank(coverBase64) ? coverBase64 : null); // 保存原始base64数据如果有
.setCoverBase64(StrUtil.isNotBlank(coverBase64) ? coverBase64 : null) // 保存原始base64数据如果有
.setDuration(duration); // 设置视频时长(如果有)
userFileMapper.insert(userFile);

View File

@@ -56,6 +56,9 @@ public class AppTikUserFileRespVO {
@Schema(description = "文件描述")
private String description;
@Schema(description = "视频时长(秒)")
private Integer duration;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@@ -24,5 +24,8 @@ public class AppTikUserFileUploadReqVO {
@Schema(description = "文件描述", example = "测试视频")
private String description;
@Schema(description = "视频时长(秒)", example = "60")
private Integer duration;
}

View File

@@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import cn.iocoder.yudao.module.tik.mix.vo.MixTaskSaveReqVO;
import java.util.*;
// 成功视频
@@ -205,4 +207,193 @@ public class BatchProduceAlignment {
return jobIdWithUrl.split(" : ")[1];
}
/**
* 计算裁剪参数
*
* @param sourceWidth 源素材宽度
* @param sourceHeight 源素材高度
* @param cropMode 裁剪模式center(居中裁剪)、smart(智能裁剪)、fill(填充模式)
* @return 裁剪参数Map包含X、Y、Width、Height
*/
private Map<String, Integer> calculateCropParams(int sourceWidth, int sourceHeight, String cropMode) {
Map<String, Integer> cropParams = new HashMap<>();
double targetRatio = 9.0 / 16.0; // 9:16竖屏比例
if ("fill".equals(cropMode)) {
// 填充模式:不裁剪,保持原尺寸
cropParams.put("X", 0);
cropParams.put("Y", 0);
cropParams.put("Width", sourceWidth);
cropParams.put("Height", sourceHeight);
} else if ("smart".equals(cropMode)) {
// 智能裁剪功能暂未开放,自动降级为居中裁剪
log.info("[裁剪模式] smart模式暂未开放自动降级为center模式");
double cropHeight = sourceHeight;
double cropWidth = cropHeight * targetRatio;
int cropX = (int) Math.round((sourceWidth - cropWidth) / 2);
int cropY = 0;
cropParams.put("X", cropX);
cropParams.put("Y", cropY);
cropParams.put("Width", (int) Math.round(cropWidth));
cropParams.put("Height", (int) Math.round(cropHeight));
} else {
// center模式居中裁剪默认
double cropHeight = sourceHeight;
double cropWidth = cropHeight * targetRatio;
int cropX = (int) Math.round((sourceWidth - cropWidth) / 2);
int cropY = 0;
cropParams.put("X", cropX);
cropParams.put("Y", cropY);
cropParams.put("Width", (int) Math.round(cropWidth));
cropParams.put("Height", (int) Math.round(cropHeight));
}
log.debug("[裁剪计算] 源尺寸={}x{}, 模式={}, 裁剪参数={}", sourceWidth, sourceHeight, cropMode, cropParams);
return cropParams;
}
/**
* 生成单个视频(支持随机截取起始点)
*
* 多视频差异化原理:
* - 每个视频使用随机截取起点,确保内容完全不同
* - 支持不同长度的素材ICE自动容错处理
* - 容错机制如果起点超出素材长度从0开始截取
*
* @param materials 素材列表包含fileUrl和duration
* @param videoIndex 视频序号0开始用于生成随机种子
* @param userId 用户ID
* @param cropMode 裁剪模式center(居中裁剪)、smart(智能裁剪)、fill(填充模式)
* @return jobId : outputUrl 格式字符串
*/
public String produceSingleVideoWithOffset(List<MixTaskSaveReqVO.MaterialItem> materials,
int videoIndex, Long userId, String cropMode) throws Exception {
if (iceClient == null) {
initClient();
}
JSONArray videoClipArray = new JSONArray();
JSONArray audioClipArray = new JSONArray();
float timelinePos = 0;
for (int i = 0; i < materials.size(); i++) {
MixTaskSaveReqVO.MaterialItem material = materials.get(i);
String videoUrl = material.getFileUrl();
int duration = material.getDuration();
// 验证视频URL必须是阿里云OSS地址
if (!videoUrl.contains(".aliyuncs.com")) {
log.error("[ICE][视频URL不是阿里云OSS地址][视频{}: {}]", i + 1, videoUrl);
throw new IllegalArgumentException("视频URL必须是阿里云OSS地址当前URL: " + videoUrl);
}
// 计算随机截取起点
// 优先使用前端传入的素材实际时长无则从0开始截取兜底
Integer fileDuration = material.getFileDuration();
int startOffset = 0;
int endOffset = duration;
if (fileDuration != null && fileDuration > duration) {
// 有实际时长且足够:随机起点范围 0 到 (实际时长 - 截取时长)
long randomSeed = ((material.getFileId() != null ? material.getFileId() : i) * 1000000L) +
(videoIndex * 10000L) + (material.getFileUrl().hashCode() % 1000);
Random random = new Random(randomSeed);
int maxStartOffset = fileDuration - duration;
startOffset = random.nextInt(maxStartOffset + 1);
endOffset = startOffset + duration;
log.debug("[ICE][随机截取] fileId={}, fileDuration={}s, In={}, Out={}",
material.getFileId(), fileDuration, startOffset, endOffset);
} else {
// 无时长或时长不足从0开始截取兜底
log.debug("[ICE][兜底截取] fileId={}, fileDuration={}, In=0, Out={}",
material.getFileId(), fileDuration, duration);
}
log.debug("[ICE][添加视频片段][视频{}: {}, In={}, Out={}, TimelineIn={}, TimelineOut={}]",
videoIndex + 1, videoUrl, startOffset, endOffset, timelinePos, timelinePos + duration);
// 构建视频片段(带 In/Out 参数)
JSONObject videoClip = new JSONObject();
videoClip.put("MediaURL", videoUrl);
videoClip.put("In", startOffset);
videoClip.put("Out", endOffset);
videoClip.put("TimelineIn", timelinePos);
videoClip.put("TimelineOut", timelinePos + duration);
// 添加裁剪效果9:16竖屏输出
// 假设源素材为1920x108016:9可根据实际情况调整
int sourceWidth = 1920;
int sourceHeight = 1080;
if (cropMode != null && !"fill".equals(cropMode)) {
// 非填充模式需要裁剪
Map<String, Integer> cropParams = calculateCropParams(sourceWidth, sourceHeight, cropMode);
JSONArray effects = new JSONArray();
JSONObject cropEffect = new JSONObject();
cropEffect.put("Type", "Crop");
cropEffect.put("X", cropParams.get("X"));
cropEffect.put("Y", cropParams.get("Y"));
cropEffect.put("Width", cropParams.get("Width"));
cropEffect.put("Height", cropParams.get("Height"));
effects.add(cropEffect);
videoClip.put("Effects", effects);
log.debug("[裁剪效果] 视频{}应用裁剪,模式={}, 参数={}", i + 1, cropMode, cropParams);
}
videoClipArray.add(videoClip);
// 为每个视频片段添加静音的音频轨道
JSONObject audioClip = new JSONObject();
audioClip.put("MediaURL", videoUrl);
audioClip.put("In", startOffset);
audioClip.put("Out", endOffset);
audioClip.put("TimelineIn", timelinePos);
audioClip.put("TimelineOut", timelinePos + duration);
audioClip.put("Effects", new JSONArray() {{
add(new JSONObject() {{
put("Type", "Volume");
put("Gain", 0); // 静音
}});
}});
audioClipArray.add(audioClip);
timelinePos += duration;
}
// 构建时间线
String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":" + videoClipArray.toJSONString() +
"}],\"AudioTracks\":[{\"AudioTrackClips\":" + audioClipArray.toJSONString() + "}]}";
// 生成输出文件路径
String targetFileName = UUID.randomUUID().toString().replace("-", "");
String mixDirectory = ossInitService.getOssDirectoryByCategory(userId, "mix");
String dateDir = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
String outputMediaPath = mixDirectory + "/" + dateDir + "/" + targetFileName + ".mp4";
String bucketEndpoint = "https://" + properties.getBucket() + ".oss-" + properties.getRegionId() + ".aliyuncs.com";
String outputMediaUrl = bucketEndpoint + "/" + outputMediaPath;
int width = 720;
int height = 1280;
int bitrate = 2000;
String outputMediaConfig = "{\"MediaURL\":\"" + outputMediaUrl + "\",\"Width\":" + width +
",\"Height\":" + height + ",\"Bitrate\":" + bitrate + "}";
SubmitMediaProducingJobRequest request = new SubmitMediaProducingJobRequest();
request.setTimeline(timeline);
request.setOutputMediaConfig(outputMediaConfig);
log.info("[ICE][提交任务][videoIndex={}, 素材数量={}, 总时长={}s]",
videoIndex, materials.size(), (int)timelinePos);
SubmitMediaProducingJobResponse response = iceClient.submitMediaProducingJob(request);
String jobId = response.getBody().getJobId();
log.info("[ICE][任务提交成功][videoIndex={}, jobId={}, outputUrl={}]", videoIndex, jobId, outputMediaUrl);
return jobId + " : " + outputMediaUrl;
}
}

View File

@@ -24,9 +24,9 @@ public class MixTaskConstants {
/**
* 定时任务配置
* 改为每2分钟检查一次,降低API调用频率
* 改为每30秒检查一次,提供更实时的进度更新
*/
public static final String CRON_CHECK_STATUS = "0 */2 * * * ?";
public static final String CRON_CHECK_STATUS = "*/30 * * * * ?";
/**
* 任务状态检查优化配置

View File

@@ -46,6 +46,12 @@ public class MixTaskDO extends TenantBaseDO {
@TableField("video_urls")
private String videoUrls;
/**
* 素材配置JSON包含fileId、fileUrl、duration
*/
@TableField("materials_json")
private String materialsJson;
/**
* 背景音乐URL列表(逗号分隔)
*/
@@ -162,4 +168,18 @@ public class MixTaskDO extends TenantBaseDO {
public void setOutputUrlList(List<String> outputUrls) {
this.outputUrls = outputUrls == null || outputUrls.isEmpty() ? null : String.join(",", outputUrls);
}
/**
* 获取素材配置JSON
*/
public String getMaterialsJson() {
return materialsJson;
}
/**
* 设置素材配置JSON
*/
public void setMaterialsJson(String materialsJson) {
this.materialsJson = materialsJson;
}
}

View File

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.tik.mix.service;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import cn.iocoder.yudao.module.tik.mix.client.IceClient;
@@ -46,8 +47,11 @@ public class MixTaskServiceImpl implements MixTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createMixTask(MixTaskSaveReqVO createReqVO, Long userId) {
log.info("[MixTask][创建任务] userId={}, title={}, videoCount={}, produceCount={}",
userId, createReqVO.getTitle(), createReqVO.getVideoUrls().size(), createReqVO.getProduceCount());
// 1. 校验时长
validateDuration(createReqVO);
log.info("[MixTask][创建任务] userId={}, title={}, materialCount={}, produceCount={}",
userId, createReqVO.getTitle(), createReqVO.getMaterials().size(), createReqVO.getProduceCount());
// 1. 创建初始任务对象
MixTaskDO task = MixTaskUtils.createInitialTask(createReqVO, userId);
@@ -168,10 +172,29 @@ public class MixTaskServiceImpl implements MixTaskService {
// 3. 重新提交到ICE
CompletableFuture.runAsync(() -> {
try {
// 手动构建请求对象纯画面模式无需text和bgMusicUrls
// 从 materialsJson 重建请求对象
List<MixTaskSaveReqVO.MaterialItem> materials = null;
if (StrUtil.isNotEmpty(existTask.getMaterialsJson())) {
materials = JsonUtils.parseArray(existTask.getMaterialsJson(), MixTaskSaveReqVO.MaterialItem.class);
} else if (existTask.getVideoUrlList() != null && !existTask.getVideoUrlList().isEmpty()) {
// 兼容旧版本:从 videoUrls 重建默认3秒时长
materials = existTask.getVideoUrlList().stream()
.map(url -> {
MixTaskSaveReqVO.MaterialItem item = new MixTaskSaveReqVO.MaterialItem();
item.setFileUrl(url);
item.setDuration(3); // 默认3秒
return item;
})
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
if (materials == null || materials.isEmpty()) {
throw new IllegalArgumentException("无法重建素材列表");
}
MixTaskSaveReqVO saveReqVO = new MixTaskSaveReqVO();
saveReqVO.setTitle(existTask.getTitle());
saveReqVO.setVideoUrls(existTask.getVideoUrlList());
saveReqVO.setMaterials(materials);
saveReqVO.setProduceCount(existTask.getProduceCount());
submitToICE(id, saveReqVO, existTask.getUserId());
} catch (Exception e) {
@@ -353,24 +376,32 @@ public class MixTaskServiceImpl implements MixTaskService {
/**
* 提交任务到阿里云 ICE
*
* 多视频差异化逻辑:
* - 每个视频使用相同的素材顺序和时长
* - 但截取起始点不同videoIndex * duration
* - 生成内容不同的多个视频
*/
private void submitToICE(Long taskId, MixTaskSaveReqVO createReqVO, Long userId) {
try {
// 1. 转换为ICE需要的参数格式
String[] videoArray = createReqVO.getVideoUrls().toArray(new String[0]);
List<String> jobIdWithUrls = new ArrayList<>();
int produceCount = createReqVO.getProduceCount();
// 2. 调用ICE批量生成接口纯画面模式无需text和bgMusic
List<String> jobIdWithUrls = batchProduceAlignment.batchProduceAlignment(
createReqVO.getTitle(),
videoArray,
createReqVO.getProduceCount(),
userId
);
// 循环生成多个视频,每个视频使用不同的截取起始点
for (int videoIndex = 0; videoIndex < produceCount; videoIndex++) {
String jobIdWithUrl = batchProduceAlignment.produceSingleVideoWithOffset(
createReqVO.getMaterials(),
videoIndex,
userId,
createReqVO.getCropMode()
);
jobIdWithUrls.add(jobIdWithUrl);
}
// 3. 解析jobId和输出URL
// 解析jobId和输出URL
MixTaskUtils.JobIdUrlPair jobIdUrlPair = MixTaskUtils.parseJobIdsAndUrls(jobIdWithUrls);
// 4. 更新任务信息(包含状态和进度)
// 更新任务信息
updateTaskWithResults(taskId, jobIdUrlPair.getJobIds(), jobIdUrlPair.getOutputUrls(),
MixTaskConstants.STATUS_RUNNING, MixTaskConstants.PROGRESS_UPLOADED);
@@ -498,4 +529,36 @@ public class MixTaskServiceImpl implements MixTaskService {
}
});
}
/**
* 校验混剪任务时长
*/
private void validateDuration(MixTaskSaveReqVO req) {
// 1. 素材列表不能为空
if (req.getMaterials() == null || req.getMaterials().isEmpty()) {
throw new IllegalArgumentException("素材列表不能为空");
}
// 2. 计算总时长
int totalDuration = req.getMaterials().stream()
.mapToInt(MixTaskSaveReqVO.MaterialItem::getDuration)
.sum();
// 3. 总时长校验15s-30s
if (totalDuration < 15) {
throw new IllegalArgumentException("总时长不能小于15秒当前" + totalDuration + "");
}
if (totalDuration > 30) {
throw new IllegalArgumentException("总时长不能超过30秒当前" + totalDuration + "");
}
// 4. 单个素材时长校验3s-5s
for (MixTaskSaveReqVO.MaterialItem item : req.getMaterials()) {
if (item.getDuration() < 3 || item.getDuration() > 5) {
throw new IllegalArgumentException("单个素材时长需在3-5秒之间当前" + item.getDuration() + "");
}
}
log.info("[MixTask][时长校验通过] totalDuration={}s, materialCount={}", totalDuration, req.getMaterials().size());
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.tik.mix.util;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.tik.mix.constants.MixTaskConstants;
import cn.iocoder.yudao.module.tik.mix.dal.dataobject.MixTaskDO;
import cn.iocoder.yudao.module.tik.mix.vo.MixTaskSaveReqVO;
@@ -27,7 +28,19 @@ public class MixTaskUtils {
task.setUserId(userId);
task.setTitle(reqVO.getTitle());
task.setText(null); // 纯画面模式,不需要文案
task.setVideoUrlList(reqVO.getVideoUrls());
// 存储素材配置JSON
String materialsJson = JsonUtils.toJsonString(reqVO.getMaterials());
task.setMaterialsJson(materialsJson);
// 兼容旧版本:同时存储 videoUrls取第一个视频的URL用于兼容查询
if (reqVO.getMaterials() != null && !reqVO.getMaterials().isEmpty()) {
List<String> videoUrls = reqVO.getMaterials().stream()
.map(MixTaskSaveReqVO.MaterialItem::getFileUrl)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
task.setVideoUrlList(videoUrls);
}
task.setBgMusicUrlList(null); // 纯画面模式,不需要背景音乐
task.setProduceCount(reqVO.getProduceCount());
task.setStatus(MixTaskConstants.STATUS_PENDING);
@@ -127,4 +140,54 @@ public class MixTaskUtils {
return outputUrls;
}
}
/**
* 构建 ICE Timeline
*
* @param materials 素材列表
* @return ICE Timeline JSON 字符串
*/
public static String buildTimeline(List<MixTaskSaveReqVO.MaterialItem> materials) {
StringBuilder tracks = new StringBuilder();
float currentTime = 0;
for (int i = 0; i < materials.size(); i++) {
MixTaskSaveReqVO.MaterialItem material = materials.get(i);
if (i > 0) {
tracks.append(",");
}
tracks.append(String.format("""
{
"MediaURL": "%s",
"In": 0,
"Out": %d,
"TimelineIn": %.2f,
"TimelineOut": %.2f
}
""",
material.getFileUrl(),
material.getDuration(),
currentTime,
currentTime + material.getDuration()
));
currentTime += material.getDuration();
}
return buildFullTimeline(tracks.toString());
}
/**
* 构建完整的 ICE Timeline
*/
private static String buildFullTimeline(String tracks) {
return String.format("""
{
"VideoTracks": [{
"TrackItems": [%s]
}]
}
""", tracks);
}
}

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.tik.mix.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@@ -16,11 +18,36 @@ public class MixTaskSaveReqVO {
@NotBlank(message = "视频标题不能为空")
private String title;
@Schema(description = "视频素材URL列表", required = true)
@NotEmpty(message = "视频素材不能为空")
private List<String> videoUrls;
@Schema(description = "素材配置列表", required = true)
@NotEmpty(message = "素材列表不能为空")
private List<MaterialItem> materials;
@Schema(description = "生成数量", required = true, example = "1")
@NotNull(message = "生成数量不能为空")
private Integer produceCount = 1; // 默认生成1个
@Schema(description = "裁剪模式", example = "center")
private String cropMode = "center"; // 默认居中裁剪
@Schema(description = "素材项")
@Data
public static class MaterialItem {
@Schema(description = "素材文件ID", required = true, example = "12345")
@NotNull(message = "素材文件ID不能为空")
private Long fileId;
@Schema(description = "素材URL", required = true, example = "https://xxx.com/video1.mp4")
@NotBlank(message = "素材URL不能为空")
private String fileUrl;
@Schema(description = "截取时长(秒)", required = true, example = "3")
@Min(value = 3, message = "单个素材时长不能小于3秒")
@Max(value = 5, message = "单个素材时长不能超过5秒")
@NotNull(message = "素材时长不能为空")
private Integer duration;
@Schema(description = "素材实际时长(秒)", example = "60")
private Integer fileDuration;
}
}

View File

@@ -61,9 +61,9 @@ public class CosyVoiceProperties {
private Duration connectTimeout = Duration.ofSeconds(10);
/**
* 读取超时时间
* 读取超时时间改为3分钟提升语音合成成功率
*/
private Duration readTimeout = Duration.ofSeconds(60);
private Duration readTimeout = Duration.ofSeconds(180);
/**
* 是否启用

View File

@@ -56,9 +56,9 @@ public class LatentsyncProperties {
private Duration connectTimeout = Duration.ofSeconds(10);
/**
* 读取超时时间
* 读取超时时间改为3分钟提升语音合成成功率
*/
private Duration readTimeout = Duration.ofSeconds(60);
private Duration readTimeout = Duration.ofSeconds(180);
/**
* 是否打开调用