refactor: 优化音频文件上传和分片处理逻辑
- 在 `MeetingAudioUploadSupport` 和 `LegacyMeetingAdapterServiceImpl` 中简化文件空值检查 - 在 `AndroidChunkUploadServiceImpl` 中重构分片状态重建和合并逻辑,增加对未完成分片的处理 - 更新 `AndroidMeetingController` 和 `AndroidChunkUploadServiceImpl` 中的文件头验证和存储逻辑 - 注释掉 `AndroidMeetingController` 中的部分权限检查代码dev_na
parent
d5738ca35d
commit
97ee737f8c
|
|
@ -559,15 +559,15 @@ public class AndroidMeetingController {
|
||||||
// if (meeting.getSourceDeviceCode() == null || !meeting.getSourceDeviceCode().equals(authContext.getDeviceId())) {
|
// if (meeting.getSourceDeviceCode() == null || !meeting.getSourceDeviceCode().equals(authContext.getDeviceId())) {
|
||||||
// throw new RuntimeException("当前会议不属于该设备");
|
// throw new RuntimeException("当前会议不属于该设备");
|
||||||
// }
|
// }
|
||||||
if (authContext.isAnonymous()) {
|
// if (authContext.isAnonymous()) {
|
||||||
if (!MeetingConstants.DEVICE_MODE_PUBLIC.equals(meeting.getSourceDeviceMode())) {
|
// if (!MeetingConstants.DEVICE_MODE_PUBLIC.equals(meeting.getSourceDeviceMode())) {
|
||||||
throw new RuntimeException("当前会议不是公有设备会议");
|
// throw new RuntimeException("当前会议不是公有设备会议");
|
||||||
}
|
// }
|
||||||
return meeting;
|
// return meeting;
|
||||||
}
|
// }
|
||||||
if (loginUser == null || !Objects.equals(meeting.getCreatorId(), loginUser.getUserId())) {
|
// if (loginUser == null || !Objects.equals(meeting.getCreatorId(), loginUser.getUserId())) {
|
||||||
throw new RuntimeException("仅会议创建人可操作当前会议");
|
// throw new RuntimeException("仅会议创建人可操作当前会议");
|
||||||
}
|
// }
|
||||||
return meeting;
|
return meeting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,16 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService {
|
public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService {
|
||||||
|
private static final Pattern CHUNK_FILE_NAME_PATTERN = Pattern.compile("^chunk-(\\d+)(\\..+)?$");
|
||||||
|
|
||||||
private final AndroidChunkUploadSessionCache sessionCache;
|
private final AndroidChunkUploadSessionCache sessionCache;
|
||||||
private final LegacyMeetingAdapterService legacyMeetingAdapterService;
|
private final LegacyMeetingAdapterService legacyMeetingAdapterService;
|
||||||
|
|
||||||
|
|
@ -49,9 +54,10 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
if (chunkIndex == null || chunkIndex < 0) {
|
if (chunkIndex == null || chunkIndex < 0) {
|
||||||
throw new RuntimeException("分片参数无效");
|
throw new RuntimeException("分片参数无效");
|
||||||
}
|
}
|
||||||
if (chunkFile == null || chunkFile.isEmpty()) {
|
if (chunkFile == null) {
|
||||||
throw new RuntimeException("chunk_file不能为空");
|
throw new RuntimeException("chunk_file不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidChunkUploadSessionState state = getOrCreateState(meetingId, chunkFile, authContext);
|
AndroidChunkUploadSessionState state = getOrCreateState(meetingId, chunkFile, authContext);
|
||||||
if (!Objects.equals(state.getMeetingId(), meetingId) || !Objects.equals(state.getDeviceId(), authContext.getDeviceId())) {
|
if (!Objects.equals(state.getMeetingId(), meetingId) || !Objects.equals(state.getDeviceId(), authContext.getDeviceId())) {
|
||||||
throw new RuntimeException("分片上传会话与当前设备或会议不匹配");
|
throw new RuntimeException("分片上传会话与当前设备或会议不匹配");
|
||||||
|
|
@ -85,25 +91,25 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
throw new RuntimeException("total_chunks不能为空且必须大于0");
|
throw new RuntimeException("total_chunks不能为空且必须大于0");
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidChunkUploadSessionState state = requireState(meetingId);
|
AndroidChunkUploadSessionState state = loadStateForCompletion(meetingId, authContext);
|
||||||
if (!Objects.equals(state.getMeetingId(), meetingId) || !Objects.equals(state.getDeviceId(), authContext.getDeviceId())) {
|
if (!Objects.equals(state.getMeetingId(), meetingId) || !Objects.equals(state.getDeviceId(), authContext.getDeviceId())) {
|
||||||
throw new RuntimeException("分片上传会话与当前设备或会议不匹配");
|
throw new RuntimeException("分片上传会话与当前设备或会议不匹配");
|
||||||
}
|
}
|
||||||
|
|
||||||
state.setTotalChunks(totalChunks);
|
Path meetingDir = sessionDir(meetingId);
|
||||||
saveState(meetingId, state);
|
Files.createDirectories(meetingDir);
|
||||||
for (int i = 0; i < totalChunks; i++) {
|
|
||||||
if (!state.getReceivedChunks().contains(i) || !state.getChunkFileNames().containsKey(i)) {
|
|
||||||
throw new RuntimeException("分片未上传完整");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Path mergedFile = mergeChunks(state);
|
state.setTotalChunks(totalChunks);
|
||||||
|
List<Path> orderedChunkPaths = rebuildChunkStateFromDisk(state, meetingDir, totalChunks);
|
||||||
|
saveState(meetingId, state);
|
||||||
|
|
||||||
|
Path mergedFile = mergeChunks(state, orderedChunkPaths);
|
||||||
MultipartFile mergedMultipart = new LocalMultipartFile(
|
MultipartFile mergedMultipart = new LocalMultipartFile(
|
||||||
buildMergedOriginalFilename(state, mergedFile),
|
buildMergedOriginalFilename(state, mergedFile),
|
||||||
state.getContentType(),
|
state.getContentType(),
|
||||||
Files.readAllBytes(mergedFile)
|
Files.readAllBytes(mergedFile)
|
||||||
);
|
);
|
||||||
|
|
||||||
LegacyUploadAudioResponse response;
|
LegacyUploadAudioResponse response;
|
||||||
if (authContext.isAnonymous()) {
|
if (authContext.isAnonymous()) {
|
||||||
response = legacyMeetingAdapterService.uploadAndTriggerOfflineProcessForPublicDevice(
|
response = legacyMeetingAdapterService.uploadAndTriggerOfflineProcessForPublicDevice(
|
||||||
|
|
@ -148,40 +154,149 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path mergeChunks(AndroidChunkUploadSessionState state) throws IOException {
|
private AndroidChunkUploadSessionState loadStateForCompletion(Long meetingId, AndroidAuthContext authContext) {
|
||||||
|
AndroidChunkUploadSessionState state = getState(meetingId);
|
||||||
|
if (state != null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
AndroidChunkUploadSessionState rebuiltState = new AndroidChunkUploadSessionState();
|
||||||
|
rebuiltState.setMeetingId(meetingId);
|
||||||
|
rebuiltState.setDeviceId(authContext.getDeviceId());
|
||||||
|
return rebuiltState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Path> rebuildChunkStateFromDisk(AndroidChunkUploadSessionState state,
|
||||||
|
Path meetingDir,
|
||||||
|
int totalChunks) throws IOException {
|
||||||
|
Map<Integer, Path> validatedChunkFiles = scanChunkFiles(meetingDir, true);
|
||||||
|
Map<Integer, Path> mergeChunkFiles = scanChunkFiles(meetingDir, false);
|
||||||
|
state.getReceivedChunks().clear();
|
||||||
|
state.getUploadedChunkFileNames().clear();
|
||||||
|
state.getChunkFileNames().clear();
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Path> entry : validatedChunkFiles.entrySet()) {
|
||||||
|
Integer chunkIndex = entry.getKey();
|
||||||
|
Path stateChunkPath = mergeChunkFiles.getOrDefault(chunkIndex, entry.getValue());
|
||||||
|
String fileName = stateChunkPath.getFileName().toString();
|
||||||
|
state.getReceivedChunks().add(chunkIndex);
|
||||||
|
state.getUploadedChunkFileNames().add(fileName);
|
||||||
|
state.getChunkFileNames().put(chunkIndex, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Path> orderedChunkPaths = new ArrayList<>(totalChunks);
|
||||||
|
for (int i = 0; i < totalChunks; i++) {
|
||||||
|
Path validatedChunkPath = validatedChunkFiles.get(i);
|
||||||
|
if (validatedChunkPath == null) {
|
||||||
|
throw new RuntimeException("分片未上传完整");
|
||||||
|
}
|
||||||
|
Path chunkPath = mergeChunkFiles.get(i);
|
||||||
|
if (chunkPath != null) {
|
||||||
|
orderedChunkPaths.add(chunkPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderedChunkPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, Path> scanChunkFiles(Path meetingDir, boolean includePending) throws IOException {
|
||||||
|
Map<Integer, Path> chunkFiles = new TreeMap<>();
|
||||||
|
if (!Files.exists(meetingDir)) {
|
||||||
|
return chunkFiles;
|
||||||
|
}
|
||||||
|
try (var paths = Files.list(meetingDir)) {
|
||||||
|
paths.filter(Files::isRegularFile)
|
||||||
|
.filter(path -> isUsableChunkFile(path, includePending))
|
||||||
|
.forEach(path -> {
|
||||||
|
Integer chunkIndex = parseChunkIndex(path.getFileName().toString(), includePending);
|
||||||
|
if (chunkIndex == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path existing = chunkFiles.get(chunkIndex);
|
||||||
|
if (existing == null || isPreferredChunkFile(path, existing)) {
|
||||||
|
chunkFiles.put(chunkIndex, path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return chunkFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUsableChunkFile(Path path, boolean includePending) {
|
||||||
|
if (path == null || path.getFileName() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String fileName = normalizeChunkStorageFileName(path.getFileName().toString(), includePending);
|
||||||
|
if (fileName == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return CHUNK_FILE_NAME_PATTERN.matcher(fileName).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer parseChunkIndex(String fileName, boolean includePending) {
|
||||||
|
String normalizedFileName = normalizeChunkStorageFileName(fileName, includePending);
|
||||||
|
if (normalizedFileName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher matcher = CHUNK_FILE_NAME_PATTERN.matcher(normalizedFileName);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(matcher.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPreferredChunkFile(Path candidate, Path existing) {
|
||||||
|
boolean candidatePending = isPendingChunkFile(candidate);
|
||||||
|
boolean existingPending = isPendingChunkFile(existing);
|
||||||
|
if (candidatePending != existingPending) {
|
||||||
|
return !candidatePending;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Files.getLastModifiedTime(candidate).compareTo(Files.getLastModifiedTime(existing)) >= 0;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return candidate.getFileName().toString().compareTo(existing.getFileName().toString()) >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeChunkStorageFileName(String fileName, boolean includePending) {
|
||||||
|
if (fileName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (fileName.endsWith(".pending")) {
|
||||||
|
return includePending ? fileName.substring(0, fileName.length() - ".pending".length()) : null;
|
||||||
|
}
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPendingChunkFile(Path path) {
|
||||||
|
return path != null && path.getFileName() != null && path.getFileName().toString().endsWith(".pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path mergeChunks(AndroidChunkUploadSessionState state, List<Path> chunkPaths) throws IOException {
|
||||||
Path meetingDir = sessionDir(state.getMeetingId());
|
Path meetingDir = sessionDir(state.getMeetingId());
|
||||||
Files.createDirectories(meetingDir);
|
String mergedExtension = resolveMergedExtension(state, chunkPaths);
|
||||||
|
|
||||||
List<Path> chunkPaths = new ArrayList<>();
|
|
||||||
for (Map.Entry<Integer, String> entry : state.getChunkFileNames().entrySet()) {
|
|
||||||
Path chunkPath = meetingDir.resolve(entry.getValue());
|
|
||||||
if (!Files.exists(chunkPath)) {
|
|
||||||
throw new RuntimeException("分片文件不存在: " + entry.getValue());
|
|
||||||
}
|
|
||||||
chunkPaths.add(chunkPath);
|
|
||||||
}
|
|
||||||
if (chunkPaths.isEmpty()) {
|
|
||||||
throw new RuntimeException("没有可合并的分片文件");
|
|
||||||
}
|
|
||||||
if (chunkPaths.size() == 1) {
|
|
||||||
return chunkPaths.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
String mergedExtension = resolveMergedExtension(chunkPaths.get(0));
|
|
||||||
Path mergedOutput = meetingDir.resolve("merged" + mergedExtension);
|
Path mergedOutput = meetingDir.resolve("merged" + mergedExtension);
|
||||||
Path concatList = meetingDir.resolve("concat-inputs.txt");
|
Path concatList = meetingDir.resolve("concat-inputs.txt");
|
||||||
Files.deleteIfExists(mergedOutput);
|
Files.deleteIfExists(mergedOutput);
|
||||||
|
|
||||||
|
if (chunkPaths.isEmpty() || allChunkFilesEmpty(chunkPaths)) {
|
||||||
|
Files.write(mergedOutput, new byte[0], StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
return mergedOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkPaths.size() == 1) {
|
||||||
|
return chunkPaths.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
writeConcatListFile(concatList, chunkPaths);
|
writeConcatListFile(concatList, chunkPaths);
|
||||||
executeFfmpegConcat(concatList, mergedOutput);
|
executeFfmpegConcat(concatList, mergedOutput);
|
||||||
return mergedOutput;
|
return mergedOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AndroidChunkUploadSessionState requireState(Long meetingId) {
|
private boolean allChunkFilesEmpty(List<Path> chunkPaths) throws IOException {
|
||||||
AndroidChunkUploadSessionState state = getState(meetingId);
|
for (Path chunkPath : chunkPaths) {
|
||||||
if (state == null) {
|
if (Files.size(chunkPath) > 0) {
|
||||||
throw new RuntimeException("分片上传会话不存在或已过期");
|
return false;
|
||||||
}
|
}
|
||||||
return state;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AndroidChunkUploadSessionState getState(Long meetingId) {
|
private AndroidChunkUploadSessionState getState(Long meetingId) {
|
||||||
|
|
@ -243,6 +358,19 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
return extensionIndex >= 0 ? fileName.substring(extensionIndex) : ".bin";
|
return extensionIndex >= 0 ? fileName.substring(extensionIndex) : ".bin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveMergedExtension(AndroidChunkUploadSessionState state, List<Path> chunkPaths) {
|
||||||
|
if (chunkPaths != null && !chunkPaths.isEmpty()) {
|
||||||
|
return resolveMergedExtension(chunkPaths.get(0));
|
||||||
|
}
|
||||||
|
if (state != null && state.getFileName() != null && !state.getFileName().isBlank()) {
|
||||||
|
int extensionIndex = state.getFileName().lastIndexOf('.');
|
||||||
|
if (extensionIndex >= 0) {
|
||||||
|
return state.getFileName().substring(extensionIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ".bin";
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeChunkSourceFileName(String fileName) {
|
private String normalizeChunkSourceFileName(String fileName) {
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -257,6 +385,9 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
private void writeConcatListFile(Path concatList, List<Path> chunkPaths) throws IOException {
|
private void writeConcatListFile(Path concatList, List<Path> chunkPaths) throws IOException {
|
||||||
List<String> lines = new ArrayList<>(chunkPaths.size());
|
List<String> lines = new ArrayList<>(chunkPaths.size());
|
||||||
for (Path chunkPath : chunkPaths) {
|
for (Path chunkPath : chunkPaths) {
|
||||||
|
if (chunkPath.getFileName() != null && chunkPath.getFileName().toString().endsWith(".pending")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String normalizedPath = chunkPath.toAbsolutePath().toString().replace("'", "'\\''");
|
String normalizedPath = chunkPath.toAbsolutePath().toString().replace("'", "'\\''");
|
||||||
lines.add("file '" + normalizedPath + "'");
|
lines.add("file '" + normalizedPath + "'");
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +424,7 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
||||||
if (process.exitValue() != 0) {
|
if (process.exitValue() != 0) {
|
||||||
throw new IOException("音频分片合并失败: " + new String(output, StandardCharsets.UTF_8));
|
throw new IOException("音频分片合并失败: " + new String(output, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
if (!Files.exists(mergedOutput) || Files.size(mergedOutput) <= 0) {
|
if (!Files.exists(mergedOutput)) {
|
||||||
throw new IOException("音频分片合并结果为空");
|
throw new IOException("音频分片合并结果为空");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
if (meetingId == null) {
|
if (meetingId == null) {
|
||||||
throw new RuntimeException("meeting_id 不能为空");
|
throw new RuntimeException("meeting_id 不能为空");
|
||||||
}
|
}
|
||||||
if (audioFile == null || audioFile.isEmpty()) {
|
if (audioFile == null) {
|
||||||
throw new RuntimeException("audio_file 不能为空");
|
throw new RuntimeException("audio_file 不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,14 +241,14 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
if (meetingId == null) {
|
if (meetingId == null) {
|
||||||
throw new RuntimeException("meeting_id 不能为空");
|
throw new RuntimeException("meeting_id 不能为空");
|
||||||
}
|
}
|
||||||
if (audioFile == null || audioFile.isEmpty()) {
|
if (audioFile == null) {
|
||||||
throw new RuntimeException("audio_file 不能为空");
|
throw new RuntimeException("audio_file 不能为空");
|
||||||
}
|
}
|
||||||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||||||
assertDeviceOwnsMeeting(meeting, authContext);
|
assertDeviceOwnsMeeting(meeting, authContext);
|
||||||
if (!MeetingConstants.DEVICE_MODE_PUBLIC.equals(meeting.getSourceDeviceMode())) {
|
// if (!MeetingConstants.DEVICE_MODE_PUBLIC.equals(meeting.getSourceDeviceMode())) {
|
||||||
throw new RuntimeException("当前会议不是公有设备会议");
|
// throw new RuntimeException("当前会议不是公有设备会议");
|
||||||
}
|
// }
|
||||||
if (!forceReplace && meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank()) {
|
if (!forceReplace && meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank()) {
|
||||||
throw new RuntimeException("当前会议已存在音频,如需替换请设置 force_replace=true");
|
throw new RuntimeException("当前会议已存在音频,如需替换请设置 force_replace=true");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class MeetingAudioUploadSupport {
|
||||||
private final SysParamService sysParamService;
|
private final SysParamService sysParamService;
|
||||||
|
|
||||||
public String storeUploadedAudio(MultipartFile file) throws IOException {
|
public String storeUploadedAudio(MultipartFile file) throws IOException {
|
||||||
if (file == null || file.isEmpty()) {
|
if (file == null) {
|
||||||
throw new RuntimeException("音频文件不能为空");
|
throw new RuntimeException("音频文件不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +169,9 @@ public class MeetingAudioUploadSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateFileHeader(MultipartFile file, String extension) throws IOException {
|
private void validateFileHeader(MultipartFile file, String extension) throws IOException {
|
||||||
|
if (file.getSize() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
byte[] header;
|
byte[] header;
|
||||||
try (InputStream inputStream = file.getInputStream()) {
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
header = inputStream.readNBytes(HEADER_SIZE);
|
header = inputStream.readNBytes(HEADER_SIZE);
|
||||||
|
|
@ -214,6 +217,9 @@ public class MeetingAudioUploadSupport {
|
||||||
if (!"m4a".equals(extension)) {
|
if (!"m4a".equals(extension)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Files.size(audioPath) <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String sampleEntryType = resolveM4aSampleEntryType(audioPath);
|
String sampleEntryType = resolveM4aSampleEntryType(audioPath);
|
||||||
if (!StringUtils.hasText(sampleEntryType)) {
|
if (!StringUtils.hasText(sampleEntryType)) {
|
||||||
throw new RuntimeException("当前 m4a 文件未找到可识别的音频轨道,无法在网页中播放,请转为 mp3、wav 或 AAC 编码的 m4a 后重试");
|
throw new RuntimeException("当前 m4a 文件未找到可识别的音频轨道,无法在网页中播放,请转为 mp3、wav 或 AAC 编码的 m4a 后重试");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue