Search

#영상 스트리밍06 - 영상 플레이

영상 요청시 동작하는 프로세스

0. 사용자 구독상태 체크, 확인후 사용자 정보 반환 1. 사용자정보를 검증(userId, userKey, contentId) 2. 인코딩 서버에서 저장된 userKey와 비교해서 일치할 경우 라이센스 키를 발급 3. Shaka Player에 발급받은 라이센스 키로 drm setting을 하고 다시 S3서버에 mpd파일요청 4. S3서버 mpd 파일 반환 5. Shaka Player는 영상을 복호화하여 재생
JavaScript
복사

사용자 구독 상태 체크

재생버튼을 누르면 해당 구독 체크를 한다.

영상 재생을 누르면
영상정보는 동적으로 전달된다.
// 비디오 재생 버튼 클릭 이벤트 리스너 추가 document.querySelector('.playButton').addEventListener('click', function (event) { event.preventDefault(); // 앵커 태그의 기본 동작을 막습니다. const contentId = this.dataset.contentid; const filename = this.dataset.filename; // 여기서 구독 체크를 하고 사용자의 정보를 반환한다. fetch(`http://localhost:8080/api/check-subscription/${contentId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.response.isSubscribed) { if (filename) { const s3BaseUrl = 'https://ohflix-bucket.s3.ap-northeast-2.amazonaws.com/'; const videoUrl = `${s3BaseUrl}${filename}`; // 반환받은 사용자 정보로 라이센스 키와 영상을 요청한다. loadEncryptedVideo(data.response); } else { alert('동영상이 아직 업로드 되지 않았습니다.'); } } else { const userConfirmed = confirm('구독 후에 이용할 수 있습니다. 결제 페이지로 이동하시겠습니까?'); if (userConfirmed) { window.location.href = '/api/paymethod-form'; // 결제 페이지 URL로 변경 } } }) .catch(error => { console.error('Error:', error); alert('서버와 통신 중 오류가 발생했습니다.'); }); }); }) .catch(error => { modalData.innerText = 'Error loading data'; console.error('Error:', error); });
JavaScript
복사

구독 체크 메소드 /api/check-subscription

package com.project.ohflix.domain.user; import com.project.ohflix._core.utils.ApiUtil; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController public class UserApiController { private final UserService userService; // 구독 여부체크 @GetMapping("/api/check-subscription/{contentId}") public ResponseEntity<?> checkSubscription(@PathVariable Integer contentId, HttpSession session) { SessionUser sessionUser = (SessionUser) session.getAttribute("sessionUser"); SessionUser sessionAdmin = (SessionUser) session.getAttribute("sessionAdmin"); Integer userId = null; if (sessionAdmin != null) { userId = sessionAdmin.getId(); } if (sessionUser != null) { userId = sessionUser.getId(); } // 회색 로직은 여기서 중요하지 않으니 일단 색을 빼겠다. // 구독 체크에는 사용자 ID랑 영상 ID만 넘어가면 된다. UserResponse.IsSubscribed respDTO = userService.checkSubscription(userId, contentId); return ResponseEntity.ok(new ApiUtil<>(respDTO)); }
JavaScript
복사
구독 체크시 사용자의 ID는 세션으로 부터 가져오고, 영상 정보는 쿼리스트링으로 받아서 구독 체크 로직에 전달 한다.

구독 체크 로직 userService.checkSubscription

// 사용자 구독 여부 체크 public UserResponse.IsSubscribed checkSubscription(Integer userId, Integer contentId) { User user = userRepository.findById(userId) .orElseThrow(() -> new Exception404("사용자를 찾을 수 없습니다.")); Content content = contentRepository.findById(contentId).orElseThrow(() -> new Exception404("영상을 찾을 수 없습니다.")); return new UserResponse.IsSubscribed(user, content); }
JavaScript
복사
그냥 영속화된 사용자와 영상을 찾아서 DTO로 가공을 한다.

구독 체크 응답 DTO

public class UserResponse { @Data public static class IsSubscribed { private Integer userId; private Integer contentId; private String userKey; private String videoPath; private Boolean isSubscribed; public IsSubscribed(User user, Content content) { this.userId = user.getId(); this.contentId = content.getId(); this.userKey = user.getUserKey(); this.videoPath = content.getVideoPath(); this.isSubscribed = user.getIsSubscribe(); } } }
JavaScript
복사
이중 userId, contentId, userKey는 라이센스서버에서 검증할 때 필요하고, VideoPath는 S3에 mpd 파일 요청할 때 필요하고, isSubscription은 구독여부 체크가 확인이 되어야지 이 모든 로직을 진행할 것이기 때문에 필요하다.

사용자 정보 검증

구독 체크가 확인이 되면 사용자 정보를 검증하는 요청을 한다.

사용자 검증 & 영상 요청 전체 코드

사용자 검증 요청

비디오 서버의 /get-license 메소드를 호출한다. 필요한 요청 데이터는 userId, userKey, contentId 이다. 여기서 사용자가 구독하면서 발급받은 userKey를 검증하고, 확인이 되면 라이센스를 발급한다.

라이센스 서버 : 사용자 검증

// 사용자 검증후 라이센스 발급 @PostMapping("/get-license") public ResponseEntity<?> getVideo(@RequestBody @Valid KeyRequest.GetVideoDTO reqDTO, Error error) throws Exception { KeyResponse.LicenseKey respDTO = keyService.getLisence(reqDTO); return ResponseEntity.ok(new ApiUtil<>(respDTO)); }
JavaScript
복사
POST 요청으로 사용자 검증 요청을 한다.
@RequiredArgsConstructor @Service public class KeyService { private final KeyRepository keyRepository; @Value("${content.contentKey}") private String contentKey; @Value("${content.keyId}") private String keyId; public KeyResponse.LicenseKey getLisence(KeyRequest.GetVideoDTO reqDTO) { Key storedKey = keyRepository.findById(reqDTO.getUserId()).orElse(null); if (storedKey != null && storedKey.getUserKey().equals(reqDTO.getUserKey())){ return new KeyResponse.LicenseKey(keyId, contentKey); } else { throw new RuntimeException("Invalid user key"); } }
JavaScript
복사
userKey 검증에 통과하면 KeyId와 ContentId를 발급받는다. 여기서 이전 블로그에 설명했던것 처럼 KeyId와 ContentId는 환경변수로 불러왔다. 잘 기억안나면 한번 더 보고 와주시면 좋을 것 같다. #008 #영상 스트리밍03 - 인코딩 서버 로직02
public class KeyResponse { @Data public static class LicenseKey { private String keyId; private String contentKey; public LicenseKey(String keyId, String contentKey) { this.keyId = keyId; this.contentKey = contentKey; } }
JavaScript
복사
응답 DTO는 KeyId와 contentKey로 구성되어있다.

암호화 비디오 재생

위에 전체코드에서 나와있지만 여기서 좀 더 자세히 설명하자면, 발급 받은 라이센스키는 data 라는 변수에 저장이 되었다. 그리고 사용자가 직접 drm을 구성하였다면, clearKeys라는 변수에 라이센스를 매핑해서 사용해야 하는데, 로드된 shaka 플레이어의 .configureclearKeys를 매핑하면 된다. load함수에 암호화된 mpd파일을 url로 걸어주면 플레이어가 알아서 다운받아 영상을 복호화 하고 플레이를 한다.

영상 재생

영상이 화질별로 세그먼트화 해서 호출이 되는 것을 볼 수 가 있다. 영상 비율은 자바스크립트로 조절하며 될 것 같다.