package com.guwan.backend.controller; import com.guwan.backend.entity.Video; import com.guwan.backend.service.HdfsStorageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.ResourceRegion; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.io.InputStream; @Slf4j @RestController @RequestMapping("/api/stream") @RequiredArgsConstructor public class VideoStreamController { private final HdfsStorageService hdfsStorageService; private static final long CHUNK_SIZE = 1024 * 1024; // 1MB chunks @GetMapping("/video/{id}") public ResponseEntity streamVideo( @PathVariable Long id, @RequestHeader(value = "Range", required = false) String rangeHeader) { try { // 获取视频路径 Video video = videoMapper.selectById(id); if (video == null) { return ResponseEntity.notFound().build(); } // 获取视频流 InputStream videoStream = hdfsStorageService.getVideoStream(video.getUrl()); InputStreamResource resource = new InputStreamResource(videoStream); // 获取视频元数据 VideoMetadata metadata = hdfsStorageService.getVideoMetadata(video.getUrl()); long contentLength = metadata.getSize(); // 处理Range请求 HttpRange range = rangeHeader == null ? null : HttpRange.parseRanges(rangeHeader).get(0); ResourceRegion region; if (range != null) { long start = range.getRangeStart(contentLength); long end = range.getRangeEnd(contentLength); long rangeLength = Math.min(CHUNK_SIZE, end - start + 1); region = new ResourceRegion(resource, start, rangeLength); } else { long rangeLength = Math.min(CHUNK_SIZE, contentLength); region = new ResourceRegion(resource, 0, rangeLength); } return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT) .contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)) .body(region); } catch (IOException e) { log.error("视频流处理失败", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } }