fix incorrect hls segment handling

This commit is contained in:
2026-07-25 13:44:34 +01:00
parent 92cecb1084
commit aabeae5e8b
5 changed files with 82 additions and 34 deletions

View File

@@ -11,6 +11,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -31,8 +32,6 @@ public class StreamActionsService {
private static final Logger logger = LoggerFactory.getLogger(StreamActionsService.class);
private final CommandRunner commandRunner;
private static final int HLS_SEGMENT_LENGTH = 3;
public StreamActionsService(CommandRunner commandRunner) {
this.commandRunner = commandRunner;
}
@@ -65,21 +64,33 @@ public class StreamActionsService {
float duration,
Path outputFile,
ProgressTracker progressTracker
) {
) throws IOException {
if (segments.isEmpty()) {
return CompletableFuture.failedFuture(new IllegalArgumentException("Segment list cannot be empty"));
}
Path firstSegment = segments.getFirst();
Path trimmedFirst = Files.createTempFile("trimmed-first-", ".ts");
try {
String concatInput = segments.stream()
// First segment must be trimmed manually due to keyframe issues with the concat protocol
List<String> trimCommand = List.of(
"ffmpeg", "-y",
"-ss", String.valueOf(trimOffset),
"-i", firstSegment.toAbsolutePath().toString(),
"-c", "copy",
trimmedFirst.toAbsolutePath().toString()
);
commandRunner.run(trimCommand);
String concatInput = Stream.concat(Stream.of(trimmedFirst), segments.stream().skip(1))
.map(p -> p.toAbsolutePath().toString())
.collect(Collectors.joining("|"));
if (concatInput.isEmpty()) {
throw new IllegalStateException("Must have at least one segment");
}
List<String> command = List.of(
"ffmpeg",
"-y",
"-i", "concat:" + concatInput,
"-ss", String.valueOf(trimOffset),
"-i", "concat:" + concatInput,
"-t", String.valueOf(duration),
"-c", "copy",
outputFile.toAbsolutePath().toString()
@@ -101,6 +112,8 @@ public class StreamActionsService {
} catch (RuntimeException e) {
logger.error("Failed to save section to '{}': {}", outputFile, e.toString());
return CompletableFuture.failedFuture(e);
} finally {
Files.deleteIfExists(trimmedFirst);
}
}
@@ -112,17 +125,29 @@ public class StreamActionsService {
throw new IllegalArgumentException("Start time must be before end time");
}
List<Path> results = new ArrayList<>();
try (Stream<Path> files = Files.list(streamDirectory)) {
return files
List<Path> streamFiles = files
.filter(p -> p.getFileName().toString().endsWith(".ts"))
.filter(Files::isRegularFile)
.filter(p -> {
long segMs = parseTimestampMs(p);
return segMs < endMs && (segMs + HLS_SEGMENT_LENGTH * 1000) > startMs;
})
.sorted(Comparator.comparingLong(this::parseTimestampMs))
.collect(Collectors.toList());
.toList();
for (int i = 0; i < streamFiles.size(); i++) {
Path streamFile = streamFiles.get(i);
long segmentStart = parseTimestampMs(streamFile);
long segmentEnd = (i + 1 < streamFiles.size())
? parseTimestampMs(streamFiles.get(i + 1))
: Long.MAX_VALUE;
if (segmentStart < endMs && segmentEnd > startMs) {
results.add(streamFile);
}
}
}
return results;
}
public long parseTimestampMs(Path path) {