24 changed files with 641 additions and 77 deletions
@ -0,0 +1,88 @@ |
|||||
|
package org.dromara.business.config; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
/** |
||||
|
* 视频流配置类 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Configuration |
||||
|
@ConfigurationProperties(prefix = "zlm") |
||||
|
public class ZlmConfig { |
||||
|
|
||||
|
/** |
||||
|
* 请求url |
||||
|
*/ |
||||
|
private String apiUrl; |
||||
|
/** |
||||
|
*应用名 |
||||
|
*/ |
||||
|
private String app; |
||||
|
/** |
||||
|
* 流 id |
||||
|
*/ |
||||
|
private String stream; |
||||
|
|
||||
|
/** |
||||
|
* 0 为 hls,1 为 mp4 |
||||
|
*/ |
||||
|
private String type; |
||||
|
/** |
||||
|
* api 操作密钥 |
||||
|
*/ |
||||
|
private String secret; |
||||
|
/** |
||||
|
* 虚拟主机 |
||||
|
*/ |
||||
|
private String vhost; |
||||
|
|
||||
|
/** |
||||
|
* mp4 录像切片时间大小,单位秒,置 0 则采用配置项 |
||||
|
*/ |
||||
|
private Integer maxSecond; |
||||
|
|
||||
|
/** |
||||
|
* 截图需要的url |
||||
|
*/ |
||||
|
private String rtmp; |
||||
|
|
||||
|
/** |
||||
|
* 开始录制url |
||||
|
*/ |
||||
|
private String startRecordUrl; |
||||
|
|
||||
|
/** |
||||
|
* 停止录制url |
||||
|
*/ |
||||
|
private String stopRecordUrl; |
||||
|
|
||||
|
/** |
||||
|
* 录制状态url |
||||
|
*/ |
||||
|
private String isRecordUrl; |
||||
|
|
||||
|
/** |
||||
|
* 设置录像速度url |
||||
|
*/ |
||||
|
private String recordSpeedUrl; |
||||
|
|
||||
|
/** |
||||
|
* 获取截图url |
||||
|
*/ |
||||
|
private String snapUrl; |
||||
|
|
||||
|
/** |
||||
|
* 设置录像流播放位置 |
||||
|
*/ |
||||
|
private String seekRecordStampUrl; |
||||
|
|
||||
|
@Bean |
||||
|
public RestTemplate restTemplate() { |
||||
|
return new RestTemplate(); |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,90 @@ |
|||||
|
package org.dromara.business.controller; |
||||
|
|
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.Operation; |
||||
|
import io.swagger.v3.oas.annotations.Parameter; |
||||
|
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.dromara.business.service.BusinessVideoService; |
||||
|
import org.dromara.common.core.domain.R; |
||||
|
import org.springframework.validation.annotation.Validated; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
/** |
||||
|
* rtmp默认端口1935, rtsp默认端口554 |
||||
|
*/ |
||||
|
@Validated |
||||
|
@RequiredArgsConstructor |
||||
|
@RestController |
||||
|
@RequestMapping("/video") |
||||
|
@Tag(name = "ZLMediaKit视频流相关操作") |
||||
|
public class BusinessVideoController { |
||||
|
|
||||
|
private final BusinessVideoService businessVideoService; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 开始录制 |
||||
|
*/ |
||||
|
@Operation(summary ="开始录制",description = "开始录制") |
||||
|
@GetMapping("/startRecord") |
||||
|
public R<?> startRecording() { |
||||
|
return businessVideoService.startRecording()?R.ok("启动成功!"):R.fail("启动失败!"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 停止录制 |
||||
|
*/ |
||||
|
@Operation(summary ="停止录制",description = "停止录制") |
||||
|
@GetMapping("/stopRecord") |
||||
|
public R<?> stopRecording() { |
||||
|
return businessVideoService.stopRecording()?R.ok("停止成功!"):R.fail("停止失败!"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取录制状态 |
||||
|
*/ |
||||
|
@Operation(summary ="获取录制状态",description = "获取录制状态") |
||||
|
@GetMapping("/isRecording") |
||||
|
public R<?> isRecording() { |
||||
|
return R.ok("获取录制状态!",businessVideoService.isRecording()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取截图 |
||||
|
*/ |
||||
|
@Operation(summary ="获取截图",description = "获取截图",parameters = { |
||||
|
@Parameter(name = "timeoutSec",description = "截图失败超时时间"), |
||||
|
@Parameter(name = "expireSec",description = "截图的过期时间,该时间内产生的截图都会作为缓存返回") |
||||
|
}) |
||||
|
@GetMapping("/getSnap") |
||||
|
public R<?> getSnap(@RequestParam("timeoutSec") Integer timeoutSec,@RequestParam("expireSec") Integer expireSec) { |
||||
|
return R.ok("截图成功!",businessVideoService.getSnap(timeoutSec,expireSec)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 设置录像流播放位置(官方提供的接口有问题)暂时不用 |
||||
|
*/ |
||||
|
@Operation(summary ="设置录像流播放位置",description = "设置录像流播放位置",parameters = { |
||||
|
@Parameter(name = "stamp",description = "要设置的录像播放位置") |
||||
|
}) |
||||
|
@GetMapping("/seekRecordStamp") |
||||
|
public R<?> seekRecordStamp(@RequestParam("stamp") Integer stamp) { |
||||
|
return businessVideoService.seekRecordStamp(stamp)?R.ok("设置成功!"):R.fail("设置失败!"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置录像速度(官方提供的接口有问题)暂时不用 |
||||
|
*/ |
||||
|
@Operation(summary ="设置录像速度",description = "设置录像速度",parameters = { |
||||
|
@Parameter(name = "speed",description = "要设置的录像倍速") |
||||
|
}) |
||||
|
@GetMapping("/setRecordSpeed") |
||||
|
public R<?> setRecordSpeed(@RequestParam("speed") Double speed) { |
||||
|
return businessVideoService.setRecordSpeed(speed)?R.ok("获取成功!"):R.fail("获取失败!"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package org.dromara.business.service; |
||||
|
|
||||
|
|
||||
|
public interface BusinessVideoService { |
||||
|
Boolean startRecording(); |
||||
|
|
||||
|
Boolean stopRecording(); |
||||
|
|
||||
|
String isRecording(); |
||||
|
|
||||
|
Boolean setRecordSpeed(Double speed); |
||||
|
|
||||
|
String getSnap(Integer timeoutSec, Integer expireSec); |
||||
|
|
||||
|
Boolean seekRecordStamp(Integer stamp); |
||||
|
} |
@ -0,0 +1,188 @@ |
|||||
|
package org.dromara.business.service.impl; |
||||
|
|
||||
|
import cn.hutool.core.util.ObjectUtil; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.dromara.business.config.ZlmConfig; |
||||
|
import org.dromara.business.service.BusinessVideoService; |
||||
|
import org.springframework.http.ResponseEntity; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
import java.net.URLEncoder; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
@RequiredArgsConstructor |
||||
|
public class BusinessVideoServiceImpl implements BusinessVideoService { |
||||
|
|
||||
|
private final ZlmConfig zlmConfig; |
||||
|
private static final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
private final RestTemplate restTemplate; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public Boolean startRecording() { |
||||
|
|
||||
|
String url = String.format("%s%s?app=%s&stream=%s&type=%s&vhost=%s&secret=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getStartRecordUrl(), |
||||
|
zlmConfig.getApp(), |
||||
|
zlmConfig.getStream(), |
||||
|
zlmConfig.getType(), |
||||
|
zlmConfig.getVhost(), |
||||
|
zlmConfig.getSecret()); |
||||
|
|
||||
|
//是否需要切片视频
|
||||
|
if (ObjectUtil.isNotEmpty(zlmConfig.getMaxSecond())){ |
||||
|
url = url + "&max_second=" + zlmConfig.getMaxSecond(); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
Map<String, Object> result = objectMapper.readValue(response.getBody(), Map.class); |
||||
|
log.info("录制启动成功: {}", result); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Boolean stopRecording() { |
||||
|
String url = String.format("%s%s?app=%s&stream=%s&type=%s&vhost=%s&secret=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getStopRecordUrl(), |
||||
|
zlmConfig.getApp(), |
||||
|
zlmConfig.getStream(), |
||||
|
zlmConfig.getType(), |
||||
|
zlmConfig.getVhost(), |
||||
|
zlmConfig.getSecret()); |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
Map<String, Object> result = objectMapper.readValue(response.getBody(), Map.class); |
||||
|
log.info("录制停止成功: {}", result); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String isRecording() { |
||||
|
String result = null; |
||||
|
|
||||
|
String url = String.format("%s%s?app=%s&stream=%s&type=%s&vhost=%s&secret=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getIsRecordUrl(), |
||||
|
zlmConfig.getApp(), |
||||
|
zlmConfig.getStream(), |
||||
|
zlmConfig.getType(), |
||||
|
zlmConfig.getVhost(), |
||||
|
zlmConfig.getSecret()); |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
Map<String, Object> resultMap = objectMapper.readValue(response.getBody(), Map.class); |
||||
|
result = resultMap.get("status") + ""; |
||||
|
log.info("获取录制状态成功: {}", resultMap); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Boolean setRecordSpeed(Double speed) { |
||||
|
String url = String.format("%s%s?app=%s&stream=%s&type=%s&vhost=%s&secret=%s&speed=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getRecordSpeedUrl(), |
||||
|
zlmConfig.getApp(), |
||||
|
zlmConfig.getStream(), |
||||
|
zlmConfig.getType(), |
||||
|
zlmConfig.getVhost(), |
||||
|
zlmConfig.getSecret(), |
||||
|
speed); |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
Map<String, Object> result = objectMapper.readValue(response.getBody(), Map.class); |
||||
|
log.info("设置录制速度成功: {}", result); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getSnap(Integer timeoutSec, Integer expireSec) { |
||||
|
String url = String.format("rtmp://%s/%s/%s",zlmConfig.getRtmp(),zlmConfig.getApp(),zlmConfig.getStream()); |
||||
|
String apiUrl = String.format("%s%s?secret=%s&url=%s&timeout_sec=%s&expire_sec=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getSnapUrl(), |
||||
|
zlmConfig.getSecret(), |
||||
|
url, |
||||
|
timeoutSec, |
||||
|
expireSec); |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(apiUrl, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
log.info("获取截图成功: {}", response.getBody()); |
||||
|
} |
||||
|
|
||||
|
return response.getBody(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public Boolean seekRecordStamp(Integer stamp) { |
||||
|
String url = String.format("%s%s?app=%s&stream=%s&vhost=%s&secret=%s&stamp=%s", |
||||
|
zlmConfig.getApiUrl(), |
||||
|
zlmConfig.getSeekRecordStampUrl(), |
||||
|
zlmConfig.getApp(), |
||||
|
zlmConfig.getStream(), |
||||
|
zlmConfig.getVhost(), |
||||
|
zlmConfig.getSecret(), |
||||
|
stamp); |
||||
|
|
||||
|
try { |
||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); |
||||
|
if (response.getStatusCode().is2xxSuccessful()) { |
||||
|
Map<String, Object> result = objectMapper.readValue(response.getBody(), Map.class); |
||||
|
log.info("设置录像流播放位置成功: {}", result); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("请求异常: {}", e.getMessage(), e); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue