实现用于Spring Boot的字节服务 [英] Implement Byte serving for Spring Boot
问题描述
我想使用Spring Boot Rest API在Angular中实现视频播放器.我可以播放视频,但不能进行视频搜索.每当我使用Chrome或Edge时,视频一遍又一遍地播放.
I want to implement video Player in Angular using Spring Boot Rest API. I can play the video but I can't make video seeking. Every time the video starts over and over again when I use Chrome or Edge.
我尝试了此端点:
@RequestMapping(value = "/play_video/{video_id}", method = RequestMethod.GET)
@ResponseBody public ResponseEntity<byte[]> getPreview1(@PathVariable("video_id") String video_id, HttpServletResponse response) {
ResponseEntity<byte[]> result = null;
try {
String file = "/opt/videos/" + video_id + ".mp4";
Path path = Paths.get(file);
byte[] image = Files.readAllBytes(path);
response.setStatus(HttpStatus.OK.value());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(image.length);
result = new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
} catch (java.nio.file.NoSuchFileException e) {
response.setStatus(HttpStatus.NOT_FOUND.value());
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
return result;
}
我发现这篇文章提供了一些建议:如何在Spring MVC中实现HTTP字节范围请求
I found this post which gives some ides: How to Implement HTTP byte-range requests in Spring MVC
但是目前无法正常工作.当我尝试移动位置时,视频又从头开始播放.
But currently it's not working. Video is again playing from the start when I try to shift the position.
我使用此播放器: https://github.com/smnbbrv/ngx-plyr
我以这种方式配置它:
<div class="media">
<div
class="class-video mr-3 mb-1"
plyr
[plyrPlaysInline]="true"
[plyrSources]="gymClass.video"
(plyrInit)="player = $event"
(plyrPlay)="played($event)">
</div>
<div class="media-body">
{{ gymClass.description }}
</div>
</div>
您知道如何解决此问题吗?
Do you know how I can fix this issue?
推荐答案
第一个解决方案:使用 FileSystemResource
FileSystemResource在内部处理字节范围标头支持,并读写适当的标头.
First solution: Using FileSystemResource
FileSystemResource internally handles byte-range header support, reading and writing the appropriate headers.
此方法存在两个问题.
-
它内部使用
FileInputStream
读取文件.这适用于小文件,但不适用于通过字节范围请求提供服务的大文件.FileInputStream
将从头开始读取文件,并丢弃不需要的内容,直到还原请求的起始偏移量为止.这可能会导致文件变大的情况变慢.
It uses
FileInputStream
internally for reading files. This is fine for small files, but not for large files served through byte-range requests.FileInputStream
will read the file from the beginning and discard the not needed content until it reches the requested start offset. This can cause slowdowns with larger files.
它将"application/json"
设置为"Content-Type"
响应标头.因此,我提供了自己的"Content-Type"
标头.查看此线程
It sets "application/json"
as the "Content-Type"
response header. So, I provide my own "Content-Type"
header. See this thread
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Stream3 {
@GetMapping(value = "/play_video/{video_id}")
@ResponseBody
public ResponseEntity<FileSystemResource> stream(@PathVariable("video_id") String video_id) {
String filePathString = "/opt/videos/" + video_id + ".mp4";
final HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "video/mp4");
return new ResponseEntity<>(new FileSystemResource(filePathString), responseHeaders, HttpStatus.OK);
}
}
第二个解决方案:使用 HttpServletResponse
和 RandomAccessFile
使用 RandomAccessFile
,您可以实现对字节范围请求的支持.与 FileInputStream
相比,优点是您不需要每次有新的范围请求时都从头开始读取文件,从而使此方法也可用于较大的文件. RandomAccessFile
有一个名为 seek(long)
的方法,该方法调用C方法 fseek()
,该方法将文件的指针直接移动到所请求的位置偏移量.
Second solution: Using HttpServletResponse
and RandomAccessFile
With RandomAccessFile
you can implement support for byte-range requests. The advantage over FileInputStream
, is that you don't need to read the file from the beginning every time there is a new range request, making this method usable also for larger files. RandomAccessFile
has a method called seek(long)
which calls the C method fseek()
, which directly moves the pointer for the file to the requested offset.
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Stream {
@GetMapping(value = "/play_video/{video_id}")
@ResponseBody
public void stream(
@PathVariable("video_id") String video_id,
@RequestHeader(value = "Range", required = false) String rangeHeader,
HttpServletResponse response) {
try {
OutputStream os = response.getOutputStream();
long rangeStart = 0;
long rangeEnd;
String filePathString = "/opt/videos/" + video_id + ".mp4";
Path filePath = Paths.get(filePathString);
Long fileSize = Files.size(filePath);
byte[] buffer = new byte[1024];
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
if (rangeHeader == null) {
response.setHeader("Content-Type", "video/mp4");
response.setHeader("Content-Length", fileSize.toString());
response.setStatus(HttpStatus.OK.value());
long pos = rangeStart;
file.seek(pos);
while (pos < fileSize - 1) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
return;
}
String[] ranges = rangeHeader.split("-");
rangeStart = Long.parseLong(ranges[0].substring(6));
if (ranges.length > 1) {
rangeEnd = Long.parseLong(ranges[1]);
} else {
rangeEnd = fileSize - 1;
}
if (fileSize < rangeEnd) {
rangeEnd = fileSize - 1;
}
String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
response.setHeader("Content-Type", "video/mp4");
response.setHeader("Content-Length", contentLength);
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize);
response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
long pos = rangeStart;
file.seek(pos);
while (pos < rangeEnd) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
}
} catch (FileNotFoundException e) {
response.setStatus(HttpStatus.NOT_FOUND.value());
} catch (IOException e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
}
第三种解决方案:也使用 RandomAccessFile
,但是使用 StreamingResponseBody
而不是 HttpServletResponse
Third solution: Also using RandomAccessFile
, but StreamingResponseBody
instead of HttpServletResponse
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@Controller
public class Stream2 {
@GetMapping(value = "/play_video/{video_id}")
@ResponseBody
public ResponseEntity<StreamingResponseBody> stream(
@PathVariable("video_id") String video_id,
@RequestHeader(value = "Range", required = false) String rangeHeader) {
try {
StreamingResponseBody responseStream;
String filePathString = "/opt/videos/" + video_id + ".mp4";
Path filePath = Paths.get(filePathString);
Long fileSize = Files.size(filePath);
byte[] buffer = new byte[1024];
final HttpHeaders responseHeaders = new HttpHeaders();
if (rangeHeader == null) {
responseHeaders.add("Content-Type", "video/mp4");
responseHeaders.add("Content-Length", fileSize.toString());
responseStream = os -> {
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
long pos = 0;
file.seek(pos);
while (pos < fileSize - 1) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
} catch (Exception e) {}
};
return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.OK);
}
String[] ranges = rangeHeader.split("-");
Long rangeStart = Long.parseLong(ranges[0].substring(6));
Long rangeEnd;
if (ranges.length > 1) {
rangeEnd = Long.parseLong(ranges[1]);
} else {
rangeEnd = fileSize - 1;
}
if (fileSize < rangeEnd) {
rangeEnd = fileSize - 1;
}
String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
responseHeaders.add("Content-Type", "video/mp4");
responseHeaders.add("Content-Length", contentLength);
responseHeaders.add("Accept-Ranges", "bytes");
responseHeaders.add("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize);
final Long _rangeEnd = rangeEnd;
responseStream = os -> {
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
long pos = rangeStart;
file.seek(pos);
while (pos < _rangeEnd) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
} catch (Exception e) {}
};
return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.PARTIAL_CONTENT);
} catch (FileNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
在您的component.ts中:
In your component.ts:
您可以使用playVideoFile()更改当前显示的视频
You can change the currently displaying video with playVideoFile()
export class AppComponent implements OnInit {
videoSources: Plyr.Source[];
ngOnInit(): void {
const fileName = 'sample';
this.playVideoFile(fileName);
}
playVideoFile(fileName: string) {
this.videoSources = [
{
src: `http://localhost:8080/play_video/${fileName}`,
},
];
}
}
还有html:
<div
#plyr
plyr
[plyrPlaysInline]="false"
[plyrSources]="videoSources"
></div>
这篇关于实现用于Spring Boot的字节服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!