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