perf(api): add HTTP range requests on media sources (#9)
All checks were successful
CI / Maven unit tests (push) Successful in 20s
CI / Build and push images (push) Successful in 5m14s
CI / Deploy to server (push) Successful in 1m29s

Reviewed-on: https://gitea.vodsystem.ddns.net/dylan/vod-system/pulls/9
This commit was merged in pull request #9.
This commit is contained in:
2026-07-25 11:06:39 +01:00
parent 74a9d5752d
commit ae8f2f33a7
18 changed files with 1963 additions and 1447 deletions

View File

@@ -12,6 +12,7 @@ import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@RestController
@@ -77,8 +78,8 @@ public class ClipController {
);
}
@GetMapping("/{id}/media")
public ResponseEntity<Resource> downloadClip(@PathVariable Long id) {
@GetMapping(value = "/{id}/media", produces = MediaType.ALL_VALUE)
public ResponseEntity<Resource> downloadClip(@PathVariable Long id) throws IOException {
Resource resource = clipService.downloadClip(id);
if (resource == null || !resource.exists()) {
@@ -86,8 +87,11 @@ public class ClipController {
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format(FILENAME_HEADER, resource.getFilename()))
.contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM))
.header(HttpHeaders.CONTENT_DISPOSITION,
String.format(FILENAME_HEADER, resource.getFilename()))
.contentType(MediaTypeFactory.getMediaType(resource)
.orElse(MediaType.APPLICATION_OCTET_STREAM))
.contentLength(resource.contentLength())
.body(resource);
}

View File

@@ -102,6 +102,13 @@ public class GlobalExceptionHandler {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
@ExceptionHandler(ClipMediaUnavailable.class)
public ResponseEntity<APIResponse<Void>> handleClipMediaUnavailable(ClipMediaUnavailable ex) {
logger.error("ClipMediaUnavailable (clip {}): {}", ex.getClipId(), ex.getMessage());
APIResponse<Void> response = new APIResponse<>(ERROR, "Clip media is currently unavailable", null);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
@ExceptionHandler(VodNotFound.class)
public ResponseEntity<APIResponse<Void>> handleVodNotFound(VodNotFound ex) {
logger.error("VodNotFound: {}", ex.getMessage());
@@ -109,6 +116,13 @@ public class GlobalExceptionHandler {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
@ExceptionHandler(VodMediaUnavailable.class)
public ResponseEntity<APIResponse<Void>> handleVodMediaUnavailable(VodMediaUnavailable ex) {
logger.error("VodMediaUnavailable (vod {}): {}", ex.getVodId(), ex.getMessage());
APIResponse<Void> response = new APIResponse<>(ERROR, "Vod media is currently unavailable", null);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
@ExceptionHandler(NotStreaming.class)
public ResponseEntity<APIResponse<Void>> handleNotStreaming(NotStreaming ex) {
logger.error("NotStreaming: {}", ex.getMessage());

View File

@@ -30,7 +30,7 @@ public class MediaController {
public ResponseEntity<APIResponse<UUIDResponse>> compress(
@RequestParam("file")MultipartFile file,
@Valid @ModelAttribute ClipOptions options
) throws IOException, InterruptedException {
) throws IOException {
Job job = mediaService.compress(file, options);
return ResponseEntity.ok(new APIResponse<>(
SUCCESS,

View File

@@ -12,6 +12,7 @@ import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@RestController
@@ -54,8 +55,8 @@ public class VodController {
"Vod with ID " + id + " has been deleted"));
}
@GetMapping("/{id}/media")
public ResponseEntity<Resource> downloadVod(@PathVariable Long id) {
@GetMapping(value = "/{id}/media", produces = MediaType.ALL_VALUE)
public ResponseEntity<Resource> downloadVod(@PathVariable Long id) throws IOException {
Resource resource = vodService.downloadVod(id);
if (resource == null || !resource.exists()) {
@@ -63,8 +64,11 @@ public class VodController {
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format(FILENAME_HEADER, resource.getFilename()))
.contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM))
.header(HttpHeaders.CONTENT_DISPOSITION,
String.format(FILENAME_HEADER, resource.getFilename()))
.contentType(MediaTypeFactory.getMediaType(resource)
.orElse(MediaType.APPLICATION_OCTET_STREAM))
.contentLength(resource.contentLength())
.body(resource);
}