diff --git a/src/main/java/com/ddf/vodsystem/services/media/StreamActionsService.java b/src/main/java/com/ddf/vodsystem/services/media/StreamActionsService.java index dcd6664..ed37183 100644 --- a/src/main/java/com/ddf/vodsystem/services/media/StreamActionsService.java +++ b/src/main/java/com/ddf/vodsystem/services/media/StreamActionsService.java @@ -125,7 +125,7 @@ public class StreamActionsService { } } - public List getSegmentsInRange(Path streamDirectory, Instant startTime, Instant endTime) throws IOException { + private List getSegmentsInRange(Path streamDirectory, Instant startTime, Instant endTime) throws IOException { long startMs = startTime.toEpochMilli(); long endMs = endTime.toEpochMilli(); @@ -189,7 +189,7 @@ public class StreamActionsService { long firstMs = parseTimestampMs(segments.getFirst()); float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstMs) / 1000f); - // if the first segment is longer than the trim-offset, then ggs + // if the first segment is longer than the trim-offset if (trimOffset > 0 && probeDurationSeconds(segments.getFirst()) < trimOffset) { segments.removeFirst(); if (segments.isEmpty()) { diff --git a/src/test/java/com/ddf/vodsystem/services/media/StreamActionsServiceTest.java b/src/test/java/com/ddf/vodsystem/services/media/StreamActionsServiceTest.java index 2afe492..8f91f84 100644 --- a/src/test/java/com/ddf/vodsystem/services/media/StreamActionsServiceTest.java +++ b/src/test/java/com/ddf/vodsystem/services/media/StreamActionsServiceTest.java @@ -75,137 +75,131 @@ class StreamActionsServiceTest { } // --------------------------------------------------------------- - // getSegmentsInRange: missing / invalid directories + // selectSegments: missing / invalid directories // --------------------------------------------------------------- @Test - void getSegmentsInRange_directoryDoesNotExist_throwsIOException() { + void selectSegments_directoryDoesNotExist_throwsIOException() { Path missing = tempDir.resolve("does-not-exist"); - assertThatThrownBy(() -> service.getSegmentsInRange(missing, Instant.EPOCH, Instant.now())) + assertThatThrownBy(() -> service.selectSegments(missing, Instant.EPOCH, Instant.now())) .isInstanceOf(NoSuchFileException.class); } @Test - void getSegmentsInRange_pathIsAFileNotADirectory_throwsIOException() throws IOException { + void selectSegments_pathIsAFileNotADirectory_throwsIOException() throws IOException { Path file = tempDir.resolve("not-a-directory.txt"); Files.createFile(file); - assertThatThrownBy(() -> service.getSegmentsInRange(file, Instant.EPOCH, Instant.now())) + assertThatThrownBy(() -> service.selectSegments(file, Instant.EPOCH, Instant.now())) .isInstanceOf(IOException.class); } - @Test - void getSegmentsInRange_emptyDirectory_returnsEmptyList() throws IOException { - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(100_000)); - - assertThat(result).isEmpty(); - } - // --------------------------------------------------------------- - // getSegmentsInRange: filename filtering + // selectSegments: filename filtering // --------------------------------------------------------------- @Test - void getSegmentsInRange_ignoresFilesNotEndingInTs() throws IOException { + void selectSegments_ignoresFilesNotEndingInTs() throws IOException { createSegment("1.ts"); createSegment("playlist.m3u8"); createSegment("1.ts.tmp"); createSegment("readme.txt"); - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5000)).segments(); assertThat(result).extracting(p -> p.getFileName().toString()).containsExactly("1.ts"); } @Test - void getSegmentsInRange_directoryNamedLikeASegment_isNotIncluded() throws IOException { + void selectSegments_directoryNamedLikeASegment_isNotIncluded() throws IOException { Files.createDirectory(tempDir.resolve("5.ts")); - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000)); - - assertThat(result).isEmpty(); + // the directory is the only ".ts" entry; excluding it leaves nothing to select + assertThatThrownBy(() -> + service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000))) + .isInstanceOf(IllegalArgumentException.class); } @Test - void getSegmentsInRange_malformedNonNumericFilename_throwsNumberFormatException() throws IOException { + void selectSegments_malformedNonNumericFilename_throwsNumberFormatException() throws IOException { // A real HLS fragment name is always numeric. A corrupted or partially-written // file (e.g. "index.ts") crashes the whole listing instead of just being skipped. createSegment("index.ts"); assertThatThrownBy(() -> - service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000))) + service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000))) .isInstanceOf(NumberFormatException.class); } @Test - void getSegmentsInRange_emptyFilenameBeforeExtension_throwsNumberFormatException() throws IOException { + void selectSegments_emptyFilenameBeforeExtension_throwsNumberFormatException() throws IOException { createSegment(".ts"); assertThatThrownBy(() -> - service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000))) + service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(10_000))) .isInstanceOf(NumberFormatException.class); } // --------------------------------------------------------------- - // getSegmentsInRange: range-boundary arithmetic + // selectSegments: range-boundary arithmetic // --------------------------------------------------------------- @Test - void getSegmentsInRange_segmentEndsExactlyAtRangeStart_isExcluded() throws IOException { + void selectSegments_segmentEndsExactlyAtRangeStart_isExcluded() throws IOException { createSegment("0.ts"); createSegment("3.ts"); - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(3000), Instant.ofEpochMilli(10_000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(3000), Instant.ofEpochMilli(10_000)).segments(); assertThat(result).extracting(p -> p.getFileName().toString()) .containsExactly("3.ts"); } @Test - void getSegmentsInRange_segmentOverlapsRangeStartByOneMs_isIncluded() throws IOException { + void selectSegments_segmentOverlapsRangeStartByOneMs_isIncluded() throws IOException { createSegment("0.ts"); // spans [0, 3000) + stubHeadDuration(3f); // start 2999ms trims into 0.ts, so its duration is probed - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(2999), Instant.ofEpochMilli(10_000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(2999), Instant.ofEpochMilli(10_000)).segments(); assertThat(result).hasSize(1); } @Test - void getSegmentsInRange_segmentStartsExactlyAtRangeEnd_isExcluded() throws IOException { + void selectSegments_segmentStartsExactlyAtRangeEnd_isExcluded() throws IOException { createSegment("5.ts"); // starts at 5000ms - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5000)); - - assertThat(result).isEmpty(); + assertThatThrownBy(() -> + service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5000))) + .isInstanceOf(IllegalArgumentException.class); } @Test - void getSegmentsInRange_segmentStartsOneMsBeforeRangeEnd_isIncluded() throws IOException { + void selectSegments_segmentStartsOneMsBeforeRangeEnd_isIncluded() throws IOException { createSegment("5.ts"); // starts at 5000ms - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5001)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(5001)).segments(); assertThat(result).hasSize(1); } @Test - void getSegmentsInRange_startAfterEnd_throwsIllegalArgument() throws IOException { + void selectSegments_startAfterEnd_throwsIllegalArgument() throws IOException { createSegment("10.ts"); assertThatThrownBy(() -> - service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(20_000), Instant.ofEpochMilli(10_000))) + service.selectSegments(tempDir, Instant.ofEpochMilli(20_000), Instant.ofEpochMilli(10_000))) .isInstanceOf(IllegalArgumentException.class); - } @Test - void getSegmentsInRange_segmentsOutOfOrder_returnedInOrder() throws IOException { + void selectSegments_segmentsOutOfOrder_returnedInOrder() throws IOException { createSegment("20.ts"); createSegment("10.ts"); createSegment("0.ts"); - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(25_000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(25_000)).segments(); assertThat(result) .extracting(p -> p.getFileName().toString()) @@ -213,45 +207,31 @@ class StreamActionsServiceTest { } @Test - void getSegmentsInRange_mixOfOverlappingAndNonOverlappingSegments_returnsOnlyOverlapping() throws IOException { + void selectSegments_mixOfOverlappingAndNonOverlappingSegments_returnsOnlyOverlapping() throws IOException { createSegment("0.ts"); // [0, 3000) createSegment("3.ts"); // [3000, 6000) createSegment("6.ts"); // [6000, 9000) createSegment("9.ts"); // [9000, 12000) + stubHeadDuration(3f); // start 4000ms trims into 3.ts, so its duration is probed - - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(4000), Instant.ofEpochMilli(8000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(4000), Instant.ofEpochMilli(8000)).segments(); assertThat(result).extracting(p -> p.getFileName().toString()) .containsExactly("3.ts", "6.ts"); } @Test - void getSegmentsInRange_resultsAreSortedByTimestampRegardlessOfDirectoryOrder() throws IOException { + void selectSegments_resultsAreSortedByTimestampRegardlessOfDirectoryOrder() throws IOException { createSegment("30.ts"); createSegment("10.ts"); createSegment("20.ts"); - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(100_000)); + var result = service.selectSegments(tempDir, Instant.ofEpochMilli(0), Instant.ofEpochMilli(100_000)).segments(); assertThat(result).extracting(p -> p.getFileName().toString()) .containsExactly("10.ts", "20.ts", "30.ts"); } - @Test - void getSegmentsInRange_gapInStream_returnsTheGap() throws IOException { - createSegment("20.ts"); - createSegment("30.ts"); - // user stops streaming - createSegment("100.ts"); - createSegment("110.ts"); - - List result = service.getSegmentsInRange(tempDir, Instant.ofEpochMilli(70_000), Instant.ofEpochMilli(111_000)); - - assertThat(result).extracting(p -> p.getFileName().toString()) - .containsExactly("30.ts", "100.ts", "110.ts"); - } - // --------------------------------------------------------------- // selectSegments: head selection and trim-offset // ---------------------------------------------------------------