You've already forked FrameTour-BE
feat(video): 添加移动端HLS视频流播放列表生成功能
- 实现AppVideoStreamController提供HLS播放列表生成接口 - 添加HlsStreamRequest和HlsStreamResponse数据传输对象 - 实现HlsStreamService服务类处理视频流逻辑 - 支持生成JSON格式和m3u8文件格式的播放列表 - 提供视频片段查询和设备视频HLS播放列表生成功能 - 支持EVENT和VOD两种播放列表类型 - 集成设备存储操作器获取视频文件列表 - 实现播放列表内容构建和视频片段时长计算功能
This commit is contained in:
@@ -0,0 +1,150 @@
|
|||||||
|
package com.ycwl.basic.controller.mobile.manage;
|
||||||
|
|
||||||
|
import com.ycwl.basic.annotation.IgnoreToken;
|
||||||
|
import com.ycwl.basic.model.mobile.video.dto.HlsStreamRequest;
|
||||||
|
import com.ycwl.basic.model.mobile.video.dto.HlsStreamResponse;
|
||||||
|
import com.ycwl.basic.service.mobile.HlsStreamService;
|
||||||
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动端视频流控制器
|
||||||
|
* 提供HLS视频流播放列表生成功能
|
||||||
|
*
|
||||||
|
* @author Claude Code
|
||||||
|
* @date 2025-12-26
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/mobile/video-stream/v1")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AppVideoStreamController {
|
||||||
|
|
||||||
|
private final HlsStreamService hlsStreamService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成设备视频的HLS播放列表(JSON格式)
|
||||||
|
* 返回包含m3u8内容和视频片段信息的JSON对象
|
||||||
|
*
|
||||||
|
* @param request HLS流请求参数
|
||||||
|
* @return HLS播放列表响应
|
||||||
|
*/
|
||||||
|
@PostMapping("/hls/playlist")
|
||||||
|
public ApiResponse<HlsStreamResponse> generateHlsPlaylist(@Validated @RequestBody HlsStreamRequest request) {
|
||||||
|
log.info("收到HLS播放列表生成请求: deviceId={}, durationMinutes={}",
|
||||||
|
request.getDeviceId(), request.getDurationMinutes());
|
||||||
|
|
||||||
|
try {
|
||||||
|
HlsStreamResponse response = hlsStreamService.generateHlsPlaylist(request);
|
||||||
|
log.info("HLS播放列表生成成功: deviceId={}, segmentCount={}, totalDuration={}s",
|
||||||
|
response.getDeviceId(), response.getSegmentCount(), response.getTotalDurationSeconds());
|
||||||
|
|
||||||
|
return ApiResponse.success(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成HLS播放列表失败: deviceId={}", request.getDeviceId(), e);
|
||||||
|
return ApiResponse.buildResponse(500, null, "生成失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成设备视频的HLS播放列表(m3u8文件格式)
|
||||||
|
* 直接返回m3u8文件内容,可被视频播放器直接使用
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param durationMinutes 视频时长(分钟),默认2分钟
|
||||||
|
* @param eventPlaylist 是否为Event播放列表,默认true
|
||||||
|
* @param response HTTP响应对象
|
||||||
|
*/
|
||||||
|
@GetMapping("/hls/playlist.m3u8")
|
||||||
|
@IgnoreToken
|
||||||
|
public void generateHlsPlaylistFile(
|
||||||
|
@RequestParam Long deviceId,
|
||||||
|
@RequestParam(defaultValue = "2") Integer durationMinutes,
|
||||||
|
@RequestParam(defaultValue = "true") Boolean eventPlaylist,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
|
||||||
|
log.info("收到m3u8文件生成请求: deviceId={}, durationMinutes={}",
|
||||||
|
deviceId, durationMinutes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建请求参数
|
||||||
|
HlsStreamRequest request = new HlsStreamRequest();
|
||||||
|
request.setDeviceId(deviceId);
|
||||||
|
request.setDurationMinutes(durationMinutes);
|
||||||
|
request.setEventPlaylist(eventPlaylist);
|
||||||
|
|
||||||
|
// 生成播放列表
|
||||||
|
HlsStreamResponse hlsResponse = hlsStreamService.generateHlsPlaylist(request);
|
||||||
|
|
||||||
|
log.info("m3u8文件生成成功: deviceId={}, segmentCount={}, totalDuration={}s",
|
||||||
|
deviceId, hlsResponse.getSegmentCount(), hlsResponse.getTotalDurationSeconds());
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
response.setContentType("application/vnd.apple.mpegurl");
|
||||||
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
|
response.setHeader("Content-Disposition", "inline; filename=\"playlist.m3u8\"");
|
||||||
|
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
response.setHeader("Pragma", "no-cache");
|
||||||
|
response.setHeader("Expires", "0");
|
||||||
|
|
||||||
|
// 写入m3u8内容
|
||||||
|
response.getWriter().write(hlsResponse.getPlaylistContent());
|
||||||
|
response.getWriter().flush();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成m3u8文件失败: deviceId={}", deviceId, e);
|
||||||
|
try {
|
||||||
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
|
response.getWriter().write("{\"code\":500,\"message\":\"生成失败: " + e.getMessage() + "\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
log.error("写入错误响应失败", ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备最近的视频片段信息
|
||||||
|
* 仅返回视频片段列表,不包含m3u8内容
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param durationMinutes 视频时长(分钟),默认2分钟
|
||||||
|
* @return 视频片段列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/segments")
|
||||||
|
public ApiResponse<HlsStreamResponse> getVideoSegments(
|
||||||
|
@RequestParam Long deviceId,
|
||||||
|
@RequestParam(defaultValue = "2") Integer durationMinutes) {
|
||||||
|
|
||||||
|
log.info("收到视频片段查询请求: deviceId={}, durationMinutes={}",
|
||||||
|
deviceId, durationMinutes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
HlsStreamRequest request = new HlsStreamRequest();
|
||||||
|
request.setDeviceId(deviceId);
|
||||||
|
request.setDurationMinutes(durationMinutes);
|
||||||
|
request.setEventPlaylist(true);
|
||||||
|
|
||||||
|
HlsStreamResponse response = hlsStreamService.generateHlsPlaylist(request);
|
||||||
|
log.info("视频片段查询成功: deviceId={}, segmentCount={}",
|
||||||
|
deviceId, response.getSegmentCount());
|
||||||
|
|
||||||
|
return ApiResponse.success(response);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询视频片段失败: deviceId={}", deviceId, e);
|
||||||
|
return ApiResponse.buildResponse(500, null, "查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ycwl.basic.model.mobile.video.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLS视频流请求参数
|
||||||
|
* 用于生成设备视频的HLS播放列表
|
||||||
|
*
|
||||||
|
* @author Claude Code
|
||||||
|
* @date 2025-12-26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class HlsStreamRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "设备ID不能为空")
|
||||||
|
private Long deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频时长(分钟),默认2分钟
|
||||||
|
* 获取最近N分钟的视频
|
||||||
|
*/
|
||||||
|
private Integer durationMinutes = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为Event播放列表
|
||||||
|
* true: 使用EVENT类型(适合固定时长的视频回放)
|
||||||
|
* false: 使用VOD类型(适合点播)
|
||||||
|
*/
|
||||||
|
private Boolean eventPlaylist = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.ycwl.basic.model.mobile.video.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLS视频流响应
|
||||||
|
* 包含生成的m3u8播放列表内容
|
||||||
|
*
|
||||||
|
* @author Claude Code
|
||||||
|
* @date 2025-12-26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class HlsStreamResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备ID
|
||||||
|
*/
|
||||||
|
private Long deviceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* m3u8播放列表内容
|
||||||
|
*/
|
||||||
|
private String playlistContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频片段数量
|
||||||
|
*/
|
||||||
|
private Integer segmentCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总时长(秒)
|
||||||
|
*/
|
||||||
|
private Double totalDurationSeconds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频片段列表
|
||||||
|
*/
|
||||||
|
private List<VideoSegment> segments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放列表类型(EVENT/VOD)
|
||||||
|
*/
|
||||||
|
private String playlistType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频片段信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class VideoSegment {
|
||||||
|
/**
|
||||||
|
* 视频片段URL
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 片段时长(秒)
|
||||||
|
*/
|
||||||
|
private Double duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 片段序号
|
||||||
|
*/
|
||||||
|
private Integer sequence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 片段开始时间
|
||||||
|
*/
|
||||||
|
private String startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 片段结束时间
|
||||||
|
*/
|
||||||
|
private String endTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package com.ycwl.basic.service.mobile;
|
||||||
|
|
||||||
|
import com.ycwl.basic.device.DeviceFactory;
|
||||||
|
import com.ycwl.basic.device.entity.common.FileObject;
|
||||||
|
import com.ycwl.basic.device.operator.IDeviceStorageOperator;
|
||||||
|
import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO;
|
||||||
|
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
|
||||||
|
import com.ycwl.basic.model.mobile.video.dto.HlsStreamRequest;
|
||||||
|
import com.ycwl.basic.model.mobile.video.dto.HlsStreamResponse;
|
||||||
|
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
|
||||||
|
import com.ycwl.basic.repository.DeviceRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HLS视频流服务
|
||||||
|
* 用于生成设备视频的HLS播放列表(m3u8)
|
||||||
|
*
|
||||||
|
* @author Claude Code
|
||||||
|
* @date 2025-12-26
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class HlsStreamService {
|
||||||
|
|
||||||
|
private final DeviceIntegrationService deviceIntegrationService;
|
||||||
|
private final DeviceRepository deviceRepository;
|
||||||
|
|
||||||
|
private static final String M3U8_HEADER = "#EXTM3U";
|
||||||
|
private static final String M3U8_VERSION = "#EXT-X-VERSION:3";
|
||||||
|
private static final String M3U8_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE:0";
|
||||||
|
private static final String M3U8_ALLOW_CACHE = "#EXT-X-ALLOW-CACHE:YES";
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成设备视频的HLS播放列表
|
||||||
|
*
|
||||||
|
* @param request HLS流请求参数
|
||||||
|
* @return HLS播放列表响应
|
||||||
|
*/
|
||||||
|
public HlsStreamResponse generateHlsPlaylist(HlsStreamRequest request) {
|
||||||
|
log.info("开始生成HLS播放列表: deviceId={}, durationMinutes={}",
|
||||||
|
request.getDeviceId(), request.getDurationMinutes());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取设备信息
|
||||||
|
DeviceV2DTO device = deviceIntegrationService.getDevice(request.getDeviceId());
|
||||||
|
if (device == null) {
|
||||||
|
throw new RuntimeException("设备不存在: " + request.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备配置
|
||||||
|
DeviceConfigEntity config = deviceRepository.getDeviceConfig(request.getDeviceId());
|
||||||
|
if (config == null) {
|
||||||
|
throw new RuntimeException("设备配置不存在: " + request.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备的存储操作器
|
||||||
|
IDeviceStorageOperator operator = DeviceFactory.getDeviceStorageOperator(device, config);
|
||||||
|
if (operator == null) {
|
||||||
|
throw new RuntimeException("设备未配置存储操作器: " + request.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算时间范围:当前时间向前N分钟
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
Date endDate = calendar.getTime();
|
||||||
|
calendar.add(Calendar.MINUTE, -request.getDurationMinutes());
|
||||||
|
Date startDate = calendar.getTime();
|
||||||
|
|
||||||
|
log.info("查询视频文件范围: deviceId={}, startDate={}, endDate={}",
|
||||||
|
request.getDeviceId(), DATE_FORMAT.format(startDate), DATE_FORMAT.format(endDate));
|
||||||
|
|
||||||
|
// 获取视频文件列表
|
||||||
|
List<FileObject> fileList = operator.getFileListByDtRange(startDate, endDate);
|
||||||
|
if (fileList == null || fileList.isEmpty()) {
|
||||||
|
log.warn("未找到视频文件: deviceId={}, startDate={}, endDate={}",
|
||||||
|
request.getDeviceId(), DATE_FORMAT.format(startDate), DATE_FORMAT.format(endDate));
|
||||||
|
return buildEmptyResponse(request.getDeviceId(), request.getEventPlaylist());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间排序(升序)
|
||||||
|
fileList.sort(Comparator.comparing(FileObject::getCreateTime));
|
||||||
|
|
||||||
|
log.info("找到 {} 个视频文件", fileList.size());
|
||||||
|
|
||||||
|
// 生成播放列表
|
||||||
|
return buildHlsResponse(request.getDeviceId(), fileList, request.getEventPlaylist());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("生成HLS播放列表失败: deviceId={}", request.getDeviceId(), e);
|
||||||
|
throw new RuntimeException("生成HLS播放列表失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建HLS响应
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @param fileList 视频文件列表
|
||||||
|
* @param isEventPlaylist 是否为Event播放列表
|
||||||
|
* @return HLS响应
|
||||||
|
*/
|
||||||
|
private HlsStreamResponse buildHlsResponse(Long deviceId, List<FileObject> fileList, Boolean isEventPlaylist) {
|
||||||
|
StringBuilder m3u8Content = new StringBuilder();
|
||||||
|
List<HlsStreamResponse.VideoSegment> segments = new ArrayList<>();
|
||||||
|
|
||||||
|
// 添加m3u8头部
|
||||||
|
m3u8Content.append(M3U8_HEADER).append("\n");
|
||||||
|
m3u8Content.append(M3U8_VERSION).append("\n");
|
||||||
|
|
||||||
|
// 设置播放列表类型
|
||||||
|
String playlistType = Boolean.TRUE.equals(isEventPlaylist) ? "EVENT" : "VOD";
|
||||||
|
m3u8Content.append("#EXT-X-PLAYLIST-TYPE:").append(playlistType).append("\n");
|
||||||
|
|
||||||
|
// 计算目标时长(使用视频片段的平均时长)
|
||||||
|
double avgDuration = calculateAverageDuration(fileList);
|
||||||
|
int targetDuration = (int) Math.ceil(avgDuration);
|
||||||
|
m3u8Content.append("#EXT-X-TARGETDURATION:").append(targetDuration).append("\n");
|
||||||
|
|
||||||
|
m3u8Content.append(M3U8_MEDIA_SEQUENCE).append("\n");
|
||||||
|
m3u8Content.append(M3U8_ALLOW_CACHE).append("\n");
|
||||||
|
|
||||||
|
// 添加视频片段
|
||||||
|
double totalDuration = 0.0;
|
||||||
|
int sequence = 0;
|
||||||
|
|
||||||
|
for (FileObject file : fileList) {
|
||||||
|
// 计算片段时长(秒)
|
||||||
|
double duration = calculateSegmentDuration(file);
|
||||||
|
totalDuration += duration;
|
||||||
|
|
||||||
|
// 添加片段信息到m3u8
|
||||||
|
m3u8Content.append("#EXTINF:").append(String.format("%.3f", duration)).append(",\n");
|
||||||
|
m3u8Content.append(file.getUrl().replace("-internal.aliyuncs.com", ".aliyuncs.com")).append("\n");
|
||||||
|
|
||||||
|
// 记录片段信息
|
||||||
|
HlsStreamResponse.VideoSegment segment = HlsStreamResponse.VideoSegment.builder()
|
||||||
|
.url(file.getUrl())
|
||||||
|
.duration(duration)
|
||||||
|
.sequence(sequence++)
|
||||||
|
.startTime(file.getCreateTime() != null ? DATE_FORMAT.format(file.getCreateTime()) : null)
|
||||||
|
.endTime(file.getEndTime() != null ? DATE_FORMAT.format(file.getEndTime()) : null)
|
||||||
|
.build();
|
||||||
|
segments.add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加结束标记(VOD类型需要)
|
||||||
|
if (!"EVENT".equals(playlistType)) {
|
||||||
|
m3u8Content.append("#EXT-X-ENDLIST\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HlsStreamResponse.builder()
|
||||||
|
.deviceId(deviceId)
|
||||||
|
.playlistContent(m3u8Content.toString())
|
||||||
|
.segmentCount(fileList.size())
|
||||||
|
.totalDurationSeconds(totalDuration)
|
||||||
|
.segments(segments)
|
||||||
|
.playlistType(playlistType)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建空响应
|
||||||
|
*/
|
||||||
|
private HlsStreamResponse buildEmptyResponse(Long deviceId, Boolean isEventPlaylist) {
|
||||||
|
String playlistType = Boolean.TRUE.equals(isEventPlaylist) ? "EVENT" : "VOD";
|
||||||
|
StringBuilder m3u8Content = new StringBuilder();
|
||||||
|
|
||||||
|
m3u8Content.append(M3U8_HEADER).append("\n");
|
||||||
|
m3u8Content.append(M3U8_VERSION).append("\n");
|
||||||
|
m3u8Content.append("#EXT-X-PLAYLIST-TYPE:").append(playlistType).append("\n");
|
||||||
|
m3u8Content.append("#EXT-X-TARGETDURATION:0\n");
|
||||||
|
m3u8Content.append(M3U8_MEDIA_SEQUENCE).append("\n");
|
||||||
|
|
||||||
|
if (!"EVENT".equals(playlistType)) {
|
||||||
|
m3u8Content.append("#EXT-X-ENDLIST\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HlsStreamResponse.builder()
|
||||||
|
.deviceId(deviceId)
|
||||||
|
.playlistContent(m3u8Content.toString())
|
||||||
|
.segmentCount(0)
|
||||||
|
.totalDurationSeconds(0.0)
|
||||||
|
.segments(Collections.emptyList())
|
||||||
|
.playlistType(playlistType)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算视频片段的平均时长(秒)
|
||||||
|
*/
|
||||||
|
private double calculateAverageDuration(List<FileObject> fileList) {
|
||||||
|
if (fileList == null || fileList.isEmpty()) {
|
||||||
|
return 10.0; // 默认10秒
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Double> durations = fileList.stream()
|
||||||
|
.map(this::calculateSegmentDuration)
|
||||||
|
.filter(d -> d > 0)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (durations.isEmpty()) {
|
||||||
|
return 10.0; // 默认10秒
|
||||||
|
}
|
||||||
|
|
||||||
|
return durations.stream()
|
||||||
|
.mapToDouble(Double::doubleValue)
|
||||||
|
.average()
|
||||||
|
.orElse(10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算单个视频片段的时长(秒)
|
||||||
|
*/
|
||||||
|
private double calculateSegmentDuration(FileObject file) {
|
||||||
|
if (file.getCreateTime() != null && file.getEndTime() != null) {
|
||||||
|
long durationMs = file.getEndTime().getTime() - file.getCreateTime().getTime();
|
||||||
|
if (durationMs > 0) {
|
||||||
|
return durationMs / 1000.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果无法计算,返回默认值10秒
|
||||||
|
return 10.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user