매일 조금씩

[스프링] HTTP Range Requests로 비디오 스트리밍 만들기 본문

Spring Framework

[스프링] HTTP Range Requests로 비디오 스트리밍 만들기

mezo 2022. 3. 4. 17:43
728x90
반응형

동영상 업로드, 조회 기능을 구현하다보니 

업로드한 동영상을 조회하면 그 동영상을 모두 다운로드한 후에 플레이 할 수 있어서..

길이가 긴 동영상의 경우 다 다운로드 될 때 까지 기다려야했다. 

 

이 문제를 해결하기 위한 방법이 각종 동영상 사이트에서 이용되는 스트리밍 서비스이다. 

동영상을 조회하면 내가 지정한 range 만큼 request를 계속 날려서 range 만큼 동영상을 다운로드해 오고, 

그동안 로드 된부분은 플레이 할수 있다. 

 

이 기능은 HTTP range requests를 활용하면 쉽게 만들수 있다. 

 

 


HTTP  Range Requests

HTTP 범위 요청이란 HTTP를 통해 일정한 부분을 서버에서 클라이언트로 보내는 것을 허락하는 것이다.

범위를 알 수 있는 대형 미디어 파일을 나누어서 읽을 수 있다. 파일 다운로드 중 일시정지와 다시 시작 기능에 유용한 점을 이용해 클라이언트에선 미디어파일을 재생, 일시정지, 다시시작을 가능하게 만들 수 있다. 

예를 들어 4.5mb짜리 파일을 받겠다 하면, 서버는 전체 범위(Content-Length)와 일정 범위에 해당하는 파일을 bytes로 내려보내준다. 

 

[참고사이트]

https://developer.mozilla.org/ko/docs/Web/HTTP/Range_requests

 

HTTP range requests - HTTP | MDN

HTTP 범위 요청은 HTTP의 일정 부분만 서버에서 클라이언트로 보내도록 허락하는 것입니다. 부분 요청은 예를들어 대형 미디어나 파일 다운로드 도중 일시정지와 다시 시작 기능에 유용합니다.

developer.mozilla.org

 

 

Spring Http Range Request 처리

> controller

@GetMapping(value = "/{document_id}/mp4")
public ResponseEntity<ResourceRegion> getMp4ById(@RequestHeader HttpHeaders headers, @PathVariable("document_id") long id) 
	throws IOException {
    DirectoryUtil directoryUtil = new DirectoryUtil(id);
    UrlResource video = new UrlResource("file:" + Paths.get(directoryUtil.getDirectoryPath(), docService.getNameById(id).getFileName())
        .toAbsolutePath());
    ResourceRegion resourceRegion = docService.getMp4ResourceRegion(video, headers);
    return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).contentType(MediaTypeFactory.getMediaType(video).orElse(
        MediaType.APPLICATION_OCTET_STREAM)).body(resourceRegion);
}

코드를 살펴보면

  • UrlResource : 특정 URL로부터 정보를 읽어온다. 여기선 video 파일 url을 통해서 video 정보를 가져온다. 
  • ResourceRegion : 파일 객체의 Range 범위 만큼을 가져올수 있는 스프링 코어 객체다. 여기선 service의 getMp4ResourceRegion메서드를 통해 video 파일을 Range 범위 만큼 가져온다. 
  • ResponseEntity<ResourceRegion> : HttpRequest에 대한 응답 데이터인 ResourceRegion를 담는다. 
  • MediaTypeFactory : 파일의 content-type의 유형을 가져오거나, 기본값으로 8 bit 스트림 유형인 APPLICATION_OCTET_STREAM 을 리턴한다. 여기서 body에 ResouceRegion 객체를 담아 보낸다. 

[UrlResource 란?]

UrlResource 는 java.net.URL을 포장한다. 그리고 파일, HTTP target, FTP target 등등과 같은 URL을 통해 대게 접근가능한 객체에 접근하기 위해 사용된다. 모든 URL은 적절히 표준화된 접두사가 하나의 URL타입대 다른것을 표시하기 위해 사용되는 것처럼 표준적인 String 표현을 가진다. 이는 파일시스템 경로에 접근하기 위한 file:, HTTP프로토콜을 통해 자원에 접근하기 위한 http:, FTP를 통해 자원에 접근하기 위한 ftp: 등을 포함한다. 

 

[ResponseEntity 란?]

HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity 클래스이다. ResponseEntity는 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다. 따라서 HttpStatus, HttpHeaders, HttpBody를 포함한다. 

 

 

> service

@Override
public ResourceRegion getMp4ResourceRegion(UrlResource video, HttpHeaders headers)
  throws IOException {
    final long chunkSize = 1000000L;
    long contentLength = video.contentLength();
    Optional<HttpRange> optional = headers.getRange().stream().findFirst();
    HttpRange httpRange;

    if (optional.isPresent()) {			// optional값이 있을 때 (isPresent는 isEmpty의 반대)
      httpRange = optional.get();
      long start = httpRange.getRangeStart(contentLength);
      long end = httpRange.getRangeEnd(contentLength);
      long rangeLength = Long.min(chunkSize, end - start + 1);	
      return new ResourceRegion(video, start, rangeLength);
    } else {							// optional 값이 없을 때 즉, 맨 첫부분
      long rangeLength = Long.min(chunkSize, contentLength);	// range 길이가 영상길이보다 길어지지 않게
      return new ResourceRegion(video, 0, rangeLength);
    }
}

처음 요청(else 구문)은 chunk 사이즈로 자른 '시작값부터 청크 사이즈 만큼'의 ResourceRegion을 return 하고 그 다음 요청은 header에 담긴 range 범위만큼 잘라서 보낸다. 

여기서 optional변수는 Optional클래스를 사용했다. Optional 클래스는 null인 값이 들어와도 null 예외가 발생하지 않고 값을 담을 수 있게 한다. 따라서 video의 처음 시작 부분일 경우 optional은 null이 되고 else구문을 실행하게 된다. 

그다음 요청들은 이전 요청에서 보낸 구간의 끝을 start로 잡아 if문을 계속 실행한다. 

728x90
반응형