实现用于Spring Boot的字节服务 [英] Implement Byte serving for Spring Boot

查看:94
本文介绍了实现用于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.

此方法存在两个问题.

  1. 它内部使用 FileInputStream 读取文件.这适用于小文件,但不适用于通过字节范围请求提供服务的大文件. FileInputStream 将从头开始读取文件,并丢弃不需要的内容,直到还原请求的起始偏移量为止.这可能会导致文件变大的情况变慢.

  1. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆