diff --git a/pom.xml b/pom.xml index b992532..5d188e4 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,10 @@ google-api-client 2.8.0 + + org.springframework.boot + spring-boot-starter-validation + diff --git a/src/main/java/com/ddf/vodsystem/controllers/StreamController.java b/src/main/java/com/ddf/vodsystem/controllers/StreamController.java index c0b33c9..c53b8f0 100644 --- a/src/main/java/com/ddf/vodsystem/controllers/StreamController.java +++ b/src/main/java/com/ddf/vodsystem/controllers/StreamController.java @@ -1,7 +1,8 @@ package com.ddf.vodsystem.controllers; +import com.ddf.vodsystem.controllers.input.ClipSectionRequest; import com.ddf.vodsystem.dto.APIResponse; -import com.ddf.vodsystem.dto.SaveSectionRequest; +import com.ddf.vodsystem.controllers.input.SaveSectionRequest; import com.ddf.vodsystem.entities.Stream; import com.ddf.vodsystem.services.StreamService; import org.slf4j.Logger; @@ -53,6 +54,13 @@ public class StreamController { return ResponseEntity.accepted().body(new APIResponse<>("success", "Section saved", null)); } + @PostMapping("/clip") + public ResponseEntity> saveClip( + @RequestBody ClipSectionRequest request) throws IOException, InterruptedException { + streamService.clip(request.duration()); + return ResponseEntity.accepted().body(new APIResponse<>("success", "Clip saved", null)); + } + @GetMapping("/history/{userId}") public ResponseEntity>> getStreamHistory(@PathVariable Long userId) { List history = streamService.getStreamHistory(userId); diff --git a/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java b/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java new file mode 100644 index 0000000..d683b43 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/input/ClipSectionRequest.java @@ -0,0 +1,7 @@ +package com.ddf.vodsystem.controllers.input; + +import jakarta.validation.constraints.NotNull; + +public record ClipSectionRequest( + @NotNull float duration +) {} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java b/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java new file mode 100644 index 0000000..51988a5 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/input/SaveSectionRequest.java @@ -0,0 +1,8 @@ +package com.ddf.vodsystem.controllers.input; + +import java.time.Instant; +import jakarta.validation.constraints.NotNull; + +public record SaveSectionRequest( + @NotNull Instant startTime, + @NotNull Instant endTime) {} diff --git a/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java b/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java deleted file mode 100644 index 0b1eb5e..0000000 --- a/src/main/java/com/ddf/vodsystem/dto/SaveSectionRequest.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ddf.vodsystem.dto; - -import java.time.Instant; - -public record SaveSectionRequest(Instant startTime, Instant endTime) {} diff --git a/src/main/java/com/ddf/vodsystem/services/StreamService.java b/src/main/java/com/ddf/vodsystem/services/StreamService.java index f56a65f..d001e8f 100644 --- a/src/main/java/com/ddf/vodsystem/services/StreamService.java +++ b/src/main/java/com/ddf/vodsystem/services/StreamService.java @@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.io.IOException; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.util.Arrays; @@ -28,7 +29,9 @@ import java.util.stream.Collectors; @Service public class StreamService { private static final Logger logger = LoggerFactory.getLogger(StreamService.class); + private static final int HEARTBEAT_TIMEOUT_SECONDS = 60; + private static final float CLIP_MAX_LENGTH = 180; private final StreamRepository streamRepository; private final UserService userService; @@ -118,6 +121,34 @@ public class StreamService { }); } + public void clip(float duration) throws IOException, InterruptedException { + if (duration <= 0 || duration > CLIP_MAX_LENGTH) { + throw new IllegalArgumentException("Clip length must be between 0 and " + CLIP_MAX_LENGTH + " seconds"); + } + + User user = userService.getLoggedInUser() + .orElseThrow(() -> new NotAuthenticated("User is not authenticated")); + + Instant endTime = Instant.now(); + // minusSeconds() is possible, but only does integer seconds, not float + Instant startTime = Instant.now().minus(Duration.ofMillis((long) (duration * 1000))); + + String streamDirectory = streamDataPath + File.separator + user.getStreamKey(); + List fileSegments = getSegmentsInRange(streamDirectory, startTime, endTime); + + if (fileSegments.isEmpty()) { + throw new IllegalArgumentException("No stream segments found in the given time range"); + } + + long firstSegmentMs = parseTimestampMs(fileSegments.getFirst()); + float trimOffset = Math.max(0f, (startTime.toEpochMilli() - firstSegmentMs) / 1000f); + + streamActionsService.saveSection(fileSegments, trimOffset, duration) + .exceptionally(ex -> { + throw new FFMPEGException("Saving stream section failed due to FFMPEG Error"); + }); + } + @Scheduled(fixedDelay = 30_000) @Transactional public void endStaleStreams() {