I have a service that returns a byte array according to the Range header. If I just write this array into the response body, then rewinding the video does not work, but if I use ByteBuffer for the response, everything will work fine. Why?
I have a service that returns a byte array according to the Range header. If I just write this array into the response body, then rewinding the video does not work, but if I use ByteBuffer for the response, everything will work fine. Why?
Handler:
@Component
public class VideoHandler {
public Mono<ServerResponse> getRange(ServerRequest request) {
ResourceHolder holder = new ResourceHolder(request);
return ServerResponse.status(HttpStatus.PARTIAL_CONTENT)
.contentType(
MediaTypeFactory.getMediaType(holder.getResource())
.orElse(MediaType.APPLICATION_OCTET_STREAM))
.header("Content-Length", String.valueOf(holder.getReadByte()))
.header("Accept-Ranges", "bytes")
.header(
"Content-Range",
String.format(
"bytes %d-%d/%d",
holder.getStartByte(),
holder.getEndByte(),
holder.getFullSizeByte()))
.body(Mono.just(holder.getArray()), byte[].class);
}
}
ResourceHolder:
@Data
public class ResourceHolder {
private final Resource resource;
private Long fullSizeByte;
private Long skipByte;
private Long startByte;
private Long endByte;
private Long readByte;
private byte[] array;
private static final String EXTENSION = ".mp4";
private static final String MAIN_DIR = "video";
@SneakyThrows
public ResourceHolder(ServerRequest request) {
resource = new ClassPathResource(getResourceName(request));
assert resource.exists();
initCounters(request);
readContent();
}
private void initCounters(ServerRequest request) {
String[] rangesFromHeader = getRangeFromHeader(request);
startByte = Long.valueOf(rangesFromHeader[0]);
skipByte = startByte;
endByte = rangesFromHeader.length == 2 ? Long.valueOf(rangesFromHeader[1]) : null;
}
private String getResourceName(ServerRequest request) {
return Optional.of(request.pathVariable("videoDir"))
.filter(dir -> !dir.isBlank())
.map(dir -> getFullFileName(dir, request))
.filter(this::isMatch)
.orElseThrow();
}
private boolean isMatch(String pathToFile) {
return Pattern.compile("video.\d..+\.mp4").matcher(pathToFile).matches();
}
private String getFullFileName(String dir, ServerRequest request) {
return MAIN_DIR +
File.separator +
dir +
File.separator +
request.pathVariable("videoName") +
EXTENSION;
}
@SneakyThrows
private String[] getRangeFromHeader(ServerRequest request) {
return request.headers().header("Range").stream()
.findFirst()
.orElseThrow()
.replace("bytes=", "")
.split("-");
}
@SneakyThrows
private void readContent() {
try (InputStream is = resource.getInputStream()) {
fullSizeByte = (long) is.available();
checkRangeAndInitArray();
assert startByte == is.skip(skipByte);
readByte = (long) is.read(array, 0, array.length);
}
}
private void checkRangeAndInitArray() {
Optional.ofNullable(endByte)
.ifPresentOrElse(
end -> array = new byte[(int) (end - startByte + 1)],
() -> {
endByte = fullSizeByte - 1;
array = new byte[(int) (endByte - startByte + 1)];
});
}
}
With ByteBuffer:
@Component
public class VideoHandler {
public Mono<ServerResponse> getRange(ServerRequest request) {
ResourceHolder holder = new ResourceHolder(request);
return ServerResponse.status(HttpStatus.PARTIAL_CONTENT)
.contentType(
MediaTypeFactory.getMediaType(holder.getResource())
.orElse(MediaType.APPLICATION_OCTET_STREAM))
.header("Content-Length", String.valueOf(holder.getReadByte()))
.header("Accept-Ranges", "bytes")
.header(
"Content-Range",
String.format(`your text`
"bytes %d-%d/%d",
holder.getStartByte(),
holder.getEndByte(),
holder.getFullSizeByte()))
.body((outputMessage, context) -> {
Flux<DataBuffer> read = DataBufferUtils.read(
holder.getResource(),
holder.getStartByte(),
outputMessage.bufferFactory(),
10000);
Flux<DataBuffer> dataBufferFlux = DataBufferUtils.takeUntilByteCount(
read,
holder.getEndByte() - holder.getStartByte() + 1);
return outputMessage.writeWith(dataBufferFlux);
});
}
}
Saveliy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.